Git:将文件的历史记录从一个存储库复制到另一个存储库

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/44777043/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-19 12:48:11  来源:igfitidea点击:

Git: Copy history of file from one repository to another

git

提问by Paul Varghese

I've two git repositories say A and B, both contains a file named file1.cc. Is it possible to merge/copy the history of file1.cc in repo A to file1.cc in repo B?

我有两个 git 存储库说 A 和 B,都包含一个名为 file1.cc 的文件。是否可以将 repo A 中的 file1.cc 的历史合并/复制到 repo B 中的 file1.cc?

The problem is we've already moved the files from repo A to repo B and the history of all the files are lost. but now some of the developers already started working on the repo B and pushed their changes. So now I want merge/copy history of some files from repo A to repo B and which are applicable only for some of the files. Is it possible to do so? Or the history of the files once lost is lost forever?

问题是我们已经将文件从 repo A 移动到 repo B 并且所有文件的历史记录都丢失了。但是现在一些开发人员已经开始在 repo B 上工作并推动他们的更改。所以现在我想要将一些文件从 repo A 合并/复制到 repo B 并且仅适用于某些文件。有可能这样做吗?或者曾经丢失的文件的历史永远丢失?

Please help. Thanks in advance.

请帮忙。提前致谢。

回答by Mark Adelsberger

It can be done, but it may not be easy. But first things first: there is no "moving the history of a file". There is only moving commits, so if you want commits that represent the history of a subset of files, then creating those commits is the first challenge.

这是可以做到的,但可能并不容易。但首先要注意的是:没有“移动文件的历史记录”。只有移动提交,所以如果你想要代表文件子集历史的提交,那么创建这些提交是第一个挑战。

The simplest thing would be to transfer allhistory. (In fact, if it happens that you made Repo B as a shallow clone of Repo A, then you could just un-shallow it and be done. But I'm guessing that's not how you created Repo B...)

最简单的事情是转移所有历史记录。(实际上,如果碰巧您将 Repo B 作为 Repo A 的浅层克隆制作,那么您可以将它取消浅层并完成。但我猜这不是您创建 Repo B 的方式......)

Regardless, since you're moving from Repo A to Repo B, maybe there's some history you specifically want to remove. That's potentially a whole topic of its own, but let's just assume you really want only the history of a few files.

无论如何,由于您要从 Repo A 转移到 Repo B,因此您可能特别想删除某些历史记录。这可能是一个完整的主题,但让我们假设您真的只想要几个文件的历史记录。

In the special case where all the files you want (and no others) are in a subdirectory, and you want (or, at least, can accept) to move those files to the repo's root directory, you can use filter-branchwith the --subdirectory-filter.

在你想要的所有文件的特殊情况(没有人)是一个子目录,并且希望(或者,至少,可以接受),以将这些文件移动到回购的根目录下,就可以使用filter-branch--subdirectory-filter

More generally, if we assume paths shouldn'tchange and that the files you want could be anywhere in the tree, then you could use filter-branchwith an --index-filter.

更一般地,如果我们假设路径不应该改变,你想要的文件可以在任何地方树,那么你可以使用filter-branch同一个--index-filter

git filter-branch --index-filter 'git rm --cached --ignore-unmatch each file or *glob* you do NOT want' --prune-empty -- all

That could take a while if the repo had a lot of commits. If the list of files to rmis not trivial, you may want to put multiple git rmcommands in a shell script and use that as the --index-filterargument instead of inlining it as shown above.

如果 repo 有很多提交,那可能需要一段时间。如果文件列表rm不重要,您可能希望将多个git rm命令放在一个 shell 脚本中并将其用作--index-filter参数,而不是如上所示将其内联。

Well, one way or other hopefully you've got a history you'd like to graft into Repo B.

好吧,无论如何,希望你有一段你想嫁接到回购 B 的历史。

cd repo-b
git remote add repo-a path/to/repo-a
git fetch repo-a

Now you have in Repo B:

现在你在回购 B 中:

... A -- B <--(repo-a/master)
  \
   (repo-a/other-branches-maybe)

B' -- C -- D (master)(origin/master)

So I'm making an assumption here, that the TREEfrom the last mastercommit in Repo A - the one from which our history rewrite created B- or at least some part of that tree, was imported as the root commit in Repo B.

所以我在这里做出一个假设,即Repo A 中TREE的最后一次master提交——我们的历史重写创建B的那个提交——或者至少那棵树的一部分,作为 Repo B 中的根提交导入。

Now you have three options: re-parent, rebase, or replace

现在您有三个选项:重新父级、重新设置基准或替换

Since I assume the recent history state is more important than the older-history state, and that the older history is just being added for reference, the safest thing would be to reparent Cto B. (You could choose to reparent B'to Ainstead, but I'm assuming that doesn't make much difference...)

由于我认为最近的历史状态比旧的历史状态更重要,并且旧的历史只是被添加以供参考,因此最安全的做法是CB. (你可以选择重新B'设置A,但我认为这没有太大区别......)

So drawing from the filter-branchdocs at https://git-scm.com/docs/git-filter-branchyou could

因此filter-branch,您可以从https://git-scm.com/docs/git-filter-branch 上的文档中提取

# be sure you're on master
echo "$commit-id $graft-id" >> .git/info/grafts
git filter-branch $graft-id..HEAD

where $commit-idis the SHA for Band $graft-idis the SHA for C

其中,$commit-id对于SHAB$graft-id对于SHAC

A rebase might be a little simpler (assuming a certain level of consistency between the histories) but introduces the possibility that you end up modifying the tree at D. If you do decide to try a rebase, it would be

变基可能更简单一些(假设历史记录之间具有一定程度的一致性),但会引入您最终修改D. 如果您决定尝试重新设置基准,那将是

git rebase --onto repo-A/master B' master

where B'is the Repo B root commit's SHA ID. (Alternately

B'回购 B 根提交的 SHA ID在哪里。(交替

git rebase --interactive --onto repo-A/master --root master

and then drop the entry for B'.)

然后删除条目B'。)

Either of these options will rewrite commits Cand D. (Even though re-parenting ensures the TREEis unchanged, the commits are still replaced.) Your developers would have to treat this as an upstream rebase (see the git rebasedocumentation under "recovering from upstream rebase"). To mitigate this, I generally recommend doing a coordinated cut-over where devs check in everything they have, discard their clones, then you do the rewrite and they re-clone from the new repo.

这些选项中的任何一个都将重写提交CD. (即使重新养育确保TREE不变,提交仍会被替换。)您的开发人员必须将其视为上游变基(请参阅git rebase“从上游变基恢复”下的文档)。为了缓解这种情况,我通常建议进行协调切换,开发人员检查他们拥有的一切,丢弃他们的克隆,然后你进行重写,他们从新的 repo 重新克隆。

If you want to avoid the rewrite, you can use the third option: git replace. This is known to have a few quirks, and it requires each clone to be set up correctly in order to "see" the spliced history.

如果你想避免重写,你可以使用第三个选项:git replace. 众所周知,这有一些怪癖,它需要正确设置每个克隆,以便“查看”拼接历史。

So to support this, you'd just tag B(and maybe also B'):

因此,为了支持这一点,您只需标记B(也许还有B'):

git tag old-history repo-a/master
git tag new-root B'

(where B'is the appropriate SHA value ID, or equivalent expression).

(其中B'是适当的 SHA 值 ID 或等效表达式)。

When someone clones the repo, they'll see only the new history, but they can say

当有人克隆 repo 时,他们只会看到新的历史记录,但他们可以说

git replace new-root old-history

and this will paper over the break in history.

这将掩盖历史的突破。

Once you've done your reparent, rebase, or replace - you can remove the repo-aremote.

完成重新父级、重新设置基准或替换后 - 您可以移除repo-a遥控器。