我之前问过如何 压缩前两次提交 在 git 存储库中。

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

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

那么,我想做的是有一个空的初始提交,它的存在只是为了成为第一个。没有代码,什么都没有。只是占用空间,因此它可以作为变基的基础。

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

有帮助吗?

解决方案

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
    

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

(注:在实践中我现在会使用 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

瞧,你已经结束了 master 其历史记录被重写以包括空的根提交。


注意:在缺少 --orphan 切换到 checkout, ,您需要管道来创建一个空分支:

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

其他提示

这是亚里士多德Pagaltzis的乌韦和克莱 - 柯尼希的答案和理查德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

(只是把一切在一个地方)

我喜欢亚里士多德的答案。但是发现,对于一个大的库(> 5000个提交)滤波器分支作品优于底垫有以下几个原因 1)它的速度更快 2)它不需要人工干预时,有一个合并冲突。 3)它可以重写标签 - 保存它们。 注意,滤波器的分支作品,因为没有关于每个内容问题提交 - 这是完全一样的这种“重订”之前

我的步骤是:

# 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名滤波器猫”选项意味着标签将被重写以指向新创建的提交。

我用亚里士多德件和Kent的回答成功:

# 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)除了标记。

git rebase --root --onto $emptyrootcommit

应该很容易做的伎俩

我很兴奋,并写了这个漂亮的脚本的“幂”版......它总是会插入相同的空承诺,如果你运行它两次,它不会改变每次你的提交哈希值。所以,这里是我采取在混帐插入空根

#!/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

好了,这里就是我想出了:

# 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

我认为使用 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"

您可以毫无风险地运行此脚本(即使在执行您以前从未执行过的操作之前进行备份也是一个好主意;)),如果结果不是预期的,只需删除在文件夹中创建的文件即可 .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 rebase --root --onto $(git commit-tree -m 'Initial commit (empty)' 4b825dc642cb6eb9a060e54bf8d69288fbee4904)

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

  • 它检查原始分支,而不仅仅是 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的变基-i --root

这是编辑器将与提交出现,直到根提交,如:

<强>挑1234旧的根消息

<强>挑0294一个提交的中间

挑5678承诺要放在在根

您可以然后把你要第一,承诺通过将其放置在第一线。在该示例:

挑5678承诺要放在在根

<强>挑1234旧的根消息

<强>挑0294一个提交的中间

退出提交顺序将已经改变了编辑器。

PS:要改变编辑GIT中的用途,运行:

<强>混帐配置--global core.editor name_of_the_editor_program_you_want_to_use

以下的答案亚里士多德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).

请注意你的条件不应该包含没有本地修改等待COMMITED。结果 注git checkout --orphan将在混帐的新版本的工作,我想。结果 注意大部分时间git status的给出有用的提示。

启动一个新的存储库。

设置回溯到你想要的开始日期。

尽你希望你做到了,调整系统时间来反映当你希望你做了这样的方式。根据需要,以避免大量的不必要的分型从现有存储库拉文件。

当你到今天,交换库和你做。

如果你只是疯了(建立),但比较聪明的(有可能的,因为你必须有一定的智慧的想出疯狂的想法是这样),你将脚本的进程。

这也将使它更好,当你决定要过去已经发生一些其他的方式,每周从现在开始。

我知道这篇文章很旧,但这个页面是谷歌搜索“插入提交 git”时的第一个页面。

为什么要把简单的事情搞复杂呢?

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

  1. git rebase -i trunk (或 B 之前的任何内容)
  2. 将 B 行的 pick 更改为编辑
  3. 进行更改: git add ..
  4. git commit (git commit --amend 这将编辑 B 而不会创建 Z)

[你可以做尽可能多的 git commit 因为您想在这里插入更多提交。当然,你可能在第5步中遇到麻烦,但是解决与git的合并冲突是你应该具备的技能。如果不会,那就练习吧!]

  1. git rebase --continue

很简单,不是吗?

如果你明白的话 git rebase, ,添加“root”提交应该不是问题。

祝你玩得开心!

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top