Как раздавить все git коммиты в один?


481

Как вы сократите весь свой репозиторий до первого коммита?

Я могу вернуться к первому коммиту, но это оставило бы меня с 2 коммитами. Есть ли способ ссылаться на коммит до первого?


8
"совершить до первого"?
— InnaM

31
@innaM - Изначальный коммит породил мерзавца . (Надежды юмора достаточно хорошо проходят через межплетение).
— ripper234

11
Для тех, кто придет позже к этому вопросу, обязательно используйте более современный ответ .
— Дрооганс

1
Связанный, но не дубликат ( --rootна самом деле не лучшее решение для сжатия всех коммитов, если их много для сквоша): Объединить первые два коммита Git-репозитория? ,

2
Имо: это лучшее из @MrTux: stackoverflow.com/questions/30236694/… .
— J0hnG4lt

Ответы:


129

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

rm -rf .git
git init
git add .
git commit

или

git log > original.log
# edit original.log as desired
rm -rf .git
git init
git add .
git commit -F original.log

62
но вы теряете ветви с помощью этого метода
— Оливье Рефало

150
Git эволюционировал с тех пор, как был дан этот ответ. Нет, есть более простой и лучший способ git rebase -i --root. См .: stackoverflow.com/a/9254257/109618
— Дэвид Дж

5
Это может работать в некоторых случаях, но по сути это не ответ на вопрос. С этим рецептом вы теряете всю свою конфигурацию и все другие ветви.
— iwein

3
Это ужасное решение, которое без необходимости разрушительно. Пожалуйста, не используйте это.
— Даниэль Камиль Козар

5
и сломает подмодули. -1
— Крум,

694

Начиная с git 1.6.2 , вы можете использовать git rebase --root -i.

Для каждого коммита, кроме первого, измените pickна squash.


49
Пожалуйста, добавьте полный рабочий пример команды, который отвечает на исходный вопрос.
— Джейк

29
Я хотел бы прочитать это, прежде чем выбросить весь свой репозиторий, как принято в ответе: /
— Майк Чемберлен

38
Этот ответ в порядке , но если вы интерактивно перебазируете больше, чем, скажем, 20 коммитов, интерактивная перебазировка, вероятно, будет слишком медленной и громоздкой. Вам, вероятно, придется нелегко пытаться раздавить сотни или тысячи коммитов. Я бы пошел с мягким или смешанным сбросом к корневому коммиту, а затем повторил в этом случае.

20
@Pred Не использовать squashдля всех коммитов . Самый первый должен быть pick.
— Герт

14
Если у вас много коммитов, трудно поменять 'pick' на 'squash' вручную. Используйте :% s / pick / squash / g в командной строке VIM, чтобы сделать это быстрее.
— Эйлас

315

Обновить

Я сделал псевдоним git squash-all.
Пример использования : git squash-all "a brand new start".

[alias]
  squash-all = "!f(){ git reset $(git commit-tree HEAD^{tree} -m \"${1:-A new start}\");};f"

Предостережение : не забудьте предоставить комментарий, в противном случае будет использоваться сообщение о фиксации по умолчанию «Новое начало».

Или вы можете создать псевдоним с помощью следующей команды:

git config --global alias.squash-all '!f(){ git reset $(git commit-tree HEAD^{tree} -m "${1:-A new start}");};f'

Один лайнер

git reset $(git commit-tree HEAD^{tree} -m "A new start")

Примечание : здесь " A new start" это просто пример, не стесняйтесь использовать свой собственный язык.

TL; DR

Не нужно раздавливать, использовать git commit-treeдля создания коммитов сирот и идти с ним.

объяснять

  1. создать один коммит через git commit-tree

    Что git commit-tree HEAD^{tree} -m "A new start"это такое:

    Создает новый объект фиксации на основе предоставленного объекта дерева и генерирует новый идентификатор объекта фиксации в stdout. Сообщение журнала читается из стандартного ввода, если не указаны опции -m или -F.

    Выражение HEAD^{tree}означает соответствующий объект дерева HEAD, а именно кончик вашей текущей ветви. см. Tree-Objects и Commit-Objects .

  2. сбросить текущую ветку на новый коммит

    Затем git resetпросто сбросьте текущую ветку на вновь созданный объект коммита.

Таким образом, ничего в рабочей области не затрагивается, и нет необходимости в ребазе / сквоше, что делает его действительно быстрым. И необходимое время не зависит от размера хранилища или глубины истории.

Вариация: новый репо из шаблона проекта

Это полезно для создания «начального коммита» в новом проекте с использованием другого хранилища в качестве шаблона / архетипа / семени / скелета. Например:

cd my-new-project
git init
git fetch --depth=1 -n https://github.com/toolbear/panda.git
git reset --hard $(git commit-tree FETCH_HEAD^{tree} -m "initial commit")

Это позволяет избежать добавления репо-шаблона в качестве удаленного ( originили иного) и сворачивает историю репо-шаблона в исходную фиксацию.


