20 分鐘教你搞懂 Git!

2021-02-14 CSDN

Git 是最流行的版本管理工具,也是程式設計師必備的技能之一。本文就來教你 20 分鐘搞懂 Git!

以下為譯文:

儘管每天你都會用到Git,但也有可能搞不懂它的工作原理。為什麼Git可以管理版本?基本命令git add和git commit到底在幹什麼?

在這篇文章中,我將用一個例子來解釋Git的運行過程,幫助你理解Git的工作原理。

初始化

讓我們創建一個項目的目錄,然後進入該目錄。

$ mkdir git-demo-project
$ cd git-demo-project

如果想管理項目的版本,那麼我們應該做的第一件事情就是通過git init初始化。

git init只做了一件事情,那就是在項目的根目錄下創建.git子目錄來保存版本信息。

$ ls .git

branches/
config
description
HEAD
hooks/
info/
objects/
refs/

上述命令顯示了.git子目錄中的內容。

保存對象

接下來讓我們創建一個新的空文件test.txt。

然後把這個文件添加到Git代碼庫中,這一步將創建test.txt現有內容的一個副本。

$ git hash-object -w test.txt

e69de29bb2d1d6434b8b29ae775ad8c2e48c5391

在上述代碼中,git hash-object命令將test.txt現有的內容壓縮成二進位文件,並保存到Git中。該壓縮文件叫做Git對象,保存在.git/objects目錄中。

我們可以通過這個命令根據對象的文件名獲取當前內容,並計算成SHA1 哈希(長度為40的字符串)。讓我們看看下列新生成的Git對象文件。

$ ls -R .git/objects

.git/objects/e6:
9de29bb2d1d6434b8b29ae775ad8c2e48c5391

如上述代碼所示,.git/objects目錄下又多出了一個子目錄,而且這個子目錄名是上述哈希值的前兩個字符。在這個子目錄下有一個文件,文件名是上述哈希值中其餘的38個字符。

讓我們再來看看文件內容。

$ cat .git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391

上述代碼輸出的文件內容是一些二進位字符。你可能會問既然test.txt是空文件,又怎麼會有這些內容呢?這是因為該二進位對象中還存儲了一些元數據。

如果你想看看該文件原始的文本內容,那麼應該使用git cat-file。

$ git cat-file -p e69de29bb2d1d6434b8b29ae775ad8c2e48c5391

因為原文件為空,所以上述命令什麼都沒有顯示。現在我們往test.txt文件中寫點東西。

$ echo 'hello world' > test.txt

這個文件的內容已經改變了,所以你需要再次把它保存為Git對象。

$ git hash-object -w test.txt

3b18e512dba79e4c8300dd08aeb37f8e728b8dad

如上述代碼所示,test.txt的哈希值已經隨著文件內容的改變而發生了變化。同時還生成了新文件.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad。現在你可以看到這個文件的內容了。

$ git cat-file -p 3b18e512dba79e4c8300dd08aeb37f8e728b8dad

hello world

更新索引

當文件保存成二進位對象以後,你需要告訴Git哪個文件發生了變化。Git會在一個名叫「索引」(或階段)的區域記錄所有發生了變化的文件。然後等到所有的變更都結束後,將索引中的這些文件一起寫入正式的版本歷史記錄中。

$ git update-index 
3b18e512dba79e4c8300dd08aeb37f8e728b8dad test.txt

上述命令記錄了文件名test.txt、二進位對象名(哈希值)以及索引中文件的訪問權限。

git ls-files命令可以顯示索引中當前的內容。

$ git ls-files --stage

100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0   test.txt

上述代碼顯示索引中只有一個test.txt文件,還顯示了該文件的二進位對象名和訪問該文件的權限。如果你知道該二進位對象名,就可以查看.git/objects子目錄中該文件的內容。

git status命令可以輸出更多可讀的結果。

$ git status

Changes to submit:
    The new file:   test.txt

上述代碼顯示索引中只有一個新文件test.txt,該文件正在等候寫入版本的歷史記錄中。

git add命令

針對每個文件執行上述兩個步驟非常繁瑣。所以Git提供了git add命令來簡化這些操作。

上述命令相當於針對當前項目中所有發生了變化的文件執行上述兩個步驟。

提交(Commit)

索引保存發生了變化的文件信息。等到修改完成,所有這些信息都會被寫入版本的歷史記錄中,這相當於生成一個當前項目的快照。

項目的歷史記錄由不同時間點的項目快照組成。Git可以將項目恢復成任何一個快照。在Git中「快照」有一個專門的術語,即「提交」(commit)。所以生成快照也可以稱之為完成提交。

下列所有「快照」的引用指的都是提交。

完成提交

首先,我們需要設置用戶名和郵件地址。在你保存快照的時候,Git需要記錄是誰執行的提交。

$ git config user.name "username" 
$ git config user.email "Email address"

接下來,保存現有的目錄結構。在本文的前面我們討論了保存對象只會保存一個文件,並不會記錄文件之間的目錄結構。

git write-tree命令可以根據當前目錄結構生成一個Git對象。

$ git write-tree

c3b8bb102afeca86037d5b5dd89ceeb0090eae9d

在上述代碼中,目錄結構保存成了二進位對象,而對象的名字是哈希值。它也保存在.git/objects目錄中。

讓我們來看看該文件的內容。

$ git cat-file -p c3b8bb102afeca86037d5b5dd89ceeb0090eae9d

100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad    test.txt

可以看到,當前目錄中只有一個文件test.txt。

這個所謂的快照就是保存當前的目錄結構,以及每個文件相對應的二進位對象。之前的操作已經保存了文件結構,所以現在你需要把這個目錄結構和一些元數據一起寫入版本的歷史記錄中。

git commit-tree可以將目錄樹對象寫入到版本的歷史記錄中。

$ echo "first commit" | git commit-tree c3b8bb102afeca86037d5b5dd89ceeb0090eae9d

c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa

在上述代碼中,在提交時你需要提供提交的描述,而且你可以通過echo "first commit"提供提交描述。git commit-tree命令會根據元數據以及目錄樹生成一個Git對象。現在,讓我們來看看該對象的內容。

$ git cat-file -p c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa

tree c3b8bb102afeca86037d5b5dd89ceeb0090eae9d
author jam  1538889134 +0800
committer jam  1538889134 +0800

first commit

在上述代碼中,第一行輸出是對應於該快照的目錄樹對象,而第二行和第三行是有關作者和提交者的信息,最後一行內容是提交的描述。

通過git log命令我們還可以查看某個快照的信息。

$ git log 

commit c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa
Author: jam 
Date:   Sun Oct 7 13:12:14 2018 +0800

    first commit

 test.txt | 1 +
 1 file changed, 1 insertion(+)

git commit命令

Git提供了git commit來簡化上述提交操作。在保存到索引後,你只需要執行git commit命令,就可以同時提交目錄結構和描述,並生成快照。

$ git commit -m "first commit"

另外,還有兩個命令也非常實用。

通過git checkout命令,我們可以切換到某個快照。

$ git checkout c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa

通過git show命令,我們可以顯示某個快照的所有代碼變更。

$ git show c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa

分支(branch)

然而,如果你使用git log命令來查看整個版本的歷史記錄時,卻無法看到剛剛生成的快照。

上述命令輸出為空。這是為什麼?這個快照剛剛不是寫入到歷史記錄中了嗎?

真相是:git log命令只可以顯示當前分支上的變化。儘管我們已經提交了這個快照,但是還沒有記錄這個快照屬於哪個分支。

分支是快照的指針,分支的名字就是該指針的名字。雖然哈希值不可讀,但是分支允許用戶給快照起別名。另外,分支還會自動更新,如果當前分支是一個新的快照,那麼這個指針會自動指向它。例如,主分支(master branch)有一個名為master的指針指向主分支當前的快照。

用戶可以為任何快照創建新指針。例如,如果你想創建一個新的fix-typo分支,那麼只需創建一個名為fix-typo的指針,並指向一個快照。因此,在Git中創建一個新分支非常容易,而且開銷非常低。

Git有一個特殊的指針HEAD,它始終指向當前分支中最新的那個快照。另外,Git還提供了快捷方式。例如,HEAD^指向HEAD之前的快照(父節點),而HEAD~6指向HEAD之前的第六個快照。

每個分支的指針都是一個文本文件,存儲在.git/refs/heads/目錄中。文件的內容是它指向的快照的二進位文件名(哈希值)。

更新分支

下面我們將演示如何更新分支。首先,修改test.txt。

$ echo "hello world again" > test.txt

然後保存二進位對象。

$ git hash-object -w test.txt

c90c5155ccd6661aed956510f5bd57828eec9ddb

接下來,將該對象寫入索引,並保存目錄結構。

$ git update-index test.txt
$ git write-tree

1552fd52bc14497c11313aa91547255c95728f37

最後,提交目錄結構,並生成一個快照。

$ echo "second commit" | git commit-tree 1552fd52bc14497c11313aa91547255c95728f37 -p c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa

785f188674ef3c6ddc5b516307884e1d551f53ca

在上述代碼中,我們可以通過git commit-tree命令的參數-p來指定父節點,即以哪個快照為基礎。

下面我們把快照的哈希值寫入到.git/refs/heads/master文件中,並讓master指針指向該快照。

$ echo 785f188674ef3c6ddc5b516307884e1d551f53ca > .git/refs/heads/master

現在,通過git log命令你可以看到兩個快照了。

$ git log

commit 785f188674ef3c6ddc5b516307884e1d551f53ca (HEAD -> master)
Author: jam 
Date:   Sun Oct 7 13:38:00 2018 +0800

    second commit

commit c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa
Author: jam 
Date:   Sun Oct 7 13:12:14 2018 +0800

    first commit

git log命令的運行過程大致如下:

找到HEAD指針對應的分支。在上述示例中為master。

找到master指針指向的快照。在上述示例中為785f188674ef3c6ddc5b516307884e1d551f53ca。

找到父節點(即前一個快照)c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa。

等等,最後顯示當前分支中所有的快照。

另外,上述我們曾提到分支指針是動態的,下述三個命令會自動覆蓋分支指針。

Git commit:當前分支的指針將移動到新創建的快照上。

Git pull:在當前分支和遠程分支合併後,指針會指向新創建的快照。

Git reset [commit_sha]:當前分支的指針將被復位到某個指定的快照上。

原文:https://www.tutorialdocs.com/article/how-git-works.html

作者:Alex

譯者:彎月,責編:郭芮

相關焦點

  • 30分鐘教你學會Git
    3.創建一個新倉庫 – git initgit會把所有文件以及歷史記錄直接記錄成一個文件夾保存在你的項目中。創建一個新的倉庫,首先要去到項目路徑下,執行git init。這時Git會創建一個隱藏的文件夾.git,所有的歷史和配置信息都儲存在其中。
  • 1小時搞懂 Git 版本控制
    當時因為需要做一個項目,所以他教我如何使用 Git 將寫好的代碼推送到 GitHub 上,然後再從遠程倉庫拉到本地。起初因為沒有接觸過 Git,覺得這玩意很難學,又是一大堆命令需要記憶,在他教我的時候內心是牴觸的,當時覺得為什麼不把寫好的代碼發送給我呢?你是否也有過這樣的疑問呢?學習 Git 的時候,因為沒有和他認真學,在他教過我一遍之後還是一臉懵逼,寫命令的時候也是不時地回頭查看。
  • 7000+ 字帶你全面搞懂 Git 命令+原理!
    Index/Stage:暫存區,一般存放在 .git目錄下,即.git/index,它又叫待提交更新區,用於臨時存放你未提交的改動。比如,你執行git add,這些改動就添加到這個區域啦。Repository:本地倉庫,你執行git clone 地址,就是把遠程倉庫克隆到本地倉庫。
  • 一篇文章,教你學會Git
    暫存區標記了你當前工作區中,哪些內容是被git管理的。當你完成某個需求或功能後需要提交到遠程倉庫,那麼第一步就是通過 git add 先提交到暫存區,被git管理。保存了對象被提交 過的各個版本,比起工作區和暫存區的內容,它要更舊一些。
  • 20 個最常用的 Git 命令用法說明及示例
    在這篇文章中,我將介紹在使用 Git 時最常使用的 20 個命令。git checkout用法:git checkout [branch name]你可以通過該命令切換分支。用法:git checkout -b [branch name]你可以通過該命令創建一個分支,並切換到新分支上。
  • 30 分鐘 git 命令入門到放棄
    1.安裝Git安裝git非常直接:sudo apt-get install git-allbrew install git如果你是在是先用圖形工具的話,那麼推薦你使用Github desktop,Sourcetree。但我還是推薦你使用命令行,下面的內容就都是命令行的。
  • 一張腦圖帶你徹底掌握Git
    一張腦圖帶你徹底掌握Git北遊小屋2020-10-28 22:03:39首先我們的了解Git通常的操作流程,網上流行的不錯一張圖Git腦圖基本概念基於上面的圖,我們就有接下來一些概念版本庫.git當我們使用git管理文件時,比如git init時,這個時候,會多一個.git文件,我們把這個文件稱之為版本庫
  • 10種Git技巧,讓你省時省力又省心!
    全文共1800字,預計學習時長4分鐘圖片來源:unsplash.com/@sam_truong1. 從另一分支切換單個文件大家是否曾銷毀文件只為能重新開始? 或需要在另一分支的某文件中進行更改?而git指令可以實現從另一分支切換文件。
  • 你不知道的Git
    這時當他想更新代碼,老李說我更新代碼了你先pull一下,於是就產生了上面的問題!!!產生原因:多人操作場景,其中一人將代碼提交到遠程git,另一個人也修改了文件準備pull的時候會產生該問題;解決方案一:保留本地新修改的代碼。
  • 困擾你的Git操作?
    upstream:指上遊,fork 別人項目的地址,別人的項目就是你的上遊,即上一層源文件。master:指主分支。pull:指拉代碼,把你 Github 上的遠程倉庫拉到本地計算機上。push:指提交代碼,把你本機上的項目提交到 Github 上的遠程倉庫。
  • git環境配置和.gitconfig配置文件詳解
    為此今天蟲蟲專門撰寫本文來介紹下git的基本配置,以幫助大家解疑你遇到一些莫名其妙的問題,答惑為什麼會出現這樣的問題。而實際中這些的出現僅僅是因為你使用了不一樣的配置,而你卻不知道。git的配置文件當你在git伺服器Web管理頁面創建完第一個項目的時候,接下來的一個頁面會提示你做一些基本的git配置,比如github的提示頁面如下:你按照提示這些命令中有一個藍色標註的git remote add命令。實際提到這些命令配置還比較繁瑣,你可以用clone在你的機器上直接創建一個空項目,忽略到上面提示中這許多步驟。
  • 30分鐘吃掉Git和GitHub常用操作
    配置用戶信息git config --global user.name "XX"git config --global user.email "XX@XX"新建文件夾並切入mkdir git-learncd git-learn創建倉庫git init新建readme.txtecho "hello
  • Git cherry-pick 這個命令你會經常用到!
    概述git cherry-pick可以理解為」挑揀」提交,它會獲取某一個分支的單筆提交,並作為一個新的提交引入到你當前分支上。 當我們需要在本地合入其他分支的提交時,如果我們不想對整個分支進行合併,而是只想將某一次提交合入到本地當前分支上,那麼就要使用git cherry-pick了。
  • Git入門到高級系列2-git高級操作
    v1.4tag v1.4Tagger: Ben Straub <ben@straub.cc>Date: Sat May 3 20:19:12 2014 -0700my version 1.4commit ca82a6dff817ec66f44342007202690a93763949Author: Scott Chacon <schacon@gee-mail.com>Date
  • 如何規範你的Git commit?
    如果你的修改影響了不止一個scope,你可以使用*代替。subject是commit目的的簡短描述,不超過50個字符。用戶可以在項目根目錄的.git目錄下面配置使用,也可以配置全局git template用於個人pc上的所有git項目使用。服務端hook又分為pre-receive、post-receive、update,主要在服務端接受提交對象時進行調用。以上這種採用webhook的形式對git commit進行監控就是一種server端的hook,相當於post-receive。
  • 【譯文】Git merge 和 Git rebase比較
    但如果使用得當的話,它能給你的團隊開發省去太多煩惱。在這篇文章中,我們會比較git rebase和類似的git merge命令,找到Git工作流中rebase的所有用法。概述   你要知道的第一件事是,git rebase 和git merge 做的事其實是一樣的。它們都被設計來將一個分支的更改併入另一個分支,只不過他們實現方式有些不同。
  • 詳細介紹下git中的多種撤銷
    手把手教你入門git,下面我們再介紹下git中關於各種撤銷的操作流程。當你在git倉庫中進行一次新的提交的時,git保存這個特定時間的倉庫的快照,並生產一個唯一串與本次提交相對應。之後,就可以利用 git返回到項目的一個早期版本。下面我們說一些常見的撤銷常見場景,以及 git的命令操作步驟。
  • Git版本管理完全指南—學好Git一文足矣
    3. git stash pop 恢復暫存並刪除暫存記錄4. git stash list 查看暫存列表5. git stash drop 暫存名(例:stash@{0})  移除某次暫存6. git stash clear 清除暫存3、回退操作1. git reset 2. git reset 3. git checkout
  • 前端面試題:git reset、git revert 和 git checkout 有什麼區別
    三個區的轉換關係以及轉換所使的命令如下圖git reset、git revert 和 git checkout的共同點:來撤銷代碼倉庫中的某些更改。然後是不同點: 先,從 commit 層來說: git reset 可以將個分的末端指向之前的個 commit。然後再下次 git 執垃圾回收的時候,會把這個 commit之後的 commit 都扔掉。
  • Git 的奇技淫巧
    而 「版本管理工具」 能記錄每次的修改,只要提交到版本倉庫,你就可以找到之前任何時刻的狀態(文本狀態)。下面的內容就是列舉了常用的 Git 命令和一些小技巧,可以通過 "頁面內查找" 的方式進行快速查詢:Ctrl/Command+f。如果之前未使用過 Git,可以學習 Git 小白教程[2]入門。