我曾经问过如何压缩git存储库中的前两次提交。
虽然这些解决方案相当有趣,而且不像git中的其他一些东西那样令人费解,但如果您需要在项目开发过程中多次重复这个过程,那么它们仍然是一个众所周知的伤害。
所以,我宁愿只经历一次痛苦,然后能够永远使用标准的交互式rebase。
那么,我想做的是有一个空的初始提交,它的存在仅仅是为了成为第一个。没有代码,什么都没有。只是占地方,这样就可以做地基了。
我的问题是,有了一个现有的存储库,我如何在第一个提交之前插入一个新的空提交,并将其他所有人向前移动?
以下是我的bash脚本基于肯特的回答与改进:
it checks out the original branch, not just master, when done;
I tried to avoid the temporary branch, but git checkout --orphan only works with a branch, not detached-head state, so it's checked out long enough to make the new root commit and then deleted;
it uses the hash of the new root commit during the filter-branch (Kent left a placeholder in there for manual replacement);
the filter-branch operation rewrites only the local branches, not remotes too
the author and committer metadata is standardised so that the root commit is identical across repositories.
#!/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"
我认为使用git replace和git filter-branch是一个比使用git rebase更好的解决方案:
更好的性能
更简单,风险更低(你可以在每一步验证你的结果,并撤销你所做的…)
与多家分公司合作,工作成果有保证
它背后的理念是:
在过去创建一个新的空提交
用一个完全类似的提交替换旧的根提交,除了新的根提交被添加为父提交
验证一切都如预期的那样,并运行git filter-branch
再次验证一切正常,并清除不再需要的git文件
以下是前2步的脚本:
#!/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"
您可以毫无风险地运行此脚本(即使在执行以前从未执行过的操作之前进行备份也是个好主意;)),如果结果不是预期的,只需删除在.git/refs/replace文件夹中创建的文件并重试;)
一旦您验证了存储库的状态是您所期望的,运行以下命令来更新所有分支的历史:
git filter-branch -- --all
现在,你必须看到2个历史记录,旧的和新的(更多信息请参阅filter-branch上的帮助)。您可以比较这两个并再次检查是否一切正常。如果您满意,删除不再需要的文件:
rm -rf ./.git/refs/original
rm -rf ./.git/refs/replace
你可以返回到你的主分支并删除临时分支:
git checkout master
git branch -D new-root
现在,一切都该完成了;)