使用git-subtree添加远程回购的子目录 [英] Add subdirectory of remote repo with git-subtree

查看:162
本文介绍了使用git-subtree添加远程回购的子目录的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有没有办法使用git-subtree将远程仓库的子目录添加到我的仓库的子目录中?

假设我有 main 存储库:

  / 
dir1
dir2

这个存储库:

  / 
libdir
某些文件
某些文件将被忽略

我想将 library / libdir导入 main / dir1,使其看起来像这样:

  / 
dir1
某些文件
dir2

使用git-subtree,我可以指定使用 - 前缀导入到 dir1 参数,但我也可以指定只采取子树中的特定目录的内容?



使用git子树的原因是我可以稍后同步这两个存储库。



对于这些示例,我将考虑合并来自 contrib / completion / rel =noreferrer> https://github.com/git/git.git 转换为本地存储库的 third_party / git_completion /



1。 git diff | git apply



这可能是我找到的最好方式。我只测试单向合并;

 #首次执行此操作:
$ git remote add -f -t master --no-tags gitgit https://github.com/git/git.git
#下一行是可选的。没有它,上游提交得到
#压扁;与它一起,它们将被包含在当地的历史中。
$ git merge -s我们的--no-commit gitgit / master
#尾部的斜线在这里很重要!
$ git read-tree --prefix = third_party / git-completion / -u gitgit / master:contrib / completion
$ git commit

#将来您可以合并其他更改如下:
#下一行是可选的。没有它,上游提交得到
#压扁;与它一起,它们将被包含在当地的历史中。
$ git merge -s our -no-commit gitgit / master
#使用这种技术将最近
#合并的提交哈希替换为下面的SHA1(即最近的在当时提交
#gitgit / master)。
$ git diff --color = never 53e53c7c81ce2c7c4cd45f95bc095b274cb28b76:contrib / completion gitgit / master:contrib / completion | git apply -3 --directory = third_party / git-completion
#现在修改任何冲突,如果你修改third_party / git-completion。
$ git commit

