在Git的根提交之前插入提交?

我之前曾经问过如何压缩 git仓库中的前两个提交 。

虽然解决scheme相当有趣,并不像git中的其他一些东西那样令人头痛,但是如果在项目开发过程中需要多次重复这个过程,那么它们仍然是一个公然的伤害。

所以,我宁愿只经历一次痛苦,然后才能够永远使用标准的互动式rebase。

那么我想要做的就是做一个空的初始提交,仅仅是为了成为第一个目的而存在。 没有代码,没有任何东西。 只是占用空间,所以可以作为rebase的基础。

那么我的问题是,如果有一个现有的存储库,我怎样才能在第一个插入之前插入一个新的空的提交,并将所有其他人转移?

2017年中旬的答案

创build一个没有副作用的完全空的提交可能是最好直接使用Git的pipe道。 这样做可以避免任何副作用:不要触摸工作副本或索引,没有临时分支来清理等。所以:

  1. 为了创build一个提交,我们需要一个目录树,所以我们先创build一个空目录:

    tree=`git hash-object -wt tree --stdin < /dev/null` 
  2. 现在我们可以围绕它提交一个提交:

     commit=`git commit-tree -m 'root commit' $tree` 
  3. 现在我们可以改变:

     git rebase --onto $commit --root master 

就是这样。 如果你足够了解你的shell,你可以将整个事物重新排列成一行。

(注意:实际上我现在使用filter-branch ,以后再编辑。)


历史答案(参考其他答案)

这是相同解决scheme的一个更清晰的实现,因为它的工作原理不需要创build额外的存储库,使用遥控器,修正一个分离的头部:

 # first you need a new empty branch; let's call it `newroot` git checkout --orphan newroot git rm -rf . # then you apply the same steps git commit --allow-empty -m 'root commit' git rebase --onto newroot --root master git branch -d newroot 

瞧,你已经结束了与重写其历史包括一个空的根提交的master


注意:在旧版本的Git上,缺less用于checkout--orphan开关,需要pipe道来创build一个空分支:

 git symbolic-ref HEAD refs/heads/newroot git rm --cached -r . git clean -f -d 

亚里斯多德·帕加尔齐斯和乌韦·克莱纳 – 柯尼格的答案和理查·布罗诺斯基的评论合并。

 git symbolic-ref HEAD refs/heads/newroot git rm --cached -r . git clean -f -d # touch .gitignore && git add .gitignore # if necessary git commit --allow-empty -m 'initial' git rebase --onto newroot --root master git branch -d newroot 

(只是把所有东西放在一个地方)

我喜欢亚里士多德的答案。 但是发现对于一个大的仓库(> 5000次提交)来说,filter-branch比rebase有更好的工作效率1)速度更快2)合并冲突时不需要人工干预。 3)它可以重写标签 – 保存它们。 请注意,filter-branch是可行的,因为每个提交的内容都没有问题 – 它和之前的“rebase”完全一样。

我的步骤是:

 # first you need a new empty branch; let's call it `newroot` git symbolic-ref HEAD refs/heads/newroot git rm --cached -r . git clean -f -d # then you apply the same steps git commit --allow-empty -m 'root commit' # then use filter-branch to rebase everything on newroot git filter-branch --parent-filter 'sed "s/^\$/-p <sha of newroot>/"' --tag-name-filter cat master 

请注意,'–tag-name-filter cat'选项意味着标签将被重写为指向新创build的提交。

git rebase --root --onto $emptyrootcommit

应该轻松搞定

那么,这就是我想到的:

 # Just setting variables on top for clarity. # Set this to the path to your original repository. ORIGINAL_REPO=/path/to/original/repository # Create a new repository… mkdir fun cd fun git init # …and add an initial empty commit to it git commit --allow-empty -m "The first evil." # Add the original repository as a remote git remote add previous $ORIGINAL_REPO git fetch previous # Get the hash for the first commit in the original repository FIRST=`git log previous/master --pretty=format:%H --reverse | head -1` # Cherry-pick it git cherry-pick $FIRST # Then rebase the remainder of the original branch on top of the newly # cherry-picked, previously first commit, which is happily the second # on this branch, right after the empty one. git rebase --onto master master previous/master # rebase --onto leaves your head detached, I don't really know why) # So now you overwrite your master branch with the newly rebased tree. # You're now kinda done. git branch -f master git checkout master # But do clean up: remove the remote, you don't need it anymore git remote rm previous 

