1. 程式人生 > 實用技巧 >六、快取查詢

六、快取查詢

git 是目前全世界最流行的分散式版本控制工具,與之前的集中化的版本控制系統相比,它有如下的優勢

  1. 專案管理更安全。集中式版本控制系統,只要伺服器崩潰,那所有人的程式碼都將丟失。git 的每個專案參與者都會在自己的本地儲存一份完整的程式碼備份,即便其中的一個或者多個電腦出現問題,依然可以通過其他人的程式碼恢復。
  2. 不需要聯網也可以管理。git 在本地有一套自己完整的版本管理機制,即便斷網程式設計師也可以在本地進行修改,新增或者刪除檔案,將自己的程式碼提交到本地的版本庫,所有的操作都被忠實得記錄在本地版本庫中,到時候只要聯網恢復,將本地的所有修改 push 到遠端的伺服器即可。

核心概念

git 的版本管理基於如下的概念框架,只有掌握這些概念才能理解它指令之後的真正含義。核心的 git 框架如下圖所示

我們先不用管其中的橫向的命令,僅僅關注其中的 4 個圓柱體,分別代表不同的實體,本地包括 workspace, index 和 local repository 三個部分,在伺服器端有 remote repository,下面分別詳細介紹它們

  • workspace, 本地的工作目錄,它是 Git 用來儲存元資料和物件資料庫的地方,新增,修改,刪除等操作就在這個目錄裡面進行;
  • index, 暫存區域,這是 git 的快取機制,在 workspace 的修改可以暫存在這個區域;
  • local repository,本地倉庫,我們將 index 區域的程式碼提交到本地倉庫,就完成一次完整的修改。
  • remote repository,遠端倉庫,這是遠端伺服器存放程式碼的地方,比如現在非常流行的
    GitHub
    ,可以將本地倉庫的程式碼推送到遠端倉庫,供其他人瀏覽和下載。

檔案狀態

workspace 裡面的檔案無外乎兩種狀態:已跟蹤或者未跟蹤。如果檔案已經被納入到版本管理中就為已跟蹤,直觀的理解就是之前在版本庫裡新增或者提交過該檔案,版本庫有該檔案的記錄,它已經感知到該檔案的存在;如果該檔案從未提交或者新增到版本庫中,那麼它就是未被跟蹤的。

如下圖所示,比如你在 workspace 裡面新建了一個檔案,之後沒有任何動作,此時該檔案是未跟蹤狀態,但是如果你將它新增到暫存區域,或者之後又提交到本地倉庫,那麼就變成了已跟蹤狀態。

基本流程

下面我們結合具體的例項,介紹下使用 git 在本地進行版本管理的流程,重點是熟悉基本的操作以及其中所用到的指令。我最近在學習 C++,需要根據《C++ primer plus》寫一些小程式進行練習,及時記錄學習的進度,所以我自己在本地新建一個 git 倉庫管理這些檔案。

建立 git 倉庫

git init         //建立新倉庫
git clone <url>  //從url上下載已有的倉庫

這是使用 git 的基礎,我們有兩種方式建立倉庫

  • 本地新建。我在本地建立資料夾 C++ primer plus,然後在 windows 下切換到該目錄下,使用敲入git init,命令列提示

    $ git init
    Initialized empty Git repository in D:/Study/C++ primer plus/.git/
    

初始化後,在當前目錄下會出現一個名為.git的目錄,所有 Git 需要的資料和資源都存放在這個目錄中。,自己不要去修改它

  • 遠端下載。我可如果想對某個開源專案出一份力,可以先把該專案的 Git 倉庫複製一份出來,這就需要用到 git clone 命令。比如我想將 ss-qt5 的程式碼拉下來,可以使用如下的指令git clone [email protected]:xxxsocks/xxxsocks-qt5.git,拉下來的基本格式是git clone [url],之後在我的本地電腦就有一份完整的該專案的程式碼

檢查檔案狀態

git status

