我可以重写整个git存储库的历史记录以包含我们忘记的内容吗? [英] Can I rewrite an entire git repository's history to include something we forgot?

查看:79
本文介绍了我可以重写整个git存储库的历史记录以包含我们忘记的内容吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

We recently completed a conversion from Mercurial to Git, everything went smoothly, we were even able to get the transforms needed to make everything look / work relatively correctly in the repository. We added a .gitignore and got underway.

但是,一旦我们与任何旧功能分支合并/一起工作,我们就会遇到一些极端的问题.经过一番探索,我们发现由于.gitignore仅在查看其他提交而不合并开发时才添加到develop分支,因此git chuggs是令人沮丧的,因为它试图分析我们所有的构建工件(二进制文件)等...因为这些旧分支没有.gitignore文件.

However we're experiencing some extreme slowdowns as soon as we encorporate/work with any of our old feature branches. A little exploring and we found that since the .gitignore was only added to the develop branch when we look at other commits without merging develop up into them git chuggs because it's choking trying to analyze all our build artifacts (binary files) etc... since there was no .gitignore file for these old branches.

我们想要做的是有效地使用.gitignore插入一个新的root提交,以便它可追溯地填充到所有heads/tags中.我们对重新编写历史记录感到满意,我们的团队规模相对较小,因此在历史记录重写完成后,让每个人都停止执行此操作并重新拉动其存储库是没有问题的.

What we'd like to do is effectively insert a new root commit with the .gitignore so it would retroactively populate in all heads/tags. We're comfortable with a re-write of history, our team is relatively small so getting everyone to halt for this operation and re-pull thier repositories when the history re-write is done is no problem.

我已经找到了有关将master重新部署到新的根提交上的信息,并且该信息对master有效,问题是它使功能分支脱​​离了旧的历史树,还重播了整个历史带有新的提交日期/时间.

I've found information about rebasing master onto a new root commit and this works for master, the problem is it leaves our feature branches detached on the old history tree, it also replays the entire history with a new commit date/time.

有什么想法吗?或者我们对这个想法不满意吗?

Any ideas or are we out of luck on this one?

推荐答案

您想要做的事情将涉及两个阶段:追溯添加具有合适的.gitignore的新根目录,并清理您的历史记录以删除本不应该被删除的文件.添加. git filter-branch命令可以同时做到.

What you want to do will involve two phases: retroactively add a new root with a suitable .gitignore and scrub your history to remove files that should not have been added. The git filter-branch command can do both.

请考虑一下您的历史记录.

Consider a representative of your history.

$ 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是应忽略的已编译目标文件.

For clarity, the *.c files represent C source files and *.o are compiled object files that should have been ignored.

在bar功能分支上,添加了一个合适的.gitignore并删除了不应该被跟踪的对象文件,但是您希望该策略在导入过程中随处可见.

On the bar-feature branch, you added a suitable .gitignore and deleted object files that should not have been tracked, but you want that policy reflected everywhere in your import.

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

Note that git lola is a non-standard but useful alias.

git config --global alias.lola \
  'log --graph --decorate --pretty=oneline --abbrev-commit --all'

新的根提交

按如下所示创建新的根提交.

New Root Commit

Create the new root commit as follows.

$ git checkout --orphan new-root
Switched to a new branch 'new-root'

git checkout文档记录了新的孤立分支的可能意外状态.

The git checkout documentation notes a possible unanticipated state of the new orphan branch.

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

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

这有点误导,因为它使新根看起来像是bar功能的后代,但它实际上没有父级.

That is slightly misleading because it makes new-root look like it is a descendant of bar-feature, but it really has no parent.

$ 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,因为稍后将需要它.在此示例中,是

Make note of the SHA for the orphan because you will need it later. In this example, it is

$ git rev-parse HEAD
00c778087723ae890e803043493214fb09706ec7

重写历史记录

我们希望git filter-branch进行三项重大更改.

Rewriting History

We want git filter-branch to make three broad changes.

  1. 接合新的根落实.
  2. 删除所有临时文件.
  3. 使用新根目录中的.gitignore,除非已经存在.
  1. Splice in the new root commit.
  2. Delete all the temporary files.
  3. Use the .gitignore from the new root unless one already exists.

在命令行上,该提示为

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选项挂接新的root提交.
    • eb215...是旧的根提交 cf. git rev-parse eb215
    • 的完整SHA
    • The --parent-filter option hooks in your new root commit.
      • eb215... is the full SHA of the old root commit, cf. git rev-parse eb215
      • 如上所述,运行git rm会从整个树中删除所有与*.o匹配的内容,因为glob模式是由git而不是shell引用和解释的.
      • 使用git ls-files检查现有的.gitignore,如果不存在,请指向new-root中的一个.
      • Running git rm as above deletes anything matching *.o from the entire tree because the glob pattern is quoted and interpreted by git rather than the shell.
      • Check for an existing .gitignore with git ls-files, and if it is not there, point to the one in new-root.

      您看到的输出将类似于

      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
      

      您的原件仍然安全.例如,主分支现在位于refs/original/refs/heads/master下.查看新重写的分支中的更改.准备删除备份时,运行

      Your originals are still safe. The master branch now lives under refs/original/refs/heads/master, for example. Review the changes in your newly rewritten branches. When you are ready to delete the backup, run

      git update-ref -d refs/original/refs/heads/master
      

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

      You could cook up a command to cover all backup refs in one command, but I recommend careful review for each one.

      最后,新历史是

      $ 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功能中对.gitignore的修改是因为我使用了不同的内容来确保将其保留.为了完整性:

      Observe that all the object files are gone. The modification to .gitignore in bar-feature is because I used different contents to make sure it would be preserved. For completeness:

      $ 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
      

      新的根引用不再有用,因此请使用

      The new-root ref is no longer useful, so dispose of it with

      $ git checkout master
      $ git branch -d new-root
      

      这篇关于我可以重写整个git存储库的历史记录以包含我们忘记的内容吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