如果你想精通Git,直接到 Git官網 把這本ProGit掌握已足以Pro Git

此文主要介紹一切開發中常用的git命令和一些配置技巧(諸如git別名配置,log列印技巧,版本回退以及分支管理等)。

後來我又寫了篇主要介紹Git變基的文章《開發中關於Git那些事(Git Rebasing)》,有興趣的可以看看。

1. 簡介


Git與SVN相比而言,Git的好處自然不用多說,Git完全分散式檔案管理系統,加上其簡單速度,可以高效管理類似 Linux 核心一樣的超大規模專案。完全分散式的系統,讓你可以在公交車上,火車上,家中,甚至在廁所都可以敲程式碼。何時何地你都可以敲程式碼,甚至不需要網路。好不好使,開不開心?換是SVN,SVN伺服器掛了,全部人停止敲程式碼,停下來八卦去吧。

Git 和其他版本控制系統的主要差別在於,Git 只關心檔案資料的整體是否發生變化,而大多數其他系統則只關心檔案內容的具體差異。這類系統(CVS,Subversion,Perforce,Bazaar 等等)每次記錄有哪些檔案作了更新,以及都更新了哪些行的什麼內容。如同下圖所示:

但是,Git 並不儲存這些前後變化的差異資料。實際上,Git 更像是把變化的檔案作快照後,記錄在一個微型的檔案系統中。每次提交更新時,它會縱覽一遍所有檔案的指紋資訊並對檔案作一快照,然後儲存一個指向這次快 照的索引。為提高效能,若檔案沒有變化,Git 不會再次儲存,而只對上次儲存的快照作一連線。工作方式類似下圖:

對比可以發現,Git高效也在情理之中。

現在再簡單介紹一下Git管理下檔案的三種狀態。對於任何一個檔案,只要在Git管理下,那麼該檔案只有三種狀態:已修改(modified),已暫存(staged)和已提交 (committed)。

已修改(modified): 檔案被修改,但是還沒有提交儲存(也就是沒有使用git add,此時使用git status顯示為紅色)。

已暫存(staged): 已修改的檔案放入下次要提交的清單中(使用了git add後的狀態,此時使用git status顯示為綠色)。

已提交(committed): 該檔案已經被安全地儲存在本地資料庫中(使用了git commit後,此時使用git status已經不存在該檔案的任何資訊)。

具體可以參考下面兩幅圖來理解。

2. 配置使用者資訊


user和email,--global引數全域性配置,當然你也可以不加此引數,不同的專案用不同的使用者名稱和郵箱。

注意:一定要認真填寫郵箱,如果你專案在OSChina或則GitHub,郵箱應該和OSChina或則GitHub繫結的郵箱一致,不然之後就算你提交了程式碼,是看不到自己的貢獻度的。

git config --global user.name Super
git config --global user.email 1342449****@163.com

不過之後也是可以重寫已經提交的commit的使用者名稱和郵箱,但是一般情況下不要這樣操作,不提倡,重寫命令:

此命令會重寫所有commit,謹慎操作

git filter-branch -f --env-filter '
if [ "$GIT_AUTHOR_NAME" = "Super" ]
then
export GIT_AUTHOR_NAME="Super"
export GIT_AUTHOR_EMAIL="[email protected]"
fi
'

3. 配置全域性別名


此配置在開發中相當重要,尤其是對於使用Terminal,習慣使用命令列的朋友,由於git不支援tab自動補全,每次想要看下工作目錄 狀態都要git status,相當耗時。除非你能確定你敲兩個字母比六個字母用時少。

git config --global alias.st "status -s"
git config --global alias.br "branch"
git config --global alias.co "checkout"
git config --global alias.ci "commit -m"
git config --global alias.aci "commit -a -m" // 跳過使用暫緩區,直接將git add和git commit合併為一條命令
git config --global alias.lg "log --color --pretty=format:'%Cred%h - %Cgreen%an %C(yellow)| %ad | %Creset%s' --graph" //自定義log

第一條:git status是開發中使用最多最頻繁的,至於-s 是簡潔輸入(Give the output in the short-format)

第二條:此條也使用頻繁,但是我在開發中直接使用第三條跳過。

第三條:配置git aci 因為這樣直接跳過使用暫存區域,對於已經跟蹤的檔案,我不要再此次使用git add加入暫緩區,然後再git commit提交到本地資料庫,為了方便省事,直接將兩條命令合併為一條,使用git aci "提交說明" 即可。省不省事,用下自然知道。

第四條:這裡是我自定義的log資訊,當然,一會看完本文你也可以自己格式化成自己喜歡的log格式。可以先看下效果,下圖所示:


以後直接使用"git lg"即可,非常方便,顯示簡潔明瞭。
補充,也可以不加引號,比如這樣子
```ruby
$ git config --global alias.co checkout
$ git config --global alias.br branch
$ git config --global alias.ci commit
$ git config --global alias.st status
```

4. 分支管理


git clone https://gitee.com/xxx/Super.git(遠端地址) // 直接拉取遠端分支(預設master主分支)
git clone -b dev https://gitee.com/xxx/Super.git(遠端地址) // 直接拉取遠端git dev分支(非主分支)
git checkout -b dev origin/dev // 拉取遠端開發分支到本地並且切換到開發分支
git push dev origin/dev
git branch new // 建立新分支
git push origin new // 將new分支推送到遠端(git push [remote-name] [branch-name])
git push origin :new // 刪除遠端分支,注意冒號位置
git branch -r -d origin/dev // 刪除遠端分支(同上一行命令,作用一樣)
git brach -d new // 刪除本地分支,如果有沒有merge的資訊,確保該分支的確不用merge,直接使用-D強行刪除
git merge dev // 將dev分支合併到當前分支
git checkout master // 切換到主分支
其他不再一一列出

關於實際開發中,獨立開發的話其實兩個分支完全夠用,一個主分支(master),一個開發分支(develop)。多人的話就按功能模組和人員來具體新建分支即可。由於這邊由我一人負責整個專案,那麼分支的話,我就建立了一個develop分支,釋出版本時切換到master處理即可。平時都在develop分支開發,如果此時發現線上版本有bug,那麼只需要切換到master分支,修改bug,然後封版即可。處理好後切換到develop分支開發就可以了。當然最好還是把剛才在master修改bug的程式碼merge到當前的develop分支上。

還有一點使用技巧,也就是git stash的靈活使用。當我們正在當前分支編程式碼,突然某人中斷你的思路,提出某功能需修改且比較緊急,更煩人的是當前的程式碼已經開始編寫,而且改動了好多檔案,重要的是不想中途進行git commit提交,此時git stash就發揮作用了。

git stash // 暫時隱藏,之後就可以正常切換分支了,當前的修改內容只是儲存並且隱藏起來
git stash pop // 回覆到之前的工作狀態,預設pop的是最近的一條,這樣就可以愉快的繼續編寫當時被中斷的程式碼了

git stash可以執行多次,我們可以使用git stash list命令來檢視清單。當然還有好多命令,可以使用git stash --help來檢視。

其實關於git stash還有一個使用技巧,那就是如果當前修改了眾多檔案,突然又不想改了,想恢復到原來的樣子。那麼可能你會使用git checkout 命令來取消所有的更改,但是有種情況並不好使,因為你除了修改檔案,還添加了一些檔案或則拖入了一些檔案到工程目錄下,此時使用git checkout 然後再使用git status檢視的時候,會發現工作區多出了尚未識別的檔案(處於等待add的狀態)。在這個時候,使用git stash是再好不過了,因為這樣將所有git控制下的檔案,包括之前的目錄,全部還原到之前的狀態(也就是說git stash把新增的檔案也暫時隱藏起來)。如果想徹底刪掉,那就再把所有stash的列表清空吧,直接git stash list看下,然後執行git stash clear

還有一點,關於在Git伺服器上刪除分支,本地使用git branch -a依舊可以看到被刪除的分支。好比你的程式碼託管到了開源中國(OSChina),你通過網站,線上刪除分支,就會出現這種問題,稍微有點強迫症的自然受不了。此時可以使用 git fetch -p使fetch之後刪除沒有與遠端分支對應的本地分支。 當然也可以通過檢視遠端分支。使用命令git remote show origin顯示如下:

YJTSuper:yjtim super$ git remote show origin
* remote origin
Fetch URL: [email protected]:lingsui/yjtim.git
Push URL: [email protected]:lingsui/yjtim.git
HEAD branch: master
Remote branches:
dev tracked
im tracked
master tracked
proV2.3.0 tracked
refs/remotes/origin/test stale (use 'git remote prune' to remove)
Local branches configured for 'git pull':
master merges with remote master
show merges with remote master
Local refs configured for 'git push':
dev pushes to dev (up to date)
im pushes to im (fast-forwardable)
master pushes to master (up to date)
proV2.3.0 pushes to proV2.3.0 (up to date)

我們可以看到分支origin/test 已經過期(stale),可以使用命令git remote prune origin同樣可以處理。

5. 遠端地址切換


