Тримминг Git Commits / Squashing Git History - PullRequest
63 голосов
/ 20 февраля 2010

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

Затем каждые несколько минут / часов / дней я делаю серьезный коммит с реальным комментарием вроде: «Исправлена ​​ошибка № 22.55, 3-й раз». Как я могу разделить эти два понятия? Я хотел бы иметь возможность удалить все свои частые коммиты и просто оставить серьезные.

Ответы [ 4 ]

98 голосов
/ 20 февраля 2010

Отредактированный ответ с новым (во второй половине этой записи) новым исправлением Git1.7! действие и --autosquash опция для быстрого переупорядочения фиксации и редактирования сообщения.


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

Я хотел бы иметь возможность удалить все мои частые проверки и просто оставить серьезные.

Это называется фиксация коммитов .
У вас есть хороший пример «очистки comit» в этой Git ready статье:
(Примечание: интерактивная функция rebase появилась с сентября 2007 года и позволяет раздавливать, разбивать, удалять или изменять порядок коммитов: см. Также страницу GitPro )

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

alt text

Последние 4 коммита были бы намного счастливее, если бы они были свернуты вместе

$ git rebase -i HEAD~4

pick 01d1124 Adding license
pick 6340aaa Moving license into its own file
pick ebfd367 Jekyll has become self-aware.
pick 30e0ccb Changed the tagline in the binary, too.

# Rebase 60709da..30e0ccb onto 60709da
#
# Commands:
#  p, pick = use commit
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

перебазировать, используя последние четыре коммита, откуда HEAD с HEAD~4.
Мы просто собираем все в один коммит.
Таким образом, изменение первых четырех строк файла на это поможет:

pick 01d1124 Adding license
squash 6340aaa Moving license into its own file
squash ebfd367 Jekyll has become self-aware.
squash 30e0ccb Changed the tagline in the binary, too.

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

# This is a combination of 4 commits.
# The first commit's message is:
Adding license

# This is the 2nd commit message:

Moving license into its own file

# This is the 3rd commit message:

Jekyll has become self-aware.

# This is the 4th commit message:

Changed the tagline in the binary, too.

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# Explicit paths specified without -i nor -o; assuming --only paths...
# Not currently on any branch.
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   new file:   LICENSE
#   modified:   README.textile
#   modified:   Rakefile
#   modified:   bin/jekyll
#

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

Created commit 0fc4eea: Creating license file, and making jekyll self-aware.
 4 files changed, 27 insertions(+), 30 deletions(-)
  create mode 100644 LICENSE
    Successfully rebased and updated refs/heads/master.

И если мы снова посмотрим на историю ...

alt text


Примечание: в целях "фиксации" Git1.7 (февраль 2010 г.) введено 2 новых элемента (как отмечено в комментарии Дастин ):

  • "git rebase -i" изучено новое действие "fixup", которое отменяет изменение, но не влияет на существующее сообщение журнала.
  • "git rebase -i" также выучил опцию --autosquash, которая полезна вместе с новым действием "fixup".

Оба (действие исправления и опция --autosquash) проиллюстрированы в этой записи Thechnosorcery Networks . Эти функции готовятся с прошлого июня 2009 года и обсуждаются далее в декабре прошлого года .

Действие или директива fixup предназначены для сжатия коммита, который вы бы вручную переупорядочили в списке редактирования коммита rebase --interactive, игнорируя при этом второе сообщение коммита, что ускорит шаг редактирования сообщения (вы можете просто сохраните его: сжатый коммит будет иметь только первое сообщение о коммите)
Полученное сообщение о коммите будет только первым.

  # s, squash = use commit, but meld into previous commit
  # f, fixup = like "squash", but discard this commit's log message

Опция --autosquash предназначена для автоматического выполнения процесса изменения порядка коммитов:

Если вы знаете, с каким коммитом вы хотите что-то раздавить, вы можете передать это с сообщением «squash! $other_commit_subject». Затем, если вы запустите @git rebase --interactive --autosquash commitish@, строка будет автоматически установлена ​​как сквош и помещена под коммитом с темой $ other_commit_subject.

