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" .)
git checkout -m, yang menggabungkan pohon kerja Anda dan perubahan indeks ke checkout baru.