我可以重写整个git存储库的历史记录以包含我们忘记的内容吗?

Are*_*ren 6 git git-rebase git-filter-branch git-rewrite-history

我们最近完成了从Mercurial到Git的转换,一切顺利,我们甚至能够获得所需的转换,使所有内容在存储库中看起来/工作相对正确.我们添加了一个.gitignore并开始了.

但是,只要我们加入/合作任何旧的功能分支,我们就会遇到一些极端的减速.稍微探索一下,我们发现,因为当我们查看其他提交而没有合并时.gitignore,它只被添加到develop分支中,因为它正在窒息,试图分析我们所有的构建工件(二进制文件)等...因为它有...没有.gitignore这些旧分支的文件.

我们想要做的是有效地使用.gitignore插入新的根提交,以便它可以追溯填充所有头/标签.我们很乐意重写历史记录,我们的团队规模相对较小,所以当历史重写完成后,让每个人都停下来执行此操作并重新启动他们的存储库是没有问题的.

我已经找到了关于将master重新设置到新的root提交上的信息,这对master来说很有用,问题是它在旧的历史记录树上保留了我们的功能分支,它还用新的提交日期/时间重放整个历史记录.

任何想法或我们在这一个运气不好?

Gre*_*con 9

您要执行的操作将涉及两个阶段:追溯添加具有合适的新根.gitignore并擦除历史记录以删除不应添加的文件.该git filter-branch命令可以同时执行.

建立

考虑一下您的历史代表.

$ git lola --name-status
* f1af2bf (HEAD, bar-feature) Add bar
| A     .gitignore
| A     bar.c
| D     main.o
| D     module.o
| * 71f711a (master) Add foo
|/
|   A   foo.c
|   A   foo.o
* 7f1a361 Commit 2
| A     module.c
| A     module.o
* eb21590 Commit 1
  A     main.c
  A     main.o
Run Code Online (Sandbox Code Playgroud)

为清楚起见,这些*.c文件代表C源文件,并且*.o是应该被忽略的编译目标文件.

在条形特征分支上,您添加了一个合适.gitignore且已删除的对象文件,这些文件不应该被跟踪,但您希望该策略反映在导入的任何位置.

请注意,这git lola是一个非标准但有用的别名.

git config --global alias.lola \
  'log --graph --decorate --pretty=oneline --abbrev-commit --all'
Run Code Online (Sandbox Code Playgroud)

新根提交

按如下方式创建新的根提交.

$ git checkout --orphan new-root
Switched to a new branch 'new-root'
Run Code Online (Sandbox Code Playgroud)

git checkout文档指出了新的孤儿分支可能出乎意料的状态.

如果要启动记录一组完全不同于start_point的路径的断开连接的历史记录,则应在创建孤立分支后立即清除索引和工作树,方法是git rm -rf .从工作树的顶层运行.之后,您将准备好准备新文件,重新填充工作树,从其他地方复制它们,提取tarball等.

继续我们的例子:

$ git rm -rf .
rm 'foo.c'
rm 'foo.o'
rm 'main.c'
rm 'main.o'
rm 'module.c'
rm 'module.o'

$ echo '*.o' >.gitignore

$ git add .gitignore

$ git commit -m 'Create .gitignore'
[new-root (root-commit) 00c7780] Create .gitignore
 1 file changed, 1 insertion(+)
 create mode 100644 .gitignore
Run Code Online (Sandbox Code Playgroud)

现在的历史看起来像

$ git lola
* 00c7780 (HEAD, new-root) Create .gitignore
* f1af2bf(bar-feature) Add bar
| * 71f711a (master) Add foo
|/
* 7f1a361 Commit 2
* eb21590 Commit 1
Run Code Online (Sandbox Code Playgroud)

这有点误导,因为它使新根看起来像是bar-feature的后代,但它确实没有父级.

$ git rev-parse HEAD^
HEAD^
fatal: ambiguous argument 'HEAD^': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'
Run Code Online (Sandbox Code Playgroud)

记下孤儿的SHA,因为稍后你会需要它.在这个例子中,它是

$ git rev-parse HEAD
00c778087723ae890e803043493214fb09706ec7
Run Code Online (Sandbox Code Playgroud)

重写历史

我们想git filter-branch做出三大改变.

  1. 在新的根提交中拼接.
  2. 删除所有临时文件.
  3. .gitignore除非已存在,否则请使用新根.

在命令行上,被视为

git filter-branch \
  --parent-filter '
    test $GIT_COMMIT = eb215900cd15ca2cf9ded74f1a0d9d25f65eb2bf && \
              echo "-p 00c778087723ae890e803043493214fb09706ec7" \
      || cat' \
  --index-filter '
    git rm --cached --ignore-unmatch "*.o"; \
    git ls-files --cached --error-unmatch .gitignore >/dev/null 2>&1 ||
      git update-index --add --cacheinfo \
        100644,$(git rev-parse new-root:.gitignore),.gitignore' \
  --tag-name-filter cat \
  -- --all
Run Code Online (Sandbox Code Playgroud)

说明:

  • --parent-filter选项挂钩您的新根提交.
    • eb215...是旧的root提交的完整SHA,参见 git rev-parse eb215
  • --index-filter选项包括两部分:
    • git rm如上所述运行会*.o从整个树中删除任何匹配项,因为glob模式由git而不是shell引用和解释.
    • 检查现有.gitignoregit ls-files,如果不存在,则指向new-root中的那个.
  • 如果您有任何标签,它们将通过身份操作进行映射cat.
  • 单独--终止选项,并且--all是所有引用的简写.

你看到的输出会很像

Rewrite eb215900cd15ca2cf9ded74f1a0d9d25f65eb2bf (1/5)rm 'main.o'
Rewrite 7f1a361ee918f7062f686e26b57788dd65bb5fe1 (2/5)rm 'main.o'
rm 'module.o'
Rewrite 71f711a15fa1fc60542cc71c9ff4c66b4303e603 (3/5)rm 'foo.o'
rm 'main.o'
rm 'module.o'
Rewrite f1af2bf89ed2236fdaf2a1a75a34c911efbd5982 (5/5)
Ref 'refs/heads/bar-feature' was rewritten
Ref 'refs/heads/master' was rewritten
WARNING: Ref 'refs/heads/new-root' is unchanged
Run Code Online (Sandbox Code Playgroud)

你的原件仍然安全.例如,主分支现在生活在refs/original/refs/heads/master.查看新重写分支中的更改.准备好删除备份后,运行

git update-ref -d refs/original/refs/heads/master
Run Code Online (Sandbox Code Playgroud)

您可以在一个命令中编写一个命令来覆盖所有备份引用,但我建议您仔细检查每个命令.

结论

最后,新的历史是

$ git lola --name-status
* ab8cb1c (bar-feature) Add bar
| M     .gitignore
| A     bar.c
| * 43e5658 (master) Add foo
|/
|   A   foo.c
* 6469dab Commit 2
| A     module.c
* 47f9f73 Commit 1
| A     main.c
* 00c7780 (HEAD, new-root) Create .gitignore
  A     .gitignore
Run Code Online (Sandbox Code Playgroud)

观察到所有目标文件都消失了.对.gitignorebar-feature 的修改是因为我使用了不同的内容来确保它被保留.为了完整性:

$ git diff new-root:.gitignore bar-feature:.gitignore
diff --git a/new-root:.gitignore b/bar-feature:.gitignore
index 5761abc..c395c62 100644
--- a/new-root:.gitignore
+++ b/bar-feature:.gitignore
@@ -1 +1,2 @@
 *.o
+*.obj
Run Code Online (Sandbox Code Playgroud)

new-root ref不再有用,所以请将其丢弃

$ git checkout master
$ git branch -d new-root
Run Code Online (Sandbox Code Playgroud)

  • 你是我的flippin英雄! (2认同)