Pertanyaan Tambahkan subdirektori repo jarak jauh dengan git-subtree


Apakah ada cara untuk menambahkan subdirektori dari repositori jauh ke dalam subdirektori repositori saya dengan git-subtree?

Misalkan saya punya ini utama gudang:

/
    dir1
    dir2

Dan ini Perpustakaan gudang:

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

Saya ingin mengimpor Perpustakaan/ libdir ke dalam utama/ dir1 sehingga terlihat seperti ini:

/
    dir1
        some-file
    dir2

Menggunakan git-subtree, saya dapat menentukan untuk diimpor ke dir1 dengan --prefix argumen, tetapi dapatkah saya juga menentukan untuk hanya mengambil isi dari direktori tertentu di subtree?

Alasan menggunakan git-subtree adalah bahwa saya nantinya dapat menyinkronkan dua repositori.


32
2018-05-29 15:49


asal


Jawaban:


Saya telah bereksperimen dengan ini, dan menemukan beberapa solusi parsial, meskipun tidak ada yang cukup sempurna.

Untuk contoh-contoh ini, saya akan mempertimbangkan menggabungkan empat file dari contrib/completion/ dari https://github.com/git/git.git ke third_party/git_completion/ dari repositori lokal.

1. git diff | git berlaku

Ini mungkin cara terbaik yang saya temukan. Saya hanya menguji penggabungan satu arah; Saya belum mencoba mengirim perubahan kembali ke repositori hulu.

# 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

Karena canggung harus mengingat SHA1 terbaru yang Anda gabungkan dari repositori hulu, saya telah menulis fungsi Bash ini yang melakukan semua kerja keras untuk Anda (mengambilnya dari 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
}

Gunakan seperti ini:

# 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 baca-pohon

Jika Anda tidak akan melakukan perubahan lokal pada file yang digabungkan, Anda senang untuk selalu menimpa subdirektori lokal dengan versi terbaru dari hulu, maka pendekatan yang serupa tetapi lebih sederhana adalah dengan menggunakan 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

saya menemukan sebuah posting blog yang diklaim mampu menggabungkan (tanpa timpa) menggunakan teknik yang serupa, tetapi tidak berhasil ketika saya mencobanya.

3. subtit git

Saya benar-benar menemukan solusi yang menggunakan git subtree, terimakasih untuk http://jrsmith3.github.io/merging-a-subdirectory-from-another-repo-via-git-subtree.html, tapi sangat lambat (masing-masing git subtree split perintah di bawah ini membutuhkan waktu 9 menit untuk repo 28 MB dengan 39000 commit pada Xeon X5675 ganda, sedangkan solusi lain yang saya temukan kurang dari satu detik).

Jika Anda dapat hidup dengan kelambatan, itu harus bisa diterapkan:

# 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

Perhatikan bahwa saya lulus --squash untuk menghindari polusi pada repositori lokal dengan banyak komit, tetapi Anda dapat menghapusnya --squash jika Anda lebih memilih mempertahankan sejarah komitmen.

Ada kemungkinan bahwa perpecahan berikutnya dapat dibuat lebih cepat menggunakan --rejoin (Lihat https://stackoverflow.com/a/16139361/691281) - Saya tidak menguji itu.

4. Seluruh repo git subtree

OP menyatakan dengan jelas bahwa mereka ingin menggabungkan subdirektori dari repositori hulu ke dalam subdirektori dari repositori lokal. Namun, jika sebaliknya Anda ingin menggabungkan seluruh hulu repositori menjadi subdirektori repositori lokal Anda, maka ada alternatif yang lebih sederhana, bersih, dan didukung lebih baik:

# 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

Atau jika Anda lebih memilih untuk tidak mengulang URL repositori, maka Anda dapat menambahkannya sebagai 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

Lihat juga:

5. Seluruh repo git submodule

Teknik terkait adalah submodul git, tetapi mereka datang dengan peringatan yang menjengkelkan (misalnya orang yang mengkloning repositori Anda tidak akan mengkloning submodules kecuali mereka memanggil git clone --recursive), jadi saya tidak menyelidiki apakah mereka dapat mendukung sub jalan.


36
2018-05-21 23:30



Saya bisa melakukan sesuatu seperti ini dengan menambahkan :dirname ke perintah baca-pohon. (perhatikan bahwa saya sebenarnya hanya mencoba untuk belajar git dan git-sub judul sendiri minggu ini, dan mencoba untuk mengatur lingkungan yang mirip dengan bagaimana saya memiliki proyek saya di subversi menggunakan svn: externals - poin saya adalah bahwa mungkin ada yang lebih baik atau cara yang lebih mudah daripada perintah yang saya tunjukkan di sini ...)

Jadi misalnya, menggunakan struktur contoh Anda di atas:

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

0
2018-06-04 18:54