简体   繁体   English

在子目录中合并 git 存储库

[英]Merge git repository in subdirectory

I'd like to merge a remote git repository in my working git repository as a subdirectory of it.我想在我的工作 git 存储库中合并一个远程 git 存储库作为它的子目录。 I'd like the resulting repository to contain the merged history of the two repositories and also that each file of the merged-in repository retain its history as it was in the remote repository.我希望生成的存储库包含两个存储库的合并历史记录,并且合并存储库的每个文件都保留其在远程存储库中的历史记录。 I tried using the subtree strategy as mentioned in How to use the subtree merge strategy , but after following that procedure, although the resulting repository contains indeed the merged history of the two repositories, individual files coming from the remote one haven't retained their history (`git log' on any of them just shows a message "Merged branch...").我尝试使用如何使用子树合并策略中提到的子树策略,但是在执行该过程之后,尽管生成的存储库确实包含两个存储库的合并历史记录,但来自远程存储库的单个文件并未保留其历史记录(其中任何一个的`git log'只显示一条消息“合并分支......”)。

Also I don't want to use submodules because I do not want the two combined git repositories to be separate anymore.此外,我不想使用子模块,因为我不想再将两个合并的 git 存储库分开。

Is it possible to merge a remote git repository in another one as a subdirectory with individual files coming from the remote repository retaining their history?是否可以将远程 git 存储库中的远程 git 存储库合并为一个子目录,其中来自远程存储库的单个文件保留其历史记录?

Thanks very much for any help.非常感谢您的帮助。

EDIT: I'm currently trying out a solution that uses git filter-branch to rewrite the merged-in repository history.编辑:我目前正在尝试使用 git filter-branch 重写合并存储库历史的解决方案。 It does seem to work, but I need to test it some more.它似乎确实有效,但我需要再测试一下。 I'll return to report on my findings.我会回来报告我的发现。

EDIT 2: In hope I make myself more clear I give the exact commands I used with git's subtree strategy, which result in apparent loss of history of the files of the remote repository.编辑 2:希望我让自己更清楚,我给出了我在 git 的子树策略中使用的确切命令,这导致远程存储库文件的历史记录明显丢失。 Let A be the git repo I'm currently working in and B the git repo I'd like to incorporate into A as a subdirectory of it.让 A 成为我目前正在使用的 git 存储库,而 B 是我想合并到 A 中作为它的子目录的 git 存储库。 It did the following:它做了以下事情:

git remote add -f B <url-of-B>
git merge -s ours --no-commit B/master
git read-tree --prefix=subdir/Iwant/to/put/B/in/ -u B/master
git commit -m "Merge B as subdirectory in subdir/Iwant/to/put/B/in."

After these commands and going into directory subdir/Iwant/to/put/B/in, I see all files of B, but git log on any one of them shows just the commit message "Merge B as subdirectory in subdir/Iwant/to/put/B/in."在执行这些命令并进入目录 subdir/Iwant/to/put/B/in 后,我看到了 B 的所有文件,但其中任何一个的git log仅显示提交消息“将 B 合并为 subdir/Iwant/to 中的子目录/放/B/输入。” Their file history as it is in B is lost.他们在 B 中的文件历史记录丢失了。

What seems to work (since I'm a beginner on git I may be wrong) is the following:似乎有效(因为我是 git 的初学者,我可能是错的)如下:

git remote add -f B <url-of-B>
git checkout -b B_branch B/master  # make a local branch following B's master
git filter-branch --index-filter \ 
   'git ls-files -s | sed "s-\t\"*-&subdir/Iwant/to/put/B/in/-" |
        GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
                git update-index --index-info &&
        mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' HEAD 
git checkout master
git merge B_branch

The command above for filter-branch is taken from git help filter-branch , in which I only changed the subdir path.上面的 filter-branch 命令取自git help filter-branch ,其中我只更改了 subdir 路径。

git-subtree is a script designed for exactly this use case of merging multiple repositories into one while preserving history (and/or splitting history of subtrees, though that is seems to be irrelevant to this question). git-subtree是一个脚本,专门用于将多个存储库合并为一个同时保留历史记录(和/或拆分子树的历史记录,尽管这似乎与此问题无关)的用例。 It is distributed as part of the git tree since release 1.7.11 . 自 1.7.11 版以来,它作为 git 树的一部分分发。

To merge a repository <repo> at revision <rev> as subdirectory <prefix> , use git subtree add as follows:要将修订版<rev> <repo>处的存储库<repo>合并为子目录<prefix> ,请使用git subtree add如下:

git subtree add -P <prefix> <repo> <rev>

git-subtree implements the subtree merge strategy in a more user friendly manner. git-subtree 以更加用户友好的方式实现了子树合并策略

The downside is that in the merged history the files are unprefixed (not in a subdirectory).缺点是在合并的历史记录中,文件没有前缀(不在子目录中)。 Say you merge repository a into b .假设您将存储库a合并到b As a result git log a/f1 will show you all the changes (if any) except those in the merged history.结果git log a/f1将向您显示除合并历史记录中的更改之外的所有更改(如果有)。 You can do:你可以做:

git log --follow -- f1

but that won't show the changes other then in the merged history.但这不会显示合并历史记录中的其他更改。

In other words, if you don't change a 's files in repository b , then you need to specify --follow and an unprefixed path.换句话说,如果你不改变a在库中的文件b ,那么你需要指定--follow和前缀的路径。 If you change them in both repositories, then you have 2 commands, none of which shows all the changes.如果您在两个存储库中更改它们,那么您有 2 个命令,其中没有一个显示所有更改。

More on ithere .更多关于它在这里

After getting the fuller explanation of what is going on, I think I understand it and in any case at the bottom I have a workaround.在对正在发生的事情有了更全面的解释后,我想我明白了,无论如何,在底部我有一个解决方法。 Specifically, I believe what is happening is rename detection is being fooled by the subtree merge with --prefix.具体来说,我相信正在发生的事情是重命名检测被带有 --prefix 的子树合并所愚弄。 Here is my test case:这是我的测试用例:

mkdir -p z/a z/b
cd z/a
git init
echo A>A
git add A
git commit -m A
echo AA>>A
git commit -a -m AA
cd ../b
git init
echo B>B
git add B
git commit -m B
echo BB>>B
git commit -a -m BB
cd ../a
git remote add -f B ../b
git merge -s ours --no-commit B/master
git read-tree --prefix=bdir -u B/master
git commit -m "subtree merge B into bdir"
cd bdir
echo BBB>>B
git commit -a -m BBB

We make git directories a and b with several commits each.我们创建 git 目录 a 和 b,每个目录都有多个提交。 We do a subtree merge, and then we do a final commit in the new subtree.我们进行子树合并,然后在新的子树中进行最终提交。

Running gitk (in z/a) shows that the history does appear, we can see it.运行gitk (在 z/a 中)显示历史确实出现了,我们可以看到它。 Running git log shows that the history does appear.运行git log显示历史确实出现了。 However, looking at a specific file has a problem: git log bdir/B但是,查看特定文件有问题: git log bdir/B

Well, there is a trick we can play.好吧,我们可以玩一个技巧。 We can look at the pre-rename history of a specific file using --follow.我们可以使用 --follow 查看特定文件的重命名前历史记录。 git log --follow -- B . git log --follow -- B . This is good but isn't great since it fails to link the history of the pre-merge with the post-merge.这很好,但不是很好,因为它无法将合并前的历史与合并后的历史联系起来。

I tried playing with -M and -C, but I wasn't able to get it to follow one specific file.我尝试使用 -M 和 -C,但我无法让它跟随一个特定的文件。

So, the solution, I feel, is to tell git about the rename that will be taking place as part of the subtree merge.所以,我觉得解决方案是告诉 git 将作为子树合并的一部分进行的重命名。 Unfortunately git-read-tree is pretty fussy about subtree merges so we have to work through a temporary directory, but that can go away before we commit.不幸的是 git-read-tree 对子树合并非常挑剔,所以我们必须通过一个临时目录工作,但这可以在我们提交之前消失。 Afterwards, we can see the full history.之后,我们可以看到完整的历史。

First, create an "A" repository and make some commits:首先,创建一个“A”存储库并进行一些提交:

mkdir -p z/a z/b
cd z/a
git init
echo A>A
git add A
git commit -m A
echo AA>>A
git commit -a -m AA

Second, create a "B" repository and make some commits:其次,创建一个“B”存储库并进行一些提交:

cd ../b
git init
echo B>B
git add B
git commit -m B
echo BB>>B
git commit -a -m BB

And the trick to making this work : force Git to recognize the rename by creating a subdirectory and moving the contents into it.以及完成这项工作的技巧:通过创建子目录并将内容移动到其中来强制 Git 识别重命名。

mkdir bdir
git mv B bdir
git commit -a -m bdir-rename

Return to repository "A" and fetch and merge the contents of "B":返回存储库“A”并获取并合并“B”的内容:

cd ../a
git remote add -f B ../b
git merge -s ours --no-commit B/master
# According to Alex Brown and pjvandehaar, newer versions of git need --allow-unrelated-histories
# git merge -s ours --allow-unrelated-histories --no-commit B/master
git read-tree --prefix= -u B/master
git commit -m "subtree merge B into bdir"

To show that they're now merged:为了表明它们现在已合并:

cd bdir
echo BBB>>B
git commit -a -m BBB

To prove the full history is preserved in a connected chain:为了证明完整的历史记录保存在一个连接的链中:

git log --follow B

We get the history after doing this, but the problem is that if you are actually keeping the old "b" repo around and occasionally merging from it (say it is actually a third party separately maintained repo) you are in trouble since that third party will not have done the rename.这样做后我们得到了历史记录,但问题是,如果您实际上保留旧的“b”存储库并偶尔从中合并(假设它实际上是第三方单独维护的存储库),那么您就会遇到麻烦,因为该第三方不会进行重命名。 You must try to merge new changes into your version of b with the rename and I fear that will not go smoothly.您必须尝试通过重命名将新更改合并到您的 b 版本中,我担心这不会顺利进行。 But if b is going away, you win.但是如果 b 消失了,你就赢了。

I wanted to我想

  1. keep a linear history without explicit merge, and在没有显式合并的情况下保持线性历史,并且
  2. make it look like the files of the merged repository had always existed in the subdirectory, and as a side effect make git log -- file work without --follow .使它看起来像合并存储库的文件一直存在于子目录中,并且作为副作用使git log -- file在没有--follow情况下--follow

Step 1 : Rewrite history in the source repository to make it look like all files always existed below the subdirectory.第 1 步:重写源存储库中的历史记录,使其看起来所有文件始终存在于子目录下。

Create a temporary branch for the rewritten history.为重写的历史创建一个临时分支。

git checkout -b tmp_subdir

Then use git filter-branch as described in How can I rewrite history so that all files, except the ones I already moved, are in a subdirectory?然后使用git filter-branch ,如如何重写历史记录中所述,以便除我已经移动的文件之外的所有文件都在子目录中? :

git filter-branch --prune-empty --tree-filter '
if [ ! -e foo/bar ]; then
    mkdir -p foo/bar
    git ls-tree --name-only $GIT_COMMIT | xargs -I files mv files foo/bar
fi'

Step 2 : Switch to the target repository.第 2 步:切换到目标存储库。 Add the source repository as remote in the target repository and fetch its contents.将源存储库作为远程存储库添加到目标存储库中并获取其内容。

git remote add sourcerepo .../path/to/sourcerepo
git fetch sourcerepo

Step 3 : Use merge --onto to add the commits of the rewritten source repository on top of the target repository.第 3 步:使用merge --onto将重写的源存储库的提交添加到目标存储库之上。

git rebase --preserve-merges --onto master --root sourcerepo/tmp_subdir

You can check the log to see that this really got you what you wanted.您可以检查日志以查看这是否确实满足了您的需求。

git log --stat

Step 4 : After the rebase you're in “detached HEAD” state.第 4 步:在 rebase 之后,您处于“分离的 HEAD”状态。 You can fast-forward master to the new head.您可以快进掌握到新的头部。

git checkout -b tmp_merged
git checkout master
git merge tmp_merged
git branch -d tmp_merged

Step 5 : Finally some cleanup: Remove the temporary remote.第 5 步:最后一些清理工作:删除临时遥控器。

git remote rm sourcerepo

If you are really wanting to stitch things together, look up grafting.如果你真的想把东西缝合在一起,看看嫁接。 You should also be using git rebase --preserve-merges --onto .您还应该使用git rebase --preserve-merges --onto There is also an option to keep the author date for the committer information.还有一个选项可以保留提交者信息的作者日期。

I found the following solution workable for me.我发现以下解决方案对我有用。 First I go into project B, create a new branch in which already all files will be moved to the new sub directory.首先,我进入项目 B,创建一个新分支,其中已经将所有文件移动到新的子目录。 I then push this new branch to origin.然后我将这个新分支推到原点。 Next I go to project A, add and fetch the remote of B, then I checkout the moved branch, I go back into master and merge:接下来,我转到项目 A,添加并获取 B 的遥控器,然后检出移动的分支,返回 master 并合并:

# in local copy of project B
git checkout -b prepare_move
mkdir subdir
git mv <files_to_move> subdir/
git commit -m 'move files to subdir'
git push origin prepare_move

# in local copy of project A
git remote add -f B_origin <remote-url>
git checkout -b from_B B_origin/prepare_move
git checkout master
git merge from_B

If I go to sub directory subdir , I can use git log --follow and still have the history.如果我转到子目录subdir ,我可以使用git log --follow并且仍然有历史记录。

I'm not a git expert, so I cannot comment whether this is a particularly good solution or if it has caveats, but so far it seems all fine.我不是 git 专家,所以我无法评论这是否是一个特别好的解决方案,或者它是否有警告,但到目前为止似乎一切都很好。

Have you tried adding the extra repository as a git submodule?您是否尝试将额外的存储库添加为 git 子模块? It won't merge the history with the containing repository, in fact, it will be an independent repository.它不会将历史记录与包含的存储库合并,实际上,它将是一个独立的存储库。

I mention it, because you haven't.我提到它,因为你没有。

Say you want to merge repository a into b (I'm assuming they're located alongside one another):假设您想将存储库a合并到b (我假设它们彼此并排放置):

cd a
git filter-repo --to-subdirectory-filter a
cd ..
cd b
git remote add a ../a
git fetch a
git merge --allow-unrelated-histories a/master
git remote remove a

For this you need git-filter-repo installed ( filter-branch is discouraged ).为此,您需要安装git-filter-repo不鼓励使用filter-branch )。

An example of merging 2 big repositories, putting one of them into a subdirectory:https://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731合并 2 个大型存储库,将其中一个放入子目录的示例:https ://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731

More on ithere .更多关于它在这里

Similar to hfs' answer I wanted to类似于 hfs 的回答我想

  • keep a linear history without explicit merge and保持线性历史,无需显式合并和
  • make it look like the files of the merged repository had always existed in the subdirectory, and as a side effect make git log -- file work without --follow .使它看起来像合并存储库的文件一直存在于子目录中,并且作为副作用使git log -- file在没有--follow情况下--follow

However, I chose the more modern filter-repo (assuming the new repo exists and is checked out):但是,我选择了更现代的filter-repo (假设new repo 存在并已签出):

git clone git@host/repo/old.git
cd old
git checkout -b tmp_subdir
git filter-repo --to-subdirectory-filter old

cd ../new
git remote add old ../old
git fetch old
git rebase --rebase-merges --onto main --root old/tmp_subdir --committer-date-is-author-date

you might need to fix conflicts (manually) or change the rebase command to include --merge -s recursive -X theirs if you want to try solving it with theirs version:如果您想尝试使用theirs版本解决冲突,您可能需要(手动)修复冲突或更改 rebase 命令以包含--merge -s recursive -X theirs

git rebase --rebase-merges --onto main --root old/tmp_subdir --committer-
date-is-author-date --merge -s recursive -X theirs

you end up on a detached HEAD, so create a new branch and merge it to main note that modern repositories should not use a "master" branch but a "main"你最终在一个分离的 HEAD 上,所以创建一个新分支并将其合并到主注意现代存储库不应该使用“主”分支而是“主”

branch for a more inclusive language.
git checkout -b old_merge
git checkout main
git merge old_merge

cleanup清理

git branch -d old_merge
git remote rm old

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM