Git
Chapters ▾ 2nd Edition

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

Участие в проекте

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

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

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

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

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

Правила создания коммитов

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

Для начала, вам не следует отправлять ненужные пробелы. Git предоставляет простой способ проверки — перед коммитом выполните команду git diff --check, которая выведет список ненужных пробелов.

Вывод команды `git diff --check`
Рисунок 56. Вывод команды git diff --check

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

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

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

Последнее, что нужно иметь ввиду — это сообщение коммита. Привычка создавать качественные сообщения к коммитам позволяет упростить использование и взаимодействие посредством Git. Как правило, ваши сообщения должны начинаться кратким однострочным описанием не более 50 символов, затем должна идти пустая строка, после которой следует более детальное описание. Проект Git требует, чтобы детальное описание включало причину внесения изменений и сравнение с текущей реализацией — это хороший пример для подражания. Пишите сообщение коммита в императиве: «Fix bug» а не «Fixed bug» или «Fixes bug». Вот отличный шаблон хорошего сообщения коммита, который мы слегка адаптировали из шаблона, изначально написанного Тимом Поупом:

Краткое (не более 50 символов) резюме с заглавной буквы

Более детальный, поясняющий текст, если он требуется.
Старайтесь не превышать длину строки в 72 символа.
В некоторых случаях первая строка подразумевается как тема письма, а всё остальное -- как тело письма.
Пустая строка, отделяющая сводку от тела, имеет решающее значение
(за исключением случаев, когда детального описания нет);
в противном случае такие инструменты, как rebase, могут вас запутать.

Сообщения коммитов следует писать используя неопределённую форму глагола совершенного вида повелительного наклонения: «Fix bug» (Исправить баг), а не «Fixed bug» (Исправлен баг) или «Fixes bug» (Исправляет баг).
Это соглашение соответствует сообщениям коммитов, генерируемых такими командами, как `git merge` и `git revert`.

Последующие абзацы идут после пустых строк.

  - Допускаются обозначения пунктов списка

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

  - Допускается обратный абзацный отступ.

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

Примечание
Делайте как мы говорим, а не как делаем сами.

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

Поступайте так как мы говорим сейчас, а не так как делаем мы.

Небольшая команда

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

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

# Компьютер Джона
$ git clone john@githost:simplegit.git
Cloning into 'simplegit'...
...
$ cd simplegit/
$ vim lib/simplegit.rb
$ git commit -am 'Remove invalid default value'
[master 738ee87] Remove invalid default value
 1 files changed, 1 insertions(+), 1 deletions(-)

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

# Компьютер Джессики
$ git clone jessica@githost:simplegit.git
Cloning into 'simplegit'...
...
$ 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

В последней строке примера приведена полезная информация, выводимая после каждой операции отправки изменений. Её базовый формат такой <oldref>..<newref> fromref → toref, где oldref — коммит, на который указывала ветка до отправки, newref — новый коммит, на который ветка указывает сейчас, fromref — имя отправленной локальной ветки, toref — имя ветки в удалённом репозитории, в которую были отправлены изменения. Далее вы увидите похожие результаты в выводе команд, поэтому имея общее представление о значении поможет вам лучше понимать различные состояния репозиториев. Дополнительную информацию можно найти в документации к команде git-push.

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

# Компьютер Джона
$ 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

В этот момент локальный репозиторий Джона выглядит примерно так:

Расходящаяся история Джона
Рисунок 57. Расходящаяся история Джона

Теперь Джон может слить полученные изменения Джессики со своей локальной веткой:

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

Процесс слияния проходит гладко — история коммитов у Джона выглядит примерно так:

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

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

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

В результате история коммитов у Джона выглядит так:

История коммитов у Джона после отправки на `origin` сервер
Рисунок 59. История коммитов у Джона после отправки на origin сервер

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

Тематическая ветка Джессики
Рисунок 60. Тематическая ветка Джессики

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

# Компьютер Джессики
$ git fetch origin
...
From jessica@githost:simplegit
   fbff5bc..72bbc59  master     -> origin/master

Это приводит к получению изменений, отправленных Джоном в репозиторий. Теперь, история коммитов у Джессики выглядит так:

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

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

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

   Remove invalid default value

issue54..origin/master — это синтаксис фильтра, который указывает Git отображать только список коммитов, которые существуют в последней ветке (в данном случае origin/master), но отсутствуют в первой (в данном случае issue54). Более детально этот синтаксис рассматривается в разделе Диапазоны коммитов главы 7.

В данном случае, в выводе команды мы видим только один коммит, сделанный Джоном и ещё не слитый Джессикой. Если она сольёт origin/master, то это будет единственный коммит, который изменит локальное состояние.

Теперь, Джессика может слить изменения тематической ветки и изменения Джона (origin/master) в свою локальную ветку 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 the 'recursive' strategy.
 lib/simplegit.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

Слияние прошло чисто и теперь история коммитов у Джессики выглядит следующим образом:

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

Теперь Джессика может отправить свою ветку master в origin/master, при условии что Джон больше не отправлял изменений:

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

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

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

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

Общий вид последовательности событий в рабочем процессе для нескольких разработчиков
Рисунок 64. Общий вид последовательности событий в рабочем процессе для нескольких разработчиков

