前言
本篇文章主要Git 參考手冊。目的是為學(xué)習(xí)與記憶 Git 使用中最重要、最普遍的命令提供快速翻閱。 這些命令以你可能需要的操作類型劃分,并且將提供日常使用中需要的一些常用的命令以及參數(shù)。
文章源自:Git參考手冊
獲取與創(chuàng)建項(xiàng)目
你得先有一個(gè)Git庫,才能對它進(jìn)行操作。倉庫是Git存放你要保存的快照的地方。
$ git init 將一個(gè)目錄初始化為一個(gè)Git倉庫
在目錄中git init,就可以創(chuàng)建一個(gè)git倉庫了。
$ cd konichiwa
$ ls
README hello.rb
在這個(gè)項(xiàng)目里頭,我們會(huì)用各種編程語言寫 "Hello World" 實(shí)例。 到目前為止,我們只有 Ruby 的,不過,這才剛上路嘛。為了開始用 Git 對這個(gè)項(xiàng)目作版本控制,我們執(zhí)行一下 git init。
$ git init
Initialized empty Git repository in /opt/konichiwa/.git/
# 在 /opt/konichiwa/.git 目錄初始化空 Git 倉庫完畢。
現(xiàn)在你可以看到在你的項(xiàng)目目錄中有個(gè) .git 的子目錄。 這就是你的 Git 倉庫了,所有有關(guān)你的此項(xiàng)目的快照數(shù)據(jù)都存放在這里。
$ ls -a
. .. .git README hello.rb
恭喜,現(xiàn)在你就有了一個(gè) Git 倉庫的架子,可以開始快照你的項(xiàng)目了。
$ git clone 復(fù)制一個(gè) Git 倉庫,以上下其手
如果你需要與他人合作一個(gè)項(xiàng)目,或者想要復(fù)制一個(gè)項(xiàng)目,看看代碼,你就可以克隆那個(gè)項(xiàng)目。 執(zhí)行 git clone [url],[url] 為你想要復(fù)制的項(xiàng)目,就可以了。
$ git clone git://github.com/schacon/simplegit.git
Initialized empty Git repository in /private/tmp/simplegit/.git/
remote: Counting objects: 100, done.
remote: Compressing objects: 100% (86/86), done.
remote: Total 100 (delta 35), reused 0 (delta 0)
Receiving objects: 100% (100/100), 9.51 KiB, done.
Resolving deltas: 100% (35/35), done.
$ cd simplegit/
$ ls
README Rakefile lib
上述操作將復(fù)制該項(xiàng)目的全部記錄,讓你本地?fù)碛羞@些。并且該操作將拷貝該項(xiàng)目的主分支, 使你能夠查看代碼,或編輯、修改。進(jìn)到該目錄中,你會(huì)看到 .git 子目錄。 所有的項(xiàng)目數(shù)據(jù)都存在那里。
$ ls -a
. .. .git README Rakefile lib
$ cd .git
$ ls
HEAD description info packed-refs
branches hooks logs refs
config index objects
默認(rèn)情況下,Git 會(huì)按照你提供的 URL 所指示的項(xiàng)目的名稱創(chuàng)建你的本地項(xiàng)目目錄。 通常就是該 URL 最后一個(gè) / 之后的任何東西。如果你想要一個(gè)不一樣的名字, 你可以在該命令后加上它,就在那個(gè) URL 后面。
簡而言之,使用 git clone 拷貝一個(gè) Git 倉庫到本地,讓自己能夠查看該項(xiàng)目,或者進(jìn)行修改。
基本快照
Git 的工作就是創(chuàng)建和保存你的項(xiàng)目的快照及與之后的快照進(jìn)行對比。本章將對有關(guān)創(chuàng)建與提交你的項(xiàng)目的快照的命令作介紹。
這里有個(gè)重要的概念,Git 有一個(gè)叫做“索引”的東東,有點(diǎn)像是你的快照的緩存區(qū)。這就使你能夠從更改的文件中創(chuàng)建出一系列組織良好的快照,而不是一次提交所有的更改。
簡而言之,使用git add添加需要追蹤的新文件和待提交的更改, 然后使用 git status 和 git diff 查看有何改動(dòng), 最后用 git commit 將你的快照記錄。這就是你要用的基本流程,絕大部分時(shí)候都是這樣的。
$ git add添加文件到緩存
在 Git 中,在提交你修改的文件之前,你需要把它們添加到緩存。如果該文件是新創(chuàng)建的,你可以執(zhí)行 git add 將該文件添加到緩存,但是,即使該文件已經(jīng)被追蹤了 —— 也就是說,曾經(jīng)提交過了 —— 你仍然需要執(zhí)行
git add將新更改的文件添加到緩存去。讓我們看幾個(gè)例子:
回到我們的 Hello World 示例,初始化該項(xiàng)目之后,我們就要用 git add 將我們的文件添加進(jìn)去了。 我們可以用git status 看看我們的項(xiàng)目的當(dāng)前狀態(tài)。
$ git status -s
?? README
?? hello.rb
我們有倆尚未被追蹤的文件,得添加一下。
$ git add README hello.rb
現(xiàn)在我們再執(zhí)行 git status,就可以看到這倆文件已經(jīng)加上去了。
$ git status -s
A README
A hello.rb
新項(xiàng)目中,添加所有文件很普遍,可以在當(dāng)前工作目錄執(zhí)行命令:git add .。 因?yàn)?Git 會(huì)遞歸地將你執(zhí)行命令時(shí)所在的目錄中的所有文件添加上去,所以如果你將當(dāng)前的工作目錄作為參數(shù), 它就會(huì)追蹤那兒的所有文件了。如此git add .就和git add README hello.rb 有一樣的效果。 此外,效果一致的還有 git add *,不過那只是因?yàn)槲覀冞@還木有子目錄,不需要遞歸地添加新文件。
好了,現(xiàn)在我們改個(gè)文件,再跑一下 git status,有點(diǎn)古怪。
$ vim README
$ git status -s
AM README
A hello.rb
“AM” 狀態(tài)的意思是,這個(gè)文件在我們將它添加到緩存之后又有改動(dòng)。這意味著如果我們現(xiàn)在提交快照, 我們記錄的將是上次跑 git add 的時(shí)候的文件版本,而不是現(xiàn)在在磁盤中的這個(gè)。 Git 并不認(rèn)為磁盤中的文件與你想快照的文件必須是一致的 —— (如果你需要它們一致,)得用 git add 命令告訴它。
$ git status 查看你的文件在工作目錄與緩存的狀態(tài)
正如你在 git add 小節(jié)中所看到的,你可以執(zhí)行 git status 命令查看你的代碼在緩存與當(dāng)前工作目錄的狀態(tài)。我演示該命令的時(shí)候加了 -s 參數(shù),以獲得簡短的結(jié)果輸出。 若沒有這個(gè)標(biāo)記,命令 git status 將告訴你更多的提示與上下文欣喜。 以下便是同樣狀態(tài)下,有跟沒有 -s 參數(shù)的輸出對比。簡短的輸出如下:
$ git status -s
AM README
A hello.rb
而同樣的狀態(tài),詳細(xì)的輸出看起來是這樣的:
$ git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
# (use "git rm --cached <file>..." to unstage)
#
# new file: README
# new file: hello.rb
#
# Changed but not updated:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: README
#
你很容易發(fā)現(xiàn)簡短的輸出看起來很緊湊。而詳細(xì)輸出則很有幫助,提示你可以用何種命令完成你接下來可能要做的事情。
Git 還會(huì)告訴你在你上次提交之后,有哪些文件被刪除、修改或者存入緩存了。
$ git status -s
M README
D hello.rb
你可以看到,在簡短輸出中,有兩欄。第一欄是緩存的,第二欄則是工作目錄的。 所以假設(shè)你臨時(shí)提交了 README 文件,然后又改了些,并且沒有執(zhí)行 git add,你會(huì)看到這個(gè):
$ git status -s
MM README
D hello.rb
一言以蔽之,執(zhí)行 git status 以查看在你上次提交之后有啥被修改或者臨時(shí)提交了, 從而決定自己是否需要提交一次快照,同時(shí)也能知道有什么改變被記錄進(jìn)去了。
$ git diff 顯示已寫入緩存與已修改但尚未寫入緩存的改動(dòng)的區(qū)別
git diff 有兩個(gè)主要的應(yīng)用場景。我們將在此介紹其一, 在 檢閱與對照 一章中,我們將介紹其二。 我們這里介紹的方式是用此命令描述已臨時(shí)提交的或者已修改但尚未提交的改動(dòng)。
git diff #尚未緩存的改動(dòng)
如果沒有其他參數(shù),git diff 會(huì)以規(guī)范化的 diff 格式(一個(gè)補(bǔ)?。╋@示自從你上次提交快照之后尚未緩存的所有更改。
$ vim hello.rb
$ git status -s
M hello.rb
$ git diff
diff --git a/hello.rb b/hello.rb
index d62ac43..8d15d50 100644
--- a/hello.rb
+++ b/hello.rb
@@ -1,7 +1,7 @@
class HelloWorld
def self.hello
- puts "hello world"
+ puts "hola mundo"
end
end
所以,git status顯示你上次提交更新至后所更改或者寫入緩存的改動(dòng), 而 git diff 一行一行地顯示這些改動(dòng)具體是啥。 通常執(zhí)行完 git status 之后接著跑一下 git diff 是個(gè)好習(xí)慣。
$ git diff --cached #查看已緩存的改動(dòng)
git diff --cached 命令會(huì)告訴你有哪些內(nèi)容已經(jīng)寫入緩存了。 也就是說,此命令顯示的是接下來要寫入快照的內(nèi)容。所以,如果你將上述示例中的 hello.rb 寫入緩存,因?yàn)?git diff 顯示的是尚未緩存的改動(dòng),所以在此執(zhí)行它不會(huì)顯示任何信息。
$ git status -s
M hello.rb
$ git add hello.rb
$ git status -s
M hello.rb
$ git diff
$
如果你想看看已緩存的改動(dòng),你需要執(zhí)行的是 git diff --cached。
$ git status -s
M hello.rb
$ git diff
$
$ git diff --cached
diff --git a/hello.rb b/hello.rb
index d62ac43..8d15d50 100644
--- a/hello.rb
+++ b/hello.rb
@@ -1,7 +1,7 @@
class HelloWorld
def self.hello
- puts "hello world"
+ puts "hola mundo"
end
end
$ git diff HEAD 查看已緩存的與未緩存的所有改動(dòng)
如果你想一并查看已緩存的與未緩存的改動(dòng),可以執(zhí)行 git diff HEAD —— 也就是說你要看到的是工作目錄與上一次提交的更新的區(qū)別,無視緩存。 假設(shè)我們又改了些 ruby.rb 的內(nèi)容,那緩存的與未緩存的改動(dòng)我們就都有了。 以上三個(gè) diff 命令的結(jié)果如下:
$ vim hello.rb
$ git diff
diff --git a/hello.rb b/hello.rb
index 4f40006..2ae9ba4 100644
--- a/hello.rb
+++ b/hello.rb
@@ -1,7 +1,7 @@
class HelloWorld
+ # says hello
def self.hello
puts "hola mundo"
end
end
$ git diff --cached
diff --git a/hello.rb b/hello.rb
index 2aabb6e..4f40006 100644
--- a/hello.rb
+++ b/hello.rb
@@ -1,7 +1,7 @@
class HelloWorld
def self.hello
- puts "hello world"
+ puts "hola mundo"
end
end
$ git diff HEAD
diff --git a/hello.rb b/hello.rb
index 2aabb6e..2ae9ba4 100644
--- a/hello.rb
+++ b/hello.rb
@@ -1,7 +1,8 @@
class HelloWorld
+ # says hello
def self.hello
- puts "hello world"
+ puts "hola mundo"
end
end
$ git diff --stat 顯示摘要而非整個(gè) diff
如果我們不想要看整個(gè) diff 輸出,但是又想比 git status 詳細(xì)點(diǎn), 就可以用 --stat 選項(xiàng)。該選項(xiàng)使它顯示摘要而非全文。上文示例在使用 --stat 選項(xiàng)時(shí),輸出如下:
$ git status -s
MM hello.rb
$ git diff --stat
hello.rb | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
$ git diff --cached --stat
hello.rb | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
$ git diff HEAD --stat
hello.rb | 3 ++-
1 files changed, 2 insertions(+), 1 deletions(-)
你還可以在上述命令后面制定一個(gè)目錄,從而只查看特定文件或子目錄的 diff 輸出。
$ git commit 記錄緩存內(nèi)容的快照
現(xiàn)在你使用git add 命令將想要快照的內(nèi)容寫入了緩存, 執(zhí)行git commit就將它實(shí)際存儲(chǔ)快照了。 Git 為你的每一個(gè)提交都記錄你的名字與電子郵箱地址,所以第一步是告訴 Git 這些都是啥。
$ git config --global user.name 'Your Name'
$ git config --global user.email you@somedomain.com
讓我們寫入緩存,并提交對 hello.rb 的所有改動(dòng)。在首個(gè)例子中,我們使用 -m 選項(xiàng)以在命令行中提供提交注釋。
$ git add hello.rb
$ git status -s
M hello.rb
$ git commit -m 'my hola mundo changes'
[master 68aa034] my hola mundo changes
1 files changed, 2 insertions(+), 1 deletions(-)
現(xiàn)在我們已經(jīng)記錄了快照。如果我們再執(zhí)行 git status,會(huì)看到我們有一個(gè)“干凈的工作目錄”。 這意味著我們在最近一次提交之后,沒有做任何改動(dòng) —— 在我們的項(xiàng)目中沒有未快照的工作。
$ git status
# On branch master
nothing to commit (working directory clean)
如果你漏掉了 -m 選項(xiàng),Git 會(huì)嘗試為你打開一個(gè)編輯器以填寫提交信息。 如果 Git 在你對它的配置中找不到相關(guān)信息,默認(rèn)會(huì)打開 vim。屏幕會(huì)像這樣:
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: hello.rb
#
~
~
".git/COMMIT_EDITMSG" 9L, 257C
提交注解是很重要的。因?yàn)?Git 很大一部分能耐就是它在組織本地提交和與他人分享的彈性, 它很給力地能夠讓你為邏輯獨(dú)立的改變寫三到四條提交注解,以便你的工作被同仁審閱。因?yàn)樘峤慌c推送改動(dòng)是有區(qū)別的, 請務(wù)必花時(shí)間將各個(gè)邏輯獨(dú)立的改動(dòng)放到另外一個(gè)提交,并附上一份良好的提交注解, 以使與你合作的人能夠方便地了解你所做的,以及你為何要這么做。
$ git commit -a 自動(dòng)將在提交前將已記錄、修改的文件放入緩存區(qū)
如果你覺得 git add 提交緩存的流程太過繁瑣,Git 也允許你用 -a 選項(xiàng)跳過這一步。 基本上這句話的意思就是,為任何已有記錄的文件執(zhí)行 git add —— 也就是說,任何在你最近的提交中已經(jīng)存在,并且之后被修改的文件。 這讓你能夠用更 Subversion 方式的流程,修改些文件,然后想要快照所有所做的改動(dòng)的時(shí)候執(zhí)行 git commit -a。 不過你仍然需要執(zhí)行 git add 來添加新文件,就像 Subversion 一樣。
$ vim hello.rb
$ git status -s
M hello.rb
$ git commit -m 'changes to hello file'
# On branch master
# Changed but not updated:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: hello.rb
#
no changes added to commit (use "git add" and/or "git commit -a")
$ git commit -am 'changes to hello file'
[master 78b2670] changes to hello file
1 files changed, 2 insertions(+), 1 deletions(-)
注意,如果你不緩存改動(dòng),直接執(zhí)行 git commit,Git 會(huì)直接給出 git status 命令的輸出,提醒你啥也沒緩存。我已將該消息中的重要部分高亮,它說沒有添加需要提交的緩存。 如果你使用 -a,它會(huì)緩存并提交每個(gè)改動(dòng)(不含新文件)。
現(xiàn)在你就完成了整個(gè)快照的流程 ——改些文件,然后用 git add 將要提交的改動(dòng)提交到緩存, 用 git status 和 git diff 看看你都改了啥,最后 git commit 永久地保存快照。
$ git reset HEAD 取消緩存已緩存的內(nèi)容
git reset 可能是人類寫的最費(fèi)解的命令了。 我用 Git 有些年頭了,甚至還寫了本書,但有的時(shí)候還是會(huì)搞不清它會(huì)做什么。 所以,我只說三個(gè)明確的,通常有用的調(diào)用。請你跟我一樣盡管用它 —— 因?yàn)樗梢院苡杏谩?/p>
在此例中,我們可以用它來將不小心緩存的東東取消緩存。假設(shè)你修改了兩個(gè)文件,想要將它們記錄到兩個(gè)不同的提交中去。 你應(yīng)該緩存并提交一個(gè),再緩存并提交另外一個(gè)。如果你不小心兩個(gè)都緩存了,那要如何才能取消緩存呢? 你可以用 git reset HEAD -- file。 技術(shù)上說,在這里你不需要使用 -- —— 它用來告訴 Git 這時(shí)你已經(jīng)不再列選項(xiàng),剩下的是文件路徑了。 不過養(yǎng)成使用它分隔選項(xiàng)與路徑的習(xí)慣很重要,即使在你可能并不需要的時(shí)候。
好,讓我們看看取消緩存是什么樣子的。這里我們有兩個(gè)最近提交之后又有所改動(dòng)的文件。我們將兩個(gè)都緩存,并取消緩存其中一個(gè)。
$ git status -s
M README
M hello.rb
$ git add .
$ git status -s
M README
M hello.rb
$ git reset HEAD -- hello.rb
Unstaged changes after reset:
M hello.rb
$ git status -s
M README
M hello.rb
現(xiàn)在你執(zhí)行 git commit 將只記錄 README 文件的改動(dòng),并不含現(xiàn)在并不在緩存中的 hello.rb。
如果你好奇,它實(shí)際的操作是將該文件在“索引”中的校驗(yàn)和重置為最近一次提交中的值。 git add 會(huì)計(jì)算一個(gè)文件的校驗(yàn)和,將它添加到“索引”中, 而 git reset HEAD 將它改寫回原先的,從而取消緩存操作。
如果你想直接執(zhí)行 git unstage,你可以在 Git 中配置個(gè)別名。 執(zhí)行 git config --global alias.unstage "reset HEAD" 即可。 一旦執(zhí)行完它,你就可以直接用 git unstage [file] 作為代替了。
如果你忘了取消緩存的命令,Git 的常規(guī) git status 輸出的提示會(huì)很有幫助。 例如,在你有已緩存的文件時(shí),如果你不帶 -s 執(zhí)行 git status,它將告訴你怎樣取消緩存:
簡而言之,執(zhí)行
git reset HEAD以取消之前git add添加,但不希望包含在下一提交快照中的緩存。
$ git rm 將文件從緩存區(qū)移除
git rm 會(huì)將條目從緩存區(qū)中移除。這與 git reset HEAD 將條目取消緩存是有區(qū)別的。 “取消緩存”的意思就是將緩存區(qū)恢復(fù)為我們做出修改之前的樣子。 在另一方面,git rm 則將該文件徹底從緩存區(qū)踢出,因此它不再下一個(gè)提交快照之內(nèi),進(jìn)而有效地刪除它。
默認(rèn)情況下,git rm file 會(huì)將文件從緩存區(qū)和你的硬盤中(工作目錄)刪除。 如果要在工作目錄中留著該文件,可以使用 git rm --cached
git mv git rm --cached orig; mv orig new; git add new
不像絕大多數(shù)其他版本控制系統(tǒng),Git 并不記錄記錄文件重命名。它反而只記錄快照,并對比快照以找到有啥文件可能被重命名了。 如果一個(gè)文件從更新中刪除了,而在下次快照中新添加的另一個(gè)文件的內(nèi)容與它很相似,Git 就知道這極有可能是個(gè)重命名。 因此,雖然有 git mv 命令,但它有點(diǎn)多余 —— 它做得所有事情就是 git rm --cached, 重命名磁盤上的文件,然后再執(zhí)行 git add 把新文件添加到緩存區(qū)。 你并不需要用它,不過如果覺得這樣容易些,盡管用吧。
我自己并不使用此命令的普通形式 —— 刪除文件。通常直接從硬盤刪除文件,然后執(zhí)行 git commit -a 會(huì)簡單些。 它會(huì)自動(dòng)將刪除的文件從索引中移除。
簡而言之, 執(zhí)行
git rm來刪除 Git 追蹤的文件。它還會(huì)刪除你的工作目錄中的相應(yīng)文件。
分支與合并
分支是我最喜歡的 Git 特性之一。如果你用過其他版本控制系統(tǒng),把你所知的分支給忘記,倒可能更有幫助些 —— 事實(shí)上,以我們使用分支的方式,把 Git 的分支看作 上下文 反而更合適。 當(dāng)你檢出分支時(shí),你可以在兩三個(gè)不同的分支之間來回切換。
簡而言之,你可以執(zhí)行
git branch (branchname)來創(chuàng)建分支, 使用git checkout (branchname)命令切換到該分支,在該分支的上下文環(huán)境中, 提交快照等,之后可以很容易地來回切換。當(dāng)你切換分支的時(shí)候,Git 會(huì)用該分支的最后提交的快照替換你的工作目錄的內(nèi)容, 所以多個(gè)分支不需要多個(gè)目錄。使用 git merge 來合并分支。你可以多次合并到統(tǒng)一分支, 也可以選擇在合并之后直接刪除被并入的分支。
$ git branch 列出、創(chuàng)建與管理工作上下文
git branch 命令是 Git 中的通用分支管理工具,可以通過它完成多項(xiàng)任務(wù)。 我們先說你會(huì)用到的最多的命令 —— 列出分支、創(chuàng)建分支和刪除分支。 我們還會(huì)介紹用來切換分支的 git checkout 命令。
$ git branch 列出可用的分支
沒有參數(shù)時(shí),git branch 會(huì)列出你在本地的分支。你所在的分支的行首會(huì)有個(gè)星號作標(biāo)記。 如果你開啟了彩色模式,當(dāng)前分支會(huì)用綠色顯示。
$ git branch
* master
此例的意思就是,我們有一個(gè)叫做“master”的分支,并且該分支是當(dāng)前分支。 當(dāng)你執(zhí)行 git init 的時(shí)候,缺省情況下 Git 就會(huì)為你創(chuàng)建“master”分支。 但是這名字一點(diǎn)特殊意味都沒有 —— 事實(shí)上你并不非得要一個(gè)叫做“master”的分支。 不過由于它是缺省分支名的緣故,絕大部分項(xiàng)目都有這個(gè)分支。
$ git branch (branchname) 創(chuàng)建新分支
我們動(dòng)手創(chuàng)建一個(gè)分支,并切換過去。執(zhí)行 git branch (branchname) 即可。
$ git branch testing
$ git branch
* master
testing
現(xiàn)在我們可以看到,有了一個(gè)新分支。當(dāng)你以此方式在上次提交更新之后創(chuàng)建了新分支,如果后來又有更新提交, 然后又切換到了“testing”分支,Git 將還原你的工作目錄到你創(chuàng)建分支時(shí)候的樣子 —— 你可以把它看作一個(gè)記錄你當(dāng)前進(jìn)度的書簽。讓我們實(shí)際運(yùn)用看看 —— 我們用 git checkout (branch) 切換到我們要修改的分支。
$ ls
README hello.rb
$ echo 'test content' > test.txt
$ echo 'more content' > more.txt
$ git add *.txt
$ git commit -m 'added two files'
[master 8bd6d8b] added two files
2 files changed, 2 insertions(+), 0 deletions(-)
create mode 100644 more.txt
create mode 100644 test.txt
$ ls
README hello.rb more.txt test.txt
$ git checkout testing
Switched to branch 'testing'
$ ls
README hello.rb
當(dāng)我們切換到“測試”分支的時(shí)候,我們添加的新文件被移除了。切換回“master”分支的時(shí)候,它們又重新出現(xiàn)了。
$ ls
README hello.rb
$ git checkout master
Switched to branch 'master'
$ ls
README hello.rb more.txt test.txt
$ git checkout 切換到新的分支上下文
$ git checkout -b (branchname) 創(chuàng)建新分支,并立即切換到它
通常情況下,你會(huì)更希望立即切換到新分支,從而在該分支中操作,然后當(dāng)此分支的開發(fā)日趨穩(wěn)定時(shí), 將它合并到穩(wěn)定版本的分支(例如“master”)中去。 執(zhí)行git branch newbranch; git checkout newbranch 也很簡單, 不過 Git 還為你提供了快捷方式:git checkout -b newbranch。
$ git branch
* master
$ ls
README hello.rb more.txt test.txt
$ git checkout -b removals
Switched to a new branch 'removals'
$ git rm more.txt
rm 'more.txt'
$ git rm test.txt
rm 'test.txt'
$ ls
README hello.rb
$ git commit -am 'removed useless files'
[removals 8f7c949] removed useless files
2 files changed, 0 insertions(+), 2 deletions(-)
delete mode 100644 more.txt
delete mode 100644 test.txt
$ git checkout master
Switched to branch 'master'
$ ls
README hello.rb more.txt test.txt
如你所見,我們創(chuàng)建了一個(gè)分支,在該分支的上下文中移除了一些文件,然后切換回我們的主分支,那些文件又回來了。 使用分支將工作切分開來,從而讓我們能夠在不同上下文中做事,并來回切換。
創(chuàng)建新分支,在其中完成一部分工作,完成之后將它合并到主分支并刪除。你會(huì)覺得這很方便,因?yàn)檫@么做很快很容易。 如此,當(dāng)你覺得這部分工作并不靠譜,舍棄它很容易。并且,如果你必須回到穩(wěn)定分支做些事情, 也可以很方便地這個(gè)獨(dú)立分支的工作先丟在一邊,完成要事之后再切換回來。
git branch -d (branchname) 刪除分支
假設(shè)我們要?jiǎng)h除一個(gè)分支(比如上例中的“testing”分支,該分支沒啥特殊的內(nèi)容了), 可以執(zhí)行 git branch -d (branch) 把它刪掉。
$ git branch
* master
testing
$ git branch -d testing
Deleted branch testing (was 78b2670).
$ git branch
* master
簡而言之 使用
git branch列出現(xiàn)有的分支、創(chuàng)建新分支以及刪除不必要或者已合并的分支。
$ git merge 將分支合并到你的當(dāng)前分支
一旦某分支有了獨(dú)立內(nèi)容,你終究會(huì)希望將它合并回到你的主分支。 你可以使用 git merge 命令將任何分支合并到當(dāng)前分支中去。 我們那上例中的“removals”分支為例。假設(shè)我們創(chuàng)建了一個(gè)分支,移除了一些文件,并將它提交到該分支, 其實(shí)該分支是與我們的主分支(也就是“master”)獨(dú)立開來的。 要想將這些移除操作包含在主分支中,你可以將“removals”分支合并回去。
$ git branch
* master
removals
$ ls
README hello.rb more.txt test.txt
$ git merge removals
Updating 8bd6d8b..8f7c949
Fast-forward
more.txt | 1 -
test.txt | 1 -
2 files changed, 0 insertions(+), 2 deletions(-)
delete mode 100644 more.txt
delete mode 100644 test.txt
$ ls
README hello.rb
更多復(fù)雜合并
當(dāng)然,合并并不僅僅是簡單的文件添加、移除的操作,Git 也會(huì)合并修改 —— 事實(shí)上,它很會(huì)合并修改。 舉例,我們看看在某分支中編輯某個(gè)文件,然后在另一個(gè)分支中把它的名字改掉再做些修改, 最后將這倆分支合并起來。你覺得會(huì)變成一坨 shi?我們試試看。
$ git branch
* master
$ cat hello.rb
class HelloWorld
def self.hello
puts "Hello World"
end
end
HelloWorld.hello
首先,我們創(chuàng)建一個(gè)叫做“change_class”的分支,切換過去,從而將重命名類等操作獨(dú)立出來。我們將類名從 “HelloWorld” 改為 “HiWorld”。
$ git checkout -b change_class
M hello.rb
Switched to a new branch 'change_class'
$ vim hello.rb
$ head -1 hello.rb
class HiWorld
$ git commit -am 'changed the class name'
[change_class 3467b0a] changed the class name
1 files changed, 2 insertions(+), 4 deletions(-)
然后,將重命名類操作提交到 “change_class” 分支中。 現(xiàn)在,假如切換回 “master” 分支我們可以看到類名恢復(fù)到了我們切換到 “change_class” 分支之前的樣子。 現(xiàn)在,再做些修改(即代碼中的輸出),同時(shí)將文件名從 hello.rb 改為 ruby.rb。
$ git checkout master
Switched to branch 'master'
$ git mv hello.rb ruby.rb
$ vim ruby.rb
$ git diff
diff --git a/ruby.rb b/ruby.rb
index 2aabb6e..bf64b17 100644
--- a/ruby.rb
+++ b/ruby.rb
@@ -1,7 +1,7 @@
class HelloWorld
def self.hello
- puts "Hello World"
+ puts "Hello World from Ruby"
end
end
$ git commit -am 'added from ruby'
[master b7ae93b] added from ruby
1 files changed, 1 insertions(+), 1 deletions(-)
rename hello.rb => ruby.rb (65%)
現(xiàn)在這些改變已經(jīng)記錄到我的 “master” 分支了。請注意,這里類名還是 “HelloWorld”,而不是 “HiWorld”。 然后我想將類名的改變合并過來,我把 “change_class” 分支合并過來就行了。 但是,我已經(jīng)將文件名都改掉了,Git 知道該怎么辦么?
$ git branch
change_class
* master
$ git merge change_class
Renaming hello.rb => ruby.rb
Auto-merging ruby.rb
Merge made by recursive.
ruby.rb | 6 ++----
1 files changed, 2 insertions(+), 4 deletions(-)
$ cat ruby.rb
class HiWorld
def self.hello
puts "Hello World from Ruby"
end
end
HiWorld.hello
合并沖突
那么,Git 合并很有魔力,我們再也不用處理合并沖突了,對嗎?不太確切。 不同分支中修改了相同區(qū)塊的代碼,電腦自己猜不透神馬的情況下,沖突就擺在我們面前了。 我們看看兩個(gè)分支中改了同一行代碼的例子。
$ git branch
* master
$ git checkout -b fix_readme
Switched to a new branch 'fix_readme'
$ vim README
$ git commit -am 'fixed readme title'
[fix_readme 3ac015d] fixed readme title
1 files changed, 1 insertions(+), 1 deletions(-)
我們在某分支中修改了 README 文件中的一行,并提交了。我們再在 “master” 分支中對同個(gè)文件的同一行內(nèi)容作不同的修改。
$ git checkout master
Switched to branch 'master'
$ vim README
$ git commit -am 'fixed readme title differently'
[master 3cbb6aa] fixed readme title differently
1 files changed, 1 insertions(+), 1 deletions(-)
有意思的來了 —— 我們將前一個(gè)分支合并到 “master” 分支,一個(gè)合并沖突就出現(xiàn)了。
$ git merge fix_readme
Auto-merging README
CONFLICT (content): Merge conflict in README
Automatic merge failed; fix conflicts and then commit the result.
$ cat README
<<<<<<< HEAD
Many Hello World Examples
=======
Hello World Lang Examples
>>>>>>> fix_readme
This project has examples of hello world in
nearly every programming language.
你可以看到,Git 在產(chǎn)生合并沖突的地方插入了標(biāo)準(zhǔn)的與 Subversion 很像的合并沖突標(biāo)記。 輪到我們?nèi)ソ鉀Q這些沖突了。在這里我們就手動(dòng)把它解決。如果你要 Git 打開一個(gè)圖形化的合并工具, 可以看看 git 合并工具 (比如 kdiff3、emerge、p4merge 等)。
$ vim README here I'm fixing the conflict
$ git diff
diff --cc README
index 9103e27,69cad1a..0000000
--- a/README
+++ b/README
@@@ -1,4 -1,4 +1,4 @@@
- Many Hello World Examples
-Hello World Lang Examples
++Many Hello World Lang Examples
This project has examples of hello world in
在 Git 中,處理合并沖突的時(shí)候有個(gè)很酷的提示。 如果你執(zhí)行 git diff,就像我演示的這樣,它會(huì)告訴你沖突的兩方,和你是如何解決的。 現(xiàn)在是時(shí)候把它標(biāo)記為已解決了。在 Git 中,我們可以用 git add —— 要告訴 Git 文件沖突已經(jīng)解決,你必須把它寫入緩存區(qū)。
$ git status -s
UU README
$ git add README
$ git status -s
M README
$ git commit
[master 8d585ea] Merge branch 'fix_readme'
簡而言之 使用
git merge將另一個(gè)分支并入當(dāng)前的分支中去。 Git 會(huì)自動(dòng)以最佳方式將兩個(gè)不同快照中獨(dú)特的工作合并到一個(gè)新快照中去。
$ git log 顯示一個(gè)分支中提交的更改記錄
到目前為止,我們已經(jīng)提交快照到項(xiàng)目中,在不同的各自分離的上下文中切換, 但假如我們忘了自己是如何到目前這一步的那該怎么辦?或者假如我們想知道此分支與彼分支到底有啥區(qū)別? Git 提供了一個(gè)告訴你使你達(dá)成當(dāng)前快照的所有提交消息的工具,叫做 git log。
要理解日志(log)命令,你需要了解當(dāng)執(zhí)行 git commit 以存儲(chǔ)一個(gè)快照的時(shí)候,都有啥信息被保存了。 除了文件詳單、提交消息和提交者的信息,Git 還保存了你的此次提交所基于的快照。 也就是,假如你克隆了一個(gè)項(xiàng)目,你是在什么快照的基礎(chǔ)上做的修改而得到新保存的快照的? 這有益于為項(xiàng)目進(jìn)程提供上下文,使 Git 能夠弄明白誰做了什么改動(dòng)。 如果 Git 有你的快照所基于的快照的話,它就能自動(dòng)判斷你都改變了什么。而新提交所基于的提交,被稱作新提交的“父親”。
某分支的按時(shí)間排序的“父親”列表,當(dāng)你在該分支時(shí),可以執(zhí)行 git log 以查看。 例如,如果我們在本章中操作的 Hello World 項(xiàng)目中執(zhí)行 git log,我們可以看到已提交的消息。
$ git log
commit 8d585ea6faf99facd39b55d6f6a3b3f481ad0d3d
Merge: 3cbb6aa 3ac015d
Author: Scott Chacon <schacon@gmail.com>
Date: Fri Jun 4 12:59:47 2010 +0200
Merge branch 'fix_readme'
Conflicts:
README
commit 3cbb6aae5c0cbd711c098e113ae436801371c95e
Author: Scott Chacon <schacon@gmail.com>
Date: Fri Jun 4 12:58:53 2010 +0200
fixed readme title differently
commit 3ac015da8ade34d4c7ebeffa2053fcac33fb495b
Author: Scott Chacon <schacon@gmail.com>
Date: Fri Jun 4 12:58:36 2010 +0200
fixed readme title
commit 558151a95567ba4181bab5746bc8f34bd87143d6
Merge: b7ae93b 3467b0a
Author: Scott Chacon <schacon@gmail.com>
Date: Fri Jun 4 12:37:05 2010 +0200
Merge branch 'change_class'
...
我們可以用 --oneline 選項(xiàng)來查看歷史記錄的緊湊簡潔的版本。
$ git log --oneline
8d585ea Merge branch 'fix_readme'
3cbb6aa fixed readme title differently
3ac015d fixed readme title
558151a Merge branch 'change_class'
b7ae93b added from ruby
3467b0a changed the class name
17f4acf first commit
這告訴我們的是,此項(xiàng)目的開發(fā)歷史。如果提交消息描述性很好,這就能為我們提供關(guān)于有啥改動(dòng)被應(yīng)用、或者影響了當(dāng)前快照的狀態(tài)、以及這快照里頭都有啥。
我們還可以用它的十分有幫助的 --graph 選項(xiàng),查看歷史中什么時(shí)候出現(xiàn)了分支、合并。以下為相同的命令,開啟了拓?fù)鋱D選項(xiàng):
$ git log --oneline --graph
* 8d585ea Merge branch 'fix_readme'
|\
| * 3ac015d fixed readme title
* | 3cbb6aa fixed readme title differently
|/
* 558151a Merge branch 'change_class'
|\
| * 3467b0a changed the class name
* | b7ae93b added from ruby
|/
* 17f4acf first commit
現(xiàn)在我們可以更清楚明了地看到何時(shí)工作分叉、又何時(shí)歸并。 這對查看發(fā)生了什么、應(yīng)用了什么改變很有幫助,并且極大地幫助你管理你的分支。 讓我們創(chuàng)建一個(gè)分支,在里頭做些事情,然后切回到主分支,也做點(diǎn)事情,然后看看 log 命令是如何幫助我們理清這倆分支上都發(fā)生了啥的。
首先我們創(chuàng)建一個(gè)分支,來添加 Erlang 編程語言的 Hello World 示例 —— 我們想要在一個(gè)分支里頭做這個(gè),以避免讓可能還不能工作的代碼弄亂我們的穩(wěn)定分支。 這樣就可以切來切去,片葉不沾身。
$ git checkout -b erlang
Switched to a new branch 'erlang'
$ vim erlang_hw.erl
$ git add erlang_hw.erl
$ git commit -m 'added erlang'
[erlang ab5ab4c] added erlang
1 files changed, 5 insertions(+), 0 deletions(-)
create mode 100644 erlang_hw.erl
由于我們玩函數(shù)式編程很開心,以至于沉迷其中,又在“erlang”分支中添加了一個(gè) Haskell 的示例程序。
$ vim haskell.hs
$ git add haskell.hs
$ git commit -m 'added haskell'
[erlang 1834130] added haskell
1 files changed, 4 insertions(+), 0 deletions(-)
create mode 100644 haskell.hs
最后,我們決定還是把 Ruby 程序的類名改回原先的樣子。與其創(chuàng)建另一個(gè)分支,我們可以返回主分支,改變它,然后直接提交。
$ git checkout master
Switched to branch 'master'
$ ls
README ruby.rb
$ vim ruby.rb
$ git commit -am 'reverted to old class name'
[master 594f90b] reverted to old class name
1 files changed, 2 insertions(+), 2 deletions(-)
現(xiàn)在假設(shè)我們有段時(shí)間不做這個(gè)項(xiàng)目了,我們做別的去了。 當(dāng)我們回來的時(shí)候,我們想知道“erlang”分支都是啥,而主分支的進(jìn)度又是怎樣。 僅僅看分支的名字,我們是無從知道自己還在里面有 Haskell 的改動(dòng)的,但是用 git log 我們就可以。 如果你在命令行中提供一個(gè)分支名字,它就會(huì)顯示該分支歷史中“可及”的提交,即從該分支創(chuàng)立起可追溯的影響了最終的快照的提交。
$ git log --oneline erlang
1834130 added haskell
ab5ab4c added erlang
8d585ea Merge branch 'fix_readme'
3cbb6aa fixed readme title differently
3ac015d fixed readme title
558151a Merge branch 'change_class'
b7ae93b added from ruby
3467b0a changed the class name
17f4acf first commit
如此,我們很容易就看到分支里頭還包括了 Haskell 代碼(高亮顯示了)。 更酷的是,我們很容易地告訴 Git,我們只對某個(gè)分支中可及的提交感興趣。換句話說,某分支中與其他分支相比唯一的提交。
在此例中,如果我們想要合并“erlang”分支,我們需要看當(dāng)合并的時(shí)候,都有啥提交會(huì)作用到我們的快照上去。 我們告訴 Git 的方式是,在不想要看到的分支前放一個(gè) ^。 例如,如果我們想要看“erlang”分支中但不在主分支中的提交,我們可以用 erlang ^master,或者反之。
$ git log --oneline erlang ^master
1834130 added haskell
ab5ab4c added erlang
$ git log --oneline master ^erlang
594f90b reverted to old class name
簡而言之 使用
git log列出促成當(dāng)前分支目前的快照的提交歷史記錄。這使你能夠看到項(xiàng)目是如何到達(dá)現(xiàn)在的狀況的。
$ git tag 給歷史記錄中的某個(gè)重要的一點(diǎn)打上標(biāo)簽
如果你達(dá)到一個(gè)重要的階段,并希望永遠(yuǎn)記住那個(gè)特別的提交快照,你可以使用 git tag 給它打上標(biāo)簽。 該 tag 命令基本上會(huì)給該特殊提交打上永久的書簽,從而使你在將來能夠用它與其他提交比較。 通常,你會(huì)在切取一個(gè)發(fā)布版本或者交付一些東西的時(shí)候打個(gè)標(biāo)簽。
比如說,我們想為我們的 Hello World 項(xiàng)目發(fā)布一個(gè)“1.0”版本。 我們可以用 git tag -a v1.0 命令給最新一次提交打上(HEAD)“v1.0”的標(biāo)簽。 -a 選項(xiàng)意為“創(chuàng)建一個(gè)帶注解的標(biāo)簽”,從而使你為標(biāo)簽添加注解。絕大部分時(shí)候都會(huì)這么做的。 不用 -a 選項(xiàng)也可以執(zhí)行的,但它不會(huì)記錄這標(biāo)簽是啥時(shí)候打的,誰打的,也不會(huì)讓你添加個(gè)標(biāo)簽的注解。 我推薦一直創(chuàng)建帶注解的標(biāo)簽。
$ git tag -a v1.0
當(dāng)你執(zhí)行 git tag -a 命令時(shí),Git 會(huì)打開你的編輯器,讓你寫一句標(biāo)簽注解,就像你給提交寫注解一樣。
現(xiàn)在,注意當(dāng)我們執(zhí)行 git log --decorate 時(shí),我們可以看到我們的標(biāo)簽了:
$ git log --oneline --decorate --graph
* 594f90b (HEAD, tag: v1.0, master) reverted to old class name
* 8d585ea Merge branch 'fix_readme'
|\
| * 3ac015d (fix_readme) fixed readme title
* | 3cbb6aa fixed readme title differently
|/
* 558151a Merge branch 'change_class'
|\
| * 3467b0a changed the class name
* | b7ae93b added from ruby
|/
* 17f4acf first commit
如果我們有新提交,該標(biāo)簽依然會(huì)待在該提交的邊上,所以我們已經(jīng)給那個(gè)特定快照永久打上標(biāo)簽,并且能夠?qū)⑺c未來的快照做比較。
不過我們并不需要給當(dāng)前提交打標(biāo)簽。如果我們忘了給某個(gè)提交打標(biāo)簽,又將它發(fā)布了,我們可以給它追加標(biāo)簽。 在相同的命令末尾加上提交的 SHA,執(zhí)行,就可以了。 例如,假設(shè)我們發(fā)布了提交 558151a(幾個(gè)提交之前的事情了),但是那時(shí)候忘了給它打標(biāo)簽。 我們現(xiàn)在也可以:
$ git tag -a v0.9 558151a
$ git log --oneline --decorate --graph
* 594f90b (HEAD, tag: v1.0, master) reverted to old class name
* 8d585ea Merge branch 'fix_readme'
|\
| * 3ac015d (fix_readme) fixed readme title
* | 3cbb6aa fixed readme title differently
|/
* 558151a (tag: v0.9) Merge branch 'change_class'
|\
| * 3467b0a changed the class name
* | b7ae93b added from ruby
|/
* 17f4acf first commit
分享與更新項(xiàng)目
Git 并不像 Subversion 那樣有個(gè)中心服務(wù)器。 目前為止所有的命令都是本地執(zhí)行的,更新的知識(shí)本地的數(shù)據(jù)庫。 要通過 Git 與其他開發(fā)者合作,你需要將數(shù)據(jù)放到一臺(tái)其他開發(fā)者能夠連接的服務(wù)器上。 Git 實(shí)現(xiàn)此流程的方式是將你的數(shù)據(jù)與另一個(gè)倉庫同步。在服務(wù)器與客戶端之間并沒有實(shí)質(zhì)的區(qū)別 —— Git 倉庫就是 Git 倉庫,你可以很容易地在兩者之間同步。
一旦你有了個(gè) Git 倉庫,不管它是在你自己的服務(wù)器上,或者是由 GitHub 之類的地方提供, 你都可以告訴 Git 推送你擁有的遠(yuǎn)端倉庫還沒有的數(shù)據(jù),或者叫 Git 從別的倉庫把差別取過來。
聯(lián)網(wǎng)的時(shí)候你可以隨時(shí)做這個(gè),它并不需要對應(yīng)一個(gè) commit 或者別的什么。 一般你會(huì)本地提交幾次,然后從你的項(xiàng)目克隆自的線上的共享倉庫提取數(shù)據(jù)以保持最新,將新完成的合并到你完成的工作中去,然后推送你的改動(dòng)會(huì)服務(wù)器。
簡而言之 使用
git fetch更新你的項(xiàng)目,使用git push分享你的改動(dòng)。 你可以用git remote管理你的遠(yuǎn)程倉庫。
$ git remote 羅列、添加和刪除遠(yuǎn)端倉庫別名
不像中心化的版本控制系統(tǒng)(客戶端與服務(wù)端很不一樣),Git 倉庫基本上都是一致的,并且并可以同步他們。 這使得擁有多個(gè)遠(yuǎn)端倉庫變得容易 —— 你可以擁有一些只讀的倉庫,另外的一些也可寫的倉庫。
當(dāng)你需要與遠(yuǎn)端倉庫同步的時(shí)候,不需要使用它詳細(xì)的鏈接。Git 儲(chǔ)存了你感興趣的遠(yuǎn)端倉庫的鏈接的別名或者昵稱。 你可以使用 git remote 命令管理這個(gè)遠(yuǎn)端倉庫列表。
$ git remote 列出遠(yuǎn)端別名
如果沒有任何參數(shù),Git 會(huì)列出它存儲(chǔ)的遠(yuǎn)端倉庫別名了事。默認(rèn)情況下,如果你的項(xiàng)目是克隆的(與本地創(chuàng)建一個(gè)新的相反), Git 會(huì)自動(dòng)將你的項(xiàng)目克隆自的倉庫添加到列表中,并取名“origin”。 如果你執(zhí)行時(shí)加上 -v 參數(shù),你還可以看到每個(gè)別名的實(shí)際鏈接地址。
$ git remote add 為你的項(xiàng)目添加一個(gè)新的遠(yuǎn)端倉庫
如果你希望分享一個(gè)本地創(chuàng)建的倉庫,或者你想要獲取別人的倉庫中的貢獻(xiàn) —— 如果你想要以任何方式與一個(gè)新倉庫溝通,最簡單的方式通常就是把它添加為一個(gè)遠(yuǎn)端倉庫。 執(zhí)行 git remote add [alias] [url] 就可以。 此命令將 [url]以 [alias] 的別名添加為本地的遠(yuǎn)端倉庫。
例如,假設(shè)我們想要與整個(gè)世界分享我們的 Hello World 程序。 我們可以在一臺(tái)服務(wù)器上創(chuàng)建一個(gè)新倉庫(我以 GitHub 為例子)。 它應(yīng)該會(huì)給你一個(gè)鏈接,在這里就是“git@github.com:schacon/hw.git”。 要把它添加到我們的項(xiàng)目以便我們推送以及獲取更新,我們可以這樣:
$ git remote
$ git remote add github git@github.com:schacon/hw.git
$ git remote -v
github git@github.com:schacon/hw.git (fetch)
github git@github.com:schacon/hw.git (push)
像分支的命名一樣,遠(yuǎn)端倉庫的別名是強(qiáng)制的 —— 就像“master”,沒有特別意義,但它廣為使用, 因?yàn)?code>git init 默認(rèn)用它;“origin”經(jīng)常被用作遠(yuǎn)端倉庫別名,就因?yàn)?git clone 默認(rèn)用它作為克隆自的鏈接的別名。此例中,我決定給我的遠(yuǎn)端倉庫取名“github”,但我叫它隨便什么都可以。
git remote rm 刪除現(xiàn)存的某個(gè)別名
Git addeth and Git taketh away. 如果你需要?jiǎng)h除一個(gè)遠(yuǎn)端 —— 不再需要它了、項(xiàng)目已經(jīng)沒了,等等 —— 你可以使用 git remote rm [alias]把它刪掉。
$ git remote -v
github git@github.com:schacon/hw.git (fetch)
github git@github.com:schacon/hw.git (push)
$ git remote add origin git://github.com/pjhyett/hw.git
$ git remote -v
github git@github.com:schacon/hw.git (fetch)
github git@github.com:schacon/hw.git (push)
origin git://github.com/pjhyett/hw.git (fetch)
origin git://github.com/pjhyett/hw.git (push)
$ git remote rm origin
$ git remote -v
github git@github.com:schacon/hw.git (fetch)
github git@github.com:schacon/hw.git (push)
簡而言之 你可以用 git remote 列出你的遠(yuǎn)端倉庫和那些倉庫的鏈接。 你可以使用 git remote add 添加新的遠(yuǎn)端倉庫,用 git remote rm 刪掉已存在的那些。
$ git fetch 從遠(yuǎn)端倉庫下載新分支與數(shù)據(jù)
$ git pull 從遠(yuǎn)端倉庫提取數(shù)據(jù)并嘗試合并到當(dāng)前分支
Git 有兩個(gè)命令用來從某一遠(yuǎn)端倉庫更新。 git fetch 會(huì)使你與另一倉庫同步,提取你本地所沒有的數(shù)據(jù),為你在同步時(shí)的該遠(yuǎn)端的每一分支提供書簽。 這些分支被叫做“遠(yuǎn)端分支”,除了 Git 不允許你檢出(切換到該分支)之外,跟本地分支沒區(qū)別 —— 你可以將它們合并到當(dāng)前分支,與其他分支作比較差異,查看那些分支的歷史日志,等等。同步之后你就可以在本地操作這些。
第二個(gè)會(huì)從遠(yuǎn)端服務(wù)器提取新數(shù)據(jù)的命令是 git pull。 基本上,該命令就是在 git fetch 之后緊接著 git merge 遠(yuǎn)端分支到你所在的任意分支。 我個(gè)人不太喜歡這命令 —— 我更喜歡 fetch 和 merge 分開來做。少點(diǎn)魔法,少點(diǎn)問題。 不過,如果你喜歡這主意,你可以看一下 git pull 的 官方文檔。
假設(shè)你配置好了一個(gè)遠(yuǎn)端,并且你想要提取更新,你可以首先執(zhí)行 git fetch [alias] 告訴 Git 去獲取它有你沒有的數(shù)據(jù),然后你可以執(zhí)行 git merge [alias]/[branch] 以將服務(wù)器上的任何更新(假設(shè)有人這時(shí)候推送到服務(wù)器了)合并到你的當(dāng)前分支。 那么,如果我是與兩三個(gè)其他人合作 Hello World 項(xiàng)目,并且想要將我最近連接之后的所有改動(dòng)拿過來,我可以這么做:
$ git fetch github
remote: Counting objects: 4006, done.
remote: Compressing objects: 100% (1322/1322), done.
remote: Total 2783 (delta 1526), reused 2587 (delta 1387)
Receiving objects: 100% (2783/2783), 1.23 MiB | 10 KiB/s, done.
Resolving deltas: 100% (1526/1526), completed with 387 local objects.
From github.com:schacon/hw
8e29b09..c7c5a10 master -> github/master
0709fdc..d4ccf73 c-langs -> github/c-langs
6684f82..ae06d2b java -> github/java
* [new branch] ada -> github/ada
* [new branch] lisp -> github/lisp
可以看到自從上一次與遠(yuǎn)端倉庫同步以后,又新贈(zèng)或更新了五個(gè)分支。 “ada”與“l(fā)isp”分支是新的,而“master”、“clang”與“java”分支則被更新了。 在此例中,我的團(tuán)隊(duì)在合并入主分支之前,將提議的更新推送到遠(yuǎn)端分支以審核。
你可以看到 Git 做的映射。遠(yuǎn)端倉庫的主分支成為了本地的一個(gè)叫做“github/master”的分支。 這樣我就可以執(zhí)行 git merge github/master將遠(yuǎn)端的主分支和并入我的本地主分支。 或者,我可以 git log github/master ^master 看看該分支上的新提交。 如果你的遠(yuǎn)端倉庫叫做“origin”,那遠(yuǎn)端主分支就會(huì)叫做 origin/master。幾乎所有能在本地分支上執(zhí)行的命令都可以在遠(yuǎn)端分支上用。
如果你有多個(gè)遠(yuǎn)端倉庫,你可以執(zhí)行 git fetch [alias] 提取特定的遠(yuǎn)端倉庫, 或者執(zhí)行 git fetch --all 告訴 Git 同步所有的遠(yuǎn)端倉庫。
$ git push 推送你的新分支與數(shù)據(jù)到某個(gè)遠(yuǎn)端倉庫
想要與他人分享你牛鼻的提交,你需要將改動(dòng)推送到遠(yuǎn)端倉庫。 執(zhí)行 git push [alias] [branch],就會(huì)將你的 [branch] 分支推送成為 [alias] 遠(yuǎn)端上的 [branch] 分支。 讓我們試試推送我們的主分支到先前添加的“github”遠(yuǎn)端倉庫上去。
$ git push github master
Counting objects: 25, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (25/25), done.
Writing objects: 100% (25/25), 2.43 KiB, done.
Total 25 (delta 4), reused 0 (delta 0)
To git@github.com:schacon/hw.git
* [new branch] master -> master
挺簡單?,F(xiàn)在如果有人從該倉庫克隆,他會(huì)得到我提交的完完全全的一份歷史記錄了。
如果有個(gè)像之前創(chuàng)建的“erlang”分支那樣的主題分支,想只分享這個(gè),該怎么辦呢?你可以相應(yīng)的只推送該分支。
$ git push github erlang
Counting objects: 7, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 652 bytes, done.
Total 6 (delta 1), reused 0 (delta 0)
To git@github.com:schacon/hw.git
* [new branch] erlang -> erlang
現(xiàn)在當(dāng)人們從該倉庫克隆時(shí),他們就會(huì)得到一個(gè)“erlang”分支以查閱、合并。 用這種方式,你可以推送任何分支到任何你有寫權(quán)限的倉庫。 如果你的分支已經(jīng)在該倉庫中了,它會(huì)試著去更新,如果它不再,Git 會(huì)把它加上。
最后一個(gè)當(dāng)你推送到遠(yuǎn)端分支時(shí)會(huì)碰到的主要問題是,其他人在此期間也推送了的情況。 如果你和另一個(gè)開發(fā)者同時(shí)克隆了,又都有提交,那么當(dāng)她推送后你也想推送時(shí),默認(rèn)情況下 Git 不會(huì)讓你覆蓋她的改動(dòng)。 相反的,它會(huì)在你試圖推送的分支上執(zhí)行 git log,確定它能夠在你的推送分支的歷史記錄中看到服務(wù)器分支的當(dāng)前進(jìn)度。 如果它在在你的歷史記錄中看不到,它就會(huì)下結(jié)論說你過時(shí)了,并打回你的推送。 你需要正式提取、合并,然后再次推送 —— 以確定你把她的改動(dòng)也考慮在內(nèi)了。
當(dāng)你試圖推送到某個(gè)以被更新的遠(yuǎn)端分支時(shí),會(huì)出現(xiàn)下面這種情況:
$ git push github master
To git@github.com:schacon/hw.git
! [rejected] master -> master (non-fast-forward)
error: failed to push some refs to 'git@github.com:schacon/hw.git'
To prevent you from losing history, non-fast-forward updates were rejected
Merge the remote changes before pushing again. See the 'Note about
fast-forwards' section of 'git push --help' for details.
你可以修正這個(gè)問題。執(zhí)行 git fetch github; git merge github/master,然后再推送
簡而言之 執(zhí)行
git push [alias] [branch]將你的本地改動(dòng)推送到遠(yuǎn)端倉庫。 如果可以的話,它會(huì)依據(jù)你的 [branch] 的樣子,推送到遠(yuǎn)端的 [branch] 去。 如果在你上次提取、合并之后,另有人推送了,Git 服務(wù)器會(huì)拒絕你的推送,知道你是最新的為止。
檢查與比較
現(xiàn)在你有了一堆分支,短期的主題、長期的特性或者其它。怎樣追蹤他們呢? Git 有一組工具,可以幫助你弄明白工作是在哪兒完成的,兩個(gè)分支間的區(qū)別是啥,等等。
簡而言之 執(zhí)行
git log找到你的項(xiàng)目歷史中的特定提交 —— 按作者、日期、內(nèi)容或者歷史記錄。執(zhí)行git diff比較歷史記錄中的兩個(gè)不同的點(diǎn) —— 通常是為了看看兩個(gè)分支有啥區(qū)別,或者從某個(gè)版本到另一個(gè)版本,你的軟件都有啥變化。
$ git log 過濾你的提交歷史記錄
通過查看分支中另一分支看不到的提交記錄,我們已經(jīng)看到如何用 git log 來比較分支。 (如果你不記得了,它看起來是這樣的:git log branchA ^branchB)。 而且,你也可以用 git log 去尋找特定的提交。 在此,我們會(huì)看到一些更廣為使用的 git log 選項(xiàng),不過哪有很多。 完整的清單可以看看官方文檔。
$ git log --author 只尋找某個(gè)特定作者的提交
要過濾你的提交歷史,只尋找某個(gè)特定作者的提交,你可以使用 --author 選項(xiàng)。 例如,比方說我們要找 Git 源碼中 Linus 提交的部分。 我們可以執(zhí)行類似 git log --author=Linus 的命令。 這個(gè)查找是大小寫敏感的,并且也會(huì)檢索電子郵箱地址。 我在此例中使用 -[number] 選項(xiàng),以限制結(jié)果為最近[number]次的提交。
$ git log --author=Linus --oneline -5
81b50f3 Move 'builtin-*' into a 'builtin/' subdirectory
3bb7256 make "index-pack" a built-in
377d027 make "git pack-redundant" a built-in
b532581 make "git unpack-file" a built-in
112dd51 make "mktag" a built-in
$ git log --since --before 根據(jù)日期過濾提交記錄
如果你要指定一個(gè)你感興趣的日期范圍以過濾你的提交,可以執(zhí)行幾個(gè)選項(xiàng) —— 我用 --since 和 --before,但是你也可以用 --until 和 --after。 例如,如果我要看 Git 項(xiàng)目中三周前且在四月十八日之后的所有提交,我可以執(zhí)行這個(gè)(我還用了 --no-merges 選項(xiàng)以隱藏合并提交):
$ git log --oneline --before={3.weeks.ago} --after={2010-04-18} --no-merges
5469e2d Git 1.7.1-rc2
d43427d Documentation/remote-helpers: Fix typos and improve language
272a36b Fixup: Second argument may be any arbitrary string
b6c8d2d Documentation/remote-helpers: Add invocation section
5ce4f4e Documentation/urls: Rewrite to accomodate transport::address
00b84e9 Documentation/remote-helpers: Rewrite description
03aa87e Documentation: Describe other situations where -z affects git diff
77bc694 rebase-interactive: silence warning when no commits rewritten
636db2c t3301: add tests to use --format="%N"
$ git log --grep 根據(jù)提交注釋過濾提交記錄
你或許還想根據(jù)提交注釋中的某個(gè)特定短語查找提交記錄??梢杂?--grep 選項(xiàng)。 比如說我知道有個(gè)提交是有關(guān)使用 P4EDITOR 環(huán)境變量,又想回憶起那個(gè)改動(dòng)是啥樣子的 —— 我可以用 --grep 選項(xiàng)找到該提交。
$ git log --grep=P4EDITOR --no-merges
commit 82cea9ffb1c4677155e3e2996d76542502611370
Author: Shawn Bohrer
Date: Wed Mar 12 19:03:24 2008 -0500
git-p4: Use P4EDITOR environment variable when set
Perforce allows you to set the P4EDITOR environment variable to your
preferred editor for use in perforce. Since we are displaying a
perforce changelog to the user we should use it when it is defined.
Signed-off-by: Shawn Bohrer <shawn.bohrer@gmail.com>
Signed-off-by: Simon Hausmann <simon@lst.de>
Git 會(huì)對所有的 --grep 和 --author 參數(shù)作邏輯或。 如果你用 --grep 和 --author 時(shí),想看的是某人寫作的并且有某個(gè)特殊的注釋內(nèi)容的提交記錄, 你需要加上 --all-match 選項(xiàng)。 在這些例子中,我會(huì)用上 --format 選項(xiàng),這樣我們就可以看到每個(gè)提交的作者是誰了。
如果我查找注釋內(nèi)容含有 “p4 depo”的提交,我得到了三個(gè)提交:
$ git log --grep="p4 depo" --format="%h %an %s"
ee4fd1a Junio C Hamano Merge branch 'master' of git://repo.or.cz/git/fastimport
da4a660 Benjamin Sergeant git-p4 fails when cloning a p4 depo.
1cd5738 Simon Hausmann Make incremental imports easier to use by storing the p4 d
如果我加上 --author=Hausmann 參數(shù),與進(jìn)一步過濾上述結(jié)果到 Simon 的唯一提交相反, 它會(huì)告訴我所有 Simon 的提交,或者注釋中有“p4 demo”的提交:
$ git log --grep="p4 depo" --format="%h %an %s" --author="Hausmann"
cdc7e38 Simon Hausmann Make it possible to abort the submission of a change to Pe
f5f7e4a Simon Hausmann Clean up the git-p4 documentation
30b5940 Simon Hausmann git-p4: Fix import of changesets with file deletions
4c750c0 Simon Hausmann git-p4: git-p4 submit cleanups.
0e36f2d Simon Hausmann git-p4: Removed git-p4 submit --direct.
edae1e2 Simon Hausmann git-p4: Clean up git-p4 submit's log message handling.
4b61b5c Simon Hausmann git-p4: Remove --log-substitutions feature.
36ee4ee Simon Hausmann git-p4: Ensure the working directory and the index are cle
e96e400 Simon Hausmann git-p4: Fix submit user-interface.
38f9f5e Simon Hausmann git-p4: Fix direct import from perforce after fetching cha
2094714 Simon Hausmann git-p4: When skipping a patch as part of "git-p4 submit" m
1ca3d71 Simon Hausmann git-p4: Added support for automatically importing newly ap
...
不過,如果加上 --all-match,結(jié)果就是我想要的了:
$ git log --grep="p4 depo" --format="%h %an %s" --author="Hausmann" --all-match
1cd5738 Simon Hausmann Make incremental imports easier to use by storing the p4 d
$ git log -S 依據(jù)所引入的差值過濾
如果你寫的提交注釋都極度糟糕怎么辦?或者,如果你要找某個(gè)函數(shù)是何時(shí)引入的,某些變量是在哪里開始被使用的? 你可以告訴 Git 在每個(gè)提交之間的差值中查找特定字符串。 例如,如果我們想要找出哪個(gè)提交修改出了類似函數(shù)名“userformat_find_requirements”, 我們可以執(zhí)行(注意在“-S”與你要找的東東之間沒有“=”):
$ git log -Suserformat_find_requirements
commit 5b16360330822527eac1fa84131d185ff784c9fb
Author: Johannes Gilger
Date: Tue Apr 13 22:31:12 2010 +0200
pretty: Initialize notes if %N is used
When using git log --pretty='%N' without an explicit --show-notes, git
would segfault. This patches fixes this behaviour by loading the needed
notes datastructures if --pretty is used and the format contains %N.
When --pretty='%N' is used together with --no-notes, %N won't be
expanded.
This is an extension to a proposed patch by Jeff King.
Signed-off-by: Johannes Gilger
Signed-off-by: Junio C Hamano
$ git log -p 顯示每個(gè)提交引入的補(bǔ)丁
每個(gè)提交都是項(xiàng)目的一個(gè)快照。由于每個(gè)提交都記錄它所基于的快照,Git 能夠經(jīng)常對它們求差值,并以補(bǔ)丁形式向你展示。 這意味著,對任意提交,你都可以獲取該提交給項(xiàng)目引入補(bǔ)丁。 你可以用 git show [SHA] 加上某個(gè)特定的提交 SHA 獲取,或者執(zhí)行 git log -p, 它會(huì)告訴 Git 輸出每個(gè)提交之后的補(bǔ)丁。這是個(gè)總結(jié)某一分支或者兩個(gè)提交之間都發(fā)生了神馬的好途徑。
$ git log -p --no-merges -2
commit 594f90bdee4faf063ad07a4a6f503fdead3ef606
Author: Scott Chacon <schacon@gmail.com>
Date: Fri Jun 4 15:46:55 2010 +0200
reverted to old class name
diff --git a/ruby.rb b/ruby.rb
index bb86f00..192151c 100644
--- a/ruby.rb
+++ b/ruby.rb
@@ -1,7 +1,7 @@
-class HiWorld
+class HelloWorld
def self.hello
puts "Hello World from Ruby"
end
end
-HiWorld.hello
+HelloWorld.hello
commit 3cbb6aae5c0cbd711c098e113ae436801371c95e
Author: Scott Chacon <schacon@gmail.com>
Date: Fri Jun 4 12:58:53 2010 +0200
fixed readme title differently
diff --git a/README b/README
index d053cc8..9103e27 100644
--- a/README
+++ b/README
@@ -1,4 +1,4 @@
-Hello World Examples
+Many Hello World Examples
======================
This project has examples of hello world in
這是個(gè)總結(jié)改動(dòng),以及合并或發(fā)布之前重審一系列提交的好方式。
$ git log --stat 顯示每個(gè)提交引入的改動(dòng)的差值統(tǒng)計(jì)
如果 -p 選項(xiàng)對你來說太詳細(xì)了,你可以用 --stat 總結(jié)這些改動(dòng)。 這是不用 -p,而用 --stat 選項(xiàng)時(shí),同一份日志的輸出。
$ git log --stat --no-merges -2
commit 594f90bdee4faf063ad07a4a6f503fdead3ef606
Author: Scott Chacon <schacon@gmail.com>
Date: Fri Jun 4 15:46:55 2010 +0200
reverted to old class name
ruby.rb | 4 ++--
1 files changed, 2 insertions(+), 2 deletions(-)
commit 3cbb6aae5c0cbd711c098e113ae436801371c95e
Author: Scott Chacon <schacon@gmail.com>
Date: Fri Jun 4 12:58:53 2010 +0200
fixed readme title differently
README | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
同樣的基本信息,但更緊湊 —— 它仍然讓你看到相對改動(dòng),和改動(dòng)了哪些文件。
$ git diff
最后,要查看兩個(gè)提交快照的絕對改動(dòng),你可以用 git diff 命令。 這在兩個(gè)主要情況中廣為使用 —— 查看兩個(gè)分支彼此之間的差值,和查看自發(fā)布或者某個(gè)舊歷史點(diǎn)之后都有啥變了。讓我們看看這倆情況。
你僅需執(zhí)行 git diff [version](或者你給該發(fā)布打的任何標(biāo)簽)就可以查看自最近發(fā)布之后的改動(dòng)。 例如,如果我們想要看看自 v0.9 發(fā)布之后我們的項(xiàng)目改變了啥,我們可以執(zhí)行 git diff v0.9
$ git diff v0.9
diff --git a/README b/README
index d053cc8..d4173d5 100644
--- a/README
+++ b/README
@@ -1,4 +1,4 @@
-Hello World Examples
+Many Hello World Lang Examples
======================
This project has examples of hello world in
diff --git a/ruby.rb b/ruby.rb
index bb86f00..192151c 100644
--- a/ruby.rb
+++ b/ruby.rb
@@ -1,7 +1,7 @@
-class HiWorld
+class HelloWorld
def self.hello
puts "Hello World from Ruby"
end
end
-HiWorld.hello
+HelloWorld.hello
正如 git log,你可以給它加上 --stat 參數(shù)。
$ git diff v0.9 --stat
README | 2 +-
ruby.rb | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
要比較兩個(gè)不同的分支,你可以執(zhí)行類似 git diff branchA branchB 的命令。 不過它的問題在于它會(huì)完完全全按你說的作 —— 它會(huì)直接給你個(gè)補(bǔ)丁文件,該補(bǔ)丁能夠?qū)⒓追种У淖钚驴煺兆兂梢曳种У淖钚驴煺盏臉幼印?這意味著如果兩個(gè)分支已經(jīng)產(chǎn)生分歧 —— 奔往兩個(gè)不同方向了 —— 它會(huì)移除甲分支中引入的所有工作,然后累加乙分支中的所有工作。 這大概不是你要的吧 —— 你想要不在甲分支中的乙分支的改動(dòng)。所以你真的需要的是兩個(gè)分支叉開去時(shí),和最新的乙分支的差別。 所以,如果我們的歷史記錄看起來像這樣:
$ git log --graph --oneline --decorate --all
* 594f90b (HEAD, tag: v1.0, master) reverted to old class name
| * 1834130 (erlang) added haskell
| * ab5ab4c added erlang
|/
* 8d585ea Merge branch 'fix_readme'
...
并且,我們想要看“erlang”分支與主分支相比的查別。執(zhí)行 git diff master erlang會(huì)給我們錯(cuò)誤的結(jié)果。
$ git diff --stat master erlang
erlang_hw.erl | 5 +++++
haskell.hs | 4 ++++
ruby.rb | 4 ++--
3 files changed, 11 insertions(+), 2 deletions(-)
你可以看到,它加上了 erlang 和 haskell 文件,這確實(shí)是我們在該分支中做的, 但是它同時(shí)恢復(fù)了我們在主分支中改動(dòng)的 ruby 文件。我們真心想要的只是“erlang”分支中的改動(dòng)(添加兩個(gè)文件)。 我們可以通過求兩個(gè)分支分歧時(shí)的共同提交與該分支的差值得到想要的結(jié)果:
$ git diff --stat 8d585ea erlang
erlang_hw.erl | 5 +++++
haskell.hs | 4 ++++
2 files changed, 9 insertions(+), 0 deletions(-)
這才是我們在找的,但是我們可不想要每次都要找出兩個(gè)分支分歧時(shí)的那次提交。 幸運(yùn)的是,Git 為此提供了一個(gè)快捷方式。 如果你執(zhí)行 git diff master...erlang(在分支名之間有三個(gè)半角的點(diǎn)), Git 就會(huì)自動(dòng)找出兩個(gè)分支的共同提交(也被成為合并基礎(chǔ)),并求差值。
$ git diff --stat master erlang
erlang_hw.erl | 5 +++++
haskell.hs | 4 ++++
ruby.rb | 4 ++--
3 files changed, 11 insertions(+), 2 deletions(-)
$ git diff --stat master...erlang
erlang_hw.erl | 5 +++++
haskell.hs | 4 ++++
2 files changed, 9 insertions(+), 0 deletions(-)
幾乎每一次你要對比兩個(gè)分支的時(shí)候,你都會(huì)想用三個(gè)點(diǎn)的語法,因?yàn)樗ǔ?huì)給你你想要的。
順帶提一句,你還可以讓 Git 手工計(jì)算兩次提交的合并基礎(chǔ)(第一個(gè)共同的祖提交),即 git merge-base 命令:
$ git merge-base master erlang
8d585ea6faf99facd39b55d6f6a3b3f481ad0d3d
所以你執(zhí)行下面這個(gè)也跟 git diff master...erlang 一樣:
$ git diff --stat $(git merge-base master erlang) erlang
erlang_hw.erl | 5 +++++
haskell.hs | 4 ++++
2 files changed, 9 insertions(+), 0 deletions(-)
當(dāng)然,我會(huì)推薦簡單點(diǎn)的那個(gè)。
簡而言之 使用
git diff查看某一分支自它偏離出來起與過去某一點(diǎn)之間項(xiàng)目的改動(dòng)。 總是使用git diff branchA...branchB來查看 branchB 與 branchA 的相對差值,這會(huì)讓事情簡單點(diǎn)。