我成功地使用了亚里士多德和肯特的答案:

 # first you need a new empty branch; let's call it `newroot` git checkout --orphan newroot git rm -rf . git commit --allow-empty -m 'root commit' git filter-branch --parent-filter \ 'sed "s/^\$/-p <sha of newroot>/"' --tag-name-filter cat -- --all # clean up git checkout master git branch -D newroot # make sure your branches are OK first before this... git for-each-ref --format="%(refname)" refs/original/ | \ xargs -n 1 git update-ref -d 

除了标签之外,这也将重写所有分支(不只是master分支)。

我很兴奋,写了一个'idempotent'版本的这个漂亮的脚本…它总是会插入相同的空提交,如果你运行两次,它不会改变你的提交散列每次。 所以,这里是我的git-insert-empty-root

 #!/bin/sh -ev # idempotence achieved! tmp_branch=__tmp_empty_root git symbolic-ref HEAD refs/heads/$tmp_branch git rm --cached -r . || true git clean -f -d touch -d '1970-01-01 UTC' . GIT_COMMITTER_DATE='1970-01-01T00:00:00 +0000' git commit \ --date='1970-01-01T00:00:00 +0000' --allow-empty -m 'initial' git rebase --committer-date-is-author-date --onto $tmp_branch --root master git branch -d $tmp_branch 

这是否值得额外的复杂? 也许不是,但我会用这个。

这应该也允许在repo的几个克隆拷贝上执行这个操作,并且得到相同的结果,所以它们仍然是兼容的…testing…是的,它工作,但也需要删除和添加再次遥控,例如:

 git remote rm origin git remote add --track master user@host:path/to/repo 

我认为使用git replacegit filter-branch是比使用git rebase更好的解决scheme:

  • 更好的性能
  • 更容易和更less的风险(你可以在每一步validation你的结果,并取消你所做的…)
  • 与多个分支机构保持良好的合作关系

其背后的想法是:

  • 在过去创build一个新的空提交
  • 除了将新的根提交添加为父项之外,将旧的根提交replace为完全相似的提交
  • validation一切都如预期,并运行git filter-branch
  • 再一次,validation一切正常,并清理没有更多所需的git文件

这里有两个步骤的脚本:

 #!/bin/bash root_commit_sha=$(git rev-list --max-parents=0 HEAD) git checkout --force --orphan new-root find . -path ./.git -prune -o -exec rm -rf {} \; 2> /dev/null git add -A GIT_COMMITTER_DATE="2000-01-01T12:00:00" git commit --date==2000-01-01T12:00:00 --allow-empty -m "empty root commit" new_root_commit_sha=$(git rev-parse HEAD) echo "The commit '$new_root_commit_sha' will be added before existing root commit '$root_commit_sha'..." parent="parent $new_root_commit_sha" replacement_commit=$( git cat-file commit $root_commit_sha | sed "s/author/$parent\nauthor/" | git hash-object -t commit -w --stdin ) || return 3 git replace "$root_commit_sha" "$replacement_commit" 

你可以在没有风险的情况下运行这个脚本(即使在做了之前从未做过的动作之前进行备份也是一个好主意);)如果结果不是预期的结果,只要删除文件夹中创build的文件.git/refs/replace然后再试一次;)

一旦validation了存储库的状态是您所期望的,请运行以下命令更新所有分支的历史logging:

 git filter-branch -- --all 

现在,你必须看到2个历史,旧的和新的历史(请参阅filter-branch帮助以获取更多信息)。 你可以比较2,并再次检查,如果一切正常。 如果您满意,请删除不需要的文件:

 rm -rf ./.git/refs/original rm -rf ./.git/refs/replace 

您可以返回到您的master分支并删除临时分支:

 git checkout master git branch -D new-root 

现在,一切都应该完成;)

