Periksa cabang lain saat ada perubahan yang tidak dikomit pada cabang saat ini

349

Sebagian besar waktu ketika saya mencoba untuk checkout cabang lain yang ada, Git tidak mengizinkan saya jika saya memiliki beberapa perubahan yang tidak terikat pada cabang saat ini. Jadi saya harus melakukan atau menyembunyikan perubahan itu terlebih dahulu.

Namun, kadang-kadang Git mengizinkan saya untuk checkout cabang lain tanpa melakukan atau menyembunyikan perubahan itu, dan itu akan membawa perubahan itu ke cabang saya checkout.

Apa aturannya di sini? Apakah penting apakah perubahan itu dilakukan atau tidak? Membawa perubahan ke cabang lain tidak masuk akal bagi saya, mengapa kadang-kadang git mengizinkannya? Yaitu, apakah itu membantu dalam beberapa situasi?

Xufeng
sumber

Jawaban:

350

Catatan awal

Pengamatan di sini adalah bahwa, setelah Anda mulai bekerja branch1(lupa atau tidak menyadari bahwa akan lebih baik untuk beralih ke cabang yang berbeda branch2terlebih dahulu), Anda menjalankan:

git checkout branch2

Terkadang Git berkata, "Oke, kamu ada di branch2 sekarang!" Terkadang, Git berkata, "Aku tidak bisa melakukan itu, aku akan kehilangan beberapa perubahanmu."

Jika Git tidak akan membiarkan Anda melakukannya, Anda harus melakukan perubahan Anda, untuk menyimpannya di tempat permanen. Anda mungkin ingin menggunakannya git stashuntuk menyimpannya; ini adalah salah satu hal yang dirancang untuk itu. Perhatikan bahwa git stash saveatau git stash pushsebenarnya berarti "Komit semua perubahan, tetapi tidak pada cabang sama sekali, lalu hapus dari tempat saya sekarang." Itu memungkinkan untuk beralih: Anda sekarang tidak memiliki perubahan dalam proses. Anda bisa kemudian git stash applysetelah berpindah.

Sidebar: git stash saveadalah sintaks lama; git stash pushdiperkenalkan di Git versi 2.13, untuk memperbaiki beberapa masalah dengan argumen git stashdan memungkinkan untuk opsi baru. Keduanya melakukan hal yang sama, ketika digunakan dengan cara dasar.

Anda bisa berhenti membaca di sini, jika mau!

Jika Git tidak akan membiarkan Anda beralih, Anda sudah memiliki obat: gunakan git stashatau git commit; atau, jika perubahan Anda sepele untuk dibuat kembali, gunakan git checkout -funtuk memaksanya. Jawaban ini adalah tentang kapan Git akan membiarkan Anda git checkout branch2meskipun Anda mulai membuat beberapa perubahan. Mengapa kadang-kadang itu berhasil , dan tidak kali lain ?

Aturan di sini sederhana dalam satu cara, dan rumit / sulit untuk dijelaskan dengan cara lain:

Anda bisa berganti cabang dengan perubahan yang tidak dikomit di pohon-kerja jika dan hanya jika mengatakan pergantian tidak membutuhkan clobbering perubahan itu.

Yaitu — dan harap dicatat bahwa ini masih disederhanakan; ada beberapa kasus sudut yang sangat sulit dengan git adds, git rms dan semacamnya — anggaplah Anda sedang aktif branch1. A git checkout branch2harus melakukan ini:

  • Untuk setiap file yang adalah di branch1dan tidak di branch2, 1 menghapus file.
  • Untuk setiap file yang adalah di branch2dan tidak di branch1, membuat file yang (dengan isi yang sesuai).
  • Untuk setiap file yang ada di kedua cabang, jika versi dalam branch2berbeda, perbarui versi pohon kerja.

Masing-masing langkah ini bisa mengalahkan sesuatu di pohon pekerjaan Anda:

  • Menghapus file adalah "aman" jika versi di pohon-kerja sama dengan versi yang dilakukan di branch1; "tidak aman" jika Anda melakukan perubahan.
  • Membuat file dengan cara itu muncul branch2adalah "aman" jika tidak ada sekarang. 2 "Tidak aman" jika memang ada sekarang tetapi memiliki konten "salah".
  • Dan tentu saja, mengganti versi work-tree dari file dengan versi yang berbeda adalah "aman" jika versi work-tree sudah berkomitmen branch1.

