<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>GQ&#039;s blog &#187; git</title>
	<atom:link href="http://gq.net.ru/tag/git/feed/" rel="self" type="application/rss+xml" />
	<link>http://gq.net.ru</link>
	<description>Next step is the world domination...</description>
	<lastBuildDate>Tue, 06 Jul 2010 13:57:38 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0</generator>
		<item>
		<title>Редактирование истории в git</title>
		<link>http://gq.net.ru/2009/12/16/git-history-rewrite/</link>
		<comments>http://gq.net.ru/2009/12/16/git-history-rewrite/#comments</comments>
		<pubDate>Wed, 16 Dec 2009 13:35:00 +0000</pubDate>
		<dc:creator>GQ</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[git]]></category>
		<category><![CDATA[HOWTO]]></category>
		<category><![CDATA[Linux]]></category>

		<guid isPermaLink="false">http://gq.net.ru/2009/12/16/%d1%80%d0%b5%d0%b4%d0%b0%d0%ba%d1%82%d0%b8%d1%80%d0%be%d0%b2%d0%b0%d0%bd%d0%b8%d0%b5-%d0%b8%d1%81%d1%82%d0%be%d1%80%d0%b8%d0%b8-%d0%b2-git/</guid>
		<description><![CDATA[Еще один внутренний документик по работе с гитом от Максима Чистолинова: Редактирование истории в git Более строго следует говорить не о "редактировании" или "изменении" истории, а о cоздании "альтернативной" истории. Если специально ничего не предпринимать, в репозитории git остаются все объекты "старой" истории, соответствующие предыдущим коммитам и версиям файлов. На эти объекты не будут "ссылаться" [...]]]></description>
			<content:encoded><![CDATA[<p>Еще один внутренний документик по работе с гитом от Максима Чистолинова:</p>
<p><strong>Редактирование истории в git</strong></p>
<pre>Более строго следует говорить не о "редактировании" или "изменении" истории,
а о cоздании "альтернативной" истории. Если специально ничего не предпринимать,
в репозитории git остаются все объекты "старой" истории, соответствующие
предыдущим коммитам и версиям файлов.
На эти объекты не будут "ссылаться" ветки, но если Вы вспомните их SHA1-ключи,
либо как-то специально позаботитесь их "пометить" (тэгом, или другой веткой),
то старая история будет c точки зрения git "ничем не хуже" новой.

Почти во всех командах git можно ссылаться на коммиты любым способом:
 - с помощью SHA1-ключа,
 - с помощью имени ветки (если это последний коммит на ветке),
 - с помощью тэга (если вы его предусмотрительно поставили git tag),
 - c помощью специальных имён, например HEAD - последний коммит на
   данной ветке, HEAD^ - предпоследний (точнее, первый предок
   последнего коммита) и т.п. Подробности см. git-rev-parse --help
Ниже в командах, которые допускают любую идентификацию коммита, я буду
указывать в качестве аргумента &lt;id&gt;, или &lt;id-...&gt;. Если допускается только
имя ветки, указывается &lt;ветка&gt;.

Для начинающих я рекомендую приступая к редактированию истории пометить
все ключевые точки тэгами. Их хорошо видно в gitk.
Только не забудьте их потом удалить git tag -d

В понятие истории git я буду включать не только совокупность коммитов
git-а, но и содержание рабочего каталога (да простят меня потомки).

Типовые задачи редактирования истории:

1. Отказаться от всех изменений в рабочем каталоге (аналог revert в svn).
   Кошерный способ: git checkout -f
   Отказаться от части изменений можно с помощью: git checkout &lt;path&gt;
   НО: git checkout . не удалит, например, вновь добавленных файлов.
   Более жёсткий способ удалить _все_ изменения: git reset --hard HEAD

2. "Сохранить" изменения (состояние) рабочего каталога.
   git stash
   При этом рабочий каталог "очищается" до HEAD, а сохранённые изменения
   можно в последствии "применить" к текущему, либо к любому другому
   состоянию рабочего каталога с помощью git stash apply
   В частности, это позволяет "переносить" изменения между ветками
   (хотя, лучше их оформлять как коммиты, и оперировать потом уже с ними).

3. Отредактировать/дополнить последний коммит:
   git commit --amend
   Можно применять даже если Вам просто понадобилось переписать commit-log
   (например, Вы его "недописали" или он оказался не в той кодировке).
   Фактически при выполнении этой операции будет создан _другой_ commit
   object, и HEAD ветки будет связан с ним. (Старый объект в репозитории
   git тоже сохранится).

4. "Отказаться" от нескольких последних коммитов в истории (в частности,
    от последнего)
   Создать новую ветку new в нужной нам точке истории и переставить на
   неё существующую:
   git checkout &lt;id&gt; -b new
   git branch -M &lt;нужная нам ветка&gt;
   Например, отказаться от последнего коммита на ветке master (если мы
   на нём находимся), можно так:
   git checkout HEAD^ -b new_master
   git branch -M master
   После первой команды мы находимся "на один коммит назад" и создали там
   новую ветку с именем new_master (текущей веткой является new_master).
   После второй команды мы "переименовали" new_master в master, -M позволяет
   проигнорировать, что master уже есть.
   Тоже самое можно сделать одной командой:
   git reset --hard &lt;id&gt;
   Но это менее безопасно (см. ниже).

5. "Переставить" метки веток.
   git reset [--ключ] &lt;id&gt;
   Позволяет "передвинуть" текущий HEAD (и метку ветки) на заданный коммит.
   Есть три варианта, задаваемых ключами:
    --hard - "выкидывает" всё текущее состояние рабочий копии, вы оказываетесь
             на коммите &lt;id&gt;, как будто после него ничего не было;
             Т.е. это просто "перестановка ветки".
    --soft - "сохраняет" изменения в рабочей копии (и в "индексе" git) и добавляет
             к ним изменения из "истории" от &lt;id&gt; до точки, из которой мы переходим.
             Более подробно см. п. "Слияние нескольких коммитов в один".
    --mixed - (по умолчанию) - ведёт себя как --soft, но не изменяет состояние
             "индекса" git (оно будет соответствовать коммиту &lt;id&gt;, на который мы
             перешли) - новые и изменённые файлы не считаются "добавленными" в индекс,
             т.е. в отличии от --soft для них требуется явно делать git add,
             git rm, .etc
   Поскольку git reset (особенно --hard), позволяет "потерять" последнее
   положение ветки (т.е. оставить HEAD "непомеченным"), следует использовать
   эту команду с осторожностью.

6. Слияние нескольких коммитов в один.
   Если это "последние" коммиты в истории этой ветки:
   git reset --soft &lt;id&gt;
   git commit -a -s [--amend]
   Первая команда позволяет "отскочить" HEAD на несколько коммитов назад, при
   этом сохранив все "изменения" этих коммитов в рабочем каталоге.
   Например, git reset --soft HEAD^^ позволит "объединить" изменения последнего
   и предпоследнего коммитов.
   Если мы хотим "добавить" к этим изменениям, изменения из коммитов с другой
   ветки, нам поможет git cherry-pick --no-commit &lt;id&gt;
   Эта команда "добавляет" изменения коммита в рабочий каталог и в индекс, но не
   выполняет операцию commit.

7. Удаление нескольких коммитов "внутри истории". git-rebase magic
   Например, у Вас есть история ветки:
    ...-(N-5)-(N-4)-(N-3)-(N-2)-(N-1)-(N) - ветка
   и вам захотелось удалить коммиты (N-4)-(N-2) включительно.
   Это можно сделать с помощью команды git-rebase:
   git-rebase --onto &lt;ветка&gt;~5 &lt;ветка&gt;~2 &lt;ветка&gt;
   Например, git-rebase --onto master~5 master~2 master
   Нотация &lt;id&gt;~&lt;n&gt; означает n-ый коммит назад, т.е. в данном случае:
    - master - (N)
    - master~2 - (N-2)
    - master~5 - (N-5)
   Смысл операции git-rebase --onto &lt;id-newbase&gt; &lt;id-upstream&gt; &lt;id-head&gt;:
    1) Переключиться на коммит &lt;id-head&gt; (== git checkout &lt;ветка&gt;, если
       &lt;id-head&gt; - это HEAD ветки)
    2) Начать новую ветку от точки &lt;id-newbase&gt;
    3) "Поместить" на новую ветку коммиты от &lt;id-upstream&gt; до &lt;id-head&gt;,
       не включая &lt;id-upstream&gt;
    4) Если &lt;id-head&gt; - это HEAD ветки, переставить &lt;ветку&gt; на то, что получилось
   В данном случае:
   От коммита (N-5) мы начинаем "применять" коммиты (N-1) и (N), и переставляем
   метку ветки, в результате чего получается "новая история":
        (N-1)'-(N)' - ветка
         /
   ...-(N-5)-(N-4)-(N-3)-(N-2)-(N-1)-(N)