(На самом деле, squash! может использовать только начало другого сообщения фиксации)

$ vim Foo.txt
$ git commit -am "Change all the 'Bar's to 'Foo's"
[topic 8374d8e] Change all the 'Bar's to 'Foo's
 1 files changed, 2 insertions(+), 2 deletions(-)
$ vim Bar.txt
$ git commit -am "Change all the 'Foo's to 'Bar's"
[topic 2d12ce8] Change all the 'Foo's to 'Bar's
 1 files changed, 1 insertions(+), 1 deletions(-)

$ vim Foo.txt
$ git commit -am "squash! Change all the 'Bar's"
[topic 259a7e6] squash! Change all the 'Bar's
 1 files changed, 2 insertions(+), 1 deletions(-)

См? Здесь третий коммит использует только начало сообщения first commit.
rebase --interactive --autosquash переместит сдавленный коммит ниже соответствующего:

pick 8374d8e Change all the 'Bar's to 'Foo's
squash 259a7e6 squash! Change all the 'Bar's
pick 2d12ce8 Change all the 'Foo's to 'Bar's

Редактирование сообщенияИон будет:

# This is a combination of 2 commits.
# The first commit's message is:

Change all the 'Bar's to 'Foo's

# This is the 2nd commit message:

squash! Change all the 'Bar's

То есть по умолчанию вы сохраняете операцию сжатия, записанную в сообщении фиксации.
Но с исправлением! Директива, вы можете оставить это сжатие «невидимым» в сообщении коммита, но при этом использовать автоматическое изменение порядка коммитов с опцией --autosquash (и тот факт, что ваше второе сообщение коммита основано на первом коммите, с которым вы хотите быть сжато ).

pick 8374d8e Change all the 'Bar's to 'Foo's
fixup cfc6e54 fixup! Change all the 'Bar's
pick 2d12ce8 Change all the 'Foo's to 'Bar's

Сообщение по умолчанию будет:

# This is a combination of 2 commits.
# The first commit's message is:

Change all the 'Bar's to 'Foo's

# The 2nd commit message will be skipped:

#    fixup! Change all the 'Bar's

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

Теперь, если вы хотите исправить или раздавить на основе предыдущего коммита , который вы только что сделали, Джейкоб Хелвиг (автор записи в блоге Technosorcery Networks) рекомендует следующие псевдонимы:

[alias]
    fixup = !sh -c 'git commit -m \"fixup! $(git log -1 --format='\\''%s'\\'' $@)\"' -
    squash = !sh -c 'git commit -m \"squash! $(git log -1 --format='\\''%s'\\'' $@)\"' -

И для выполнения интерактивной перебазировки, которая всегда выигрывает от автоматического изменения порядка коммитов, предназначенных для раздавливания:

[alias]
    ri = rebase --interactive --autosquash

Обновление для Git 2.18 (Q2 2018): «git rebase -i» иногда оставляло промежуточное сообщение «# This is a combination of N commits», предназначенное для потребления человеком в редакторе в конечном результате в определенных угловых случаях, которые было исправлено.

См. коммит 15ef693 , коммит dc4b5bc , коммит e12a7ef , коммит d5bc6f2 (27 апреля 2018) Йоханнес Шинделин (dscho) .
(Объединено Junio ​​C Hamano - gitster - в коммит 4a3bf32 , 23 мая 2018 г.)

rebase --skip: очистить сообщение о коммите после неудачного исправления / сквоша

Во время серии команд исправления / сквоша интерактивная сборка ребаз составить сообщение с комментариями. Это будет представлено пользователю в редактор, если хотя бы одна из этих команд была squash.

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

Однако, если последняя команда fixup / squash в такой цепочке терпит неудачу с конфликты слияния, и если пользователь затем решает пропустить его (или разрешить на чистое рабочее дерево, а затем продолжить перебазирование), текущий код не удается очистить сообщение фиксации.

