Apa yang dimaksud Linus Torvalds ketika dia mengatakan bahwa Git “tidak pernah” melacak file?

284

Mengutip Linus Torvalds ketika ditanya berapa banyak file yang bisa ditangani Git selama Tech Talk-nya di Google pada 2007 (43:09):

... Git melacak konten Anda. Tidak pernah melacak satu file pun. Anda tidak dapat melacak file di Git. Yang dapat Anda lakukan adalah Anda dapat melacak proyek yang memiliki satu file, tetapi jika proyek Anda memiliki satu file, tentu lakukan itu dan Anda bisa melakukannya, tetapi jika Anda melacak 10.000 file, Git tidak pernah melihat itu sebagai file individual. Git menganggap semuanya sebagai konten lengkap. Semua sejarah di Git didasarkan pada sejarah seluruh proyek ...

(Transkrip di sini .)

Namun, ketika Anda menyelam ke buku Git , hal pertama yang Anda diberitahu adalah bahwa file dalam Git dapat berupa dilacak atau tidak terlacak . Selain itu, bagi saya sepertinya seluruh pengalaman Git diarahkan untuk versi file. Saat menggunakan git diffatau git statusoutput disajikan berdasarkan per file. Saat menggunakan git addAnda juga bisa memilih berdasarkan per file. Anda bahkan dapat meninjau riwayat berdasarkan file dan cepat kilat.

Bagaimana seharusnya pernyataan ini ditafsirkan? Dalam hal pelacakan file, bagaimana Git berbeda dari sistem kontrol sumber lain, seperti CVS?

Simón Ramírez Amaya
sumber
20
reddit.com/r/git/comments/5xmrkv/what_is_a_snapshot_in_git - "Untuk di mana Anda berada saat ini, saya menduga apa yang lebih penting untuk disadari adalah bahwa ada perbedaan antara bagaimana Git menyajikan file kepada pengguna dan bagaimana berurusan dengan mereka secara internal . Seperti yang disajikan kepada pengguna, snapshot berisi file lengkap, bukan hanya beda. Tetapi secara internal, ya, Git menggunakan diff untuk menghasilkan paket file yang secara efisien menyimpan revisi. " (Ini sangat kontras dengan, mis. Subversion.)
user2864740
5
Git tidak melacak file, ia melacak perubahan . Sebagian besar sistem kontrol versi melacak file. Sebagai contoh bagaimana / mengapa ini bisa berarti, cobalah untuk memeriksa di direktori kosong untuk git (spolier: Anda tidak bisa, karena itu adalah setset "kosong").
Elliott Frisch
12
@ElliottFrisch Kedengarannya tidak benar. Deskripsi Anda lebih dekat dengan apa misalnya darcs tidak. Git menyimpan snapshot, bukan perubahan.
melpomene
4
Saya pikir maksudnya Git tidak melacak file secara langsung. File menyertakan nama dan kontennya. Git melacak konten sebagai gumpalan. Diberikan gumpalan saja, Anda tidak bisa mengatakan apa nama file yang sesuai. Ini bisa berupa konten beberapa file dengan nama berbeda di bawah jalur yang berbeda. Binding antara nama jalur dan gumpalan dijelaskan dalam objek pohon.
ElpieKay
3
Terkait: Randal Schwartz ' tindak lanjut pembicaraan Linus' (juga Google Tech talk) - "... Apa Git benar-benar semua tentang ... Linus mengatakan apa Git TIDAK".
Peter Mortensen

Jawaban:

316

Di CVS, riwayat dilacak berdasarkan per file. Cabang mungkin terdiri dari berbagai file dengan berbagai revisinya sendiri, masing-masing dengan nomor versinya sendiri. CVS didasarkan pada RCS ( Revision Control System ), yang melacak file individual dengan cara yang sama.

Di sisi lain, Git mengambil snapshot dari keadaan keseluruhan proyek. File tidak dilacak dan diversi secara independen; revisi dalam repositori mengacu pada keadaan keseluruhan proyek, bukan satu file.

Ketika Git merujuk pada pelacakan file, itu berarti bahwa itu harus dimasukkan dalam sejarah proyek. Pembicaraan Linus tidak mengacu pada pelacakan file dalam konteks Git, tetapi membandingkan model CVS dan RCS dengan model berbasis snapshot yang digunakan dalam Git.

bk2204
sumber
4
Anda dapat menambahkan bahwa inilah sebabnya di CVS dan Subversion, Anda dapat menggunakan tag seperti $Id$dalam file. Hal yang sama tidak berfungsi di git, karena desainnya berbeda.
gerrit
58
Dan konten tidak terikat pada file seperti yang Anda harapkan. Coba pindahkan 80% kode dari satu file ke file lainnya. Git secara otomatis mendeteksi pemindahan file + 20% perubahan, bahkan ketika Anda baru saja memindahkan kode di file yang sudah ada.
allo
13
@allo Sebagai efek sampingnya, git dapat melakukan satu hal yang tidak dapat dilakukan oleh yang lain: ketika dua file digabungkan dan Anda menggunakan "git menyalahkan -C", git dapat melihat ke bawah kedua sejarah. Dalam pelacakan berbasis file, Anda harus memilih file asli mana yang asli asli, dan baris lainnya semuanya tampak baru.
Izkata
1
@allo, Izkata - Dan entitas kueri yang menyelesaikan semua ini dengan menganalisis konten repo pada waktu kueri (komit riwayat dan perbedaan antara pohon dan blob yang dirujuk), daripada meminta entitas yang berkomitmen dan pengguna manusianya untuk menentukan atau mensintesis dengan benar informasi ini pada waktu yang ditentukan - atau pengembang alat repo untuk merancang & mengimplementasikan kemampuan ini dan skema metadata yang sesuai sebelum alat tersebut digunakan. Torvalds berpendapat bahwa analisis tersebut hanya akan menjadi lebih baik dari waktu ke waktu, dan semua riwayat setiap repo git sejak hari pertama akan mendapat manfaat.
Jeremy
1
@allo Yep, dan untuk memalu fakta bahwa git tidak bekerja pada level file, Anda bahkan tidak perlu melakukan semua perubahan dalam file sekaligus; Anda dapat melakukan rentang garis sewenang-wenang sambil meninggalkan perubahan lain di file di luar komit. Tentu saja UI untuk itu tidak sesederhana sehingga sebagian besar tidak melakukannya, tetapi jarang ada kegunaannya.
Alvin Thompson
103

Saya setuju dengan brian m. jawaban carlson : Linus memang membedakan, setidaknya sebagian, antara sistem kontrol versi berorientasi file dan komit. Tapi saya pikir ada lebih dari itu.

Dalam buku saya , yang macet dan mungkin tidak akan pernah selesai, saya mencoba untuk membuat taksonomi untuk sistem kontrol versi. Dalam taksonomi saya, istilah untuk apa yang kami minati di sini adalah atomicity dari sistem kontrol versi. Lihat apa yang saat ini halaman 22. Ketika VCS memiliki atomisitas tingkat file, sebenarnya ada sejarah untuk setiap file. VCS harus mengingat nama file dan apa yang terjadi padanya di setiap titik.

Git tidak melakukan itu. Git hanya memiliki sejarah commit - komit adalah unit atomisitasnya, dan sejarah adalah himpunan commit dalam repositori. Apa yang komit ingat adalah data — seluruh pohon penuh dengan nama file dan konten yang menyertai masing-masing file tersebut — ditambah beberapa metadata: misalnya, siapa yang membuat komit, kapan, dan mengapa, dan ID hash Git internal dari orang tua komit komit. (Ini adalah orang tua ini, dan grafik acycling yang diarahkan dibentuk dengan membaca semua komit dan orang tua mereka, yang merupakan sejarah dalam repositori.)

Perhatikan bahwa VCS dapat berorientasi komit, namun masih menyimpan data file per file. Itu detail implementasi, meskipun terkadang yang penting, dan Git juga tidak melakukannya. Sebagai gantinya, setiap komit merekam pohon , dengan objek pohon yang menyandikan nama file , mode (yaitu, apakah file ini dapat dieksekusi atau tidak?), Dan penunjuk ke konten file yang sebenarnya . Konten itu sendiri disimpan secara independen, dalam objek gumpalan . Seperti objek komit, gumpalan mendapat ID hash yang unik untuk kontennya — tetapi tidak seperti komit, yang hanya dapat muncul sekali, gumpalan itu dapat muncul dalam banyak komit. Jadi konten file yang mendasarinya di Git disimpan secara langsung sebagai gumpalan, dan kemudian secara tidak langsung di objek pohon yang ID hashnya direkam (langsung atau tidak langsung) di objek komit.

Ketika Anda meminta Git untuk menunjukkan kepada Anda riwayat file menggunakan:

git log [--follow] [starting-point] [--] path/to/file

Apa yang sebenarnya dilakukan Git adalah menjalankan sejarah commit , yang merupakan satu-satunya sejarah yang dimiliki Git, tetapi tidak menunjukkan kepada Anda salah satu dari komitmen ini kecuali:

  • komit adalah komit non-gabungan, dan
  • induk dari komit juga memiliki file, tetapi konten di induk berbeda, atau induk dari komit tidak memiliki file sama sekali

(tetapi beberapa kondisi ini dapat dimodifikasi melalui git logopsi tambahan , dan ada yang sangat sulit untuk menggambarkan efek samping yang disebut Penyederhanaan Sejarah yang membuat Git menghilangkan beberapa komitmen dari berjalan sejarah sepenuhnya). Riwayat file yang Anda lihat di sini tidak benar-benar ada di repositori, dalam beberapa hal: alih-alih, itu hanya subset sintetis dari sejarah nyata. Anda akan mendapatkan "riwayat file" yang berbeda jika menggunakan git logopsi yang berbeda !

torek
sumber
Hal lain untuk ditambahkan adalah ini memungkinkan Git untuk melakukan hal-hal seperti klon dangkal. Itu hanya perlu mengambil komit kepala dan semua gumpalan yang dimaksud. Tidak perlu membuat ulang file dengan menerapkan set perubahan.
Wes Toleman
@WesToleman: itu pasti membuatnya lebih mudah. Mercurial menyimpan delta, dengan pengaturan ulang sesekali, dan sementara orang-orang Mercurial berniat untuk menambahkan klon dangkal di sana (yang mungkin karena ide "reset"), mereka belum benar-benar melakukannya (karena ini lebih merupakan tantangan teknis).
torek
@torek Saya ragu dengan deskripsi Anda tentang Git yang menjawab permintaan riwayat file tetapi saya pikir itu layak untuk pertanyaan yang tepat: stackoverflow.com/questions/55616349/…
Simón Ramírez Amaya
@torek Terima kasih atas tautan ke buku Anda, saya tidak melihat yang lain seperti itu.
gnarledRoot
17

Bit yang membingungkan ada di sini:

Git tidak pernah melihat itu sebagai file individual. Git menganggap semuanya sebagai konten lengkap.

Git sering menggunakan hash 160 bit sebagai ganti objek dalam repo sendiri. Pohon file pada dasarnya adalah daftar nama dan hash yang terkait dengan konten masing-masing (ditambah beberapa metadata).

Tetapi hash 160 bit secara unik mengidentifikasi konten (dalam jagat basis data git). Jadi pohon dengan hash sebagai konten termasuk konten dalam kondisinya.

Jika Anda mengubah status konten file, hashnya berubah. Tetapi jika hashnya berubah, hash yang terkait dengan konten nama file juga berubah. Yang pada gilirannya mengubah hash dari "direktori tree".

Ketika database git menyimpan pohon direktori, pohon direktori itu menyiratkan dan mencakup semua konten dari semua subdirektori dan semua file di dalamnya .

Ini diatur dalam struktur pohon dengan (tidak dapat diubah, dapat digunakan kembali) pointer ke gumpalan atau pohon lain, tetapi secara logis itu adalah snapshot tunggal dari seluruh konten seluruh pohon. The representasi dalam database git tidak isi data yang datar, tapi secara logis itu adalah semua data dan tidak ada lagi.

Jika Anda membuat serial pohon ke sistem file, menghapus semua folder .git, dan menyuruh git untuk menambahkan pohon kembali ke dalam database-nya, Anda akan berakhir dengan menambahkan apa-apa ke database - elemen sudah ada di sana.

Mungkin membantu untuk memikirkan hash git sebagai referensi penghitung pointer ke data yang tidak dapat diubah.

Jika Anda membangun aplikasi di sekitar itu, sebuah dokumen adalah sekelompok halaman, yang memiliki lapisan, yang memiliki grup, yang memiliki objek.

Saat Anda ingin mengubah objek, Anda harus membuat grup yang benar-benar baru untuknya. Jika Anda ingin mengubah grup, Anda harus membuat layer baru, yang membutuhkan halaman baru, yang membutuhkan dokumen baru.

Setiap kali Anda mengubah satu objek, itu memunculkan dokumen baru. Dokumen lama terus ada. Dokumen baru dan lama membagikan sebagian besar konten mereka - mereka memiliki halaman yang sama (kecuali 1). Satu halaman itu memiliki layer yang sama (kecuali 1). Lapisan itu memiliki kelompok yang sama (kecuali 1). Grup itu memiliki objek yang sama (kecuali 1).

Dan dengan cara yang sama, maksud saya secara logis salinan, tetapi implementasi-bijaksana itu hanyalah referensi dihitung pointer ke objek abadi yang sama.

Repositori git sangat mirip.

Ini berarti bahwa git changeset yang diberikan berisi pesan komitnya (sebagai kode hash), berisi pohon kerjanya, dan berisi perubahan induknya.

Perubahan orangtua tersebut berisi perubahan orangtua mereka, sepanjang perjalanan kembali.

Bagian dari git repo yang berisi sejarah adalah rantai perubahan itu. Rantai perubahan itu pada tingkat di atas pohon "direktori" - dari pohon "direktori", Anda tidak dapat secara unik mencapai set perubahan dan rantai perubahan.

Untuk mengetahui apa yang terjadi pada suatu file, Anda mulai dengan file itu di changeset. Changeet itu memiliki sejarah. Seringkali dalam riwayat itu, file bernama yang sama ada, kadang-kadang dengan konten yang sama. Jika kontennya sama, tidak ada perubahan pada file. Jika berbeda, ada perubahan, dan pekerjaan harus dilakukan untuk mengetahui apa.

Terkadang file hilang; tetapi, pohon "direktori" mungkin memiliki file lain dengan konten yang sama (kode hash yang sama), jadi kami dapat melacaknya seperti itu (perhatikan; ini sebabnya Anda ingin sebuah komit untuk memindahkan file terpisah dari komit ke -edit). Atau nama file yang sama, dan setelah memeriksa file tersebut cukup mirip.

Jadi git dapat menambal bersama "file history".

Tetapi riwayat file ini berasal dari penguraian efisien "seluruh perubahan", bukan dari tautan dari satu versi file ke yang lain.

Yakk - Adam Nevraumont
sumber
12

"git tidak melacak file" pada dasarnya berarti komit git terdiri dari snapshot pohon file yang menghubungkan lintasan di pohon ke "gumpalan" dan grafik komit yang melacak sejarah komit . Segala sesuatu yang lain direkonstruksi saat itu dengan perintah seperti "git log" dan "git menyalahkan". Rekonstruksi ini dapat dikatakan melalui berbagai opsi seberapa sulit seharusnya mencari perubahan berbasis file. Heuristik default dapat menentukan kapan gumpalan perubahan terjadi di pohon file tanpa perubahan, atau ketika file dikaitkan dengan gumpalan yang berbeda dari sebelumnya. Mekanisme kompresi yang digunakan Git tidak terlalu peduli tentang batasan gumpalan / file. Jika konten sudah ada di suatu tempat, ini akan menjaga pertumbuhan repositori kecil tanpa mengaitkan berbagai gumpalan.

Nah, itu repositori. Git juga memiliki pohon yang berfungsi, dan di pohon yang berfungsi ini ada file yang dilacak dan tidak terlacak. Hanya file yang dilacak yang direkam dalam indeks (staging area? Cache?) Dan hanya apa yang dilacak yang membuatnya masuk ke dalam repositori.

