Git
Chapters ▾ 1st Edition

5.2 Распределённый Git - Содействие проекту

Содействие проекту

Мы узнали, что представляют собой различные рабочие процессы, также у вас должно быть достаточно хорошее понимание основ использования Git'а. В этом разделе вы узнаете о нескольких типичных способах внести свой вклад в проект.

Главная трудность в описании этого процесса состоит в том, что существует огромное количество вариаций того, как он организован. Так как Git очень гибок, люди могут осуществлять совместную работу по-разному, и проблематично описать то, как вы должны содействовать проекту — все проекты немного разные. Многое зависит от количества активных участников, от выбранного типа рабочего процесса, от ваших прав доступа к репозиториям, и, возможно, от метода принятия изменений от внешних разработчиков.

Первый фактор — это количество активных участников. Как много пользователей активно вносят свой вклад в проект и как часто? Во многих случаях это два-три разработчика с несколькими коммитами в день, возможно, меньше, для вялотекущих проектов. В по-настоящему больших компаниях или проектах число разработчиков может измеряться тысячами, с десятками или даже сотнями ежедневно поступающих патчей. Это важно, поскольку с увеличением числа разработчиков вам становится труднее убедиться, что ваши изменения можно будет чисто применить или беспрепятственно слить. Изменения, которые вы отправляете, могут оказаться устаревшими или частично сломанными той работой, которая была влита, пока вы работали, или пока ваши изменения ожидали утверждения или применения. Как сохранить свой код согласованным, а патчи применимыми?

Следующий фактор — это рабочий процесс, используемый в проекте. Он централизован, и каждый разработчик имеет равные права на запись в главный репозиторий? Есть у проекта мейнтейнер или менеджер по интеграции, который проверяет патчи? Все ли патчи проверяются и утверждаются экспертами? Вы вовлечены в этот процесс? Присутствует ли система помощников и должны ли вы сначала отправлять свою работу им?

Следующий пункт — это доступ на отправку изменений. Рабочий процесс, требуемый для внесения вклада в проект, сильно отличается в зависимости от того, имеете ли вы доступ на запись или нет. Если у вас нет доступа на запись, то как в проекте принято принимать вклад в работу? Вообще, существует ли какая-либо политика? Какой объём работы вы вносите за раз? Как часто вы это делаете?

Все эти вопросы могут повлиять на то, как эффективно вы будете вносить вклад в проект и какой рабочий процесс предпочтителен или доступен вам. Я расскажу об аспектах каждого из них на серии примеров, продвигаясь от простых к более сложным; на основе этих примеров вы сможете создать специфический нужный вам в вашей работе тип рабочего процесса.

Рекомендации по созданию коммитов

Прежде чем мы приступим к рассмотрению специфичных примеров использования, сделаем короткое замечание о сообщениях коммитов. Обладание хорошим руководством по созданию коммитов и следование ему значительно облегчает работу с Git'ом и сотрудничество с другими разработчиками. У проекта Git имеется документ с хорошими советами по созданию коммитов, из которых делаются патчи — прочитать его можно в исходном коде Git'а в файле Documentation/SubmittingPatches.

Во-первых, не стоит отсылать ничего с ошибками в пробельных символах. Git предоставляет простой способ их обнаружения — перед коммитом, запустите git diff --check, это определит возможные проблемы и перечислит их вам. Вот пример, в котором я заменил красный цвет терминала символами X:

$ git diff --check
lib/simplegit.rb:5: trailing whitespace.
+    @git_dir = File.expand_path(git_dir)XX
lib/simplegit.rb:7: trailing whitespace.
+ XXXXXXXXXXX
lib/simplegit.rb:26: trailing whitespace.
+    def command(git_cmd)XXXX

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