在新增檔案之前,先介紹一個非常有用的命令git status,它可以檢視當前版本庫中的所有檔案狀態,包括已跟蹤或者未跟蹤的檔案。

git status會將 workspace 和暫存區的檔案比對,同時比對本地倉庫有暫存區的檔案。

在當前的目錄下,敲入該命令,輸出如下

$ git status
On branch master
nothing to commit, working directory clean

這個提示說明,現在的工作目錄相當乾淨,所以已經跟蹤的檔案在上次提交之後沒有被修改過。而且,在當前的目錄中沒有任何還沒有被跟蹤的新檔案,當前工作的分支是master,凡事本地建立分支預設都是這個名字,後續分支管理可以改變分支,下篇再介紹。

新增檔案

git add <file> //新增並跟蹤檔案file(的修改)
git add .      //將workspace中的所有檔案新增到暫存區

建立本地倉庫之後,就可以在倉庫裡面新增檔案了。我在倉庫裡面新建readme.txt檔案,並在檔案中寫入

This is the 1st line of the file.

使用git status檢視當前的窗臺狀態,顯示如下

$ git status
On branch master
Initial commit
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        readme.txt
nothing added to commit but untracked files present (use "git add" to track)

顯示可能會有差異,我將其中的空行去掉了,這段話表明當前檔案readme.txt還沒有被跟蹤 (untracked),當前沒有檔案需要新增,使用git add <file>將其跟蹤。那我們使用git add readme.txt跟蹤該檔案,命令列沒有任何迴應,表明一切正常。之後再次使用git status檢視如下

$ git status
On branch master
Initial commit
Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   readme.txt

顯示當前的readme.txt已經進入暫存區,該檔案可以被 committed。

git add 的潛臺詞就是把目標檔案快照放入暫存區域,也就是 add file into staged area,同時未曾跟蹤過的檔案標記為需要跟蹤。如果一次需要新增所有的檔案,可以使用git add .,其中的.表示當前工作區中的所有檔案。

我繼續修改readme.txt檔案,新增一行this is the 2nd line,之後再新增檔案test.cc,檢視檔案狀態,顯示如下

$ git status
On branch master

Initial commit

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

        new file:   readme.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   readme.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        test.cc

這裡有比較有意思的地方,test.cc與之前的readme.txt一樣,後來新加的檔案,因此它顯示該檔案未被跟蹤。這裡需要詳細說明一下readme.txt,現在在 workspace 和 index 的暫存區都有該檔案,但是兩個檔案的內容不同,workspace 中的是最新的修改版本,包含了第 2 行,但是 index 中的僅僅包含第一行(因為第 2 次修改之後還沒有git add),所以才會有兩條顯示資訊。

  1. 暫存區與本地版本庫相比,還有些修改 (第 1 行) 沒有 commit;

  2. workspace 與暫存區相比,還有些修改 (第 2 行) 沒有被跟蹤。

    所以,可以看出 git 記錄的是檔案的修改而不是檔案本身。現在使用git add readme.txt則將會把 workspace 中的新修改一併跟蹤並記錄在暫存區中,如下所示

$ git status
On branch master
Initial commit
Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   readme.txt
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        test.cc

此時的readme.txt已經是 to be committed 狀態了,之前的 workspace 中的修改已經被跟蹤了。

提交修改

提交修改就是將暫存區的檔案以及它們的修改提交到本地倉庫,使用git commit -m "balabla",該命令將所有的暫存區中的檔案提交到本地倉庫,雙引號中的字元是提交的說明,輸入該命令將readme.txt提交到本地倉庫,如下所示

git commit -m "1st commit"
[master 7aecba8] 1st commit
 2 files changed, 1 insertion(+), 1 deletion(-)
 create mode 100644 test.cc

表示暫存區的所有檔案已經提交,此時檢視狀態如下

git status
On branch master
nothing to commit, working directory clean

說明工作區很乾淨,workspace/index/local repository 三個區的所有東西都是一致的沒有任何的差異。

檢視提交歷史