Indeks berorientasi file dan ada beberapa perintah berorientasi file untuk memanipulasinya. Tetapi apa yang berakhir di repositori hanyalah komit dalam bentuk snapshot pohon file dan data gumpalan terkait dan leluhur komit.

Karena Git tidak melacak histori dan penggantian nama file dan efisiensinya tidak bergantung padanya, kadang-kadang Anda harus mencoba beberapa kali dengan opsi yang berbeda sampai Git menghasilkan histori / diff / blames yang Anda minati untuk sejarah non-sepele.

Itu berbeda dengan sistem seperti Subversion yang merekam daripada merekonstruksi sejarah. Jika tidak ada dalam catatan, Anda tidak bisa mendengarnya.

Saya benar-benar membangun penginstal diferensial pada suatu waktu yang baru saja membandingkan pohon rilis dengan memeriksa mereka ke Git dan kemudian menghasilkan skrip yang menduplikasi efeknya. Karena kadang-kadang seluruh pohon dipindahkan, ini menghasilkan installer diferensial yang jauh lebih kecil daripada menimpa / menghapus semua yang akan dihasilkan.


sumber
7

Git tidak melacak file secara langsung, tetapi melacak snapshot dari repositori, dan snapshot ini terdiri dari file.

Ini cara untuk melihatnya.

Dalam sistem kontrol versi lainnya (SVN, Rational ClearCase), Anda dapat mengklik kanan pada file dan mendapatkan riwayat perubahannya .

Di Git, tidak ada perintah langsung yang melakukan ini. Lihat pertanyaan ini . Anda akan terkejut dengan banyaknya jawaban yang berbeda. Tidak ada satu jawaban sederhana karena Git tidak hanya melacak file , tidak seperti SVN atau ClearCase yang melakukannya.

Double Vision Stout Fat Heavy
sumber
5
Saya pikir saya mendapatkan apa yang Anda coba katakan, tetapi "Dalam Git, tidak ada perintah langsung yang melakukan ini" secara langsung bertentangan dengan jawaban atas pertanyaan yang Anda tautkan. Meskipun benar bahwa versi terjadi pada tingkat seluruh repositori, biasanya ada banyak cara untuk mencapai apa pun di Git, jadi memiliki banyak perintah untuk menunjukkan riwayat file tidak banyak bukti.
Joe Lee-Moyet
Saya membaca sekilas beberapa jawaban pertama dari pertanyaan yang Anda tautkan dan semuanya menggunakan git logatau beberapa program yang dibangun di atasnya (atau beberapa alias yang melakukan hal yang sama). Tetapi bahkan jika ada banyak cara berbeda, seperti yang dikatakan Joe, itu juga berlaku untuk memperlihatkan sejarah cabang. (Juga git log -p <file>dibangun dan melakukan hal itu)
Voo
Apakah Anda yakin bahwa SVN secara internal menyimpan perubahan per file? Saya belum menggunakannya dalam beberapa waktu, tapi saya samar-samar ingat memiliki file bernama id versi, daripada refleksi struktur file proyek.
Artur Biesiadowski
3

Pelacakan "konten", kebetulan, adalah apa yang menyebabkan tidak melacak direktori kosong.
Itu sebabnya, jika Anda git file terakhir folder, folder itu sendiri akan dihapus .

Itu tidak selalu terjadi, dan hanya Git 1.4 (Mei 2006) yang memberlakukan kebijakan "pelacakan konten" dengan komit 443f833 :

status git: lewati direktori kosong, dan tambahkan -u untuk menampilkan semua file yang tidak dilacak

Secara default, kami menggunakan --others --directoryuntuk menampilkan direktori yang tidak menarik (untuk mendapatkan perhatian pengguna) tanpa kontennya (untuk mengosongkan output).
Menampilkan direktori kosong tidak masuk akal, jadi sampaikan --no-empty-directoryketika kita melakukannya.

Memberi -u(atau --untracked) menonaktifkan kekacauan ini agar pengguna mendapatkan semua file yang tidak terlacak.

