Сквош мой последний X коммитов вместе с помощью Git


3593

Как я могу сжать мои последние X коммитов вместе в один коммит, используя Git?


12
Аналогичный вопрос: stackoverflow.com/questions/7275508/…
koppor


2
@matt TortoiseGit - ваш инструмент. Он предоставляет единственную функцию «Объединить в один коммит», которая автоматически вызывает все шаги в фоновом режиме. К сожалению, доступно только для Windows. Смотрите мой ответ ниже.
Матиас М

1
Для получения информации о первом
коммите

1
после сквоша нужно сделать принудительный толчок stackoverflow.com/questions/10298291/…
vikramvi

Ответы:


2093

Используйте git rebase -i <after-this-commit>и замените «pick» на втором и последующих коммитах на «squash» или «fixup», как описано в руководстве .

В этом примере <after-this-commit>это либо хеш SHA1, либо относительное местоположение из HEAD текущей ветви, из которой анализируются коммиты для команды rebase. Например, если пользователь желает просмотреть 5 коммитов из текущего HEAD в прошлом, команда имеет вид git rebase -i HEAD~5.


260
Это, я думаю, отвечает на этот вопрос немного лучше stackoverflow.com/a/5201642/295797
Рой Truelove

92
Что подразумевается под <after-this-commit>?
2540625

43
<after-this-commit>это коммит X + 1, т.е. родитель самого старого коммита, который вы хотите раздавить.
Joozek

340
Я нашел этот ответ слишком кратким, чтобы понять однозначно. Пример помог бы.
Ян Оллманн

54
Разница между этим rebase -iподходом и reset --softзаключается в том, что rebase -iя могу сохранить автора коммита, в то время как reset --softя могу подтвердить его. Иногда мне нужно раздавить коммиты по запросу, сохраняя при этом информацию об авторе. Иногда мне нужно сбросить софт на свои коммиты. В любом случае возбуждаю оба великолепных ответа.
zionyx

3835

Вы можете сделать это довольно легко без 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 на последний коммит, который вы не хотите раздавливать. Программный сброс не затрагивает ни индекс, ни рабочее дерево, оставляя индекс в желаемом состоянии для вашего нового коммита (т. Е. Он уже содержит все изменения из коммитов, которые вы собираетесь «выбросить»).


163
Ха! Мне нравится этот метод. Это тот, который закрывает дух проблемы. Жаль, что для этого нужно столько вуду. Нечто подобное должно быть добавлено к одной из основных команд. Возможно git rebase --squash-recentили даже git commit --amend-many.
Адриан Ратнапала

13
@ABB: Если ваша ветвь имеет «восходящий» набор, вы можете использовать ее branch@{upstream}(или только @{upstream}для текущей ветки; в обоих случаях последняя часть может быть сокращена до @{u}; см. Gitrevisions ). Это может отличаться от вашего «последнего выдвинутого коммита» (например, если кто-то выдвинул что-то, что построено поверх вашего самого последнего толчка, а затем вы его получили), но кажется, что это может быть близко к тому, что вы хотите.
Крис Джонсен

104
Это своего рода Сорта потребовал от меня, push -fно в остальном это было прекрасно, спасибо.
2rs2ts

39
@ 2rs2ts git push -f звучит опасно. Будьте осторожны, чтобы раздавить только местные коммиты. Никогда не прикасайтесь к нажатым коммитам!
Матиас М

20
Я также должен использовать git push --forceпотом, чтобы он взял коммит
Зак Saucier

740

Вы можете использовать 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предложенным Крисом Джонсеном в его ответе, состоит в том, что вы получаете сообщение о коммите, предварительно заполненное каждым сообщением о коммите, которое вы подавляете.


16
Вы говорите, что это более «элегантно» git rebase -i, но не приводите причину почему. Ориентировочно, потому что мне кажется, что на самом деле все наоборот, и это взлом; Разве вы не выполняете больше команд, чем необходимо, только для того, чтобы заставить вас git mergeвыполнить одну из задач, git rebaseспециально предназначенных для этого?
Марк Амери

