Gabungkan dua repositori Git tanpa melanggar riwayat file

226

Saya perlu menggabungkan dua repositori Git ke dalam repositori ketiga yang baru. Saya telah menemukan banyak deskripsi tentang bagaimana melakukan ini menggunakan penggabungan subtree (misalnya jawaban Jakub Narębski pada Bagaimana Anda menggabungkan dua repositori Git? ) Dan mengikuti instruksi tersebut sebagian besar berfungsi, kecuali ketika saya melakukan subtree menggabungkan semua file dari repositori lama dicatat sebagai file baru yang ditambahkan. Saya bisa melihat komit sejarah dari repositori lama ketika saya melakukannya git log, tetapi jika saya melakukannya git log <file>hanya menunjukkan satu komit untuk file itu - penggabungan subtree. Dilihat dari komentar pada jawaban di atas, saya tidak sendirian dalam melihat masalah ini tetapi saya tidak menemukan solusi yang dipublikasikan untuk itu.

Apakah ada cara menggabungkan repositori dan membiarkan riwayat file individu utuh?

Eric Lee
sumber
Saya tidak menggunakan Git, tetapi di Mercurial saya pertama kali akan melakukan konversi jika perlu untuk memperbaiki jalur file dari repo yang akan digabung, dan kemudian memaksa-tarik satu repo ke target untuk mendapatkan perubahan, dan kemudian melakukan menggabungkan cabang-cabang yang berbeda. Ini diuji dan berfungsi;) Mungkin ini membantu untuk menemukan solusi untuk Git juga ... dibandingkan dengan pendekatan subtree-merge. Saya kira langkah konversi berbeda di mana sejarah ditulis ulang daripada hanya memetakan jalan (jika saya mengerti benar). Ini kemudian memastikan penggabungan yang mulus tanpa penanganan path file yang khusus.
Lucero
Saya juga menemukan pertanyaan ini bermanfaat stackoverflow.com/questions/1683531/…
nacross
Saya membuat pertanyaan tindak lanjut. Mungkin menarik: Gabungkan dua repositori Git dan pertahankan riwayat master: stackoverflow.com/questions/42161910/…
Dimitri Dewaele
Solusi otomatis yang bekerja untuk saya adalah stackoverflow.com/a/30781527/239408
xverges

Jawaban:

269

Ternyata jawabannya jauh lebih sederhana jika Anda hanya mencoba merekatkan dua repositori bersama dan membuatnya tampak seperti itu selama ini daripada mengelola ketergantungan eksternal. Anda hanya perlu menambahkan remote ke repo lama Anda, menggabungkannya ke master baru Anda, memindahkan file dan folder ke subdirektori, melakukan perpindahan, dan ulangi untuk semua repo tambahan. Submodules, subtree merge, dan rebase mewah dimaksudkan untuk menyelesaikan masalah yang sedikit berbeda dan tidak cocok untuk apa yang saya coba lakukan.

Berikut ini contoh skrip Powershell untuk merekatkan dua repositori bersama:

# Assume the current directory is where we want the new repository to be created
# Create the new repository
git init

# Before we do a merge, we have to have an initial commit, so we'll make a dummy commit
git commit --allow-empty -m "Initial dummy commit"

# Add a remote for and fetch the old repo
git remote add -f old_a <OldA repo URL>

# Merge the files from old_a/master into new/master
git merge old_a/master --allow-unrelated-histories

# Move the old_a repo files and folders into a subdirectory so they don't collide with the other repo coming later
mkdir old_a
dir -exclude old_a | %{git mv $_.Name old_a}

# Commit the move
git commit -m "Move old_a files into subdir"

# Do the same thing for old_b
git remote add -f old_b <OldB repo URL>
git merge old_b/master --allow-unrelated-histories
mkdir old_b
dir exclude old_a,old_b | %{git mv $_.Name old_b}
git commit -m "Move old_b files into subdir"

Jelas Anda malah bisa menggabungkan old_b ke old_a (yang menjadi repo gabungan baru) jika Anda lebih suka melakukannya - modifikasi skrip yang sesuai.

Jika Anda juga ingin membawa cabang fitur yang sedang dalam proses, gunakan ini:

# Bring over a feature branch from one of the old repos
git checkout -b feature-in-progress
git merge -s recursive -Xsubtree=old_a old_a/feature-in-progress

Itu adalah satu-satunya bagian yang tidak jelas dari proses - itu bukan penggabungan subtree, melainkan argumen untuk penggabungan rekursif normal yang memberi tahu Git bahwa kami mengganti nama target dan yang membantu Git mengatur semuanya dengan benar.

