Mengapa git-rebase memberi saya menggabungkan konflik ketika semua yang saya lakukan adalah menekan komitmen?

146

Kami memiliki repositori Git dengan lebih dari 400 komit, beberapa lusin pertama adalah banyak trial-and-error. Kami ingin membersihkan komitmen ini dengan menekan banyak menjadi satu komit. Tentu saja, git-rebase sepertinya jalan yang harus ditempuh. Masalah saya adalah bahwa itu berakhir dengan menggabungkan konflik, dan konflik ini tidak mudah untuk diselesaikan. Saya tidak mengerti mengapa harus ada konflik sama sekali, karena saya hanya menekan komitmen (tidak menghapus atau mengatur ulang). Sangat mungkin, ini menunjukkan bahwa saya tidak sepenuhnya memahami bagaimana git-rebase melakukan squash-nya.

Ini versi modifikasi dari skrip yang saya gunakan:


repo_squash.sh (ini adalah skrip yang sebenarnya dijalankan):


rm -rf repo_squash
git clone repo repo_squash
cd repo_squash/
GIT_EDITOR=../repo_squash_helper.sh git rebase --strategy theirs -i bd6a09a484b8230d0810e6689cf08a24f26f287a

repo_squash_helper.sh (skrip ini hanya digunakan oleh repo_squash.sh):


if grep -q "pick " $1
then
#  cp $1 ../repo_squash_history.txt
#  emacs -nw $1
  sed -f ../repo_squash_list.txt < $1 > $1.tmp
  mv $1.tmp $1
else
  if grep -q "initial import" $1
  then
    cp ../repo_squash_new_message1.txt $1
  elif grep -q "fixing bad import" $1
  then
    cp ../repo_squash_new_message2.txt $1
  else
    emacs -nw $1
  fi
fi

repo_squash_list.txt: (file ini hanya digunakan oleh repo_squash_helper.sh)


# Initial import
s/pick \(251a190\)/squash \1/g
# Leaving "Needed subdir" for now
# Fixing bad import
s/pick \(46c41d1\)/squash \1/g
s/pick \(5d7agf2\)/squash \1/g
s/pick \(3da63ed\)/squash \1/g

Saya akan meninggalkan konten "pesan baru" ke imajinasi Anda. Awalnya, saya melakukan ini tanpa opsi "--strategy theirs" (yaitu, menggunakan strategi default, yang jika saya memahami dokumentasi dengan benar adalah rekursif, tapi saya tidak yakin strategi rekursif mana yang digunakan), dan juga tidak t bekerja. Juga, saya harus menunjukkan bahwa, dengan menggunakan kode komentar di repo_squash_helper.sh, saya menyimpan file asli yang digunakan pada skrip sed dan menjalankan skrip sed terhadapnya untuk memastikan ia melakukan apa yang saya inginkan ( dulu). Sekali lagi, saya bahkan tidak tahu mengapa akan ada konflik, jadi sepertinya tidak masalah strategi mana yang digunakan. Setiap saran atau wawasan akan sangat membantu, tetapi kebanyakan saya hanya ingin agar squashing ini bekerja.

Diperbarui dengan informasi tambahan dari diskusi dengan Jefromi:

Sebelum mengerjakan repositori "nyata" kami yang besar, saya menggunakan skrip yang mirip pada repositori uji. Itu adalah repositori yang sangat sederhana dan tesnya bekerja dengan baik.

Pesan yang saya dapatkan saat gagal adalah:

Finished one cherry-pick.
# Not currently on any branch.
nothing to commit (working directory clean)
Could not apply 66c45e2... Needed subdir

Ini adalah pilihan pertama setelah komit squash pertama. Menjalankan git statusmenghasilkan direktori kerja yang bersih. Jika saya kemudian melakukan git rebase --continue, saya mendapatkan pesan yang sangat mirip setelah beberapa komitmen lagi. Jika saya melakukannya lagi, saya mendapatkan pesan yang sangat mirip setelah beberapa lusin melakukan. Jika saya melakukannya lagi, kali ini melewati sekitar seratus komit, dan menghasilkan pesan ini:

Automatic cherry-pick failed.  After resolving the conflicts,
mark the corrected paths with 'git add <paths>', and
run 'git rebase --continue'
Could not apply f1de3bc... Incremental

Jika saya lari git status, saya dapat:

