Ответ 1
Я экспериментировал с этим и нашел некоторые частичные решения, хотя ни один из них не является совершенным.
В этих примерах я рассмотрю возможность объединения четырех файлов из contrib/completion/
https://github.com/git/git.git в third_party/git_completion/
локального репозитория.
1. git diff | 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):
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 read-tree
Если вы никогда не будете делать локальные изменения в сложенных файлах, то есть вы всегда можете переписать локальный подкаталог с последней версией из восходящего потока, то аналогичный, но более простой подход - использовать 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
Я нашел сообщение в блоге, в котором утверждалось, что он может объединиться (без перезаписи) с использованием подобной методики, но это не сработало, когда Я попробовал.
3. git поддерево
Я действительно нашел решение, которое использует git subtree
, благодаря http://jrsmith3.github.io/merging-a-subdirectory-from-another-repo-via-git-subtree.html, но это невероятно медленно (каждая команда git subtree split
ниже занимает 9 минут для репо 28 МБ с 39000 коммитов на двойной Xeon X5675, тогда как другие найденные решения занимают меньше секунды).
Если вы можете жить с медлительностью, она должна быть работоспособной:
# 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
(см. fooobar.com/questions/210728/...). Я не тестировал это.
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
См. также:
- http://blogs.atlassian.com/2013/05/alternatives-to-git-submodule-git-subtree/
- https://hpc.uni.lu/blog/2014/understanding-git-subtree/
- http://getlevelten.com/blog/tom-mccracken/smarter-drupal-projects-projects-management-git-subtree
5. Целый репо git подмодуль
Связанный с этим метод git подмодули, но они содержат раздражающие оговорки (например, люди, которые клонируют ваш репозиторий, не будут клонировать подмодулей, если они не назовут git clone --recursive
), поэтому я не исследовал, могут ли они поддерживать подпапки.