1. 程式人生 > >廖雪峰老師Git教程程式碼梳理

廖雪峰老師Git教程程式碼梳理

建立版本庫 

建立一個版本庫非常簡單,首先,選擇一個合適的地方,建立一個空目錄(repository):

$ mkdir learngit  //建立learngit目錄
$ cd learngit  //切換當前目錄為learngit目錄
$ pwd    //用於顯示當前目錄
/Users/michael/learngit  //最終路徑

第二步,通過git init命令把這個目錄變成Git可以管理的倉庫:

$ git init
Initialized empty Git repository in /Users/michael/learngit/.git/

瞬間Git就把倉庫建好了。如果你沒有看到子目錄.git

,那是因為這個目錄預設是隱藏的,用ls -ah命令就可以看見。

把檔案新增到版本庫

先編寫一個readme.txt檔案,內容如下:

Git is a version control system.
Git is free software.

一定要放到learngit目錄下(子目錄也行),因為這是一個Git倉庫,放到其他地方Git再厲害也找不到這個檔案。

第一步,用命令git add告訴Git,把檔案新增到倉庫:

$ git add readme.txt

第二步,用命令git commit告訴Git,把檔案提交到倉庫:

$ git commit -m "wrote a readme file"
[master (root-commit) eaadf4e] wrote a readme file
 1 file changed, 2 insertions(+)
 create mode 100644 readme.txt

簡單解釋一下git commit命令,-m後面輸入的是本次提交的說明,可以輸入任意內容,當然最好是有意義的,這樣你就能從歷史記錄裡方便地找到改動記錄。

git commit命令執行成功後會告訴你,1 file changed:1個檔案被改動(我們新新增的readme.txt檔案);2 insertions:插入了兩行內容(readme.txt有兩行內容)。

為什麼Git新增檔案需要addcommit一共兩步呢?因為commit可以一次提交很多檔案,所以你可以多次add不同的檔案,比如:

$ git add file1.txt
$ git add file2.txt file3.txt
$ git commit -m "add 3 files."

小結

現在總結一下今天學的兩點內容:

初始化一個Git倉庫,使用git init命令。

新增檔案到Git倉庫,分兩步:

  1. 使用命令git add <file>,注意,可反覆多次使用,新增多個檔案;
  2. 使用命令git commit -m <message>,完成。

 我們繼續修改readme.txt檔案,改成如下內容:

Git is a distributed version control system.
Git is free software.

現在,執行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 status命令可以讓我們時刻掌握倉庫當前的狀態,上面的命令輸出告訴我們,readme.txt被修改過了,但還沒有準備提交的修改。

看看具體修改了什麼內容,需要用git diff這個命令看看:

$ git diff readme.txt 
diff --git a/readme.txt b/readme.txt
index 46d49bf..9247db6 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,2 +1,2 @@
-Git is a version control system.
+Git is a distributed version control system.
 Git is free software.

git diff顧名思義就是檢視difference,顯示的格式正是Unix通用的diff格式,可以從上面的命令輸出看到,我們在第一行添加了一個distributed單詞。

提交修改和提交新檔案是一樣的兩步,第一步是git add

$ git add readme.txt

同樣沒有任何輸出。在執行第二步git commit之前,我們再執行git status看看當前倉庫的狀態:

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

    modified:   readme.txt

git status告訴我們,將要被提交的修改包括readme.txt,下一步,就可以放心地提交了:

$ git commit -m "add distributed"
[master e475afc] add distributed
 1 file changed, 1 insertion(+), 1 deletion(-)

提交後,我們再用git status命令看看倉庫的當前狀態:

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

Git告訴我們當前沒有需要提交的修改,而且,工作目錄是乾淨(working tree clean)的。

小結

  • 要隨時掌握工作區的狀態,使用git status命令。

  • 如果git status告訴你有檔案被修改過,用git diff可以檢視修改內容。

 在Git中,我們用git log命令檢視歷史記錄

$ git log
commit 1094adb7b9b3807259d8cb349e7df1d4d6477073 (HEAD -> master)
Author: Michael Liao <[email protected]>
Date:   Fri May 18 21:06:15 2018 +0800

    append GPL

