git merge: menerapkan perubahan pada kode yang dipindahkan ke file yang berbeda

132

Saya sedang mencoba manuver git git cukup gemuk sekarang. Satu masalah yang saya temui adalah saya membuat beberapa perubahan pada beberapa kode di cabang saya, tetapi rekan saya memindahkan kode itu ke file baru di cabangnya. Jadi ketika saya melakukannya git merge my_branch his_branch, git tidak memperhatikan bahwa kode pada file baru sama dengan yang lama, jadi tidak ada perubahan saya di sana.

Apa cara termudah untuk menerapkan perubahan saya lagi ke kode di file baru. Saya tidak akan memiliki terlalu banyak masalah mencari tahu komit yang perlu diterapkan kembali (saya hanya dapat menggunakan git log --stat). Tapi sejauh yang saya tahu, tidak ada cara untuk mendapatkan git untuk mengajukan kembali perubahan ke file baru. Hal termudah yang saya lihat saat ini adalah secara manual mengajukan kembali perubahan, yang sepertinya bukan ide yang bagus.

Saya tahu bahwa git mengenali gumpalan, bukan file, jadi tentunya harus ada cara untuk mengatakannya, "terapkan perubahan kode persis ini dari komit ini, kecuali bukan di mana dulu tapi di mana sekarang berada di file baru ini".

penilai
sumber
2
Tidak persis sama, tetapi di sini ada pertanyaan serupa dengan jawaban yang baik yang mungkin berlaku: stackoverflow.com/questions/2701790/…
Mariano Desanze
4
Tidak ada jawaban yang menjelaskan mengapa git tidak dapat melakukan penggabungan seperti ini secara otomatis. Saya pikir seharusnya cukup pintar untuk mendeteksi nama baru dan melakukan penggabungan yang sesuai secara otomatis?
John
Mungkin ini tidak benar ketika pertanyaan diajukan, tetapi versi modern git (Saya menggunakan 1.9.5) dapat menggabungkan perubahan di seluruh file yang diubah namanya dan dipindahkan. Bahkan ada --rename-thresholdopsi untuk mengubah jumlah kesamaan yang diperlukan.
Todd Owen
1
@ToddOwen yang akan berfungsi jika Anda melakukan vanilla rebase dari cabang hulu, tetapi Anda masih akan mengalami masalah jika Anda memilih atau mem-porting kembali sejumlah perubahan yang mungkin tidak termasuk komit yang mengubah nama file. .
GuyPaddock
Apakah ini masalah sama sekali jika Anda melakukan rebase dulu?
hbogert

Jawaban:

132

Saya memiliki masalah serupa, dan saya mengatasinya dengan menghentikan pekerjaan saya agar sesuai dengan organisasi file target.

Katakanlah Anda memodifikasi original.txtpada cabang Anda ( localcabang), tetapi pada cabang master, original.txttelah disalin ke yang lain, katakanlah copy.txt. Salinan ini telah dilakukan dalam komit yang kami beri nama komit CP.

Anda ingin menerapkan semua perubahan lokal Anda, melakukan Adan di Bbawah, yang dilakukan pada original.txt, ke file baru copy.txt.

 ---- X -----CP------ (master)
       \ 
        \--A---B--- (local)

Buat cabang sekali pakai movepada titik awal perubahan Anda dengan git branch move X. Artinya, letakkan movecabang di komit X, yang di depan komit yang ingin Anda gabungkan; kemungkinan besar, ini adalah komitmen tempat Anda bercabang untuk mengimplementasikan perubahan Anda. Seperti yang dilakukan oleh pengguna @digory doo di bawah ini, Anda dapat melakukannya git merge-base master localuntuk menemukannya X.

 ---- X (move)-----CP----- (master)
       \ 
        \--A---B--- (local)

Di cabang ini, buat perintah penggantian nama berikut:

git mv original.txt copy.txt

Ini mengganti nama file. Perhatikan bahwa copy.txtbelum ada di pohon Anda pada saat ini.
Komit perubahan Anda (kami beri nama komit ini MV).

        /--MV (move)
       /
 ---- X -----CP----- (master)
       \ 
        \--A---B--- (local)

Anda sekarang dapat menyusun ulang pekerjaan Anda di atas move:

git rebase move local

Ini seharusnya bekerja tanpa masalah, dan perubahan Anda diterapkan copy.txtdi cabang lokal Anda.

        /--MV (move)---A'---B'--- (local)
       /
 ---- X -----CP----- (master)

Sekarang, Anda tidak perlu ingin atau perlu memiliki komit MVdalam sejarah cabang utama Anda, karena operasi pemindahan dapat menyebabkan konflik dengan operasi penyalinan saat komit CPdi cabang utama.

