如何将所有的git提交压缩为一个?

752

如何将整个代码库压缩到第一个提交?

我可以将其变基到第一个提交,但这样会留下两个提交。是否有一种方法可以引用第一个提交之前的提交?


42
@innaM - 这是原初提交(Primordial commit)创造了Git。(希望幽默感能够通过互联网传递得足够好)。 - ripper234
14
对于后来者,务必使用更现代的答案 - yurisich
1
那个现代的答案并不是最好的,没有压缩开销的更好的解决方案是https://dev59.com/CnI-5IYBdhLWcg3w0cKG。 - ryenus
2
我认为这是@MrTux的最佳答案:https://dev59.com/EYrda4cB1Zd3GeqPRtmI。 - J0hnG4lt
1
我知道问题要求采用压缩方法,但也可以使用以下命令:´rm -rf .git´和´git commit -m“新消息”´。 - Andres Zapata
显示剩余2条评论
24个回答

971

git 1.6.2 开始,你可以使用 git rebase --root -i

对于第一个提交以外的每个提交,将 pick 改为 squash


3
1.7.12版本更新内容如下:https://github.com/git/git/blob/master/Documentation/RelNotes/1.7.12.txt - serbaut
84
这个回答还好,但是如果你要交互式地变基超过20个提交,那么交互式变基可能会非常缓慢和笨重。你可能会很难尝试合并数百或数千个提交。在这种情况下,我建议使用软重置或混合重置到根提交,然后重新提交。 - user456814
42
如果您有许多提交记录,手动将“pick”更改为“squash”可能会很麻烦。在VIM命令行中使用_:%s/pick/squash/g_ 可以更快地完成此操作,无需手动修改每个提交。 - eilas
5
这个操作可以正常工作,但我不得不进行强制推送。请小心!git push -f - philshem
3
@eilas 不需要 g。你只想要替换每行中第一个出现的单词 pick。更好的方法是 :%s/^pick/squash/ - user137369
显示剩余5条评论

532

Bash/Zsh 一行命令

你可以从 HEAD 直接创建一个 squash-all commit完全不需要 rebase,只需运行以下命令:


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

注意:这需要一个类似于bash/zsh的POSIX兼容shell,或者在Windows上使用Git Bash

~/.gitconfig中创建别名

[alias]
  squash-all = "!f(){ git reset $(git commit-tree HEAD^{tree} \"$@\");};f"

然后只需运行:git squash-all -m“全新的开始”

注意:要么从标准输入提供提交消息,要么通过-m/-F选项提供,就像git commit命令一样。请参见git-commit-tree手册

或者,您可以使用以下命令创建别名:

git config --global alias.squash-all '!f(){ git reset $(git commit-tree HEAD^{tree} "$@");};f'

解释

  1. 通过git commit-tree创建单个提交

    git commit-tree HEAD^{tree} -m "A new start"的作用是:

    基于提供的树对象创建新的提交对象,并将新的提交对象ID输出到标准输出。如果没有给出-m或-F选项,则从标准输入读取日志消息。

    表达式HEAD^{tree}表示与HEAD对应的树对象,即当前分支的末端。参见Tree-ObjectsCommit-Objects

  2. 将当前分支重置为新提交

    然后git reset将当前分支简单地重置为新创建的提交对象。

    这种方法不会触及工作区,也不需要rebase/squash操作,所以速度非常快。而且所需的时间与存储库大小或历史深度无关。


变体:使用项目模板创建新存储库

这对于使用其他存储库作为模板/原型/种子/骨架创建新项目的“初始提交”非常有用。例如:

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或其他)添加,同时将模板仓库的历史记录合并到你的初始提交中。


8
这里解释了git修订语法(HEAD^{tree})的语法,以防其他人也想知道:http://jk.gs/gitrevisions.html - Colin Bowern
2
这个操作会重置本地和远程仓库吗?还是只有其中一个被重置了? - aleclarson
6
@aleclarson,这只重置了本地代码库中的当前分支,请使用 git push -f 进行推送以进行传播。 - ryenus
2
我在寻找一个不需要使用 git clone 的项目模板存储库来启动新项目的方法时找到了这个答案。如果你在 git reset 中添加 --hard,并将 HEADFETCH_HEADgit commit-tree 中交换,那么在获取模板存储库后就可以创建一个初始提交。我在答案的最后编辑了一个部分来演示这一点。 - user23987
6
您可以使用 ${1?请输入一条信息} 来替换掉那个 "Caveat"。此举不会改变原有含义,同时使语义更加通俗易懂。 - Elliot Cameron
显示剩余5条评论

286
如果你只想将所有的提交压缩成根提交,那么在执行以下命令时:
git rebase --interactive --root

虽然git的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

文档:


53
备选方案1:孤立分支 - 非常棒! - Thomas
13
替代方案 #1 胜利。只是补充一下,如果你想将你的更改推送到远程,请执行 git push origin master --force - Eddy Verbruggen
3
不要忘记 git push --force - NecipAllef
2
如果你即将推送到一个开放的Github拉取请求,请不要在代码上进行孤立分支(即不要执行上面的替代方案#1)!!! Github会关闭您的PR,因为当前头部不是存储的头部sha的后代 - Andrew Mackie
1
替代方案#1还避免了在压缩提交时可能发生的合并冲突 - FernAndr
显示剩余6条评论

170

也许最简单的方法就是使用当前工作副本的状态创建一个新仓库。如果您想保留所有提交消息,可以先执行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

78
但是您使用这种方法会失去枝干。 - Olivier Refalo
213
自这个答案发布以来,Git已经发生了改变。现在有一种更简单、更好的方法:git rebase -i --root。参见:https://dev59.com/CnI-5IYBdhLWcg3w0cKG#9254257。 - David J.
11
这个方法可能适用于某些情况,但它并不是问题的答案。使用这个方法,你会失去所有的配置和所有其他分支。 - iwein
18
这个解决方案很糟糕,毫无必要地具有破坏性。请不要使用它。 - Daniel Kamil Kozar
11
也会破坏子模块。-1 - Krum
显示剩余10条评论

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

这将创建一个孤立的提交,其树对象与HEAD相同,并将其名称(SHA-1)输出到标准输出。然后只需将你的分支重置到该提交上即可。

git reset SHA-1

要一步完成以上操作:

git reset $(git commit-tree HEAD^{tree} -m "Initial commit.")

41
使用 git reset $(git commit-tree HEAD^{tree} -m "commit message") 将会更加方便。 - ryenus
7
“^ THIS! - should be an answer. Not entirely sure if it was the author's intention, but was mine (needed a pristine repo with a single commit, and that gets the job done).”翻译:「^ THIS!」应该是一个回答。不确定作者是否有此意,但这是我的意图(需要一个只有一个提交记录的原始版本库,这样做可以达到目的)。 - chesterbr
@ryenus,你的解决方案正是我所需要的。如果你把你的评论作为答案添加进来,我会接受它。 - tldr
2
我没有建议使用子shell变体的原因是它在Windows的cmd.exe上无法工作。 - kusma
1
在Windows提示符中,您可能需要引用最后一个参数:echo "message" | git commit-tree "HEAD^{tree}" - Bernard
在我看来,无论如何在Windows中都应该使用带有mintty的git bash。 - denns

48

这是我用的方法,希望对其他人有用:

记住这样做总会存在风险,在开始之前创建一个保存分支永远不是坏主意。

首先进行登录

git log --oneline

滚动到第一次提交,复制SHA。
git reset --soft <#sha#>

将从日志中复制的 SHA 替换为 <#sha#>
git status

确保一切都是绿色的,否则运行git add -A

git commit --amend

修改所有当前更改为当前首次提交

现在强制推送此分支,它将覆盖原有内容。


1
请注意,这似乎实际上会保留历史记录。您将其孤立,但它仍然存在。 - Brad
非常有用的答案...但你应该知道,在修改命令之后,你会进入 vim 编辑器,使用其特殊的语法。ESC、ENTER、:x 是你的好朋友。 - Erich Kuester

47

我读到有关使用修补合并(grafts)的内容,但从未深入研究。

无论如何,您可以手动使用以下类似代码压缩最后2个提交:

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

37

最简单的方法是使用“管道”命令update-ref删除当前分支。

你不能使用git branch -D,因为它有一个安全阀来阻止你删除当前分支。

这会将你带回到“初始提交”状态,你可以从新的初始提交开始。

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

27

用六个单词概括:

git checkout --orphan new_root_branch  &&  git commit

@AlexanderMills,你应该阅读 git help checkout 有关于 --orphan 的内容。 - kyb
你能否提供相关文档的链接,这样阅读此内容的每个人都不必手动搜索吗? - Alexander Mills
2
很简单,这是关于编程的内容。git help checkout --orphan - kyb

17

创建备份

git branch backup

重置到指定提交

git reset --soft <#root>

然后将所有文件添加到暂存区

git add .

提交时不更新信息

git commit --amend --no-edit

将压缩后的提交推送到存储库中的新分支

git push -f

这会保留之前的提交信息吗? - not2qubit
1
@not2qubit 不,这不会保留之前的提交消息。相反,您将得到所有更改打包到单个提交#1中,而不是提交#1,提交#2,提交#3。提交#1将是您重置回去的<root>提交。 git commit --amend --no-edit将提交所有更改到当前提交<root>,无需编辑提交消息。 - David

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接