如何将当前提交设置为 Git 仓库的唯一(初始)提交?

885

我目前有一个本地Git仓库,我将其推送到Github仓库。

本地仓库有大约10个提交,而Github仓库是这些提交的同步副本。

我想做的是从本地Git仓库中删除所有版本历史记录,以便仓库中只显示当前内容作为唯一提交(因此不会存储仓库内文件的旧版本)。

然后,我想将这些更改推送到Github。

我已经调查了Git rebase,但这似乎更适用于删除特定版本。 另一个潜在的解决方案是删除本地仓库并创建一个新的 - 虽然这可能会创建很多工作!

预计完成时间:有一些特定目录/文件未被跟踪 - 如果可能的话,我希望保持对这些文件的未跟踪。


6
请参考 https://dev59.com/t3RC5IYBdhLWcg3wAcbU (“如何合并 Git 仓库的前两个提交?”) - Anonymoose
可能相关: 如何将Git仓库的前两个提交合并? - user456814
如何将所有的git提交压缩成一个? - ryenus
请参见 https://stackoverflow.com/a/66091688/8079868 - anon
18个回答

1133

以下是一种暴力方法,它也会删除存储库的配置。

注意:如果存储库具有子模块,则此方法不起作用!如果您正在使用子模块,您应该使用例如交互式变基

步骤1:删除所有历史记录 (请确保您已备份,因为此操作无法恢复)

cat .git/config  # save your <github-uri> somewhere
rm -rf .git

第二步:使用当前内容重建Git仓库

在第二步之前,如果您尚未设置init.defaultBranch配置,请通过git config --global init.defaultBranch <branch-name>进行设置。在此示例中,您可以选择将<branch-name>设为main

git init
git add .
git commit -m "Initial commit"

第三步:将代码推送到GitHub。

git remote add origin <github-uri>
git push -u --force origin main

5
谢谢larsmans - 我选择使用这个作为我的解决方案。虽然初始化Git仓库会丢失旧仓库中未被跟踪的文件记录,但对于我的问题来说,这可能是一个更简单的解决方案。 - kaese
6
我认为你的 .gitignore 应该处理这些,对吧? - Fred Foo
55
先备份你的 .git/config 文件,然后再恢复它。 - lalebarde
45
如果您试图删除敏感数据,请小心处理:在新推送的主分支中只有一个提交存在是具有误导性的 - 历史记录仍然存在,只是无法从该分支访问。例如,如果您有指向旧提交的标签,则可以访问这些提交。事实上,对于任何了解一点git技巧的人来说,在git push后,他们仍然能够从GitHub存储库中恢复所有历史记录 - 如果您有其他分支或标签,那么他们甚至不需要太多的git技巧。 - Robert Muil
2
世上有很多不好的答案,但经过一个小时的努力,我终于让它按照我的意愿工作了! - user6885115
显示剩余12条评论

837

对我有效且能使子模块正常工作的唯一解决方案是:

git checkout --orphan newBranch
git add -A  # Add all files and commit them
git commit
git branch -D master  # Deletes the master branch
git branch -m master  # Rename the current branch to master
git push -f origin master  # Force push master branch to github
git gc --aggressive --prune=all     # remove the old files

当我有子模块时,删除.git/总是会导致巨大的问题。对我来说,使用git rebase --root会产生冲突(而且需要很长时间,因为我有很多历史记录)。


75
这应该是正确的答案!只需要在最后添加git push -f origin master,太阳将再次照耀您的新存储库! :) - gru
6
git fetch; git reset --hard origin/master https://dev59.com/O2445IYBdhLWcg3wl7bW请执行"git fetch;" 和 "git reset --hard origin/master" 命令,以强制覆盖本地更改并与远程的"master"分支同步。有关更多信息,请参阅此StackOverflow问题:https://dev59.com/O2445IYBdhLWcg3wl7bW - echo
7
做完这个之后,仓库的可用空间会增加吗? - Inuart
7
Git会暂时保留旧文件,要将它们清除,请运行git gc --aggressive --prune=all。此外,对于任何被分支或标签引用的提交,Git将继续存储历史记录。要检查,请运行git tag -lgit branch -v,然后删除找到的任何标签或分支。还要用git ls-remote再次确认你的远程库,你可能需要删除远程的标签/分支,否则在获取时将再次获得所有链接的文件。 - Jason Goemaat
11
我认为你应该在回答的最后一行加上@JasonGoemaat的建议。如果没有使用git gc --aggressive --prune all,那么失去历史记录的整个意义将会被忽略。 - Tuncay Göncüoğlu
显示剩余14条评论

124

这是我偏爱的方法:

git branch new_branch_name $(echo "commit message" | git commit-tree HEAD^{tree})

这将创建一个新分支,其中包含一个提交,添加了 HEAD 中的所有内容。 它不会更改任何其他内容,因此完全安全。