Membuat cabang baru ( git checkout -b newbranch) selalu dianggap "aman": tidak ada file yang akan ditambahkan, dihapus, atau diubah di pohon-kerja sebagai bagian dari proses ini, dan area indeks / staging juga tidak tersentuh. (Peringatan: aman saat membuat cabang baru tanpa mengubah titik awal cabang baru; tetapi jika Anda menambahkan argumen lain, misalnya git checkout -b newbranch different-start-point, ini mungkin harus mengubah hal-hal, untuk pindah different-start-point. Git kemudian akan menerapkan aturan keselamatan checkout seperti biasa .)


1 Ini mengharuskan kita mendefinisikan apa artinya file berada dalam cabang, yang pada gilirannya mengharuskan mendefinisikan kata cabang dengan benar. (Lihat juga Apa sebenarnya yang kita maksud dengan "cabang"? ) Di sini, apa yang saya benar-benar berarti adalah komit untuk yang cabang-nama resolves: sebuah file yang jalan adalah adalah dalam jika menghasilkan hash. File yang tidak di jika Anda mendapatkan pesan kesalahan sebagai gantinya. Keberadaan jalur di indeks atau pohon kerja Anda tidak relevan saat menjawab pertanyaan khusus ini. Jadi, rahasianya di sini adalah untuk memeriksa hasil masing-masingP branch1git rev-parse branch1:Pbranch1Pgit rev-parsebranch-name:path. Ini gagal karena file "dalam" paling banyak satu cabang, atau memberi kita dua ID hash. Jika kedua hash ID adalah sama , file tersebut sama di kedua cabang. Tidak diperlukan perubahan. Jika ID hash berbeda, file berbeda di dua cabang, dan harus diubah untuk beralih cabang.

Gagasan utama di sini adalah bahwa file dalam komit dibekukan selamanya. File yang akan Anda edit jelas tidak beku. Kami, setidaknya pada awalnya, hanya melihat ketidakcocokan antara dua komitmen beku. Sayangnya, kami — atau Git — juga harus berurusan dengan file yang tidak ada dalam komit yang akan Anda alihkan dan berada di komit yang akan Anda alihkan. Ini mengarah pada komplikasi yang tersisa, karena file juga dapat ada di indeks dan / atau di pohon-kerja, tanpa harus ada dua komit beku tertentu yang sedang kami kerjakan.

2 Ini mungkin dianggap "semacam aman" jika sudah ada dengan "konten yang tepat", sehingga Git tidak harus membuatnya lagi. Saya ingat setidaknya beberapa versi Git mengizinkan ini, tetapi pengujian barusan menunjukkan itu dianggap "tidak aman" di Git 1.8.5.4. Argumen yang sama akan berlaku untuk file yang dimodifikasi yang kebetulan dimodifikasi agar sesuai dengan cabang to-be-switch-to. Sekali lagi, 1.8.5.4 hanya mengatakan "akan ditimpa", meskipun. Lihat akhir catatan teknis juga: ingatan saya mungkin salah karena saya tidak berpikir aturan baca-pohon telah berubah sejak saya mulai menggunakan Git pada versi 1.5.something.


Apakah penting apakah perubahan itu dilakukan atau tidak?

Ya, dalam beberapa hal. Secara khusus, Anda dapat melakukan perubahan, lalu "membatalkan modifikasi" file pohon kerja. Berikut adalah file dalam dua cabang, yang berbeda dalam branch1dan branch2:

$ git show branch1:inboth
this file is in both branches
$ git show branch2:inboth
this file is in both branches
but it has more stuff in branch2 now
$ git checkout branch1
Switched to branch 'branch1'
$ echo 'but it has more stuff in branch2 now' >> inboth

Pada titik ini, file pohon kerja inbothcocok dengan yang ada di branch2, meskipun kita aktif branch1. Perubahan ini tidak dilakukan untuk komit, yang git status --shortditunjukkan di sini:

$ git status --short
 M inboth

Space-then-M berarti "dimodifikasi tetapi tidak dipentaskan" (atau lebih tepatnya, copy pohon kerja berbeda dari salinan staged / index).

$ git checkout branch2
error: Your local changes ...

