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

481

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

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

Verhogen
источник
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или иного) и сворачивает историю репо-шаблона в исходную фиксацию.

ryenus
источник
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
kusma
источник
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"
CB Bailey
источник
16

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

Фрерих Раабе
источник
15

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

git checkout --orphan new_root_branch  &&  git commit
KYB
источник
@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

Dimus
источник
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
')
javabrett
источник
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

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

T.EL MOUSSAOUI
источник
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

Влад
источник