git log  //檢視提交歷史
gitk     //圖形化介面檢視提交歷史

在下一節之前,先學會檢視本地的提交歷史,使用git log可以檢視本倉庫的從最近到最遠的所有提交歷史,如下圖所示【圖片暫缺】

$ git log
commit 1655db95f38ef46cf846e4e8c8abb58866c30433 (HEAD -> master)
Author: bugxch <[email protected]>
Date:   Sun Apr 15 09:57:25 2018 +0800

    2rd commit

commit 8e740248d2d72e4188468094c7b4c4ccb0b5e050
Author: bugxch <[email protected]>
Date:   Sun Apr 15 09:57:03 2018 +0800

    2nd commit

commit 2040c1ee8dc7bffd58fe0a206e97aa5a01db0275
Author: bugxch <[email protected]>
Date:   Sun Apr 15 09:56:14 2018 +0800

    1st commit

可以看出一共有 3 次提交,每個提交中包含自己的 commit_id,作者以及提交的具體時間。commit 之後的那一長串字母和數字,唯一標記每次 git 的提交,類似於 SVN 中的提交 id,之所以用這麼一長串的十六進位制的數字表示是為了避免很多人在同一版本庫中提交時候發生衝突。如果提交的次數很多,這麼看就比較長了,可以使用git log --pretty=oneline來檢視比較簡略的版本,如下所示

$ git log --pretty=oneline
1655db95f38ef46cf846e4e8c8abb58866c30433 (HEAD -> master) 2rd commit
8e740248d2d72e4188468094c7b4c4ccb0b5e050 2nd commit
2040c1ee8dc7bffd58fe0a206e97aa5a01db0275 1st commit

僅僅包含每次提交的 id 以及說明,看起來一目瞭然。此外還可以使用gitk使用圖形化介面檢視提交歷史,如下圖所示

撤銷修改

git checkout -- <file> //撤銷對workspace中檔案file的修改
git reset HEAD <file>  //撤銷對暫存區中檔案file的修改
git commit --amend     //撤銷本地倉庫的上一次提交,並編輯新的說明重新提交

如果發現程式碼修改有問題,就需要撤銷修改,這裡依照不同的儲存位置,分為如下三種情況。

撤銷 workspace 中的修改

我在readme.txt檔案中不小心添加了一行字,檔案內容如下

This is the 1st line of the file.
this is the 2nd lines
my boss is a idiot

幸好及時發現,我們現在要做的就是將最後一行去掉,依然使用git status檢視工作區的檔案,可以看到如下的結果

$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)
        modified:   readme.txt
no changes added to commit (use "git add" and/or "git commit -a")

明顯可以看出檔案的修改還沒有被跟蹤,而且提示我們使用git checkout -- <file>來放棄對工作區檔案的修改,那我們試試看輸入git checkout -- readme.txt,使用該命令後果然最後一行不見了。

撤銷暫存區中的修改

依然以上面的檔案為例,我在檔案末尾新增那一句my boss is an idiot,之後使用git add readme.txt新增到暫存庫,檢視狀態

$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   readme.txt

提示我可以使用git reset HEAD <file>放棄修改,使用該命令看看

$ git reset HEAD readme.txt
Unstaged changes after reset:
M       readme.txt
$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   readme.txt

可以看出修改又回覆到之前在工作區的狀態,繼續使用git checkout -- readme.txt徹底撤銷之前的修改。

撤銷本地倉庫中的修改

之前的撤銷相對比較容易,如果已經提交到了本地倉庫該撤銷修改呢?現在我們修改readme.txt檔案為如下內容

This is the 1st line of the file.
this is the 2nd lines
my boss is a idiot

然後git add .,git commit -m "hahahaha",這樣就提交到了本地倉庫,檢視提交歷史,如下所示