# Not currently on any branch.
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
# modified:   repo/file_A.cpp
# modified:   repo/file_B.cpp
#
# Unmerged paths:
#   (use "git reset HEAD <file>..." to unstage)
#   (use "git add/rm <file>..." as appropriate to mark resolution)
#
# both modified:      repo/file_X.cpp
#
# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
# deleted:    repo/file_Z.imp

Bit "keduanya dimodifikasi" terdengar aneh bagi saya, karena ini hanya hasil dari pick. Perlu juga dicatat bahwa jika saya melihat "konflik", itu bermuara pada satu baris dengan satu versi memulainya dengan karakter [tab], dan yang lainnya dengan empat spasi. Ini terdengar seperti itu mungkin masalah dengan bagaimana saya mengatur file konfigurasi saya, tetapi tidak ada yang seperti itu di dalamnya. (Saya memang mencatat bahwa core.ignorecase disetel ke true, tetapi ternyata git-clone melakukannya secara otomatis. Saya tidak sepenuhnya terkejut dengan hal itu mengingat sumber aslinya ada di mesin Windows.)

Jika saya memperbaiki file_X.cpp secara manual, kemudian gagal tidak lama kemudian dengan konflik lain, kali ini antara file (CMakeLists.txt) yang menurut satu versi seharusnya ada dan satu versi berpikir tidak seharusnya. Jika saya memperbaiki konflik ini dengan mengatakan saya ingin file ini (yang saya lakukan), beberapa komitmen kemudian saya mendapatkan konflik lain (dalam file yang sama) di mana sekarang ada beberapa perubahan yang tidak sepele. Masih hanya sekitar 25% dari jalan melalui konflik.

Saya juga harus menunjukkan, karena ini mungkin sangat penting, bahwa proyek ini dimulai dalam repositori svn. Sejarah awal itu sangat mungkin diimpor dari repositori svn itu.

Perbarui # 2:

Pada lark (dipengaruhi oleh komentar Jefromi), saya memutuskan untuk melakukan perubahan repo_squash.sh saya menjadi:

rm -rf repo_squash
git clone repo repo_squash
cd repo_squash/
git rebase --strategy theirs -i bd6a09a484b8230d0810e6689cf08a24f26f287a

Dan kemudian, saya baru saja menerima entri asli, sebagaimana adanya. Yaitu, "rebase" seharusnya tidak mengubah apa pun. Itu berakhir dengan hasil yang sama dengan yang dijelaskan sebelumnya.

Perbarui # 3:

Atau, jika saya menghilangkan strategi dan mengganti perintah terakhir dengan:

git rebase -i bd6a09a484b8230d0810e6689cf08a24f26f287a

Saya tidak lagi mendapatkan masalah rebase "apa-apa untuk dilakukan", tetapi saya masih memiliki konflik lainnya.

Perbarui dengan repositori mainan yang menciptakan masalah:

test_squash.sh (ini adalah file yang sebenarnya Anda jalankan):

#========================================================
# Initialize directories
#========================================================
rm -rf test_squash/ test_squash_clone/
mkdir -p test_squash
mkdir -p test_squash_clone
#========================================================

#========================================================
# Create repository with history
#========================================================
cd test_squash/
git init
echo "README">README
git add README
git commit -m"Initial commit: can't easily access for rebasing"
echo "Line 1">test_file.txt
git add test_file.txt
git commit -m"Created single line file"
echo "Line 2">>test_file.txt 
git add test_file.txt 
git commit -m"Meant for it to be two lines"
git checkout -b dev
echo Meaningful code>new_file.txt
git add new_file.txt 
git commit -m"Meaningful commit"
git checkout master
echo Conflicting meaningful code>new_file.txt
git add new_file.txt 
git commit -m"Conflicting meaningful commit"
# This will conflict
git merge dev
# Fixes conflict
echo Merged meaningful code>new_file.txt
git add new_file.txt
git commit -m"Merged dev with master"
cd ..

#========================================================
# Save off a clone of the repository prior to squashing
#========================================================
git clone test_squash test_squash_clone
#========================================================

#========================================================
# Do the squash
#========================================================
cd test_squash
GIT_EDITOR=../test_squash_helper.sh git rebase -i HEAD@{7}
#========================================================

#========================================================
# Show the results
#========================================================
git log
git gc
git reflog
#========================================================

test_squash_helper.sh (digunakan oleh test_sqash.sh):

