即時分支合併是Git最給力的殺手鐧。
問題 :外部因素要求必須切換場景。在發佈版本中突然蹦出個嚴重缺陷。某個特性完 成的截至日期就要來臨。在項目關鍵部分可以提供幫助的一個開發正打算離職。所有情 況逼迫你停下所有手頭工作,全力撲到到這個完全不同的任務上。
打斷思維的連續性會使你的生產力大大降低,並且切換上下文也更麻煩,更大的損失。 使用中心版本控制我們必須從中心伺服器下載一個新的工作拷貝。分散式系統的情況就 好多了,因為我們能夠在本地克隆所需要的版本。
但是克隆仍然需要拷貝整個工作目錄,還有直到給定點的整個歷史記錄。儘管Git使用文 件共享和硬連結減少了花費,項目檔案自身還是必須在新的工作目錄裡重建。
方案 :Git有一個更好的工具對付這種情況,比克隆快多了而且節省空間: git branch 。
使用這個魔咒,目錄裡的檔案突然從一個版本變到另一個。除了只是在歷史記錄裡上跳 下竄外,這個轉換還可以做更多。你的檔案可以從上一個發佈版變到實驗版本到當前開 發版本到你朋友的版本等等。
曾經玩過那樣的遊戲嗎?按一個鍵(“老闆鍵”),屏幕立即顯示一個電子表格或別的? 那麼如果老闆走進辦公室,而你正在玩遊戲,就可以快速將遊戲藏起來。
在某個目錄:
$ echo "I'm smarter than my boss" > myfile.txt $ git init $ git add . $ git commit -m "Initial commit"
我們已經創建了一個Git倉庫,該倉庫記錄一個包含特定信息的檔案。現在我們鍵入:
$ git checkout -b boss # 之後似乎沒啥變化 $ echo "My boss is smarter than me" > myfile.txt $ git commit -a -m "Another commit"
看起來我們剛剛只是覆蓋了原來的檔案並提交了它。但這是個錯覺。鍵入:
$ git checkout master # 切到檔案的原先版本
嘿真快!這個檔案就恢復了。並且如果老闆決定窺視這個目錄,鍵入:
$ git checkout boss # 切到適合老闆看的版本
你可以在兩個版本之間相切多少次就切多少次,而且每個版本都可以獨立提交。
比如你正在開發某個特性,並且由於某種原因,你需要回退三個版本,臨時加進幾行打 印語句來,來看看一些東西是如何工作的。那麼:
$ git commit -a $ git checkout HEAD~3
現在你可以到處加醜陋的臨時代碼。你甚至可以提交這些改動。當你做完的時候,
$ git checkout master
來返回到你原來的工作。看,所有未提交變更都結轉了。
如果你後來想保存臨時變更怎麼辦?簡單:
$ git checkout -b dirty
只要在切換到主分支之前提交就可以了。無論你什麼時候想回到髒的變更,只需鍵入:
$ git checkout dirty
我們在前面章節討論加載舊狀態的時候,曾經接觸過這個命令。最終我們把故事說全: 檔案改變成請求的狀態,但我們必須離開主分支。從現在開始的任何提交都會將你的文 件提交到另一條不同的路,這個路可以之後命名。
換一個說法,在checkout一個舊狀態之後,Git自動把你放到一個新的,未命名的分支, 這個分支可以使用 git checkout -b 來命名和保存。
你正在做某件事的當間,被告知先停所有的事情,去修理一個新近發現的臭蟲,這個臭 蟲在提交 `1b6d…`:
$ git commit -a $ git checkout -b fixes 1b6d
那麼一旦你修正了這個臭蟲:
$ git commit -a -m "Bug fixed" $ git checkout master
並可以繼續你原來的任務。你甚至可以“合併”到最新修訂:
$ git merge fixes
一些版本控制系統,創建分支很容易,但把分支合併回來很難。使用Git,合併簡直是家 常便飯,以至于甚至你可能對其發生沒有察覺。
我們很久之前就遇到合併了。 pull 命令取出提交併合並它們到你的當前分支。如果 你沒有本地變更,那這個合併就是一個“快進”,相當於中心式版本控制系統裡的一個 弱化的獲取最新版本操作。但如有本地變更,Git將自動合併,並報告任何衝突。
通常,一個提交只有一個“父提交”,也叫前一個提交。合併分支到一起產生一個至少 有兩個父的提交。這就引出了問題:
HEAD~10
真正指哪個提交?一個提交可能有多個
父,那我們跟哪個呢?
原來這個表示每次選擇第一個父。這是可取的,因為在合併時候當前分支成了第一個父; 多數情況下我們只關注我們在當前分支都改了什麼,而不是從其他分支合併來的變更。
你可以用插入符號來特別指定父。比如,顯示來自第二個父的日誌:
$ git log HEAD^2
你可以忽略數字以指代第一個父。比如,顯示與第一個父的差別:
$ git diff HEAD^
你可以結合其他類型使用這個記號。比如:
$ git checkout 1b6d^^2~10 -b ancient
開始一個新分支 “ancient” ,表示第一個父的第二個父的倒數第十次提交的狀態。
經常在硬件項目裡,計劃的第二步必須等第一步完成才能開始。待修的汽車傻等在車庫 裡,直到特定的零件從工廠運來。一個原型在其可以構建之前,可能苦等晶片成型。
軟件項目可能也類似。新功能的第二部分不得不等待,直到第一部分發佈並通過測試。 一些項目要求你的代碼需要審批才能接受,因此你可能需要等待第一部分得到批准,才 能開始第二部分。
多虧了無痛分支合併,我們可以不必遵循這些規則,在第一部分正式準備好前開始第二 部分的工作。假設你已經將第一部分提交並發去審批,比如說你現在在主分支。那麼分 岔:
$ git checkout -b part2
接下來,做第二部分,隨時可以提交變更。只要是人就可能犯錯誤,經常你將回到第一 部分在修修補補。如果你非常幸運,或者超級棒,你可能不必做這幾行:
$ git checkout master # 回到第一部分 $ 修復問題 $ git commit -a # 提交變更 $ git checkout part2 # 回到第二部分 $ git merge master # 合併這些改動
最終,第一部分獲得批准:
$ git checkout master # 回到第一部分 $ submit files # 對世界發佈 $ git merge part2 # 合併第二部分 $ git branch -d part2 # 刪除分支“part2”
現在你再次處在主分支,第二部分的代碼也在工作目錄。
很容易擴展這個技巧,應用到任意數目的部分。它也很容易追溯分支:假如你很晚才意 識到你本應在7次提交前就創建分支。那麼鍵入:
$ git branch -m master part2 # 重命名“master”分支為“part2”。 $ git branch master HEAD~7 # 以七次前提交建一個新的“master”。
分支 master
只有第一部分內容,其他內容在分支
part2
。 我們現在後一個分支; 我們創建了
master
分支還沒有切換過去,因為我們想繼續工作在
part2
。這是不
尋常的。直到現在,我們已經在創建之後切換到分支,如:
$ git checkout HEAD~7 -b master # 創建分支,並切換過去。
或許你喜歡在同一個分支下完成工作的方方面面。你想為自己保留工作進度並希望其他 人只能看到你仔細整理過後的提交。開啟一對分支:
$ git branch sanitized # 為乾淨提交創建分支 $ git checkout -b medley # 創建並切換分支以進去工作
接下來,做任何事情:修臭蟲,加特性,加臨時代碼,諸如此類,經常按這種方式提交。 然後:
$ git checkout sanitized $ git cherry-pick medley^^
應用分支 “medley” 的祖父提交到分支 “sanitized” 。通過合適的挑選(像選櫻桃 那樣)你可以構建一個只包含成熟代碼的分支,而且相關的提交也組織在一起。
列出所有分支:
$ git branch
預設你從叫 “master” 的分支開始。一些人主張別碰“master”分支,而是創建你自 己版本的新分支。
選項 -d 和 -m 允許你來刪除和移動(重命名)分支。參見 git help branch 。
分支“master” 是一個有用的慣例。其他人可能假定你的倉庫有一個叫這個名字的分 支,並且該分支包含你項目的官方版本。儘管你可以重命名或抹殺 “master” 分支, 你最好還是尊重這個約定。
很快你會發現你經常會因為一些相似的原因創建短期的分支:每個其它分支只是為了保 存當前狀態,那樣你就可以直接跳到較老狀態以修復高優先順序的臭蟲之類。
可以和電視的換台做類比,臨時切到別的頻道,來看看其它台那正放什麼。但並不是簡 單地按幾個按鈕,你不得不創建,檢出,合併,以及刪除臨時分支。幸運的是,Git已經 有了和電視機遙控器一樣方便的快捷方式:
$ git stash
這個命令保存當前狀態到一個臨時的地方(一個隱藏的地方)並且恢復之前狀態。你的 工作目錄看起來和你開始編輯之前一樣,並且你可以修復臭蟲,引入之前變更等。當你 想回到隱藏狀態的時候,鍵入:
$ git stash apply # 你可能需要解決一些衝突
你可以有多個隱藏,並用不同的方式來操作他們。參見 git help slash 。也許你已 經猜到,Git維護在這個場景之後的分支以執行魔法技巧.