8. Объединение коммита с "внутренним" коммитом в истории.
   Например, в коммите &lt;id-src&gt; Вы исправили ошибку в "старом исправлении" &lt;id-dst&gt;,
   которое было несколько коммитов назад.
   Последовательность действий:
   1) Создать новую ветку new_branch от коммита &lt;id-dst&gt;, который надо
      поменять (дополнить).
      git checkout &lt;id-dst&gt; -b new_branch
   2) Сделать cherry-pick коммита &lt;id-src&gt;, который вы хотите "приплюсовать" к
      внутреннему.
      git cherry-pick --no-commit &lt;id-src&gt;
   3) "Дополнить" последний коммит изменениями из рабочего каталога.
      git commit --amend
   4) Добавить в новую историю последовательность "правильных" коммитов:
      git rebase --onto HEAD &lt;id-первый коммит&gt;^  &lt;id-последний коммит&gt;
   5) Переставить ветку на новый HEAD
      git branch -f &lt;имя ветки&gt;

   Пояснения требуют два последних действия:
     git rebase в данном случае добавляет нужную последовательность коммитов
     "в голову" новой ветки, но если &lt;id-последний коммит&gt; - это не HEAD
     старой ветки, то после git rebase новый HEAD не будет соответствовать
     ни какой ветке ! (так уж работает git rebase)
     Для этого требуется последняя операция, она явно переставляет ветку
     на HEAD.

   Если наше исправление было бы не закоммичено, можно было воспользоваться
   git stash и git stash apply вместо git cherry-pick.