Saya menulis penjelasan yang sedikit lebih rinci di sini .

Eric Lee
sumber
16
menggunakan solusi git mvini tidak bekerja dengan baik. ketika nanti Anda menggunakan git logpada salah satu file yang dipindahkan, Anda hanya mendapatkan komit dari langkah tersebut. semua riwayat sebelumnya hilang. ini karena git mvbenar-benar git rm; git addtetapi dalam satu langkah .
mholm815
15
Ini sama dengan operasi pemindahan / penggantian nama lain di Git: dari baris perintah Anda bisa mendapatkan semua riwayat dengan melakukan git log --follow, atau semua alat GUI melakukannya untuk Anda secara otomatis. Dengan penggabungan subtree, Anda tidak bisa mendapatkan riwayat untuk file individual, sejauh yang saya tahu, jadi metode ini lebih baik.
Eric Lee
3
@EricLee Ketika repo old_b digabung, saya mendapatkan banyak konflik penggabungan. Apakah itu diharapkan? Saya mendapatkan CONFLICT (mengganti nama / menghapus)
Jon
9
Ketika saya mencoba "dir -exclude old_a |% {git mv $ _. Nama old_a}", saya mendapatkan sh.exe ": dir: perintah tidak ditemukan dan sh.exe": git: perintah tidak ditemukan. Menggunakan ini berfungsi: ls -I old_a | xargs -I '{}' git mv '{}' old_a /
George
5
Ini adalah 1(nomor satu) untuk lsdan modal 'mata' untuk xargs. Terima kasih atas tip ini!
Dominique Vial
149

Inilah cara yang tidak menulis ulang riwayat apa pun, sehingga semua ID komit akan tetap valid. Hasil akhirnya adalah bahwa file repo kedua akan berakhir di subdirektori.

  1. Tambahkan repo kedua sebagai remote:

    cd firstgitrepo/
    git remote add secondrepo username@servername:andsoon
    
  2. Pastikan Anda telah mengunduh semua komitmen dari secondrepo:

    git fetch secondrepo
    
  3. Buat cabang lokal dari cabang repo kedua:

    git branch branchfromsecondrepo secondrepo/master
    
  4. Pindahkan semua file-nya ke subdirektori:

    git checkout branchfromsecondrepo
    mkdir subdir/
    git ls-tree -z --name-only HEAD | xargs -0 -I {} git mv {} subdir/
    git commit -m "Moved files to subdir/"
    
  5. Gabungkan cabang kedua ke cabang utama repo pertama:

    git checkout master
    git merge --allow-unrelated-histories branchfromsecondrepo
    

Repositori Anda akan memiliki lebih dari satu root commit, tetapi itu seharusnya tidak menimbulkan masalah.

Flimm
sumber
1
Langkah 2 tidak berfungsi untuk saya: fatal: Bukan nama objek yang valid: 'secondrepo / master'.
Keith
@Keith: pastikan Anda telah menambahkan repo kedua sebagai remote bernama "secondrepo", dan repo itu memiliki cabang bernama "master" (Anda dapat melihat cabang pada repo jarak jauh dengan perintah git remote show secondrepo)
Flimm
Saya harus melakukan pengambilan untuk menurunkannya juga. Di antara 1 dan 2 saya melakukan git mengambil secondrepo
sksamuel
@monkjack: Saya telah mengedit jawaban saya untuk memasukkan langkah git fetch. Jangan ragu untuk mengedit jawaban sendiri di masa mendatang.
Flimm
4
@ MartijnHeemels Untuk versi Git yang lebih lama, cukup hapus --allow-unrelated-histories. Lihat riwayat posting jawaban ini.
Flimm
8

Beberapa tahun telah berlalu dan ada solusi up-vote berbasis baik tetapi saya ingin berbagi milik saya karena itu sedikit berbeda karena saya ingin menggabungkan 2 repositori jarak jauh menjadi yang baru tanpa menghapus sejarah dari repositori sebelumnya.

  1. Buat repositori baru di Github.

    masukkan deskripsi gambar di sini

  2. Unduh repo yang baru dibuat dan tambahkan repositori jarak jauh yang lama.

    git clone https://github.com/alexbr9007/Test.git
    cd Test
    git remote add OldRepo https://github.com/alexbr9007/Django-React.git
    git remote -v
    
  3. Ambil semua file dari repo lama sehingga cabang baru dibuat.

    git fetch OldRepo
    git branch -a
    

    masukkan deskripsi gambar di sini

  4. Di cabang master, lakukan penggabungan untuk menggabungkan repo lama dengan yang baru dibuat.

    git merge remotes/OldRepo/master --allow-unrelated-histories
    

    masukkan deskripsi gambar di sini

  5. Buat folder baru untuk menyimpan semua konten baru yang dibuat yang ditambahkan dari OldRepo dan pindahkan file-nya ke folder baru ini.

  6. Terakhir, Anda dapat mengunggah file dari repo gabungan dan menghapus OldRepo dengan aman dari GitHub.