78
@Mark Amery: Есть несколько причин, по которым я сказал, что это более элегантно. Например, это не включает ненужное создание редактора, а затем поиск и замену строки в файле «to-do». Использование git merge --squashтакже легче использовать в скрипте. По сути, причина была в том, что вам вообще не нужна «интерактивность» git rebase -i.
Марк Лонгэйр

12
Другое преимущество заключается в том, что git merge --squashменее вероятно возникновение конфликтов слияния при перемещении / удалении / переименовании по сравнению с перебазированием, особенно если вы выполняете слияние из локальной ветки. (Отказ от ответственности: основываясь только на одном опыте, поправьте меня, если это не так в общем случае!)
Cheezmeister

2
Я всегда очень неохотно отношусь к жесткому сбросу - я бы использовал временную метку вместо того, HEAD@{1}чтобы просто быть в безопасности, например, когда ваш рабочий процесс прерывается на час из-за отключения электроэнергии и т. Д.
Тобиас Кинцлер,

8
@BT: уничтожил твой коммит? :( Я не уверен, что ты имеешь в виду под этим. Все, что ты совершил, ты легко сможешь получить из реф-журнала git. Если у тебя была незафиксированная работа, но файлы были подготовлены, ты все равно сможешь получить их содержание обратно, хотя это будет больше работы . Если ваша работа даже не была организована, однако, я боюсь, что мало что можно сделать, поэтому ответ говорит заранее: «Сначала проверьте, что git-статус чистый (так как git reset --hard будет отбрасывать поэтапные и неустановленные изменения) ".
Марк Лонгэйр

218

Я рекомендую избегать, git resetкогда это возможно, особенно для Git-новичков. Если вам действительно не нужно автоматизировать процесс, основанный на ряде коммитов, есть менее экзотический способ ...

  1. Поместите коммиты, которые будут сдавлены, в рабочую ветку (если они еще не сделаны) - используйте для этого gitk
  2. Проверьте целевую ветку (например, «мастер»)
  3. git merge --squash (working branch name)
  4. git commit

Сообщение фиксации будет предварительно заполнено на основе сквоша.


4
Это самый безопасный метод: без сброса софт / хард (!!) или reflog!
TeChn4K

14
Было бы здорово, если бы вы расширили (1).
Адам

2
@Adam: По сути, это означает использование интерфейса графического интерфейса пользователя gitkдля обозначения строки кода, которую вы подавляете, а также для обозначения основы, на которую нужно нажимать. В обычном случае обе эти метки уже существуют, поэтому шаг (1) можно пропустить.
nobar

3
Обратите внимание, что этот метод не помечает рабочую ветвь как полностью объединенную, поэтому удаление ее требует принудительного удаления. :(
Kyrstellaine

2
Для (1) я нашел git branch your-feature && git reset --hard HEAD~Nнаиболее удобный способ. Тем не менее, он снова включает git reset, чего этот ответ пытался избежать.
EIS

134

Основываясь на ответе Криса Джонсена ,

Добавьте глобальный псевдоним "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можете изменить его вручную. (Или измените псевдоним в соответствии со своими вкусами.)


6
Интересно, но я бы предпочел сам напечатать сжатое сообщение о коммите в качестве описательной сводки моих нескольких коммитов, чем автоматически вводить его для меня. Так что я бы лучше определил git squash -m "New summary."и Nопределил автоматически как число невыдвинутых коммитов.
Acumenus

1
@ABB, это звучит как отдельный вопрос. (Я не думаю, что это именно то, о чем спрашивал ОП; я никогда не чувствовал в этом необходимости в моем
рабочем процессе

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

@funroll Согласен. Просто удаление последнего коммита MSG - это очень распространенная потребность для меня. Мы должны быть в состоянии придумать это ...
Стив Клэй

2
@ABB вы можете использовать git commit --amendдля дальнейшего изменения сообщения, но этот псевдоним позволяет вам хорошо начать с того, что должно быть в сообщении фиксации.
дашесы

131

Благодаря этому удобному сообщению в блоге я обнаружил, что вы можете использовать эту команду, чтобы раздавить последние 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.

При закрытии редактора исправление попадет в коммит функции, и моя история коммитов будет выглядеть красиво и чисто!

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

введите описание изображения здесь


5
Вы должны выбрать лучший и раздавить остальных? Вы должны отредактировать свой ответ, чтобы объяснить, как использовать интерактивный редактор rebase более подробно
Kolob Canyon

2
Да, оставьте pickв строке 1. Если вы выберите squashили fixupдля коммита в строке 1, git покажет сообщение «ошибка: невозможно исправить» без предыдущего коммита ». Затем он даст вам возможность исправить это: «Вы можете исправить это с помощью« git rebase --edit-todo »и затем запустить« git rebase --continue »». или вы можете просто прервать и начать заново: «Или вы можете прервать ребаз с помощью« git rebase --abort ».».
18.3

56

Если вы используете TortoiseGit, вы можете использовать функцию Combine to one commit:

  1. Открыть контекстное меню TortoiseGit
  2. Выбрать Show Log
  3. Отметьте соответствующие коммиты в представлении журнала
  4. Выберите Combine to one commitиз контекстного меню

Объединить коммиты

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


Насколько я знаю, это не будет работать для коммитов слияния.
Торкил Холм-Якобсен

1
Хотя это не комментируется другими, это работает даже для коммитов, которых нет в HEAD. Например, мне нужно было раздавить некоторые коммиты WIP, которые я сделал, с более вменяемым описанием, прежде чем нажать. Работал красиво. Конечно, я все еще надеюсь, что смогу научиться делать это с помощью команд.
Чарльз Роберто Канато

55

Для этого вы можете использовать следующую команду 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....

Для получения подробной информации нажмите на ссылку


48

Основываясь на этой статье, я нашел этот метод проще для моего сценария использования.

Моя ветка 'dev' опередила 'origin / dev' на 96 коммитов (поэтому эти коммиты еще не были перенесены на удаленный).

Я хотел раздавить эти коммиты в один, прежде чем приступить к изменениям. Я предпочитаю сбросить ветку в состояние origin / dev (это оставит все изменения из 96 коммитов без изменений), а затем зафиксировать изменения сразу:

git reset origin/dev
git add --all
git commit -m 'my commit message'

1
Как раз то, что мне было нужно. Squash down коммиты из моей ветки, а затем я делаю коммит вишни, который фиксирует мой мастер.
Дэвид Виктор

1
Это не раздавит предыдущие коммиты!
Игорь Ганапольский

не могли бы вы уточнить это немного дальше @igorGanapolsky?
труддольф

3
@trudolf На самом деле это не сквош (выбор отдельных коммитов на сквош). Это больше для фиксации всех ваших изменений одновременно.
Игорь Ганапольский

15
да, следовательно, он сводит все ваши коммиты в один. Поздравляем!
труддольф

45

В ветке, в которой вы хотите объединить коммиты, запустите:

git rebase -i HEAD~(n number of commits back to review)

пример:

git rebase -i HEAD~1

Это откроет текстовый редактор, и вы должны переключить «pick» перед каждым коммитом с помощью «squash», если вы хотите, чтобы эти коммиты были объединены вместе. Из документации:

p, pick = использовать коммит

s, squash = использовать коммит, но слиться с предыдущим коммитом

Например, если вы хотите объединить все коммиты в один, «пикап» - это первый коммит, который вы сделали, и все будущие (расположенные ниже первого) должны быть установлены на «сквош». Если используется vim, используйте : x в режиме вставки для сохранения и выхода из редактора.

Затем, чтобы продолжить ребаз:

git rebase --continue

Подробнее об этом и других способах переписывания истории коммитов читайте в этом полезном посте.


2
Пожалуйста, объясните, что делает --continuevim :x.
not2qubit

Перебазировка будет происходить в блоках по мере того, как она проходит через коммиты в вашей ветке, после того, как вы git addукажете правильную конфигурацию в ваших файлах, git rebase --continueчтобы перейти к следующему коммиту и начать слияние. :xэто одна команда, которая сохранит изменения в файле при использовании vim, смотрите это
aabiro

32

Ответ аномалии хороший, но я чувствовал себя неуверенно по этому поводу, поэтому я решил добавить пару скриншотов.

Шаг 0: Git Log

Посмотри, где ты git log. Самое главное, найти хэш коммита первого коммита, который вы не хотите раздавить. Так что только

введите описание изображения здесь

Шаг 1: git rebase

Выполните git rebase -i [your hash], в моем случае:

$ git rebase -i 2d23ea524936e612fae1ac63c95b705db44d937d

Шаг 2: выбрать / раздавить, что вы хотите

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

введите описание изображения здесь

Шаг 3: Настройте сообщение (я)

Если вы выбрали только один коммит и раздавили остальные, вы можете настроить одно сообщение о коммите:

введите описание изображения здесь

Вот и все. Как только вы сохраните это ( :wq), все готово. Посмотрите на это с git log.


2
было бы неплохо увидеть конечный результат, например,git log
Тимоти Л.Дж. Стюарт

2
Нет, спасибо. Это именно то, как я теряю свои коммиты.
Axalix

@Axalix Вы удалили все свои строки? Вот как вы теряете свои коммиты.
a3y3

31

Процедура 1

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), то приведенные выше команды такие же, кроме редактора. Вы можете получить другой редактор.

Процедура 2

  1. Сначала добавьте необходимые файлы для фиксации
git add <files>
  1. Затем совершите коммит, используя --fixupопцию, и OLDCOMMITмы должны объединить (сквош) этот коммит.
git commit --fixup=OLDCOMMIT

Теперь это создает новый коммит поверх HEAD с fixup1 <OLDCOMMIT_MSG>.

  1. Затем выполните приведенную ниже команду, чтобы объединить (сквош) новый коммит с OLDCOMMIT.
git rebase --interactive --autosquash OLDCOMMIT^

Здесь ^означает предыдущий коммит OLDCOMMIT. Эта rebaseкоманда открывает интерактивное окно в редакторе (vim или nano) о том, что нам не нужно ничего делать, просто сохранить и выйти достаточно. Поскольку переданная опция будет автоматически перемещать последнюю фиксацию на следующую за старой фиксацией и изменять операцию на fixup(эквивалентную сквошу). Затем перебазирование продолжается и заканчивается.

Процедура 3

  1. Если необходимо добавить новые изменения в последний коммит, --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

Вывод

  • Преимущество 1-й процедуры состоит в том, чтобы раздавить несколько коммитов и изменить порядок. Но эта процедура будет сложной, если нам нужно объединить исправление с очень старым коммитом.
  • Таким образом, вторая процедура помогает легко объединить коммит с очень старым коммитом.
  • И 3-я процедура полезна в том случае, если вы вносите новые изменения в последний коммит.

Он обновляет только последние два коммита, даже если я сбросил идентификатор коммита до 6-го последнего коммита, не знаю почему
Карлос Лю

Даже вы можете изменить порядок фиксации. Работает нормально.
рашок

29

Чтобы раздавить последние 10 коммитов в один коммит:

git reset --soft HEAD~10 && git commit -m "squashed commit"

Если вы также хотите обновить удаленную ветку с помощью сжатого коммита:

git push -f

--force опасно, когда над общей веткой работает несколько человек, поскольку она слепо обновляет удаленную локальную копию. --force-with-lease мог бы быть лучше, так как он гарантирует, что удаленный не имеет коммитов от других с момента последней загрузки.
Бхарат

27

Если вы находитесь в удаленной ветви (называемой feature-branch), клонированной из Золотого репозитория ( golden_repo_name), то вот метод, позволяющий объединить ваши коммиты в один:

  1. Оформить заказ на золотой репо

    git checkout golden_repo_name
    
  2. Создайте из него новую ветку (золотой репо) следующим образом

    git checkout -b dev-branch
    
  3. Сквош слиться с местным филиалом, который у вас уже есть

    git merge --squash feature-branch
    
  4. Зафиксируйте ваши изменения (это будет единственный коммит в ветке dev)

    git commit -m "My feature complete"
    
  5. Вставьте ветку в ваш локальный репозиторий

    git push origin dev-branch
    

Поскольку я просто подавлял ~ 100 коммитов (для синхронизации веток svn через git-svn), это намного быстрее, чем интерактивная перебазировка!
мудрец

1
Читая, я вижу комментарий @ Криса, который я обычно делал (rebase --soft ...) - очень плохо, что stackoverflow больше не ставит ответ с сотнями голосов вверху ...
мудрец

1
согласен с вами @sage, давайте надеяться, что они могут сделать это когда-нибудь в будущем
Сандеш Кумар

Это правильный путь. Подход Rebase хорош, но его следует использовать только для игры в сквош в качестве крайней меры.
Axalix

20

Что может быть действительно удобно:
скажем, найти хеш коммита, который вы хотите сжать поверх d43e15.

Сейчас использую

git reset d43e15
git commit -am 'new commit name'

2
Эта. Почему больше людей не используют это? Это намного быстрее, чем перебазирование и раздавливание отдельных коммитов.
a3y3

17

Это очень круто, но довольно круто, так что я просто брошу это на ринг:

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, но вы можете изменить это так, как вам нравится.)

Я бы, наверное, сделал то, что предложил Марк Лонгэйр .


7
+1: это забавно и поучительно, потому что для меня не было очевидно, что вы можете поместить что-нибудь более сложное, чем имя программы, в переменную окружения GIT_EDITOR.
Марк Лонгэйр

16

Если вы хотите объединить каждый коммит в один коммит (например, при первом публичном выпуске проекта), попробуйте:

git checkout --orphan <new-branch>
git commit

15

2020 Простое решение без перебазирования:

git reset --soft HEAD~2

git commit -m "new commit message"

git push --force

2 означает, что последние два коммита будут раздавлены. Вы можете заменить его на любой номер


14

Я думаю, что самый простой способ сделать это - создать новую ветку из master и выполнить слияние - сквош функциональной ветви.

git checkout master
git checkout -b feature_branch_squashed
git merge --squash feature_branch

Тогда у вас есть все изменения, готовые к фиксации.


12

Простой однострочный, который всегда работает, учитывая, что вы в данный момент находитесь в ветви, которую хотите сжать, master - это ветвь, из которой он возник, а последний коммит содержит сообщение о коммите и автора, которого вы хотите использовать:

git reset --soft $(git merge-base HEAD master) && git commit --reuse-message=HEAD@{1}

4
Я был совершенно в ярости от разочарования коммитов и того, насколько это глупо сложно - просто используйте последнее сообщение и раздавите их всех за один коммит! Почему это так сложно ???? Этот лайнер делает это для меня. Спасибо от всего сердца.
Локейн

12

например, если вы хотите раздавить последние 3 коммита в один коммит в ветке (удаленное хранилище), например: https://bitbucket.org

Что я сделал

  1. git reset --soft Head ~ 3 &&
  2. мерзавец совершить
  3. git push origin (branch_name) --force

3
Только будьте осторожны, так как если вы используете силу, то нет способа восстановить предыдущие коммиты, так как вы удалили его
Альберт Руелан

12

⚠️ ВНИМАНИЕ: «Мои последние X коммиты» могут быть неоднозначными.

  (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 вы видите это:

Четыре коммитов:

  • Добавить Дэнни Кирван
  • Добавить Кристина Идеал
  • LA1974
  • Билл Клинтон

Думая, что никто не захочет читать всю историю хранилища. (Там на самом деле есть хранилище, нажмите на ссылку выше!) Вы решили раздавить эти коммиты. Так ты иди и беги git reset --soft HEAD~4 && git commit. Затем вы git push --forceотправляете его на GitHub, чтобы очистить ваш PR.

А что происходит? Вы только что сделали один коммит от Фрица до Билла Клинтона. Потому что вы забыли, что вчера вы работали над версией Бекингемского Ника. И git logне соответствует тому, что вы видите на GitHub.

🐻 МОРАЛЬ ИСТОРИИ

  1. Найти точные файлы , которые вы хотите получить в , и git checkoutих
  2. Найдите точный предыдущий коммит, который вы хотите сохранить в истории, и git reset --softэто
  3. Сделайте git commitдеформацию непосредственно от к к

1
Это на 100% самый простой способ сделать это. Если ваш текущий заголовок - это то состояние, которое вы хотите, тогда вы можете пропустить # 1.
Стэн

Это единственный известный мне способ, который позволяет переписать историю первого коммита.
Vadorequest

9

Если вас не интересуют сообщения о коммитах промежуточных коммитов, вы можете использовать

git reset --mixed <commit-hash-into-which-you-want-to-squash>
git commit -a --amend

7

Если вы работаете с GitLab, вы можете просто щелкнуть опцию Squash в запросе на слияние, как показано ниже. Сообщение фиксации будет названием запроса на слияние.

введите описание изображения здесь



6

В дополнение к другим превосходным ответам, я хотел бы добавить, как git rebase -iвсегда меня смущает порядок коммитов - от старого к более новому или наоборот? Так что это мой рабочий процесс:

  1. git rebase -i HEAD~[N]где N - количество коммитов, к которым я хочу присоединиться, начиная с самого последнего . Так git rebase -i HEAD~5будет означать «раздавить последние 5 коммитов в новый»;
  2. появится редактор, показывающий список коммитов, которые я хочу объединить. Теперь они отображаются в обратном порядке : более старый коммит находится сверху. Отметьте как "squash" или "s" все коммиты, кроме первого / более старого : он будет использоваться в качестве отправной точки. Сохраните и закройте редактор;
  3. снова появится редактор с сообщением по умолчанию для нового коммита: измените его по своему усмотрению, сохраните и закройте. Сквош завершен!

Источники и дополнительные материалы: № 1 , № 2 .


6

Как насчет ответа на вопрос, связанный с таким рабочим процессом?

  1. много локальных коммитов, смешанных с несколькими слияниями от мастера ,
  2. наконец толчок к пульту,
  3. PR и слияние с мастером рецензентом . (Да, merge --squashпосле пиара разработчику будет легче , но команда подумала, что это замедлит процесс.)

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

Так что, похоже, это работает для нас.

  1. git pull master
  2. git checkout -b new-branch
  3. git checkout -b new-branch-temp
  4. редактировать и фиксировать много локально, регулярно объединять мастера
  5. git checkout new-branch
  6. git merge --squash new-branch-temp // помещает все изменения в стадию
  7. git commit 'one message to rule them all'
  8. git push
  9. Рецензент делает пиар и сливается с мастером.

Из многих мнений мне нравится ваш подход. Это очень удобно и быстро
Артем Соловьев

5

Я считаю, что более общим решением является не указание 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 должны с этим справиться.)


Я попробовал псевдоним, но я не уверен, что заменяет sed какой-либо эффект. Что они должны делать?
дождь

Первый sed просто сбрасывает историю в консоль. Второй sed заменяет все 'pick' на 'f' (fixup) и перезаписывает файл редактора на месте (опция -i). Так что второй делает всю работу.
Итан

Вы правы, подсчет N-го числа конкретных коммитов очень подвержен ошибкам. Это напортачило мне несколько раз и потратило впустую часы, пытаясь отменить ребаз.
Игорь Ганапольский

Привет, Итан, я хотел бы знать, скроет ли этот рабочий процесс возможные конфликты при слиянии. Поэтому, пожалуйста, подумайте, есть ли у вас две ветви: ведущая и подчиненная. Если у ведомого есть конфликт с ведущим, и мы используем, git squash masterкогда мы проверены на ведомом. что это будет происходить? мы будем скрывать конфликт?
Серхио Билелло

@Sergio Это случай переписывания истории, поэтому у вас, вероятно, возникнут конфликты, если вы сдавите коммиты, которые уже были переданы, а затем попытаетесь объединить / перебазировать сжатую версию обратно. (Некоторые тривиальные случаи могут сойти с рук.)
Итан

5

Это может быть неоднозначно, что подразумевается под «последним».

например 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 (потеря полностью фиксирует изменения после ветвления и до слияния.


4

1) git reset --soft HEAD ~ n

n - номер коммита, нужно сквош

2) git commit -m "новое сообщение коммита"

3) git push origin_name_force

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.