Почему не стоит бояться ребаз в Git

Почему не стоит бояться ребаз в Git

7 декабря 2022 г.

Новички часто сталкиваются с рядом вопросов, связанных с перебазированием при использовании Git:

* Кто они такие * Почему люди делают это * Почему push-уведомления могут завершиться ошибкой после успешной перебазировки

В этой статье я дам ответы на эти вопросы. А пока, TL;DR: перебазирование — это способ превратить вашу сырую историю коммитов в то, чем вы захотите поделиться с остальной частью своей команды.

Как сделать ребаз

Чтобы выполнить перебазирование, запустите git rebase <commit-reference>. Ссылка фиксации может быть любой, например:

* Название филиала, * Тег или * Идентификатор фиксации.

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

Простая перебазировка

В своей простейшей форме перебазирование берет изменения из одного места (одной базы) и перемещает их в другое. Это меняет место, где история ответвилась. Итак, с псевдонимом дерева, о котором я писал ранее, мы начинаем с графа ветвей, подобного этому:

$ git tree
* 293b722 (HEAD -> test) add test.txt file
| * abc01e7 (origin/main, origin/HEAD, main) Add lorem ipsum to readme
|/
* edd3504 Add readme

И мы перемещаем нашу ветку, чтобы она начиналась с другого места — с вершины основной ветки:

$ git rebase main
Successfully rebased and updated refs/heads/test.

И в результате получаем вот такое дерево:

$ git tree
* fe4254e (HEAD -> test) add test.txt file
* abc01e7 (origin/main, origin/HEAD, main) Add lorem ipsum to readme
* edd3504 Add readme

Наша ветвь test раньше начиналась с edd3504 Добавить файл readme, а теперь она начинается с abc01e7 Добавить lorem ipsum в файл readme.

Выполняется перебазирование

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

$ git rebase main
Auto-merging test.txt
CONFLICT (add/add): Merge conflict in test.txt
error: could not apply 293b722... add test.txt file
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 293b722... add test.txt file

При проверке статуса в такой ситуации вы получите инструкцию от Git о дальнейших действиях:

$ git status
interactive rebase in progress; onto a03989b
Last command done (1 command done):
   pick 293b722 add test.txt file
No commands remaining.
You are currently rebasing branch 'test' on 'a03989b'.
  (fix conflicts and then run "git rebase --continue")
  (use "git rebase --skip" to skip this patch)
  (use "git rebase --abort" to check out the original branch)

Unmerged paths:
  (use "git restore --staged <file>..." to unstage)
  (use "git add <file>..." to mark resolution)
        both added:      test.txt

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

Для устранения конфликта необходимо отредактировать файл. Когда вы закончите с этим, вы добавите изменение в staging:

$ git add test.txt
(no output)

И вы продолжаете перебазирование:

$ git rebase --continue
[detached HEAD b81cad4] add test.txt file
 1 file changed, 1 insertion(+)
Successfully rebased and updated refs/heads/test.

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

Перебазировать интерактивно

Более продвинутый вариант перебазирования — интерактивный. Это позволяет вам вносить очень точные изменения в ветку, а не просто перемещать ее из одного места в другое. Давайте рассмотрим несколько примеров ниже.

Переименовать коммиты

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

Сжатие коммитов

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

Редактирование коммитов

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

Удаление коммитов

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

Изменение порядка коммитов

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

Файловый интерфейс

Как видите, интерактивное перемещение требует большого количества тонких действий. Git получает эти данные в виде текстового файла. Когда я запускаю git rebase main -i, я получаю свой редактор со следующим содержимым:

pick a03989b add test.txt

# Rebase abc01e7..a03989b onto abc01e7 (1 command)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
#                    commit's log message, unless -C is used, in which case
#                    keep only this commit's message; -c is same as -C but
#                    opens the editor
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified); use -c <commit> to reword the commit message
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#

Когда вы сохраните файл и выйдете из редактора, Git будет следовать указанным вами инструкциям. Это интерфейс, который поначалу может показаться неудобным, но к нему можно довольно быстро привыкнуть. Единственный «загвоздка» в том, что вам нужно знать свой редактор по умолчанию и уметь его использовать. Я написал об этом немного больше в разделе другой статьи.

Практический совет

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

  • Изменить начальную точку ветки
  • Изменить порядок некоторых коммитов
  • Раздавить других
  • и т. д.

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

* Сначала удалите лишние коммиты, * Коммиты, связанные со сквошем, а затем * Переместить ветку на другую базу.

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

История изменений

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

$ git push origin test
To github.com:how-to-js/git.md.git
 ! [rejected]        test -> test (non-fast-forward)
error: failed to push some refs to 'github.com:how-to-js/git.md.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details

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

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

Возможные побочные эффекты

Помимо присущей перебазам сложности, они создают некоторые проблемы в командной среде:

только локальные филиалы

Когда ваша работа находится только на вашем компьютере, всегда безопасно перебазировать или изменить историю каким-либо другим способом. Это хорошая практика, потому что она всегда помогает вам очистить свою работу до того, как ее увидят другие. Если вы начали какую-то работу месяц назад и с тех пор регулярно вносите изменения в main — не стоит сохранять информацию с помощью коммитов, таких как Объединить ветку удаленного отслеживания «origin/main» в test. Вместо этого вы можете регулярно перемещать базу и сохранять историю в виде прямой линии.

Ветки, загруженные на удаленку

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

Удаленные ветки, над которыми работают другие

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

* Убедиться, что все в курсе, когда происходит изменение — что нет двух человек, вносящих изменения в ветку одновременно; * Обновите всех до новой, перебазированной ветки, прежде чем они начнут новую работу в ней.

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

Продолжайте учиться

Если вам интересно узнать больше о ветках в Git, вы можете найти отличный (и красивый) ресурс по адресу Learngitbranching.js.org/">Learn Git Branching. Если вы хотите узнать больше о Git, зарегистрируйтесь здесь, чтобы получать обновления о моем контенте, посвященном Git.< /p>


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE