<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          git原理淺析

          共 46113字,需瀏覽 93分鐘

           ·

          2021-06-06 14:46

          git 歷史

          linux 之父 Linus Torvalds 大家應(yīng)該都知道,而 git 也是由 Linus 開發(fā)的。從 1991 年發(fā)布了第一版的 linux 內(nèi)核,Linux 內(nèi)核開源項目有著眾多的參與者,但絕大多數(shù)的 Linux 內(nèi)核維護(hù)工作都花在了提交補(bǔ)丁和保存歸檔的繁瑣事務(wù)上(19912002年間)。到 2002 年,整個項目組開始啟用一個專有的分布式版本控制系統(tǒng) BitKeeper 來管理和維護(hù)代碼,之前市面上也有其他的版本管理系統(tǒng),比如 CSVSVN,但是 Linus 覺得它們很蠢,直到有了 BitKeeper 才開始使用版本管理系統(tǒng)。

          至于為什么又自己開發(fā)了 git ,看完下邊對 Linus 的采訪就明白了。

          你為什么要開發(fā) Git?

          Torvalds:我從來沒有想過去做版本控制軟件,因為在我看來那是計算機(jī)世界里最無聊的事了(如果數(shù)據(jù)庫除外的話 ;^),我天生就不喜歡 source-control management (SCM)。但是 BitKeeper(BK) 的誕生改變了我對版本控制的認(rèn)識。BK 在大多數(shù)方面是正確的,在本地保存一個倉庫的副本,分布式合并確實是一大創(chuàng)新。這個分布式版本控制的創(chuàng)新完美地解決了 SCM 的通病:“誰可以修改代碼”的難題。BK 告訴我們,你只要給每個人一個倉庫,問題就解決了。但是 BK 也存在一些問題,技術(shù)上的問題(例如重命名很麻煩)還不算什么,它最大的壞處是不開源,很多人因為這個不使用它。所以即使我們有幾個核心維護(hù)者使用 BK——開源項目可以免費使用——但它也沒有普及。雖然它幫助過我們開發(fā)內(nèi)核,但依然有不少痛點沒有解決。

          當(dāng) Tridge 違反 BK 的使用協(xié)議反編譯 BK 的時候,我們到達(dá)了緊急關(guān)頭。我花了幾個周(還是幾個月來著?)試圖調(diào)解 Tridge 和 Larry McVoy(注:他是 Bitkeeper 的 老大),最后也沒有成功。我意識到我不能繼續(xù)使用 BK 了,但我真的不想回到?jīng)]有 BK 的黑暗時代。遺憾的是,我們想用其他 SCM 來代替它,卻沒有找到能在遠(yuǎn)程方面工作得好的。現(xiàn)有的軟件不能滿足我對遠(yuǎn)程方面的需求,我又擔(dān)心整個流程和代碼的完整性,所以最后我決定自己寫一個。

          總結(jié)就是,本來 BK 免費給他們用,但是有 linux 內(nèi)核有成員開始反編譯 BKBK 就不讓他們用了,然后 Linus 就用了幾周的時間自己寫了一個,git 就此誕生。。。然后 linus 就專心又去搞 linux 了,把 git 交給團(tuán)隊成員 Junio Hamano 進(jìn)行后期的迭代維護(hù)。

          git 原理淺析

          首先在一個空文件夾中執(zhí)行 git init 命令初始化 git 倉庫,然后會自動生成一個隱藏文件夾 .git ,目錄樹如下。

          .git
          ├── HEAD
          ├── config
          ├── description
          ├── hooks
          │   ├── applypatch-msg.sample
          │   ├── commit-msg.sample
          │   ├── fsmonitor-watchman.sample
          │   ├── post-update.sample
          │   ├── pre-applypatch.sample
          │   ├── pre-commit.sample
          │   ├── pre-merge-commit.sample
          │   ├── pre-push.sample
          │   ├── pre-rebase.sample
          │   ├── pre-receive.sample
          │   ├── prepare-commit-msg.sample
          │   └── update.sample
          ├── info
          │   └── exclude
          ├── objects
          │   ├── info
          │   └── pack
          └── refs
              ├── heads
              └── tags

          下邊依次分析下上邊的文件。

          description 文件

          description 文件僅供 GitWeb 程序使用,一般用不到。

          info文件夾

          info 目錄包含一個全局性排除(global exclude)文件, 用以放置那些不希望被記錄在 .gitignore 文件中的忽略模式(ignored patterns),和 .gitignore文件是 一個作用。

          config 文件

          默認(rèn)的配置文件,打開后顯示的是下邊的內(nèi)容。

          [core]
                  repositoryformatversion = 0
                  filemode = true
                  bare = false
                  logallrefupdates = true
                  ignorecase = true
                  precomposeunicode = true

          主要是當(dāng)前倉庫的一些配置,git 除了在這里有配置文件,還存在于 ~/.gitconfig ,打開后看一下。

          [http]
                  proxy = socks5://127.0.0.1:1080
          [https]
                  proxy = socks5://127.0.0.1:1080
          [user]
                  name = windliang
                  email = 6489178757@qq.com

          /etc/gitconfig 也是 git 的一個配置文件,但由于沒有配置過這個文件,所以我電腦里這個文件不存在。

          git 為我們提供了 config 命令用來配置上邊的文件。

          git config --list 是展示配置文件中已有的配置項,輸出如下

          http.proxy=socks5://127.0.0.1:1080
          https.proxy=socks5://127.0.0.1:1080
          user.name=windliang
          user.email=6489178757@qq.com
          core.repositoryformatversion=0
          core.filemode=true
          core.bare=false
          core.logallrefupdates=true
          core.ignorecase=true
          core.precomposeunicode=true

          可以看到就是把之前兩個配置文件的內(nèi)容按一定的格式輸出。

          上邊講到配置文件分布在三個文件中,git 為我們提供了三個參數(shù) --local--global--system ,分別處理 git 當(dāng)前倉庫下的 config 文件、 ~/.gitconfig 、以及/etc/gitconfig,如果存在同名的配置項,當(dāng)前倉庫下的配置文件優(yōu)先級最高,其次是~/.gitconfig/etc/gitconfig 優(yōu)先級最低。

          舉幾個例子。

          比如我們只想查看當(dāng)前倉庫下配置文件的配置項,可以執(zhí)行 git config --local --list ,輸出如下。

          core.repositoryformatversion=0
          core.filemode=true
          core.bare=false
          core.logallrefupdates=true
          core.ignorecase=true
          core.precomposeunicode=true

          在  ~/.gitconfig 中增加一個配置項,git config --global alias.ss status,執(zhí)行后再打開   ~/.gitconfig ,可以看到就增加了一個配置項。

          [http]
                  proxy = socks5://127.0.0.1:1080
          [https]
                  proxy = socks5://127.0.0.1:1080
          [user]
                  name = windliang
                  email = 6489178757@qq.com
          [alias]
                  ss = status

          通過上邊 alias 的配置,下次如果我們想執(zhí)行 git status 只需要輸入 git ss 就可以了,也就是別名。

          如果想刪除某個配置項,可以添加 --unset 參數(shù),比如執(zhí)行 git config --global --unset alias.ss

          也可以單獨查看某個配置項,例如輸入 git config --global user.name,輸出如下

          windliang

          底層命令和上層命令

          我們經(jīng)常使用的命令其實是上層命令(porcelain commands),參考下邊的表格。

          git-add                 git-rebase              git-cherry
          git-am                  git-reset               git-count-objects
          git-archive             git-revert              git-difftool
          git-bisect              git-rm                  git-fsck
          git-branch              git-shortlog            git-get-tar-commit-id
          git-bundle              git-show                git-help
          git-checkout            git-stash               git-instaweb
          git-cherry-pick         git-status              git-merge-tree
          git-citool              git-submodule           git-rerere
          git-clean               git-tag                 git-rev-parse
          git-clone               git-worktree            git-show-branch
          git-commit              gitk                    git-verify-commit
          git-describe            git-config              git-verify-tag
          git-diff                git-fast-export         git-whatchanged
          git-fetch               git-fast-import         gitweb
          git-format-patch        git-filter-branch       git-archimport
          git-gc                  git-mergetool           git-cvsexportcommit
          git-grep                git-pack-refs           git-cvsimport
          git-gui                 git-prune               git-cvsserver
          git-init                git-reflog              git-imap-send
          git-log                 git-relink              git-p4
          git-merge               git-remote              git-quiltimport
          git-mv                  git-repack              git-request-pull
          git-notes               git-replace             git-send-email
          git-pull                git-annotate            git-svn
          git-push                git-blame

          其實還有我們沒有用過的底層命令(plumbing commands),多數(shù)底層命令并不面向最終用戶,它們更適合作為新工具的組件和自定義腳本的組成部分。

          git-apply               git-for-each-ref        git-receive-pack
          git-checkout-index      git-ls-files            git-shell
          git-commit-tree         git-ls-remote           git-upload-archive
          git-hash-object         git-ls-tree             git-upload-pack
          git-index-pack          git-merge-base          git-check-attr
          git-merge-file          git-name-rev            git-check-ignore
          git-merge-index         git-pack-redundant      git-check-mailmap
          git-mktag               git-rev-list            git-check-ref-format
          git-mktree              git-show-index          git-column
          git-pack-objects        git-show-ref            git-credential
          git-prune-packed        git-unpack-file         git-credential-cache
          git-read-tree           git-var                 git-credential-store
          git-symbolic-ref        git-verify-pack         git-fmt-merge-msg
          git-unpack-objects      git-daemon              git-interpret-trailers
          git-update-index        git-fetch-pack          git-mailinfo
          git-update-ref          git-http-backend        git-mailsplit
          git-write-tree          git-send-pack           git-merge-one-file
          git-cat-file            git-update-server-info  git-patch-id
          git-diff-files          git-http-fetch          git-sh-i18n
          git-diff-index          git-http-push           git-sh-setup
          git-diff-tree           git-parse-remote        git-stripspace

          下邊用底層命令來進(jìn)行 git 的相關(guān)操作,以便對 git 原理有個更深的了解。

          objects 文件夾

          這個文件夾顧名思義,就是存儲對象的。git 主要有三種對象,blob 對象,tree 對象,commit 對象。和文件有關(guān)的東西都會存到這個文件夾中,相當(dāng)于一個鍵值對的數(shù)據(jù)庫。

          blob 對象

          首先新建一個 test.txt,寫入 hello worldecho 'hello world' > test.txt

          然后執(zhí)行 git hash-object -w test.txt 命令,得到

          3b18e512dba79e4c8300dd08aeb37f8e728b8dad

          hash-object 命令會返回生成對象的鍵值,-w 會把該對象寫入數(shù)據(jù)庫,也就是 objects 文件夾中。

          鍵值其實就是【頭部信息】加上【文件原始內(nèi)容】做了 SHA-1 得到的 40 位的哈希值,其中「頭部信息」指的是 對象類型+空格+數(shù)據(jù)的字節(jié)數(shù)+空字節(jié)

          我們來看一下 objects 文件夾的變化。

          objects
          ├── 3b
          │   └── 18e512dba79e4c8300dd08aeb37f8e728b8dad
          ├── info
          └── pack

          可以看到多了一個文件夾 3b 和一個文件 18e512dba79e4c8300dd08aeb37f8e728b8dadf ,組合起來剛好就是我們得到的鍵值。

          通過指令 git cat-file -p 3b18e512d 看一下該文件的內(nèi)容。

          hello world

          cat-file 可以解碼剛剛生成的對象,-p 參數(shù)會自動選擇內(nèi)容的編碼,3b18e512d 是鍵值的前幾位。

          然后我們修改一下文件的內(nèi)容,echo 'hello world 2' > test.txt,再次執(zhí)行 git hash-object -w test.txt

          再看一下 objects 文件夾。

          objects
          ├── 3b
          │   └── 18e512dba79e4c8300dd08aeb37f8e728b8dad
          ├── d0
          │   └── e1e95455754bd31d56260d19a7774fd7aebe5d
          ├── info
          └── pack

          可以看到我們又多了一個對象,此時我們把本地的 test.txt 文件刪除,rm test.txt

          然后看一下之前寫的內(nèi)容還在不在,git cat-file -p d0e1e954557

          hello world 2

          可以看到還是能取到之前的內(nèi)容,git 把之前所有的內(nèi)容都存了起來,這就是簡單的版本管理。但有個問題就是,當(dāng)前我們只存了鍵值,并沒有存文件名,這種類型的對象我們叫做「數(shù)據(jù)對象」blob object,通過 git cat-file -t 命令加上 SHA-1 的鍵值前幾位,就能查看該對象的內(nèi)部類型。git cat-file -t d0e1e954557

          blob

          tree 對象

          tree 對象記錄了文件名以及文件之間的關(guān)系,相當(dāng)于就是文件夾的作用,可以理解為下邊的圖。

          下邊演示如何用底層命令生成一個 tree 對象。

          生成 tree 對象之前,我們需要將文件加入到暫存區(qū)。

          新建一個空項目,然后 git init,新建文件 test.txt

          執(zhí)行 git update-index --add test.txt,這個命令會生成相應(yīng)的對象存入 objects 文件夾中,并將 test.txt 加入暫存區(qū),

          可以執(zhí)行 git status 看一下。

          On branch master

          No commits yet

          Changes to be committed:
            (use "git rm --cached <file>..." to unstage)
              new file:   test.txt

          同時可以看到 objects 文件夾中多了一個文件。

          objects
          ├── 95
          │   └── d09f2b10159347eece71399a7e2e907ea3df4f
          ├── info
          └── pack

          暫存區(qū)有了文件以后,就可以生成一個 tree 對象了,執(zhí)行 git write-tree,生成當(dāng)前暫存區(qū)的一個快照,返回值如下:

          f03546f10f086a5cbc7b8580632ca6db2ba9411d

          會返回 tree 對象的 key 值,看一下 objects 文件夾,新生成了一個文件。

          objects
          ├── 95
          │   └── d09f2b10159347eece71399a7e2e907ea3df4f
          ├── f0
          │   └── 3546f10f086a5cbc7b8580632ca6db2ba9411d
          ├── info
          └── pack

          然后我們通過 git cat-file -p f0354 看一下 tree 對象的內(nèi)容。

          100644 blob 95d09f2b10159347eece71399a7e2e907ea3df4f    test.txt

          可以看到當(dāng)前 tree 對象包含有一個 blob 對象,文件名是 test.txt100644 表示普通文件,還包括:100755,表示一個可執(zhí)行文件;120000,表示一個符號鏈接。

          有了 tree 對象我們就有了文件名,以及文件之間的關(guān)系,但是更改文件后我們可能還需要一些注釋,如果是多人合作,還需要指明是誰生成的快照。因此我們需要將當(dāng)前 tree 對象再包裝一層,生成 commit 對象。

          commit 對象

          執(zhí)行 echo 'first commit' | git commit-tree f0354 將之前的 tree 對象包裝成一個 commit 對象。f0354 是之前 tree 對象的 key,返回值如下:

          84340eaeccbc0854bdec82f6b07f05eb01bd4dcd

          同樣的給我們返回了 commit 對象的 key,同時看一下 objects 文件夾,也會多一個文件。

          objects
          ├── 84
          │   └── 340eaeccbc0854bdec82f6b07f05eb01bd4dcd
          ├── 95
          │   └── d09f2b10159347eece71399a7e2e907ea3df4f
          ├── f0
          │   └── 3546f10f086a5cbc7b8580632ca6db2ba9411d
          ├── info
          └── pack

          我們看一下這個 commit 對象的內(nèi)容,git cat-file -p 8434

          tree f03546f10f086a5cbc7b8580632ca6db2ba9411d
          author windliang <6489178757@qq.com> 1594200559 +0800
          committer windliang <6489178757@qq.com> 1594200559 +0800

          first commit

          此外 git commit-tree 還有一個 -p 參數(shù),用來指定當(dāng)前 commit 對象的父 commit 對象。

          比如我們修改一下文件,再生成新的 tree 對象,依次執(zhí)行下邊的命令。

          echo 'hello world2' > test2.txt 
          git update-index --add test2.txt
          git write-tree  

          此時就得到了一個 tree 對象。

          ab00cb505b3f955ab5fb245b7ca155a5820d2cd4

          接下再生成新的 commit 對象,并且指定父 commit 對象,echo 'second commit' | git commit-tree ab00 -p 8434

          44d318147e6b7bf2c3a5268018390440b2beae56

          然后我們通過這個 commit 對象的 key 查看一下 loggit log 44d3

          commit 44d318147e6b7bf2c3a5268018390440b2beae56
          Author: windliang <6489178757@qq.com>
          Date:   Wed Jul 8 17:47:31 2020 +0800

              second commit

          commit 84340eaeccbc0854bdec82f6b07f05eb01bd4dcd
          Author: windliang <6489178757@qq.com>
          Date:   Wed Jul 8 17:29:19 2020 +0800

              first commit

          因為設(shè)置了父 commit 對象,所以第一次的提交也可以看到。

          refs 文件夾,HEAD 文件

          heads

          我們剛剛執(zhí)行 git log 命令的時候,寫的是 git log 44d3,多加了 commit 對象的 key44d3,寫起來很麻煩,我們可以給它起一個別名,這個別名就是我們一直用的分支了。

          我們將 commit 對象的 key 值寫入 .git/refs/heads/master 文件中

          echo 44d318147e6b7bf2c3a5268018390440b2beae56 > .git/refs/heads/master

          然后執(zhí)行 git log master 就可以看到 log 了。

          commit 44d318147e6b7bf2c3a5268018390440b2beae56 (HEAD -> master)
          Author: windliang <6489178757@qq.com>
          Date:   Wed Jul 8 17:47:31 2020 +0800

              second commit

          commit 84340eaeccbc0854bdec82f6b07f05eb01bd4dcd
          Author: windliang <6489178757@qq.com>
          Date:   Wed Jul 8 17:29:19 2020 +0800

              first commit

          可以省略 master ,直接執(zhí)行 git log ,默認(rèn)查詢的就是當(dāng)前分支的 log

          我們也可以給另外一個 commit 對象創(chuàng)建一個別名,換句話說創(chuàng)建一個新的分支。

          echo 84340eaeccbc0854bdec82f6b07f05eb01bd4dcd > .git/refs/heads/dev

          然后執(zhí)行 git checkout dev 會發(fā)現(xiàn)可以成功的切換分支,說明分支創(chuàng)建成功了。

          這里我們直接操控了文件,git 其實給我提供了一個命令,會更加安全

          git update-ref refs/heads/dev 84340eaeccbc0854bdec82f6b07f05eb01bd4dcd

          回想一下我們之前創(chuàng)建分支的命令,會執(zhí)行 git branch fix ,注意到我們并沒有指定 commit 對象的 key 值,為什么可以成功創(chuàng)建分支呢?

          HEAD 文件!它里邊始終保存著最新的 commit 對象的 key 值,當(dāng)有新的 commit 的時候它會更新,當(dāng)切換分支的時候它也會更新。

          打開 HEAD 文件可以看一下。

          ref: refs/heads/master

          他保存了一個引用,refs/heads/master 文件保存的就是當(dāng)前分支最新的 commit 對象的 key 值。

          如果我們切換分支,git checkout dev ,可以看到 HEAD 中的值也會相應(yīng)的變化。

          ref: refs/heads/dev

          tags

          上邊介紹了 blob 對象,tree 對象,commit 對象。commit 對象是包裝了 tree 對象,還有個 tag 對象,通常是對 commit 對象的包裝。

          標(biāo)簽的話主要分為附注標(biāo)簽和輕量標(biāo)簽。可以像創(chuàng)建分支那樣創(chuàng)建一個輕量標(biāo)簽:

          git update-ref refs/tags/v1.0 44d318147e6b7bf2c3a5268018390440b2beae56

          輕量標(biāo)簽的話相當(dāng)于就是對 commit 的一個引用,沒有創(chuàng)建新的對象。

          我們再來創(chuàng)建一個附錄對象,可以添加一些注釋。

          git tag -a v1.1 84340eaeccbc0854bdec82f6b07f05eb01bd4dcd -m 'test tag'

          看一下新創(chuàng)建對象的 key 值,cat .git/refs/tags/v1.1

          b518af197d2a925b668043edf1af88b82664e19f

          然后查看一下該對象,git cat-file -p b518

          object 84340eaeccbc0854bdec82f6b07f05eb01bd4dcd
          type commit
          tag v1.1
          tagger windliang <6489178757@qq.com> 1594208408 +0800

          test tag

          我們順便看一下這個對象的類型,git cat-file -t b518

          tag

          另外要注意的是,標(biāo)簽對象并非必須指向某個 commit 對象,它可以對任意類型的 git 對象打標(biāo)簽。

          remotes

          如果有遠(yuǎn)程倉庫,并對其執(zhí)行過推送操作,git 會記錄下最近一次推送操作時的分支,并保存在 refs/remotes 目錄下。

          $ git remote add origin git@github.com:wind-liang/test.git
          $ git push -u origin master
          Enumerating objects: 6, done.
          Counting objects: 100% (6/6), done.
          Delta compression using up to 12 threads
          Compressing objects: 100% (3/3), done.
          Writing objects: 100% (6/6), 467 bytes | 467.00 KiB/s, done.
          Total 6 (delta 0), reused 0 (delta 0)
          To github.com:wind-liang/test.git
           * [new branch]      master -> master
          Branch 'master' set up to track remote branch 'master' from 'origin'.

          然后查看 remotes 里的文件,cat .git/refs/remotes/origin/master

          44d318147e6b7bf2c3a5268018390440b2beae56

          這個值就是最新的 commit 對象的 key 值。遠(yuǎn)程引用和分支(位于 refs/heads 目錄下的引用)之間最主要的區(qū)別在于,遠(yuǎn)程引用是只讀的。雖然可以 git checkout 到某個遠(yuǎn)程引用,但是 Git 并不會將 HEAD 引用指向該遠(yuǎn)程引用。

          objects/pack

          前邊我們講了每一個文件都作為一個對象存到 objects 目錄下,如果只修改了文件的某一行,然后進(jìn)行提交,依舊會新生成一個 object。如果 git 只保存其中一個,再保存另一個對象與之前版本的差異內(nèi)容,不是能省些空間嗎?

          事實上 Git 可以那樣做。但 Git 最初向磁盤中存儲對象時所使用的格式被稱為「松散(loose)」對象格式。Git 會時不時地將多個這些對象打包成一個稱為“包文件(packfile)”的二進(jìn)制文件,以節(jié)省空間和提高效率。當(dāng)版本庫中有太多的松散對象,或者手動執(zhí)行 git gc 命令,或者你向遠(yuǎn)程服務(wù)器執(zhí)行推送時,Git 都會這樣做。

          Git 打包對象時,會查找命名及大小相近的文件,并只保存文件不同版本之間的差異內(nèi)容。可以找一個項目看一下 pack 下的目錄。

          pack
          ├── pack-0efa08fc30b8d98dff42203c71c6afe0533ce468.idx
          ├── pack-0efa08fc30b8d98dff42203c71c6afe0533ce468.pack
          ├── pack-19571818da01df976f52298facf362dc93d61026.idx
          ├── pack-19571818da01df976f52298facf362dc93d61026.pack
          ├── pack-725ab685133ed6e35083c5b3dcaf02ebc238489c.idx
          ├── pack-725ab685133ed6e35083c5b3dcaf02ebc238489c.pack
          ├── pack-7c01d29f0cb068c617aa49471cbf9f6eb1cb2156.idx
          ├── pack-7c01d29f0cb068c617aa49471cbf9f6eb1cb2156.pack
          ├── pack-a59c4fce103fb83cec0f513ab32cd92e6122e7a4.idx
          ├── pack-a59c4fce103fb83cec0f513ab32cd92e6122e7a4.pack
          ├── pack-db0d185ea0e7d96bbad911bb371c67869d8599b0.idx
          ├── pack-db0d185ea0e7d96bbad911bb371c67869d8599b0.pack
          ├── pack-f07ea07e30bb0aa4dfbb1fcb08da4cd5e5e5f793.idx
          └── pack-f07ea07e30bb0aa4dfbb1fcb08da4cd5e5e5f793.pack

          可以是兩種類型,一種是打包文件,另一種就是索引文件,用來記錄不同版本之間的差異。更詳細(xì)的可以看一下 Git 內(nèi)部原理 - 包文件。

          packed-refs文件

          執(zhí)行 gc 以后,會將 refs 文件夾中的引用打包到這個文件中。

          index

          當(dāng)我們執(zhí)行了 git add 或者上邊講到的 git update-index --add 命令,我們就會發(fā)現(xiàn) .git 目錄下增加了一個 index 文件。這個文件存儲的東西就是我們常說的「暫存區(qū)」。它主要存儲了每個文件的索引,也就是在 objects 目錄下生成的對象的 SHA-1 哈希值。還有生成 tree 對象的一些信息,比如文件名以及文件之間的關(guān)系,為下一步生成 tree 對象做準(zhǔn)備。

          hooks 文件夾

          這里主要是 git 為我們提供了一些鉤子函數(shù),把下邊的 .sample 去掉,當(dāng)前鉤子就會生效。可以編輯各個鉤子文件,就可以在執(zhí)行 pushcommit 等操作時完成一些自己想要的一些動作。

          hooks
          ├── applypatch-msg.sample
          ├── commit-msg.sample
          ├── fsmonitor-watchman.sample
          ├── post-update.sample
          ├── pre-applypatch.sample
          ├── pre-commit.sample
          ├── pre-merge-commit.sample
          ├── pre-push.sample
          ├── pre-rebase.sample
          ├── pre-receive.sample
          ├── prepare-commit-msg.sample
          └── update.sample

          通過鉤子,可以實現(xiàn)提交代碼前自動格式化代碼、規(guī)范化 commit-msg 等功能,還可以做到當(dāng)遠(yuǎn)程倉庫 github 更新后,讓服務(wù)器端自動拉取最新項目,實現(xiàn)一些 web 項目的自動更新。

          COMMIT_EDITMSG

          存儲最后一次提交的信息內(nèi)容。git commit 命令之后打開的編輯器就是在編輯此文件,退出編輯器保存后,git 會把此文件內(nèi)容寫入 commit 記錄。一般直接在 commit 命令后添加 -m 選項,附加提交信息。

          ORIG_HEAD 文件

          相當(dāng)于 HEAD 文件的一個備份,會指向 HEAD 之前的一個 commit 對象。當(dāng)執(zhí)行一些危險的操作,比如 git rebase 等,需要先記錄 ORIG_HEAD 再執(zhí)行其他的操作。

          FETCH_HEAD 文件

          FETCH_HEAD 記錄了 fetch 時候遠(yuǎn)程分支的 key 值,也就是 commit 對象的 SHA-1 哈希值。當(dāng)執(zhí)行 git pull 的時候相當(dāng)于先執(zhí)行 git fetch ,然后執(zhí)行 git merge FETCH_HEAD ,也就是和拉取下來的遠(yuǎn)程分支合并。

          打開 FETCH_HEAD 文件,第一行就是 FETCH_HEAD 的值,用于 merge,其它行是同時拉取下來的分支。

          d6a81fdb23503d5e85cb8f74ea77cd4ab20e0659        branch 'master' of ssh://git.github.com/ed-f2e/test
          5ce98b8e6f832382417c5a1ef55f1f1ca303f86d    not-for-merge   branch 'foodsafetab-20200701' of ssh://git.github.com/ed-f2e/test
          d46bec5fea5af996d75497b05592802ef31fe63b    not-for-merge   branch 'overview-20200601' of ssh://git.github.com/ed-f2e/test
          005ef114aa680401681f582b8f71dfe020417989    not-for-merge   branch 'visual-20200622' of ssh://git.github.com/ed-f2e/test

          關(guān)鍵字 not-for-merge,表明 git pull 時只 fetch,不 merge

          logs 文件夾

          記錄了操作信息,git reflog 命令以及像 HEAD@{1} 形式的路徑會用到。如果刪除此文件夾,那么依賴于 reflog 的命令就會報錯。

          文件夾總

          基本上把 .git 目錄總結(jié)完了,下邊匯總一下。

          .
          ├── COMMIT_EDITMSG // git commit 時候編輯的文件
          ├── FETCH_HEAD // git fetch 保存從遠(yuǎn)程倉庫抓取下來的 commit 對象的鍵值
          ├── HEAD // 保存當(dāng)前 commit 對象的鍵值
          ├── ORIG_HEAD // 執(zhí)行危險操作時 HEAD 的備份
          ├── config // 當(dāng)前 git 倉庫的相關(guān)配置,優(yōu)先級最高
          ├── description // 僅供 GitWeb 程序使用
          ├── hooks // 保存 git 的所有鉤子
          │   ├── applypatch-msg.sample
          │   ├── commit-msg
          │   ├── commit-msg.git-flow
          │   ├── commit-msg.sample
          │   ├── fsmonitor-watchman.sample
          │   ├── post-update.sample
          │   ├── pre-applypatch.sample
          │   ├── pre-commit
          │   ├── pre-commit.git-flow
          │   ├── pre-commit.mt-eslint-check
          │   ├── pre-commit.sample
          │   ├── pre-merge-commit.sample
          │   ├── pre-push
          │   ├── pre-push.git-flow
          │   ├── pre-push.sample
          │   ├── pre-rebase.sample
          │   ├── pre-receive.sample
          │   ├── prepare-commit-msg.sample
          │   └── update.sample
          ├── index // 暫存區(qū),保存對象的索引和 tree 對象的相關(guān)信息
          ├── info
          │   └── exclude // 和 .gitignore 一樣的作用
          ├── logs // 記錄歷史的一些操作,git reflog 命令依賴于此目錄
          ├── objects // git 的數(shù)據(jù)庫,存放所有對象
          │   ├── 0f
          │   │   ├── 01ae962d3a527bfd692175ee5600501eef43fb
          │   │   ├── 5ef114aa680401681f582b8f71dfe020417989
          │   │   ├── 71685e674a8190ba2f94391cae5f99ea4854fb
          │   │   └── cef7d6ec44ae5ffabf2514bd19ccb1ec303eb4
          │   ├── 34
          │   │   ├── 621a5fb9cdb057a09c559586702a4d20388c71
          │   │   └── 6e1d53b34578f0bfcef11dd2d59dc25072e79e
          │   ├── 55
          │   │   ├── 137e5e9f4e37218033af62bc9fcc2fded5545b
          │   │   ├── c0d6f9e2d8df065ccb512d45cbbd61d327e7fd
          │   │   └── eeddf196be55abc4292f562b8bd4fb19bbb2d4
          │   ├── info
          │   └── pack
          │       ├── pack-679830bc13d16192a07ddaa0f51f49b0163b7578.idx
          │       └── pack-679830bc13d16192a07ddaa0f51f49b0163b7578.pack
          ├── packed-refs // 執(zhí)行 gc 以后,將 refs 中的引用進(jìn)行打包
          └── refs // 對 commit 對象的引用
              ├── heads //所有分支
              │   ├── master
              ├── remotes // 遠(yuǎn)程分支所對應(yīng)的 key 值
              │   └── origin
              │       ├── HEAD
              │       └── master
              └── tags // 所有標(biāo)簽

          對象總結(jié)

          主要包括 blob 對象,tree 對象,commit 對象,還有 tag 對象。通過 commit 對象以鏈表的形式連接在了一起。

          可以看到第一次 commit 的時候,創(chuàng)建了 README.mdindex.htmljs 文件夾以及 index.js

          第二次 commit 的時候,僅僅修改了 index.html,其他文件仍舊指向原來的對象。并且用當(dāng)前 commit 包裝了一個 tag 對象。

          第三次 commit 的時候,增加了 index.css 文件,其他文件仍舊指向原來的對象。此外當(dāng)前 commit 對象是當(dāng)前操作的對象,所以 HEAD 指向當(dāng)前 commit 對象,另外 mater 分支也指向當(dāng)前 commit 對象。

          換一種眼光看命令

          這一節(jié)回顧一下 git 經(jīng)常用的命令和上邊介紹的文件的一些關(guān)系。為了方便監(jiān)測每個命令改變了哪些文件,我們在 .git 目錄中再執(zhí)行一次 git init,也就是將 .git 目錄看作我們的另外一個項目,操作如下:

          新建一個目錄,learnGit,在里邊新建 index.htmlREADME.mdjs 文件夾,js 文件夾中新建 index.js。目錄結(jié)構(gòu)如下:

          .
          ├── README.md
          ├── index.html
          └── js
              └── index.js

          然后初始化當(dāng)前目錄為 git 倉庫。

          learnGit % git init
          Initialized empty Git repository in /Users/learnGit/.git/

          此時就會自動生成 .git 目錄,進(jìn)入 .git 目錄再執(zhí)行一次 git initgit add .git commit -m "init",來監(jiān)測后續(xù)  .git 目錄的變化情況。然后回到我們的根目錄learnGit 中進(jìn)行下邊的實驗。

          git add .

          此時會發(fā)現(xiàn)每個文件會生成一個對象,因此 objects 文件夾中多了 3 個文件(如果是 mac 系統(tǒng)會發(fā)現(xiàn)多了 4 個文件,原因是系統(tǒng)自動生成了一個 .DS_Store 文件,這里就不考慮了),也就是 3blob 對象。此外,增加了 index 文件,也就是暫存區(qū),會存儲每個 commit 對象的索引,以及生成 tree 對象的相關(guān)信息。

           create mode 100644 index
           create mode 100644 objects/4d/7a16a7949cf8206f6f910535fd6811d4a5e3d2
           create mode 100644 objects/94/a127e7307c6562a2bdbf2d156589572c31963e
           create mode 100644 objects/c3/b573586becc940e02cd0914ef2eaf6d1ff7a28

          相當(dāng)于執(zhí)行了 git hash-object -w 文件名 生成對象,以及 git update-index --add 文件名 命令,將文件加入暫存區(qū)。

          git commit -m

          執(zhí)行 git commit -m "first",文件變化情況如下。

          create mode 100644 COMMIT_EDITMSG
          create mode 100644 logs/HEAD
          create mode 100644 logs/refs/heads/master
          create mode 100644 objects/74/900affe800f97c02e9cad8a9b2304e21f0a412 //commit 對象
          create mode 100644 objects/bc/4a821da58aa317d1790199b98b1e1b638baebb //tree 對象
          create mode 100644 objects/d2/eb8f97312f90fe39586e6deefb6b41b4d8340f //tree 對象
          create mode 100644 refs/heads/master

          會發(fā)現(xiàn) objects 文件夾中多了三個文件,其中兩個是 tree 對象,因為我們的目錄有兩個文件夾。另一個就是包裝了 tree 對象的 commit 對象。

          增加了 COMMIT_EDITMSG,也就是 commit 時候?qū)懙奶峤恍畔ⅲ谶@里的話里邊內(nèi)容就是 "first"。

          logs 目錄發(fā)生了一些變化,reflog 命令依賴這里的文件。

          自動為我們創(chuàng)建了 mater 分支,因此增加了 refs/heads/master 文件,里邊的內(nèi)容就是我們剛剛生成的 commit 對象的 hash 值,也就是 74900affe800f97c02」9cad8a9b2304e21f0a412

          git push

          我們先執(zhí)行 git remote add origin [email protected]:wind-liang/learnGit.git 添加一個遠(yuǎn)程倉庫。此時 config 文件多了三行,記錄了遠(yuǎn)程倉庫。

          [remote "origin"]
              url = git@github.com:wind-liang/learnGit.git
              fetch = +refs/heads/*:refs/remotes/origin/*

          記錄了遠(yuǎn)程倉庫的名字 origin,以及 url 地址,還有就是執(zhí)行 fetch 時候的默認(rèn)操作,從遠(yuǎn)程取回所有分支的更新,可以看下一節(jié)fetch 命令的介紹。

          格式:git push <遠(yuǎn)程主機(jī)名> <本地分支名>:<遠(yuǎn)程分支名>

          我們執(zhí)行 git push origin master,可以省略遠(yuǎn)程分支名,默認(rèn)和本地分支名一致。

          create mode 100644 logs/refs/remotes/origin/master
          create mode 100644 refs/remotes/origin/master

          logs 就不說了。會發(fā)現(xiàn)本地新建了一個遠(yuǎn)程分支 origin/master,里邊內(nèi)容就是我們剛剛推送的本地 mater 分支指向的 commit 對象的 hash 值,也就是 74900affe800f97c02e9cad8a9b2304e21f0a412

          此時我們修改 index.html ,然后執(zhí)行 git add . 加到暫存區(qū)。看一下文件的變化。

           index                                             | Bin 396 -> 377 bytes
           objects/07/51aaed4e8f37c1f84eb7780ca08989029ec504 | Bin 0 -> 247 bytes

          此時會多一個對象,也就是新一版的 index.html ,以及 index 文件會發(fā)生變化。

          接著執(zhí)行 git commit -m "second",會根據(jù)暫存區(qū)的信息生成當(dāng)前的樹對象以及 commit 對象,objects 文件夾中應(yīng)該會增加兩個對象,一個 tree 對象,一個 commit 對象。

           COMMIT_EDITMSG                                    |   2 +-
           index                                             | Bin 377 -> 396 bytes
           logs/HEAD                                         |   1 +
           logs/refs/heads/master                            |   1 +
           objects/63/53b5966f6bbf3f6a840b1261030519b67fdf51 | Bin 0 -> 150 bytes
           objects/af/f7b5ebae94eba99e5fa0ef245595c21686562b | Bin 0 -> 154 bytes
           refs/heads/master                                 |   2 +-

          refs/heads/master 也會更新,指向最新的 commit 對象。

          如果我們想把當(dāng)前改變再推送到遠(yuǎn)程倉庫,又需要執(zhí)行 git push origin master ,有些長。git 為我們提供了 --set-upstream 參數(shù),簡寫是 -u,可以讓本地分支關(guān)聯(lián)都某個遠(yuǎn)程分支,這樣的話如果下次想把當(dāng)前分支推送到遠(yuǎn)程,只需要執(zhí)行 git push 就可以了。

          我們執(zhí)行一下 git push -u origin master ,看一下哪些文件會變化。

          config                          | 3 +++
          logs/refs/remotes/origin/master | 1 +
          refs/remotes/origin/master      | 2 +-

          看一下 config 文件。

          [core]
              repositoryformatversion = 0
              filemode = true
              bare = false
              logallrefupdates = true
              ignorecase = true
              precomposeunicode = true
          [remote "origin"]
              url = [email protected]:wind-liang/learnGit.git
              fetch = +refs/heads/*:refs/remotes/origin/*
          [branch "master"]
              remote = origin
              merge = refs/heads/master

          可以看到,它記錄了本地 mater 分支和遠(yuǎn)程倉庫 origin 中的 refs/heads/mater 關(guān)聯(lián)。除了在 git push 起作用,git fetchgit pull 的默認(rèn)操作也會依賴這里的配置,可以繼續(xù)看下邊的小節(jié)。

          此外,這里建立的遠(yuǎn)程分支必須要和本地分支同名,因為在 Git 2.0 之后 git push 不加任何參數(shù)的話,默認(rèn)模式為 simple,推送當(dāng)前分支到upstream 分支上,必須保證本地分支與 upstream 分支同名,不然的話 git push 是沒有用的。

          比如我們將 mater 分支和遠(yuǎn)程倉庫的 dev 分支關(guān)聯(lián),執(zhí)行 git branch -u origin/dev,再執(zhí)行 git push 就會得到下邊的提示。

          fatal: The upstream branch of your current branch does not match
          the name of your current branch.  To push to the upstream branch
          on the remote, use

              git push origin HEAD:dev

          To push to the branch of the same name on the remote, use

              git push origin HEAD

          To choose either option permanently, see push.default in 'git help config'.

          還有其他的模式,nothing, current, upstream, matching,一般就用默認(rèn)的 simple,這里就不介紹了。

          git fetch

          為了更詳細(xì)的看 git fetch 命令的作用。我新建了另一個遠(yuǎn)程倉庫 origin2,關(guān)聯(lián)到了當(dāng)前本地倉庫,并且在遠(yuǎn)程倉庫中添加了 index2.txt

          同時在原來 origin 的遠(yuǎn)程倉庫中,在 mater 分支新增了 index.css 文件。增加了 dev 分支,并且修改了 index.html

          當(dāng)前本地倉庫的配置文件如下:

          [core]
              repositoryformatversion = 0
              filemode = true
              bare = false
              logallrefupdates = true
              ignorecase = true
              precomposeunicode = true
          [remote "origin"]
              url = [email protected]:wind-liang/learnGit.git
              fetch = +refs/heads/*:refs/remotes/origin/*
          [branch "master"]
              remote = origin
              merge = refs/heads/master
          [remote "origin2"]
              url = [email protected]:wind-liang/learnGit2.git
              fetch = +refs/heads/*:refs/remotes/origin2/*

          git fetch  的命令格式為 git fetch <遠(yuǎn)程主機(jī)名> <分支名>

          我們不加參數(shù),只執(zhí)行 git fetch 看一下效果。

          FETCH_HEAD                                        |   2 ++
          logs/refs/remotes/origin/dev                      |   1 +
          logs/refs/remotes/origin/master                   |   1 +
          objects/08/f4162aa74066285166b9b46f845ea648941943 | Bin 0 -> 151 bytes
          objects/36/619ae122d6e01b7c82838247754173c1b6930a | Bin 0 -> 177 bytes
          objects/88/688aebd84396af325f55f82a4bdf0e283a8e4a | Bin 0 -> 251 bytes
          objects/b1/be33b8a27e1fcfa2c9f01b0fcb76a28b091071 |   2 ++
          objects/be/5678cb94b6da6f94cad10077739a850cd893b5 | Bin 0 -> 38 bytes
          objects/ff/383d0247ea2c27dd2d44753dd5b18ca4ecfeaf | Bin 0 -> 177 bytes
          refs/remotes/origin/dev                           |   1 +
          refs/remotes/origin/master                        |   2 +-
          11 files changed, 8 insertions(+), 1 deletion(-)

          默認(rèn)抓取了遠(yuǎn)程倉庫 origin 的兩個分支。由于遠(yuǎn)程倉庫新增了 index.css 文件,并且修改了 dev 分支中的 index.html ,所以是 2blob 對象,2tree 對象,2  個 commit 對象,所以 objects 文件中增加了 6 個對象。

          FETCH_HEAD 記錄了兩個分支指向的最新 commit 對象的 hash 值。

          b1be33b8a27e1fcfa2c9f01b0fcb76a28b091071        branch 'master' of github.com:wind-liang/learnGit
          08f4162aa74066285166b9b46f845ea648941943    not-for-merge   branch 'dev' of github.com:wind-liang/learnGit

          refs/remotes/origin/devrefs/remotes/origin/master 分別記錄了分支所對應(yīng)的 commit 對象。

          git merge

          git fetch 僅僅把遠(yuǎn)程分支拉取了下來,我們還需要通過 git merge 將遠(yuǎn)程分支的內(nèi)容和本地內(nèi)容進(jìn)行合并。

          我們將本地的 mater 分支和遠(yuǎn)程的 mater 分支進(jìn)行合并。

          首先可以執(zhí)行 git diff origin/mater 看一下和遠(yuǎn)程倉庫代碼的區(qū)別。

          然后可以執(zhí)行 git merge origin/master 將剛剛拉下來的遠(yuǎn)端分支和當(dāng)前分支合并。

           ORIG_HEAD              |   1 +
           index                  | Bin 396 -> 468 bytes
           logs/HEAD              |   1 +
           logs/refs/heads/master |   1 +
           refs/heads/master      |   2 +-
           5 files changed, 4 insertions(+), 1 deletion(-)

          可以看到 index 文件進(jìn)行了更新,也就是更新了暫存區(qū)。refs/heads/master 文件進(jìn)行了更新,也就是將 mater 分支指向了最新的 commit 對象。查看 refs/heads/master 文件中的內(nèi)容是 b1be33b8a27e1fcfa2c9f01b0fcb76a28b091071 ,和我們剛剛 FETCH_HEAD 中遠(yuǎn)端 mater 分支的 commit 對象的 hash 值一致。新增的 ORIG_HEAD 文件是 HEAD 的備份。

          這種合并方式屬于 Fast Forward,合并的時候直接將 mater 分支指向了最新的提交。是因為要合并過來的分支是之前 mater 分出去的,并且分出去之后 mater 分支沒有再產(chǎn)生新的 commit 對象,也就是下面的情況。

                 ------------ origin/master
                /
          -----master

          這種情況合并的話,直接把 mater 指向 origin/master 即可。

          還有另外一種情況,如下圖。

                 ------------ origin/master
                /
          -------------master

          分出去以后,mater 分支又進(jìn)行了幾次提交,此時我們再執(zhí)行 git merge origin/master 看一下會是什么情況。

          Merge remote-tracking branch 'origin/master'
          # Please enter a commit message to explain why this merge is necessary,
          # especially if it merges an updated upstream into a topic branch.
          #
          # Lines starting with '#' will be ignored, and an empty message aborts
          # the commit.
          ~                  

          此時會進(jìn)入一個編輯文件,讓我們填寫 commit 對象的信息,填寫退出后,文件變化如下。

          ORIG_HEAD                                         |   2 +-
          index                                             | Bin 468 -> 468 bytes
          logs/HEAD                                         |   1 +
          logs/refs/heads/master                            |   1 +
          objects/09/1d34a18801e88da14b609d29ac6d6ee8ea9079 | Bin 0 -> 209 bytes
          objects/dd/c0e866456daf833034119e6a797eb63614cb4a | Bin 0 -> 178 bytes
          refs/heads/master                                 |   2 +-

          相比之前的 Fast Forward 模式,這里我們相當(dāng)于多進(jìn)行了一個 commit 操作,增加了 tree 對象和 commit 對象。

          而且這個 commit 對象比較特殊,它有兩個 parent 對象, 通過命令 git cat-file -p 091d 來看一下。

          tree ddc0e866456daf833034119e6a797eb63614cb4a
          parent b9ba3b492e1fad20acd003ea4dad463a012b357a
          parent 5f4cfe0f32a1580924a1b5a2d2673d6e6b13639c
          author windliang <6489178757@qq.com> 1595149755 +0800
          committer windliang <6489178757@qq.com> 1595149755 +0800

          Merge remote-tracking branch 'origin/master'

          所以最后合并后的情況相當(dāng)于下邊的樣子:

                 ------------ origin/master
                /            \
                              - master
          -------------      /    

          git pull

          dev 分支下執(zhí)行 git pull 命令。

          理解了 git fetchgit mergegit pull 就好說了。它相當(dāng)于先執(zhí)行 git fetch 拉取下了所有分支,然后再執(zhí)行 git merge FETCH_HEADFETCH_HEAD 就是當(dāng)前分支跟蹤的遠(yuǎn)程分支的 commit 對象的 HASH 值。它怎么知道當(dāng)前分支追蹤的遠(yuǎn)程分支是哪個呢?就是我們之前 git push 設(shè)置的。

          [branch "dev"]
              remote = origin
              merge = refs/heads/dev

          這樣的話,如下所示,FETCH_HEAD 文件第一行存儲的是遠(yuǎn)端 dev 分支的 commit 對象的 HASH 值,做為 FETCH_HEAD 的引用,用于接下來的 git merge FETCH_HEAD 操作。

          cc28c96446ecf074671a0521c917025d5942ef9c        branch 'dev' of github.com:wind-liang/learnGit
          0932e6eb2fc387f7eeb94147a98cf16f8a0c27b8    not-for-merge   branch 'master' of github.com:wind-liang/learnGit

          如果我們執(zhí)行git branch -u origin/master ,讓當(dāng)前分支 dev 去追蹤遠(yuǎn)程倉庫的 master 分支。此時再執(zhí)行 git fetchFETCH_HEAD 第一行記錄的就是遠(yuǎn)程倉庫 mater 分支了。

          a2837cd3f45d3f9ebe7d5d6a3f90ff633938a40b        branch 'master' of github.com:wind-liang/learnGit
          79d8411ee2466e7e6f361a18601b81d5b7a98156    not-for-merge   branch 'dev' of github.com:wind-liang/learnGit

          上邊是 git pull/fetch 的默認(rèn)操作,git pull 的完整格式為 git pull <遠(yuǎn)程主機(jī)名> <遠(yuǎn)程分支名>:<本地分支名>。如果不指定本地分支名,則默認(rèn)為當(dāng)前分支。

          如果我們指定了遠(yuǎn)程的分支,執(zhí)行git pull origin mater ,就相當(dāng)于先執(zhí)行 git fetch origin master,此時就不會拉取所有分支,FETCH_HEAD 就指向這個唯一拉下來的分支了。

          總結(jié)

          主要從兩個角度介紹了 git ,一方面介紹了 .git 目錄中每個文件的作用以及相關(guān)的底層命令,另一方面介紹了常用的一些命令對 .git 目錄的影響。花了不少時間總結(jié)下來,自己對 git 有了更深的理解,希望對大家也能夠有所幫助。

          參考鏈接:

          [Git維基百科](https://en.wikipedia.org/wiki/Git)

          [Git 10 周年訪談:Linus Torvalds 講述背后故事](https://linuxstory.org/10-years-of-git-an-interview-with-git-creator-linus-torvalds/)

          [10 Years of Git: An Interview with Git Creator Linus Torvalds](https://www.linux.com/news/10-years-git-interview-git-creator-linus-torvalds/)


          [Torvalds on Version Control: Git good, SVN terrible!](https://www.zensoftware.nl/nl/3412-2/)


          [探秘git隱藏文件夾](https://cloud.tencent.com/developer/article/1565474)


          [圖解git原理的幾個關(guān)鍵概念](https://tonybai.com/2020/04/07/illustrated-tale-of-git-internal-key-concepts/) 


           [Git 內(nèi)部原理 - 底層命令與上層命令]([https://git-scm.com/book/zh/v2/Git-%E5%86%85%E9%83%A8%E5%8E%9F%E7%90%86-%E5%BA%95%E5%B1%82%E5%91%BD%E4%BB%A4%E4%B8%8E%E4%B8%8A%E5%B1%82%E5%91%BD%E4%BB%A4](https://git-scm.com/book/zh/v2/Git-內(nèi)部原理-底層命令與上層命令))


          [Which are the plumbing and porcelain commands?](https://stackoverflow.com/questions/39847781/which-are-the-plumbing-and-porcelain-commands)


          [Git 是怎樣生成 diff 的:Myers 算法](https://cjting.me/2017/05/13/how-git-generate-diff/)


          [Git 原理入門](http://www.ruanyifeng.com/blog/2018/10/git-internals.html)


          [.git文件夾探秘,理解git運作機(jī)制](https://developer.aliyun.com/article/716483)


          [Use  of index and Racy Git problem](https://github.com/git/git/blob/master/Documentation/technical/racy-git.txt?spm=a2c6h.12873639.0.0.592469418daUIL&file=racy-git.txt)


          [Git index format](https://github.com/git/git/blob/master/Documentation/technical/index-format.txt?spm=a2c6h.12873639.0.0.592469418daUIL&file=index-format.txt)


          [Refs and the Reflog](https://www.atlassian.com/git/tutorials/refs-and-the-reflog)


          [Git中的push和pull的默認(rèn)行為](Git中的push和pull的默認(rèn)行為)

          瀏覽 94
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91无码电影 | 仓井空一区二区 | 三级在线视频播放 | 操操操操操操操操操操操操操操操操操逼 | 青青操在线|