Далее, старайтесь делать так, чтобы каждый коммит был логически отдельным набором изменений. Если можете, старайтесь делать ваши изменения обозримыми — не стоит писать код все выходные, работая над пятью задачами, а затем отправлять их все в понедельник одним массивным коммитом. Даже если вы не делали коммитов в течение выходных, воспользуйтесь индексом, чтобы разбить свою работу на части, как минимум по одному коммиту для каждой проблемы с полезным сообщением к каждому. Если некоторые из изменений затрагивают один и тот же файл, попробуйте использовать git add --patch для индексирования файла по частям (это подробно рассмотрено в главе 6). Снимок состояния проекта на верхушке ветки будет идентичным, сделаете ли вы один коммит или пять, покуда все ваши изменения добавлены в какой-то момент, так что попытайтесь облегчить жизнь вашим коллегам разработчикам, когда они будут просматривать ваши изменения. При таком подходе будет проще выделить или отменить одно из изменений, если возникнет такая необходимость. В главе 6 описано множество полезных ухищрений для переписывания истории и интерактивного индексирования файлов — пользуйтесь этими инструментами для изготовления ясной и понятной истории.

Последняя вещь, которую стоит иметь в виду, — это сообщение коммита. Написание качественных сообщений коммитов должно войти в привычку, это сделает сотрудничество с использованием Git'а гораздо проще. По общему правилу, ваши сообщения должны начинаться с одной строки не длиннее 50 символов, лаконично описывающей набор изменений, затем пустая строка, затем более детальное описание. Проект Git требует, чтобы детальное объяснение включало в себя мотивацию на изменения и противопоставляло вашу реализацию с предыдущим поведением — это хорошее руководство к действию. Если вы пишете сообщения к коммитам на английском языке, то хорошей идеей является использование повелительного наклонения глаголов в настоящем времени. Другими словами, пишите команды. Вместо "I added tests for" или "Adding tests for" используйте "Add tests for".

Вот шаблон, изначально написанный Тимом Поупом на сайте tpope.net:

Краткое (до 50 символов) описание изменений

Более детальное объяснение, если необходимо. Перенос на 72 символе
или около того. В некоторых контекстах первая строка рассматривается
как тема письма, а остальное как тело. Пустая строка, отделяющая сводку
от тела, важна (если вы не опустили тело целиком); если вы оставите их
вместе, инструменты, такие как rebase, могут воспринять это неправильно.

Дальнейшие параграфы идут после пустых строк

 - также можно применять маркеры списков

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

Если все ваши сообщения о коммитах будут выглядеть как это, всё будет намного проще для вас и для разработчиков, с которыми вы работаете. Проект Git содержит хорошо отформатированные сообщения о коммитах — я советую вам запустить git log --no-merges там, чтобы увидеть, как выглядит хорошо отформатированная история коммитов проекта.

В последующих примерах и почти везде в этой книге для краткости я не форматирую сообщения так красиво, как это; вместо этого я использую опцию -m для команды git commit. Делайте, как я говорю, а не как я делаю.

Отдельная маленькая команда

Наиболее простой тип организации, с которой вы легко можете столкнуться — частный проект с одним или двумя другими разработчиками. Под термином частный я подразумеваю закрытый код, недоступный для чтения остальному миру. Вы и все остальные разработчики имеете право записи в репозиторий.

В этой среде вы можете придерживаться рабочего процесса, похожего на тот, который вы бы использовали в Subversion или другой централизованной системе. Вы по-прежнему получите такие преимущества, как локальные коммиты (коммиты в offline) и возможность гораздо более простого ветвления и слияния, но сам рабочий процесс может оставаться очень похожим; главное отличие — во время выполнения коммита слияние происходит на стороне клиента, а не на сервере. Давайте посмотрим, как выглядел бы процесс, когда два разработчика начинают работать вместе с общим репозиторием. Первый разработчик, Джон, клонирует репозиторий, делает изменения и создаёт локальный коммит. (Я заменяю служебные сообщения знаком ... в этих примерах, чтобы немного их сократить.)

# Машина Джона
$ git clone john@githost:simplegit.git
Initialized empty Git repository in /home/john/simplegit/.git/
...
$ cd simplegit/
$ vim lib/simplegit.rb
$ git commit -am 'removed invalid default value'
[master 738ee87] removed invalid default value
 1 files changed, 1 insertions(+), 1 deletions(-)