Команда с руководителем

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

Предположим, что Джон и Джессика вместе работают над одной функцией (назовём её «featureA»), при этом Джессика и Джози работают над другой («featureB»). В этом случае компания использует тип рабочего процесса с менеджером по интеграции, при котором работа отдельных групп интегрируется определёнными инженерами, а ветка 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 -u 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 'Make ls-tree function recursive'
[featureB e5b0fdc] Make 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(-)

Репозиторий Джессики выглядит следующим образом:

Начальное состояние истории коммитов Джессики
Рисунок 65. Начальное состояние истории коммитов Джессики

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

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

Полагая что Джессика находится в ветке featureB, она может слить полученные изменения Джози со своими при помощи команды git merge:

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

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

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

Это называется спецификация ссылок. В разделе Спецификации ссылок главы 10 приведено более детальное описание спецификаций ссылок Git и различные способы их использования. Так же обратите внимание на флаг -u; это сокращение для --set-upstream, который настраивает ветки для упрощения отправки и получения изменений в дальнейшем.

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

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

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

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

    Increase 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(-)

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

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

В результате история коммитов у Джессики выглядит так:

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

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

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

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

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

Форк публичного проекта

Участие в публичном проекте сильно отличается. Так как у вас нет доступа обновлять ветки проекта напрямую, то передавать проделанную работу следует другим способом. В первом примере рассматривается участие в публичном проекте посредством форка на Git платформах, где возможно его простое создание. Большинство сайтов Git хостинга поддерживают такую функцию (включая GitHub, BitBucket, repo.or.cz и другие), как и большинство тех, кто сопровождает проекты, ожидают такого же стиля участия. Следующий раздел посвящён проектам, которые предпочитают принимать исправления в виде патчей по электронной почте.

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

$ git clone <url>
$ cd project
$ git checkout -b featureA
  ... work ...
$ git commit
  ... work ...
$ git commit
Примечание

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

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

$ git remote add myfork <url>

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

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

$ git push -u myfork featureA

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

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

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

are available in the git repository at:

  git://githost/simplegit.git featureA

Jessica Smith (2):
      Add limit to log function
      Increase 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
  ... work ...
$ git commit
$ git push myfork featureB
$ git request-pull origin/master myfork
  ... email generated request pull to maintainer ...
$ git fetch origin

Теперь, каждая из ваших тематик разработки изолирована — аналогично очереди патчей — каждую из которых можно переписать, перебазировать или исправить без влияния на другие ветки:

История коммитов в начале работы над `featureB`
Рисунок 69. История коммитов в начале работы над featureB

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

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

Эти действия перепишут историю ваших коммитов, которая станет похожа на История коммитов после работы над featureA.

История коммитов после работы над `featureA`
Рисунок 70. История коммитов после работы над featureA

Так как вы перебазировали ветку, то должны указать флаг -f во время отправки на сервер, чтобы переписать историю ветки featureA коммитами, не являющимися её потомками. Альтернативным решением может быть отправка этих исправлений в ветку с другим названием (например, featureAv2).

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

$ git checkout -b featureBv2 origin/master
$ git merge --squash featureB
  ... change implementation ...
$ git commit
$ git push myfork featureBv2

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

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

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

Публичный проект посредством E-Mail

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

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

$ git checkout -b topicA
  ... work ...
$ git commit
  ... work ...
$ git commit

Сейчас у вас два коммита, которые вы хотите отправить в почтовую рассылку. Используйте команду git format-patch для генерации файлов в формате mbox, которые можно отправить по почте — это обернёт каждый коммит в сообщение e-mail, где первая строка из сообщения коммита будет темой письма, а остальные строки плюс сам патч будут телом письма. Применение патча в формате e-mail, сгенерированного с помощью команды format-patch, сохраняет всю информацию о коммите должным образом.

$ git format-patch -M origin/master
0001-add-limit-to-log-function.patch
0002-increase-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')
--
2.1.0

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

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

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

[imap]
  folder = "[Gmail]/Drafts"
  host = imaps://imap.gmail.com
  user = user@gmail.com
  pass = YX]8g76G_2^sFbd
  port = 993
  sslverify = false

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

$ cat *.patch |git imap-send
Resolving imap.gmail.com... ok
Connecting to [74.125.142.109]:993... ok
Logging in...
sending 2 messages
100% (2/2) done

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

Так же вы можете отправить свои патчи используя SMTP сервер. Как и в предыдущем случае, вы можете использовать набор команд git config или создать секцию sendemail в файле ~/.gitconfig:

[sendemail]
  smtpencryption = tls
  smtpserver = smtp.gmail.com
  smtpuser = user@gmail.com
  smtpserverport = 587

Отправить патчи можно командой git send-email:

$ git send-email *.patch
0001-add-limit-to-log-function.patch
0002-increase-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] Add 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
Подсказка

Помощь по конфигурации, дополнительные советы и рекомендации, а так же тестовое окружение для отправки патчей по email доступны здесь git-send-email.io.

Заключение

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

Далее рассмотрим ситуацию с другой стороны: как сопровождать проект Git. Вы узнаете, как быть великодушным диктатором или менеджером по интеграции.

scroll-to-top