6
Синтаксис git revision (HEAD ^ {tree}) объясняется здесь на случай, если кому-то еще будет интересно: jk.gs/gitrevisions.html
— Колин

1
Сбрасывает ли это как локальный, так и удаленный репозиторий или только один из них?
— aleclarson

4
@aleclarson, это только сбрасывает текущую ветку в локальном репозитории, используется git push -fдля распространения.
— ryenus

2
Я нашел этот ответ, когда искал способ начать новый проект из репозитория шаблонов проектов, который не включал git clone. Если добавить --hardк git resetи переключатель HEADс FETCH_HEADв git commit-treeвы можете создать первоначальное обязательство после загрузки шаблона репо. Я отредактировал ответ с разделом в конце, демонстрирующим это.
— toolbear

4
Вы могли бы избавиться от этого "Caveat", но просто используя${1?Please enter a message}
— Эллиот Кэмерон

172

Если все, что вы хотите сделать, это сжать все ваши коммиты до корневого коммита, тогда

git rebase --interactive --root

может работать, это нецелесообразно для большого количества коммитов (например, сотен коммитов), потому что операция rebase, вероятно, будет выполняться очень медленно, чтобы сгенерировать список коммитов редактора rebase редактора, а также запустить сам rebase.

Вот два более быстрых и эффективных решения, когда вы подавляете большое количество коммитов:

Альтернативное решение № 1: сиротские ветви

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

git checkout --orphan new-master master
git commit -m "Enter commit message for your new initial commit"

# Overwrite the old master branch reference with the new one
git branch -M new-master master

Документация:

Альтернативное решение № 2: мягкий сброс

Другое эффективное решение - просто использовать смешанный или программный сброс в корневой коммит <root>:

git branch beforeReset

git reset --soft <root>
git commit --amend

# Verify that the new amended root is no different
# from the previous branch state
git diff beforeReset

Документация:


22
Альтернативное решение № 1: сиротские ветви - камни!
— Томас

8
Альтернативное решение № 1 FTW. Просто чтобы добавить, если вы хотите отправить свои изменения на пульт, сделайте git push origin master --force.
— Эдди Вербругген,

1
Не следует забыватьgit push --force
— NecipAllef

Не делайте бесхозную ветвь в коде (то есть не делайте альтернативное решение № 1 выше), если вы собираетесь отправить открытый запрос на Github !!! Github закроет ваш PR, потому что текущая голова не является потомком сохраненной головы ша .
— Эндрю Маки

Альтернативное решение № 1 также позволяет избежать конфликтов слияния, которые могут возникнуть при фиксации
— сквоша

52
echo "message" | git commit-tree HEAD^{tree}

Это создаст потерянный коммит с деревом HEAD и выведет его имя (SHA-1) на стандартный вывод. Тогда просто перезагрузите свою ветку там.

git reset SHA-1

27
git reset $(git commit-tree HEAD^{tree} -m "commit message")сделает это проще.
— Ryenus

4
^ ЭТО! - должен быть ответ. Не совсем уверен, что это было намерение автора, но мое (нужно было нетронутое репо с одним коммитом, и это сделало работу).
— chesterbr

@ryenus, твое решение сделало именно то, что я искал. Если вы добавите свой комментарий в качестве ответа, я приму его.
— Tldr

2
Причина, по которой я сам не предложил вариант subshell, заключается в том, что он не будет работать с cmd.exe в Windows.
— Кусма

В приглашениях Windows может потребоваться заключить в кавычки последний параметр: echo "message" | git commit-tree "HEAD^{tree}"
— Бернард

41

Вот как я это сделал, на случай, если это сработает для кого-то другого:

Помните, что всегда есть риск сделать что-то подобное, и никогда не плохая идея создать ветку сохранения перед запуском.

Начните с входа

git log --oneline

Выделите первый коммит, скопируйте SHA

git reset --soft <#sha#>

Заменить <#sha#>с SHA, скопированной из журнала

git status

Убедитесь, что все зеленое, иначе бегите git add -A

git commit --amend

Изменить все текущие изменения в текущем первом коммите

Теперь принудительно нажмите эту ветку, и она перезапишет то, что там.


1
Отличный вариант! Действительно просто.
— дважды

1
Это более чем фантастично! Спасибо!
— Мэтт Комарницки

1
Обратите внимание, что это на самом деле, кажется, оставляет историю вокруг. Вы осиротели, но это все еще там.
— Брэд

Очень полезный ответ ... но вы должны знать, что после команды исправления вы окажетесь в редакторе vim с его специальным синтаксисом. ESC, ENTER,: х твой друг.
— Эрих

Отличный вариант!
— Данивикарио

36

Я читал кое-что об использовании трансплантатов, но никогда не исследовал это много.

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

git reset HEAD~1
git add -A
git commit --amend

4
Это на самом деле ответ, который я искал, хотелось бы, чтобы он был принят!
— Джей

Это такой удивительный ответ
— Мастер Йода

36

Самый простой способ - использовать команду «сантехника» update-refдля удаления текущей ветви.