Oke, sekarang mari kita buat salinan pohon kerja, yang sudah kita ketahui juga cocok dengan salinannya branch2.

$ git add inboth
$ git status --short
M  inboth
$ git checkout branch2
Switched to branch 'branch2'

Di sini, salinan yang dipentaskan dan bekerja cocok dengan apa yang ada di dalam branch2, sehingga checkout diizinkan.

Mari kita coba langkah lain:

$ git checkout branch1
Switched to branch 'branch1'
$ cat inboth
this file is in both branches

Perubahan yang saya buat hilang dari area pementasan sekarang (karena checkout menulis melalui area pementasan). Ini sedikit kasus sudut. Perubahan itu tidak hilang, tapi fakta bahwa aku telah dipentaskan itu, yang hilang.

Mari kita buat varian ketiga dari file tersebut, berbeda dari kedua cabang-salinan, lalu atur copy pekerjaan agar sesuai dengan versi cabang saat ini:

$ echo 'staged version different from all' > inboth
$ git add inboth
$ git show branch1:inboth > inboth
$ git status --short
MM inboth

Dua Mdi sini berarti: file bertahap berbeda dari HEADfile, dan , file pohon kerja berbeda dari file bertahap. Versi pohon kerja tidak cocok dengan versi branch1(alias HEAD):

$ git diff HEAD
$

Tetapi git checkouttidak akan mengizinkan checkout:

$ git checkout branch2
error: Your local changes ...

Mari kita tetapkan branch2versi sebagai versi kerja:

$ git show branch2:inboth > inboth
$ git status --short
MM inboth
$ git diff HEAD
diff --git a/inboth b/inboth
index ecb07f7..aee20fb 100644
--- a/inboth
+++ b/inboth
@@ -1 +1,2 @@
 this file is in both branches
+but it has more stuff in branch2 now
$ git diff branch2 -- inboth
$ git checkout branch2
error: Your local changes ...

Meskipun copy pekerjaan saat ini cocok dengan yang ada di branch2, file yang dipentaskan tidak, jadi git checkoutakan kehilangan salinan itu, dan git checkoutditolak.

Catatan teknis — hanya untuk yang penasaran :-)

Mekanisme implementasi yang mendasari semua ini adalah indeks Git . Indeks, juga disebut "area pementasan", adalah tempat Anda membangun komit berikutnya : mulai cocok dengan komit saat ini, yaitu, apa pun yang Anda telah check-out sekarang, dan kemudian setiap kali Anda git addfile, Anda mengganti versi indeks dengan apa pun yang Anda miliki di pohon pekerjaan Anda.

Ingat, pohon-kerja adalah tempat Anda mengerjakan file Anda. Di sini, mereka memiliki bentuk normal, daripada beberapa bentuk khusus yang hanya berguna untuk Git seperti yang mereka lakukan di commit dan di indeks. Jadi, Anda mengekstrak file dari komit, melalui indeks, dan kemudian ke pohon-kerja. Setelah mengubahnya, Anda git addke indeks. Jadi sebenarnya ada tiga tempat untuk setiap file: komit saat ini, indeks, dan pohon kerja.

Ketika Anda menjalankan git checkout branch2, apa Git tidak di bawah selimut adalah untuk membandingkan ujung berkomitmen dari branch2apa pun adalah baik di saat melakukan dan indeks sekarang. File apa pun yang cocok dengan yang ada di sana sekarang, Git dapat dibiarkan sendiri. Semuanya tidak tersentuh. File apa pun yang sama di kedua komit , Git juga dapat dibiarkan sendiri — dan ini adalah yang memungkinkan Anda beralih cabang.

Banyak Git, termasuk komit-switching, relatif cepat karena indeks ini. Apa yang sebenarnya ada di indeks bukanlah masing-masing file itu sendiri, melainkan setiap hash file . Salinan file itu sendiri disimpan sebagai apa yang Git sebut sebagai objek gumpalan , dalam repositori. Ini mirip dengan bagaimana file disimpan dalam komit juga: komit sebenarnya tidak mengandung file , mereka hanya membawa Git ke ID hash dari setiap file. Jadi Git dapat membandingkan ID hash — string 160-bit-saat ini — untuk memutuskan apakah komit X dan Y memiliki file yang sama atau tidak. Kemudian dapat membandingkan ID hash tersebut dengan ID hash dalam indeks juga.

