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 diff
atau git status
output disajikan berdasarkan per file. Saat menggunakan git add
Anda 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?
sumber
Jawaban:
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.
sumber
$Id$
dalam file. Hal yang sama tidak berfungsi di git, karena desainnya berbeda.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:
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:
(tetapi beberapa kondisi ini dapat dimodifikasi melalui
git log
opsi 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 menggunakangit log
opsi yang berbeda !sumber
Bit yang membingungkan ada di sini:
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.
sumber
"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
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.
sumber
git log
atau 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. (Jugagit log -p <file>
dibangun dan melakukan hal itu)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 :
Itu digaungkan bertahun-tahun kemudian pada Januari 2011 dengan komit 8fe533 , Git v1.7.4:
Sementara itu, dengan Git 1.4.3 (September 2006), Git mulai membatasi konten yang tidak terlacak ke folder yang tidak kosong, dengan komit 2074cb0 :
Melacak konten adalah apa yang diizinkan oleh git, sejak awal (Git 1.4.4, Oktober 2006, melakukan cee7f24 ) menjadi lebih berkinerja:
Itu (konten pelacakan) juga merupakan hal yang ditambahkan git ke dalam API Git, dengan Git 1.5.0 (Desember 2006, komit 366bfcb )
Itulah yang
git add --interactive
dimungkinkan, dengan Git 1.5.0 yang sama ( komit 5cde71d )Itu juga sebabnya, untuk menghapus semua konten dari direktori secara rekursif, Anda harus memberikan
-r
opsi, 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)
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):
sumber