Git 是目前最流行的分布式版本控制系統(tǒng)之一。
版本控制指的是,記錄每次版本變更的內容和時間等細節(jié),保留各版本之間的差異。
軟件版本控制系統(tǒng)(version control system,VCS)的方式主要分為集中式和分布式:
- 集中式版本控制系統(tǒng)中,版本庫集中存放在中央服務器,由中央服務器管理訪問權限“鎖上”文件庫中的文件,一次只讓一個開發(fā)者工作,且要求必須聯(lián)網(wǎng)才可提交。常見的集中式版本控制系統(tǒng)有早期的 CVS 和現(xiàn)在較為流行的 SVN 等。
- 分布式版本控制系統(tǒng)中,開發(fā)者直接在各自的本地文件庫工作,并允許多個開發(fā)者同時更改同一文件(即協(xié)作),而各個文件庫有另一個合并各個改變的功能。這允許無網(wǎng)絡時也可以提交到本地鏡像,待聯(lián)網(wǎng)時再推送到服務器。分布式系統(tǒng)仍然可以有文件上鎖功能。
安裝 Git
在 Windows 上安裝 Git
在 Windows 上使用 Git,可以從 Git 官網(wǎng)下載頁面直接下載,然后按默認選項安裝即可。
安裝完成后,在開始菜單里找到 Git Bash,彈出一個類似命令行窗口的東西,就說明 Git 安裝成功。
安裝完成后,還需要最后一步設置,在 Git Bash 輸入:
$ git config --global user.name "Your Name"
$ git config --global user.email "someone@example.com"
因為 Git 是分布式版本控制系統(tǒng),所以每個機器都必須自報家門:用戶名和電子郵箱地址。
注意 git config
命令的 --global
參數(shù)。用了這個參數(shù),表示這臺機器上所有的 Git 倉庫都會使用這個配置。當然也可以對某個倉庫指定不同的用戶名和電子郵箱地址。
創(chuàng)建版本庫
版本庫又名倉庫(repository,簡稱 repo),可以簡單理解成一個目錄。這個目錄里面的所有文件都可以被 Git 管理起來,每個文件的修改、刪除,Git 都能跟蹤,以便任何時刻都可以追蹤歷史,或者在將來某個時刻可以“還原”。
新建空的 Git 倉庫
所以,創(chuàng)建一個版本庫非常簡單。首先,選擇一個合適的地方,創(chuàng)建一個空目錄:
$ mkdir learnGit
$ cd learnGit
$ pwd
/d/OneDrive/Documents (no Chinese)/Programming projects/Git/learnGit
pwd
命令用于顯示當前目錄的完整路徑。在當前演示的機器上,這個倉庫位于 /d/OneDrive/Documents (no Chinese)/Programming projects/Git/learnGit
,即 D:\\OneDrive\\Documents (no Chinese)\\Programming projects\\Git\\learnGit
。
為了避免遇到奇怪的問題,請確保路徑不含中文和全角字符。
第二步,通過 git init
命令把這個目錄變成 Git 可以管理的倉庫:
$ git init
Initialized empty Git repository in D:/OneDrive/Documents (no Chinese)/Programming projects/Git/learnGit/.git/
這樣就創(chuàng)建好了一個空的 Git 倉庫。
可以發(fā)現(xiàn)當前目錄下多了一個 .git
的目錄,這個目錄是 Git 用來跟蹤管理版本庫的,不要改動它。
如果沒有看到 .git
目錄,那是因為這個目錄是默認隱藏的,用 ls -ah
命令就可以看見。
把文件添加到版本庫
首先聲明,版本控制系統(tǒng)只能跟蹤文本文件(如 txt 文件、網(wǎng)頁、代碼等)的改動,而對于二進制文件(如圖片、視頻、Microsoft Word 文檔等),則無法跟蹤其改動的具體內容。
至于文本的編碼問題,建議統(tǒng)一使用標準的 UTF-8 編碼。
注意不要使用 Windows 內置的記事本,因為它會在文本的開頭添加 BOM(byte-order mark,字節(jié)順序標記)標識符,從而產生一些奇怪的問題。
建議使用其它的文本編輯器或者代碼編輯器代替。
這里演示時使用 Visual Studio Code,其默認編碼是 UTF-8 (without BOM)。
現(xiàn)在,在 learnGit
或其子目錄下新建一個 readme.txt
文件,內容如下:
Git is a kind of version control system.
Git is a free software.
然后將其添加到 Git 倉庫:
-
使用
git add
命令,把文件添加到倉庫:$ git add readme.txt
執(zhí)行上面的命令,沒有任何顯示,說明添加成功。
-
使用
git commit
命令,把文件提交到倉庫:$ git commit -m "wrote a readme file" [master (root-commit) fe1e4d3] wrote a readme file 1 file changed, 2 insertions(+) create mode 100644 readme.txt
git commit
命令中的-m
后面輸入的是本次提交的說明。
git commit
命令執(zhí)行成功后會提示:
-
1 file changed
:1 個文件被改動(新添加了readme.txt
文件); -
2 insertions
:插入了 2 行內容(readme.txt 有兩行內容)。
為什么 Git 添加文件需要 add
和 commit
一共兩步呢?因為 commit
可以一次提交多個文件,所以可以多次 add
不同的文件,比如:
$ git add file1.txt
$ git add file2.txt file3.txt
$ git commit -m "add 3 files."
版本切換
前面已經(jīng)成功地添加并提交了一個 readme.txt
文件。
現(xiàn)在繼續(xù)修改該文件,內容如下:
Git is a kind of distributed version control system.
Git is an open-source and free software.
現(xiàn)在運行 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
命令可以時刻掌握倉庫當前的狀態(tài)。上面的命令輸出顯示,readme.txt
被修改過了,尚無準備提交的修改。
為了查看修改的具體內容,使用 git diff
命令:
$ git diff readme.txt
diff --git a/readme.txt b/readme.txt
index 094488a..979fc42 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,2 +1,2 @@
-Git is a kind of version control system.
-Git is a free software.
\ No newline at end of file
+Git is a kind of distributed version control system.
+Git is an open-source and free software.
\ No newline at end of file
git diff
顧名思義就是查看區(qū)別(difference)。可以從上面的命令輸出看到所做的詳細更改。
提交修改和提交新文件的步驟相同。第一步是 git add
:
$ git add readme.txt
同樣沒有任何輸出。在執(zhí)行第二步 git commit
之前,先執(zhí)行 git status
看下當前倉庫的狀態(tài):
$ 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 and open-source"
[master c1bde27] add distributed and open-source
1 file changed, 2 insertions(+), 2 deletions(-)
提交后,再用 git status
命令查看倉庫的當前狀態(tài):
$ git status
On branch master
nothing to commit, working tree clean
Git 顯示當前沒有需要提交的修改,而且工作目錄(working tree)是干凈(clean)的。
版本回退
再一次修改 readme.txt
文件:
Git is a kind of distributed version control system.
Git is an open-source and free software under the GPL.
然后提交:
$ git add readme.txt
$ git commit -m "append GPL"
[master f5009b5] append GPL
1 file changed, 1 insertion(+), 1 deletion(-)
現(xiàn)在,readme.txt
文件一共提交了 3 個版本。實際應用場景下的次數(shù)可能遠大于這個數(shù)字,這時候就需要能夠利用版本控制系統(tǒng)查看歷史記錄。Git 使用 git log
命令查看提交日志:
$ git log
commit dd14d507f36008027b17a64b63ab2ac6fd12bd21 (HEAD -> master)
Author: Roger Kung .WIN <RogerKung.WIN@outlook.com>
Date: Fri Jul 27 16:58:57 2018 +0800
append GPL
commit 8ffb301a9ad7ec9bbecb5f0c0c9defd744b3011d
Author: Roger Kung .WIN <RogerKung.WIN@outlook.com>
Date: Fri Jul 27 16:58:33 2018 +0800
add distributed and open-source
commit 77598901e83a6145a57311b181596c1837627335
Author: Roger Kung .WIN <RogerKung.WIN@outlook.com>
Date: Fri Jul 27 16:56:15 2018 +0800
wrote a readme file
git log
命令顯示從最近到最遠的提交日志,可以看到 3 次提交。
如果嫌輸出信息太多,看得眼花繚亂,可以試試加上 --pretty=oneline
參數(shù):
$ git log --pretty=oneline
dd14d507f36008027b17a64b63ab2ac6fd12bd21 (HEAD -> master) append GPL
8ffb301a9ad7ec9bbecb5f0c0c9defd744b3011d add distributed and open-source
77598901e83a6145a57311b181596c1837627335 wrote a readme file
這里需要說明的是,commit
后面的一長串字符是提交 ID(commit id),是由 SHA1 計算出來的十六進制數(shù)字。之所以不用 1, 2, 3, … 這樣遞增的數(shù)字,是為了防止多人協(xié)作時出現(xiàn)沖突。
每提交一個新版本,實際上 Git 就會把它們自動串成一條時間線。如果使用可視化工具查看 Git 歷史,就可以更清楚地看到提交歷史的時間線。
接下來將 readme.txt
回退到上一個版本,也就是第 2 個版本。
首先,Git 必須知道當前版本是哪個版本。在 Git 中,用 HEAD
表示當前版本,也就是最新的提交 ID。上一個版本是 HEAD^
,再上一個版本就是 HEAD^^
,往上 個版本寫成
HEAD~n
。
接下來使用 git reset
命令:
$ git reset --hard HEAD^
HEAD is now at 8ffb301 add distributed and open-source
檢查一下 readme.txt
的內容:
$ cat readme.txt
Git is a kind of distributed version control system.
Git is an open-source and free software.
可以看到的確被還原了。
使用 git log
看看現(xiàn)在版本庫的狀態(tài):
$ git log
commit 8ffb301a9ad7ec9bbecb5f0c0c9defd744b3011d (HEAD -> master)
Author: Roger Kung .WIN <RogerKung.WIN@outlook.com>
Date: Fri Jul 27 16:58:33 2018 +0800
add distributed and open-source
commit 77598901e83a6145a57311b181596c1837627335
Author: Roger Kung .WIN <RogerKung.WIN@outlook.com>
Date: Fri Jul 27 16:56:15 2018 +0800
wrote a readme file
最新版本 append GPL
已經(jīng)不顯示了。
為了回到第 3 個版本,可以找到該版本的提交 ID,是 dd14d50…
,然后繼續(xù)使用 git reset
命令:
$ git reset --hard dd14d
HEAD is now at dd14d50 append GPL
版本號無需寫全,一般最長 7 位即可,當然也不可太短,以免找到多個,總之能確定是哪個版本即可。
再次檢查 readme.txt
的內容:
$ cat readme.txt
Git is a kind of distributed version control system.
Git is an open-source and free software under the GPL.
并使用 git log
命令:
$ git log
commit dd14d507f36008027b17a64b63ab2ac6fd12bd21 (HEAD -> master)
Author: Roger Kung .WIN <RogerKung.WIN@outlook.com>
Date: Fri Jul 27 16:58:57 2018 +0800
append GPL
commit 8ffb301a9ad7ec9bbecb5f0c0c9defd744b3011d
Author: Roger Kung .WIN <RogerKung.WIN@outlook.com>
Date: Fri Jul 27 16:58:33 2018 +0800
add distributed and open-source
commit 77598901e83a6145a57311b181596c1837627335
Author: Roger Kung .WIN <RogerKung.WIN@outlook.com>
Date: Fri Jul 27 16:56:15 2018 +0800
wrote a readme file
可以看到已經(jīng)成功恢復。
Git 提供了一個命令
git reflog
用來記錄每一次命令:$ git reflog dd14d50 (HEAD -> master) HEAD@{0}: reset: moving to dd14d 8ffb301 HEAD@{1}: reset: moving to HEAD^ dd14d50 (HEAD -> master) HEAD@{2}: commit: append GPL 8ffb301 HEAD@{3}: commit: add distributed and open-source 7759890 HEAD@{4}: commit (initial): wrote a readme file
這可以確保在關掉命令行窗口或清空后依然能找到每一個版本的提交 ID。
工作區(qū)和暫存區(qū)
Git 和其它版本控制系統(tǒng)如 SVN 的一個不同之處就是有暫存區(qū)的概念。
工作區(qū):當前工作的目錄。比如這里演示時的
learnGit
文件夾。-
版本庫:工作區(qū)有一個隱藏目錄
.git
,這個不算工作區(qū),而是 Git 的版本庫。Git 的版本庫里存了很多東西,其中最重要的就是稱為 stage(或者 index)的暫存區(qū),還有 Git 自動創(chuàng)建的第一個分支 master,以及指向 master 的一個指針
HEAD
。工作區(qū)與版本庫分支和
HEAD
的概念后面再講。
前面講到,在把文件往 Git 版本庫里添加的時候,是分兩步執(zhí)行的:
- 使用
git add
把文件添加進去,實際上就是把文件更改添加到暫存區(qū); - 使用
git commit
提交更改,實際上就是把暫存區(qū)的所有內容提交到當前分支。
因為創(chuàng)建 Git 版本庫時,Git 自動創(chuàng)建了唯一一個 master 分支,所以現(xiàn)在的 git commit
就是往 master 分支上提交更改。
可以簡單地理解為,需要提交的文件更改全部放到暫存區(qū),然后一次性提交暫存區(qū)的所有更改。
現(xiàn)在繼續(xù)修改 readme.txt
:
Git is a kind of distributed version control system.
Git is an open-source and free software under the GPL.
Git has a mutable index called stage.
然后,在工作區(qū)新增一個無擴展名的 LICENSE
文本文件,內容隨意,示例如下:
MIT License
Copyright (c) 2018 - present Roger Kung
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
先用 git status
查看一下狀態(tài):
$ 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
Untracked files:
(use "git add <file>..." to include in what will be committed)
LICENSE
no changes added to commit (use "git add" and/or "git commit -a")
Git 顯示,readme.txt
被修改了,而 LICENSE
還尚未添加過,所以它的狀態(tài)是 Untracked
(未跟蹤)。
現(xiàn)在,將這兩個文件添加之后再重新查看狀態(tài):
$ git add readme.txt
$ git add LICENSE
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: LICENSE
modified: readme.txt
現(xiàn)在,暫存區(qū)的狀態(tài)就變成這樣了:
所以,git add
命令實際上就是把要提交的所有更改放到暫存區(qū)(stage)。然后,執(zhí)行 git commit
就可以一次性把暫存區(qū)的所有更改都提交到分支。
$ git commit -m "understand how stage works"
[master d3daca8] understand how stage works
2 files changed, 25 insertions(+), 1 deletion(-)
create mode 100644 LICENSE
此時已全部提交完畢,工作區(qū)是“干凈”的:
$ git status
On branch master
nothing to commit, working tree clean
現(xiàn)在版本庫變成了這樣,暫存區(qū)就沒有任何內容了:
管理更改
Git 跟蹤并管理的是更改,而非文件。
”更改“包含的除了文件內容的增刪改之外,還有創(chuàng)建新文件、刪除文件等。
下面繼續(xù)通過示例來說明為什么說 Git 管理的是更改而不是文件。
-
修改
readme.txt
,比如加一行內容:Git is a kind of distributed version control system. Git is an open-source and free software under the GPL. Git has a mutable index called stage. Git tracks changes.
-
添加,并查看狀態(tài):
$ git add readme.txt $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified: readme.txt
-
再次修改
readme.txt
:Git is a kind of distributed version control system. Git is an open-source and free software under the GPL. Git has a mutable index called stage. Git tracks changes of files.
-
提交:
$ git commit -m "git tracks changes" [master 2d05731] git tracks changes 1 file changed, 2 insertions(+), 1 deletion(-)
-
提交后,再查看狀態(tài):
$ 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")
可以發(fā)現(xiàn)第二次更改并沒有提交。
回顧一下會發(fā)現(xiàn),第二次更改后并沒有 git add
就直接提交了。也就是說,第二次更改并沒有放入暫存區(qū),暫存區(qū)只包含第一次更改。而 git commit
只會提交暫存區(qū)包含的更改,所以就只提交第一次更改了。
可以查看一下工作區(qū)和分支里最新版本的區(qū)別:
$ git diff HEAD -- readme.txt
diff --git a/readme.txt b/readme.txt
index 60c6271..0582b8a 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,4 +1,4 @@
Git is a kind of distributed version control system.
Git is an open-source and free software under the GPL.
Git has a mutable index called stage.
-Git tracks changes.
\ No newline at end of file
+Git tracks changes of files.
\ No newline at end of file
可見,第二次更改確實沒有提交。
要提交第二次更改,可以繼續(xù) git add
再 git commit
;也可以在所有更改完成后,最后統(tǒng)一提交。
撤銷更改
假設現(xiàn)在 readme.txt
中包含了錯誤的更改:
Git is a kind of distributed version control system.
Git is an open-source and free software under the GPL.
Git has a mutable index called stage.
Git tracks changes of files.
PHP is the best language.
這時查看一下狀態(tài):
$ 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")
可以發(fā)現(xiàn),Git 會提示可以使用 git checkout -- <file>
命令丟棄工作區(qū)的更改。
這里分兩種情況:
- 文件自更改后還沒有被放到暫存區(qū)。現(xiàn)在撤銷更改就回到和版本庫一模一樣的狀態(tài);
- 文件已經(jīng)添加到暫存區(qū)后,又作了修改。現(xiàn)在撤銷更改就回到添加到暫存區(qū)后的狀態(tài)。
總之,就是讓這個文件回到最近一次 git commit
或 git add
時的狀態(tài)。
在撤銷之后,查看一下 readme.txt
的內容:
$ git checkout -- readme.txt
$ cat readme.txt
Git is a kind of distributed version control system.
Git is an open-source and free software under the GPL.
Git has a mutable index called stage.
Git tracks changes of files.
文件內容的確復原了。
現(xiàn)在假設這個錯誤的更改已經(jīng)加入到暫存區(qū)了:
$ cat readme.txt
Git is a kind of distributed version control system.
Git is an open-source and free software under the GPL.
Git has a mutable index called stage.
Git tracks changes of files.
PHP is the best language.
$ git add readme.txt
用 git status
查看一下會發(fā)現(xiàn),更改只是添加到了暫存區(qū),還沒有提交:
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: readme.txt
Git 依然會提示,使用 git reset HEAD <file>
可以把暫存區(qū)的更改撤銷(unstage),重新放回工作區(qū):
$ git reset HEAD readme.txt
Unstaged changes after reset:
M readme.txt
再用 git status
查看發(fā)現(xiàn),現(xiàn)在暫存區(qū)是干凈的,工作區(qū)有更改:
$ 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")
接下來如何丟棄工作區(qū)的更改就不言而喻了,使用 git checkout -- <file>
即可:
$ git checkout -- readme.txt
$ git status
On branch master
nothing to commit, working tree clean
這下撤銷就完成了。
那么如果錯誤的更改已經(jīng)提交到版本庫了,該怎么辦呢?這時候需要用到的就是版本回退了,前面已經(jīng)講過。
刪除文件
在 Git 中,刪除文件也屬于更改。
現(xiàn)在先添加一個新文件 test.txt
到 Git 并提交:
$ git add test.txt
$ git commit -m "add test.txt"
[master cd6db6b] add test.txt
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 test.txt
一般情況下,通常直接在文件資源管理器中刪除文件,或者使用 rm
命令:
$ rm test.txt
此時,Git 會發(fā)現(xiàn)刪除了文件,工作區(qū)和版本庫就不一致了。
git status
會提示刪除了 test.txt
:
$ 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 rm test.txt
rm 'test.txt'
$ git commit -m "remove test.txt"
[master 9b333fa] remove test.txt
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 test.txt
這里使用
git rm <file>
與git add <file>
是一樣的效果。
如果是誤刪,因為版本庫里還存有副本,所以可以直接從版本庫恢復:
$ git checkout -- test.txt
git checkout -- <file>
的原理是用版本庫的版本替換工作區(qū)的版本,不論工作區(qū)的更改是怎樣的。
遠程倉庫
遠程倉庫,即是將倉庫放在服務器上,每個人都可以從這個倉庫克隆到自己的機器上,并且可以把各自的提交推送到服務器倉庫,還可以從服務器倉庫拉取別人的提交。
這里使用 GitHub 存放遠程倉庫。
GitHub 是目前最流行的提供 Git 倉庫托管服務的網(wǎng)站。因此只要注冊一個 GitHub 賬號,就可以免費獲得 Git 遠程倉庫。請讀者自行注冊 GitHub 賬號。
GitHub 上的免費倉庫是公開的。
如果想不公開,可以選擇付費以創(chuàng)建私有倉庫,也可以自己動手搭建 Git 服務器(也就是不使用 GitHub 提供的倉庫)。
添加遠程倉庫
現(xiàn)在已經(jīng)有了一個本地倉庫,而又想在 GitHub 創(chuàng)建一個倉庫,并讓這兩個倉庫進行遠程同步。這樣,GitHub 上的倉庫既可以作為備份,又可以讓其他人通過該倉庫來協(xié)作。
首先,登錄 GitHub,在頁面右上角找到「New repository」,創(chuàng)建一個新的倉庫。在「Repository name」文本框填入 learnGit
,「Description」任意,其余保持默認,點擊「Create repository」,就成功地創(chuàng)建了一個新的 Git 倉庫。
目前,GitHub 上的這個 learnGit
倉庫還是空的。GitHub 提示,可以把一個已有的本地倉庫與之關聯(lián),然后把本地倉庫的內容推送到 GitHub 倉庫。
現(xiàn)在在本地的 learnGit
倉庫下運行命令:
# 切記將 username 替換成自己 GitHub 賬戶的用戶名
$ git remote add origin https://github.com/username/learnGit.git
添加后,遠程倉庫的名字就是 origin,這是 Git 默認的叫法,也可以改成別的,但是 origin 這個名字一看就知道是遠程倉庫。
下一步,就可以把本地倉庫的所有內容推送到遠程倉庫上:
$ git push -u origin master
Enumerating objects: 25, done.
Counting objects: 100% (25/25), done.
Delta compression using up to 4 threads.
Compressing objects: 100% (21/21), done.
Writing objects: 100% (25/25), 2.73 KiB | 310.00 KiB/s, done.
Total 25 (delta 8), reused 0 (delta 0)
remote: Resolving deltas: 100% (8/8), done.
# 此處的 username 會顯示為自己 GitHub 賬戶的用戶名
To https://github.com/username/learnGit.git
* [new branch] master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.
期間會彈出窗口要求登錄,輸入自己 GitHub 賬戶的用戶名、密碼登錄即可:
這樣就把 learnGit
本地倉庫的 master 分支推送到了遠程倉庫。
由于遠程倉庫是空的,第一次推送 master 分支時,加上了 -u
參數(shù),Git 不但會把本地 master 分支的內容推送到遠程新的 master 分支,還會把這兩個分支關聯(lián)起來,在以后的推送或者拉取時就可以簡化命令。
推送成功后,可以在 GitHub 頁面中看到遠程倉庫的內容已經(jīng)和本地一樣。
從現(xiàn)在起,只要本地作了提交,就可以通過命令:
$ git push origin master
把本地 master 分支的最新更改推送至 GitHub。
從遠程倉庫克隆
前面講了先有本地倉庫,后有遠程倉庫的時候,如何關聯(lián)遠程倉庫。
現(xiàn)在,假設從零開始,那么最好的方式是先創(chuàng)建遠程倉庫,然后從遠程倉庫克隆。
首先,登錄 GitHub,創(chuàng)建一個新的倉庫,名字叫 gitSkills
。勾選上 Initialize this repository with a README
這項,這樣 GitHub 會自動創(chuàng)建一個 README。創(chuàng)建完畢后,可以看到 README.md
文件。
現(xiàn)在,遠程倉庫已經(jīng)準備好了。
先定位到 learnGit
目錄的上一級目錄,以免將新的倉庫克隆到 learnGit
倉庫下:
$ cd ../
然后用 git clone
命令克隆出一個本地倉庫:
# 切記將 username 替換成自己 GitHub 賬戶的用戶名
$ git clone https://github.com/username/gitSkills.git
Cloning into 'gitSkills'...
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
會發(fā)現(xiàn)在 gitSkills
目錄下已經(jīng)有 README.md
文件了:
$ cd gitSkills
$ ls
README.md
如果有多個人協(xié)作開發(fā),那么每個人各自從遠程克隆一份就可以了。
分支管理
創(chuàng)建與合并分支
每次提交,Git 都把它們串成一條時間線,這條時間線就是一個分支。截止到目前,只有一條時間線,在 Git 里,這個分支叫主分支,即 master 分支。HEAD
嚴格來說不是指向提交,而是指向 master, master 才是指向提交的。所以,HEAD
指向的就是當前分支。
一開始的時候, master 分支是一條線,Git 用 master 指向最新的提交,再用 HEAD
指向 master ,就能確定當前分支,以及當前分支的提交點:
每次提交,master 分支都會向前移動一步。這樣,隨著不斷的提交,master 分支的線也越來越長:
當創(chuàng)建新的分支,例如 dev 時,Git 新建了一個指針叫 dev,指向 master 相同的提交,再把 HEAD
指向 dev,就表示當前分支在 dev 上:
可以看到,Git 創(chuàng)建一個分支很快,因為僅僅是增加一個 dev 指針和改變 HEAD
的指向,而工作區(qū)的文件沒有任何變化。
不過,從現(xiàn)在開始,對工作區(qū)的修改和提交就是針對 dev 分支了。比如新提交一次后,dev 指針往前移動一步,而 master 指針不變:
假如在 dev 上的工作完成了,就可以把 dev 合并到 master 上。最簡單的合并方法,就是直接把 master 指向 dev 的當前提交,就完成了合并:
所以 Git 合并分支也很快。只是改改指針,工作區(qū)內容是不變的。
合并完分支后,甚至可以刪除 dev 分支。刪除 dev 分支就是把 dev 指針給刪掉。刪掉后,就剩下了一條 master 分支:
整個過程十分簡單:
下面開始在本地的 learnGit
倉庫中實際操作。
首先,創(chuàng)建 dev 分支,然后切換到 dev 分支:
$ git checkout -b dev
Switched to a new branch 'dev'
git checkout
命令加上 -b
參數(shù)表示創(chuàng)建并切換,相當于以下兩條命令:
$ 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 617d781] branch test
1 file changed, 2 insertions(+), 1 deletion(-)
現(xiàn)在,dev 分支的工作完成,就可以切換回 master 分支:
$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
切換回 master 分支后,再查看一下 readme.txt
文件,會發(fā)現(xiàn)剛才添加的內容不見了。因為那個提交是在 dev 分支上,而 master 分支此刻的提交點并沒有變:
現(xiàn)在,把 dev 分支的工作成果合并到 master 分支上:
$ git merge dev
Updating 91c72e7..617d781
Fast-forward
readme.txt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
git merge
命令用于合并指定分支到當前分支。合并后,再查看 readme.txt
的內容,就可以看到,和 dev 分支的最新提交是完全一樣的。
注意到上面的 Fast-forward
信息,表示這次合并是 “快進模式”,也就是直接把 master 指向 dev 的當前提交,所以合并速度非常快。
當然,也不是每次合并都能
Fast-forward
,后面會講其他方式的合并。
合并完成后,就可以放心地刪除 dev 分支了:
$ git branch -d dev
Deleted branch dev (was 617d781).
刪除后,查看 branch
,就只剩下 master 分支了:
$ git branch
* master
因為創(chuàng)建、合并和刪除分支非常快,所以 Git 推薦使用分支完成某個任務,合并后再刪掉分支,這和直接在 master 分支上工作效果是一樣的,但過程更安全。
解決沖突
準備新的 feature1 分支,繼續(xù)新分支開發(fā):
$ 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 f07a008] 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.
(use "git push" to publish your local commits)
Git 還提示道當前本地的 master 分支比遠程的 master 分支要超前 1 個提交。
在 master 分支上把 readme.txt
文件的最后一行改為:
Creating a new branch is quick & simple.
然后提交:
$ git add readme.txt
$ git commit -m "& simple"
[master b7955cc] & simple
1 file changed, 1 insertion(+), 1 deletion(-)
現(xiàn)在,master 分支和 feature1 分支各自都分別有新的提交,變成了這樣:
這種情況下,Git 無法執(zhí)行“快速合并”,只能試圖把各自的修改合并起來,但這種合并就可能會有沖突,試試看:
$ 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.
(use "git push" to publish your local commits)
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." 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 kind of distributed version control system.
Git is an open-source and free software 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 d6ba86f] conflict fixed
現(xiàn)在,master 分支和 feature1 分支變成了下圖所示:
用帶參數(shù)的 git log
也可以看到分支的合并情況:
$ git log --graph --pretty=oneline --abbrev-commit
* d6ba86f (HEAD -> master) conflict fixed
|\
| * f07a008 (feature1) AND simple
* | b7955cc & simple
|/
* 617d781 branch test
* 91c72e7 (origin/master, origin/HEAD) remove test.txt
* cd6db6b add test.txt
* 5a3018c append that changes belong to files
* 2d05731 git tracks changes
* d3daca8 understand how stage works
* dd14d50 append GPL
* 8ffb301 add distributed and open-source
* 7759890 wrote a readme file
最后,刪除 feature1 分支:
$ git branch -d feature1
Deleted branch feature1 (was f07a008).
完成。
分支管理策略
通常,合并分支時,如果可能,Git 會用 Fast forward
模式,但這種模式下,刪除分支后,會丟掉分支信息。
如果要強制禁用 Fast forward
模式,Git 就會在合并時生成一個新的提交,這樣從分支歷史上就可以看出分支信息。
下面實戰(zhàn)一下 --no-ff
方式的 git merge
。
首先,仍然創(chuàng)建并切換 dev 分支:
$ git checkout -b dev
Switched to a new branch 'dev'
修改 readme.txt
文件后,提交:
$ git add readme.txt
$ git commit -m "add merge"
[dev 5653f90] add merge
1 file changed, 2 insertions(+), 1 deletion(-)
現(xiàn)在,切換回 master 分支:
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 4 commits.
(use "git push" to publish your local commits)
準備合并 dev 分支,請注意 --no-ff
參數(shù),表示禁用 Fast forward
:
$ git merge --no-ff -m "merge with no-ff" dev
Merge made by the 'recursive' strategy.
readme.txt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
因為本次合并要創(chuàng)建一個新的提交,所以加上 -m
參數(shù),把提交描述寫進去。
合并后,用 git log
看看分支歷史:
$ git log --graph --pretty=oneline --abbrev-commit
* f23e624 (HEAD -> master) merge with no-ff
|\
| * 5653f90 (dev) add merge
|/
* d6ba86f conflict fixed
…
可以看到,不使用 Fast forward
模式,合并后就像這樣:
分支策略
在實際開發(fā)中,我們應該按照幾個基本原則進行分支管理:
- master 分支應當是非常穩(wěn)定的,也就是僅用來發(fā)布新版本,平時不能在上面干活;
- 干活都在 dev 分支上,也就是說,dev 分支是不穩(wěn)定的。到某個時候,比如 1.0 版本發(fā)布時,再把 dev 分支合并到 master 上,在 master 分支發(fā)布 1.0 版本;
- 每個人都在 dev 分支上干活,每個人都有自己的分支,時不時地往 dev 分支上合并就可以了。
所以,團隊合作的分支看起來就像這樣:
Bug 分支
在 Git 中,由于分支十分強大,所以每個 bug 都可以通過一個新的臨時分支來修復。修復后,合并分支,然后將臨時分支刪除。
假設現(xiàn)在接到修復一個代號 101 的 bug 的任務。很自然地,會想到創(chuàng)建一個分支 issue-101
來修復它,但是當前正在 dev 上進行的工作還沒有提交:
$ git status
On branch dev
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: hello.py
并不是不想提交,而是工作只進行到一半,還沒法提交。
這時,可以使用 Git 提供的 stash
功能,把當前工作現(xiàn)場“儲藏”起來,等以后恢復現(xiàn)場后繼續(xù)工作:
$ git stash
Saved working directory and index state WIP on dev: 5653f90 add merge
現(xiàn)在,用 git status
查看工作區(qū),就是干凈的(除非有沒被 Git 管理的文件):
$ git status
On branch dev
nothing to commit, working tree clean
因此可以放心地創(chuàng)建分支來修復 bug。
首先確定要在哪個分支上修復 bug。假定需要在 master 分支上修復,就從 master 創(chuàng)建臨時分支:
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 6 commits.
(use "git push" to publish your local commits)
$ git checkout -b issue-101
Switched to a new branch 'issue-101'
現(xiàn)在修復 bug,然后提交:
$ git add readme.txt
$ git commit -m "fix bug 101"
[issue-101 76bb2e6] fix bug 101
1 file changed, 1 insertion(+), 1 deletion(-)
修復完成后,切換到 master 分支,并完成合并,最后刪除 issue-101 分支:
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 6 commits.
(use "git push" to publish your local commits)
$ git merge --no-ff -m "merged bug fix 101" issue-101
Merge made by the 'recursive' strategy.
readme.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
$ git branch -d issue-101
Deleted branch issue-101 (was 76bb2e6).
現(xiàn)在,回到 dev 分支:
$ git checkout dev
Switched to branch 'dev'
$ git status
On branch dev
nothing to commit, working tree clean
現(xiàn)在工作區(qū)是干凈的。用 git stash list
命令查看先前保存的工作現(xiàn)場:
$ git stash list
stash@{0}: WIP on dev: 5653f90 add merge
工作現(xiàn)場還在,Git 把 stash 內容存在某個地方了,但是需要恢復一下。有兩個辦法:
一種是用 git stash apply
恢復,但是恢復后,stash 內容并不刪除,需要用 git stash drop
來刪除;
另一種是用 git stash pop
,恢復的同時把 stash 內容也刪了:
$ git stash pop
On branch dev
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: hello.py
Dropped refs/stash@{0} (261bfc06d2c90d373f86441cbd64fbeb79941d09)
再用 git stash list
查看,就看不到任何 stash 內容了:
$ git stash list
可以多次 stash。恢復的時候,先用 git stash list
查看,然后恢復指定的 stash,用命令:
$ git stash apply stash@{0}
Feature 分支
添加一個新功能時,最好新建一個 feature 分支,在上面開發(fā)。完成后合并,最后刪除該 feature 分支。
假設現(xiàn)在接到了開發(fā)代號為 Vulcan 的新功能的任務。
于是準備開發(fā):
$ git checkout -b feature-vulcan
Switched to a new branch 'feature-vulcan'
開發(fā)完畢后提交:
$ git add vulcan.py
$ git status
On branch feature-vulcan
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: vulcan.py
$ git commit -m "add feature vulcan"
[feature-vulcan 0514503] add feature vulcan
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 vulcan.py
切回 dev,準備合并:
$ git checkout dev
Switched to branch 'dev'
一切順利的話,feature 分支和 bug 分支是類似的,合并,然后刪除。
然而,收到通知說,這個功能現(xiàn)在要取消,已完成的要銷毀:
$ git branch -d feature-vulcan
error: The branch 'feature-vulcan' is not fully merged.
If you are sure you want to delete it, run 'git branch -D feature-vulcan'.
銷毀失敗。Git 提示,feature-vulcan
分支還沒有被合并,如果刪除,將丟失更改。如果要強行刪除,需要使用大寫的 -D
參數(shù)。
現(xiàn)在強行刪除:
$ git branch -D feature-vulcan
Deleted branch feature-vulcan (was 0514503).
銷毀完成。
多人協(xié)作
當從遠程倉庫克隆時,實際上 Git 自動把本地的 master 分支和遠程的 master 分支對應起來了。并且,遠程倉庫的默認名稱是 origin。
要查看遠程庫的信息,用 git remote
:
$ git remote
origin
或者,用 git remote -v
顯示更詳細的信息:
$ git remote -v
origin https://github.com/username/learnGit.git (fetch)
origin https://github.com/username/learnGit.git (push)
上面顯示了可以抓取和推送的 origin 的地址。如果沒有推送權限,就看不到 push 的地址。
推送分支
推送分支,就是把該分支上的所有本地提交推送到遠程倉庫。推送時,要指定本地分支,這樣 Git 就會把該分支推送到遠程倉庫對應的遠程分支上:
$ git push origin master
如果要推送其他分支,比如 dev,就改成:
$ git push origin dev
但是,并不是所有本地分支都要往遠程推送:
- master 分支是主分支,因此要時刻與遠程同步;
- dev 分支是開發(fā)分支,團隊所有成員都需要在上面工作,所以也需要與遠程同步;
- bug 分支只用于在本地修復 bug,就沒必要推到遠程了;
- feature 分支是否推到遠程,取決于是否合作開發(fā)。
總之,可以按需選擇。
抓取分支
多人協(xié)作時,大家都會往 master 和 dev 分支上推送各自的修改。
現(xiàn)在,模擬一個同伴(即協(xié)作者),可以在另一臺機器或者同一臺機器的另一個目錄下克隆:
$ git clone https://github.com/username/learnGit.git
Cloning into 'learnGit'...
remote: Counting objects: 55, done.
remote: Compressing objects: 100% (33/33), done.
remote: Total 55 (delta 19), reused 47 (delta 16), pack-reused 0
Unpacking objects: 100% (55/55), done.
當從遠程倉庫克隆時,默認情況下,只能看到本地的 master 分支:
$ git branch
* master
因此,同伴要在 dev 分支上開發(fā),就必須創(chuàng)建遠程 origin 的 dev 分支到本地:
$ git checkout -b dev origin/dev
Switched to a new branch 'dev'
Branch 'dev' set up to track remote branch 'dev' from 'origin'.
現(xiàn)在,同伴就可以在 dev 上繼續(xù)修改,然后時不時地把 dev 分支推送到遠程倉庫:
$ git add env.txt
$ git commit -m "add env"
[dev e9e7f30] add env
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 env.txt
$ git push origin dev
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 350 bytes | 175.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://github.com/username/learnGit.git
e9e7f30..451d62a dev -> dev
同伴已經(jīng)向 origin/dev 分支推送了提交,而碰巧這邊也對同樣的文件作了修改,并試圖推送:
$ cat env.txt
env
$ git add env.txt
$ git commit -m "add new env"
[dev 0fdad88] add new env
1 file changed, 1 insertion(+)
create mode 100644 env.txt
$ git push origin dev
To https://github.com/username/learnGit.git
! [rejected] dev -> dev (fetch first)
error: failed to push some refs to 'https://github.com/username/learnGit.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
推送失敗,因為兩方推送的提交有沖突。
Git 提示,先用 git pull
把最新的提交從 origin/dev 抓下來,然后在本地合并,解決沖突,再推送:
$ git pull
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-to=origin/<branch> dev
git pull
也失敗了,原因是沒有指定本地 dev 分支與遠程 origin/dev 分支的鏈接。根據(jù)提示,設置 dev 和 origin/dev 的鏈接:
$ git branch --set-upstream-to=origin/dev dev
Branch 'dev' set up to track remote branch 'dev' from 'origin'.
再拉取:
$ git pull
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
From https://github.com/username/learnGit
e9e7f30..451d62a dev -> origin/dev
Auto-merging env.txt
CONFLICT (content): Merge conflict in env.txt
Automatic merge failed; fix conflicts and then commit the result.
這回 git pull
成功,但是合并有沖突,需要手動解決,解決的方法和分支管理中的解決沖突完全一樣。解決后,提交,再推送:
$ git commit -m "fix env conflict"
[dev 962eabe] fix env conflict
$ git push origin dev
Enumerating objects: 10, done.
Counting objects: 100% (10/10), done.
Delta compression using up to 4 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (5/5), 705 bytes | 352.00 KiB/s, done.
Total 5 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), done.
To https://github.com/username/learnGit.git
451d62a..962eabe dev -> dev
因此,多人協(xié)作的工作模式通常是這樣:
- 首先,可以試圖用
git push origin <branch-name>
推送自己的更改; - 如果推送失敗,則因為遠程分支比本地的要新,需要先用
git pull
試圖合并; - 如果合并有沖突,則解決沖突,并在本地提交;
- 沒有沖突或者解決掉沖突后,再用
git push origin <branch-name>
推送就能成功。
如果 git pull
提示 no tracking information
,則說明本地分支和遠程分支的鏈接關系沒有創(chuàng)建,用命令 git branch --set-upstream-to <branch-name> origin/<branch-name>
。
這就是多人協(xié)作的工作模式,一旦熟悉了,就非常簡單。
變基
多人在同一個分支上協(xié)作時,很容易出現(xiàn)沖突。即使沒有沖突,后推送的人不得不先抓取,在本地合并,然后才能推送成功。
每次合并再推送后,分支變成了這樣:
$ git log --graph --pretty=oneline --abbrev-commit
* 962eabe (HEAD -> dev, origin/dev) fix env conflict
|\
| * 451d62a add env
* | 96eb6c8 Merge branch 'dev' of https://github.com/username/learnGit into dev|\ \
| |/
| * e9e7f30 add env
* | 0fdad88 add new env
|/
* 153a20a add hello.py
* 5653f90 add merge
* d6ba86f conflict fixed
|\
| * f07a008 and simple
* | b7955cc & simple
|/
* 617d781 branch test
總之看上去很亂,而通常希望 Git 的提交歷史是一條干凈的直線。
Git 有一種稱為“變基”的操作,英文是 rebase。
假設現(xiàn)在本地分支比遠程分支超前兩個提交。在嘗試推送到遠程時,提示推送失敗。這說明有人先推送了。這時就得先抓取到本地。
然而這時,本地就超前了三個提交。提交歷史更亂了。
這時,就可以嘗試變基:
$ git rebase
First, rewinding head to replay your work on top of it...
Applying: add comment
Using index info to reconstruct a base tree...
M hello.py
Falling back to patching base and 3-way merge...
Auto-merging hello.py
Applying: add author
Using index info to reconstruct a base tree...
M hello.py
Falling back to patching base and 3-way merge...
Auto-merging hello.py
再用 git log
看看:
$ git log --graph --pretty=oneline --abbrev-commit
* 7e61ed4 (HEAD -> master) add author
* 3611cfe add comment
* f005ed4 (origin/master) set exit=1
* d1be385 init hello
…
注意觀察,發(fā)現(xiàn) Git 把本地的提交“挪動”了位置,放到了 f005ed4 (origin/master) set exit=1
之后。這樣,整個提交歷史就成了一條直線。變基前后,最終的提交內容是一致的。但是,本地提交的更改內容已經(jīng)變化了,不再基于 d1be385 init hello
,而是基于 f005ed4 (origin/master) set exit=1
,但最后的提交 7e61ed4
內容是一致的。
這就是變基的特點:把分叉的提交歷史 “整理” 成一條直線,看上去更直觀。缺點是本地的分叉提交已經(jīng)被修改過了。
最后,把本地分支推送到遠程:
$ git push origin master
Counting objects: 6, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (6/6), 576 bytes | 576.00 KiB/s, done.
Total 6 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), completed with 1 local object.
To https://github.com/username/learnGit.git
f005ed4..7e61ed4 master -> master
再用 git log
看看效果:
$ git log --graph --pretty=oneline --abbrev-commit
* 7e61ed4 (HEAD -> master, origin/master) add author
* 3611cfe add comment
* f005ed4 set exit=1
* d1be385 init hello
…
遠程分支的提交歷史也是一條直線。
標簽管理
發(fā)布一個版本時,通常先在版本庫中打一個標簽(tag),這樣就唯一確定了打標簽時刻的版本。將來無論什么時候,取某個標簽的版本,就是把那個打標簽的時刻的歷史版本取出來。所以,標簽也是版本庫的一個快照。
Git 的標簽雖然是版本庫的快照,但其實它就是指向某個提交的指針,所以創(chuàng)建和刪除標簽都是瞬間完成的。
創(chuàng)建標簽
在 Git 中打標簽非常簡單。首先,切換到需要打標簽的分支上:
$ git branch
* dev
master
$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
然后,敲命令 git tag <name>
就可以打一個新標簽:
$ git tag v1.0
可以用 git tag
命令查看所有標簽:
$ git tag
v1.0
默認標簽是打在最新的提交上的。如果想打在歷史提交上,只要找到歷史提交的 ID,然后使用 git tag <name> <commit id>
就可以了:
$ git log --pretty=oneline --abbrev-commit
4347952 (HEAD -> master, tag: v1.0, origin/master, origin/HEAD) Merge branch 'dev'
153a20a add hello.py
dd13e36 merged bug fix 101
76bb2e6 fix bug 101
f23e624 merge with no-ff
5653f90 add merge
d6ba86f conflict fixed
b7955cc & simple
f07a008 and simple
617d781 branch test
91c72e7 Create LICENSE
03c1b56 Delete LICENSE
2187014 append that changes belong to files
1f36c20 remove 'of files'
75ae830 remove test.txt
ce8c248 add test.txt
9b333fa remove test.txt
cd6db6b add test.txt
5a3018c append that changes belong to files
2d05731 git tracks changes
d3daca8 understand how stage works
dd14d50 append GPL
$ git tag v0.9 91c72e7
再用命令 git tag
查看標簽:
$ git tag
v0.9
v1.0
注意,標簽不是按時間順序列出,而是按字母排序的。可以用 git show <tagname>
查看標簽信息:
$ git show v0.9
commit 91c72e7820cba69a2d198692115fb06dadbf4d03 (tag: v0.9)
Author: Roger Kung .WIN <RogerKung.WIN@outlook.com>
Date: Sat Jul 28 12:33:09 2018 +0800
Create LICENSE
diff --git a/LICENSE b/LICENSE
…
還可以創(chuàng)建帶有說明的標簽,用 -a
指定標簽名,-m
指定說明文字:
$ git tag -a v0.8 -m "version 0.8 released" 2187014
用命令 git show <tagname>
可以看到說明文字:
$ git show v0.8
tag v0.8
Tagger: Roger Kung .WIN <RogerKung.WIN@outlook.com>
Date: Tue Jul 31 14:14:40 2018 +0800
version 0.8 released
commit 2187014dc4830c08c4a988dbd5c9d6d5bbdfab08 (tag: v0.8)
Author: Roger Kung .WIN <RogerKung.WIN@outlook.com>
Date: Sat Jul 28 11:30:56 2018 +0800
append that changes belong to files
diff --git a/readme.txt b/readme.txt
…
標簽總是和某個提交掛鉤。如果這個提交既出現(xiàn)在 master 分支,又出現(xiàn)在 dev 分支,那么在這兩個分支上都可以看到這個標簽。
刪除標簽
如果標簽打錯了,也可以刪除:
$ git tag -d v0.9
Deleted tag 'v0.9' (was 91c72e7)
因為創(chuàng)建的標簽都只存儲在本地,不會自動推送到遠程,所以打錯的標簽可以在本地安全刪除。
如果要推送某個標簽到遠程,使用命令 git push origin <tagname>
:
$ git push origin v1.0
Total 0 (delta 0), reused 0 (delta 0)
To https://github.com/username/learnGit.git
* [new tag] v1.0 -> v1.0
或者,一次性推送全部尚未推送到遠程的本地標簽:
$ git push origin --tags
Enumerating objects: 1, done.
Counting objects: 100% (1/1), done.
Writing objects: 100% (1/1), 176 bytes | 88.00 KiB/s, done.
Total 1 (delta 0), reused 0 (delta 0)
To https://github.com/username/learnGit.git
* [new tag] v0.8 -> v0.8
如果標簽已經(jīng)推送到遠程,要刪除遠程標簽就麻煩一點。先從本地刪除:
$ git tag -d v0.8
Deleted tag 'v0.8' (was a6e8d23)
然后從遠程刪除。刪除命令也是 push,且格式類似于刪除本地標簽:
$ git push origin -d tag v0.8
To https://github.com/username/learnGit.git
- [deleted] v0.8
要看看是否真的從遠程倉庫刪除了標簽,可以登錄 GitHub 查看。