簡單地說,Git 究竟是怎樣的一個系統呢?請注意接下來的內容非常重要,若你理解了 Git 的思想和基本工作原理,用起來就會知其所以然,游刃有余。在學習 Git 時,請盡量理清你對其它版本管理系統已有的認識,如 CVS、Subversion 或 Perforce, 這樣能幫助你使用工具時避免發生混淆。盡管 Git 用起來與其它的版本控制系統非常相似, 但它在對信息的存儲和認知方式上卻有很大差異,理解這些差異將有助于避免使用中的困惑。
Git 初始化代碼倉庫
執行完成了 git init 命令,究竟做了什么呢? 執行完成如下命令之后,我們可以得到下圖所示的內容,右側的就是 Git 為我們創建的代碼倉庫,其中包含了用于版本管理所需要的內容。#左邊執行 $mkdirgit-demo $cdgit-demo&&gitinit $rm-rf.git/hooks/*.sample #右邊執行 $watch-n1-dfind.
?tree.git .git ├──HEAD ├──config ├──description ├──hooks ├──info │└──exclude ├──objects │├──info │└──pack └──refs ├──heads └──tags
.git/config- 當前代碼倉庫本地的配置文件-
本地配置文件(.git/config)和全局配置文件(~/.gitconfig)
-
通過執行如下命令,可以將用戶配置記錄到本地代碼倉庫的配置文件中去
-
git config user.name "demo"
-
git config user.email "demo@demo.com"
?cat.git/config [core] repositoryformatversion=0 filemode=true bare=false logallrefupdates=true ignorecase=true precomposeunicode=true [user] name=demo email=demo@demo.com.git/objects- 當前代碼倉庫代碼的存儲位置
-
blob類型
-
commit類型
-
tree類型
#均無內容 ?ll.git/objects total0 drwxr-xr-x2escapestaff64BNov2320:39info drwxr-xr-x2escapestaff64BNov2320:39pack ?ll.git/objects/info ?ll.git/objects/pack
.git/info- 當前倉庫的排除等信息?cat./.git/info/exclude #gitls-files--others--exclude-from=.git/info/exclude #Linesthatstartwith'#'arecomments. #ForaprojectmostlyinC,thefollowingwouldbeagoodsetof #excludepatterns(uncommentthemifyouwanttousethem): #*.[oa] #*~.git/hooks- 當前代碼倉庫默認鉤子腳本
./.git/hooks/commit-msg.sample ./.git/hooks/pre-rebase.sample ./.git/hooks/pre-commit.sample ./.git/hooks/applypatch-msg.sample ./.git/hooks/fsmonitor-watchman.sample ./.git/hooks/pre-receive.sample ./.git/hooks/prepare-commit-msg.sample ./.git/hooks/post-update.sample ./.git/hooks/pre-merge-commit.sample ./.git/hooks/pre-applypatch.sample ./.git/hooks/pre-push.sample ./.git/hooks/update.sample
.git/HEAD- 當前代碼倉庫的分支指針?cat.git/HEAD ref:refs/heads/master
.git/refs- 當前代碼倉庫的頭指針#均無內容 ?ll.git/refs total0 drwxr-xr-x2escapestaff64BNov2320:39heads drwxr-xr-x2escapestaff64BNov2320:39tags ?ll.git/refs/heads ?ll.git/refs/tags
.git/description- 當前代碼倉庫的描述信息?cat.git/description Unnamedrepository;editthisfile'description'tonametherepository.
add 之后發生了什么
執行完成了 git add 命令,究竟做了什么呢? 執行完成如下命令之后,我們可以得到下圖所示的內容,我們發現右側新增了一個文件,但是 Git 目錄里面的內容絲毫沒有變化。這是因為,我們現在執行的修改默認是放在工作區的,而工作區里面的修改不歸 Git 目錄去管理。 而當我們執行 git status 命令的時候,Git 又可以識別出來現在工作區新增了一個文件,這里怎么做到的呢?——詳見[理解 blob 對象和 SHA1]部分 而當我們執行 git add 命令讓 Git 幫助我們管理文件的時候,發現右側新增了一個目錄和兩個文件,分別是 8d 目錄、index 和0e41.. 文件。#左邊執行 $echo"hellogit">helle.txt $gitstatus $gitaddhello.txt #右邊執行 $watch-n1-dfind.
#查看objects的文件類型 $gitcat-file-t8d0e41 blob #查看objects的文件內容 $gitcat-file-p8d0e41 hellogit #查看objects的文件大小 $gitcat-file-s8d0e41 10 #拼裝起來 blob10hellogit
現在我們就知道了,執行 git add 命令將文件從工作區添加到暫存區里面,Git 會把幫助我們生成一些 Git 的對象,它存儲的是文件的內容和文件類型并不存儲文件名稱。 為了驗證我們上述的說法,我們可以添加同樣的內容到另一個文件,然后進行提交,來觀察 .git 目錄的變化。我們發現,右側的 objects 目錄并沒有新增目錄和文件。這就可以證明,blob 類型的 object 只存儲的是文件的內容,如果兩個文件的內容一致的話,則只需要存儲一個 object 即可。 話說這里 object 為什么沒有存儲文件名稱呢?這里因為 SHA1 的 Hash 算法計算哈希的時候,本身就不包括文件名稱,所以取什么名稱都是無所謂的。那問題來了,就是文件名的信息都存儲到哪里去了呢?——詳見[理解 blob 對象和 SHA1]部分#左邊執行 $echo"hellogit">tmp.txt $gitaddtmp.txt #右邊執行 $watch-n1-dfind.
理解 blob 對象和 SHA1
了解 Git 的 blob 對象和 SHA1 之前的關系和對應計算! Hash 算法是把任意長度的輸入通過散列算法變化成固定長度的輸出,根據算法的不同,生成的長度也有所不同。 Hash 算法:-
MD5-128bit- 不安全 - 文件校驗
-
SHA1-160bit(40位)- 不安全 -Git存儲
-
SHA256-256bit- 安全 -Docker鏡像
-
SHA512-512bit- 安全
?echo"hellogit"|shasum d6a96ae3b442218a91512b9e1c57b9578b487a0b-
這里因為 Git 工具的計算方式,是使用類型 長度 內容的方式進行計算的。這里,我們算了下文件內容只有九位,但是這里是十位,這里因為內容里面有換行符的存在導致的。現在我們就可以使用 git cat-file 命令來拼裝 Git 工具存儲的完整內容了。?ls-lhhello.txt -rw-r--r--1escapestaff10BNov2321:12hello.txt ?echo"blob10hellogit"|shasum 8d0e41234f24b6da002d962a26c2495ea16a425f- #拼裝起來 blob10hellogit
?cat.git/objects/8d/0e41234f24b6da002d962a26c2495ea16a425f xKOR04`HWH,6A% ?ls-lh.git/objects/8d/0e41234f24b6da002d962a26c2495ea16a425f -r--r--r--1escapestaff26BNov2321:36.git/objects/8d/0e41234f24b6da002d962a26c2495ea16a425f ?file.git/objects/8d/0e41234f24b6da002d962a26c2495ea16a425f .git/objects/8d/0e41234f24b6da002d962a26c2495ea16a425f:VAXCOFFexecutablenotstripped-version16694
其實,我們這里也是可以通過 Python 代碼來獲取二進制 object 對象的內容的。importzlib contents=open('0e41234f24b6da002d962a26c2495ea16a425f','rb').read() zlib.decompress(contents)
聊聊工作區和暫存區
聊聊工作區和暫存區,以及文件如何在工作區和緩存區之間同步的問題。 之前的章節我們也聊到了,當我們執行 git status 命令的時候,Git 工具怎么知道我們有一個文件沒有追蹤,以及文件名的信息都存儲到哪里去了? 這一切的答案,都要從工作區和索引區講起。Git 根據其存儲的狀態不同,將對應狀態的“空間”分為工作區、暫存區(也可稱為索引區)和版本區三類。具體示例,可以參考下圖。
#左邊執行 $echo"file1">file1.txt $gitaddfile1.txt $cat.git/index $gitls-files#列出當前暫存區的文件列表信息 $gitls-files-s#列出當前暫存區文件的詳細信息 #右邊執行 $watch-n1-dtree.git
#左邊執行 $gitstatus $echo"file2">file2.txt $gitls-files-s $gitstatus $gitaddfile2.txt $gitls-files-s $gitstatus #右邊執行 $watch-n1-dtree.git
#左邊執行 $gitls-files-s $echo"file.txt">file1.txt $gitstatus #右邊執行 $watch-n1-dtree.git
#左邊執行 $gitls-files-s $gitaddfile1.txt $gitls-files-s #右邊執行 $watch-n1-dtree.git
理解 commit 提交原理
執行完成了 git commit 命令,究竟做了什么呢? Git 倉庫中的提交記錄保存的是你的目錄下所有文件的快照,就像是把整個目錄復制,然后再粘貼一樣,但比復制粘貼優雅許多!Git 希望提交記錄盡可能地輕量,因此在你每次進行提交時,它并不會盲目地復制整個目錄。條件允許的情況下,它會將當前版本與倉庫中的上一個版本進行對比,并把所有的差異打包到一起作為一個提交記錄。Git 還保存了提交的歷史記錄。這也是為什么大多數提交記錄的上面都有父節點的原因。 當我們使用 add 命令將工作區提交到暫存區,而暫存區其實保存的是當前文件的一個狀態,其中包括有哪些目錄和文件,以及其對應的大小和內容等信息。但是我們最終是需要將其提交到代碼倉庫(本地)的,而其命令就是 git commit 了。
#左邊執行 $gitcommit-m"1stcommit" $gitcat-file-t6e4a700#查看commit對象的類型 $gitcat-file-p6e4a700#查看commit對象的內容 $gitcat-file-t64d6ef5#查看tree對象的類型 $gitcat-file-p64d6ef5#查看tree對象的內容 #右邊執行 $watch-n1-dtree.git
#左邊執行 $cat.git/refs/heads/master $cat.git/HEAD #右邊執行 $watch-n1-dtree.git
加深理解 commit 提交
執行完成了 git commit 命令,究竟做了什么呢? 當我們再次對 file2.txt 文件的內容進行變更、添加以及提交之后,發現在提交的時候,查看的 commit 對象的內容時,其包含有父節點的 commit 信息。而對于理解的話,可以看看下面的這個提交流程圖。#左邊執行 $echo"file2.txt">file2.txt $gitstatus $gitaddfile2.txt $gitls-files-s $gitcat-file-p0ac9638 $gitcommit-m"2ndcommit" $gitcat-file-pbab53ff $gitcat-file-p2f07720 #右邊執行 $watch-n1-dtree.git
#左邊執行 $mkdirfloder1 $echo"file3">floder1/file3.txt $gitaddfloder1 $gitls-files-s $gitcommit-m"3rdcommit" $gitcat-file-p1711e01 $gitcat-file-p9ab67f8 #右邊執行 $watch-n1-dtree.git
文件的生命周期狀態
總結一下,Git 里面的文件狀態和如何切換。 現在,我們已經基本理解了文件如何在工作區、暫存區以及代碼倉庫之間進行狀態的跟蹤和同步。在 Git 的操作中,文件的可能狀態有哪些,以及如何進行狀態切換的,我們這里一起總結一下!
Branch 和 HEAD 的意義
執行完成了 git branch 命令,究竟做了什么呢? 到底什么是分支?分支切換又是怎么一回事?我們通過查看 Git 的官方文檔,就可以得到,分支就是一個有名字的(master/dev)指向 commit 對象的一個指針。 我們在初始化倉庫的時候,提供會默認給我們分配一個叫做 master 的分支(在最新的版本默認倉庫已經變更為 main 了),而 master 分支就是指向最新的一次提交。為什么需要給分支起名字呢?就是為了方便我們使用和記憶,可以簡單理解為 alias 命令的意義一致。
#左邊執行 $cat.git/HEAD $cat.git/refs/heads/master $gitcat-file-t1711e01 #右邊執行 $glo=gitlog
分支操作的背后邏輯
執行完成了 git branch 命令,究竟做了什么呢? 這里我們可以看到分支切換之后,HEAD 指向發生變動了。#左邊執行 $gitbranch $gitbranchdev $ll.git/refs/heads $cat.git/refs/heads/master $cat.git/refs/heads/dev $cat.git/HEAD $gitcheckoutdev $cat.git/HEAD #右邊執行 $glo=gitlog
#左邊執行 $echo"dev">dev.txt $gitadddev.txt $gitcommit-m"1stcommitfromdevbranch" $gitcheckoutmaster $gitbranch-ddev $gitbranch-Ddev $gitcat-file-t861832c $gitcat-file-p861832c $gitcat-file-p680f6e9 $gitcat-file-p38f8e88 #右邊執行 $glo=gitlog
checkout 和 commit 操作
我們一起聊一聊,checkout 和 commit 的操作! 我們執行 checkout 命令的時候,其不光可以切換分支,而且可以切換到指定的 commit 上面,即 HEAD 文件會指向某個 commit 對象。在 Git 里面,將 HEAD 文件沒有指向 master 的這個現象稱之為 detached HEAD。 這里不管 HEAD 文件指向的是分支名稱也好,是 commit 對象也罷,其實本質都是一樣的,因為分支名稱也是指向某個 commit 對象的。
#左邊執行 $gitcheckout6e4a700 $gitlog #右邊執行 $glo=gitlog
$gitcheckout-btmp $gitlog
即使可以這樣操作,我們也很少使用。還記得我們上一章節創建的 dev 分支嗎?我們創建了該分支并有了一個新的提交,但是沒有合并到 master 分支就直接刪除了?,F在再使用 log 命令查看的話,是看不到了。 實際,真的看不到了嗎?大家要記住,在 Git 里面任何的操作,比如分支的刪除。它只是刪除了指向某個特定 commit 的指針引用而已,而那個 commit 本身并不會被刪除,即 dev 分支的那個 commit 提交還是在的。 那我們怎么找到這個 commit 呢?找到之后,我們就可以在上面繼續工作,或者找到之前的文件數據等。 第一種方法:-
[費勁不太好,下下策]
-
在 objects 目錄下面,自己一個一個看,然后切換過去。
-
[推薦的操作方式]
-
使用 Git 提供的 git reflog 專用命令來查找。
-
該命令的作用就是用于將我們之前的所有操作都記錄下來。
#左邊執行 $gitreflog $gitcheckout9fb7a14 $gitcheckout-bdev #右邊執行 $glo=gitlog
聊聊 diff 的執行邏輯
當我們執行 diff 命令之后,Git 的邏輯它們是怎么對比出來的呢? 就在本節中中,我們使用上節的倉庫,修改文件內容之后,看看 diff 命令都輸出了哪些內容呢?我們這里一起來看看,研究研究!$echo"hello">file1.txt $gitdiff $gitcat-file-p42d9955 $gitcat-file-pce01362 #下述命令原理也是一樣的 $gitdiff--cached $gitdiffHEAD
Git 如何添加遠程倉庫
如何將我們本地的倉庫和遠程服務器上面的倉庫關聯起來呢? 初始化倉庫$gitinit $gitaddREADME.md $gitcommit-m"firstcommit"
關聯遠程倉庫 當我們使用上述命令來關聯遠程服務器倉庫的時候,我們本地 .git 目錄也是會發生改變的。通過命令查看 .git/config 文件的話,可以看到配置文件中出現了[remote]字段。#關聯遠程倉庫 $gitremoteaddorigingit@github.com:escapelife/git-demo.git
?cat.git/config [core] repositoryformatversion=0 filemode=true bare=false logallrefupdates=true ignorecase=true precomposeunicode=true [remote"origin"] url=git@github.com:escapelife/git-demo.git fetch=+refs/heads/*:refs/remotes/origin/*
推送本地分支 當我們執行如下命令,將本地 master 分支推送到遠程 origin 倉庫的 master 分支。之后,我們登陸 GitHub 就可以看到推送的文件及目錄內容了。 推送分支內容的時候,會列舉推送的 objects 數量,并將其內容進行壓縮,之后推送到我們遠程的 GitHub 倉庫,并且創建了一個遠程的 master 分支(origin 倉庫)。#推送本地分支 $gitpush-uoriginmaster
推送之后,我們可以發現,本地的 .git 生成了一些文件和目錄,它們都是什么呢?如下所示,會新增四個目錄和兩個文件,皆為遠程倉庫的信息。當我們通過命令查看 master 這個文件的內容時,會發現其也是一個 commit 對象。此時與我們本地 master 分支所指向的一致。而其用于表示遠程倉庫的當前版本,用于和本地進行區別和校對的。?tree.git ├──logs │├──HEAD │└──refs │├──heads ││├──dev ││├──master ││└──tmp │└──remotes#新增目錄 │└──origin#新增目錄 │└──master#新增文件 └──refs ├──heads │├──dev │├──master │└──tmp ├──remotes#新增目錄 │└──origin#新增目錄 │└──master#新增文件 └──tags
遠程倉庫存儲代碼
使用 GitLab 來了解遠程倉庫的服務器到底是如何存儲,我們的代碼的! 當我們編寫完代碼之后,將其提交到對應的遠程服務器上面,其存儲結構和我們地址是一模一樣的。如果我們仔細想想的話,不一樣的話才見怪了。 Git 本來就是代碼的分發平臺,無中心節點,即每個節點都是主節點,所以其存儲的目錄結構都是一直的。這樣,不管哪一個節點的內容發生丟失或缺失的話,我們都可以通過其他節點來找到。而 Git 服務器就是一個可以幫助我們,實時都可以找到的節點,而已。 原文鏈接:https://www.escapelife.site/posts/da89563c.html-
存儲
+關注
關注
13文章
4263瀏覽量
85674 -
代碼
+關注
關注
30文章
4748瀏覽量
68356 -
Git
+關注
關注
0文章
196瀏覽量
15736
原文標題:Git 基本原理介紹
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論