Anda hanya perlu mengubah kembali pekerjaan Anda, membuang operasi pemindahan, sebagai berikut:

git rebase move local --onto CP

... di mana CPkomit di mana copy.txtdiperkenalkan di cabang lain. Ini mengubah semua perubahan copy.txtdi atas CPkomit. Sekarang, localcabang Anda persis seperti Anda selalu dimodifikasi copy.txtdan tidak original.txt, dan Anda dapat terus bergabung dengan yang lain.

                /--A''---B''-- (local)
               /
 -----X-------CP----- (master)

Penting bahwa perubahan diterapkan CPatau tidak copy.txtakan ada dan perubahan akan diterapkan kembali original.txt.

Semoga ini jelas. Jawaban ini datang terlambat, tetapi ini mungkin berguna bagi orang lain.

coredump
sumber
2
Itu banyak pekerjaan, tetapi saya pikir itu harus bekerja pada prinsipnya. Saya pikir Anda akan lebih beruntung dengan merger daripada rebase.
penanggung jawab
7
Solusi ini hanya melibatkan perintah git dasar, tidak seperti mengedit tambalan atau menggunakan patch(yang melibatkan juga banyak pekerjaan), dan itulah mengapa saya pikir mungkin menarik untuk menunjukkannya. Juga, perhatikan bahwa saya mengambil lebih banyak waktu untuk menulis jawaban daripada benar-benar menerapkan perubahan, dalam kasus saya. Pada langkah mana Anda akan merekomendasikan menggunakan penggabungan? dan mengapa? Satu-satunya perbedaan yang saya lihat adalah dengan rebasing, saya membuat komit sementara yang kemudian dibuang (komit MV), yang tidak mungkin dengan penggabungan saja.
coredump
1
Dengan rebase, Anda memiliki peluang lebih tinggi untuk berurusan dengan konflik gabungan, karena Anda berurusan dengan setiap komit, sedangkan dengan gabungan Anda menangani semuanya sekaligus, yang berarti beberapa perubahan yang mungkin terjadi dengan rebase tidak akan ada dengan merger. Apa yang akan saya lakukan adalah menggabungkan, lalu memindahkan file yang digabungkan secara manual. Mungkin saya salah mengerti ide jawaban Anda.
penanggung jawab
4
Saya menambahkan beberapa pohon ASCII untuk memperjelas pendekatan. Mengatur penggabungan vs rebase dalam kasus itu: yang ingin saya lakukan adalah mengambil semua perubahan pada 'original.txt' di cabang saya dan menerapkannya ke 'copy.txt' di cabang master, karena karena alasan tertentu, 'asli. txt 'disalin (dan tidak dipindahkan) ke' copy.txt 'di beberapa titik. Setelah salinan itu, 'original.txt' mungkin juga telah berevolusi di cabang master. Jika saya bergabung langsung, perubahan lokal saya pada original.txt akan diterapkan ke original.txt yang dimodifikasi di cabang master, yang akan sulit untuk digabungkan. Salam.
coredump
1
Bagaimanapun, saya percaya solusi ini akan berhasil (walaupun saya untungnya tidak memiliki situasi untuk mencobanya sekarang), jadi untuk saat ini saya akan menandainya sebagai jawabannya.
penanggung jawab
31

Anda selalu dapat menggunakan git diff(atau git format-patch) untuk membuat tambalan, lalu pergi secara manual mengedit nama file di tambalan, dan menerapkannya dengan git apply(atau git am).

Singkatnya, satu-satunya cara itu akan bekerja secara otomatis adalah jika deteksi ganti nama git dapat mengetahui bahwa file lama dan baru adalah hal yang sama - yang sepertinya tidak benar-benar ada dalam kasus Anda, hanya sebagian saja. Memang benar bahwa git menggunakan gumpalan, bukan file, tetapi gumpalan hanyalah isi dari seluruh file, tanpa nama file dan metadata terlampir. Jadi, jika Anda memiliki sepotong kode yang dipindahkan di antara dua file, mereka tidak benar-benar gumpalan yang sama - sisa konten gumpalan itu berbeda, hanya potongan yang sama.

Cascabel
sumber
1
Yah, itu jawaban terbaik sejauh ini. Saya tidak tahu bagaimana cara membuat git format-patchkomit. Jika saya lakukan git format-patch SHA1, itu menghasilkan sejumlah besar file tambalan untuk seluruh sejarah. Tapi saya kira git show SHA1 > diff.patchakan berhasil juga.
penanggung jawab
1
@asmeurer: gunakan -1opsi. Mode operasi normal untuk format-patch adalah rentang revisi, seperti origin/master..master, jadi Anda dapat dengan mudah menyiapkan seri tambalan.
Cascabel
1
Sebenarnya, catatan lain. git applydan git amterlalu pemilih, karena mereka ingin nomor baris yang sama. Tetapi saya saat ini sedang sukses dengan patchperintah UNIX .
penanggung jawab
24

