尽管解决方案相当有趣,并且不像git中的其他某些东西那样令人费解,但如果您需要在项目开发过程中多次重复该过程,那么它们仍然会给我们带来很多伤害。
因此,我宁愿只经历一次痛苦,然后才能永远使用标准的交互式基础。
那么,我想做的是拥有一个空的初始提交,仅作为第一个提交而存在。没有代码,什么都没有。只是占用空间,因此它可以作为基础的基础。
那么我的问题是,拥有一个现有的存储库,我该如何着手在第一个空的提交之前插入一个新的空提交,并将其他所有人向前移动?
尽管解决方案相当有趣,并且不像git中的其他某些东西那样令人费解,但如果您需要在项目开发过程中多次重复该过程,那么它们仍然会给我们带来很多伤害。
因此,我宁愿只经历一次痛苦,然后才能永远使用标准的交互式基础。
那么,我想做的是拥有一个空的初始提交,仅作为第一个提交而存在。没有代码,什么都没有。只是占用空间,因此它可以作为基础的基础。
那么我的问题是,拥有一个现有的存储库,我该如何着手在第一个空的提交之前插入一个新的空提交,并将其他所有人向前移动?
Answers:
有两个步骤可以实现此目的:
newroot
为了方便起见,我们将新的空提交放在临时分支上。
您可以通过多种方法来执行此操作。
最干净的方法是使用Git的管道直接创建提交,从而避免触摸工作副本或索引或检出哪个分支等。
为空目录创建树对象:
tree=`git hash-object -wt tree --stdin < /dev/null`
包装一个提交:
commit=`git commit-tree -m 'root commit' $tree`
创建对此的引用:
git branch newroot $commit
如果您对外壳的了解足够深,那么您当然可以将整个过程重新排列成一个线性。
使用常规的瓷器命令,如果newroot
没有充分的理由,就不能在不检出分支,重复更新索引和工作副本的情况下创建空提交。但是有些人可能会觉得这更容易理解:
git checkout --orphan newroot
git rm -rf .
git clean -fd
git commit --allow-empty -m 'root commit'
请注意,在缺少--orphan
切换到的非常旧的Git版本上checkout
,您必须用以下内容替换第一行:
git symbolic-ref HEAD refs/heads/newroot
您在此处有两个选择:重新定基础或重写干净的历史记录。
git rebase --onto newroot --root master
这具有简单性的优点。但是,它还将在分支上的每个最后一次提交时更新提交者名称和日期。
此外,对于某些极端情况,即使合并到一个不包含任何内容的提交,它甚至可能由于合并冲突而失败。
较干净的方法是重写分支。与with不同git rebase
,您将需要查找分支从哪个提交开始:
git replace <currentroot> --graft newroot
git filter-branch master
显然,重写发生在第二步;这是需要说明的第一步。什么git replace
做的是它告诉Git的,每当它看到您要更换的对象的引用,Git的应改为看替换该对象的。
使用该--graft
开关,您将告诉它与正常情况有些不同。您是说还没有替换对象,但是您想<currentroot>
用其自身的精确副本替换提交对象,除了替换的父提交应该是您列出的newroot
提交对象(即提交) )。然后git replace
继续为您创建此提交,然后声明该提交代替您的原始提交。
现在,如果您执行a git log
,您将看到事情已经看起来像您想要的那样:分支从开始newroot
。
但是,请注意,git replace
它实际上并不会修改历史记录,也不会从您的存储库中传播出去。它只是将本地重定向从一个对象添加到另一个对象。这意味着没有其他人看到此替换的效果,只有您自己。
这就是为什么filter-branch
必须执行此步骤的原因。这样,git replace
您就可以为根提交创建一个具有调整后的父提交的精确副本;git filter-branch
然后对以下所有提交重复此过程。这实际上是历史记录被重写的地方,以便您可以共享它。
--onto newroot
选项是多余的;您可以不用它,因为您传递的参数newroot
与上游参数-相同newroot
。
git-checkout
此切换之前编写的。我已经对其进行了更新,以首先提到该方法,谢谢您的指导。
亚里士多德·帕加尔齐斯(Aristotle Pagaltzis)和UweKleine-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
(只是将所有内容放在一起)
git rebase newroot master
由于错误,我不得不将rebase命令更改为。
我喜欢亚里斯多德的回答。但是发现对于大型存储库(> 5000次提交),filter-branch的工作原理比rebase更好,原因如下:1)速度更快2)发生合并冲突时,不需要人工干预。3)它可以重写标签-保留它们。请注意,filter-branch之所以起作用是因为对于每个提交的内容都没有疑问-它与此“重新设置”之前的内容完全相同。
我的步骤是:
# 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'选项意味着标记将被重写以指向新创建的提交。
我成功地使用了亚里斯多德和肯特的答案:
# 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
除标记外,这还将重写所有分支(而不仅仅是)。
refs/original/
并删除每个引用。它删除的引用应该已经被其他分支引用,因此它们并没有真正消失,只是refs/original/
被删除了。
timedatectl set-time '2017-01-01 00:00:00'
给过newroot
一个旧的时间戳。
git rebase --root --onto $emptyrootcommit
应该很容易做到
$emptyrootcommit
是肯定会扩展为空的shell变量吗?
我认为使用git replace
和git filter-branch
比使用更好的解决方案git rebase
:
其背后的想法是:
git filter-branch
这是前两个步骤的脚本:
#!/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
获取更多信息)。您可以比较2并再次检查是否一切正常。如果满意,请删除不再需要的文件:
rm -rf ./.git/refs/original
rm -rf ./.git/refs/replace
您可以返回master
分支并删除临时分支:
git checkout master
git branch -D new-root
现在,所有步骤都应该完成;)
我很兴奋,并为这个漂亮的脚本编写了一个“幂等”版本……它将始终插入相同的空提交,并且如果您运行两次,它不会每次都更改您的提交哈希。所以,这是我对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
值得额外的复杂性吗?也许不是,但是我会用到这个。
此功能还应允许对存储库的多个克隆副本执行此操作,并得到相同的结果,因此它们仍然兼容...测试...是的,可以,但是还需要删除并添加您的再次遥控,例如:
git remote rm origin
git remote add --track master user@host:path/to/repo
要在存储库的开头添加一个空的提交,如果您忘记在“ git init”之后立即创建一个空的提交:
git rebase --root --onto $(git commit-tree -m 'Initial commit (empty)' 4b825dc642cb6eb9a060e54bf8d69288fbee4904)
好吧,这是我想出的:
# 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
这是我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"
要切换根提交:
首先,创建您想要的提交。
其次,使用以下命令切换提交的顺序:
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
结合最新和最伟大的。没有副作用,没有冲突,保留标签。
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可能会混乱,除非还修复了其他分支。
按照答案Aristotle 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
给出有用的提示。
我知道这篇文章很旧,但是在谷歌搜索“插入commit git”时,这是第一页。
为什么使简单的事情变得复杂?
您有ABC,而您想要ABZC。
git rebase -i trunk
(或B之前的任何内容)git add ..
git commit
(git commit --amend
它将编辑B而不创建Z)[您可以git commit
在此处添加任意数量的内容以插入更多提交。当然,您可能在步骤5上遇到了麻烦,但是解决git合并冲突是您应该具备的技能。如果没有,请练习!]
git rebase --continue
很简单,不是吗?
如果您了解了git rebase
,添加“ root”提交应该不是问题。
玩得开心!
git rebase
不能做到这一点。