# If the file has the phrase "pick " in it, assume it's the log file
if grep -q "pick " $1
then
  sed -e "s/pick \(.*\) \(Meant for it to be two lines\)/squash \1 \2/g" < $1 > $1.tmp
  mv $1.tmp $1
# Else, assume it's the commit message file
else
# Use our pre-canned message
  echo "Created two line file" > $1
fi

PS: Ya, saya tahu beberapa dari Anda merasa ngeri ketika melihat saya menggunakan emacs sebagai editor yang mundur.

PPS: Kami tahu kami harus membuang semua klon dari repositori yang ada setelah rebase. (Di sepanjang baris "kamu tidak boleh rebase repositori setelah itu diterbitkan".)

PPPS: Adakah yang bisa memberi tahu saya cara menambahkan hadiah untuk ini? Saya tidak melihat opsi di mana pun di layar ini apakah saya dalam mode edit atau mode tampilan.

Ben Hocking
sumber
Lebih relevan daripada skrip yang digunakan adalah tindakan terakhir yang dicoba - kelihatannya cukup pasti untuk menjadi daftar pick dan squash yang dicampur, kan? Dan apakah ada gabungan yang dilakukan di cabang rebased? (Meskipun Anda tidak menggunakan rebase -plagi)
Cascabel
Saya tidak yakin apa yang Anda maksud dengan "tindakan berusaha akhir", tetapi adalah hanya daftar pick bercampur dan squash, dengan yang terakhir 400 atau lebih menjadi semua pick. Tidak ada penggabungan dalam daftar itu, meskipun rebasing sendiri melakukan penggabungannya sendiri. Menurut apa yang saya baca, "rebase -p" tidak direkomendasikan dengan mode interaktif (yang dalam kasus saya tidak semua yang interaktif, tentu saja). Dari kernel.org/pub/software/scm/git/docs/git-rebase.html : "Ini menggunakan mesin --interaktif secara internal, tetapi menggabungkannya dengan opsi --interaktif secara eksplisit umumnya bukan ide yang baik"
Ben Hocking
Dengan "tindakan akhir yang dicoba" maksud saya daftar pick / squash dikembalikan ke rebase --interactive- itu adalah semacam daftar tindakan untuk git untuk mencoba. Saya berharap Anda dapat mengurangi ini menjadi satu squash yang menyebabkan konflik, dan menghindari semua kerumitan tambahan dari skrip pembantu Anda. Informasi lain yang hilang adalah ketika konflik terjadi - kapan git menerapkan tambalan untuk membentuk squash, atau ketika ia mencoba bergerak melewati squash dan menerapkan tambalan berikutnya? (Dan Anda yakin tidak ada hal buruk terjadi dengan GIT_EDITOR kludge Anda? Voting lain untuk test case sederhana.)
Cascabel
Terima kasih atas komentar yang bermanfaat. Saya telah memperbarui pertanyaan untuk mencerminkan jawaban saya.
Ben Hocking
2
Kasing penyimpanan mainan jauh, jauh lebih mudah dipahami daripada skrip Anda - dan menunjukkan bahwa skrip tidak ada hubungannya dengan skrip itu, hanya fakta bahwa Anda mencoba untuk rebase cabang yang berisi penggabungan dengan resolusi konflik (penggabungan sedikit kejahatan ).
Cascabel

Jawaban:

69

Baiklah, saya cukup percaya diri untuk mengeluarkan jawaban. Mungkin harus mengeditnya, tapi saya yakin saya tahu apa masalah Anda.

Kasing pengujian mainan repo Anda memiliki penggabungan - yang lebih buruk, penggabungan dengan konflik. Dan Anda rebasing melintasi penggabungan. Tanpa -p(yang tidak sepenuhnya bekerja dengan -i), gabungan diabaikan. Ini berarti bahwa apa pun yang Anda lakukan dalam resolusi konflik Anda tidak ada ketika rebase mencoba untuk memilih komit berikutnya, jadi tambalannya mungkin tidak berlaku. (Saya percaya ini ditampilkan sebagai konflik penggabungan karena git cherry-pickdapat menerapkan tambalan dengan melakukan penggabungan tiga arah antara komit asli, komit saat ini, dan leluhur bersama.)