Inilah yang mengarah ke semua kasus sudut aneh di atas. Kami memiliki X dan Y yang keduanya memiliki file path/to/name.txt, dan kami memiliki entri indeks untuk path/to/name.txt. Mungkin ketiga hash cocok. Mungkin dua dari mereka cocok dan satu tidak. Mungkin ketiganya berbeda. Dan, kita mungkin juga memiliki another/file.txtitu hanya di X atau hanya di Y dan sedang atau tidak dalam indeks sekarang. Masing-masing dari berbagai kasus ini memerlukan pertimbangan tersendiri: apakah Git perlu menyalin file dari commit ke index, atau menghapusnya dari index, untuk beralih dari X ke Y ? Jika demikian, itu juga harussalin file ke work-tree, atau hapus dari work-tree. Dan jika itu masalahnya, versi indeks dan versi work-tree lebih cocok dengan setidaknya satu versi yang dikomit; jika tidak, Git akan menghancurkan beberapa data.

(Aturan yang lengkap untuk semua ini dijelaskan dalam, bukan git checkoutdokumentasi seperti yang Anda harapkan, melainkan para git read-treedokumentasi, di bawah bagian berjudul "Two Tree Merge" .)

torek
sumber
3
... ada juga git checkout -m, yang menggabungkan pohon kerja Anda dan perubahan indeks ke checkout baru.
jthill
1
Terima kasih atas penjelasan yang luar biasa ini! Tetapi di mana saya dapat menemukan informasi dalam dokumen resmi? Atau mereka tidak lengkap? Jika demikian, apa referensi resmi untuk git (semoga selain dari kode sumbernya)?
maks
1
(1) Anda tidak bisa, dan (2) kode sumber. Masalah utama adalah bahwa Git terus berkembang. Misalnya, saat ini, ada dorongan besar untuk menambah atau menyingkirkan SHA-1 dengan atau mendukung SHA-256. Bagian khusus dari Git ini sudah cukup stabil untuk waktu yang lama sekarang, dan mekanisme yang mendasarinya mudah: Git membandingkan indeks saat ini dengan saat ini dan target berkomitmen dan memutuskan file mana yang akan diubah (jika ada) berdasarkan komit target , lalu uji "cleanness" file work-tree jika entri indeks perlu diganti.
torek
6
Jawaban singkat: Ada aturan, tetapi terlalu tumpul bagi pengguna rata-rata untuk memiliki harapan untuk memahami apalagi mengingat, karena itu alih-alih bergantung pada alat untuk berperilaku secara cerdas, Anda harus mengandalkan konvensi disiplin hanya memeriksa ketika Anda cabang saat ini berkomitmen dan bersih. Saya tidak melihat bagaimana ini menjawab pertanyaan kapan akan berguna untuk membawa perubahan yang luar biasa ke cabang lain, tetapi saya mungkin melewatkannya karena saya kesulitan memahaminya.
Neutrino
2
@ HawkeyeParker: jawaban ini telah mengalami banyak pengeditan, dan saya tidak yakin ada di antara mereka yang banyak meningkatkannya, tetapi saya akan mencoba menambahkan sesuatu tentang apa artinya file menjadi "di cabang". Pada akhirnya ini akan menjadi goyah karena gagasan "cabang" di sini tidak didefinisikan dengan benar di tempat pertama, tapi itu belum item lain.
torek
50

Anda memiliki dua pilihan: menyimpan perubahan Anda:

git stash

kemudian untuk mendapatkannya kembali:

git stash apply

atau letakkan perubahan Anda di cabang sehingga Anda bisa mendapatkan cabang jarak jauh dan kemudian gabungkan perubahan Anda ke cabang itu. Itu salah satu hal terbesar tentang git: Anda bisa membuat cabang, berkomitmen padanya, lalu mengambil perubahan lain ke cabang tempat Anda berada.

Anda mengatakan itu tidak masuk akal, tetapi Anda hanya melakukannya sehingga Anda dapat menggabungkannya sesuka hati setelah melakukan tarikan. Tentunya pilihan Anda yang lain adalah berkomitmen pada salinan cabang Anda dan kemudian melakukan penarikan. Anggapannya adalah Anda tidak ingin melakukan itu (dalam hal ini saya bingung bahwa Anda tidak menginginkan cabang) atau Anda takut akan konflik.

