我们最近完成了从 Mercurial 到 Git 的转换,一切进展顺利,我们甚至能够获得所需的转换,使存储库中的所有内容看起来/工作相对正确。我们添加了一个 .gitignore
并开始了。
但是,一旦我们 merge/使用我们的任何旧功能分支,我们就会遇到一些极端的减速。稍微探索一下,我们发现由于 .gitignore
仅在我们查看其他提交时添加到 develop
分支,而没有将 develop merge 到它们中 git chuggs 因为它令人窒息分析我们所有的构建工件(二进制文件)等...因为这些旧分支没有 .gitignore
文件。
我们想要做的是使用 .gitignore 有效地插入一个新的根提交,这样它就会追溯填充到所有的头部/标签中。我们很乐意重写历史,我们的团队相对较小,因此让每个人都停止此操作并在历史重写完成后重新 pull 他们的存储库是没有问题的。
我找到了关于将 master rebase 到一个新的根提交上的信息,这对 master 有效,问题是它让我们的特性分支在旧的历史树上分离,它还会重播整个历史具有新的提交日期/时间。
有什么想法或者我们在这方面运气不好吗?
最佳答案
你想要做的事情将涉及两个阶段:追溯添加一个带有合适的 .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
为清楚起见,*.c
文件代表 C 源文件,*.o
是本应忽略的已编译目标文件。
在 bar-feature 分支上,您添加了一个合适的 .gitignore
并删除了不应被跟踪的对象文件,但您希望该策略在您的导入中随处可见。
请注意 git lola
是一个 non-standard但有用的别名。
git config --global alias.lola \
'log --graph --decorate --pretty=oneline --abbrev-commit --all'
新根提交
按如下方式创建新的根提交。
$ git checkout --orphan new-root
Switched to a new branch 'new-root'
git checkout
文档记录了新孤立分支可能出现的意外状态。
If you want to start a disconnected history that records a set of paths that is totally different from the one of start_point, then you should clear the index and the working tree right after creating the orphan branch by running
git rm -rf .
from the top level of the working tree. Afterwards you will be ready to prepare your new files, repopulating the working tree, by copying them from elsewhere, extracting a tarball, etc.
继续我们的例子:
$ 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
现在的历史是这样的
$ git lola
* 00c7780 (HEAD, new-root) Create .gitignore
* f1af2bf(bar-feature) Add bar
| * 71f711a (master) Add foo
|/
* 7f1a361 Commit 2
* eb21590 Commit 1
这有点误导,因为它使 new-root 看起来像是 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>...]'
记下孤儿的 SHA,因为您稍后会用到它。在这个例子中,它是
$ git rev-parse HEAD
00c778087723ae890e803043493214fb09706ec7
改写历史
我们希望 git filter-branch
进行三项广泛的更改。
- 拼接新的根提交。
- 删除所有临时文件。
- 使用新根中的
.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
解释:
--parent-filter
选项 Hook 在您的新根提交中。eb215...
是旧根提交的完整 SHA,cf.git rev-parse eb215
--index-filter
选项有两部分:- 如上所述运行
git rm
会从整个树中删除任何匹配*.o
的内容,因为 glob 模式是由 git 而不是 shell 引用和解释的。 - 使用
git ls-files
检查现有的.gitignore
,如果不存在,指向 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
您的原件仍然安全。例如,master 分支现在位于 refs/original/refs/heads/master
下。查看新重写的分支中的更改。当您准备好删除备份时,运行
git update-ref -d refs/original/refs/heads/master
您可以编写一条命令以在一条命令中覆盖所有备份引用,但我建议仔细检查每条命令。
结论
最后,新的历史是
$ 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
观察所有目标文件都消失了。 bar-feature 中对 .gitignore
的修改是因为我使用了不同的内容来确保它会被保留。为了完整性:
$ 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
new-root ref 不再有用,所以处理它
$ git checkout master
$ git branch -d new-root
关于git - 我可以重写整个 git 存储库的历史以包含我们忘记的东西吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27927933/