Sayangnya, seperti yang kita catat di komentar, -idan -p(pertahankan penggabungan) tidak rukun. Saya tahu pekerjaan pengeditan / penulisan ulang, dan pemesanan ulang itu tidak. Namun, saya percaya itu berfungsi baik dengan squash. Ini tidak didokumentasikan, tetapi berfungsi untuk kasus uji yang saya jelaskan di bawah ini. Jika kasing Anda adalah jalan, jauh lebih rumit, Anda mungkin memiliki banyak kesulitan untuk melakukan apa yang Anda inginkan, meskipun itu masih mungkin. (Moral cerita: bersihkan dengan rebase -i sebelum penggabungan.)

Jadi, anggaplah kita memiliki kasus yang sangat sederhana, di mana kita ingin menyatukan A, B, dan C:

- o - A - B - C - X - D - E - F (master)
   \             /
    Z -----------

Sekarang, seperti yang saya katakan, jika tidak ada konflik di X, git rebase -i -pberfungsi seperti yang Anda harapkan.

Jika ada konflik, segalanya menjadi sedikit rumit. Ini akan melakukan squashing yang baik, tetapi kemudian ketika ia mencoba untuk membuat ulang penggabungan, konflik akan terjadi lagi. Anda harus menyelesaikannya lagi, menambahkannya ke indeks, lalu gunakan git rebase --continueuntuk melanjutkan. (Tentu saja, Anda dapat menyelesaikannya lagi dengan memeriksa versi dari komit gabungan asli.)

Jika Anda kebetulan telah rererediaktifkan pada repo Anda ( rerere.enabledset ke true), ini akan menjadi cara yang lebih mudah - git akan dapat kembali menggunakan kembali dijalin dgn tali re solusi dari ketika Anda awalnya memiliki konflik, dan semua yang harus Anda lakukan yaitu memeriksanya untuk memastikan itu berfungsi dengan benar, tambahkan file ke indeks, dan lanjutkan. (Anda bahkan dapat melangkah lebih jauh, menyalakan rerere.autoupdate, dan itu akan menambahkannya untuk Anda, sehingga penggabungan bahkan tidak akan gagal). Saya menduga, bagaimanapun, bahwa Anda tidak pernah mengaktifkan rerere, jadi Anda harus melakukan resolusi konflik sendiri. *

* Atau, Anda dapat mencoba rerere-train.shskrip dari git-contrib, yang mencoba "Perdana [yang] menjalankan kembali database dari komit gabungan yang ada" - pada dasarnya, ia memeriksa semua komit gabungan, mencoba untuk menggabungkannya, dan jika gabungan tersebut gagal, itu meraih hasil dan menunjukkannya git-rerere. Ini bisa menghabiskan waktu, dan saya belum pernah menggunakannya, tetapi mungkin akan sangat membantu.

Cascabel
sumber
PS Melihat kembali komentar saya, saya melihat saya seharusnya menangkap ini lebih cepat. Saya bertanya apakah Anda memunculkan kembali cabang yang berisi gabungan, dan Anda berkata tidak ada gabungan dalam daftar rebase interaktif, yang bukan hal yang sama - tidak ada gabungan di sana karena Anda tidak menyediakan -popsi.
Cascabel
Saya pasti akan mencobanya. Sejak memposting ini, saya juga perhatikan dalam beberapa kasus, saya cukup mengetik git commit -a -m"Some message"dan git rebase --continue, dan itu akan berlanjut. Ini berfungsi bahkan tanpa -popsi, tetapi bekerja lebih baik dengan -popsi (karena saya tidak melakukan pemesanan ulang, tampaknya itu -pbaik-baik saja). Bagaimanapun, saya akan membuat Anda tetap diposting.
Ben Hocking
Pengaturan git config --global rerere.enabled truedan git config --global rerere.autoupdate truesebelum menjalankan contoh pengujian tidak menyelesaikan masalah utama. Cukup menarik, bagaimanapun, itu tidak mempertahankan penggabungan, bahkan ketika menentukan --preserve-merges. Namun, jika saya tidak memiliki set itu, dan saya mengetik git commit -a -m"Meaningful commit"dan git rebase --continuesaat menentukan --preserve-merges, itu mempertahankan penggabungan. Bagaimanapun, terima kasih telah membantu saya mengatasi masalah ini!
Ben Hocking
84

Jika Anda tidak keberatan membuat cabang baru, inilah cara saya menangani masalah ini:

Menjadi yang utama:

# create a new branch
git checkout -b new_clean_branch

# apply all changes
git merge original_messy_branch

# forget the commits but have the changes staged for commit
git reset --soft main        

git commit -m "Squashed changes from original_messy_branch"
hlidka
sumber
3
Solusi tercepat dan efisien :)
IslamTaha
2
Ini saran yang bagus! Bekerja sangat baik untuk mengkonsolidasikan beberapa komitmen dengan cepat.
philidem
3
Ini adalah solusi yang jauh lebih aman. Saya juga menambahkan --squash ke penggabungan. Being: git merge --squash original_messy_branch
jezpez
1
Terima kasih karena telah menyelamatkan hari dan hidupku :) Solusi terbaik yang bisa didapat siapa pun
Kapan Anda melakukannya git diff new_clean_branch..original_messy_branchharuskah mereka sama? Bagi saya mereka berbeda.
LeonardChallis
5

Saya sedang mencari persyaratan yang sama, yaitu membuang komitmen intermeiate dari cabang pengembangan saya, saya telah menemukan prosedur ini bekerja untuk saya.
di cabang kerja saya

git reset –hard mybranch-start-commit
git checkout mybranch-end-commit . // files only of the latest commit
git add -a
git commit -m”New Message intermediate commits discarded”

viola kami telah menghubungkan komit terbaru ke komit awal cabang! Tidak ada masalah konflik penggabungan! Dalam praktik belajar saya, saya sampai pada kesimpulan ini pada tahap ini, Apakah ada pendekatan yang lebih baik untuk tujuan tersebut.

pengguna28186
sumber
FYI - Saya baru saja mencoba ini dan menemukan bahwa melakukan checkout dari mybranch-end-commit tidak membuat saya menghapus yang terjadi pada intermediate commit. Jadi ini hanya memeriksa file yang ada di mybranch-end-commit.
ahains
1

Membangun di atas jawaban hebat @ hlidka di atas yang meminimalkan intervensi manual, saya ingin menambahkan versi yang mempertahankan setiap komit baru pada master yang tidak di cabang untuk squash.

Karena saya percaya ini bisa dengan mudah hilang pada git resetlangkah dalam contoh itu.

# create a new branch 
# ...from the commit in master original_messy_branch was originally based on. eg 5654da06
git checkout -b new_clean_branch 5654da06

# apply all changes
git merge original_messy_branch

# forget the commits but have the changes staged for commit
# ...base the reset on the base commit from Master
git reset --soft 5654da06       

git commit -m "Squashed changes from original_messy_branch"

# Rebase onto HEAD of master
git rebase origin/master

# Resolve any new conflicts from the new commits
JonoB
sumber
0

Perhatikan bahwa -Xdan opsi strategi diabaikan ketika digunakan dalam rebase interaktif.

Lihat commit db2b3b820e2b28da268cc88adff076b396392dfe (Juli 2013, git 1.8.4+),

Jangan abaikan opsi menggabungkan dalam rebase interaktif

Strategi penggabungan dan opsinya dapat ditentukan dalam git rebase, tetapi dengan -- interactive, mereka sepenuhnya diabaikan.

Ditandatangani oleh: Arnaud Fontaine

Itu berarti -Xdan strategi sekarang bekerja dengan rebase interaktif, serta rebase sederhana, dan skrip awal Anda sekarang bisa berfungsi lebih baik.

VONC
sumber
0

Saya mengalami masalah yang lebih sederhana namun serupa, di mana saya telah 1) menyelesaikan konflik gabungan di cabang lokal, 2) terus bekerja menambahkan banyak lebih sedikit komitmen, 3) ingin rebase dan memukul konflik gabungan.

Bagi saya, git rebase -p -i masterberhasil. Itu membuat resolusi konflik asli berkomitmen dan memungkinkan saya untuk menekan yang lain di atas.

Semoga itu bisa membantu seseorang!

abaldwinhunter
sumber
Saya masih punya beberapa konflik untuk diselesaikan secara manual ketika mencoba rebase dengan -p Harus diakui ada banyak konflik yang lebih sedikit dan mereka hanya terbatas pada file proyek daripada semua file kode saya mendapatkan konflik dengan sebelumnya. Rupanya "Gabungkan resolusi konflik atau amandemen manual untuk menggabungkan komitmen tidak dipertahankan." - stackoverflow.com/a/35714301/727345
JonoB