合併兩個分支:git-merge
既然我們為專案建立了不同的分支,那麼我們就要經常地將自己或者是別人在一個分支上的工作合併到其他的分支上去。現在我們看看怎麼將 robin 分支上的工作合併到 master 分支中。現在轉移我們當前的工作分支到 master,並且將 robin 分支上的工作合併進來。
$ git-checkout master
$ git-merge "Merge work in robin" HEAD robin
合併兩個分支,還有一個更簡便的方式,下面的命令和上面的命令是等價的。
$ git-checkout master
$ git-pull . robin
但是,此時 git 會出現合併衝突提示:
Trying really trivial in-index merge...
fatal: Merge requires file-level merging
Nope.
Merging HEAD with d2659fcf690ec693c04c82b03202fc5530d50960
Merging:
1d2fa05b13b63e39f621d8ee911817df0662d9b7 Some fun
d2659fcf690ec693c04c82b03202fc5530d50960 some work
found 1 common ancestor(s):
3ecebc0cb4894a33208dfa7c7c6fc8b5f9da0eda a new day for git
Auto-merging hello
CONFLICT (content): Merge conflict in hello
Automatic merge failed; fix up by hand
git 的提示指出,在合併作用於檔 hello 的 'Some fun' 和 'some work' 這兩個物件時有衝突,具體通俗點說,就是在 master, robin 這兩個分支中的 hello 檔的某些相同的行中的內容不一樣。我們需要手動解決這些衝突,現在先讓我們看看現在的 hello 檔中的內容。
$ cat hello
此時的 hello 檔應是這樣的,用過其他的版本控制系統的朋友應該很容易看出這個典型的衝突表示格式:
Hello World
It's a new day for git
<<<<<<< HEAD/hello
Play, play, play
=======
Work, work, work
>>>>>>> d2659fcf690ec693c04c82b03202fc5530d50960/hello
我們用編輯器將 hello 檔改為:
Hello World
It's a new day for git
Play, play, play
Work, work, work
現在可以將手動解決了衝突的檔提交了。
$ git-commit -i hello
以上是典型的兩路合併(2-way merge)演算法,絕大多數情況下已經夠用。但是還有更複雜的三路合併和多內容樹合併的情況。詳情可參看: git-read-tree, git-merge 等文檔。
逆轉與恢復:git-reset
項目跟蹤工具的一個重要任務之一,就是使我們能夠隨時逆轉(Undo)和恢復(Redo)某一階段的工作。
git-reset 命令就是為這樣的任務準備的。它將當前的工作分支的 頭 定位到以前提交的任何版本中,它有三個重置的演算法選項。
命令形式:
git-reset [--mixed | --soft | --hard] [<commit-ish>]
命令的選項:
--mixed
僅是重置索引的位置,而不改變你的工作樹中的任何東西(即,檔中的所有變化都會被保留,也不標記他們為待提交狀態),並且提示什麼內容還沒有被更新了。這個是預設的選項。
--soft
既不觸動索引的位置,也不改變工作樹中的任何內容,我們只是要求這些內容成為一份好的內容(之後才成為真正的提交內容)。這個選項使你可以將已經提交的東西重新逆轉至“已更新但未提交(Updated but not Check in)”的狀態。就像已經執行過 git-update-index 命令,但是還沒有執行 git-commit 命令一樣。
--hard
將工作樹中的內容和頭索引都切換至指定的版本位置中,也就是說自 <commit-ish> 之後的所有的跟蹤內容和工作樹中的內容都會全部丟失。因此,這個選項要慎用,除非你已經非常確定你的確不想再看到那些東西了。
一個重要技巧--逆轉提交與恢復
可能有人會問,--soft 選項既不重置頭索引的位置,也不改變工作樹中的內容,那麼它有什麼用呢?現在我們介紹一個 --soft 選項的使用技巧。下面我們用例子來說明:
$ git-checkout master
$ git-checkout -b softreset
$ git-show-branch
這裡我們建立了一個 master 的拷貝分支 softreset,現在我們可以看到兩個分支是在同一起跑線上的。
! [master] Merge branch 'robin'
! [robin] some work
* [softreset] Merge branch 'robin'
---
- - [master] Merge branch 'robin'
+ * [master^] Some fun
++* [robin] some work
我們為 檔增加一些內容並提交。
$ echo "Botch, botch, botch" >> hello
$ git-commit -a -m "some botch"
$ git-show-branch
我們可以看到此時 softreset 比 master 推進了一個版本 "some botch" 。
! [master] Merge branch 'robin'
! [robin] some work
* [softreset] some botch
---
* [softreset] some botch
- - [master] Merge branch 'robin'
+ * [master^] Some fun
++* [robin] some work
現在讓我們來考慮這樣的一種情況,假如我們現在對剛剛提交的內容不滿意,那麼我們再編輯專案的內容,再提交的話,那麼 "some botch" 的內容就會留在版本庫中了。我們當然不希望將有明顯問題的內容留在版本庫中,這個時候 --soft 選項就很有用了。為了深入瞭解 --soft 的機制,我們看看現在 softreset 分支的頭和 ORIG_HEAD 保存的索引。
$ cat .git/refs/heads/softreset .git/ORIG_HEAD
結果如下:
5e7cf906233e052bdca8c598cad2cb5478f9540a
7bbd1370e2c667d955b6f6652bf8274efdc1fbd3
現在用 --soft 選項逆轉剛才提交的內容:
git-reset --soft HEAD^
現在讓我們再看看 .git/ORIG_HEAD 的中保存了什麼?
$ cat .git/ORIG_HEAD
結果如下:
5e7cf906233e052bdca8c598cad2cb5478f9540a
看!現在的 .git/ORIG_HEAD 等於逆轉前的 .git/refs/heads/softreset 。也就是說,git-reset --soft HEAD^ 命令逆轉了剛才提交的版本進度,但是它將那次提交的物件的索引拷貝到了 .git/ORIG_HEAD 中。
我們再編輯 hello 檔成為下面的內容:
Hello World
It's a new day for git
Play, play, play
Work, work, work
Nice, nice, nice
我們甚至可以比較一下現在的工作樹中的內容和被取消了的那次提交的內容有什麼差異:
$ git-diff ORIG_HEAD
結果如下:
diff --git a/hello b/hello
index f978676..dd02c32 100644
--- a/hello
+++ b/hello
@@ -2,4 +2,4 @@ Hello World
It's a new day for git
Play, play, play
Work, work, work
-Botch, botch, botch
+Nice, nice, nice
接著,我們可以恢復剛才被取消了的那次提交了。
$ git-commit -a -c ORIG_HEAD
注意,這個命令會打開默認的文字編輯器以編輯原來提交的版本日誌資訊,我們改為 "nice work" 。大家可以自行用 git-show-branch 命令來查看一下現在的分支狀態。並且我們還可以不斷地重複上述的步驟,一直修改到你對這個版本進度滿意為止。
git-reset 命令還有很多的用途和技巧,請參考 git-reset ,以及 Everyday GIT with 20 commands or So 。
提取版本庫中的資料
這是個很有用的小技巧,如果你對你現在的工作目錄下的東西已經不耐煩了,隨時可以取出你提交過的東西覆蓋掉當前的文件,譬如:
$ git-checkout -f foo.c
標定版本
在 git 中,有兩種類型的標籤,“輕標籤”和“署名標籤”。
技術上說,一個“輕標籤”和一個分支沒有任何區別,只不過我們將它放在了 .git/refs/tags/ 目錄,而不是 heads 目錄。因此,打一個“輕標籤”再簡單不過了。
$ git-tag my-first-tag
如果你打算針對某個commit ID來打標籤,雖然該命令可以通過gitk裡的右鍵功能表來實現,但是該命令對實際應用是很有幫助的。
$ git-tag mytag f0af6283824688f9d23426031734657661b54388
“署名標籤”是一個真正的 git 物件,它不但包含指向你想標記的狀態的指標,還有一個標記名和資訊,可選的 PGP 簽名。你可以通過 -a 或者是 -s 選項來建立“署名標籤”。
$ git-tag -s <tag-name>
留言列表