commit e475afc93c209a690c39c13a46716e8fa000c366
Author: Michael Liao <[email protected]>
Date:   Fri May 18 21:03:36 2018 +0800

    add distributed

commit eaadf4e385e865d25c48e7ca9c8395c3f7dfaef0
Author: Michael Liao <[email protected]>
Date:   Fri May 18 20:59:18 2018 +0800

    wrote a readme file

git log命令顯示從最近到最遠的提交日誌,我們可以看到3次提交,最近的一次是append GPL,上一次是add distributed,最早的一次是wrote a readme file

如果嫌輸出資訊太多,看得眼花繚亂的,可以試試加上--pretty=oneline引數:

$ git log --pretty=oneline
1094adb7b9b3807259d8cb349e7df1d4d6477073 (HEAD -> master) append GPL
e475afc93c209a690c39c13a46716e8fa000c366 add distributed
eaadf4e385e865d25c48e7ca9c8395c3f7dfaef0 wrote a readme file

需要友情提示的是,你看到的一大串類似1094adb...的是commit id(版本號)

readme.txt回退到上一個版本,也就是add distributed的那個版本:

首先,Git必須知道當前版本是哪個版本,在Git中,用HEAD表示當前版本,也就是最新的提交1094adb...(注意我的提交ID和你的肯定不一樣),上一個版本就是HEAD^,上上一個版本就是HEAD^^,當然往上100個版本寫100個^比較容易數不過來,所以寫成HEAD~100

現在,我們要把當前版本append GPL回退到上一個版本add distributed,就可以使用git reset命令:

$ git reset --hard HEAD^
HEAD is now at e475afc add distributed

看看readme.txt的內容是不是版本add distributed

$ cat readme.txt
Git is a distributed version control system.
Git is free software.

果然被還原了。

我們用git log再看看現在版本庫的狀態:

$ git log
commit e475afc93c209a690c39c13a46716e8fa000c366 (HEAD -> master)
Author: Michael Liao <[email protected]>
Date:   Fri May 18 21:03:36 2018 +0800

    add distributed

commit eaadf4e385e865d25c48e7ca9c8395c3f7dfaef0
Author: Michael Liao <[email protected]>
Date:   Fri May 18 20:59:18 2018 +0800

    wrote a readme file

最新的那個版本append GPL已經看不到了!好比你從21世紀坐時光穿梭機來到了19世紀,想再回去已經回不去了,腫麼辦?

辦法其實還是有的,只要上面的命令列視窗還沒有被關掉,你就可以順著往上找啊找啊,找到那個append GPLcommit id1094adb...,於是就可以指定回到未來的某個版本:

$ git reset --hard 1094a
HEAD is now at 83b0afe append GPL

版本號沒必要寫全,前幾位就可以了,Git會自動去找。當然也不能只寫前一兩位,因為Git可能會找到多個版本號,就無法確定是哪一個了。

再小心翼翼地看看readme.txt的內容:

$ cat readme.txt
Git is a distributed version control system.
Git is free software distributed under the GPL.

 可以!

現在,你回退到了某個版本,關掉了電腦,第二天早上就後悔了,想恢復到新版本怎麼辦?找不到新版本的commit id怎麼辦?

在Git中,總是有後悔藥可以吃的。當你用$ git reset --hard HEAD^回退到add distributed版本時,再想恢復到append GPL,就必須找到append GPL的commit id。Git提供了一個命令git reflog用來記錄你的每一次命令:

$ git reflog
e475afc [email protected]{1}: reset: moving to HEAD^
1094adb (HEAD -> master) [email protected]{2}: commit: append GPL
e475afc [email protected]{3}: commit: add distributed
eaadf4e [email protected]{4}: commit (initial): wrote a readme file

終於舒了口氣,從輸出可知,append GPL的commit id是1094adb,現在,你又可以乘坐時光機回到未來了。

 小結:

  • HEAD指向的版本就是當前版本,因此,Git允許我們在版本的歷史之間穿梭,使用命令git reset --hard commit_id

  • 穿梭前,用git log可以檢視提交歷史,以便確定要回退到哪個版本。

  • 要重返未來,用git reflog檢視命令歷史,以便確定要回到未來的哪個版本。