9. Редактирование "внутреннего" коммита.
   Действия аналогичны п.8, но проще. Пусть мы находимся на ветке &lt;имя ветки&gt;.
   1) Извлечь коммит &lt;id-dst&gt;, подлежащий редактированию; ветку new_branch
      создавать при этом не обязательно, но желательно:
      git checkout &lt;id-dst&gt; [-b new_branch]
   2) Исправить код, "дополнить" последний коммит изменениями из рабочего
      каталога.
      git commit -a --amend
   3) Добавить в новую историю последовательность "правильных" коммитов:
      git rebase --onto HEAD &lt;id-dst&gt; &lt;имя ветки&gt;
   4) Удалить ветку new_branch, если она была создана на шаге 1)
      git branch -D new_branch

   Специально переставлять ветку &lt;имя ветки&gt; в данном случае не требуется, т.к.
   в команде git rebase в п. 3) в качестве последнего аргумента было имя ветки,
   а не просто SHA1-id. В такой ситуации эта команда "автоматически" переставит
   ref ветки.

10. rebase ветки с помощью git rebase.
    git rebase &lt;upstream-branch&gt;
    Эта операция подробно рассмотрена в разъяснениях Никиты по идеологии и
    сценариям использования git.
    Не следует относится к git rebase "формально": например, если Вы считаете,
    что некоторые коммиты с ветки разумнее было бы переместить на master, можно
    "продублировать" их на master с помощью git cherry-pick, после чего сделать
    git rebase. После этого, с веки эти коммиты волшебным образом исчезнут.

11. "Откат" отдельного коммита.
    Строго говоря, это не редактирование истории: просто автоматически добавляется
    коммит (либо, изменение в рабочей копии), "отменяющее" заданный коммит.
    git revert [--no-commit] &lt;id&gt;
    Эту возможность следует использовать если Вы не хотите "честно" редактировать
    историю. Например, коммит надо откатить только на одной из ветвей, либо
    этот коммит был "очень давно", и не хочется перестраивать из-за него всю
    историю целиком.</pre>
