我曾经问过如何压缩git存储库中的前两次提交。

虽然这些解决方案相当有趣,而且不像git中的其他一些东西那样令人费解,但如果您需要在项目开发过程中多次重复这个过程,那么它们仍然是一个众所周知的伤害。

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

那么,我想做的是有一个空的初始提交,它的存在仅仅是为了成为第一个。没有代码,什么都没有。只是占地方,这样就可以做地基了。

我的问题是,有了一个现有的存储库,我如何在第一个提交之前插入一个新的空提交,并将其他所有人向前移动?


当前回答

好吧,这是我想到的:

# 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

其他回答

合并亚里士多德Pagaltzis和Uwe Kleine-König的答案和理查德布罗诺斯基的评论。

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'选项意味着标签将被重写以指向新创建的提交。

以下是我的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"

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

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

请注意,您的回购不应该包含任何等待提交的本地修改。 注意git检出——孤儿将在新版本的git中工作,我猜。 注意,大多数时候git状态给出了有用的提示。

启动一个新的存储库。

将日期设置为您想要的开始日期。

按照你希望的方式做每件事,调整系统时间以反映你希望那样做的时间。根据需要从现有存储库中提取文件,以避免大量不必要的输入。

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

如果你只是一个疯狂的(成熟的)但相当聪明的人(很可能,因为你必须有一定的智慧才能想出像这样疯狂的想法),你将编写这个过程。

当你决定让过去的事情从现在开始以另一种方式发生时,这也会让事情变得更好。