web-dev-qa-db-ja.com

git-subtreeを使用してリモートリポジトリのサブディレクトリを追加する

Git-subtreeを使用して、リモートリポジトリのサブディレクトリを私のリポジトリのサブディレクトリに追加する方法はありますか?

私がこれを持っているとしましょうmainリポジトリ:

/
    dir1
    dir2

そしてこれライブラリリポジトリ:

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

library/libdirをmain/dir1にインポートして、次のようにします。

/
    dir1
        some-file
    dir2

Git-subtreeを使用すると、--prefix引数を使用してdir1にインポートするように指定できますが、内容のみを取得するように指定することもできますサブツリーの特定のディレクトリ?

Git-subtreeを使用する理由は、後で2つのリポジトリを同期できるためです。

38
Yogu

私はこれを実験して、いくつかの部分的な解決策を見つけましたが、完全ではありません。

これらの例では、- https://github.com/git/git.gitcontrib/completion/から4つのファイルをローカルリポジトリのthird_party/git_completion/にマージすることを検討します。

1. git diff | git apply

これはおそらく私が見つけた最良の方法です。私は一方向のマージのみをテストしました。変更を上流のリポジトリに送り返そうとはしていません。

# 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サブツリー

http://jrsmith3.github.io/merging-a-subdirectory-from-another-repo-via-git-subtree.html のおかげで、git subtreeを使用するソリューションを実際に見つけましたが、それは信じられないほど遅いです(以下の各git subtree splitコマンドは、デュアルXeon X5675での39000コミットで28 MBのリポジトリに対して9分かかりますが、他の解決策は1秒未満です)。

あなたが遅さで生きることができるなら、それは実行可能であるべきです:

# 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.リポジトリ全体のgitサブモジュール

関連する手法は git submodules ですが、それらには厄介な警告があります(たとえば、リポジトリを複製する人は、git clone --recursiveを呼び出さない限りサブモジュールを複製しません)。したがって、それらがサポートできるかどうかは調査しませんでしたサブパス。

編集: git-subtrac (以前のgit-subtreeの作者による)は、gitサブモジュールの問題のいくつかを解決するようです。したがって、これはアップストリームリポジトリ全体をサブディレクトリにマージするのに適したオプションですが、それでも、アップストリームリポジトリのサブディレクトリのみを含めることはサポートされていないようです。

48
John Mellor

Read-treeコマンドに:dirnameを追加することで、このようなことを行うことができました。 (私は実際には今週自分でgitとgit-subtreesを習得しようとしているだけで、svn:externalsを使用してSubversionでプロジェクトを作成したのと同じような環境をセットアップしようとしていることに注意してください-または私がここに示しているコマンドよりも簡単な方法...)

たとえば、上記の例の構造を使用して:

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
1
DotNetSparky