Вы не можете использовать, git branch -Dпоскольку он имеет предохранительный клапан, чтобы остановить удаление текущей ветви.

Это вернет вас в состояние «начальная фиксация», где вы можете начать с новой первоначальной фиксации.

git update-ref -d refs/heads/master
git commit -m "New initial commit"

16

Во-первых, раздавите все ваши коммиты в один коммит, используя git rebase --interactive. Теперь у вас осталось два коммита для сквоша. Для этого прочитайте любой из


15

В одной строке 6 слов

git checkout --orphan new_root_branch  &&  git commit

@AlexanderMills, вы должны прочитать git help checkoutо--orphan
— kyb

можете ли вы ссылаться на документы для этого, чтобы каждый, кто читает это, не должен был искать его вручную?
— Александр Миллс

1
легко. вот оноgit help checkout --orphan
— киб

8

создать резервную копию

git branch backup

сброс до указанного коммита

git reset --soft <#root>

затем добавьте все файлы в постановку

git add .

совершить без обновления сообщения

git commit --amend --no-edit

протолкнуть новую ветку с раздавленными коммитами в репо

git push -f

Сохранит ли это предыдущие сообщения коммита?
— not2qubit

1
@ not2qubit нет, это не сохранит предыдущие сообщения о коммите, вместо коммита № 1, коммита № 2, коммита № 3 вы получите все изменения в этих коммитах, упакованные в один коммит № 1. Коммит № 1 будет <root>коммитом, к которому вы вернетесь. git commit --amend --no-editпередаст все изменения в текущий коммит, который <root>не требует редактирования сообщения коммита.
— Дэвид Мортон

5

Сквош, используя трансплантаты

Добавьте файл .git/info/grafts, поместите туда хеш коммита, который вы хотите использовать в качестве корня

git log Теперь начнем с этого коммита

Чтобы сделать это «настоящий» бег git filter-branch


1

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

  • Автор (имя и адрес электронной почты)
  • Дата создания
  • Коммиттер (имя и адрес электронной почты)
  • Дата совершения
  • Записать сообщение в журнал

Конечно, коммит-SHA нового / одиночного коммита изменится, потому что он представляет новую (не) историю, становясь бездетным / корневым коммитом.

Это можно сделать, прочитав git log и установив некоторые переменные для git commit-tree. Предполагая, что вы хотите создать один коммит masterв новой ветке one-commit, сохраняя данные коммита выше:

git checkout -b one-commit master ## create new branch to reset
git reset --hard \
$(eval "$(git log master -n1 --format='\
COMMIT_MESSAGE="%B" \
GIT_AUTHOR_NAME="%an" \
GIT_AUTHOR_EMAIL="%ae" \
GIT_AUTHOR_DATE="%ad" \
GIT_COMMITTER_NAME="%cn" \
GIT_COMMITTER_EMAIL="%ce" \
GIT_COMMITTER_DATE="%cd"')" 'git commit-tree master^{tree} <<COMMITMESSAGE
$COMMIT_MESSAGE
COMMITMESSAGE
')

1

Я обычно делаю это так:

  • Убедитесь, что все зафиксировано, и запишите последний идентификатор фиксации на случай, если что-то пойдет не так, или создайте отдельную ветку в качестве резервной копии.

  • Запустите, git reset --soft `git rev-list --max-parents=0 --abbrev-commit HEAD`чтобы сбросить голову на первый коммит, но оставьте свой индекс без изменений. Все изменения, произошедшие после первой фиксации, теперь будут готовы к фиксации.

  • Запустите, git commit --amend -m "initial commit"чтобы изменить ваш коммит на первый коммит и изменить сообщение коммита, или, если вы хотите сохранить существующее сообщение коммита, вы можете запуститьgit commit --amend --no-edit

  • Беги, git push -fчтобы форсировать твои изменения


1

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

git reset your-first-commit-hashtag
git add .
git commit --amend

А затем отредактируйте первый коммит, если необходимо, и сохраните файл.


1

Для меня это работало так: у меня было всего 4 коммита, и я использовал интерактивный ребаз:

git rebase -i HEAD~3

Самый первый коммит остается, и я взял 3 последних коммита.

Если вы застряли в редакторе, который появляется рядом, вы видите что-то вроде:

pick fda59df commit 1
pick x536897 commit 2
pick c01a668 commit 3

Вы должны взять первый коммит и раздавить других на него. То, что вы должны иметь, это:

pick fda59df commit 1
squash x536897 commit 2
squash c01a668 commit 3

Для этого используйте клавишу INSERT, чтобы изменить режим «вставки» и «редактирования».

Для сохранения и выхода из редактора используйте :wq. Если ваш курсор находится между этими строками фиксации или где-то еще, нажмите ESC и попробуйте снова.

В результате у меня было два коммита: самый первый, который остался, и второй с сообщением «Это комбинация из 3 коммитов».

Проверьте подробности здесь: https://makandracards.com/makandra/527-squash-several-git-commits-into-a-single-commit

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