$ git log --pretty=oneline
498ebae5cbbb94a9fc1c549b1bd0cdc2fea5c886 hahahahaha
72b68e57aa9b18fcdd031d5901407a83c408580e revert the file
b250f9715a00d5f72caee1b6d0d7ab1d69e9c27b 2nd modification
27ed1eb9e7fb8a20176618e54c0464e721537da7 the 1st commit
abbd1c9e580a35aefe453615620d27ab464066c7 第一次提交readme.txt

程式碼已經提交了。可以使用git reset --hard HEAD^放棄這次修改。這條指令的解析如下

  • git reset,表示回退,這個指令既可以將暫存區的資料回退到工作區,也可以回退版本。
  • --hard,表示回退版本的同時,將工作區和暫存區的檔案內容也同步回退,這裡還有兩個選項,具體見下表
引數 意義
hard 僅僅回退版本,修改工作區和暫存區檔案
mixed 回退版本,修改暫存區檔案,不修改工作區
merge 回退版本,修改暫存區檔案,修改工作區中那些被跟蹤的檔案
soft 僅僅回退版本,不修改工作區和暫存區檔案
  • HEAD表示當前版本的指標所在,HAED^表示前一個版本,HEAD^^表示之前兩個版本,如果是前 100 個版本則可以使用HEAD~100

使用該命令之後的結果如下

$ git log --pretty=oneline
72b68e57aa9b18fcdd031d5901407a83c408580e revert the file
b250f9715a00d5f72caee1b6d0d7ab1d69e9c27b 2nd modification
27ed1eb9e7fb8a20176618e54c0464e721537da7 the 1st commit
abbd1c9e580a35aefe453615620d27ab464066c7 第一次提交readme.txt

此外,也可以直接指定版本號,回退到特定的版本,比如我想回退到最早的那個版本,可以使用如下的命令git reset --hard abbd1c,後面的版本號就是第一次提交的 commit_id 的前幾位,不必全部都寫全,系統可以區分其他版本即可。輸出結果如下

$ git reset --hard abbd1c
HEAD is now at abbd1c9 第一次提交readme.txt
$ git log --pretty=oneline
abbd1c9e580a35aefe453615620d27ab464066c7 第一次提交readme.txt

現在已經回退到之前最早的版本了。

誤刪怎麼辦

如果誤刪,可以使用git reflog,它記錄了我們所有的操作,可以看到之前的 commit_id 以及對應的說明,如下所示

$ git reflog
abbd1c9 HEAD@{0}: reset: moving to abbd1c
72b68e5 HEAD@{1}: reset: moving to HEAD^
498ebae HEAD@{2}: commit: hahahahaha
72b68e5 HEAD@{3}: reset: moving to HEAD^
461cb5a HEAD@{4}: commit: hahhaha
72b68e5 HEAD@{5}: reset: moving to HEAD^
7ea5d40 HEAD@{6}: commit: my boss hahaha
72b68e5 HEAD@{7}: commit: revert the file
b250f97 HEAD@{8}: commit: 2nd modification
27ed1eb HEAD@{9}: commit (amend): the 1st commit
7aecba8 HEAD@{10}: commit: 1st commit
abbd1c9 HEAD@{11}: reset: moving to abbd1c
3325ee6 HEAD@{12}: commit: 1st commit
52c2155 HEAD@{13}: commit: 1st commit
abbd1c9 HEAD@{14}: commit (initial): 第一次提交readme.txt

如果要恢復到hahahaha的版本,可以使用如下的指令

$ git reset --hard 461cb5a
HEAD is now at 461cb5a hahhaha

$ git log --pretty=oneline
461cb5a8f35e5dc2b8dd4917d825b1b2d3c9bce3 hahhaha
72b68e57aa9b18fcdd031d5901407a83c408580e revert the file
b250f9715a00d5f72caee1b6d0d7ab1d69e9c27b 2nd modification
27ed1eb9e7fb8a20176618e54c0464e721537da7 the 1st commit
abbd1c9e580a35aefe453615620d27ab464066c7 第一次提交readme.txt

修改成功~


所有上面的操作可以總結在如下的一張圖裡

參考資料