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

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

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

25 ответов

Решение

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

В последних версиях Git вы можете использовать git rebase --root -i,

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

Обновить

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

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

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

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

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

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

git reset SHA-1

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

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

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

git log --oneline

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

git reset --soft <#sha#>

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

git status

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

git commit --amend

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

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

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

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

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

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

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

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

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

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

git checkout --orphan new_root_branch  &&  git commit

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

git branch backup

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

git reset --soft <#root>

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

git add .

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

git commit --amend --no-edit

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

git push -f

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

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

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

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

Допустим, у вас есть 3 коммита в вашей ветке, и они уже отправлены в удаленную ветку.

Пример:

      git log -4

Будет отображать такие результаты, как:

      <your_third_commit_sha>
<your_second_commit_sha>
<your_first_commit_sha>
<master_branch_commit_sha - your branch created from master>

Вы хотите сжать свои последние 3 коммита до одного коммита и нажать на удаленную ветку. Вот шаги.

      git reset --soft <master_branch_commit_sha>

Теперь все изменения коммитов интегрированы, но не зафиксированы. Подтвердить:

      git status

Зафиксируйте все изменения с сообщением:

      git commit -m 'specify details'

Принудительно переместите одиночный коммит в удаленную ветку:

      git push -f

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

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

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

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

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

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

  • Бежать 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 заставить толкнуть ваши изменения

Допустим, у вас есть эти коммиты с 06 по 10.

Вы хотите объединить коммиты post 06 с одним идентификатором коммита.

      commit 10
commit 09  
commit 08  
commit 07  
commit 06

Вы можете сделать git reset soft.

      git reset --soft "06"

Затем выполните приведенную ниже команду, чтобы отправить эти изменения в удаленную ветку.

      git push origin HEAD --force

Теперь все коммиты, которые вы сделали ранее, должны быть доступны как ваши локальные изменения, и вы можете объединить все эти коммиты в один коммит.

Теперь новая структура коммита должна выглядеть следующим образом:

      commit <new_commit_containing_all_7_8_9_10>
commit 06

Для меня это работало так: у меня было всего 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

Я пробовал разные команды с HEAD^{tree}, но получил эту ошибку от zsh:

      zsh: no matches found: HEAD^{tree}

Это было то, как ohmyzsh справляется с расширением, см. этот выпуск для получения более подробной информации: https://github.com/ohmyzsh/ohmyzsh/issues/449 .

Самое простое решение - запустить эту команду:

      unsetopt nomatch

Я обычно сжимаю все дерево, когда восстанавливаю шаблон из репозитория git, чтобы получить более чистую историю и обеспечить соблюдение правовых норм. Мой рабочий процесс выглядит так:

      git clone https://git.invalid/my-awesome-template.git my-awesome-foo
cd !$
      git branch -M master my-awesome-template/master
git checkout --orphan master
git rm -rf /
git commit --allow-empty --allow-empty-message -m 'Initial commit'
git merge --squash --allow-unrelated-histories my-awesome-template/master
git commit
git branch -D my-awesome-template/master
# you can now `drop' that "Initial commit":
git rebase -i --root

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

В этом конкретном примере:

  • master это рабочая ветка
  • my-awesome-template/master это промежуточная ветвь

Это сработало для меня лучше всего.

git rebase -X ours -i master

Это будет git предпочтет, чтобы ваша ветка функций была master; избегая трудных правок слиянием. Ваша ветка должна быть обновлена ​​с помощью мастера.

ours
           This resolves any number of heads, but the resulting tree of the merge is always that of the current
           branch head, effectively ignoring all changes from all other branches. It is meant to be used to
           supersede old development history of side branches. Note that this is different from the -Xours
           option to the recursive merge strategy.

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

git merge origin/master && git reset --soft origin/master

Предыдущий cmd слияния гарантирует, что никакие недавние изменения из мастера не пойдут вам на голову при фиксации! После этого просто зафиксируйте изменения и выполните git push -f

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

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

Конечно, коммит-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 rebase --interactive --root" застрял в конфликте файлов с gitignored.

На мой взгляд самый простой способ:

      git checkout --orphan <your new branch>

Ответ, содержащий эту идею

Более технический, хотя и общий способ:

Общий способ сжать коммиты

Чтобы объединить все коммиты в один, выполните следующие команды.

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

      git clone --depth 1 <remote-url> .
git commit --amend -m <commit message you want>
git push --force
Другие вопросы по тегам