6
最佳做法!清晰明了,而且能够完成工作。此外,我将拥有大量更改的分支从“master”重命名为“local-work”,并将“new_branch_name”重命名为“master”。在“master”中,请执行以下操作: git -m local-changes git branch -m local-changes git checkout new_branch_name git branch -m master - Valtoni Boaventura
1
这看起来真的很简短,唯一我不理解或者还没见过的是 HEAD^{tree},有人可以解释一下吗?除此之外,我会把它理解为“从给定的提交创建新分支,通过使用给定的提交消息创建一个新的提交对象来创建”。 - TomKeegasi
6
寻找有关git参考语法的答案的权威位置是在 git-rev-parse 文档中。这里发生的是 git-commit-tree 需要一个指向树(存储库的快照)的引用,但 HEAD 是一个修订版本。为了找到与提交相关联的树,我们使用 <rev>^{<type>} 格式。 - dan_waterworth
2
很好的回答。运作良好。最后说git push --force <remote> new_branch_name:<remote-branch> - Felipe Alvarez
2
一行搞定:git branch newbranch $(echo "commit message" | git commit-tree HEAD^{tree}) | git push --force origin newbranch:master - Peroxy
如果想要覆盖上一次提交的提交信息,请使用git branch newbranch $(git log -1 --pretty=%B | git commit-tree HEAD^{tree}) | git push --force github newbranch:master - undefined

36
另一种选择是交互式变基,如果你有很多提交的话可能会需要很多工作(假设你的git版本 >=1.7.12):git rebase --root -i 在编辑器中呈现的提交列表中:
- 将第一个提交的 "pick" 改为 "reword" - 所有其他提交将 "pick" 改为 "fixup"
保存并关闭。Git将开始变基。
最终你将拥有一个新的根提交,其中包含了所有在它之后的提交的内容。
优点是你不必删除存储库,如果你改变了主意,你总是可以回到以前的状态。
如果你真的想清除历史记录,将主分支重置到此提交并删除所有其他分支。

1
重置完成后,我无法推送:错误:无法将某些引用推送到 - Begueradj
如果您已经推送了您的分支并进行了变基,那么您需要强制推送 git push --force-with-lease。使用 force-with-lease 是因为它比 --force 更加安全。 - Carl

21
< p > larsmans 提出的方法的变体:

保存您的未跟踪文件列表:

git ls-files --others --exclude-standard > /tmp/my_untracked_files

保存您的git配置:

mv .git/config /tmp/

然后执行larsmans的第一步:

rm -rf .git
git init
git add .

恢复您的配置:

mv /tmp/config .git/

取消跟踪未被跟踪的文件:

cat /tmp/my_untracked_files | xargs -0 git rm --cached

然后提交:

git commit -m "Initial commit"

最后将更改推送到您的代码仓库:

git push -u --force origin master

8
以下是从 @Zeelot 的回答中改编的脚本。它应该会删除所有分支的历史,而不仅仅是主分支:
for BR in $(git branch); do   
  git checkout $BR
  git checkout --orphan ${BR}_temp
  git commit -m "Initial commit"
  git branch -D $BR
  git branch -m $BR
done;
git gc --aggressive --prune=all

它对我的需求有效(我没有使用子模块)。

4
我认为你忘记强制推送主分支来完成该过程。 - not2qubit
2
我不得不进行轻微修改。git branch 命令会在你所检出的分支旁边加上一个星号,这将导致它将所有文件或文件夹解析为分支名称。相反,我使用了 git branch --format="%(refname:lstrip=2)" 命令,这样只给我提供了分支名称。 - Ben Richards
@not2qubit:谢谢你。确切的命令是什么?git push --force origin master,还是git push --force-with-lease?显然后者更安全(请参见https://dev59.com/OG035IYBdhLWcg3wSOCr)。 - Shafique Jamal
@BenRichards。有趣。我会尝试使用与分支名称匹配的文件夹再次测试它,然后更新答案。谢谢。 - Shafique Jamal

6

这将删除master分支的历史记录(在运行命令之前,您可能需要备份):

git branch tmp_branch $(echo "commit message" | git commit-tree HEAD^{tree})
git checkout tmp_branch
git branch -D master
git branch -m master
git push -f --set-upstream origin master

这基于@dan_waterworth的答案。

5

6
无法将此克隆推送到新存储库。 - Seweryn Niemiec
1
知道如何规避这个限制会很有用。有人能解释一下为什么不能强制推送吗? - not2qubit
你的问题的答案:https://dev59.com/-Gw05IYBdhLWcg3w_WxN - Matthias M

5

只需删除Github仓库并创建一个新的。这是迄今为止最快、最简单和最安全的方法。毕竟,当您只想要一个带有单个提交的主分支时,在执行所有那些接受的解决方案中的命令能获得什么好处呢?


1
其中一个主要点是能够看到它从哪里分叉出来的。 - not2qubit
我刚刚做了这件事,一切都好。 - thanos.a

4
我想做的是从本地Git存储库中删除所有版本历史记录,这样存储库的当前内容就会显示为唯一的提交(因此存储库中文件的旧版本不会被保存)。更具概念性的答案是:如果没有标签/分支/引用指向它们,则git会自动垃圾回收旧提交。因此,您只需删除所有标签/分支并创建一个新的孤立提交,与任何分支相关联-按照惯例,您会让分支master指向该提交。除非使用低级别的git命令进行挖掘,否则将永远看不到旧的、无法访问的提交。如果这对您足够了,我会停在那里,让自动GC在它希望时执行其工作。如果您想立即摆脱它们,可以使用git gc(可能还要加上--aggressive --prune=all)。对于远程git存储库,除非您可以访问其文件系统,否则无法强制执行。

在@Zeelot的回答中看到这个很不错的补充。 - Mogens TrasherDK
是的,Zeelot的命令基本上可以做到这一点(只是以不同的方式,通过完全重新开始,这对于OP来说可能是可以接受的)。@MogensTrasherDK - AnoE

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