工作區和暫存區: 

撤銷修改:

場景1:當你改亂了工作區某個檔案的內容,想直接丟棄工作區的修改時,用命令git checkout -- file

場景2:當你不但改亂了工作區某個檔案的內容,還新增到了暫存區時,想丟棄修改,分兩步,第一步用命令git reset HEAD <file>,就回到了場景1,第二步按場景1操作。

場景3:已經提交了不合適的修改到版本庫時,想要撤銷本次提交,參考版本回退一節,不過前提是沒有推送到遠端庫。

刪除檔案:

一般情況下,你通常直接在檔案管理器中把沒用的檔案刪了,或者用rm命令刪了:

$ rm test.txt

這個時候,Git知道你刪除了檔案,因此,工作區和版本庫就不一致了,git status命令會立刻告訴你哪些檔案被刪除了:

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

    deleted:    test.txt

no changes added to commit (use "git add" and/or "git commit -a")

現在你有兩個選擇,一是確實要從版本庫中刪除該檔案,那就用命令git rm刪掉,並且git commit

$ git rm test.txt
rm 'test.txt'

$ git commit -m "remove test.txt"
[master d46f35e] remove test.txt
 1 file changed, 1 deletion(-)
 delete mode 100644 test.txt

現在,檔案就從版本庫中被刪除了。

 小提示:先手動刪除檔案,然後使用git rm <file>和git add<file>效果是一樣的。

另一種情況是刪錯了,因為版本庫裡還有呢,所以可以很輕鬆地把誤刪的檔案恢復到最新版本:

$ git checkout -- test.txt

但是要小心,你只能恢復檔案到最新版本,你會丟失最近一次提交後你修改的內容。 

git checkout其實是用版本庫裡的版本替換工作區的版本,無論工作區是修改還是刪除,都可以“一鍵還原”。

遠端倉庫(github)

在繼續閱讀後續內容前,請自行註冊GitHub賬號。由於你的本地Git倉庫和GitHub倉庫之間的傳輸是通過SSH加密的,所以,需要一點設定:

第1步:建立SSH Key。在使用者主目錄下(我的是C:\Users\lw),看看有沒有.ssh目錄,如果有,再看看這個目錄下有沒有id_rsaid_rsa.pub這兩個檔案,如果已經有了,可直接跳到下一步。如果沒有,在使用者主目錄下開啟Shell(Windows下滑鼠右鍵Git Bash),建立SSH Key:

$ ssh-keygen -t rsa -C "[email protected]"

你需要把郵件地址換成你自己的郵件地址,然後一路回車,使用預設值即可,由於這個Key也不是用於軍事目的,所以也無需設定密碼。

如果一切順利的話,可以在使用者主目錄裡找到.ssh目錄,裡面有id_rsaid_rsa.pub兩個檔案,這兩個就是SSH Key的祕鑰對,id_rsa是私鑰,不能洩露出去,id_rsa.pub是公鑰,可以放心地告訴任何人。

第2步:登陸GitHub,開啟“settings”,“SSH and GPG Keys”頁面:

然後,點“New SSH Key”,填上任意Title,在Key文字框裡貼上id_rsa.pub檔案的內容:

點“Add Key”,你就應該看到已經新增的Key:

為什麼GitHub需要SSH Key呢?因為GitHub需要識別出你推送的提交確實是你推送的,而不是別人冒充的,而Git支援SSH協議,所以,GitHub只要知道了你的公鑰,就可以確認只有你自己才能推送。

當然,GitHub允許你新增多個Key。假定你有若干電腦,你一會兒在公司提交,一會兒在家裡提交,只要把每臺電腦的Key都新增到GitHub,就可以在每臺電腦上往GitHub推送了。

新增遠端庫:

現在github上建立一個New Repository,成功之後頁面會提示:可以從這個倉庫克隆出新的倉庫,也可以把一個已有的本地倉庫與之關聯,然後,把本地倉庫的內容推送到GitHub倉庫。

現在,我們根據GitHub的提示,在本地的learngit倉庫下執行命令:

$ git remote add origin [email protected]:michaelliao/learngit.git

每個新倉庫都會有https或者ssh地址,複製其中一個替代 [email protected]:michaelliao/learngit.git

