教程地址:https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000
創建版本庫
初始化一個Git倉庫,使用git init命令。
添加文件到Git倉庫,分兩步:
第一步,使用命令git add <file>,注意,可反覆多次使用,添加多個文件;
第二步,使用命令git commit,完成。
版本回退
版本1:wrote a readme file
Git is a version control system.
Git is free software.
版本2:add distributed
Git is a distributed version control system.
Git is free software.
版本3:append GPL
Git is a distributed version control system.
Git is free software distributed under the GPL.
查看歷史記錄:
$ git log
commit 3628164fb26d48395383f8f31179f24e0882e1e0
Author: Michael Liao <[email protected]>
Date: Tue Aug 20 15:11:49 2013 +0800
append GPL
commit ea34578d5496d7dd233c827ed32a8cd576c5ee85
Author: Michael Liao <[email protected]>
Date: Tue Aug 20 14:53:12 2013 +0800
add distributed
commit cb926e7ea50ad11b8f9e909c05226233bf755030
Author: Michael Liao <[email protected]>
Date: Mon Aug 19 17:51:55 2013 +0800
wrote a readme file
準備把readme.txt回退到上一個版本,也就是“add distributed”的那個版本,怎麼做呢?
首先,Git必須知道當前版本是哪個版本,在Git中,用HEAD表示當前版本,也就是最新的提交3628164...882e1e0,上一個版本就是HEAD^,上上一個版本就是HEAD^^,往上100個版本寫成HEAD~100。
現在,我們要把當前版本“append GPL”回退到上一個版本“add distributed”,就可以使用git reset命令:
$ git reset --hard HEAD^
HEAD is now at ea34578 add distributed
再想回去到最新版本:
如果之前git log結果還可以查看,找到append GPL的commit id是3628164...(版本號沒必要寫全,前幾位就可以了,Git會自動去找。)
$ git reset --hard 3628164
HEAD is now at 3628164 append GPL
如果找不到append GPL的commit id,Git提供了一個命令git reflog用來記錄你的每一次命令:
$ git reflog
ea34578 HEAD@{0}: reset: moving to HEAD^
3628164 HEAD@{1}: commit: append GPL
ea34578 HEAD@{2}: commit: add distributed
cb926e7 HEAD@{3}: commit (initial): wrote a readme file
第二行顯示append GPL的commit id是3628164。
Git的版本回退速度非常快,因爲Git在內部有個指向當前版本的HEAD指針,當你回退版本的時候,Git僅僅是把HEAD從指向append GPL:
改爲指向add distributed:
然後順便把工作區的文件更新了。所以你讓HEAD指向哪個版本號,你就把當前版本定位在哪。
HEAD指向的版本就是當前版本,因此,Git允許我們在版本的歷史之間穿梭,使用命令git reset --hard commit_id。
穿梭前,用git log可以查看提交歷史,以便確定要回退到哪個版本。
要重返未來,用git reflog查看命令歷史,以便確定要回到未來的哪個版本。
撤銷修改
場景1:當你改亂了工作區某個文件的內容,想直接丟棄工作區的修改時,用命令git checkout -- file。
場景2:當你不但改亂了工作區某個文件的內容,還添加到了暫存區時(已經執行add),想丟棄修改,分兩步,第一步用命令git reset HEAD file,就回到了場景1,第二步按場景1操作。
git reset命令既可以回退版本,也可以把暫存區的修改回退到工作區。當我們用HEAD時,表示最新的版本。
場景3:已經提交了不合適的修改到版本庫時(已經執行commit),想要撤銷本次提交,參考版本回退一節,不過前提是沒有推送到遠程庫。
創建與合併分支
一開始的時候,master分支是一條線,Git用master指向最新的提交,再用HEAD指向master,就能確定當前分支,以及當前分支的提交點:
當我們創建新的分支,例如dev時,Git新建了一個指針叫dev,指向master相同的提交,再把HEAD指向dev,就表示當前分支在dev上:
從現在開始,對工作區的修改和提交就是針對dev分支了,比如新提交一次後,dev指針往前移動一步,而master指針不變:
假如我們在dev上的工作完成了,就可以把dev合併到master上。Git怎麼合併呢?最簡單的方法,就是直接把master指向dev的當前提交,就完成了合併:
合併完分支後,甚至可以刪除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(+)
此時:
刪除dev分支後:(找不到分支信息)
git merge命令用於合併指定分支到當前分支。合併後,再查看readme.txt的內容,就可以看到,和dev分支的最新提交是完全一樣的。
注意到上面的Fast-forward信息,Git告訴我們,這次合併是“快進模式”,也就是直接把master指向dev的當前提交,所以合併速度非常快。當然,也不是每次合併都能Fast-forward(當master和dev都有新的提交併衝突時)。
通常,合併分支時,如果可能,Git會用Fast forward模式,但這種模式下,刪除分支後,會丟掉分支信息。
如果要強制禁用Fast forward模式,Git就會在merge時生成一個新的commit,這樣,從分支歷史上就可以看出分支信息。合併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(+)
此時:
合併分支時,加上--no-ff參數就可以用普通模式合併,合併後的歷史有分支,能看出來曾經做過合併,而fast forward合併就看不出來曾經做過合併。
合併完成後,就可以放心地刪除dev分支了:
$ git branch -d dev
Deleted branch dev (was fec145a).
刪除後,查看branch,就只剩下master分支了:
$ git branch
* master
解決衝突
情景: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.
可以直接查看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.(master做出的修改)
=======
Creating a new branch is quick AND simple.(feature做出的修改)
>>>>>>> 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
...
當Git無法自動合併分支時,就必須首先解決衝突。解決衝突後,再提交,合併完成。
分支管理:
在實際開發中,我們應該按照幾個基本原則進行分支管理:
首先,master分支應該是非常穩定的,也就是僅用來發布新版本,平時不能在上面幹活;
那在哪幹活呢?幹活都在dev分支上,也就是說,dev分支是不穩定的,到某個時候,比如1.0版本發佈時,再把dev分支合併到master上,在master分支發佈1.0版本;
你和你的小夥伴們每個人都在dev分支上幹活,每個人都有自己的分支,時不時地往dev分支上合併就可以了。
所以,團隊合作的分支看起來就像這樣:
多人協作:
A:改動hello.py
A:commit and push
B:也改動了hello.py
B: commit
這時:
B想要push會衝突;
先pull下來,文件會自動合併,但是存在衝突,需要手動找到文件,解決衝突,提交,再push。
多人協作時,大家都會往master和dev分支上推送各自的修改。
你的小夥伴要在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
你的小夥伴已經向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. fc38031..291bea8 dev -> dev
推送失敗,因爲你的小夥伴的最新提交和你試圖推送的提交有衝突,解決辦法也很簡單,Git已經提示我們,先用git pull把最新的提交從origin/dev抓下來,然後,在本地合併,解決衝突,再推送:
$ git pull
Auto-merging hello.py
CONFLICT (content): Merge conflict in hello.py
Automatic merge failed; fix conflicts and then commit the result.
合併有衝突,需要手動解決,解決的方法和分支管理中的解決衝突完全一樣。解決後,提交,再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推送就能成功!
衝突發生的情況:
1.不同分支修改了同一個文件,並都執行過add,commit,再執行git merge時;
解決:master:git merge --dev;然後本地解決衝突,再add,commit,實現合併
2. 開發者A向遠程 push了一個分支,B再push時發生衝突
解決:B先pull遠程分支,本地解決衝突,再push