在Git中提交根提交之前插入一个提交?

时间:2009-03-14 05:31:11

标签: git version-control rebase git-rebase

之前我曾询问过如何在git存储库中squash the first two commits

虽然这些解决方案非常有趣,而且并不像git中的其他一些东西那样令人惊讶,但如果你需要在项目开发过程中多次重复这个过程,那么它们仍然会受到一些伤害。 。

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

然后,我要做的是拥有一个空的初始提交,仅仅是为了成为第一个提交。没有代码,没有任何东西。只是占用空间,因此它可以作为rebase的基础。

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

15 个答案:

答案 0 :(得分:279)

2017年中旬回答

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

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

    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
    
  4. 就是这样。如果你足够了解你的外壳,你可以将整个东西重新排列成一个单线。

    (N.B。:在实践中,我现在使用filter-branch。稍后会对其进行编辑。)


    历史答案(由其他答案引用)

    这是同一解决方案的更清晰的实现,因为它无需创建额外的存储库,使用遥控器,并纠正一个独立的头部:

    # 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
    

    Voila,您最终在master上重写了其历史记录以包含空根提交。


    NB。:在缺少--orphan切换到checkout的旧版本的Git上,您需要管道来创建一个空分支:

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

答案 1 :(得分:28)

亚里士多德Pagaltzis和Uwe Kleine-König的答案以及Richard Bronosky的评论合并。

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

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

答案 2 :(得分:11)

我喜欢亚里士多德的回答。但是发现对于一个大型存储库(&gt; 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'选项意味着将重写标记以指向新创建的提交。

答案 3 :(得分:5)

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

# 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)。

答案 4 :(得分:3)

git rebase --root --onto $emptyrootcommit

应该轻松完成这个伎俩

答案 5 :(得分:3)

我很兴奋,写了一个这个好脚本的'幂等'版本......它总是会插入相同的空提交,如果你运行两次,它每次都不会改变你的提交哈希值。所以,这是我对 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的几个克隆副本上执行这个操作,并最终得到相同的结果,所以它们仍然是兼容的......测试......是的,它确实有效,但还需要删除并再次添加你的遥控器,例如:

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

答案 6 :(得分:2)

嗯,这就是我想出的:

# 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

答案 7 :(得分:2)

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

  • 更好的表现
  • 更容易且风险更小(您可以在每一步验证结果并撤消您所做的事情......)
  • 与多个分支机构合作并保证结果

背后的想法是:

  • 过去创建一个新的空提交
  • 除了将新的根提交添加为父
  • 之外,用提交替换旧的根提交完全相似
  • 验证所有内容符合预期并运行git filter-branch
  • 再次验证一切正常,并清除不再需要的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"

你可以毫无风险地运行这个脚本(即使在做你以前从未做过的动作之前进行备份是一个好主意;)),如果结果不是预期的结果,只需删除文件夹中创建的文件{ {1}}然后再试一次;)

确认存储库状态符合预期后,运行以下命令更新所有分支的历史记录:

.git/refs/replace

现在,您必须看到2个历史记录,旧记录和新记录(有关详细信息,请参阅git filter-branch -- --all 的帮助)。您可以比较2并再次检查是否一切正常。如果您满意,请删除不再需要的文件:

filter-branch

您可以返回rm -rf ./.git/refs/original rm -rf ./.git/refs/replace 分支并删除临时分支:

master

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

答案 8 :(得分:2)

如果您忘记在“git init”之后立即创建一个空提交,这是一个简单的单行程序,可用于在存储库的开头添加空提交:

git rebase --root --onto $(git commit-tree -m 'Initial commit (empty)' 4b825dc642cb6eb9a060e54bf8d69288fbee4904)

答案 9 :(得分:1)

以下是我的bash脚本,基于Kent的答案并做了改进:

  • 完成后检查原始分支,而不只是master;
  • 我试图避开临时分支,但git checkout --orphan仅适用于分支,而不是分离头状态,因此检查时间足以进行新的根提交然后删除;
  • 它在filter-branch期间使用新根提交的哈希值(肯特在那里留下了一个占位符进行手动替换);
  • 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"

答案 10 :(得分:1)

切换根提交:

首先,创建您想要的提交作为第一个。

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

git rebase -i --root

编辑器将显示提交,直到根提交,如:

选择1234旧根消息

选择0294中间的提交

选择5678提交你想放在根

然后,您可以将所需的提交放在第一行中。在示例中:

选择5678提交你想放在根

选择1234旧根消息

选择0294中间的提交

退出编辑器,提交顺序将发生变化。

PS:要更改git使用的编辑器,请运行:

git config --global core.editor name_of_the_editor_program_you_want_to_use

答案 11 :(得分:0)

回答亚里士多德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).

请注意,您的仓库不应包含等待提交的本地修改 注意git checkout --orphan将适用于新版本的git,我猜 请注意,大部分时间git status都会提供有用的提示。

答案 12 :(得分:0)

结合最新的和最伟大的。没有副作用,没有冲突,保留标签。

git log --reverse

tree=`git hash-object -wt tree --stdin < /dev/null`
commit=`git commit-tree -m 'Initialize empty repository' $tree`
echo $commit # copy below, interpolation didn't work for me

git filter-branch --parent-filter 'sed "s/^\$/-p <commit>/"' --tag-name-filter cat master

git log --reverse

请注意,在GitHub上,您将丢失CI运行数据,除非其他分支机构也得到修复,否则PR可能会混乱。

答案 13 :(得分:-6)

开始新的存储库。

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

按照您希望的方式完成所有事情,调整系统时间以反映您希望以何种方式完成此操作。根据需要从现有存储库中提取文件,以避免大量不必要的输入。

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

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

当你决定从现在开始一周之后发生过去时,这也会让你更好。

答案 14 :(得分:-7)

我知道这篇文章很老但是这个页面是Googling&#34;插入commit git&#34;的第一个。

为什么简单的事情变得复杂?

你有A-B-C,你想要A-B-Z-C。

  1. git rebase -i trunk(或B之前的任何内容)
  2. 更改选择以在B行上编辑
  3. 进行更改:git add ..
  4. git commitgit commit --amend将编辑B而不创建Z)
  5. [您可以在此处创建尽可能多的git commit以插入更多提交。当然,您可能在第5步遇到麻烦,但解决与git的合并冲突是您应该具备的技能。如果没有,请练习!]

    1. git rebase --continue
    2. 简单,不是吗?

      如果您了解git rebase,请添加&#39; root&#39;提交应该不是问题。

      与git玩得开心!