rampok
sumber
1
Bukankah perintah yang benar git stash apply? di sini dokumen.
Thomas8
1
Apa yang saya cari, untuk sementara waktu beralih ke cabang yang berbeda, untuk mencari sesuatu dan kembali ke keadaan cabang yang sama yang sedang saya kerjakan. Rob terima kasih!
Naishta
1
Ya, ini cara yang tepat untuk melakukan ini. Saya menghargai detail dalam jawaban yang diterima, tetapi itu membuat segalanya lebih sulit daripada yang seharusnya.
Michael Leonard
5
Juga, jika Anda tidak perlu menyimpan simpanan, Anda dapat menggunakan git stash popdan simpanan akan dikeluarkan dari daftar jika berhasil.
Michael Leonard
1
lebih baik digunakan git stash pop, kecuali Anda berniat menyimpan catatan simpanan dalam riwayat repo Anda
Damilola Olowookere
14

Jika cabang baru berisi pengeditan yang berbeda dari cabang saat ini untuk file yang diubah tertentu, maka itu tidak akan memungkinkan Anda untuk beralih cabang sampai perubahan dilakukan atau disimpan. Jika file yang diubah sama di kedua cabang (yaitu, versi file yang berkomitmen), maka Anda dapat beralih dengan bebas.

Contoh:

$ echo 'hello world' > file.txt
$ git add file.txt
$ git commit -m "adding file.txt"

$ git checkout -b experiment
$ echo 'goodbye world' >> file.txt
$ git add file.txt
$ git commit -m "added text"
     # experiment now contains changes that master doesn't have
     # any future changes to this file will keep you from changing branches
     # until the changes are stashed or committed

$ echo "and we're back" >> file.txt  # making additional changes
$ git checkout master
error: Your local changes to the following files would be overwritten by checkout:
    file.txt
Please, commit your changes or stash them before you can switch branches.
Aborting

Ini berlaku untuk file yang tidak dilacak serta file yang dilacak. Berikut adalah contoh untuk file yang tidak dilacak.

Contoh:

$ git checkout -b experimental  # creates new branch 'experimental'
$ echo 'hello world' > file.txt
$ git add file.txt
$ git commit -m "added file.txt"

$ git checkout master # master does not have file.txt
$ echo 'goodbye world' > file.txt
$ git checkout experimental
error: The following untracked working tree files would be overwritten by checkout:
    file.txt
Please move or remove them before you can switch branches.
Aborting

Contoh yang bagus tentang mengapa Anda AKAN ingin berpindah di antara cabang sambil membuat perubahan adalah jika Anda melakukan beberapa percobaan pada master, ingin mengkomitnya, tetapi belum untuk menguasainya ...

$ echo 'experimental change' >> file.txt # change to existing tracked file
   # I want to save these, but not on master

$ git checkout -b experiment
M       file.txt
Switched to branch 'experiment'
$ git add file.txt
$ git commit -m "possible modification for file.txt"
Gordolio
sumber
Sebenarnya saya masih belum mengerti. Dalam contoh pertama Anda, setelah Anda menambahkan "dan kami kembali", dikatakan perubahan lokal akan ditimpa, perubahan lokal apa sebenarnya? "dan kita kembali"? Mengapa git tidak hanya membawa perubahan ini ke master sehingga dalam master file berisi "hello world" dan "dan kami kembali"
Xufeng
Pada contoh pertama, master hanya memiliki komitmen 'hello world'. Percobaan telah dilakukan 'hello world \ ngoodbye world'. Agar perubahan cabang terjadi, file.txt perlu diubah, masalahnya adalah, ada perubahan yang tidak dikomit "hello world \ ngoodbye world \ ndan kami kembali".
Gordolio
1

Jawaban yang benar adalah

git checkout -m origin/master

Itu menggabungkan perubahan dari cabang master asal dengan perubahan lokal Anda bahkan tanpa komitmen.

JD1731
sumber
0

Jika Anda tidak ingin perubahan ini dilakukan sama sekali git reset --hard .

Selanjutnya Anda dapat checkout ke cabang yang diinginkan, tetapi ingat bahwa perubahan yang tidak dikomit akan hilang.

Kacpero
sumber