Перейти к основному содержимому

Работа с ветками

Ветвление

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

Итак, есть общий указатель HEAD и HEAD для каждой ветки. Переключение между ветками предполагает только перемещение HEAD в HEAD соответствующей ветки.

Введение в ветвление

Команда git branch

Получить список, создание или удаление ветвей.

Документация здесь.

Команды:

git branch <имя ветки> — создаёт новую ветку с HEAD, указывающим на HEAD. Если не передать аргумент <имя ветки>, то команда выведет список всех локальных веток; git checkout <имя ветки> — переключается на эту ветку. Можно передать опцию -b, чтобы создать новую ветку перед переключением; git branch -d <имя ветки> — удаляет ветку.

Пример выполнения этих команд:

Пример 1

Локальный и удалённый репозитории могут иметь немало ветвей, поэтому когда вы отслеживаете удалённый репозиторий — отслеживается удалённая ветка (git clone привязывает вашу ветку main или master к ветке origin/main или origin/master удалённого репозитория).

Привязка к удалённой ветке:

  • git branch -u <имя удалённого репозитория>/<удалённая ветка> — привязывает текущую ветку к указанной удалённой ветке;
  • git checkout --track <имя удалённого репозитория>/<удалённая ветка> — аналог предыдущей команды;
  • git checkout -b <ветка> <имя удалённого репозитория>/<удалённая ветка> — создаёт новую локальную ветку и начинает отслеживать удалённую;
  • git branch --vv — показывает локальные и отслеживаемые удалённые ветки;
  • git checkout <удалённая ветка> — создаёт локальную ветку с таким же именем, как у удалённой, и начинает её отслеживать.

В общем, git checkout связан с изменением места, на которое указывает HEAD ветки, что похоже на то, как git reset перемещает общий HEAD.

Прятки и чистка

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

Команда git stash

Скрыть изменения в "грязном рабочем" каталоге.

Документация здесь.

Однако порой у вас есть незавершённые изменения, которые нельзя фиксировать. В такой ситуации их можно сохранить и "спрятать" с помощью команды git stash.

Пример выполнения git stash:

Пример 2

Чтобы вернуть изменения, используйте git stash apply.

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

Слияние

Дадим определения:

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

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

Пример слияния (источник: tproger.ru):

Пример слияния

Команда git merge

Сливает изменения с переданной ветки в текущую.

Документация здесь.

Для слияния мы переходим на основную ветку и используем команду git merge <тематическая ветка>.

Если обе ветви меняют одну и ту же часть файла, то возникает конфликт слияния — ситуация, в которой Git не знает, какую версию файла сохранить, поэтому разрешать конфликт нужно собственноручно. Чтобы увидеть конфликтующие файлы, используйте git status.

Пример с двумя ветками, в которых изменен один и тот же файл:

Пример конфликта 1

Переходим в ветку main через git checkout main и делаем git merge new-branch.

Статус репозитория:

Пример конфликта 2

git status показывает:

On branch main
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)

Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: README

no changes added to commit (use "git add" and/or "git commit -a")

После открытия таких файлов вы увидите похожие маркеры разрешения конфликта:

Пример конфликта 3

Замените в этом блоке всё на версию, которую вы хотите оставить, и подготовьте файл. После разрешения всех конфликтов можно использовать git add и git commit для завершения слияния.

Перемещение

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

Пример перемещения (источник: tproger.ru):

Пример слияния

Команда git rebase

Повторное применение коммитов поверх другого базового "конца".

Документация здесь.

Для перемещения используется команда git rebase <основная ветка> <тематическая ветка>, которая воспроизводит изменения тематической ветки на основной; HEAD тематической ветки указывает на последний воспроизведённый коммит.

Перемещение vs. слияние

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

Сценарий:

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

Откат коммитов — revert и reset

Команда git revert

Отменить некоторые существующие коммиты.

Документация здесь.

Команда git reset

Сброс текущей HEAD в указанное состояние.

Документация здесь.

Похожие дебаты по поводу того, что лучше использовать, возникают, когда вы хотите откатить коммит. Команда git revert <коммит> создаёт новый коммит, отменяющий изменения, но сохраняющий историю, в то время как git reset <коммит> перемещает указатель HEAD, предоставляя более чистую историю (словно бы этого коммита никогда и не было). Важно отметить, что это также означает, что вы больше не сможете вернуться обратно к этим изменениям, например, если вы всё-таки решите, что отмена коммита была лишней. Чище — не значит лучше!

Атрибуция

При подготовке статьи использован материал: