- Git是什么
- Git能夠解決哪些問題
- Git的實現原理
請注意,本文的闡述邏輯是:Git是什么——>Git要解決的根本問題是什么——>git是如何解決這些問題的。
另一個個問題,為什么說Git是“分布式”版本控制系統呢?
這里的“分布式”是相對於“集中式”來說的。把數據集中保存在服務器節點,所有的客戶節點都從服務節點獲取數據的版本控制系統叫做集中式版本控制系統,比如svn就是典型的集中式版本控制系統。
與之相對,Git的數據不止保存在服務器上,同時也完整的保存在本地計算機上,所以我們稱Git為分布式版本控制系統。
Git的這種特性帶來許多便利,比如你可以在完全離線的情況下使用Git,隨時隨地提交項目更新,而且你不必為單點故障過分擔心,即使服務器宕機或數據損毀,也可以用任何一個節點上的數據恢復項目,因為每一個開發節點都保存着完整的項目文件鏡像。
- 我們可以為每一次變更提交版本更新並且備注更新的內容;
- 我們可以在項目的各個歷史版本之間自如切換;
- 我們可以一目了然的比較出兩個版本之間的差異;
- 我們可以從當前的修改中撤銷一些操作;
- 我們可以自如的創建分支、合並分支;
- 我們可以和多人協作開發;
- 我們可以采取自由多樣的開發模式。
所以,如果問“Git能夠解決哪些問題?”我們可以簡單的回答:Git解決了版本控制方面的很多問題,但最核心的是它很好的解決了版本狀態存儲(即文件變更過程存儲)的問題。
- git init 用於創建一個空的git倉庫,或重置一個已存在的git倉庫
- git hash-object git底層命令,用於向Git數據庫中寫入數據
- git cat-file git底層命令,用於查看Git數據庫中數據
$ git init GitTest Initialized empty Git repository in /home/mp/Workspace/GitTest/.git/ |
$ cd GitTest
$ ls
$ find .git
.git
.git/HEAD .git/config .git/objects .git/objects/info .git/objects/pack .git/refs .git/refs/heads .git/refs/tags .git/hooks .git/hooks/commit-msg.sample .git/hooks/post-update.sample .git/hooks/update.sample .git/hooks/pre-rebase.sample .git/hooks/pre-applypatch.sample .git/hooks/fsmonitor-watchman.sample .git/hooks/applypatch-msg.sample .git/hooks/pre-receive.sample .git/hooks/pre-push.sample .git/hooks/prepare-commit-msg.sample .git/hooks/pre-commit.sample .git/info .git/info/exclude .git/branches .git/description |
$ echo "version 1" | git hash-object -w --stdin 83baae61804e65cc73a7201a7252750c76066a30 $ find .git/objects/ -type f |
$ git cat-file -t 83baa
blob
$ git cat-file -p 83baa
version 1
|
$ echo "version 1" > file.txt
$ git hash-object -w file.txt
83baae61804e65cc73a7201a7252750c76066a30
|
$ find .git/objects -type f .git/objects/83/baae61804e65cc73a7201a7252750c76066a30 |
$ echo "version 2" > file.txt
$ git hash-object -w file.txt
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
$ find .git/objects -type f
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30 |
我們發現,.git/objects下多出了一個文件,這是我們新保存進數據庫的file.txt。接下來,我們執行git cat-file搞清楚這兩條數據的內容分別是什么。執行
$git cat-file -p 83baa
version 1
$git cat-file -p 1f7a7a
version 2
|
我們發現,file.txt的變更過程被完整的記錄下來了。
$ cat file.txt
version 2
$ git cat-file -p 83baa > file.txt
$ cat file.txt
version 1 |
file.txt的內容成功恢復到了修改前的狀態,變成了“version 1”。這其實就是版本回滾的實質。
- 第一,無法記錄文件名的變化;
- 第二,無法記錄文件夾的變化;
- 第三,記憶每一個版本對應的hash值無聊且乏味且不可能;
- 第四,無法得知文件的變更時序;
- 第五,缺少對每一次版本變化的說明。
- git update-index git底層命令,用於創建暫存區
- git ls-files --stage git底層命令,用於查看暫存區內容
- git write-tree git底層命令,用於將暫存區內容寫入一個樹對象
OK,萬事俱備, 我們將file.txt的第一個版本放入暫存區,執行
$ find .git/index find: ‘.git/index’: No such file or directory $ git update-index --add file.txt $ find .git/index .git/index $ cat .git/index DIRC[���$�;�[���$�;�A���� ���a�Ne�s� rRu vjfile.txt�݀3%A��,I� �` $ find .git/objects/ -type f .git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a .git/objects/83/baae61804e65cc73a7201a7252750c76066a30 $ git ls-files --stage 100644 83baae61804e65cc73a7201a7252750c76066a30 0 file.txt $ git write-tree 391a4e90ba882dbc9ea93855103f6b1fa6791cf6 $ find .git/objects/ -type f .git/objects/39/1a4e90ba882dbc9ea93855103f6b1fa6791cf6 .git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a .git/objects/83/baae61804e65cc73a7201a7252750c76066a30 |
$ git cat-file -t 391a4e tree $ git cat-file -p 391a4e 100644 blob 83baae61804e65cc73a7201a7252750c76066a30 file.txt |
83baae61804e65cc73a7201a7252750c76066a30 |
$ echo "new file" > new $ git ls-files --stage |
- 如果添加git數據庫中尚未存儲的數據到暫存區,則在執行update-index的時候,會同時把該數據保存到git數據庫。
- 添加文件進入暫存區的操作是追加操作,之前已經加入暫存區的文件依然存在——很多人會有誤區,認為變更提交之后,暫存區就清空了。
$ git cat-file -p 228e49 100644 blob 83baae61804e65cc73a7201a7252750c76066a30 file.txt 100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new |
$ mkdir new_dir $ git update-index --add new_dir error: new_dir: is a directory - add files inside instead fatal: Unable to process path new_dir |
$ echo "file in new dir" > new_dir/new $ git write-tree $ git cat-file -p 06564b |
從執行結果可見,文件夾new_dir對應一個tree對象。
至此,在git數據庫中,我們可以完整的記錄文件的狀態、文件夾的狀態;並且可以把多個文件或文件夾組織在一起,記錄他們的變更過程。我們離一個完善的版本控制系統似乎已經不遠了,而這一切實現起來又是如此簡單——我們只是通過幾個命令操作git數據庫就完成了這些功能。
$ git write-tree
cb0fbcc484a3376b3e70958a05be0299e57ab495
$ git commit-tree cb0fbcc -m "first commit"
7020a97c0e792f340e00e1bb8edcbafcc4dfb60f
$ git cat-file 7020a97
tree cb0fbcc484a3376b3e70958a05be0299e57ab495 author john <john@163.com> 1537961478 +0800 committer john <john@163.com> 1537961478 +0800 first commit |
$ echo "new version" > file.txt
$ git update-index file.txt
$ git write-tree
848e967643b947124acacc3a2d6c5a13c549231c $ git commit-tree 848e96 -p 7020a97 -m "second commit" e838c8678ef789df84c2666495663060c90975d7 $ git cat-file -p e838c tree 848e967643b947124acacc3a2d6c5a13c549231c parent 7020a97c0e792f340e00e1bb8edcbafcc4dfb60f author john <john@163.com> 1537962442 +0800 committer john <john@163.com> 1537962442 +0800 second commit |
$ echo "another version" > file.txt $ git update-index file.txt $ git write-tree 92867fcc5e0f78c195c43d1de25aa78974fa8103 $ git commit-tree 92867 -p e838c -m "third commit" 491404fa6e6f95eb14683c3c06d10ddc5f8e883f $ git cat-file -p 49140 tree 92867fcc5e0f78c195c43d1de25aa78974fa8103 parent e838c8678ef789df84c2666495663060c90975d7 author john <john@163.com> 1537963274 +0800 committer john <john@163.com> 1537963274 +0800
third commit
|
$ git log 49140 commit 491404fa6e6f95eb14683c3c06d10ddc5f8e883f Author: john <john@163.com> Date: Wed Sep 26 20:01:14 2018 +0800 third commit commit e838c8678ef789df84c2666495663060c90975d7 Author: john <john@163.com> Date: Wed Sep 26 19:47:22 2018 +0800 second commit commit 7020a97c0e792f340e00e1bb8edcbafcc4dfb60f Author: john <john@163.com> Date: Wed Sep 26 19:31:18 2018 +0800 first commit |
git add
和 git commit
命令時, Git 所做的實質工作是將被改寫的文件保存為數據對象,更新暫存區,記錄樹對象,最后創建一個指明了頂層樹對象和父提交的提交對象。 這三種主要的 Git 對象——數據對象、樹對象、提交對象——最初均以單獨文件的形式保存在 .git/objects
目錄下。
$ echo "491404fa6e6f95eb14683c3c06d10ddc5f8e883f" > .git/refs/heads/master $ cat .git/refs/heads/master 491404fa6e6f95eb14683c3c06d10ddc5f8e883f |
$ git log 491404 commit 491404fa6e6f95eb14683c3c06d10ddc5f8e883f (HEAD -> master) Author: john <john@163.com> Date: Wed Sep 26 20:01:14 2018 +0800
third commit
commit e838c8678ef789df84c2666495663060c90975d7
Author: john <john@163.com> Date: Wed Sep 26 19:47:22 2018 +0800
second commit
commit 7020a97c0e792f340e00e1bb8edcbafcc4dfb60f
Author: john <john@163.com> Date: Wed Sep 26 19:31:18 2018 +0800
first commit
$ git log master commit 491404fa6e6f95eb14683c3c06d10ddc5f8e883f (HEAD -> master) Author: john <john@163.com> Date: Wed Sep 26 20:01:14 2018 +0800
third commit
commit e838c8678ef789df84c2666495663060c90975d7
Author: john <john@163.com> Date: Wed Sep 26 19:47:22 2018 +0800
second commit
commit 7020a97c0e792f340e00e1bb8edcbafcc4dfb60f
Author: john <john@163.com> Date: Wed Sep 26 19:31:18 2018 +0800
first commit
|
$ git update-ref refs/heads/master 49140 |