廖雪峯Git教程學習筆記

教程地址: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 id3628164...(版本號沒必要寫全,前幾位就可以了,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:

image.png

改爲指向add distributed:

     image.png

然後順便把工作區的文件更新了。所以你讓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,就能確定當前分支,以及當前分支的提交點:

image.png

當我們創建新的分支,例如dev時,Git新建了一個指針叫dev,指向master相同的提交,再把HEAD指向dev,就表示當前分支在dev上:

image.png 

從現在開始,對工作區的修改和提交就是針對dev分支了,比如新提交一次後,dev指針往前移動一步,而master指針不變:

 image.png

假如我們在dev上的工作完成了,就可以把dev合併到master上。Git怎麼合併呢?最簡單的方法,就是直接把master指向dev的當前提交,就完成了合併:

 image.png

合併完分支後,甚至可以刪除dev分支。刪除dev分支就是把dev指針給刪掉,刪掉後,剩下了一條master分支:

image.png 

首先,我們創建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(+)

此時:

image.png 

刪除dev分支後:(找不到分支信息)

image.png 

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(+)

此時:

image.png 

合併分支時,加上--no-ff參數就可以用普通模式合併,合併後的歷史有分支,能看出來曾經做過合併,而fast forward合併就看不出來曾經做過合併。

合併完成後,就可以放心地刪除dev分支了:

git branch -d dev

Deleted branch dev (was fec145a).

刪除後,查看branch,就只剩下master分支了:

$ git branch

* master

解決衝突

情景:master分支和feature1分支各自都分別有新的提交,變成了這樣:

image.png 

這種情況下,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分支變成了下圖所示:

image.png 

用帶參數的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分支上合併就可以了。

所以,團隊合作的分支看起來就像這樣:

image.png 

多人協作:

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



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章