Этот коммит исправляет это поведение.

Исправление немного сложнее, чем кажется на первый взгляд, потому что это не только о том, что мы git rebase --skip исправление или сквош. Это также касается удаления пропущенных исправлений / сквоша сообщение коммита из накопленного сообщения коммита. И это также о вопрос, должны ли мы позволить пользователю редактировать окончательный коммит сообщение или нет («Был ли сквош в цепи , который не был пропущено ? ").

Например, в этом случае мы захотим исправить сообщение фиксации, но не открывать его в редакторе:

pick  <- succeeds
fixup   <- succeeds
squash  <- fails, will be skipped

Это то, где недавно введенный файл current-fixups приходит в реальном удобно. Беглый взгляд и мы можем определить, был ли пропущен сквош. Нам нужно только следить за тем, чтобы пропущенные команды fixup / squash. В качестве бонуса мы можем даже избежать совершения без необходимости, например когда было только одно исправление, и оно не удалось, и был пропущен.

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


Git 2.19 (Q3 2018) исправляет ошибку: когда «git rebase -i» велит объединить два или более коммитов в один, он помечает сообщение журнала для каждого коммита своим номером. Этоправильно назвал первый «1-й коммит», но следующим был «commit #1», что было одно за другим (!).

См. коммит dd2e36e (15 августа 2018 г.) от Филиппа Вуда (phillipwood) .
(Объединено Junio ​​C Hamano - gitster - в коммит 36fd1e8 , 20 августа 2018 г.)

rebase -i: исправить нумерацию в сообщении сквоша

Commit e12a7ef ("rebase -i: обрабатывать" комбинацию <n> commits "с GETTEXT_POISON ", 2018-04-27, Git 2.18) изменил способ индивидуальной фиксации сообщения помечаются, когда сжатие фиксируется вместе.
При этом была введена регрессия, когда нумерация сообщений отключена одним. Этот коммит исправляет это и добавляет тест для нумерации.

27 голосов
/ 22 февраля 2010

Использование мягкого сброса вместо переназначения для сжатия истории GIT

Я думаю, что длина ответов VonC говорит о многом - буквально - о том, насколько сложен git rebase. Это мое расширение еще один ответ на мой вопрос .

  1. У вас есть ветвь ticket-201, которую вы разветвляли от master. Вы хотите сделать вид, что все коммиты с ticket-201 никогда не происходили, но что вы сделали всю работу за один выстрел.
  2. Мягкий сброс к точке ветвления с использованием git reset --soft hash, где hash должен быть хешем фиксации, который находится в журнале ticket-201.
  3. Зафиксируйте ваши изменения, используя add, затем commit. Теперь в истории веток будет только первый коммит и новый с новым материалом.

Составление историй из произвольных комитетов в разных ветвях

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

  1. Оформить новую ветку на commit0 (представьте, что это хеш): git checkout -b new-history commit0
  2. Теперь вы можете получить файлы из commit5: git reset --hard commit5
  3. Вернитесь к своей индексной точке: git reset --soft commit0
  4. Коммит, и это будет второй коммит в ветке.

Эта идея проста, эффективна и гибка.

6 голосов
/ 08 августа 2013

Использование вместо сквоша

Недавно я работал в другой ветке и использовал squash. Другая ветка называется temp, и затем я использую git merge temp --squash, чтобы перенести ее в реальную ветку, которая отправляется на сервер.

Рабочий процесс выглядит примерно так, при условии, что я работаю в Ticket65252:

git branch -d temp #remove old temp bbranch
git checkout -b temp
# work work work, committing all the way
git checkout Ticket65252
git merge temp --squash
git commit -m "Some message here"

Преимущества перед использованием rebase? Путь менее сложный.

Преимущества по сравнению с использованием reset --hard, а затем reset --soft? Меньше путаницы и чуть меньше ошибок.

0 голосов
/ 20 февраля 2010

Используйте git rebase -i, чтобы собирать и фиксировать ваши коммиты вместе.

...