Второй разработчик, Джессика, выполняет то же самое — клонирует репозиторий и делает коммит с изменениями:

# Машина Джессики
$ git clone jessica@githost:simplegit.git
Initialized empty Git repository in /home/jessica/simplegit/.git/
...
$ cd simplegit/
$ vim TODO
$ git commit -am 'add reset task'
[master fbff5bc] add reset task
 1 files changed, 1 insertions(+), 0 deletions(-)

Теперь Джессика отправляет свою работу на сервер:

# Машина Джессики
$ git push origin master
...
To jessica@githost:simplegit.git
   1edee6b..fbff5bc  master -> master

Джон также пытается выложить свои изменения:

# Машина Джона
$ git push origin master
To john@githost:simplegit.git
 ! [rejected]        master -> master (non-fast forward)
error: failed to push some refs to 'john@githost:simplegit.git'

Джон не может выполнить отправку изменений, так как за это время Джессика уже отправила свои. Это очень важно понять, особенно если вы привыкли к Subversion, так как мы видим, что эти два разработчика не редактировали один и тот же файл. Хотя Subversion и выполняет автоматическое слияние на сервере, если редактировались разные файлы, при использовании Git'а вы должны слить коммиты локально. Прежде чем Джон сможет отправить свои изменения на сервер, он должен извлечь наработки Джессики и выполнить слияние:

$ git fetch origin
...
From john@githost:simplegit
 + 049d078...fbff5bc master     -> origin/master

На этот момент, локальный репозиторий Джона выглядит так, как показано на рисунке 5-4.


Рисунок 5-4. Исходный репозиторий Джона.

У Джона есть ссылка на изменения, выложенные Джессикой, и он должен слить их со своей работой перед тем, как ему разрешат её отправить:

$ git merge origin/master
Merge made by recursive.
 TODO |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

Слияние прошло без проблем — история коммитов Джона теперь выглядит как на рисунке 5-5.


Рисунок 5-5. Репозиторий Джона после слияния с origin/master.

Теперь Джон может протестировать свой код, дабы удостовериться, что он по-прежнему работает нормально, а затем выложить свою работу, уже объединённую с работой Джессики, на сервер:

$ git push origin master
...
To john@githost:simplegit.git
   fbff5bc..72bbc59  master -> master

В результате история коммитов Джона выглядит, как показано на рисунке 5-6.


Рисунок 5-6. История коммитов Джона после отправки изменений на сервер.

Тем временем, Джессика работала над тематической веткой. Она создала тематическую ветку с названием issue54 и сделала три коммита в этой ветке. Она ещё не извлекала изменения Джона, так что её история коммитов выглядит, как показано на рисунке 5-7.


Рисунок 5-7. Исходная история коммитов Джессики.

Джессика хочет синхронизировать свою работу с Джоном, так что она извлекает изменения с сервера:

# Машина Джессики
$ git fetch origin
...
From jessica@githost:simplegit
   fbff5bc..72bbc59  master     -> origin/master

Эта команда извлекает наработки Джона, которые он успел выложить. История коммитов Джессики теперь выглядит как на рисунке 5-8.


Рисунок 5-8. История коммитов Джессики после извлечения изменений Джона.

Джессика полагает, что её тематическая ветка закончена, но она хочет узнать, с чем ей нужно слить свою работу, чтобы она могла выложить её на сервер. Она запускает git log, чтобы выяснить это:

$ git log --no-merges origin/master ^issue54
commit 738ee872852dfaa9d6634e0dea7a324040193016
Author: John Smith <jsmith@example.com>
Date:   Fri May 29 16:01:27 2009 -0700

    removed invalid default value

Теперь Джессика может слить свою тематическую ветку в ветку master, слить работу Джона (origin/master) в свою ветку master и затем отправить изменения на сервер. Сначала она переключается на свою основную ветку, чтобы объединить всю эту работу:

$ git checkout master
Switched to branch "master"
Your branch is behind 'origin/master' by 2 commits, and can be fast-forwarded.

Она может слить сначала ветку origin/master, а может и issue54 — обе они находятся выше в истории коммитов, так что не важно какой порядок слияния она выберет. Конечное состояние репозитория должно получиться идентичным независимо от того, какой порядок слияния она выберет; только история коммитов будет немного разная. Она решает слить ветку issue54 первой:

$ git merge issue54
Updating fbff5bc..4af4298
Fast forward
 README           |    1 +
 lib/simplegit.rb |    6 +++++-
 2 files changed, 6 insertions(+), 1 deletions(-)

Никаких проблем не возникло; как видите, это была обычная перемотка. Теперь Джессика сливает работу Джона (origin/master):

$ git merge origin/master
Auto-merging lib/simplegit.rb
Merge made by recursive.
 lib/simplegit.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

Слияние проходит нормально, и теперь история коммитов Джессики выглядит так, как показано на рисунке 5-9.


Рисунок 5-9. История коммитов Джессики после слияния с изменениями Джона.

Теперь указатель origin/master доступен из ветки master Джессики, так что она может спокойно выполнить git push (полагая, что Джон не выкладывал свои изменения за это время):

$ git push origin master
...
To jessica@githost:simplegit.git
   72bbc59..8059c15  master -> master

Каждый разработчик несколько раз выполнял коммиты и успешно сливал свою работу с работой другого; смотри рисунок 5-10.


Рисунок 5-10. История коммитов Джессики после отправки всех изменений обратно на сервер.

Это один из простейших рабочих процессов. Вы работаете некоторое время, преимущественно в тематической ветке, и, когда приходит время, сливаете её в свою ветку master. Когда вы готовы поделиться этой работой с другими, вы сливаете её в ветку master, извлекаете изменения с сервера и сливаете origin/master (если за это время произошли изменения), и, наконец, отправляете свои изменения в ветку master на сервер. Общая последовательность действий выглядит так, как показано на рисунке 5-11.


Рисунок 5-11. Общая последовательность событий для простого рабочего процесса с несколькими разработчиками в Git'е.

Отдельная команда с менеджером

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

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

Давайте проследим за рабочим процессом Джессики, которая работает над двумя задачами, сотрудничая одновременно с двумя разными разработчиками. Положим, что она уже имеет свою собственную копию репозитория. Джессика решает сначала взяться за задачу featureA. Для этого она создаёт новую ветку и выполняет в ней некоторую работу:

# Машина Джессики
$ git checkout -b featureA
Switched to a new branch "featureA"
$ vim lib/simplegit.rb
$ git commit -am 'add limit to log function'
[featureA 3300904] add limit to log function
 1 files changed, 1 insertions(+), 1 deletions(-)

На этом этапе ей требуется поделиться своей работой с Джоном, так что она отправляет коммиты, выполненные на ветке featureA, на сервер. Так как Джессика не имеет права на изменение ветки master на сервере — только менеджеры по интеграции могут делать это — она вынуждена отправлять свои изменения в другую ветку, чтобы обмениваться работой с Джоном:

$ git push origin featureA
...
To jessica@githost:simplegit.git
 * [new branch]      featureA -> featureA

Джессика сообщает по электронной почте Джону, что она выложила некоторые наработки в ветку featureA, и что он может проверить их. Пока Джессика ждёт ответа от Джона, она решает начать работу над веткой featureB вместе с Джози. Для начала она создаёт новую ветку для этой задачи, используя в качестве основы ветку master на сервере:

# Машина Джессики
$ git fetch origin
$ git checkout -b featureB origin/master
Switched to a new branch "featureB"

Теперь Джессика делает пару коммитов в ветке featureB:

$ vim lib/simplegit.rb
$ git commit -am 'made the ls-tree function recursive'
[featureB e5b0fdc] made the ls-tree function recursive
 1 files changed, 1 insertions(+), 1 deletions(-)
$ vim lib/simplegit.rb
$ git commit -am 'add ls-files'
[featureB 8512791] add ls-files
 1 files changed, 5 insertions(+), 0 deletions(-)

Репозиторий Джессики выглядит, как показано на рисунке 5-12.


Рисунок 5-12. Исходная история коммитов у Джессики.

Джессика уже готова отправить свою работу на сервер, но получает от Джози сообщение о том, что некоторые наработки уже были выложены на сервер в ветку featureBee. Поэтому Джессика должна сначала слить эти изменения со своими, прежде чем она сможет отправить свою работу на сервер. Она может извлечь изменения Джози командой git fetch:

$ git fetch origin
...
From jessica@githost:simplegit
 * [new branch]      featureBee -> origin/featureBee

Теперь Джессика может слить эти изменения в свои наработки командой git merge:

$ git merge origin/featureBee
Auto-merging lib/simplegit.rb
Merge made by recursive.
 lib/simplegit.rb |    4 ++++
 1 files changed, 4 insertions(+), 0 deletions(-)

Есть небольшая проблема — ей нужно выложить изменения из своей ветки featureB в ветку featureBee на сервере. Она может сделать это при помощи команды git push, указав название локальной и удалённой ветки, разделённые двоеточием:

$ git push origin featureB:featureBee
...
To jessica@githost:simplegit.git
   fba9af8..cd685d1  featureB -> featureBee

Это называется refspec. Смотри главу 9, где более детально обсуждаются спецификации ссылок и различные вещи, которые вы можете делать с ними.

Далее, Джон сообщает Джессике по почте, что он добавил некоторые изменения в ветку featureA и просит её проверить их. Она выполняет git fetch, чтобы получить внесённые Джоном изменения:

$ git fetch origin
...
From jessica@githost:simplegit
   3300904..aad881d  featureA   -> origin/featureA

Затем, используя команду git log, она смотрит, что же было изменено:

$ git log origin/featureA ^featureA
commit aad881d154acdaeb2b6b18ea0e827ed8a6d671e6
Author: John Smith <jsmith@example.com>
Date:   Fri May 29 19:57:33 2009 -0700

    changed log output to 30 from 25

Наконец, она сливает работу Джона в свою собственную ветку featureA:

$ git checkout featureA
Switched to branch "featureA"
$ git merge origin/featureA
Updating 3300904..aad881d
Fast forward
 lib/simplegit.rb |   10 +++++++++-
1 files changed, 9 insertions(+), 1 deletions(-)

Джессика хочет кое-что подправить, так что она опять делает коммит и затем отправляет изменения на сервер:

$ git commit -am 'small tweak'
[featureA 774b3ed] small tweak
 1 files changed, 1 insertions(+), 1 deletions(-)
$ git push origin featureA
...
To jessica@githost:simplegit.git
   3300904..774b3ed  featureA -> featureA

История коммитов Джессики теперь выглядит так, как показано на рисунке 5-13.


Рисунок 5-13. История Джессики после внесения коммитов в ветку с решаемой задачей.

Джессика, Джози и Джон информируют менеджеров по интеграции, что ветки featureA и featureBee на сервере готовы к интеграции в основную ветку разработки. После того, как они интегрируют эти ветки в основную версию, извлечение данных с сервера приведёт к появлению новых коммитов слияния. Таким образом, история коммитов станет выглядеть так, как на рисунке 5-14.


Рисунок 5-14. История коммитов Джессики после слияния двух тематических веток.

Множество групп переходят на Git именно из-за возможности параллельной работы нескольких команд с последующим объединением разных линий разработки. Огромное преимущество Git'а — возможность маленьких подгрупп большой команды работать вместе через удалённые ветки, не мешая при этом всей команде. Последовательность событий в рассмотренном здесь рабочем процессе представлена на рисунке 5-15.


Рисунок 5-15. Основная последовательность действий для рабочего процесса в команде с менеджером по интеграции.

Небольшой открытый проект

Внести вклад в открытый проект — это немного другое. Из-за того, что у вас нет прав на прямое изменение веток проекта, требуется какой-нибудь другой путь для обмена наработками с мейнтейнерами. Первый пример описывает участие в проекте через разветвление (fork) на Git-хостингах, на которых это делается достаточно просто. Сайты repo.or.cz и GitHub оба поддерживают такую возможность, и большая часть мейнтейнеров проектов придерживаются такого способа сотрудничества. В следующем разделе рассматриваются проекты, которые предпочитают принимать патчи по e-mail.

Сначала вы скорее всего захотите склонировать основной репозиторий, создать тематическую ветку для одного или нескольких патчей, которые вы собираетесь внести в проект, и выполнить свою работу в ней. Последовательность действий выглядит следующим образом:

$ git clone (url)
$ cd project
$ git checkout -b featureA
$ (выполнение работы)
$ git commit
$ (выполнение работы)
$ git commit

Возможно, у вас возникнет желание воспользоваться rebase -i, чтобы сплющить (squash) свои наработки в единый коммит, или реорганизовать наработки в коммитах таким образом, чтобы их было проще воспринимать мейнтейнерам проекта — об интерактивном перемещении будет рассказано в главе 6.

Если вы закончили работу со своей веткой и готовы поделиться наработками с мейнтейнерами, перейдите на страницу исходного проекта и нажмите кнопку "Fork", создав таким образом свою собственную копию проекта, доступную на запись. Затем вам нужно добавить URL этого нового репозитория в список удалённых репозиториев, в нашем случае мы назовём его myfork:

$ git remote add myfork (url)

Вам нужно отправить свои наработки в этот репозиторий. Проще всего будет отправить в удалённый репозиторий ту ветку, над которой вы работаете, а не сливать её в ветку master и отправлять потом её. Это объясняется следующим образом — если ваша работа не будет принята или будет принята только частично, вам не придётся откатывать назад свою ветку master. Если мейнтейнеры сольют, переместят или частично включат вашу работу, вы, в конечном счёте, получите её обратно при получении изменений из их репозитория:

$ git push myfork featureA

Когда ваши наработки будут отправлены в ваш форк, вам нужно будет послать уведомление мейнтейнеру. Его часто называют запросом на включение (pull request), вы можете либо сгенерировать его через сайт — на GitHub'е есть кнопка "pull request", автоматически уведомляющая мейнтейнера, либо выполнить команду git request-pull и вручную отправить её вывод по почте мейнтейнеру.

Команда request-pull принимает в качестве аргумента имя базовой ветки, в которую вы хотите включить свою работу, и URL репозитория, из которого мейнтейнер может получить ваши наработки. Команда выводит короткую сводку всех изменений, которые вы просите включить в проект. Например, если Джессика хочет послать Джону запрос на включение, когда она сделала пару коммитов в тематической ветке и уже отправила её на сервер, ей следует выполнить следующее:

$ git request-pull origin/master myfork
The following changes since commit 1edee6b1d61823a2de3b09c160d7080b8d1b3a40:
  John Smith (1):
        added a new function

are available in the git repository at:

  git://githost/simplegit.git featureA

Jessica Smith (2):
      add limit to log function
      change log output to 30 from 25

 lib/simplegit.rb |   10 +++++++++-
 1 files changed, 9 insertions(+), 1 deletions(-)

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

Для проекта, мейнтейнером которого вы не являетесь, проще иметь ветку master, которая отслеживает ветку origin/master, и выполнять работу в тематических ветках, которые вы легко сможете удалить, в случае если они будут отклонены. Если вы распределяете свои наработки по различным темам в тематических ветках, вам будет проще выполнить перемещение своей работы, в случае если верхушка главного репозитория переместится за время работы и ваши коммиты уже не получится применить без конфликтов. Например, если вы планируете отправить в проект работу по другой теме, не продолжайте работать внутри тематической ветки, которую вы только что отправили, начните снова с ветки master главного репозитория:

$ git checkout -b featureB origin/master
$ (выполнение работы)
$ git commit
$ git push myfork featureB
$ (отправка письма мейнтейнеру)
$ git fetch origin

