如何将所有git commit压缩为一个?


479

您如何压缩整个存储库直到第一次提交?

我可以基于第一次提交,但这会使我留下2次提交。有没有办法在第一个提交之前引用提交?


8
“第一个提交之前的提交”?
innaM,2009年

31
@innaM- 是从git发起的原始提交。(希望幽默通过网络足够好地通过)。
ripper234 2011年

11
对于以后要问这个问题的人,请务必使用更现代的答案
Droogans

1
相关的,但不是重复的(如果--root要压缩很多提交,实际上不是压缩所有提交的最佳解决方案):合并Git存储库的前两个提交?

2
Imo:这是@MrTux最好的:stackoverflow.com/questions/30236694/…
J0hnG4lt 2015年

Answers:


130

也许最简单的方法是使用工作副本的当前状态创建一个新的存储库。如果要保留所有提交消息,则可以先做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
但是您使用这种方法失去了分支机构
Olivier Refalo 2012年

150
自从给出答案以来,Git已经发展起来。不,有一种更简单更好的方法:git rebase -i --root。请参阅:stackoverflow.com/a/9254257/109618
David J.

5
在某些情况下,这可能会起作用,但从本质上来说,这并不是问题的答案。使用此配方,您也可以松开所有配置和所有其他分支。
iwein

3
这是一个可怕的解决方案,不必要地具有破坏性。请不要使用它。
Daniel Kamil Kozar 2015年

5
也将破坏子模块。-1
Krum

692

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

对于除第一个提交之外的每个提交,更改picksquash


49
请添加一个完整的,有效的命令示例,以回答原始问题。
杰克

29
我希望在读完我的整个存储库之前就读过这篇文章,就像接受的答案说的那样:/
Mike Chamberlain 2013年

38
这个答案是好的,但如果你交互比,也就是说,20个提交重订越多,互动变基将可能是太缓慢和笨拙。尝试压缩数百或数千个提交可能会很困难。在这种情况下,我将对根提交进行软复位或混合复位,然后重新提交。

20
@Pred不要squash用于所有提交。第一个需要是pick
Geert

14
如果您有很多提交,则很难手动将“ pick”更改为“ squash”。在VIM命令行中使用:%s / pick / squash / g可以更快地执行此操作。
eilas

314

更新资料

我做了一个别名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"

警告:请记住要提供注释,否则将使用默认的提交消息“ A new start”。

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

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上发出新的提交对象ID。除非给出-m或-F选项,否则将从标准输入中读取日志消息。

    该表达式HEAD^{tree}表示与对应的树对象HEAD,即当前分支的尖端。请参见Tree-ObjectsCommit-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修订语法(HEAD ^ {tree})语法在此处解释,以防万一其他人想知道:jk.gs/gitrevisions.html
Colin Bowern 2014年

1
这会重设本地和远程存储库,还是只是其中之一?
aleclarson

4
@aleclarson,这只会重置本地存储库中的当前分支,git push -f用于传播。
ryenus 2014年

2
在寻找从不涉及的项目模板存储库开始新项目的方法时,我找到了这个答案git clone。如果添加--hardgit reset并在中切换HEADFETCH_HEADgit commit-tree可以在获取模板存储库后创建初始提交。我已经在答案的最后部分演示了这一点。
toolbear 2015年

4
您可以摆脱该“注意事项”,而只需使用${1?Please enter a message}
Elliot Cameron

172

如果您只想将所有提交压缩为根提交,则

git rebase --interactive --root

可以正常工作,对于大量提交(例如,数百次提交)来说是不切实际的,因为重新生成基础操作可能会非常缓慢地运行,以生成交互式重新生成编辑器提交列表,以及运行重新生成自身。

当您压缩大量提交时,有两个更快,更有效的解决方案:

替代解决方案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
Eddy Verbruggen's

1
不应该忘记git push --force
NecipAllef 18-3-28

如果您要推送到开放的Github拉取请求,请不要对代码执行孤立分支(即,不要执行上面的替代解决方案#1)!Github将关闭您的PR,因为当前的头部不是存储的头部sha的后代
Andrew Mackie

替代解决方案#1还避免了挤压提交时可能发生的合并冲突
FernAndr

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

这将创建一个带有HEAD树的孤立提交,并在stdout上输出其名称(SHA-1)。然后在那儿重置您的分支。

git reset SHA-1

27
git reset $(git commit-tree HEAD^{tree} -m "commit message")会更容易。
ryenus

4
^这!-应该是一个答案。不能完全确定这是否是作者的意图,而是我的意图(需要一次提交的原始回购,然后才能完成工作)。
chesterbr 2014年

@ryenus,您的解决方案完全符合我的要求。如果您将评论添加为答案,我会接受。
tldr

2
我自己不建议使用subshel​​l变体的原因是,它在Windows中的cmd.exe上不起作用。
Kusma 2014年

在Windows提示符下,您可能必须引用最后一个参数: echo "message" | git commit-tree "HEAD^{tree}"
Bernard

41

这是我最终要这样做的方式,以防万一它适用于其他人:

请记住,这样做总是有风险的,在开始之前创建保存分支从来都不是坏主意。

从记录开始

git log --oneline

滚动到第一次提交,复制SHA

git reset --soft <#sha#>

替换<#sha#>从日志复制的SHA

git status

确保所有内容均为绿色,否则运行 git add -A

git commit --amend

将所有当前更改修改为当前第一次提交

现在强行推动此分支,它将覆盖那里的内容。


1
极好的选择!真的很简单。
twojr

1
这真是太棒了!谢谢!
马特·科马尼克

1
请注意,这实际上似乎遗漏了历史。您孤立了它,但它仍然存在。
布拉德(Brad)

很有用的答案...但是您应该知道,在amend命令之后,您会发现它具有特殊的语法,在vim编辑器中。ESC,ENTER,:x是您的朋友。
Erich Kuester

极好的选择!
danivicario

36

我读了一些有关使用嫁接的文章,但从未对此进行过多研究。

无论如何,您可以使用以下方法手动压缩最后2次提交:

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

4
这实际上是我想要的答案,希望它被接受!
杰伊,

这是一个了不起的答案
尤达大师

36

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

您不能使用git branch -D它,因为它具有安全阀,可以阻止您删除当前分支。

这将使您回到“初始提交”状态,您可以在其中进行新的初始提交。

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



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

假设除了创建一个提交(无父母无历史记录)之外,您希望保留该提交的所有提交数据,那么上述答案在上面的几个方面有所改进(请对它们进行投票):

  • 作者(姓名和电子邮件)
  • 创作日期
  • 提交人(姓名和电子邮件)
  • 提交日期
  • 提交日志消息

当然,新/单次提交的commit-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存储库重置为第一个提交主题标签,因此该提交之后的所有更改都将被取消登台,然后可以使用--amend选项进行提交。

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

然后根据需要编辑第一个提交名称并保存文件。


1

对我来说,它的工作方式是这样的:我总共进行了4次提交,并使用了交互式rebase:

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


0

我通常这样做:

  • 确保所有内容都已提交,并记下最新的提交ID,以防出现问题,或者创建单独的分支作为备份

  • 运行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到力推更改

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.