我一直在尝试这个,并找到了一些部分解决方案,尽管没有一个是非常完美的。
对于这些示例,我将考虑将 contrib/completion/ 的 https://github.com/git/git.git 的四个文件合并到本地存储库的 third_party/git_completion/ 中。
1。混帐差异 | git 申请
这可能是我找到的最好方法。我只测试了单向合并;我还没有尝试将更改发送回上游存储库。
# 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
由于必须记住您从上游存储库合并的最新提交 SHA1 很尴尬,所以我编写了这个 Bash 函数,它为您完成了所有艰苦的工作(从 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
}
像这样使用它:
# 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 读取树
如果您永远不会对合并的文件进行本地更改,即您乐于始终使用上游的最新版本覆盖本地子目录,那么类似但更简单的方法是使用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
我找到了一个blog post,它声称可以使用类似的技术进行合并(不覆盖),但我尝试时却没有成功。
3。 git 子树
我确实找到了一个使用git subtree 的解决方案,感谢http://jrsmith3.github.io/merging-a-subdirectory-from-another-repo-via-git-subtree.html,但它非常慢(下面的每个git subtree split 命令需要我9 分钟才能在双Xeon X5675 上完成39000 次提交的28 MB 存储库,而我发现的其他解决方案不到一秒钟)。
如果你能忍受缓慢,它应该是可行的:
# 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
请注意,我传入--squash 是为了避免大量提交污染本地存储库,但如果您希望保留提交历史记录,可以删除--squash。
使用--rejoin(请参阅https://stackoverflow.com/a/16139361/691281)可能会更快地进行后续拆分 - 我没有对此进行测试。
4。整个仓库 git 子树
OP 明确表示他们希望将上游存储库的子目录合并到本地存储库的子目录中。但是,如果您想将整个上游存储库合并到本地存储库的子目录中,那么有一个更简单、更简洁且受支持更好的替代方案:
# 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
或者,如果您希望避免重复存储库 URL,则可以将其添加为远程:
# 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
另见:
5。整个 repo git 子模块
一个相关的技术是git submodules,但它们带有烦人的警告(例如,克隆您的存储库的人不会克隆子模块,除非他们调用git clone --recursive),所以我没有调查他们是否可以支持子路径.
编辑:git-subtrac(来自早期 git-subtree 的作者)似乎解决了 git 子模块的一些问题。因此,这可能是将整个上游存储库合并到子目录中的好选择,但它似乎仍然不支持仅包含上游存储库的子目录。