Теперь каждая из ваших тем представляет собой нечто похожее на очередь из патчей, которую вы можете перезаписывать, перемещать, модифицировать, не оказывая влияния на остальные, как на рисунке 5-16.


Рисунок 5-16. Исходная история коммитов при работе над featureB.

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

$ git checkout featureA
$ git rebase origin/master
$ git push -f myfork featureA

Так вы перепишете свою историю коммитов, чтобы она выглядела так, как на рисунке 5-17.


Рисунок 5-17. История коммитов после работы в featureA.

Так как вы переместили ветку, команде push вы должны передать опцию -f, чтобы иметь возможность заменить ветку featureA на сервере. Есть альтернатива — выложить новую работу на сервер в другую ветку (возможно, назвав её featureAv2).

Давайте рассмотрим более вероятный сценарий: мейнтейнер просмотрел вашу работу во второй ветке, и ему понравилась ваша идея, но он хотел бы, чтобы вы изменили некоторые детали реализации. Воспользуемся этой возможностью, чтобы заодно переместить вашу работу так, чтобы она базировалась на текущей версии ветки master в проекте. Создадим новую ветку, базирующуюся на текущей ветке origin/master, уплотним (squash) здесь изменения из ветки featureB, разрешим все конфликты, которые могут возникнуть, сделаем необходимые изменения в реализации вашей идеи и затем выложим всё это в виде новой ветки:

$ git checkout -b featureBv2 origin/master
$ git merge --no-commit --squash featureB
$ (изменение реализации)
$ git commit
$ git push myfork featureBv2

Опция --squash берёт всю работу на сливаемой ветке (featureB) и сжимает её в один коммит, не являющийся коммитом-слиянием, и помещает его на верхушку текущей ветки. Опция --no-commit сообщает Git'у, что не нужно автоматически записывать коммит. Это позволит вам внести все изменения с другой ветки и затем сделать ещё ряд изменений перед записью нового коммита.

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


Рисунок 5-18. История коммитов после работы над featureBv2.

Большой открытый проект

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

Рабочий процесс похож на описанный ранее — вы создаёте тематическую ветку для каждой серии патчей, над которой работаете. Отличие состоит в процессе внесения этих изменений в проект. Вместо того, чтобы создавать ответвление (fork) от проекта и отправлять наработки в свой собственный репозиторий с правами на запись, вы генерируете e-mail версию каждой серии коммитов и отправляете её в список рассылки для разработчиков:

$ git checkout -b topicA
$ (выполнение работы)
$ git commit
$ (выполнение работы)
$ git commit

Теперь у нас есть два коммита, которые нужно отправить в список рассылки. Воспользуемся командой git format-patch, чтобы сгенерировать файлы в формате mbox, которые вы сможете отправить по почте. Эта команда превращает каждый коммит в электронное письмо, темой которого является первая строка сообщения коммита, а оставшаяся часть сообщения коммита и патч, который он представляет, являются телом письма. Хорошей особенностью этого является то, что применение патча из сгенерированного командой format-patch электронного письма сохраняет всю информацию о коммите. Мы увидим это в следующем разделе, когда будем применять такие патчи:

$ git format-patch -M origin/master
0001-add-limit-to-log-function.patch
0002-changed-log-output-to-30-from-25.patch

Команда format-patch создаёт файлы с патчами и выводит их названия. Опция -M сообщает Git'у о необходимости отслеживания переименований файлов. Итоговые патчи выглядят так:

$ cat 0001-add-limit-to-log-function.patch
From 330090432754092d704da8e76ca5c05c198e71a8 Mon Sep 17 00:00:00 2001
From: Jessica Smith <jessica@example.com>
Date: Sun, 6 Apr 2008 10:17:23 -0700
Subject: [PATCH 1/2] add limit to log function

Limit log functionality to the first 20

---
 lib/simplegit.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/lib/simplegit.rb b/lib/simplegit.rb
