git進階
git進階
1 分支管理
分支就是科幻電影裏面的平行宇宙,當你正在電腦前努力學習Git的時候,另一個你正在另一個平行宇宙裏努力學習SVN。
如果兩個平行宇宙互不幹擾,那對現在的你也沒啥影響。不過,在某個時間點,兩個平行宇宙合並了,結果,你既學會了Git又學會了SVN!
分支在實際中有什麽用呢?假設你準備開發一個新功能,但是需要兩周才能完成,第一周你寫了50%的代碼,如果立刻提交,由於代碼還沒寫完,不完整的代碼庫會導致別人不能幹活了。如果等代碼全部寫完再一次提交,又存在丟失每天進度的巨大風險。
現在有了分支,就不用怕了。你創建了一個屬於你自己的分支,別人看不到,還繼續在原來的分支上正常工作,而你在自己的分支上幹活,想提交就提交,直到開發完畢後,再一次性合並到原來的分支上,這樣,既安全,又不影響別人工作。
其他版本控制系統如SVN等都有分支管理,但是用過之後你會發現,這些版本控制系統創建和切換分支比蝸牛還慢,簡直讓人無法忍受,結果分支功能成了擺設,大家都不去用。
但Git的分支是與眾不同的,無論創建、切換和刪除分支,Git在1秒鐘之內就能完成!無論你的版本庫是1個文件還是1萬個文件。
1.1 創建與合並分支
在初識git—版本回退裏,你已經知道,每次提交,Git都把它們串成一條時間線,這條時間線就是一個分支。截止到目前,只有一條時間線,在Git裏,這個分支叫主分支,即master
分支。HEAD
嚴格來說不是指向提交,而是指向master
,master
才是指向提交的,所以,HEAD
一開始的時候,master
分支是一條線,Git用master
指向最新的提交,再用HEAD
指向master
,就能確定當前分支,以及當前分支的提交點:
每次提交,master
分支都會向前移動一步,這樣,隨著你不斷提交,master
分支的線也越來越長:
當我們創建新的分支,例如dev
時,Git新建了一個指針叫dev
,指向master
相同的提交,再把HEAD
指向dev
,就表示當前分支在dev
上:
你看,Git創建一個分支很快,因為除了增加一個dev
指針,改改HEAD
的指向,工作區的文件都沒有任何變化!
不過,從現在開始,對工作區的修改和提交就是針對dev
分支了,比如新提交一次後,dev
master
指針不變:
假如我們在dev
上的工作完成了,就可以把dev
合並到master
上。Git怎麽合並呢?最簡單的方法,就是直接把master
指向dev
的當前提交,就完成了合並:
所以Git合並分支也很快!就改改指針,工作區內容也不變!
合並完分支後,甚至可以刪除dev
分支。刪除dev
分支就是把dev
指針給刪掉,刪掉後,我們就剩下了一條master
分支:
真是太神奇了,你看得出來有些提交是通過分支完成的嗎?
下面開始實戰。
首先,我們創建dev
分支,然後切換到dev
分支:
``` $ git checkout -b dev Switched to a new branch ‘dev‘
```
git checkout
命令加上-b
參數表示創建並切換,相當於以下兩條命令:
``` $ git branch dev $ git checkout dev Switched to branch ‘dev‘
```
然後,用git branch
命令查看當前分支:
``` $ git branch * dev master
```
git branch
命令會列出所有分支,當前分支前面會標一個*
號。
然後,我們就可以在dev
分支上正常提交,比如對readme.txt做個修改,加上一行:
``` Creating a new branch is quick.
```
然後提交:
``` $ git add readme.txt $ git commit -m "branch test" [dev fec145a] branch test 1 file changed, 1 insertion(+)
```
現在,dev
分支的工作完成,我們就可以切換回master
分支:
``` $ git checkout master Switched to branch ‘master‘
```
切換回master
分支後,再查看一個readme.txt文件,剛才添加的內容不見了!因為那個提交是在dev
分支上,而master
分支此刻的提交點並沒有變:
現在,我們把dev
分支的工作成果合並到master
分支上:
``` $ git merge dev Updating d17efd8..fec145a Fast-forward readme.txt | 1 + 1 file changed, 1 insertion(+)
```
git merge
命令用於合並指定分支到當前分支。合並後,再查看readme.txt的內容,就可以看到,和dev
分支的最新提交是完全一樣的。
註意到上面的Fast-forward
信息,Git告訴我們,這次合並是“快進模式”,也就是直接把master
指向dev
的當前提交,所以合並速度非常快。
當然,也不是每次合並都能Fast-forward
,我們後面會講其他方式的合並。
合並完成後,就可以放心地刪除dev
分支了:
``` $ git branch -d dev Deleted branch dev (was fec145a).
```
刪除後,查看branch
,就只剩下master
分支了:
``` $ git branch * master
```
因為創建、合並和刪除分支非常快,所以Git鼓勵你使用分支完成某個任務,合並後再刪掉分支,這和直接在master
分支上工作效果是一樣的,但過程更安全。
1.2 小結
Git鼓勵大量使用分支:
- 查看分支:
git branch
- 創建分支:
git branch
- 切換分支:
git checkout
- 創建+切換分支:
git checkout -b
- 合並某分支到當前分支:
git merge
- 刪除分支:
git branch -d
2 解決沖突
人生不如意之事十之八九,合並分支往往也不是一帆風順的。
準備新的feature1
分支,繼續我們的新分支開發:
``` $ git checkout -b feature1 Switched to a new branch ‘feature1‘
```
修改readme.txt最後一行,改為:
``` Creating a new branch is quick AND simple.
```
在feature1
分支上提交:
``` $ git add readme.txt $ git commit -m "AND simple" [feature1 75a857c] AND simple 1 file changed, 1 insertion(+), 1 deletion(-)
```
切換到master
分支:
``` $ git checkout master Switched to branch ‘master‘ Your branch is ahead of ‘origin/master‘ by 1 commit.
```
Git還會自動提示我們當前master
分支比遠程的master
分支要超前1個提交。
在master
分支上把readme.txt文件的最後一行改為:
``` Creating a new branch is quick & simple.
```
提交:
``` $ git add readme.txt $ git commit -m "& simple" [master 400b400] & simple 1 file changed, 1 insertion(+), 1 deletion(-)
```
現在,master
分支和feature1
分支各自都分別有新的提交,變成了這樣:
這種情況下,Git無法執行“快速合並”,只能試圖把各自的修改合並起來,但這種合並就可能會有沖突,我們試試看:
``` $ git merge feature1 Auto-merging readme.txt CONFLICT (content): Merge conflict in readme.txt Automatic merge failed; fix conflicts and then commit the result.
```
果然沖突了!Git告訴我們,readme.txt文件存在沖突,必須手動解決沖突後再提交。git status
也可以告訴我們沖突的文件:
``` $ git status
On branch master
Your branch is ahead of ‘origin/master‘ by 2 commits.
Unmerged paths:
(use "git add/rm ..." as appropriate to mark resolution)
both modified: readme.txt
no changes added to commit (use "git add" and/or "git commit -a")
```
我們可以直接查看readme.txt的內容:
``` Git is a distributed version control system. Git is free software distributed under the GPL. Git has a mutable index called stage. Git tracks changes of files. <<<<<<< HEAD
Creating a new branch is quick & simple.
Creating a new branch is quick AND simple.
feature1
```
Git用<<<<<<<
,=======
,>>>>>>>
標記出不同分支的內容,我們修改如下後保存:
``` Creating a new branch is quick and simple.
```
再提交:
``` $ git add readme.txt $ git commit -m "conflict fixed" [master 59bc1cb] conflict fixed
```
現在,master
分支和feature1
分支變成了下圖所示:
用帶參數的git log
也可以看到分支的合並情況:
``` $ git log --graph --pretty=oneline --abbrev-commit * 59bc1cb conflict fixed |\ | * 75a857c AND simple * | 400b400 & simple |/ * fec145a branch test ...
```
最後,刪除feature1
分支:
``` $ git branch -d feature1 Deleted branch feature1 (was 75a857c).
```
工作完成。
小結
當Git無法自動合並分支時,就必須首先解決沖突。解決沖突後,再提交,合並完成。
用git log --graph
命令可以看到分支合並圖。
3 分支管理策略
通常,合並分支時,如果可能,Git會用Fast forward
模式,但這種模式下,刪除分支後,會丟掉分支信息。
如果要強制禁用Fast forward
模式,Git就會在merge時生成一個新的commit,這樣,從分支歷史上就可以看出分支信息。
下面我們實戰一下--no-ff
方式的git merge
:
首先,仍然創建並切換dev
分支:
``` $ git checkout -b dev Switched to a new branch ‘dev‘
```
修改readme.txt文件,並提交一個新的commit:
``` $ git add readme.txt $ git commit -m "add merge" [dev 6224937] add merge 1 file changed, 1 insertion(+)
```
現在,我們切換回master
:
``` $ git checkout master Switched to branch ‘master‘
```
準備合並dev
分支,請註意--no-ff
參數,表示禁用Fast forward
:
``` $ git merge --no-ff -m "merge with no-ff" dev Merge made by the ‘recursive‘ strategy. readme.txt | 1 + 1 file changed, 1 insertion(+)
```
因為本次合並要創建一個新的commit,所以加上-m
參數,把commit描述寫進去。
合並後,我們用git log
看看分支歷史:
``` $ git log --graph --pretty=oneline --abbrev-commit * 7825a50 merge with no-ff |\ | * 6224937 add merge |/ * 59bc1cb conflict fixed ...
```
可以看到,不使用Fast forward
模式,merge後就像這樣:
分支策略
在實際開發中,我們應該按照幾個基本原則進行分支管理:
首先,master
分支應該是非常穩定的,也就是僅用來發布新版本,平時不能在上面幹活;
那在哪幹活呢?幹活都在dev
分支上,也就是說,dev
分支是不穩定的,到某個時候,比如1.0版本發布時,再把dev
分支合並到master
上,在master
分支發布1.0版本;
你和你的小夥伴們每個人都在dev
分支上幹活,每個人都有自己的分支,時不時地往dev
分支上合並就可以了。
所以,團隊合作的分支看起來就像這樣:
小結
Git分支十分強大,在團隊開發中應該充分應用。
合並分支時,加上--no-ff
參數就可以用普通模式合並,合並後的歷史有分支,能看出來曾經做過合並,而fast forward
合並就看不出來曾經做過合並
4 多人協作
當你從遠程倉庫克隆時,實際上Git自動把本地的master
分支和遠程的master
分支對應起來了,並且,遠程倉庫的默認名稱是origin
。
要查看遠程庫的信息,用git remote
:
``` $ git remote origin
```
或者,用git remote -v
顯示更詳細的信息:
``` $ git remote -v origin [email protected]:michaelliao/learngit.git (fetch) origin [email protected]:michaelliao/learngit.git (push)
```
上面顯示了可以抓取和推送的origin
的地址。如果沒有推送權限,就看不到push的地址。
推送分支
推送分支,就是把該分支上的所有本地提交推送到遠程庫。推送時,要指定本地分支,這樣,Git就會把該分支推送到遠程庫對應的遠程分支上:
``` $ git push origin master
```
如果要推送其他分支,比如dev
,就改成:
``` $ git push origin dev
```
但是,並不是一定要把本地分支往遠程推送,那麽,哪些分支需要推送,哪些不需要呢?
master
分支是主分支,因此要時刻與遠程同步;dev
分支是開發分支,團隊所有成員都需要在上面工作,所以也需要與遠程同步;- bug分支只用於在本地修復bug,就沒必要推到遠程了,除非老板要看看你每周到底修復了幾個bug;
- feature分支是否推到遠程,取決於你是否和你的小夥伴合作在上面開發。
總之,就是在Git中,分支完全可以在本地自己藏著玩,是否推送,視你的心情而定!
抓取分支
多人協作時,大家都會往master
和dev
分支上推送各自的修改。
現在,模擬一個你的小夥伴,可以在另一臺電腦(註意要把SSH Key添加到GitHub)或者同一臺電腦的另一個目錄下克隆:
``` $ git clone [email protected]:michaelliao/learngit.git Cloning into ‘learngit‘... remote: Counting objects: 46, done. remote: Compressing objects: 100% (26/26), done. remote: Total 46 (delta 16), reused 45 (delta 15) Receiving objects: 100% (46/46), 15.69 KiB | 6 KiB/s, done. Resolving deltas: 100% (16/16), done.
```
當你的小夥伴從遠程庫clone時,默認情況下,你的小夥伴只能看到本地的master
分支。不信可以用git branch
命令看看:
``` $ git branch * master
```
現在,你的小夥伴要在dev
分支上開發,就必須創建遠程origin
的dev
分支到本地,於是他用這個命令創建本地dev
分支:
``` $ git checkout -b dev origin/dev
```
現在,他就可以在dev
上繼續修改,然後,時不時地把dev
分支push
到遠程:
``` $ git commit -m "add /usr/bin/env" [dev 291bea8] add /usr/bin/env 1 file changed, 1 insertion(+) $ git push origin dev Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (2/2), done. Writing objects: 100% (3/3), 349 bytes, done. Total 3 (delta 0), reused 0 (delta 0) To [email protected]:michaelliao/learngit.git fc38031..291bea8 dev -> dev
```
你的小夥伴已經向origin/dev
分支推送了他的提交,而碰巧你也對同樣的文件作了修改,並試圖推送:
``` $ git add hello.py $ git commit -m "add coding: utf-8" [dev bd6ae48] add coding: utf-8 1 file changed, 1 insertion(+) $ git push origin dev To [email protected]:michaelliao/learngit.git ! [rejected] dev -> dev (non-fast-forward) error: failed to push some refs to [email protected]:michaelliao/learngit.git‘ hint: Updates were rejected because the tip of your current branch is behind hint: its remote counterpart. Merge the remote changes (e.g. ‘git pull‘) hint: before pushing again. hint: See the ‘Note about fast-forwards‘ in ‘git push --help‘ for details.
```
推送失敗,因為你的小夥伴的最新提交和你試圖推送的提交有沖突,解決辦法也很簡單,Git已經提示我們,先用git pull
把最新的提交從origin/dev
抓下來,然後,在本地合並,解決沖突,再推送:
``` $ git pull remote: Counting objects: 5, done. remote: Compressing objects: 100% (2/2), done. remote: Total 3 (delta 0), reused 3 (delta 0) Unpacking objects: 100% (3/3), done. From github.com:michaelliao/learngit fc38031..291bea8 dev -> origin/dev There is no tracking information for the current branch. Please specify which branch you want to merge with. See git-pull(1) for details
git pull <remote> <branch>
If you wish to set tracking information for this branch you can do so with:
git branch --set-upstream dev origin/<branch>
```
git pull
也失敗了,原因是沒有指定本地dev
分支與遠程origin/dev
分支的鏈接,根據提示,設置dev
和origin/dev
的鏈接:
``` $ git branch --set-upstream dev origin/dev Branch dev set up to track remote branch dev from origin.
```
再pull:
``` $ git pull Auto-merging hello.py CONFLICT (content): Merge conflict in hello.py Automatic merge failed; fix conflicts and then commit the result.
```
這回git pull
成功,但是合並有沖突,需要手動解決,解決的方法和分支管理中的解決沖突完全一樣。解決後,提交,再push:
``` $ git commit -m "merge & fix hello.py" [dev adca45d] merge & fix hello.py $ git push origin dev Counting objects: 10, done. Delta compression using up to 4 threads. Compressing objects: 100% (5/5), done. Writing objects: 100% (6/6), 747 bytes, done. Total 6 (delta 0), reused 0 (delta 0) To [email protected]:michaelliao/learngit.git 291bea8..adca45d dev -> dev
```
因此,多人協作的工作模式通常是這樣:
- 首先,可以試圖用
git push origin branch-name
推送自己的修改; - 如果推送失敗,則因為遠程分支比你的本地更新,需要先用
git pull
試圖合並; - 如果合並有沖突,則解決沖突,並在本地提交;
- 沒有沖突或者解決掉沖突後,再用
git push origin branch-name
推送就能成功!
如果git pull
提示“no tracking information”,則說明本地分支和遠程分支的鏈接關系沒有創建,用命令git branch --set-upstream branch-name origin/branch-name
。
這就是多人協作的工作模式,一旦熟悉了,就非常簡單。
小結
- 查看遠程庫信息,使用
git remote -v
; - 本地新建的分支如果不推送到遠程,對其他人就是不可見的;
- 從本地推送分支,使用
git push origin branch-name
,如果推送失敗,先用git pull
抓取遠程的新提交; - 在本地創建和遠程分支對應的分支,使用
git checkout -b branch-name origin/branch-name
,本地和遠程分支的名稱最好一致; - 建立本地分支和遠程分支的關聯,使用
git branch --set-upstream branch-name origin/branch-name
; - 從遠程抓取分支,使用
git pull
,如果有沖突,要先處理沖突。
5 標簽管理
發布一個版本時,我們通常先在版本庫中打一個標簽(tag),這樣,就唯一確定了打標簽時刻的版本。將來無論什麽時候,取某個標簽的版本,就是把那個打標簽的時刻的歷史版本取出來。所以,標簽也是版本庫的一個快照。
Git的標簽雖然是版本庫的快照,但其實它就是指向某個commit的指針(跟分支很像對不對?但是分支可以移動,標簽不能移動),所以,創建和刪除標簽都是瞬間完成的。
Git有commit,為什麽還要引入tag?
“請把上周一的那個版本打包發布,commit號是6a5819e...”
“一串亂七八糟的數字不好找!”
如果換一個辦法:
“請把上周一的那個版本打包發布,版本號是v1.2”
“好的,按照tag v1.2查找commit就行!”
所以,tag就是一個讓人容易記住的有意義的名字,它跟某個commit綁在一起。
5.1 創建標簽
在Git中打標簽非常簡單,首先,切換到需要打標簽的分支上:
``` $ git branch * dev master $ git checkout master Switched to branch ‘master‘
```
然後,敲命令git tag
就可以打一個新標簽:
``` $ git tag v1.0
```
可以用命令git tag
查看所有標簽:
``` $ git tag v1.0
```
默認標簽是打在最新提交的commit上的。有時候,如果忘了打標簽,比如,現在已經是周五了,但應該在周一打的標簽沒有打,怎麽辦?
方法是找到歷史提交的commit id,然後打上就可以了:
``` $ git log --pretty=oneline --abbrev-commit 6a5819e merged bug fix 101 cc17032 fix bug 101 7825a50 merge with no-ff 6224937 add merge 59bc1cb conflict fixed 400b400 & simple 75a857c AND simple fec145a branch test d17efd8 remove test.txt ...
```
比方說要對add merge
這次提交打標簽,它對應的commit id是6224937
,敲入命令:
``` $ git tag v0.9 6224937
```
再用命令git tag
查看標簽:
``` $ git tag v0.9 v1.0
```
註意,標簽不是按時間順序列出,而是按字母排序的。可以用git show
查看標簽信息:
``` $ git show v0.9 commit 622493706ab447b6bb37e4e2a2f276a20fed2ab4 Author: Michael Liao [email protected] Date: Thu Aug 22 11:22:08 2013 +0800
add merge
...
```
可以看到,v0.9
確實打在add merge
這次提交上。
還可以創建帶有說明的標簽,用-a
指定標簽名,-m
指定說明文字:
``` $ git tag -a v0.1 -m "version 0.1 released" 3628164
```
用命令git show
可以看到說明文字:
``` $ git show v0.1 tag v0.1 Tagger: Michael Liao [email protected] Date: Mon Aug 26 07:28:11 2013 +0800
version 0.1 released
commit 3628164fb26d48395383f8f31179f24e0882e1e0 Author: Michael Liao [email protected] Date: Tue Aug 20 15:11:49 2013 +0800
append GPL
```
還可以通過-s
用私鑰簽名一個標簽:
``` $ git tag -s v0.2 -m "signed version 0.2 released" fec145a
```
簽名采用PGP簽名,因此,必須首先安裝gpg(GnuPG),如果沒有找到gpg,或者沒有gpg密鑰對,就會報錯:
``` gpg: signing failed: secret key not available error: gpg failed to sign the data error: unable to sign the tag
```
如果報錯,請參考GnuPG幫助文檔配置Key。
用命令git show
可以看到PGP簽名信息:
``` $ git show v0.2 tag v0.2 Tagger: Michael Liao [email protected] Date: Mon Aug 26 07:28:33 2013 +0800
signed version 0.2 released -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.12 (Darwin)
iQEcBAABAgAGBQJSGpMhAAoJEPUxHyDAhBpT4QQIAKeHfR3bo... -----END PGP SIGNATURE-----
commit fec145accd63cdc9ed95a2f557ea0658a2a6537f Author: Michael Liao [email protected] Date: Thu Aug 22 10:37:30 2013 +0800
branch test
```
用PGP簽名的標簽是不可偽造的,因為可以驗證PGP簽名。驗證簽名的方法比較復雜,這裏就不介紹了。
小結
- 命令
git tag
用於新建一個標簽,默認為HEAD
,也可以指定一個commit id; git tag -a -m "blablabla..."
可以指定標簽信息;git tag -s -m "blablabla..."
可以用PGP簽名標簽;- 命令
git tag
可以查看所有標簽。
5.2 操作標簽
如果標簽打錯了,也可以刪除:
``` $ git tag -d v0.1 Deleted tag ‘v0.1‘ (was e078af9)
```
因為創建的標簽都只存儲在本地,不會自動推送到遠程。所以,打錯的標簽可以在本地安全刪除。
如果要推送某個標簽到遠程,使用命令git push origin
:
``` $ git push origin v1.0 Total 0 (delta 0), reused 0 (delta 0) To [email protected]:michaelliao/learngit.git * [new tag] v1.0 -> v1.0
```
或者,一次性推送全部尚未推送到遠程的本地標簽:
``` $ git push origin --tags Counting objects: 1, done. Writing objects: 100% (1/1), 554 bytes, done. Total 1 (delta 0), reused 0 (delta 0) To [email protected]:michaelliao/learngit.git * [new tag] v0.2 -> v0.2 * [new tag] v0.9 -> v0.9
```
如果標簽已經推送到遠程,要刪除遠程標簽就麻煩一點,先從本地刪除:
``` $ git tag -d v0.9 Deleted tag ‘v0.9‘ (was 6224937)
```
然後,從遠程刪除。刪除命令也是push,但是格式如下:
``` $ git push origin :refs/tags/v0.9 To [email protected]:michaelliao/learngit.git - [deleted] v0.9
```
要看看是否真的從遠程庫刪除了標簽,可以登陸GitHub查看。
小結
- 命令
git push origin
可以推送一個本地標簽; - 命令
git push origin --tags
可以推送全部未推送過的本地標簽; - 命令
git tag -d
可以刪除一個本地標簽; - 命令
git push origin :refs/tags/
可以刪除一個遠程標簽。
6 使用GitHub
我們一直用GitHub作為免費的遠程倉庫,如果是個人的開源項目,放到GitHub上是完全沒有問題的。其實GitHub還是一個開源協作社區,通過GitHub,既可以讓別人參與你的開源項目,也可以參與別人的開源項目。
在GitHub出現以前,開源項目開源容易,但讓廣大人民群眾參與進來比較困難,因為要參與,就要提交代碼,而給每個想提交代碼的群眾都開一個賬號那是不現實的,因此,群眾也僅限於報個bug,即使能改掉bug,也只能把diff文件用郵件發過去,很不方便。
但是在GitHub上,利用Git極其強大的克隆和分支功能,廣大人民群眾真正可以第一次自由參與各種開源項目了。
如何參與一個開源項目呢?比如人氣極高的bootstrap項目,這是一個非常強大的CSS框架,你可以訪問它的項目主頁https://github.com/twbs/bootstrap,點“Fork”就在自己的賬號下克隆了一個bootstrap倉庫,然後,從自己的賬號下clone:
``` git clone [email protected]:michaelliao/bootstrap.git
```
一定要從自己的賬號下clone倉庫,這樣你才能推送修改。如果從bootstrap的作者的倉庫地址[email protected]:twbs/bootstrap.git
克隆,因為沒有權限,你將不能推送修改。
Bootstrap的官方倉庫twbs/bootstrap
、你在GitHub上克隆的倉庫my/bootstrap
,以及你自己克隆到本地電腦的倉庫,他們的關系就像下圖顯示的那樣:
如果你想修復bootstrap的一個bug,或者新增一個功能,立刻就可以開始幹活,幹完後,往自己的倉庫推送。
如果你希望bootstrap的官方庫能接受你的修改,你就可以在GitHub上發起一個pull request。當然,對方是否接受你的pull request就不一定了。
小結
- 在GitHub上,可以任意Fork開源倉庫;
- 自己擁有Fork後的倉庫的讀寫權限;
- 可以推送pull request給官方倉庫來貢獻代碼。
7 自定義git
在安裝Git一節中,我們已經配置了user.name
和user.email
,實際上,Git還有很多可配置項。
比如,讓Git顯示顏色,會讓命令輸出看起來更醒目:
``` $ git config --global color.ui true
```
這樣,Git會適當地顯示不同的顏色,比如git status
命令:
文件名就會標上顏色。
7.1 忽略指定文件
有些時候,你必須把某些文件放到Git工作目錄中,但又不能提交它們,比如保存了數據庫密碼的配置文件啦,等等,每次git status
都會顯示Untracked files ...
,有強迫癥的童鞋心裏肯定不爽。
好在Git考慮到了大家的感受,這個問題解決起來也很簡單,在Git工作區的根目錄下創建一個特殊的.gitignore
文件,然後把要忽略的文件名填進去,Git就會自動忽略這些文件。
不需要從頭寫.gitignore
文件,GitHub已經為我們準備了各種配置文件,只需要組合一下就可以使用了。所有配置文件可以直接在線瀏覽:https://github.com/github/gitignore
忽略文件的原則是:
- 忽略操作系統自動生成的文件,比如縮略圖等;
- 忽略編譯生成的中間文件、可執行文件等,也就是如果一個文件是通過另一個文件自動生成的,那自動生成的文件就沒必要放進版本庫,比如Java編譯產生的
.class
文件; - 忽略你自己的帶有敏感信息的配置文件,比如存放口令的配置文件。
舉個例子:
假設你在Windows下進行Python開發,Windows會自動在有圖片的目錄下生成隱藏的縮略圖文件,如果有自定義目錄,目錄下就會有Desktop.ini
文件,因此你需要忽略Windows自動生成的垃圾文件:
```
Windows:
Thumbs.db ehthumbs.db Desktop.ini
```
然後,繼續忽略Python編譯產生的.pyc
、.pyo
、dist
等文件或目錄:
```
Python:
*.py[cod] *.so *.egg *.egg-info dist build
```
加上你自己定義的文件,最終得到一個完整的.gitignore
文件,內容如下:
```
Windows:
Thumbs.db ehthumbs.db Desktop.ini
Python:
*.py[cod] *.so *.egg *.egg-info dist build
My configurations:
db.ini deploykeyrsa
```
最後一步就是把.gitignore
也提交到Git,就完成了!當然檢驗.gitignore
的標準是git status
命令是不是說working directory clean
。
使用Windows的童鞋註意了,如果你在資源管理器裏新建一個.gitignore
文件,它會非常弱智地提示你必須輸入文件名,但是在文本編輯器裏“保存”或者“另存為”就可以把文件保存為.gitignore
了。
有些時候,你想添加一個文件到Git,但發現添加不了,原因是這個文件被.gitignore
忽略了:
``` $ git add App.class The following paths are ignored by one of your .gitignore files: App.class Use -f if you really want to add them.
```
如果你確實想添加該文件,可以用-f
強制添加到Git:
``` $ git add -f App.class
```
或者你發現,可能是.gitignore
寫得有問題,需要找出來到底哪個規則寫錯了,可以用git check-ignore
命令檢查:
``` $ git check-ignore -v App.class .gitignore:3:*.class App.class
```
Git會告訴我們,.gitignore
的第3行規則忽略了該文件,於是我們就可以知道應該修訂哪個規則。
小結
- 忽略某些文件時,需要編寫
.gitignore
; .gitignore
文件本身要放到版本庫裏,並且可以對.gitignore
做版本管理!
7.2 配置別名
有沒有經常敲錯命令?比如git status
?status
這個單詞真心不好記。
如果敲git st
就表示git status
那就簡單多了,當然這種偷懶的辦法我們是極力贊成的。
我們只需要敲一行命令,告訴Git,以後st
就表示status
:
``` $ git config --global alias.st status
```
好了,現在敲git st
看看效果。
當然還有別的命令可以簡寫,很多人都用co
表示checkout
,ci
表示commit
,br
表示branch
:
``` $ git config --global alias.co checkout $ git config --global alias.ci commit $ git config --global alias.br branch
```
以後提交就可以簡寫成:
``` $ git ci -m "bala bala bala..."
```
--global
參數是全局參數,也就是這些命令在這臺電腦的所有Git倉庫下都有用。
在撤銷修改一節中,我們知道,命令git reset HEAD file
可以把暫存區的修改撤銷掉(unstage),重新放回工作區。既然是一個unstage操作,就可以配置一個unstage
別名:
``` $ git config --global alias.unstage ‘reset HEAD‘
```
當你敲入命令:
``` $ git unstage test.py
```
實際上Git執行的是:
``` $ git reset HEAD test.py
```
配置一個git last
,讓其顯示最後一次提交信息:
``` $ git config --global alias.last ‘log -1‘
```
這樣,用git last
就能顯示最近一次的提交:
``` $ git last commit adca45d317e6d8a4b23f9811c3d7b7f0f180bfe2 Merge: bd6ae48 291bea8 Author: Michael Liao [email protected]Date: Thu Aug 22 22:49:22 2013 +0800
merge & fix hello.py
```
甚至還有人喪心病狂地把lg
配置成了:
``` git config --global alias.lg "log --color --graph --pretty=format:‘%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset‘ --abbrev-commit"
```
來看看git lg
的效果:
為什麽不早點告訴我?別激動,咱不是為了多記幾個英文單詞嘛!
配置文件
配置Git的時候,加上--global
是針對當前用戶起作用的,如果不加,那只針對當前的倉庫起作用。
配置文件放哪了?每個倉庫的Git配置文件都放在.git/config
文件中:
``` $ cat .git/config [core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true ignorecase = true precomposeunicode = true [remote "origin"] url = [email protected]:michaelliao/learngit.git fetch = +refs/heads/:refs/remotes/origin/ [branch "master"] remote = origin merge = refs/heads/master [alias] last = log -1
```
別名就在[alias]
後面,要刪除別名,直接把對應的行刪掉即可。
而當前用戶的Git配置文件放在用戶主目錄下的一個隱藏文件.gitconfig
中:
``` $ cat .gitconfig [alias] co = checkout ci = commit br = branch st = status [user] name = Your Name email = [email protected]
```
配置別名也可以直接修改這個文件,如果改錯了,可以刪掉文件重新通過命令配置。
小結
給Git配置好別名,就可以輸入命令時偷個懶。我們鼓勵偷懶。
參考資料:廖雪峰的官方網站
git進階