<p>(cc) Mike Chistolinov</p>
]]></content:encoded>
			<wfw:commentRss>http://gq.net.ru/2009/12/16/git-history-rewrite/feed/</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
		<item>
		<title>Басня о гит</title>
		<link>http://gq.net.ru/2009/10/23/the-git-gable/</link>
		<comments>http://gq.net.ru/2009/10/23/the-git-gable/#comments</comments>
		<pubDate>Fri, 23 Oct 2009 07:57:14 +0000</pubDate>
		<dc:creator>GQ</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[git]]></category>
		<category><![CDATA[Linux]]></category>

		<guid isPermaLink="false">http://gq.net.ru/?p=374</guid>
		<description><![CDATA[Эдик Торощин перевел довольно забавную байку. Читать здесь.]]></description>
			<content:encoded><![CDATA[<p>Эдик Торощин перевел довольно забавную байку.<br />
<a href=http://hades.name/blog/2009/05/23/the-git-parable-ru/>Читать здесь.</p>
]]></content:encoded>
			<wfw:commentRss>http://gq.net.ru/2009/10/23/the-git-gable/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Git trac post-receive hook</title>
		<link>http://gq.net.ru/2009/07/15/git-trac-post-receive-hook/</link>
		<comments>http://gq.net.ru/2009/07/15/git-trac-post-receive-hook/#comments</comments>
		<pubDate>Wed, 15 Jul 2009 14:46:42 +0000</pubDate>
		<dc:creator>GQ</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[git]]></category>
		<category><![CDATA[hook]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Trac]]></category>

		<guid isPermaLink="false">http://gq.net.ru/?p=344</guid>
		<description><![CDATA[Добавил тут для одного из проектов хук в репозиторий, который закрывает тикеты по коммит-логам. Неочевидное тут вот что: закрывать тикеты надо тогда, когда коммит попадает на master (такая у нас модель разработки), соответственно надо отлавливать мержи и в случае мержа просматривать смерженные коммиты. Upd: Гит оказался умнее меня и сам (rev-list) выводит коммиты со смерженных [...]]]></description>
			<content:encoded><![CDATA[<p>Добавил тут для одного из проектов хук в репозиторий, который закрывает тикеты по коммит-логам.</p>
<p>Неочевидное тут вот что:<br />
закрывать тикеты надо тогда, когда коммит попадает на master (такая у нас модель разработки), соответственно надо отлавливать мержи и в случае мержа просматривать смерженные коммиты.</p>
<p><b>Upd</b>: Гит оказался умнее меня и сам (rev-list) выводит коммиты со смерженных веток. Поэтому всё намного проще и получившийся скрипт даже и не интересен ни разу.</p>
<p>Скрипт получился такой:<br />
<code>
<pre>#!/bin/sh
# This script is run after receive-pack has accepted a pack and the
# repository has been updated.  It is passed arguments in through stdin
# in the form
#  oldrev newrev refname
# For example:
#  aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master

TRAC_HOOK="/srv/git/hooks/trac-post-commit-hook"
TRAC_DIR="/srv/trac/stand"

procrevs() {
    while read gitrev; do
	"$TRAC_HOOK" -p "$TRAC_DIR" -r "$gitrev"
    done
}

while read OLD NEW NAME;do
	test "$NAME" == "refs/heads/master"||continue
	git rev-list --no-merges "$OLD..$NEW"|procrevs
done
</pre>
<p></code></p>
<p>Где TRAC_HOOK &#8211; это trac-post-commit-hook из дистрибутива trac, TRAC_DIR &#8211; каталог с проектом trac. В самом trac&#8217;е должен быть включен и настроен плагин trac-git.</p>
]]></content:encoded>
			<wfw:commentRss>http://gq.net.ru/2009/07/15/git-trac-post-receive-hook/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Еще немножко про git</title>
		<link>http://gq.net.ru/2008/11/19/some-more-words-about-git/</link>
		<comments>http://gq.net.ru/2008/11/19/some-more-words-about-git/#comments</comments>
		<pubDate>Tue, 18 Nov 2008 20:07:43 +0000</pubDate>
		<dc:creator>GQ</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Программирование]]></category>
		<category><![CDATA[Рабочее]]></category>
		<category><![CDATA[git]]></category>
		<category><![CDATA[Linux]]></category>

		<guid isPermaLink="false">http://gq.net.ru/?p=282</guid>
		<description><![CDATA[Перевели крупнейший наш проект с CVS на git. В связи с этим возникло много непониманий и вопросов о том, как теперь делать правильно. Никита разродился большим описанием предлагаемого workflow: [В данном письме используется ascii-art, так что включите фиксированный шрифт] Прежде чем расписывать сценарии, хотелось бы ещё раз обратить ваше внимание на базовые свойства GIT: *) [...]]]></description>
			<content:encoded><![CDATA[<p>Перевели крупнейший наш проект с CVS на git. В связи с этим возникло много непониманий и вопросов о том, как теперь делать правильно. Никита разродился большим описанием предлагаемого workflow:</p>
<pre>[В данном письме используется ascii-art, так что включите фиксированный шрифт]

Прежде чем расписывать сценарии, хотелось бы ещё раз обратить ваше
внимание на базовые свойства GIT:

*) Коммит ВСЕГДА делается только в ЛОКАЛЬНЫЙ репозиторий.

*) За исключением несущественных сейчас особых случаев, коммит ВСЕГДА делается
на ЛОКАЛЬНУЮ ветку. Напоминаю, что ветка - это фактически указатель на коммит;
в результате создания нового коммита текущая ветка начинает указывать на вновь
созданный коммит.

Это означает, что в git разработка делается на ветке ВСЕГДА. "Разработка на
trunk" (где под trunk понимается ветка в удалённом репозитории, например в
находящемся на git.lvk.cs.msu.su) физически невозможна.

Поэтому, правильно говорить не о "разработке на trunk" или на ещё какой-то
ветке в удалённом репозитории, а о синхронизации локальных веток с удалёнными.

Тут уже можно ставить вопросы:
- с какой удалённой веткой вы синхронизируетесь (с master или с какой-то ещё),
- когда вы это делаете.

То же самое, другими словами.

В CVS/SVN вы работали с двумя сущностями:

[рабочий каталог] < -> [общий репозиторий]

Всякое изменение в рабочем каталоге сохранить некуда, кроме как в общий
репозиторий. Если изменился общий репозиторий, то сохранить изменение вообще
нельзя, предварительно не выполнив операцию update (и не выполнив слияние при
необходимости).
Вопросы Макса явно предполагают именно эту схему.

Но в GIT схема другая, там три сущности:

[рабочий каталог] < -> [локальный репозиторий] < -> [удалённые репозитории]

Операции "сохранить текущую работу в виде коммита" и "синхронизироваться с
внешними изменениями" оказываются полностью независимыми друг от друга.

В локальном репозитории можно создавать любое количество коммитов,
расположенных на любом количестве веток, не оглядываясь на какие бы то ни было
изменения удалённых репозиториев.

Независимо от этого, любую из локальных веток можно различным образом
синхронизировать с какой-нибудь веткой в каком-нибудь удалённом репозитории.

А теперь попробую ответить на вопросы.

> Прежде всего, "основной сценарий" разработки некоторого модуля

Отталкиваться лучше не от начала дня, а от начала решения некоторой задачи (не
важно, 5 минут это займёт или месяц; неважно, сколько задач решается в
параллель).

Решение задачи лучше начинать с чистого рабочего каталога (то есть, в рабочем
каталоге не должно быть изменений, не сохранённых в локальном репозитории).
Проверить это можно командой 'git status'.

Возможно, рабочий каталог не чистый. Тоесть, в нём содержится частичное или
полное решение другой задачи. Обращаю внимание, что одной - смешивать в
рабочем каталоге решение нескольких задач абсолютно противопоказано.

Тогда есть варианты:

- не браться за новую задачу, не закончив старую
  - это не всегда возможно

- "cvs-стиль 1": смешать в рабочем каталоге решение старой задачи с решением
  новой
  - иногда прокатывает, но часто приводит к проблемам - например,
    если решение разных задач потребует модифицировать один и тот же файл

- "cvs-стиль 2": слить неготовое решение в "общий репозиторий"
  - и тогда остальные разработчики будут это расхлёбывать

- "cvs-стиль 3": заводить отдельный рабочий каталог на каждую задачу
  - неэффективно (долгая сборка, перерасход дискового пространства, куча
    мусора в каталогах со временем и т.д.)

- правильное решение: сохранить незаконченное решение в локальном репозитории,
  чтобы позже к нему вернуться.

Для реализации этого "правильного решения" git предоставляет целых два средства.

- Для простых случаев - git stash. Эта команда позволяет сохранить все
  изменения рабочего каталога в некоторый буфер, и откатить рабочий каталог к
  последнему коммиту. Позже изменения можно вернуть из буфера командой
  git stash apply. Буфера можно именовать, их может быть сколько угодно.

- Для более сложных случаев - например, когда ваше частичное решение уже
  содержит несколько коммитов в локальный репозиторий - лучше использовать
  обычный git commit. При этом изменения окажутся сохранены в ещё один коммит
  в локальный репозиторий. Что совершенно не страшно - позже, когда вы
  вернётесь к прерыванной задаче, этот локальный коммит можно будет заменить
  или удалить.

Итак, получили чистый рабочий каталог.