index 76f47bc..f9815f1 100644
--- a/lib/simplegit.rb
+++ b/lib/simplegit.rb
@@ -14,7 +14,7 @@ class SimpleGit
   end

   def log(treeish = 'master')
-    command("git log #{treeish}")
+    command("git log -n 20 #{treeish}")
   end

   def ls_tree(treeish = 'master')
--
1.6.2.rc1.20.g8c5b.dirty

Вы также можете отредактировать эти файлы с патчами, чтобы добавить в электронное письмо какую-то информацию, которую вы не хотите показывать в сообщении коммита. Если вы добавите текст между строкой --- и началом патча (строка lib/simplegit.rb), то разработчик сможет его прочитать, а при применении патча он будет выброшен.

Чтобы отправить эти файлы в список рассылки, вы можете либо вставить файл в своём почтовом клиенте, либо отправить его через специальную программу из командной строки. Вставка текста часто приводит к ошибкам форматирования, особенно в "умных" клиентах, которые не сохраняют символы перевода строки и пробельные символы в исходном виде. К счастью, Git предоставляет инструмент, позволяющий вам передавать через IMAP правильно отформатированные патчи. Для вас применение этого инструмента может оказаться более простым. Я покажу, как отсылать патчи через Gmail, так как именно этот агент я и использую; вы можете прочесть подробные инструкции для множества почтовых программ в вышеупомянутом файле Documentation/SubmittingPatches, находящемся в исходном коде Git'а.

Для начала нам необходимо настроить секцию imap в файле ~/.gitconfig. Можете добавить все значения по одному несколькими командами git config, или можете добавить их все сразу вручную; но в итоге ваш файл конфигурации должен выглядеть примерно так:

[imap]
  folder = "[Gmail]/Drafts"
  host = imaps://imap.gmail.com
  user = user@gmail.com
  pass = p4ssw0rd
  port = 993
  sslverify = false

Если ваш IMAP-сервер не использует SSL, две последние строки могут отсутствовать, а параметр host примет значение imap:// вместо imaps://. Когда закончите с настройками, воспользуйтесь командой git send-email, чтобы поместить свою серию патчей в папку Drafts на указанном IMAP-сервере:

$ git send-email *.patch
0001-added-limit-to-log-function.patch
0002-changed-log-output-to-30-from-25.patch
Who should the emails appear to be from? [Jessica Smith <jessica@example.com>]
Emails will be sent from: Jessica Smith <jessica@example.com>
Who should the emails be sent to? jessica@example.com
Message-ID to be used as In-Reply-To for the first email? y

Затем Git выдаёт кучу служебных сообщений, которые для каждого отсылаемого патча выглядят следующим образом:

(mbox) Adding cc: Jessica Smith <jessica@example.com> from
  \line 'From: Jessica Smith <jessica@example.com>'
OK. Log says:
Sendmail: /usr/sbin/sendmail -i jessica@example.com
From: Jessica Smith <jessica@example.com>
To: jessica@example.com
Subject: [PATCH 1/2] added limit to log function
Date: Sat, 30 May 2009 13:29:15 -0700
Message-Id: <1243715356-61726-1-git-send-email-jessica@example.com>
X-Mailer: git-send-email 1.6.2.rc1.20.g8c5b.dirty
In-Reply-To: <y>
References: <y>

Result: OK

Если всё прошло успешно, то сейчас вы можете перейти в свою папку Drafts, изменить поле 'To' на адрес списка рассылки, в который вы собираетесь послать патчи, возможно, указать адрес мейнтейнера или лица, отвечающего за нужную часть проекта, в поле 'CC' и отправить сообщение.

Итоги

В этом разделе мы рассмотрели ряд общепринятых рабочих процессов, применяемых в разных типах проектов, использующих Git, c которыми вы наверняка столкнётесь. Также были представлены несколько новых инструментов, призванных помочь вам в организации этих процессов. Далее мы рассмотрим, как осуществляется работа с противоположной стороны баррикады — как сопровождать проект, использующий Git. Вы научитесь роли благосклонного диктатора или роли менеджера по интеграции.