Dalam kasus apa `git pull` bisa berbahaya?

409

Saya memiliki seorang kolega yang mengklaim itu git pullberbahaya, dan menjadi marah setiap kali seseorang menggunakannya.

The git pullperintah tampaknya menjadi cara kanonik untuk memperbarui repositori lokal Anda. Apakah menggunakan git pullmasalah menciptakan? Masalah apa yang diciptakannya? Apakah ada cara yang lebih baik untuk memperbarui repositori git?

Richard Hansen
sumber
53
@ j.karlsson: meta.stackexchange.com/questions/17463/…
Richard Hansen
8
Atau Anda dapat git pull --rebasemengatur strategi ini sebagai default untuk cabang baru git config branch.autosetuprebase
knoopx
4
knoopx benar, menambahkan --rebasetanda untuk git pullmenyinkronkan lokal dengan remote kemudian memutar ulang perubahan lokal Anda di atas lokal yang diperbarui. Kemudian ketika Anda mendorong semua yang Anda lakukan adalah menambahkan komit baru Anda ke ujung remote. Cukup mudah.
Heath Lilley
4
Terima kasih @BenMcCormick. Saya sudah melakukan itu, tetapi diskusi tentang validitas pertanyaan tampaknya terjadi dalam komentar di bawah pertanyaan ini. Dan saya pikir mengajukan pertanyaan untuk membuat platform untuk menyajikan pendapat pribadi Anda karena faktanya bukan untuk struktur Q&A SO sebenarnya.
mcv
4
@RichardHansen, sepertinya cara untuk menipu sistem poin, terutama dengan jawaban Anda memiliki perbedaan nada yang drastis dan jarak waktu yang singkat. Dengan menggunakan model T&J Anda, kami semua bisa hanya mengajukan pertanyaan dan menjawabnya sendiri menggunakan pengetahuan kami sebelumnya. Pada titik itu, Anda hanya perlu mempertimbangkan menulis posting blog karena itu berkali-kali lebih tepat. T&J secara khusus mencari pengetahuan orang lain. Sebuah posting blog menunjukkan milik Anda.
Josh Brown

Jawaban:

546

Ringkasan

Secara default, git pullmembuat komit gabungan yang menambahkan kebisingan dan kompleksitas ke riwayat kode. Selain itu, pullmembuatnya mudah untuk tidak memikirkan bagaimana perubahan Anda mungkin dipengaruhi oleh perubahan yang masuk.

The git pullperintah aman asalkan hanya melakukan penggabungan cepat-maju. Jika git pulldikonfigurasi untuk hanya melakukan penggabungan maju-cepat dan ketika penggabungan maju-cepat tidak memungkinkan, maka Git akan keluar dengan kesalahan. Ini akan memberi Anda kesempatan untuk mempelajari komit yang masuk, berpikir tentang bagaimana mereka dapat mempengaruhi komit lokal Anda, dan memutuskan tindakan terbaik (gabung, rebase, atur ulang, dll.).

Dengan Git 2.0 dan yang lebih baru, Anda dapat menjalankan:

git config --global pull.ff only

untuk mengubah perilaku default menjadi hanya maju cepat. Dengan versi Git antara 1.6.6 dan 1.9.x Anda harus terbiasa mengetik:

git pull --ff-only

Namun, dengan semua versi Git, saya sarankan mengonfigurasi git upalias seperti ini:

git config --global alias.up '!git remote update -p; git merge --ff-only @{u}'

