Как я могу сжать мои последние 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 LogCombine 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
Подробнее об этом и других способах переписывания истории коммитов читайте в этом полезном посте.
--continuevim :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 mastergit checkout -b new-branchgit checkout -b new-branch-tempgit checkout new-branchgit 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 (потеря полностью фиксирует изменения после ветвления и до слияния.