如果想要切換遠端地址,千萬不要重新再初始化一個git程式碼倉庫,使用git add重新新增。這樣的好處只有一點,所有歷史提交資訊全部清空,不再保留,git clone時檔案變小,理所當然,歷史記錄全部清空了麼。如果確定不保留,也建議這樣做,關鍵時,誰寫的程式碼更改了哪些東西很重要呀,還是選擇保留吧。使用下面的方法,一行程式碼搞定,並且保留了所有的commit記錄。

git remote -v  // 檢視遠端地址
git remote set-url origin https://git.oschina.net/HaiShengHuo/xxx.git // 更換遠端地址, 新建一個專案不新增任何檔案 在本地直接push即可

6. 檢視某次提交修改的具體檔案和內容


方法一

舉例:

git diff 56fd41f2 56c8822c

git reflog 或則 git log --oneline

注意:這裡的 git reflog是包含所有分支操作(切換,刪除等)以及所有提交記錄,而git log僅僅是當前分支下的提交,不存在分支操作記錄。這一點在版本回退中應該特別注意,使用git reset時,儘量使用git log.

首先要列出最近的提交記錄。

git log 56c8822c --name-status

找到對應版本號,執行後可以看到本次提交之前的所有修改檔案。

git diff HEAD@{3} HEAD@{4}

具體某次修改的內容(具體某次提交的內容和上次提交的內容進行比較)。

git diff 56fd41f2 56c8822c

當然也可以這樣,使用版本號,效果一樣。

這樣假如之前修改過具體某些內容,以後還需要修改的話,找起來真的很方便。比如說某些bug好久之後才發現,又要回頭去修改,而修改總要找到對應的程式碼進行修改吧。那麼通過這樣幾行命令就定位到具體檔案和位置了,方便且節省時間。找程式碼有技巧,但是"找"終究還是很浪費時間。這也告訴我們,commit 提交命令很重要,需要認真寫,不可為了省事而亂寫。

方法二

直接使用git log 4e732dee -p 或則 git log -p 4e732dee

其中4e732dee為版本雜湊值,會顯示之前所有歷史commit更改的具體內容

方法三

git log --stat 4e732dee或者git log 4e732dee --stat

檢視簡潔檔案變化,會顯示之前所有歷史commit更改的檔案以及程式碼增刪狀況

檢視本地修改的內容,尚未進行commit

直接使用git diff --stat

7. 忽略跟蹤


git checkout .   清空所有更改
以下命令是我們在專案中已經添加了.gitignore 但是中途突然不想再跟蹤某檔案
此時發現簡單的在.gitignore檔案中新增要忽略的檔案是不起效的,因為該檔案已
經被track,我們還需要將其狀態改為 未track(其實只需刪除暫緩區檔案然後將操作體檢即可)
git rm --cached Podfile.lock 將Podfile.lock從暫緩區刪除,不再跟蹤

8. 備份


備份的話好一些,每次新的版本打一次tag,檢視起來還是挺方便。不過不用也行。我不愛使用這個。

git tag -a WeChat1.0 -m "version 1.0" :給版本打上標籤
git tag : 檢視所有的標籤
git push origin WeChat1.0 : 將WeChat1.0 push 到預設分支

9. 版本回退


git reset --hard HEAD // 沒有提交的情況下進行版本回退
git reset --hard HEAD^ // 回退到上一個版本
git reset --hard HEAD^^ // 回退到指定回退到某個版本
git reset --hard 版本號(至少前5位) // 回退到前幾個版本
git reset --hard~1
git revert c011eb3c20ba6fb38cc94fe5a8dda366a3990c61 // 注意該行命令 reset和revert有本質區別

注意:開發中一般託管程式碼到遠端程式碼倉庫,比如Github或則OSChina,加入本地已經使用git push到遠端程式碼倉庫,在本地使用git reset回退再push明顯是不可行的。因為git reset直接回退到歷史中的某個Hash值,但是使用git revert就不一樣了。git revert將作為一次新提交(新的Hash值)來撤銷某次更改而不是歷史中的某個Hash值,此時再push到遠端程式碼倉庫情理之中。當然你嫌麻煩也可以使用git reset 後再使用git push -f強制 push 到遠端程式碼倉庫。

reset和revert區別:

• git reset 3c7bdf2 回到3c7bdf2,3c7bdf2之前的commit都會保留,3c7bdf2之後的修改都會被退回到暫存區。

• git revert 3c7bdf2 生成一個新的commit來撤銷3c7bdf2,此次提交之前的commit都會被保留。

可以參考部落格git revert和git reset的區別

10. 日誌


想回顧下提交歷史,可以使用 git log 命令,其中有個--pretty 引數可以配置