dan menggunakan git upbukan git pull. Saya lebih suka alias ini daripada git pull --ff-onlykarena:

  • ini bekerja dengan semua versi Git (non-kuno),
  • itu mengambil semua cabang hulu (bukan hanya cabang yang sedang Anda kerjakan), dan
  • membersihkan origin/*cabang - cabang lama yang tidak lagi ada di hulu.

Masalah dengan git pull

git pulltidak buruk jika digunakan dengan benar. Beberapa perubahan terbaru pada Git telah membuatnya lebih mudah untuk digunakan git pulldengan benar, tetapi sayangnya perilaku default dari suatu dataran git pullmemiliki beberapa masalah:

  • itu memperkenalkan nonlinier yang tidak perlu dalam sejarah
  • itu membuatnya mudah untuk secara tidak sengaja memperkenalkan kembali komitmen yang sengaja diubah kembali ke hulu
  • itu mengubah direktori kerja Anda dengan cara yang tidak terduga
  • menjeda apa yang Anda lakukan untuk meninjau pekerjaan orang lain itu menyebalkan git pull
  • membuatnya sulit untuk rebase dengan benar ke cabang jarak jauh
  • itu tidak membersihkan cabang yang dihapus di repo jarak jauh

Masalah-masalah ini dijelaskan secara lebih rinci di bawah ini.

Sejarah Nonlinier

Secara default, git pullperintah ini setara dengan menjalankan git fetchdiikuti oleh git merge @{u}. Jika ada komit yang tidak di-cush dalam repositori lokal, bagian gabungan dari git pullmembuat komit gabungan.

Tidak ada yang secara inheren buruk tentang gabungan yang dilakukan, tetapi mereka bisa berbahaya dan harus diperlakukan dengan hormat:

  • Komitmen gabungan secara inheren sulit untuk diperiksa. Untuk memahami apa yang dilakukan penggabungan, Anda harus memahami perbedaan untuk semua orang tua. Perbedaan konvensional tidak memberikan informasi multi-dimensi ini dengan baik. Sebaliknya, serangkaian komitmen normal mudah ditinjau.
  • Menggabungkan resolusi konflik itu rumit, dan kesalahan sering kali tidak terdeteksi untuk waktu yang lama karena menggabungkan komitmen sulit untuk ditinjau.
  • Penggabungan dapat secara diam-diam menggantikan efek dari komitmen reguler. Kode tidak lagi merupakan jumlah dari komitmen tambahan, yang mengarah pada kesalahpahaman tentang apa yang sebenarnya berubah.
  • Komitmen gabungan dapat mengganggu beberapa skema integrasi berkelanjutan (mis., Buat otomatis hanya jalur orang tua pertama di bawah konvensi yang diasumsikan bahwa orang tua kedua menunjukkan pekerjaan tidak lengkap yang sedang berlangsung).

Tentu saja ada waktu dan tempat untuk penggabungan, tetapi memahami kapan penggabungan harus dan tidak boleh digunakan dapat meningkatkan kegunaan repositori Anda.

Perhatikan bahwa tujuan Git adalah membuatnya mudah untuk berbagi dan mengonsumsi evolusi basis kode, bukan untuk mencatat sejarah dengan tepat seperti saat dibuka. (Jika Anda tidak setuju, pertimbangkan rebaseperintah dan mengapa itu dibuat.) Penggabungan yang dilakukan oleh git pulltidak menyampaikan semantik yang bermanfaat kepada orang lain — mereka hanya mengatakan bahwa ada orang lain yang mendorong ke repositori sebelum Anda selesai dengan perubahan Anda. Mengapa penggabungan tersebut dilakukan jika tidak berarti bagi orang lain dan bisa berbahaya?

Dimungkinkan untuk mengonfigurasi git pullrebase alih-alih bergabung, tetapi ini juga memiliki masalah (dibahas nanti). Sebagai gantinya, git pullharus dikonfigurasi untuk hanya melakukan penggabungan maju-cepat.

Reintroduksi Komitmen Rebased-out

Misalkan seseorang me-rebet cabang dan memaksa mendorongnya. Ini umumnya tidak boleh terjadi, tetapi kadang-kadang diperlukan (misalnya, untuk menghapus file log 50GiB yang sengaja dikomit dan didorong). Penggabungan yang dilakukan oleh git pullakan menggabungkan versi baru dari cabang hulu ke versi lama yang masih ada di repositori lokal Anda. Jika Anda mendorong hasilnya, garpu dan garpu nada akan mulai menghampiri Anda.

Beberapa mungkin berpendapat bahwa masalah sebenarnya adalah pembaruan paksa. Ya, pada umumnya disarankan untuk menghindari dorongan kekuatan kapan pun memungkinkan, tetapi kadang-kadang tidak dapat dihindari. Pengembang harus siap menghadapi pembaruan kekuatan, karena hal itu kadang-kadang terjadi. Ini berarti tidak secara membuta menggabungkan komitmen lama dengan yang biasa git pull.

Modifikasi Direktori Kerja Kejutan

Tidak ada cara untuk memprediksi seperti apa direktori atau indeks kerja sampai git pullselesai. Mungkin ada gabungan konflik yang harus Anda selesaikan sebelum Anda bisa melakukan hal lain, itu mungkin memperkenalkan file log 50GiB di direktori kerja Anda karena seseorang secara tidak sengaja mendorongnya, itu mungkin mengubah nama direktori yang sedang Anda kerjakan, dll.

git remote update -p(atau git fetch --all -p) memungkinkan Anda untuk melihat komitmen orang lain sebelum Anda memutuskan untuk bergabung atau rebase, memungkinkan Anda untuk membuat rencana sebelum mengambil tindakan.

Kesulitan Meninjau Komitmen Orang Lain

Misalkan Anda berada di tengah-tengah membuat beberapa perubahan dan orang lain ingin Anda meninjau beberapa komitmen yang baru saja mereka dorong. git pullOperasi gabungan (atau rebase) memodifikasi direktori dan indeks kerja, yang berarti direktori dan indeks kerja Anda harus bersih.

Anda bisa menggunakan git stashdan kemudian git pull, tetapi apa yang Anda lakukan ketika Anda selesai meninjau? Untuk kembali ke tempat Anda sebelumnya, Anda harus membatalkan gabungan yang dibuat oleh git pulldan menerapkan simpanan.

git remote update -p(atau git fetch --all -p) tidak mengubah direktori atau indeks kerja, jadi aman untuk dijalankan kapan saja — bahkan jika Anda telah melakukan dan / atau mengubah unstaged. Anda dapat menjeda apa yang Anda lakukan dan meninjau komit orang lain tanpa khawatir tentang menyembunyikan atau menyelesaikan komit yang sedang Anda kerjakan. git pulltidak memberi Anda fleksibilitas itu.

Rebasing ke Cabang Remote

Pola penggunaan Git yang umum adalah melakukan git pulluntuk membawa perubahan terbaru diikuti oleh git rebase @{u}untuk menghilangkan komit gabungan yang git pulldiperkenalkan. Ini cukup umum bahwa Git memiliki beberapa pilihan konfigurasi untuk mengurangi kedua langkah ini untuk satu langkah dengan mengatakan git pulluntuk melakukan rebase bukannya gabungan (lihat branch.<branch>.rebase, branch.autosetuprebasedan pull.rebasepilihan).

Sayangnya, jika Anda memiliki komit gabungan yang tidak dicuri yang ingin Anda pertahankan (mis., Komit yang menggabungkan cabang fitur yang didorong ke dalamnya master), baik tarikan rebase ( git pulldengan branch.<branch>.rebaseset ke true) maupun tarikan gabungan ( git pullperilaku default ) diikuti oleh rebase akan bekerja. Ini karena git rebasemenghilangkan gabungan (itu linierkan DAG) tanpa --preserve-mergesopsi. Operasi rebase-pull tidak dapat dikonfigurasikan untuk mempertahankan penggabungan, dan tarikan gabungan diikuti oleh git rebase -p @{u}tidak akan menghilangkan gabungan yang disebabkan oleh tarikan gabungan. Pembaruan: Git v1.8.5 ditambahkan git pull --rebase=preservedan git config pull.rebase preserve. Penyebab ini git pullharus dilakukan git rebase --preserve-mergessetelah mengambil komitmen hulu. (Terima kasih kepada funkaster untuk informasi terkini!)

Membersihkan Cabang yang Dihapus

git pulltidak memangkas cabang pelacakan jarak jauh yang terkait dengan cabang yang dihapus dari repositori jarak jauh. Misalnya, jika seseorang menghapus cabang foodari repo jarak jauh, Anda masih akan melihat origin/foo.

Hal ini menyebabkan pengguna secara tidak sengaja menghidupkan kembali cabang yang mati karena mereka pikir mereka masih aktif.

Alternatif yang Lebih Baik: Gunakan git upsebagai gantigit pull

Alih-alih git pull, saya sarankan membuat dan menggunakan git upalias berikut :

git config --global alias.up '!git remote update -p; git merge --ff-only @{u}'

Alias ​​ini mengunduh semua komit terbaru dari semua cabang hulu (memangkas cabang yang mati) dan mencoba memajukan cepat cabang lokal ke komit terbaru di cabang hulu. Jika berhasil, maka tidak ada komitmen lokal, sehingga tidak ada risiko menggabungkan konflik. Percepat-maju akan gagal jika ada komitmen lokal (tidak dicuci), memberi Anda kesempatan untuk meninjau komitmen hulu sebelum mengambil tindakan.

Ini masih memodifikasi direktori kerja Anda dengan cara yang tidak dapat diprediksi, tetapi hanya jika Anda tidak memiliki perubahan lokal. Tidak seperti itu git pull, git uptidak akan pernah menjatuhkan Anda ke prompt yang mengharapkan Anda untuk memperbaiki konflik gabungan.

Pilihan lain: git pull --ff-only --all -p

Berikut ini adalah alternatif dari git upalias di atas :

git config --global alias.up 'pull --ff-only --all -p'

Versi ini git upmemiliki perilaku yang sama dengan git upalias sebelumnya , kecuali:

  • pesan kesalahannya sedikit lebih samar jika cabang lokal Anda tidak dikonfigurasikan dengan cabang upstream
  • itu bergantung pada fitur tidak berdokumen ( -pargumen, yang diteruskan ke fetch) yang dapat berubah di versi Git yang akan datang

Jika Anda menjalankan Git 2.0 atau lebih baru

Dengan Git 2.0 dan yang lebih baru, Anda dapat mengonfigurasi git pulluntuk hanya melakukan penggabungan maju-cepat secara default:

git config --global pull.ff only

Ini menyebabkan git pulluntuk bertindak seperti git pull --ff-only, tetapi masih tidak mengambil semua komitmen upstream atau membersihkan origin/*cabang lama jadi saya masih lebih suka git up.

Richard Hansen
sumber
6
@brianz: git remote update -psetara dengan git fetch --all -p. Saya terbiasa mengetik git remote update -pkarena sekali waktu fetchtidak memiliki -popsi. Mengenai pimpinan !, lihat deskripsi alias.*di git help config. Dikatakan, "Jika ekspansi alias diawali dengan tanda seru, itu akan diperlakukan sebagai perintah shell."
Richard Hansen
13
Git 2.0 menambahkan pull.ffkonfigurasi yang tampaknya mencapai hal yang sama, tanpa alias.
Danny Thomas
51
Beberapa alasan terdengar seperti "tarik dapat menyebabkan masalah ketika orang lain melakukan hal-hal gila". Tidak, ini hal-hal gila seperti rebending komit dari repo hulu yang menyebabkan masalah. Rebase IMO hanya aman ketika Anda melakukannya secara lokal dengan komit yang belum didorong. Seperti, misalnya, ketika Anda menarik sebelum mendorong, rebitting commit lokal membantu menjaga sejarah Anda tetap linier (meskipun sejarah linier bukanlah masalah besar). Namun, git upterdengar seperti alternatif yang menarik.
mcv
16
Sebagian besar poin Anda adalah karena Anda melakukan sesuatu yang salah: Anda mencoba meninjau kode di cabang kerja Anda sendiri . Itu bukan ide yang baik, cukup buat cabang baru, tarik --rebase = pertahankan dan kemudian lemparkan cabang itu (atau gabungkan jika Anda mau).
funkaster
5
@ funkaster's point di sini sangat masuk akal, terutama ulang: "Kesulitan Meninjau Komitmen Orang Lain". Ini bukan aliran ulasan yang digunakan sebagian besar pengguna Git, ini adalah sesuatu yang belum pernah saya lihat direkomendasikan di mana pun dan itu adalah penyebab semua pekerjaan tambahan yang tidak perlu dijelaskan di bawah judul, bukan git pull.
Ben Regenspan
195

Jawaban saya, ditarik dari diskusi yang muncul di HackerNews:

Saya merasa tergoda untuk hanya menjawab pertanyaan dengan menggunakan Hukum Headline Betteridge: Mengapa git pulldianggap berbahaya? Bukan itu.

  • Nonlinier pada dasarnya tidak buruk. Jika mereka mewakili sejarah yang sebenarnya, mereka baik-baik saja.
  • Reintroduksi yang tidak disengaja dari komitmen yang diubah menjadi hulu adalah hasil dari penulisan ulang sejarah yang salah di hulu. Anda tidak dapat menulis ulang riwayat ketika sejarah direplikasi bersama beberapa repo.
  • Memodifikasi direktori kerja adalah hasil yang diharapkan; kegunaan diperdebatkan, yaitu dalam menghadapi perilaku hg / monoton / darcs / other_dvcs_predating_git, tetapi sekali lagi tidak buruk secara intrinsik.
  • Menjeda untuk meninjau pekerjaan orang lain diperlukan untuk penggabungan, dan sekali lagi merupakan perilaku yang diharapkan pada git pull. Jika Anda tidak ingin bergabung, Anda harus menggunakan git fetch. Sekali lagi, ini adalah keistimewaan git dibandingkan dengan DVD populer sebelumnya, tetapi diharapkan perilaku dan tidak buruk secara intrinsik.
  • Membuatnya sulit untuk rebase terhadap cabang yang jauh adalah baik. Jangan menulis ulang sejarah kecuali Anda benar-benar perlu. Saya tidak bisa seumur hidup saya memahami pencarian sejarah linear (palsu) ini
  • Tidak membersihkan cabang itu bagus. Setiap repo tahu apa yang ingin dipegang. Git tidak memiliki gagasan tentang hubungan tuan-budak.
Sérgio Carvalho
sumber
13
Saya setuju. Tidak ada yang secara inheren berbahaya git pull. Namun, itu mungkin bertentangan dengan beberapa praktik berbahaya, seperti ingin menulis ulang sejarah lebih dari yang diperlukan. Tetapi git fleksibel, jadi jika Anda ingin menggunakannya dengan cara yang berbeda, tentu saja lakukanlah. Tapi itu karena kamu (well, @Richard Hansen) ingin melakukan sesuatu yang tidak biasa di git, dan bukan karena git pullberbahaya.
mcv
28
Sangat setuju. Orang mengadvokasi git rebasedan mempertimbangkan git pullberbahaya? Betulkah?
Victor Moroz
10
Akan menyenangkan melihat seseorang membuat grafik, dengan moralitas sebagai poros Anda, dan mengklasifikasikan perintah git sebagai baik, buruk, atau di suatu tempat di antaranya. Bagan ini akan berbeda antara pengembang, meskipun akan mengatakan banyak tentang penggunaan satu git.
michaelt
5
Masalah saya dengan git pulltanpa --rebaseopsi adalah arah penggabungan yang dibuatnya. Saat Anda melihat perbedaan, semua perubahan dalam penggabungan sekarang menjadi milik orang yang menarik, bukan orang yang membuat perubahan. Saya suka alur kerja di mana penggabungan dicadangkan untuk dua cabang terpisah (A -> B) sehingga komit gabungan jelas apa yang diperkenalkan, dan rebasing disediakan untuk mendapatkan pembaruan di cabang yang sama (jarak jauh A -> lokal A )
Craig Kochis
4
Jadi, apa untungnya Anda mengetahui jika seseorang melakukan penarikan hanya beberapa detik sebelum orang lain atau sebaliknya? Saya pikir ini hanya kebisingan dan hanya mengaburkan sejarah yang benar-benar relevan. Ini bahkan mengurangi nilai sejarah. Sejarah yang baik harus a) bersih dan b) sebenarnya memiliki sejarah penting.
David Ongaro
26

Tidak dianggap berbahaya jika Anda menggunakan Git dengan benar. Saya melihat bagaimana ini memengaruhi Anda secara negatif karena use case Anda, tetapi Anda dapat menghindari masalah hanya dengan tidak mengubah riwayat bersama.

Perburuan Burdick
sumber
10
Untuk menguraikan ini: Jika semua orang bekerja di cabang mereka sendiri (yang menurut saya adalah cara yang tepat untuk menggunakan git), git pullbukan masalah apa pun. Bercabang di git murah.
AlexQueue
18

Klaim jawaban yang diterima

Operasi rebase-pull tidak dapat dikonfigurasikan untuk mempertahankan penggabungan

tetapi pada Git 1.8.5 , yang setelah tanggal jawaban itu, Anda bisa melakukannya

git pull --rebase=preserve

atau

git config --global pull.rebase preserve

atau

git config branch.<name>.rebase preserve

The docs mengatakan

Ketika preserve,juga diteruskan --preserve-mergeske 'git rebase' sehingga komitmen gabungan yang dilakukan secara lokal tidak akan diratakan dengan menjalankan 'git pull'.

Diskusi sebelumnya ini memiliki informasi dan diagram yang lebih terperinci: git pull --rebase --preserve-merge . Ini juga menjelaskan mengapa git pull --rebase=preservetidak sama dengan git pull --rebase --preserve-merges, yang tidak melakukan hal yang benar.

Diskusi sebelumnya lainnya ini menjelaskan apa yang sebenarnya dilakukan oleh varian rebate-merge dari rebase, dan bagaimana itu jauh lebih kompleks daripada rebase biasa: Apa sebenarnya yang dilakukan "rebase --preserve-merge" git (dan mengapa?)

Marc Liyanage
sumber
0

Jika Anda pergi ke git repositori tua git up alias mereka menyarankan berbeda. https://github.com/aanand/git-up

git config --global alias.up 'pull --rebase --autostash'

Ini berfungsi sempurna untuk saya.

Nathan Redblur
sumber