Semoga ini bisa bermanfaat bagi siapa pun yang berurusan dengan penggabungan repositori jarak jauh.

abautista
sumber
1
Ini adalah satu-satunya solusi yang berhasil bagi saya untuk melestarikan sejarah git. Jangan lupa untuk menghapus tautan jarak jauh ke repo lama git remote rm OldRepo.
Harubiyori
7

silakan lihat menggunakan

git rebase --root --preserve-merges --onto

untuk menghubungkan dua sejarah sejak dini dalam kehidupan mereka.

Jika Anda memiliki jalur yang tumpang tindih, perbaiki dengan

git filter-branch --index-filter

ketika Anda menggunakan log, pastikan Anda "menemukan salinan lebih sulit" dengan

git log -CC

dengan cara itu Anda akan menemukan pergerakan file di jalur.

Adam Dymitruk
sumber
Dokumentasi Git merekomendasikan untuk tidak rebasing ... git-scm.com/book/en/v2/Git-Branching-Rebasing#_rebase_peril
Stephen Turner
7

Saya mengubah solusi dari @Flimm ini menjadi git aliasseperti ini (ditambahkan ke saya ~/.gitconfig):

[alias]
 mergeRepo = "!mergeRepo() { \
  [ $# -ne 3 ] && echo \"Three parameters required, <remote URI> <new branch> <new dir>\" && exit 1; \
  git remote add newRepo $1; \
  git fetch newRepo; \
  git branch \"$2\" newRepo/master; \
  git checkout \"$2\"; \
  mkdir -vp \"${GIT_PREFIX}$3\"; \
  git ls-tree -z --name-only HEAD | xargs -0 -I {} git mv {} \"${GIT_PREFIX}$3\"/; \
  git commit -m \"Moved files to '${GIT_PREFIX}$3'\"; \
  git checkout master; git merge --allow-unrelated-histories --no-edit -s recursive -X no-renames \"$2\"; \
  git branch -D \"$2\"; git remote remove newRepo; \
}; \
mergeRepo"
Fredrik Erlandsson
sumber
12
Hanya ingin tahu: apakah Anda benar-benar cukup sering melakukan ini sehingga perlu alias?
Parker Coates
1
Tidak, saya tidak tetapi tidak pernah ingat bagaimana melakukannya sehingga alias hanya cara bagi saya untuk mengingatnya.
Fredrik Erlandsson
1
Ya .. tapi coba ganti komputer dan lupa untuk memindahkan alias Anda;)
quetzalcoatl
1
Apa nilainya $GIT_PREFIX?
neowulf33
github.com/git/git/blob/… 'GIT_PREFIX' diset sebagai dikembalikan dengan menjalankan 'git rev-parse --show-prefix' dari direktori asli saat ini. Lihat linkgit: git-rev-parse [1].
Fredrik Erlandsson
3

Fungsi ini akan mengkloning repo jarak jauh ke dir repo lokal:

function git-add-repo
{
    repo="$1"
    dir="$(echo "$2" | sed 's/\/$//')"
    path="$(pwd)"

    tmp="$(mktemp -d)"
    remote="$(echo "$tmp" | sed 's/\///g'| sed 's/\./_/g')"

    git clone "$repo" "$tmp"
    cd "$tmp"

    git filter-branch --index-filter '
        git ls-files -s |
        sed "s,\t,&'"$dir"'/," |
        GIT_INDEX_FILE="$GIT_INDEX_FILE.new" git update-index --index-info &&
        mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
    ' HEAD

    cd "$path"
    git remote add -f "$remote" "file://$tmp/.git"
    git pull "$remote/master"
    git merge --allow-unrelated-histories -m "Merge repo $repo into master" --edit "$remote/master"
    git remote remove "$remote"
    rm -rf "$tmp"
}

Cara Penggunaan:

cd current/package
git-add-repo https://github.com/example/example dir/to/save

Memperhatikan. Script ini dapat menulis ulang komit tetapi akan menyimpan semua penulis dan tanggal, itu berarti komit baru akan memiliki hash lain, dan jika Anda mencoba untuk mendorong perubahan ke server jarak jauh, ia dapat hanya dengan tombol paksa, juga akan menulis ulang komit di server. Jadi harap buat cadangan sebelum diluncurkan.

