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

Posted

技术标签:

【中文标题】如何将所有 git 提交压缩为一个?【英文标题】:How to squash all git commits into one? 【发布时间】:2010-12-12 00:53:15 【问题描述】:

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

我可以变基到第一次提交,但这会让我有 2 次提交。 有没有办法在第一个提交之前引用提交?

【问题讨论】:

“第一个之前的提交”? @innaM - 这是 起源于 git 的原始提交。 (希望幽默能很好地通过互联网传播)。 对于稍后回答此问题的人,请务必使用the more modern answer。 相关,但不是重复的(--root 实际上不是压缩 all 提交的最佳解决方案,如果有很多提交壁球):Combine the first two commits of a Git repository?. Imo:这是@MrTux 最好的:***.com/questions/30236694/…。 【参考方案1】:

截至git 1.6.2,您可以使用git rebase --root -i

对于除第一次之外的每个提交,将pick 更改为squash

【讨论】:

1.7.12 新增:github.com/git/git/blob/master/Documentation/RelNotes/… 这个答案是ok,但是如果你交互式地变基超过 20 次提交,那么交互式变基可能会太慢和笨拙。你可能很难尝试压缩成百上千的提交。在这种情况下,我会对根提交进行软重置或混合重置,然后重新提交。 在我推送了一个我想为其创建拉取请求的功能分支之后,这是我可以将主分支引入新创建的 github 存储库的唯一方法。 如果你有很多提交,很难手动将 'pick' 更改为 'squash'。在 VIM 命令行中使用 :%s/pick/squash/g 可以更快地完成此操作。 这个作品找到了,但我不得不强制推动。当心! git push -f【参考方案2】:

更新

我创建了一个别名 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 创建一个孤儿提交并使用它。

解释

    通过git commit-tree创建一个提交

    git commit-tree HEAD^tree -m "A new start" 所做的是:

根据提供的树对象创建一个新的提交对象 并在标准输出上发出新的提交对象 ID。日志消息是 从标准输入读取,除非给出 -m 或 -F 选项。

表达式HEAD^tree表示HEAD对应的树对象,即你当前分支的尖端。见Tree-Objects 和Commit-Objects。

    将当前分支重置为新的提交

然后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")

这避免了将模板 repo 添加为远程(origin 或其他方式)并将模板 repo 的历史记录折叠到您的初始提交中。

【讨论】:

这里解释了 git 修订语法 (HEAD^tree) 语法,以防其他人想知道:jk.gs/gitrevisions.html 这会重置本地和远程存储库,还是仅重置其中一个? @aleclarson,这只会重置本地存储库中的当前分支,使用git push -f进行传播。 我在寻找从不涉及git clone 的项目模板存储库开始新项目的方法时找到了这个答案。如果您将--hard 添加到git reset 并在git commit-tree 中将HEAD 切换为FETCH_HEAD,您可以在获取模板存储库后创建初始提交。我已经编辑了答案,最后有一节演示了这一点。 你可以摆脱那个“警告”,但只需使用$1?Please enter a message【参考方案3】:

如果您只想将所有提交压缩到根提交,那么当

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

文档:

git-checkout(1) Manual Page。

替代解决方案 #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

文档:

git-reset(1) Manual Page。

【讨论】:

替代解决方案 #1:孤枝 - 岩石! 替代解决方案 #1 FTW。只是补充一下,如果你想将你的更改推送到遥控器,请执行git push origin master --force 不要忘记git push --force 如果你要推送到一个开放的 Github 拉取请求,不要在代码上做一个孤立的分支(即不要做上面的替代解决方案#1)!!! Github 将关闭你的 PR,因为 the current head isn't a descendant of the stored head sha. 替代解决方案 #1 还避免了压缩提交时可能出现的 merge conflicts【参考方案4】:

也许最简单的方法是创建一个具有当前工作副本状态的新存储库。如果您想保留所有提交消息,您可以先执行git log &gt; 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 -i --root。见:***.com/a/9254257/109618 这可能适用于某些情况,但它本质上不是问题的答案。使用这个秘籍,你也可以释放所有配置和所有其他分支。 这是一个可怕的解决方案,具有不必要的破坏性。请不要使用它。 也会破坏子模块。 -1【参考方案5】:
echo "message" | git commit-tree HEAD^tree

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

git reset SHA-1

只需一步完成上述操作:

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

【讨论】:

git reset $(git commit-tree HEAD^tree -m "commit message") 会更容易。 ^ 这个! - 应该是一个答案。不完全确定这是否是作者的意图,而是我的意图(需要一个带有单一提交的原始存储库,这样才能完成工作)。 @ryenus,您的解决方案完全符合我的要求。如果您将评论添加为答案,我会接受。 我自己不建议使用 subshel​​l-variant 的原因是它不能在 Windows 中的 cmd.exe 上运行。 在 Windows 提示中,您可能必须引用最后一个参数:echo "message" | git commit-tree "HEAD^tree"【参考方案6】:

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

请记住,做这样的事情总是有风险的,在开始之前创建一个保存分支绝不是一个坏主意。

从记录开始

git log --oneline

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

git reset --soft <#sha#>

用从日志中复制的 SHA 替换 &lt;#sha#&gt;

git status

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

git commit --amend

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

现在强制推送这个分支,它会覆盖那里的内容。

【讨论】:

请注意,这实际上似乎留下了历史。你把它孤立了,但它仍然存在。 非常有用的答案......但您应该知道,在修改命令之后,您会发现自己在 vim 编辑器中使用其特殊的语法。 ESC, ENTER, :x 是你的朋友。【参考方案7】:

我读过一些关于使用移植物的文章,但从未对其进行过深入研究。

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

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

【讨论】:

【参考方案8】:

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

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

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

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

【讨论】:

【参考方案9】:

一行6个字:

git checkout --orphan new_root_branch  &&  git commit

【讨论】:

@AlexanderMills,您应该阅读git help checkout 关于--orphan 您能否为此链接到该文档,以便阅读本文的每个人都不必手动搜索它? 简单。这里是git help checkout --orphan【参考方案10】:

首先,使用git rebase --interactive 将所有提交压缩为一个提交。现在你剩下两个提交到壁球。为此,请阅读任何

How do I combine the first two commits of a Git repository? git: how to squash the first two commits?

【讨论】:

【参考方案11】:

创建备份

git branch backup

重置为指定的提交

git reset --soft <#root>

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

git add .

提交而不更新消息

git commit --amend --no-edit

将带有压缩提交的新分支推送到 repo

git push -f

【讨论】:

这会保留以前的提交消息吗? @not2qubit 不,这不会保留以前的提交消息,而不是提交 #1、提交 #2、提交 #3,您将把这些提交中的所有更改打包到一个提交中#1。提交 #1 将是您重置回的 &lt;root&gt; 提交。 git commit --amend --no-edit 会将所有更改提交到当前提交(&lt;root&gt;),而无需编辑提交消息。【参考方案12】:

为此,您可以将本地 git 存储库重置为第一个提交主题标签,这样您在该提交之后的所有更改都将取消暂存,然后您可以使用 --amend 选项提交。

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

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

【讨论】:

这应该被接受,因为这会保留其他分支【参考方案13】:

使用移植物进行壁球

添加一个文件.git/info/grafts,把你想成为你的根的提交哈希放在那里

git log 现在将从该提交开始

为了让它“真实”运行git filter-branch

【讨论】:

【参考方案14】:

我通常这样做:

确保所有内容都已提交,并记下最新的提交 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 强制推送您的更改

【讨论】:

【参考方案15】:

假设您的分支中有 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

【讨论】:

【参考方案16】:

这个答案改进了上面的几个(请投票),假设除了创建一个提交(no-parents no-history)之外,您想要保留所有该提交的提交数据:

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

当然,新/单次提交的 commit-SHA 会改变,因为它代表了一个新的(非)历史,变成了 parentless/root-commit。

这可以通过读取git log 并为git commit-tree 设置一些变量来完成。假设您想在新分支 one-commit 中从 master 创建单个提交,保留上面的提交数据:

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
')

【讨论】:

【参考方案17】:

对我来说,它是这样工作的: 我总共有 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

【讨论】:

【参考方案18】:

这对我来说效果最好。

git rebase -X ours -i master

这将使 git 更喜欢你的特性分支而不是 master;避免繁重的合并编辑。您的分支需要与 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.

【讨论】:

【参考方案19】:

由于我在这里提出的解决方案遇到了一些问题,我想分享一个非常简单的解决方案(将功能分支上的所有提交压缩为一个):

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

前面的合并 cmd 确保在提交时不会影响您最近从 master 所做的更改!之后,只需提交更改并执行git push -f

【讨论】:

【参考方案20】:

当我从 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 提交压缩为一个?的主要内容,如果未能解决你的问题,请参考以下文章

如何将多个提交合并到另一个分支上作为单个压缩提交?

如何通过非交互方式压缩除最近提交之外的所有提交来减少臃肿的 Git 存储库的大小?

带有压缩提交的 Git 发布分支

如何通过非交互式压缩除最近的提交之外的所有提交来减少膨胀的Git仓库的大小?

你如何使用 git format-patch 将提交压缩到一个补丁中?

git rebase 压缩提交