由于难以记忆您合并的最近提交的SHA1从上游存储库中,我写了这个Bash函数,它为你做了所有的辛苦工作(从git log中抓取它):

  git-merge-subpath(){
本地SQUASH
if [[$ 1 ==--squash]];那么
SQUASH = 1
shift
fi
if(($#!= 3));然后
本地PARAMS =[ - squash] SOURCE_COMMIT SOURCE_PREFIX DEST_PREFIX
echoUSAGE:$ {FUNCNAME [0]} $ PARAMS
return 1
fi

#友好参数名称;去除前缀中的任何尾部斜线。
本地SOURCE_COMMIT =$ 1SOURCE_PREFIX =$ {2%/}DEST_PREFIX =$ {3%/}

本地SOURCE_SHA1
SOURCE_SHA1 = $(git rev -parse --verify$ SOURCE_COMMIT ^ {commit})||返回1

本地OLD_SHA1
本地GIT_ROOT = $(git rev-parse --show-toplevel)
if [[-n$(ls -A$ GIT_ROOT / $ DEST_PREFIX2> / dev / null)]];那么
#OLD_SHA1在没有匹配的情况下将保持为空。
local RE =^ $ {FUNCNAME [0]}:[0-9a-f] {40} $ SOURCE_PREFIX $ DEST_PREFIX \ $
OLD_SHA1 = $(git log -1 --format =%b -E --grep =$ RE\
| grep --color = never -E$ RE| tail -1 | awk'{print $ 2}')
fi

当地OLD_TREEISH
如果[[-n $ OLD_SHA1]];那么
OLD_TREEISH =$ OLD_SHA1:$ SOURCE_PREFIX
else
#这是第一次运行git-merge-subpath,所以diff与
# git-merge-subpath创建的最后一个提交。
OLD_TREEISH = $(git hash-object -t tree / dev / null)
fi&&

if [[-z $ SQUASH]];然后
git合并-s我们的--no-commit$ SOURCE_COMMIT
fi&&

git diff --color = never$ OLD_TREEISH$ SOURCE_COMMIT:$ SOURCE_PREFIX\
| git apply -3 --directory =$ DEST_PREFIX|| git mergetool

if(($?== 1));那么
会回显呃 - 哦!试试清理| git reset --merge |。
else
git commit -em合并$ SOURCE_COMMIT:$ SOURCE_PREFIX /到$ DEST_PREFIX /

#随意编辑上面的标题和正文,但一定要保留
#$ {FUNCNAME [0]}:下面一行是完整的,所以$ {FUNCNAME [0]}可以在grep git log时再次找到它
#$ b $ {FUNCNAME [0]} :$ SOURCE_SHA1 $ SOURCE_PREFIX $ DEST_PREFIX
fi
}

这:

#第一次执行此操作:
$ git remote add -f -t master --no-tags gitgit https://github.com/git/git.git
$ git-merge-subpath gitgit / master contrib / completion third_party / git-completion

#今后,您可以合并其他更改,如下所示:
$ git fetch gitgit
$ git-merge-subpath gitgit / master contrib / completion third_party / git-completion
#现在修复如果您修改了third_party / git-completion,那么会有任何冲突。



2。 git read-tree



如果你永远不会对合并后的文件进行本地修改,即你总是用最新版本覆盖本地子目录从上游开始,那么类似但更简单的方法是使用 git read-tree

 #第一次执行此操作:
$ git remote add -f -t master --no-tags gitgit https://github.com/git/git.git
#下一行是可选的。没有它,上游提交得到
#压扁;与它一起,它们将被包含在当地的历史中。
$ git merge -s我们的--no-commit gitgit / master
$ git读取树--prefix = third_party / git-completion / -u gitgit / master:contrib / completion
$ git commit

#未来,您可以*用最新更改覆盖*,如下所示:
#如上所示,下一行是可选的(影响压扁)。
$ git merge -s our -no-commit gitgit / master
$ git rm -rf third_party / git-completion
$ git read-tree --prefix = third_party / git-completion / -u gitgit / master:contrib / completion
$ git commit

声称能够成功的博客文章:https://blisqu.wordpress.com/2012/09/08/merge-subdirectory-from-another-git-repository/rel =noreferrer>博客文章使用类似的技术合并(不覆盖),但是当我尝试它时,它不起作用。



3。 git subtree



我真的找到了一个使用 git subtree 的解决方案,感谢 http://jrsmith3.github.io/merging-a-subdirectory- from-another-repo-via-git-subtree.html ,但速度非常慢(每个 git subtree split 命令需要9分钟才能完成28 MB在双Xeon X5675上进行39000次提交的回购,而我发现的其他解决方案只需不到一秒)。



如果您能忍受缓慢,应该可行:

#第一次执行此操作:
$ git remote add -f - t master --no-tags gitgit https://github.com/git/git.git
$ git checkout gitgit / master
$ git subtree split -P contrib / completion -b temporary-split-分支
$ git checkout master
$ git subtree add --squash -P third_party / git-completion temporary-split-branch
$ git branch -D临时拆分分支

#今后,您可以合并其他更改,如下所示:
$ git checkout gitgit / master
$ git subtree split -P contrib / completion -b temporary-split-branch
$ git checkout master
$ git subtree merge --squash -P third_party / git-completion temporary-split-branch
#现在修复任何冲突,如果你修改third_party / git完成。
$ git branch -D temporary-split-branch

请注意,我传入 - squash 以避免污染本地存储库中的大量提交,但如果您愿意,可以删除 - squash 以保留提交历史记录。



使用 - 重新加入可能使后续拆分更快(请参阅< a href =https://stackoverflow.com/a/16139361/691281> https://stackoverflow.com/a/16139361/691281 ) - 我没有测试过。



4。整个repo git子树



OP清楚地声明他们想要将上游资源库的子目录合并到本地资源库的子目录中。但是,如果您希望将整个上游存储库合并到本地存储库的子目录中,则可以使用一种更简单,更干净且支持更好的备选方案:

 #第一次执行此操作:
$ git subtree add --squash --prefix = third_party / git https://github.com/git/git.git master

#今后,您可以合并其他更改,如下所示:
$ git subtree pull --squash --prefix = third_party / git https://github.com/git/或者,如果您不想重复存储库URL,那么您可以将其添加为一个远程:

#第一次执行此操作:
$ git remote add - f -t master --no-tags gitgit https://github.com/git/git.git
$ git subtree add --squash --prefix = third_party / git gitgit / master

#今后,您可以按照如下方式合并其他更改:
$ git subtree pull --sq uash --prefix = third_party / git gitgit / master

#你可以按如下方式将修改推回上游:
$ git subtree push --prefix = third_party / git gitgit / master
#或可能(不知道有什么区别):
$ git subtree push --squash --prefix = third_party / git gitgit / master

另见:



5。整个回购git子模块



相关技术是,但它们带有令人讨厌的注意事项(例如,克隆存储库的人不会克隆子模块,除非他们调用 git clone --recursive ),所以我没有调查他们是否可以支持子路径。


Is there a way to add a subdirectory of a remote repository into a subdirectory of my repository with git-subtree?

Suppose I have this main repository:

/
    dir1
    dir2

And this library repository:

/
    libdir
        some-file
    some-file-to-be-ignored

I want to import library/libdir into main/dir1 so that it looks like this:

/
    dir1
        some-file
    dir2

Using git-subtree, I can specify to import into dir1 with the --prefix argument, but can I also specify to only take the contents of a specific directory in the subtree?

The reason for using git-subtree is that I can later synchronize the two repositories.

解决方案

I've been experimenting with this, and found some partial solutions, though none are quite perfect.

For these examples, I'll consider merging the four files from contrib/completion/ of https://github.com/git/git.git into third_party/git_completion/ of the local repository.

1. git diff | git apply

This is probably the best way I've found. I only tested one-way merging; I haven't tried sending changes back to the upstream repository.

# Do this the first time:
$ git remote add -f -t master --no-tags gitgit https://github.com/git/git.git
# The next line is optional. Without it, the upstream commits get
# squashed; with it they will be included in your local history.
$ git merge -s ours --no-commit gitgit/master
# The trailing slash is important here!
$ git read-tree --prefix=third_party/git-completion/ -u gitgit/master:contrib/completion
$ git commit

# In future, you can merge in additional changes as follows:
# The next line is optional. Without it, the upstream commits get
# squashed; with it they will be included in your local history.
$ git merge -s ours --no-commit gitgit/master
# Replace the SHA1 below with the commit hash that you most recently
# merged in using this technique (i.e. the most recent commit on
# gitgit/master at the time).
$ git diff --color=never 53e53c7c81ce2c7c4cd45f95bc095b274cb28b76:contrib/completion gitgit/master:contrib/completion | git apply -3 --directory=third_party/git-completion
# Now fix any conflicts if you'd modified third_party/git-completion.
$ git commit

Since it's awkward having to remember the most recent commit SHA1 that you merged from the upstream repository, I've written this Bash function which does all the hard work for you (grabbing it from git log):

git-merge-subpath() {
    local SQUASH
    if [[ $1 == "--squash" ]]; then
        SQUASH=1
        shift
    fi
    if (( $# != 3 )); then
        local PARAMS="[--squash] SOURCE_COMMIT SOURCE_PREFIX DEST_PREFIX"
        echo "USAGE: ${FUNCNAME[0]} $PARAMS"
        return 1
    fi

    # Friendly parameter names; strip any trailing slashes from prefixes.
    local SOURCE_COMMIT="$1" SOURCE_PREFIX="${2%/}" DEST_PREFIX="${3%/}"

    local SOURCE_SHA1
    SOURCE_SHA1=$(git rev-parse --verify "$SOURCE_COMMIT^{commit}") || return 1

    local OLD_SHA1
    local GIT_ROOT=$(git rev-parse --show-toplevel)
    if [[ -n "$(ls -A "$GIT_ROOT/$DEST_PREFIX" 2> /dev/null)" ]]; then
        # OLD_SHA1 will remain empty if there is no match.
        local RE="^${FUNCNAME[0]}: [0-9a-f]{40} $SOURCE_PREFIX $DEST_PREFIX\$"
        OLD_SHA1=$(git log -1 --format=%b -E --grep="$RE" \
                   | grep --color=never -E "$RE" | tail -1 | awk '{print $2}')
    fi

    local OLD_TREEISH
    if [[ -n $OLD_SHA1 ]]; then
        OLD_TREEISH="$OLD_SHA1:$SOURCE_PREFIX"
    else
        # This is the first time git-merge-subpath is run, so diff against the
        # empty commit instead of the last commit created by git-merge-subpath.
        OLD_TREEISH=$(git hash-object -t tree /dev/null)
    fi &&

    if [[ -z $SQUASH ]]; then
        git merge -s ours --no-commit "$SOURCE_COMMIT"
    fi &&

    git diff --color=never "$OLD_TREEISH" "$SOURCE_COMMIT:$SOURCE_PREFIX" \
        | git apply -3 --directory="$DEST_PREFIX" || git mergetool

    if (( $? == 1 )); then
        echo "Uh-oh! Try cleaning up with |git reset --merge|."
    else
        git commit -em "Merge $SOURCE_COMMIT:$SOURCE_PREFIX/ to $DEST_PREFIX/

# Feel free to edit the title and body above, but make sure to keep the
# ${FUNCNAME[0]}: line below intact, so ${FUNCNAME[0]} can find it
# again when grepping git log.
${FUNCNAME[0]}: $SOURCE_SHA1 $SOURCE_PREFIX $DEST_PREFIX"
    fi
}

Use it like this:

# Do this the first time:
$ git remote add -f -t master --no-tags gitgit https://github.com/git/git.git
$ git-merge-subpath gitgit/master contrib/completion third_party/git-completion

# In future, you can merge in additional changes as follows:
$ git fetch gitgit
$ git-merge-subpath gitgit/master contrib/completion third_party/git-completion
# Now fix any conflicts if you'd modified third_party/git-completion.

2. git read-tree

If you're never going to make local changes to the merged in files, i.e. you're happy to always overwrite the local subdirectory with the latest version from upstream, then a similar but simpler approach is to use git read-tree:

# Do this the first time:
$ git remote add -f -t master --no-tags gitgit https://github.com/git/git.git
# The next line is optional. Without it, the upstream commits get
# squashed; with it they will be included in your local history.
$ git merge -s ours --no-commit gitgit/master
$ git read-tree --prefix=third_party/git-completion/ -u gitgit/master:contrib/completion
$ git commit

# In future, you can *overwrite* with the latest changes as follows:
# As above, the next line is optional (affects squashing).
$ git merge -s ours --no-commit gitgit/master
$ git rm -rf third_party/git-completion
$ git read-tree --prefix=third_party/git-completion/ -u gitgit/master:contrib/completion
$ git commit

I found a blog post that claimed to be able to merge (without overwriting) using a similar technique, but it didn't work when I tried it.

3. git subtree

I did actually find a solution that uses git subtree, thanks to http://jrsmith3.github.io/merging-a-subdirectory-from-another-repo-via-git-subtree.html, but it's incredibly slow (each git subtree split command below takes me 9 minutes for a 28 MB repo with 39000 commits on a dual Xeon X5675, whereas the other solutions I found take less than a second).

If you can live with the slowness, it should be workable:

# Do this the first time:
$ git remote add -f -t master --no-tags gitgit https://github.com/git/git.git
$ git checkout gitgit/master
$ git subtree split -P contrib/completion -b temporary-split-branch
$ git checkout master
$ git subtree add --squash -P third_party/git-completion temporary-split-branch
$ git branch -D temporary-split-branch

# In future, you can merge in additional changes as follows:
$ git checkout gitgit/master
$ git subtree split -P contrib/completion -b temporary-split-branch
$ git checkout master
$ git subtree merge --squash -P third_party/git-completion temporary-split-branch
# Now fix any conflicts if you'd modified third_party/git-completion.
$ git branch -D temporary-split-branch

Note that I pass in --squash to avoid polluting the local repository with lots of commits, but you can remove --squash if you'd prefer to preserve the commit history.

It's possible that subsequent splits can be made faster using --rejoin (see https://stackoverflow.com/a/16139361/691281) - I didn't test that.

4. Whole repo git subtree

The OP clearly stated that they want to merge a subdirectory of an upstream repository into a subdirectory of the local repository. If however instead you want to merge an entire upstream repository into a subdirectory of your local repository, then there's a simpler, cleaner, and better supported alternative:

# Do this the first time:
$ git subtree add --squash --prefix=third_party/git https://github.com/git/git.git master

# In future, you can merge in additional changes as follows:
$ git subtree pull --squash --prefix=third_party/git https://github.com/git/git.git master

Or if you prefer to avoid repeating the repository URL, then you can add it as a remote:

# Do this the first time:
$ git remote add -f -t master --no-tags gitgit https://github.com/git/git.git
$ git subtree add --squash --prefix=third_party/git gitgit/master

# In future, you can merge in additional changes as follows:
$ git subtree pull --squash --prefix=third_party/git gitgit/master

# And you can push changes back upstream as follows:
$ git subtree push --prefix=third_party/git gitgit/master
# Or possibly (not sure what the difference is):
$ git subtree push --squash --prefix=third_party/git gitgit/master

See also:

5. Whole repo git submodule

A related technique is git submodules, but they come with annoying caveats (for example people who clone your repository won't clone the submodules unless they call git clone --recursive), so I didn't investigate whether they can support subpaths.

这篇关于使用git-subtree添加远程回购的子目录的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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