Keuntungan!

Andrey Izman
sumber
Saya menggunakan zsh daripada bash, dan v2.13.0 dari git. Tidak peduli apa yang saya coba, saya belum bisa mulai git filter-branch --index-filterbekerja. Biasanya saya mendapatkan pesan kesalahan bahwa file indeks baru tidak ada. Apakah itu berdering?
Patrick Beard
@ PatrickBeard Saya tidak tahu zsh, Anda dapat membuat file terpisah git-add-repo.shdengan fungsi di atas, pada akhir file tuliskan baris ini git-add-repo "$@". Setelah itu Anda dapat menggunakannya dari zsh like cd current/git/packagedanbash path/to/git-add-repo.sh https://github.com/example/example dir/to/save
Andrey Izman
Masalahnya telah dibahas di sini: stackoverflow.com/questions/7798142/… mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE" terkadang gagal, jadi Anda harus menambahkan if test.
Patrick Beard
1
Saya tidak akan menggunakan metode ini! Saya mencoba skrip, secara naif dan kata demi kata (saya hanya bisa menyalahkan diri saya sendiri untuk bagian itu), dan itu menghancurkan repositori git lokal saya. Sejarah sebagian besar kelihatannya benar, tetapi melakukan git push kembali ke Github menghasilkan "RPC gagal; ikal 55 SSL_write () mengembalikan SYSCALL, errno = 32" kesalahan. Saya mencoba memperbaikinya, tetapi rusak tidak dapat diperbaiki. Saya akhirnya harus merekonstruksi hal-hal dalam repo lokal baru.
Mason Freed
@MasonFreed skrip ini membuat sejarah git baru dengan campuran kedua repo, sehingga tidak dapat didorong ke repo lama, ia perlu membuat yang baru atau mendorong dengan tombol paksa, artinya menulis ulang repo Anda di server
Andrey Izman
2

Ikuti langkah-langkah untuk menanamkan satu repo ke repo lain, memiliki satu sejarah git tunggal dengan menggabungkan kedua sejarah git.

  1. Kloning kedua repo yang ingin Anda gabungkan.

git clone [email protected]: user / parent-repo.git

git clone [email protected]: user / child-repo.git

  1. Pergi ke repo anak

cd child-repo /

  1. jalankan perintah di bawah ini, ganti jalur my/new/subdir(3 kejadian) dengan struktur direktori tempat Anda ingin repo anak.

git filter-branch --prune-empty --tree-filter 'if [! -e my / new / subdir]; lalu mkdir -p my / new / subdir git ls-tree --name-only $ GIT_COMMIT | xargs -Saya file mv file fi saya / baru / subdir

  1. Buka repo induk

cd ../parent-repo/

  1. Tambahkan remote ke repo induk, arahkan jalur ke repo anak

git remote tambahkan child-remote ../child-repo/

  1. Ambil repo anak

git ambil remote-anak

  1. Gabungkan sejarah

git merge --allow-unrelated-histories child-remote / master

Jika Anda memeriksa git log di repo induk sekarang, seharusnya repo child digabung. Anda juga dapat melihat tanda yang menunjukkan dari sumber komit.

Artikel di bawah ini membantu saya dalam Menanamkan satu repo ke repo lain, memiliki satu sejarah git tunggal dengan menggabungkan kedua sejarah git.

http://ericlathrop.com/2014/01/combining-git-repositor//

Semoga ini membantu. Selamat Coding!

AnoopGoudar
sumber
Langkah 3 gagal bagi saya dengan kesalahan sintaksis. Semi-titik dua hilang. Perbaikigit filter-branch --prune-empty --tree-filter ' if [ ! -e my/new/subdir ]; then mkdir -p my/new/subdir; git ls-tree --name-only $GIT_COMMIT | xargs -I files mv files my/new/subdir; fi'
Yuri L
1

Katakanlah Anda ingin menggabungkan repositori ake b(Saya berasumsi mereka berada di satu sama lain):

cd b
git remote add a ../a
git fetch a
git merge --allow-unrelated-histories a/master
git remote remove a

Jika Anda ingin menempatkan asubdirektori lakukan hal berikut sebelum perintah di atas:

cd a
git filter-repo --to-subdirectory-filter a
cd ..

Untuk ini, Anda perlu git-filter-repomenginstal ( filter-branchtidak disarankan ).

Contoh menggabungkan 2 repositori besar, menempatkan salah satunya ke dalam subdirektori: https://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731

Lebih lanjut di sini .

x-yuri
sumber