Как я могу сжать мои последние X коммитов вместе в один коммит, используя Git?
Как я могу сжать мои последние X коммитов вместе в один коммит, используя Git?
Ответы:
Используйте git rebase -i <after-this-commit>
и замените «pick» на втором и последующих коммитах на «squash» или «fixup», как описано в руководстве .
В этом примере <after-this-commit>
это либо хеш SHA1, либо относительное местоположение из HEAD текущей ветви, из которой анализируются коммиты для команды rebase. Например, если пользователь желает просмотреть 5 коммитов из текущего HEAD в прошлом, команда имеет вид git rebase -i HEAD~5
.
<after-this-commit>
?
<after-this-commit>
это коммит X + 1, т.е. родитель самого старого коммита, который вы хотите раздавить.
rebase -i
подходом и reset --soft
заключается в том, что rebase -i
я могу сохранить автора коммита, в то время как reset --soft
я могу подтвердить его. Иногда мне нужно раздавить коммиты по запросу, сохраняя при этом информацию об авторе. Иногда мне нужно сбросить софт на свои коммиты. В любом случае возбуждаю оба великолепных ответа.
Вы можете сделать это довольно легко без git rebase
или git merge --squash
. В этом примере мы раздавим последние 3 коммита.
Если вы хотите написать новое сообщение с нуля, этого достаточно:
git reset --soft HEAD~3 &&
git commit
Если вы хотите начать редактирование нового сообщения фиксации с конкатенации существующих сообщений фиксации (т. Е. Аналогично тому, с чего git rebase -i
начнёт список инструкций pick / squash / squash /… / squash ), то вам нужно извлечь эти сообщения и передать им git commit
:
git reset --soft HEAD~3 &&
git commit --edit -m"$(git log --format=%B --reverse HEAD..HEAD@{1})"
Оба эти метода объединяют последние три коммита в один новый коммит одинаково. Мягкий сброс просто перенаправляет HEAD на последний коммит, который вы не хотите раздавливать. Программный сброс не затрагивает ни индекс, ни рабочее дерево, оставляя индекс в желаемом состоянии для вашего нового коммита (т. Е. Он уже содержит все изменения из коммитов, которые вы собираетесь «выбросить»).
git rebase --squash-recent
или даже git commit --amend-many
.
branch@{upstream}
(или только @{upstream}
для текущей ветки; в обоих случаях последняя часть может быть сокращена до @{u}
; см. Gitrevisions ). Это может отличаться от вашего «последнего выдвинутого коммита» (например, если кто-то выдвинул что-то, что построено поверх вашего самого последнего толчка, а затем вы его получили), но кажется, что это может быть близко к тому, что вы хотите.
push -f
но в остальном это было прекрасно, спасибо.
git push --force
потом, чтобы он взял коммит
Вы можете использовать git merge --squash
для этого, что немного более элегантно, чем git rebase -i
. Предположим, что вы мастер, и вы хотите раздавить последние 12 коммитов в один.
ПРЕДУПРЕЖДЕНИЕ. Сначала убедитесь, что вы зафиксировали свою работу - убедитесь, что git status
она чистая (поскольку git reset --hard
отбросит поэтапные и неустановленные изменения)
Затем:
# Reset the current branch to the commit just before the last 12:
git reset --hard HEAD~12
# HEAD@{1} is where the branch was just before the previous command.
# This command sets the state of the index to be as it would just
# after a merge from that commit:
git merge --squash HEAD@{1}
# Commit those squashed changes. The commit message will be helpfully
# prepopulated with the commit messages of all the squashed commits:
git commit
Документацияgit merge
описывает --squash
вариант более подробно.
Обновление: единственное реальное преимущество этого метода перед более простым, git reset --soft HEAD~12 && git commit
предложенным Крисом Джонсеном в его ответе, состоит в том, что вы получаете сообщение о коммите, предварительно заполненное каждым сообщением о коммите, которое вы подавляете.
git rebase -i
, но не приводите причину почему. Ориентировочно, потому что мне кажется, что на самом деле все наоборот, и это взлом; Разве вы не выполняете больше команд, чем необходимо, только для того, чтобы заставить вас git merge
выполнить одну из задач, git rebase
специально предназначенных для этого?
git merge --squash
также легче использовать в скрипте. По сути, причина была в том, что вам вообще не нужна «интерактивность» git rebase -i
.
git merge --squash
менее вероятно возникновение конфликтов слияния при перемещении / удалении / переименовании по сравнению с перебазированием, особенно если вы выполняете слияние из локальной ветки. (Отказ от ответственности: основываясь только на одном опыте, поправьте меня, если это не так в общем случае!)
HEAD@{1}
чтобы просто быть в безопасности, например, когда ваш рабочий процесс прерывается на час из-за отключения электроэнергии и т. Д.
Я рекомендую избегать, git reset
когда это возможно, особенно для Git-новичков. Если вам действительно не нужно автоматизировать процесс, основанный на ряде коммитов, есть менее экзотический способ ...
git merge --squash (working branch name)
git commit
Сообщение фиксации будет предварительно заполнено на основе сквоша.
gitk
для обозначения строки кода, которую вы подавляете, а также для обозначения основы, на которую нужно нажимать. В обычном случае обе эти метки уже существуют, поэтому шаг (1) можно пропустить.
git branch your-feature && git reset --hard HEAD~N
наиболее удобный способ. Тем не менее, он снова включает git reset, чего этот ответ пытался избежать.
Основываясь на ответе Криса Джонсена ,
Добавьте глобальный псевдоним "squash" из bash: (или Git Bash в Windows)
git config --global alias.squash '!f(){ git reset --soft HEAD~${1} && git commit --edit -m"$(git log --format=%B --reverse HEAD..HEAD@{1})"; };f'
... или с помощью командной строки Windows:
git config --global alias.squash "!f(){ git reset --soft HEAD~${1} && git commit --edit -m\"$(git log --format=%B --reverse HEAD..HEAD@{1})\"; };f"
Вы ~/.gitconfig
должны теперь содержать этот псевдоним:
[alias]
squash = "!f(){ git reset --soft HEAD~${1} && git commit --edit -m\"$(git log --format=%B --reverse HEAD..HEAD@{1})\"; };f"
Применение:
git squash N
... который автоматически объединяет последние N
коммиты включительно.
Примечание. Результирующее сообщение о фиксации представляет собой комбинацию всех сжатых фиксаций по порядку. Если вы недовольны этим, вы всегда git commit --amend
можете изменить его вручную. (Или измените псевдоним в соответствии со своими вкусами.)
git squash -m "New summary."
и N
определил автоматически как число невыдвинутых коммитов.
git commit --amend
для дальнейшего изменения сообщения, но этот псевдоним позволяет вам хорошо начать с того, что должно быть в сообщении фиксации.
Благодаря этому удобному сообщению в блоге я обнаружил, что вы можете использовать эту команду, чтобы раздавить последние 3 коммита:
git rebase -i HEAD~3
Это удобно, так как работает, даже когда вы находитесь в локальной ветке без информации об отслеживании / удаленного репо.
Команда откроет интерактивный редактор ребаз, который затем позволит вам изменить порядок, сквош, перефразировать слова и т. Д. Как обычно.
Использование интерактивного редактора ребаз:
Интерактивный редактор rebase показывает последние три коммита. Это ограничение было определено HEAD~3
при запуске команды git rebase -i HEAD~3
.
Самый последний коммит, HEAD
отображается первым в строке 1. Строки, начинающиеся с a, #
являются комментариями / документацией.
Документация отображается довольно ясно. В любой строке вы можете изменить команду pick
на команду по вашему выбору.
Я предпочитаю использовать команду, так fixup
как она «раздавливает» изменения коммита в коммите в строке выше и отбрасывает сообщение коммита.
Поскольку коммит в строке 1 HEAD
, в большинстве случаев вы бы оставили это как pick
. Вы не можете использовать squash
или, fixup
поскольку нет другого коммита, чтобы раздавить коммит в.
Вы также можете изменить порядок коммитов. Это позволяет вам фиксировать или фиксировать коммиты, которые не являются смежными в хронологическом порядке.
Практический повседневный пример
Я недавно совершил новую функцию. С тех пор я исправил две ошибки. Но теперь я обнаружил ошибку (или, возможно, просто орфографическую ошибку) в новой функции, которую я совершил. Как раздражает! Я не хочу, чтобы новый коммит загрязнял мою историю коммитов!
Первое, что я делаю, это исправляю ошибку и делаю новый коммит с комментарием squash this into my new feature!
.
Затем я запускаю git log
или gitk
получаю коммит SHA новой функции (в данном случае 1ff9460
).
Затем я открываю интерактивный редактор ребаз git rebase -i 1ff9460~
. ~
После фиксации ША говорит редактор , чтобы включить этот коммит в редакторе.
Затем я перемещаю коммит, содержащий fix ( fe7f1e0
), под коммит функции и изменяю pick
на fixup
.
При закрытии редактора исправление попадет в коммит функции, и моя история коммитов будет выглядеть красиво и чисто!
Это хорошо работает, когда все коммиты являются локальными, но если вы попытаетесь изменить какие-либо коммиты, уже переданные на удаленный компьютер, вы действительно можете вызвать проблемы у других разработчиков, которые проверили ту же ветку!
pick
в строке 1. Если вы выберите squash
или fixup
для коммита в строке 1, git покажет сообщение «ошибка: невозможно исправить» без предыдущего коммита ». Затем он даст вам возможность исправить это: «Вы можете исправить это с помощью« git rebase --edit-todo »и затем запустить« git rebase --continue »». или вы можете просто прервать и начать заново: «Или вы можете прервать ребаз с помощью« git rebase --abort ».».
Если вы используете TortoiseGit, вы можете использовать функцию Combine to one commit
:
Show Log
Combine to one commit
из контекстного менюЭта функция автоматически выполняет все необходимые одиночные шаги git. К сожалению, доступно только для Windows.
Для этого вы можете использовать следующую команду git.
git rebase -i HEAD~n
n (= 4 здесь) - номер последнего коммита. Тогда у вас есть следующие варианты,
pick 01d1124 Message....
pick 6340aaa Message....
pick ebfd367 Message....
pick 30e0ccb Message....
Обновите, как показано ниже, pick
один коммит, а squash
остальные - в самый последний,
p 01d1124 Message....
s 6340aaa Message....
s ebfd367 Message....
s 30e0ccb Message....
Для получения подробной информации нажмите на ссылку
Основываясь на этой статье, я нашел этот метод проще для моего сценария использования.
Моя ветка 'dev' опередила 'origin / dev' на 96 коммитов (поэтому эти коммиты еще не были перенесены на удаленный).
Я хотел раздавить эти коммиты в один, прежде чем приступить к изменениям. Я предпочитаю сбросить ветку в состояние origin / dev (это оставит все изменения из 96 коммитов без изменений), а затем зафиксировать изменения сразу:
git reset origin/dev
git add --all
git commit -m 'my commit message'
В ветке, в которой вы хотите объединить коммиты, запустите:
git rebase -i HEAD~(n number of commits back to review)
пример:
git rebase -i HEAD~1
Это откроет текстовый редактор, и вы должны переключить «pick» перед каждым коммитом с помощью «squash», если вы хотите, чтобы эти коммиты были объединены вместе. Из документации:
Например, если вы хотите объединить все коммиты в один, «пикап» - это первый коммит, который вы сделали, и все будущие (расположенные ниже первого) должны быть установлены на «сквош». Если используется vim, используйте : x в режиме вставки для сохранения и выхода из редактора.
Затем, чтобы продолжить ребаз:
git rebase --continue
Подробнее об этом и других способах переписывания истории коммитов читайте в этом полезном посте.
--continue
vim :x
.
git add
укажете правильную конфигурацию в ваших файлах, git rebase --continue
чтобы перейти к следующему коммиту и начать слияние. :x
это одна команда, которая сохранит изменения в файле при использовании vim, смотрите это
Ответ аномалии хороший, но я чувствовал себя неуверенно по этому поводу, поэтому я решил добавить пару скриншотов.
Посмотри, где ты git log
. Самое главное, найти хэш коммита первого коммита, который вы не хотите раздавить. Так что только
Выполните git rebase -i [your hash]
, в моем случае:
$ git rebase -i 2d23ea524936e612fae1ac63c95b705db44d937d
В моем случае я хочу раздавить все на коммите, который был первым во времени. Порядок в порядке от первого до последнего, так же, как и в git log
. В моем случае я хочу:
Если вы выбрали только один коммит и раздавили остальные, вы можете настроить одно сообщение о коммите:
Вот и все. Как только вы сохраните это ( :wq
), все готово. Посмотрите на это с git log
.
git log
1) Определите коммит короткого хэша
# git log --pretty=oneline --abbrev-commit
abcd1234 Update to Fix for issue B
cdababcd Fix issue B
deab3412 Fix issue A
....
Здесь даже git log --oneline
можно использовать короткий хеш.
2) Если вы хотите раздавить (объединить) последние два коммита
# git rebase -i deab3412
3) Это открывает nano
редактор для слияния. И это выглядит ниже
....
pick cdababcd Fix issue B
pick abcd1234 Update to Fix for issue B
....
4) Переименуйте слово pick
в squash
которое присутствует раньше abcd1234
. После переименования это должно быть как ниже.
....
pick cdababcd Fix issue B
squash abcd1234 Update to Fix for issue B
....
5) Теперь сохраните и закройте nano
редактор. Нажмите ctrl + o
и нажмите, Enter
чтобы сохранить. А затем нажмите, ctrl + x
чтобы выйти из редактора.
6) Затем nano
снова открывается редактор для обновления комментариев, при необходимости обновляйте его.
7) Теперь его успешно завершили, вы можете проверить это, проверив логи.
# git log --pretty=oneline --abbrev-commit
1122abcd Fix issue B
deab3412 Fix issue A
....
8) Теперь нажмите на репо. Обратите внимание, чтобы добавить +
знак перед названием ветви. Это означает принудительный толчок.
# git push origin +master
Примечание: это основано на использовании git на ubuntu
оболочке. Если вы используете разные ОС ( Windows
или Mac
), то приведенные выше команды такие же, кроме редактора. Вы можете получить другой редактор.
git add <files>
--fixup
опцию, и OLDCOMMIT
мы должны объединить (сквош) этот коммит.git commit --fixup=OLDCOMMIT
Теперь это создает новый коммит поверх HEAD с fixup1 <OLDCOMMIT_MSG>
.
OLDCOMMIT
.git rebase --interactive --autosquash OLDCOMMIT^
Здесь ^
означает предыдущий коммит OLDCOMMIT
. Эта rebase
команда открывает интерактивное окно в редакторе (vim или nano) о том, что нам не нужно ничего делать, просто сохранить и выйти достаточно. Поскольку переданная опция будет автоматически перемещать последнюю фиксацию на следующую за старой фиксацией и изменять операцию на fixup
(эквивалентную сквошу). Затем перебазирование продолжается и заканчивается.
--amend
можно использовать средства с git-commit
. # git log --pretty=oneline --abbrev-commit
cdababcd Fix issue B
deab3412 Fix issue A
....
# git add <files> # New changes
# git commit --amend
# git log --pretty=oneline --abbrev-commit
1d4ab2e1 Fix issue B
deab3412 Fix issue A
....
Здесь --amend
объединяет новые изменения с последним коммитом cdababcd
и генерирует новый идентификатор коммита1d4ab2e1
Чтобы раздавить последние 10 коммитов в один коммит:
git reset --soft HEAD~10 && git commit -m "squashed commit"
Если вы также хотите обновить удаленную ветку с помощью сжатого коммита:
git push -f
Если вы находитесь в удаленной ветви (называемой feature-branch
), клонированной из Золотого репозитория ( golden_repo_name
), то вот метод, позволяющий объединить ваши коммиты в один:
Оформить заказ на золотой репо
git checkout golden_repo_name
Создайте из него новую ветку (золотой репо) следующим образом
git checkout -b dev-branch
Сквош слиться с местным филиалом, который у вас уже есть
git merge --squash feature-branch
Зафиксируйте ваши изменения (это будет единственный коммит в ветке dev)
git commit -m "My feature complete"
Вставьте ветку в ваш локальный репозиторий
git push origin dev-branch
Что может быть действительно удобно:
скажем, найти хеш коммита, который вы хотите сжать поверх d43e15
.
Сейчас использую
git reset d43e15
git commit -am 'new commit name'
Это очень круто, но довольно круто, так что я просто брошу это на ринг:
GIT_EDITOR='f() { if [ "$(basename $1)" = "git-rebase-todo" ]; then sed -i "2,\$s/pick/squash/" $1; else vim $1; fi }; f' git rebase -i foo~5 foo
Перевод: предоставьте новый «редактор» для git, который, если редактируемое имя файла git-rebase-todo
(интерактивная подсказка перебазировки), изменяет все, кроме первого «pick», на «squash», а в противном случае порождает vim - так, чтобы при появлении запроса чтобы редактировать сжатое сообщение о коммите, вы получаете vim. (И, очевидно, я давил последние пять коммитов на ветке foo, но вы можете изменить это так, как вам нравится.)
Я бы, наверное, сделал то, что предложил Марк Лонгэйр .
Если вы хотите объединить каждый коммит в один коммит (например, при первом публичном выпуске проекта), попробуйте:
git checkout --orphan <new-branch>
git commit
Я думаю, что самый простой способ сделать это - создать новую ветку из master и выполнить слияние - сквош функциональной ветви.
git checkout master
git checkout -b feature_branch_squashed
git merge --squash feature_branch
Тогда у вас есть все изменения, готовые к фиксации.
Простой однострочный, который всегда работает, учитывая, что вы в данный момент находитесь в ветви, которую хотите сжать, master - это ветвь, из которой он возник, а последний коммит содержит сообщение о коммите и автора, которого вы хотите использовать:
git reset --soft $(git merge-base HEAD master) && git commit --reuse-message=HEAD@{1}
например, если вы хотите раздавить последние 3 коммита в один коммит в ветке (удаленное хранилище), например: https://bitbucket.org
Что я сделал
(MASTER)
Fleetwood Mac Fritz
║ ║
Add Danny Lindsey Stevie
Kirwan Buckingham Nicks
║ ╚═══╦══════╝
Add Christine ║
Perfect Buckingham
║ Nicks
LA1974══════════╝
║
║
Bill <══════ YOU ARE EDITING HERE
Clinton (CHECKED OUT, CURRENT WORKING DIRECTORY)
В этой очень сокращенной истории хранилища https://github.com/fleetwood-mac/band-history вы открыли запрос на извлечение для объединения в коммит Билла Клинтона с оригинальным MASTER
коммитом Fleetwood Mac.
Вы открыли пулл-запрос и на GitHub вы видите это:
Четыре коммитов:
Думая, что никто не захочет читать всю историю хранилища. (Там на самом деле есть хранилище, нажмите на ссылку выше!) Вы решили раздавить эти коммиты. Так ты иди и беги git reset --soft HEAD~4 && git commit
. Затем вы git push --force
отправляете его на GitHub, чтобы очистить ваш PR.
А что происходит? Вы только что сделали один коммит от Фрица до Билла Клинтона. Потому что вы забыли, что вчера вы работали над версией Бекингемского Ника. И git log
не соответствует тому, что вы видите на GitHub.
git checkout
ихgit reset --soft
этоgit commit
деформацию непосредственно от к кgit rebase -i HEAD^^
где число ^ это X
(в этом случае раздавите два последних коммита)
В дополнение к другим превосходным ответам, я хотел бы добавить, как git rebase -i
всегда меня смущает порядок коммитов - от старого к более новому или наоборот? Так что это мой рабочий процесс:
git rebase -i HEAD~[N]
где N - количество коммитов, к которым я хочу присоединиться, начиная с самого последнего . Так git rebase -i HEAD~5
будет означать «раздавить последние 5 коммитов в новый»;Как насчет ответа на вопрос, связанный с таким рабочим процессом?
merge --squash
после пиара разработчику будет легче , но команда подумала, что это замедлит процесс.)Я не видел такой рабочий процесс на этой странице. (Это могут быть мои глаза.) Если я rebase
правильно понимаю , для множественных слияний потребуется многократное разрешение конфликтов . Я не хочу даже думать об этом!
Так что, похоже, это работает для нас.
git pull master
git checkout -b new-branch
git checkout -b new-branch-temp
git checkout new-branch
git merge --squash new-branch-temp
// помещает все изменения в стадиюgit commit 'one message to rule them all'
git push
Я считаю, что более общим решением является не указание N коммитов, а указание идентификатора ветки / commit, над которым вы хотите получить сжатие. Это менее подвержено ошибкам, чем подсчет коммитов до определенного коммита - просто укажите тег напрямую, или, если вы действительно хотите посчитать, вы можете указать HEAD ~ N.
В моем рабочем процессе я запускаю ветвь, и мой первый коммит в этой ветке суммирует цель (то есть обычно это то, что я отправляю как «окончательное» сообщение для функции в общедоступный репозиторий). Поэтому, когда я закончу, все Я хочу сделать, это git squash master
вернуться к первому сообщению, а затем я готов нажать.
Я использую псевдоним:
squash = !EDITOR="\"_() { sed -n 's/^pick //p' \"\\$1\"; sed -i .tmp '2,\\$s/^pick/f/' \"\\$1\"; }; _\"" git rebase -i
Это приведет к тому, что история будет сдавлена, прежде чем это произойдет, - это даст вам шанс восстановиться путем извлечения старого идентификатора коммита с консоли, если вы хотите вернуться. (Пользователи Solaris отмечают, что он использует -i
опцию GNU sed , пользователи Mac и Linux должны с этим справиться.)
git squash master
когда мы проверены на ведомом. что это будет происходить? мы будем скрывать конфликт?
Это может быть неоднозначно, что подразумевается под «последним».
например git log --graph
выводит следующее (упрощенно):
* commit H0
|
* merge
|\
| * commit B0
| |
| * commit B1
| |
* | commit H1
| |
* | commit H2
|/
|
Тогда последними коммитами по времени являются H0, merge, B0. Чтобы раздавить их, вам придется перебазировать вашу объединенную ветвь при коммите H1.
Проблема в том, что H0 содержит H1 и H2 (и, как правило, больше коммитов до слияния и после ветвления), а B0 - нет. Таким образом, вы должны управлять изменениями от H0, слияния, H1, H2, B0 по крайней мере.
Можно использовать rebase, но иначе, чем в других упомянутых ответах:
rebase -i HEAD~2
Это покажет вам варианты выбора (как упоминалось в других ответах):
pick B1
pick B0
pick H0
Положите сквош вместо кирки в H0:
pick B1
pick B0
s H0
После сохранения и выхода rebase будет применять коммиты по очереди после H1. Это означает, что он попросит вас снова разрешить конфликты (где HEAD будет сначала H1, а затем накапливает коммиты по мере их применения).
После завершения ребазирования вы можете выбрать сообщение для раздавленных H0 и B0:
* commit squashed H0 and B0
|
* commit B1
|
* commit H1
|
* commit H2
|
PS Если вы просто сделаете некоторый сброс в BO: (например, использование reset --mixed
этого объясняется более подробно здесь https://stackoverflow.com/a/18690845/2405850 ):
git reset --mixed hash_of_commit_B0
git add .
git commit -m 'some commit message'
затем вы давите на B0 изменений H0, H1, H2 (потеря полностью фиксирует изменения после ветвления и до слияния.