这是我的基于肯特的答案改进的bash脚本:

  • 它检查出原来的分支,而不仅仅是master ,完成后;
  • 我试图避免临时分支,但是git checkout --orphan只能用于分支,而不是分离头状态,所以检查足够长的时间以使新的根提交,然后删除;
  • 它在filter-branch使用新的根提交的散列(Kent在那里留下一个占位符用于手动replace);
  • filter-branch操作只重写本地分支,而不是远程
  • 作者和提交者元数据是标准化的,这样跨存储库的根提交是相同的。

 #!/bin/bash # Save the current branch so we can check it out again later INITIAL_BRANCH=`git symbolic-ref --short HEAD` TEMP_BRANCH='newroot' # Create a new temporary branch at a new root, and remove everything from the tree git checkout --orphan "$TEMP_BRANCH" git rm -rf . # Commit this empty state with generic metadata that will not change - this should result in the same commit hash every time export GIT_AUTHOR_NAME='nobody' export GIT_AUTHOR_EMAIL='nobody@example.org' export GIT_AUTHOR_DATE='2000-01-01T00:00:00+0000' export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME" export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL" export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" git commit --allow-empty -m 'empty root' NEWROOT=`git rev-parse HEAD` # Check out the commit we just made and delete the temporary branch git checkout --detach "$NEWROOT" git branch -D "$TEMP_BRANCH" # Rewrite all the local branches to insert the new root commit, delete the # original/* branches left behind, and check out the rewritten initial branch git filter-branch --parent-filter "sed \"s/^\$/-p $NEWROOT/\"" --tag-name-filter cat -- --branches git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d git checkout "$INITIAL_BRANCH" 

接着回答亚里士多德Pagaltzis和其他人,但使用更简单的命令

 zsh% git checkout --orphan empty Switched to a new branch 'empty' zsh% git rm --cached -r . zsh% git clean -fdx zsh% git commit --allow-empty -m 'initial empty commit' [empty (root-commit) 64ea894] initial empty commit zsh% git checkout master Switched to branch 'master' zsh% git rebase empty First, rewinding head to replay your work on top of it... zsh% git branch -d empty Deleted branch empty (was 64ea894). 

注意你的repo不应该包含等待被提交的本地修改。
注意git checkout --orphan将工作在新版本的git,我猜。
注意大部分时间git status都会提供有用的提示。

要切换根提交:

首先,创build你想要的提交作为第一个。

其次,使用以下命令切换提交的顺序:

git rebase -i –root

一个编辑器会出现提交,直到根提交,如:

挑1234旧根消息

select0294中间的提交

挑5678提交你想放在根

然后你可以把你想要的提交放在第一行。 在这个例子中:

挑5678提交你想放在根

挑1234旧根消息

select0294中间的提交

退出编辑器,提交顺序将会改变。

PS:要改变编辑器的使用,运行:

git config –global core.editor name_of_the_editor_program_you_want_to_use

开始一个新的存储库。

将您的date设置回您想要的开始date。

按照您希望的方式完成所有工作,调整系统时间以反映何时您希望这样做。 根据需要从现有存储库中提取文件,以避免大量不必要的input。

当你到达今天,交换存储库,你就完成了。

如果你只是疯了(已经成立),但是相当聪明(可能,因为你必须有一定的智慧来思考这样的疯狂的想法),你将编写过程的脚本。

当你决定从过去的一星期以后,过去发生的事情也会变得更好。

我知道这个post是旧的,但当谷歌search“插入提交git”时,这个页面是第一个。

为什么简单的事情复杂?

你有ABC,你想要ABZC。

  1. git rebase -i trunk (或B之前的任何东西)
  2. 在B线上更改select编辑
  3. 进行更改: git add ..
  4. git commitgit commit --amend将编辑B而不是创buildZ)

[你可以尽可能多的git commit你想要插入更多的提交。 当然,你可能在步骤5有麻烦,但是解决与git的合并冲突是你应该拥有的技能。 如果没有,练习!

  1. git rebase --continue

简单,不是吗?

如果你理解git rebase ,那么添加一个“root”提交应该不成问题。

玩得开心!