Подтвердить что ты не робот

Добавить подкаталог удаленного репо с git -subtree

Есть ли способ добавить поддиректорию удаленного репозитория в подкаталог моего репозитория с помощью git -subtree?

Предположим, что у меня есть этот основной репозиторий:

/
    dir1
    dir2

И этот репозиторий библиотека:

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

Я хочу импортировать библиотеку/libdir в main/dir1, чтобы она выглядела следующим образом:

/
    dir1
        some-file
    dir2

Используя git -subtree, я могу указать для импорта в dir1 с аргументом --prefix, но могу ли я также указать только для содержимого содержимого определенного каталога в поддереве?

Причиной использования git -subtree является то, что я могу позже синхронизировать два репозитория.

4b9b3361

Ответ 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

См. также:

5. Целый репо git подмодуль

Связанный с этим метод git подмодули, но они содержат раздражающие оговорки (например, люди, которые клонируют ваш репозиторий, не будут клонировать подмодулей, если они не назовут git clone --recursive), поэтому я не исследовал, могут ли они поддерживать подпапки.

Ответ 2

Я смог сделать что-то вроде этого, добавив :dirname в команду read-tree. (обратите внимание, что я на самом деле просто пытаюсь изучить git и git -подписки на этой неделе и попытаться настроить среду, похожую на то, как у меня были мои проекты в подрывной деятельности, используя svn: externals - моя точка зрения заключается в том, что там может быть лучше или проще, чем команды, которые я показываю здесь...)

Итак, например, используя приведенную выше структуру примера:

git remote add library_remote _URL_TO_LIBRARY_REPO_
git fetch library_remote
git checkout -b library_branch library_remote/master
git checkout master
git read-tree --prefix=dir1 -u library_branch:libdir