git log --pretty=oneline
git log --pretty=format:"%h - %an, %ar : %s"
git log --pretty=format:"%h %s" --graph
git log --since=2.weeks

自己可以試一下,這裡附上引數說明,自己可以隨意配置,當然log字型顏色也都是可以修改的,不再細述。

%H  // 提交物件(commit)的完整雜湊字串 %h 提交物件的簡短雜湊字串
%T // 樹物件(tree)的完整雜湊字串
%t // 樹物件的簡短雜湊字串
%P // 父物件(parent)的完整雜湊字串 %p 父物件的簡短雜湊字串
%an // 作者(author)的名字
%ae // 作者的電子郵件地址
%ad // 作者修訂日期(可以用 -date= 選項定製格式) %ar 作者修訂日期,按多久以前的方式顯示
%cn // 提交者(committer)的名字
%ce // 提交者的電子郵件地址
%cd // 提交日期
%cr // 提交日期,按多久以前的方式顯示 %s 提交說明

時間和提交者過濾

-(n)                  // 僅顯示最近的 n 條提交。
--since, --after // 僅顯示指定時間之後的提交。
--until, --before // 僅顯示指定時間之前的提交。
--author // 僅顯示指定作者相關的提交。
--committer // 僅顯示指定提交者相關提交。

你一定奇怪_作者(author)_和_提交者(committer)_之間究竟有何差別,其實作者指的是實際作出修改 的人,提交者指的是最後將此工作成果提交到倉庫的人。所以,當你為某個專案發去補丁,然後某個核心成員將你的補丁併入專案時,你就是作者,而那個核心成員就是提交者。我們會在第五章再詳細介紹兩者之間的細緻差別。

11. 忽略某些檔案


檔案 .gitignore 的格式規範如下:

• 所有空行或者以註釋符號 # 開頭的行都會被 Git 忽略。

• 可以使用標準的 glob 模式匹配。

• 匹配模式最後跟反斜槓(/)說明要忽略的是目錄。

• 要忽略指定模式以外的檔案或目錄,可以在模式前加上驚歎號(!)取反。

所謂的 glob 模式是指 shell 所使用的簡化了的正則表示式。星號(*)匹配零個或多個任意字元;[abc] 匹配 任何一個列在方括號中的字元(這個例子要麼匹配一個 a,要麼匹配一個 b,要麼匹配一個 c);問號(?) 只匹配一個任意字元;如果在方括號中使用短劃線分隔兩個字元,表示所有在這兩個字元範圍內的都可以匹配 (比如 [0-9] 表示匹配所有 0 到 9 的數字)。

# 此為註釋 – 將被 Git 忽略
*.[oa] Git 忽略所有以 .o 或 .a 結尾的檔案。一般這類物件檔案和存檔檔案都是編譯過程中出現 的,我們用不著跟蹤它們的版本
*~ Git 忽略所有以波浪符(~)結尾的檔案,許多文字編輯軟體 (比如 Emacs)都用這樣的檔名儲存副本
*.a # 忽略所有 .a 結尾的檔案
!lib.a # 但 lib.a 除外
/TODO # 僅僅忽略專案根目錄下的 TODO 檔案,不包括 subdir/TODO build/ # 忽略 build/ 目錄下的所有檔案
doc/*.txt # 會忽略 doc/notes.txt 但不包括 doc/server/arch.txt

補充


專案剛啟動時(比如要把專案搭建在OSChina上),建立git時推薦方案是,先在OSChina上建立一個全新的專案,然後拿到連結後clone到本地,然後在Git工作目錄建立資料夾和檔案, 開始一個新的專案。這樣本地和遠端已經建立好了連線,隨後直接git add .,然後git commit -m "init",最後push到OSChina就好。

當然,有時候我們直接在本地初始化了git, 然後已將初始化了專案,想要與遠端建立連結,也是可以的。但是一般不這樣做,既然本地已經有了git程式碼倉庫,那麼隨後再在OSChina上建立一個專案也同時生成了一個git程式碼倉庫,,兩則建立連線會有些問題, 最直接的問題就是, 本地的會直接覆蓋掉OSChina上的倉庫. 不過確定不要OSChina初始化檔案也可以這樣做。

操作步驟:

首先給本地新增遠端地址 git remote add origin https://git.oschina.net/xxx/xxx.git

然後 git push origin master -f 其中-f為強制推送遠端(直接push會被拒, 因為遠端已有一個程式碼倉庫)

這樣就可以了(遠端OSChina初始化檔案已經被覆蓋)。

修改已經提交log資訊:git commit --amend ( --amend: amend previous commit)