Itu digaungkan bertahun-tahun kemudian pada Januari 2011 dengan komit 8fe533 , Git v1.7.4:

Ini sesuai dengan filosofi UI umum: konten git track, bukan direktori kosong.

Sementara itu, dengan Git 1.4.3 (September 2006), Git mulai membatasi konten yang tidak terlacak ke folder yang tidak kosong, dengan komit 2074cb0 :

seharusnya tidak mencantumkan isi direktori yang sama sekali tidak dilacak, tetapi hanya nama direktori itu (ditambah trailing ' /').

Melacak konten adalah apa yang diizinkan oleh git, sejak awal (Git 1.4.4, Oktober 2006, melakukan cee7f24 ) menjadi lebih berkinerja:

Lebih penting lagi, struktur internalnya dirancang untuk mendukung pergerakan konten (alias cut-and-paste) lebih mudah dengan memungkinkan lebih dari satu jalur diambil dari komit yang sama.

Itu (konten pelacakan) juga merupakan hal yang ditambahkan git ke dalam API Git, dengan Git 1.5.0 (Desember 2006, komit 366bfcb )

buat 'git add' antarmuka yang ramah pengguna untuk indeks

Ini membawa kekuatan indeks di depan menggunakan model mental yang tepat tanpa berbicara tentang indeks sama sekali.
Lihat misalnya bagaimana semua diskusi teknis telah dievakuasi dari halaman manual git-add.

Setiap konten yang akan dikomit harus ditambahkan bersama.
Apakah konten itu berasal dari file baru atau file yang dimodifikasi tidak masalah.
Anda hanya perlu "menambahkan" itu, baik dengan git-add, atau dengan memberikan git-commit dengan -a(untuk file yang sudah dikenal saja tentunya).

Itulah yang git add --interactivedimungkinkan, dengan Git 1.5.0 yang sama ( komit 5cde71d )

Setelah membuat pilihan, jawab dengan baris kosong untuk menampilkan isi dari file pohon yang berfungsi untuk jalur yang dipilih dalam indeks.

Itu juga sebabnya, untuk menghapus semua konten dari direktori secara rekursif, Anda harus memberikan -ropsi, bukan hanya nama direktori sebagai <path>(masih Git 1.5.0, komit 9f95069 ).

Melihat konten file alih-alih file itu sendiri adalah apa yang memungkinkan skenario menggabungkan seperti yang dijelaskan dalam komit 1de70db (Git v2.18.0-rc0, April 2018)

Pertimbangkan penggabungan berikut dengan mengubah nama / menambahkan konflik:

  • sisi A: modifikasi foo, tambahkan yang tidak terkaitbar
  • sisi B: ganti nama foo->bar(tetapi jangan modifikasi mode atau konten)

Dalam hal ini, tiga-cara penggabungan dari foo asli, foo A, dan B barakan menghasilkan pathname diinginkan bardengan sama modus / isi yang A memiliki untuk foo.
Jadi, A memiliki mode dan konten yang tepat untuk file tersebut, dan ia memiliki pathname yang tepat (yaitu, bar).

Commit 37b65ce , Git v2.21.0-rc0, Desember 2018, baru-baru ini meningkatkan resolusi konflik bertabrakan.
Dan komit bbafc9c firther mengilustrasikan pentingnya mempertimbangkan konten file , dengan meningkatkan penanganan konflik rename / rename (2to1):

  • Alih-alih menyimpan file di collide_path~HEADdan collide_path~MERGE, file digabungkan dan direkam dua arah collide_path.
  • Alih-alih merekam versi file berganti nama yang ada di sisi yang diubah namanya di indeks (sehingga mengabaikan segala perubahan yang dilakukan pada file di sisi sejarah tanpa mengganti nama), kami melakukan penggabungan konten tiga arah pada nama yang diubah namanya. jalan, lalu simpan itu di tahap 2 atau tahap 3.
  • Perhatikan bahwa karena penggabungan konten untuk setiap penggantian nama mungkin memiliki konflik, dan kemudian kami harus menggabungkan kedua file yang diubah namanya, kami dapat berakhir dengan penanda konflik bersarang.
VONC
sumber