origin表示遠端庫,這是Git預設的叫法,也可以改成別的,但是origin這個名字一看就知道是遠端庫。

下一步,就可以把本地庫的所有內容推送到遠端庫上:

$ git push -u origin master(如果結果不是如下,請往下耐心點看!!)
Counting objects: 20, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (15/15), done.
Writing objects: 100% (20/20), 1.64 KiB | 560.00 KiB/s, done.
Total 20 (delta 5), reused 0 (delta 0)
remote: Resolving deltas: 100% (5/5), done.
To github.com:michaelliao/learngit.git
 * [new branch]      master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.

把本地庫的內容推送到遠端,用git push命令,實際上是把當前分支master推送到遠端。

由於遠端庫是空的,我們第一次推送master分支時,加上了-u引數,Git不但會把本地的master分支內容推送的遠端新的master分支,還會把本地的master分支和遠端的master分支關聯起來,在以後的推送或者拉取時就可以簡化命令。

SSH警告

當你第一次使用Git的clone或者push命令連線GitHub時,會得到一個警告:

The authenticity of host 'github.com (xx.xx.xx.xx)' can't be established.
RSA key fingerprint is xx.xx.xx.xx.xx.
Are you sure you want to continue connecting (yes/no)?

這是因為Git使用SSH連線,而SSH連線在第一次驗證GitHub伺服器的Key時,需要你確認GitHub的Key的指紋資訊是否真的來自GitHub的伺服器,輸入yes回車即可。

Git會輸出一個警告,告訴你已經把GitHub的Key新增到本機的一個信任列表裡了:

Warning: Permanently added 'github.com' (RSA) to the list of known hosts.

這個警告只會出現一次,後面的操作就不會有任何警告了。

如果你實在擔心有人冒充GitHub伺服器,輸入yes前可以對照GitHub的RSA Key的指紋資訊是否與SSH連線給出的一致。

小結

要關聯一個遠端庫,使用命令git remote add origin [email protected]:path/repo-name.git

關聯後,使用命令git push -u origin master第一次推送master分支的所有內容;

此後,每次本地提交後,只要有必要,就可以使用命令git push origin master推送最新修改;

 從遠端庫克隆:

獲取你要克隆的遠端庫地址,用命令git clone克隆一個本地庫:

$ git clone [email protected]:michaelliao/gitskills.git
Cloning into 'gitskills'...
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 3
Receiving objects: 100% (3/3), done.

注意把Git庫的地址換成你自己的,然後進入gitskills目錄看看,已經有README.md檔案了:

$ cd gitskills
$ ls
README.md

你也許還注意到,GitHub給出的地址不止一個,還可以用https://github.com/michaelliao/gitskills.git這樣的地址。實際上,Git支援多種協議,預設的git://使用ssh,但也可以使用https等其他協議。

使用https除了速度慢以外,還有個最大的麻煩是每次推送都必須輸入口令,但是在某些只開放http埠的公司內部就無法使用ssh協議而只能用https,通過ssh支援的原生git協議速度最快。

分支管理:

分支在實際中有什麼用呢?假設你準備開發一個新功能,但是需要兩週才能完成,第一週你寫了50%的程式碼,如果立刻提交,由於程式碼還沒寫完,不完整的程式碼庫會導致別人不能幹活了。如果等程式碼全部寫完再一次提交,又存在丟失每天進度的巨大風險。

現在有了分支,就不用怕了。你建立了一個屬於你自己的分支,別人看不到,還繼續在原來的分支上正常工作,而你在自己的分支上幹活,想提交就提交,直到開發完畢後,再一次性合併到原來的分支上,這樣,既安全,又不影響別人工作。

建立與合併分支:

Git鼓勵大量使用分支:

檢視分支:git branch

建立分支:git branch <name>

切換分支:git checkout <name>

建立+切換分支:git checkout -b <name>

合併某分支到當前分支:git merge <name>

刪除分支:git branch -d <name>

因為建立、合併和刪除分支非常快,所以Git鼓勵你使用分支完成某個任務,合併後再刪掉分支,這和直接在master分支上工作效果是一樣的,但過程更安全。