Berikut adalah solusi penggabungan menghadapi konflik gabungan dengan penggantian nama dan edit serta menyelesaikannya dengan mergetool mengenali 3 file sumber gabungan yang benar.

  • Setelah penggabungan gagal karena 'file terhapus' yang Anda sadari telah diubah namanya dan diedit:

    1. Anda batalkan penggabungan.
    2. Komit file yang diubah namanya di cabang Anda.
    3. Dan bergabung lagi.

Walk-through:

Buat file.txt:

$ git init
Initialized empty Git repository in /tmp/git-rename-and-modify-test/.git/

$ echo "A file." > file.txt
$ git add file.txt
$ git commit -am "file.txt added."
[master (root-commit) 401b10d] file.txt added.
 1 file changed, 1 insertion(+)
 create mode 100644 file.txt

Buat cabang tempat Anda akan mengedit nanti:

$ git branch branch-with-edits
Branch branch-with-edits set up to track local branch master.

Buat ganti nama dan edit pada master:

$ git mv file.txt renamed-and-edited.txt
$ echo "edits on master" >> renamed-and-edited.txt 
$ git commit -am "file.txt + edits -> renamed-and-edited.txt."
[master def790f] file.txt + edits -> renamed-and-edited.txt.
 2 files changed, 2 insertions(+), 1 deletion(-)
 delete mode 100644 file.txt
 create mode 100644 renamed-and-edited.txt

Tukar ke cabang, dan edit di sana juga:

$ git checkout branch-with-edits 
Switched to branch 'branch-with-edits'
Your branch is behind 'master' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)
$ 
$ echo "edits on branch" >> file.txt 
$ git commit -am "file.txt edited on branch."
[branch-with-edits 2c4760e] file.txt edited on branch.
 1 file changed, 1 insertion(+)

Coba gabungkan master:

$ git merge master
CONFLICT (modify/delete): file.txt deleted in master and modified in HEAD. Version HEAD of file.txt left in tree.
Automatic merge failed; fix conflicts and then commit the result.

Perhatikan bahwa konflik tersebut sulit diselesaikan - dan file-file diubah namanya. Batalkan, meniru nama:

$ git merge --abort
$ git mv file.txt renamed-and-edited.txt
$ git commit -am "Preparing for merge; Human noticed renames files were edited."
[branch-with-edits ca506da] Preparing for merge; Human noticed renames files were edited.
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename file.txt => renamed-and-edited.txt (100%)

Coba gabungkan lagi:

$ git merge master
Auto-merging renamed-and-edited.txt
CONFLICT (add/add): Merge conflict in renamed-and-edited.txt
Recorded preimage for 'renamed-and-edited.txt'
Automatic merge failed; fix conflicts and then commit the result.

Bagus! Menggabungkan menghasilkan konflik 'normal' yang dapat diselesaikan dengan mergetool:

$ git mergetool
Merging:
renamed-and-edited.txt

Normal merge conflict for 'renamed-and-edited.txt':
  {local}: created file
  {remote}: created file
$ git commit 
Recorded resolution for 'renamed-and-edited.txt'.
[branch-with-edits 2264483] Merge branch 'master' into branch-with-edits
Vincent Scheib
sumber
Menarik. Saya harus mencoba ini saat berikutnya saya menemukan masalah ini.
asmeurer
1
Dalam solusi itu, bagi saya tampaknya langkah pertama, penggabungan yang dibatalkan, hanya berguna untuk mencari tahu file mana yang diubah namanya dan diedit dari jarak jauh. Jika Anda tahu mereka sebelumnya. Anda dapat melewati langkah itu dan, pada dasarnya, solusinya adalah dengan mengubah nama file secara manual dan kemudian menggabungkan dan menyelesaikan konflik seperti biasa.
1
Terima kasih. Situasi saya adalah saya bergerak B.txt -> C.txtdan A.txt -> B.txtdengan git mv, dan git tidak dapat secara otomatis mencocokkan konflik gabungan dengan benar (mendapatkan konflik gabungan antara yang lama B.txtdan yang baru B.txt). Dengan menggunakan metode ini, konflik gabungan sekarang berada di antara file yang benar.
cib
Ini hanya berfungsi ketika seluruh file dipindahkan, tetapi git umumnya harus mendeteksi situasi itu secara otomatis. Situasi rumit adalah ketika hanya sebagian file dipindahkan.
Robin Green