Дальше надо решить, на какой локальной ветке будет решение новой задачи:
- если вы не использовали git commit для сохранения частичных решений (то есть,
  если ваша текущая ветка не содержит "неопубликованных" коммитов, то можно её
  и использовать,
- если же у вас на текущей ветке есть "неопубликованные" коммиты, то придётся
  завести новую локальную ветку,
- а можно и всегда заводить новую локальную ветку, именуя её по решаемой
  задаче - так порядку будет больше.

Также, надо решить, с какой базы будет начинаться решение новой задачи.
Во многих случаях базой логично сделать текущее положение ветки master
репозитория на git.lvk.cs.msu.su - но, например, если делается фикс для
кода, поставленного заказчику, то база будет соответствующей.

Новая ветка, указывающая на текущее положение ветки master репозитория на
git.lvk.cs.msu.su, создаётся например так:

git fetch origin master
git branch new_task origin/master
git checkout new_task

Первая команда загружает в ваш локальный репозиторий возможные изменения ветки
master удалённого репозитория origin.
Вторая - создаёт локальную ветку new_task, указывающую на верхний коммит ветки
origin/master.
Третья - делает ветку new_task текущей (в частности, извлекает соответствующее
этой ветке дерево файлов из локального репозитория в рабочий каталог).

Ветку репозитория master можно не указывать. Если же базой для вашей ветки
является не master, а например, devel, то первые две команды
будут выглядить так:
git fetch origin devel
git branch fix_task origin/devel

Если же:

- у вас "однозадачный режим", то есть вся работа ведётся на локальной
  ветке master,
- это именно та локальная ветка master, которая образовалась в результате
  первоначально операции git clone, создавшей ваш репозиторий (то есть, вы её
  явно не переконфигурировали, не удаляли/пересоздавали и т.п.),

то достаточно [при чистом рабочем каталоге!] выполнить команду

git pull

При этом результат будет тот же самый - в ваш локальный репозиторий будут
загружены возможные изменения ветки master удалённого репозитория origin,
локальная ветка master станет указывать на текущее значение origin/master, и
рабочий каталог будет содержать текущее дерево файлов, соответствующее этой
ветке.

После этого можно работать - решать задачу.

В процессе решения задачи возможно придётся переключиться на другие задачи -
об этом см. выше.

Результатом решения задачи должен стать один или несколько коммитов на
локальной ветке, соответствующей этой задаче (или на локальной ветке master -
см. выше).

Теперь эти коммиты надо "опубликовать" - например, на ветку master репозитоиия
на git.lvk.cs.msu.su.

Перед публикацией обязательно надо ещё раз посмотреть получившуюся
последовательность коммитов. Возможно, там имеет смысл что-то поменять. Пока
коммиты локальные, это просто. Как именно это делается - отдельная тема, об
этом потом.

Публикация осуществляется командой

git push

(более полный синтаксис - git push remote_name local_branch:remote_branch - но
в описываемом сценарии все параметры должны быть подставлены по умолчанию)

При этом на уданённый репозиторий из локального репозитория (не из рабочего
каталога, а именно из локального репозитория) будут загружены объекты,
появившиеся в результате вашей работы. А затем будет сделана попытка
продвинуть удалённую ветку.

Вот тут возможны два варианта.

Если с момента начала решения вами вашей задачи удалённую ветку никто не
продвинул, то операция закончится успехом.

А если нет, то будет сообщение об ошибке "not fast-forward".

НИ В КОЕМ СЛУЧАЕ нельзя в ответ передавать команде git push ключ -f (force) -
ЭТО ПРИВЕДЁТ К ПОТЕРЕ КОММИТОВ между вашей базой и текущим состоянием ветки.
Физически данные не потеряются, но без специальных мер они не будут
видны.
Видимо, правильно будет запретить force на важных ветках на уровне pre-receive
hook - подумаем над этим.

Правильных решений при ошибке "not fast-forward" в ответ на git push может
быть два - rebase и merge.
В большинстве случаев из них нужно выбрать rebase.
Смысл этой операции вот в чём.

Когда вы начали решение задачи, состояние дерева коммитов было таким:

... - (*) - (M)

В результате вашей работы оно стало таким:

... - (*) - (M)
              \
              (1) - ... - (K)

Внизу - несколько коммитов, составляющих решение вашей задачи.

А в результате действий кого-то другого оно стало таким:

... - (*) - (M) - ... - (N)
              \
              (1) - ... - (K)

Эти схемы изображают именно деревья коммитов. Одно и то же дерево коммитов
хранится в разных репозиториях.

Операция rebase - это ЗАМЕНА созданной вами последовательности коммитов на
ДРУГУЮ, в которой патчи будут по возможности те же, а родительские отношения -
другие. Цель - получить вот это:

... - (*) - (M) - ... - (N)
                          \
                          (1) - ... - (K)

Если это проделать, то последующая операция git push закончится без ошибки, и
приведёт к

... - (*) - (M) - ... - (N) - (1) - ... - (K)

Теперь - как проделать операцию rebase.
В этой операции задействован рабочий каталог. Соответвенно, начинать её надо с
"чистого" состояния рабочего каталога. Впрочем, нет причин начинать публикацию
не с чистого.

Операция выполняется так (если удалённая ветка - именно origin/master):

git fetch origin
git rebase origin/master

Первая команда загрузит в локальный репозиторий новые коммиты, появившиеся в
ветке master репозитория origin. Эта операция абсолютно недеструктивна - она
только помещает новые объекты в локальный репозиторий и продвигает сохранённую
в локальном репозитории ссылку origin/master.

Вторая команда пытается выполнить требуемую замену последовательности
коммитов. При этом делается следующее:
- создаётся временная локальная ветка, указывающая на точку N,
- эта временная ветка извлекается в рабочий каталог,
- на временную ветку последовательно применяются (cherry-pick) коммиты из
  заменяемой последовательности
- после этого ваша локальная ветка (как ссылка) подменяется вновь созданной.

В процессе rebase-а возможны конфликты. В этом случае их надо разрешить
вручную, после чего продолжить rebase командой git rebase --continue.
Если на каком-то этапе разрешить конфликт не получается, можно отменить всю
операцию rebase при помощи git rebase --abort. Краткая подсказка выдаётся
вместе с сообщением о конфликте. Полностью все возможности можно посмотреть
через git help rebase.

Вот в общем-то и всё.

Чтобы вернуться к старой задаче, которую вы прервали, начав эту, достаточно
выполнить операцию git checkout соответствующей локальной ветки (если другая
задача на ветке), или же операции git pull; git stash apply (если другая
задача на stash)

Если кому-то ну очень не хочется работать в терминах задач, как я тут
расписал, а хочется "тесного взаимодействия" с удалённым репозиторием, то
этого можно достичь так:

- ограничиться работой только на локальной ветке master,
- перед каждым commit делать pull и разрешать конфликты [это фактически
  извращённая форма rebase, когда локальных коммитов нет, а рабочий каталог
  грязный],
- после каждого commit делать push

Но при этом теряется как минимум описанная выше поддержка многозадачности, а
также возможность переписывать историю (так как переписывать опубликованную
историю нежелательно).

Честно говоря, я не вижу рациональных причин так поступать.

> Команд update/revert нет.
> checkout делает явно что-то "другое"

См. 'git-svn crash course, http://git.or.cz/course/svn.html'
Это простая сводная таблица команд svn и их аналогов в git.

> 1. Захожу в каталог своего модуля (.../src/XXX/)
>     (в случае, если ночевал в чужом каталоге).
> 2. Делаю команду svn status или svn status -rHEAD.
>     (этот шаг делается если "давно не был" или забыл, есть
>      ли несохраненные изменения).
> 2'. Если trunc для моего модуля "ушёл", смотрю svn log/svn diff.
> 3. Делаю svn up. В случае конфликтов разрешаю их.
>
> ...
>
> Например, что (и когда) я должен делать _вместо_ шагов 1.-3. ?

Мне кажется, расписанный выше workflow содержит исчерпывающий ответ на этот
вопрос? Если нет, то что именно непонятно?

> Что я должен делать вместо revert ?

git checkout path/to/file

> Например, git status/diff/commit без аргументов "действуют" не на
> текущий модуль, а на весь Проект <img src='http://gq.net.ru/wp-includes/images/smilies/icon_sad.gif' alt=':(' class='wp-smiley' /> . В некоторых случаях флаг --force
> существенно изменяет логику операциию.

Команда status описывает состояние всего рабочего каталога - такова
её семантика. Это не должно как-либо мешать в "ориентированном на задачи"
workflow.

Команда commit без аргументов создаёт коммит из текущего содержимого git
index, куда изменения заносятся командой git add.
Команда commit -a перед выполнением коммита заносит в git index все изменения
рабочего каталога. Использовать её надо с осторожностью.

Я лично всегда перед коммитом - даже временным/локальным - делаю git status,
чтобы понять, что произойдёт. Если это не соответствует моим пожеланиям, я
выполняю команды, которые тот же git status показывавет в качестве подсказки.

git diff по умолчанию действительно действует на весь рабочий каталог. Но
опять же, в "ориентированном на задачи" workflow он покажет тебе только
изменения, сделанные в рамках текущей задачи.

Флаг --force использовать не надо, если ты только не представляешь очень
хорошо, что делаешь.

> Добавлю, что _после_ расписывания сценария работы с транком следует
> расписать:
> 1. Сценарий разработки функциональности "в отдельной ветке"
> (в терминах CVS) с последующей заливкой на транк.

При использовании "ориентированного на задачи" workflow разницы практически не
будет. Пока ветка локальная - так вообще не будет.

Если по любой причине необходимо или хочется публиковать промежуточные
результаты в удалённом репозитории, можно создать там ветку

git fetch origin
git branch my_cool_branch origin/master
git push origin my_cool_branch:my_cool_branch
git branch -D my_cool_branch

после чего использовать в описанном workflow ветку origin/my_cool_branch в
качестве базы.

Дальнейшее слияние может быть через rebase или через merge.

Rebase делается примерно так (предполагаю, что в локальном репозитории ветка
my_cool_branch уже есть):

git fetch origin
git checkout my_cool_branch
git rebase origin/master

В результате в локальном репозитории ветка my_cool_branch будет заменена
на новую, построенную относительно текущего положения origin/master

Далее возможны два варианта:

git push origin my_cool_branch:master

Это разместит всю историю my_cool_branch поверх текущего положения master,
причём указатель ветки master в репозитории origin будет переставлен на
последний коммит my_cool_branch

git push -f origin my_cool_branch:my_cool_branch

Это переставит указатель ветки my_cool_branch в репозитории origin на её новое
положение. Тут используется -f, так как это по сути non-fast-forward операция.
В этом случае master в удалённом репозитории останется на месте и от него начнётся
новое "основание" ветки.

Если при этом с веткой my_cool_branch работали другие люди, то после этого у них
могут быть трудности. Так что если ожидается какая-либо новая работа в
my_cool_branch, то вместо rebase можно использовать merge.
(Проблемы можно устранить git reset --hard в локальном репозитории)

Тогда "ветвление и слияние" останется в истории ветки master, но зато
ветка my_cool_branch сможет продолжить своё развитие.

Когда в репозитории есть локальные ветки master и my_cool_branch, merge
делается так:

git checkout master
git merge my_cool_branch
git push

При merge возможны конфликты. Их придётся разрешить.

> 2. Сценарий формирования релиз-ветки. В том числе перетаскивания на неё
> отдельных коммитов (критических багфиксов итд) с транка.

Самое тупое - это просто создать релиз-ветку, и потом в рамках описанного выше
"ориентированного на задачи" workflow работать с этой веткой как с базой.

В частности, "перетаскивание коммита" - это выполнение операции
git cherry-pick в том месте workflow, где сказано "решать задачу".

> Также не лишне было бы понять, как _автоматически_ поддерживать в gitk
> отображение _текущего_ состояния репозитория. Делать каждый раз (а)
> обновление текущего клона репозитория и (б) нажатие каких-то кнопок в
> gitk -- очень напряжно.

gitk всегда показывает состояние локального репозитория.
Чтобы увидеть изменения в удалённом репозитории, сначала надо скопировать их в
локальный репозиторий командой git fetch.

Основной сценарий работы с gitk - запускать его, посмотреть что надо и
закрывать. У него довольно многое можно задать в командной строке (чтобы он
показал именно "что надо") - он принимает все параметры git rev-list.

"Долгоживущий" gitk - это нестандартный сценарий. Под это он не оптимизирован.
Хотя команда refresh в меню у него есть.
</pre>
<p>(cc) Nikita Youschenko, Mike Chistolinov</p>
]]></content:encoded>
			<wfw:commentRss>http://gq.net.ru/2008/11/19/some-more-words-about-git/feed/</wfw:commentRss>
		<slash:comments>11</slash:comments>
		</item>
		<item>
		<title>Сбыча мечт</title>
		<link>http://gq.net.ru/2007/11/25/etckeeper/</link>
		<comments>http://gq.net.ru/2007/11/25/etckeeper/#comments</comments>
		<pubDate>Sun, 25 Nov 2007 09:48:19 +0000</pubDate>
		<dc:creator>GQ</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Backport]]></category>
		<category><![CDATA[Debian]]></category>
		<category><![CDATA[Радостное]]></category>
		<category><![CDATA[etckeeper]]></category>
		<category><![CDATA[git]]></category>
		<category><![CDATA[HOWTO]]></category>
		<category><![CDATA[Linux]]></category>

		<guid isPermaLink="false">http://gq.net.ru/2007/11/25/etckeeper/</guid>
		<description><![CDATA[История изменений для конфигов в /etc? С возможностью откатов? И минимумом лишних телодвижений? Встречайте etckeeper! Короткая инструкция: # etckeeper init инициализирует репозиторий git. После этого # cd /etc; git commit -m "Initial commit" для первого чек-ина. И всё. Можно использовать. Можно пользоваться всеми прелестями git&#8217;а для клонирования настроек, merge коммитов между репозиториями и прочего. Пакет [...]]]></description>
			<content:encoded><![CDATA[<p>История изменений для конфигов в /etc? С возможностью откатов? И минимумом лишних телодвижений?</p>
<p>Встречайте <a href="http://packages.debian.org/etckeeper">etckeeper</a>!</p>
<p>Короткая инструкция:</p>
<pre><code># etckeeper init</code></pre>
<p>инициализирует репозиторий git. После этого</p>
<pre><code># cd /etc; git commit -m "Initial commit"</code></pre>
<p>для первого чек-ина. И всё. Можно использовать. Можно пользоваться всеми прелестями git&#8217;а для клонирования настроек, merge коммитов между репозиториями и прочего. Пакет использует хуки APT для автоматического коммита после установки/обновления/удаления пакетов, а так же metastore для хранения владельца/прав доступа для файлов.</p>
<p>ЗЫ Пока только в sid. Сейчас сделаю бэкпорт для Etch. Уж больно вкусно.<br />
ЗЗЫ Бэкпорт сделал, лежит в <a href="http://gq.net.ru/debian">репозитории</a>. Кто будет ставить: потребуется еще бэкпортнутый metastore и git-core, из того же репозитория.</p>
]]></content:encoded>
			<wfw:commentRss>http://gq.net.ru/2007/11/25/etckeeper/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
	</channel>
</rss>
