高富帥們的Git技巧

江小湖Laker發表於2014-12-27

譯者序​

Git是一個分散式版本控制系統,擁有許多神奇而易用的特性(比如:分支),這讓它可以輕鬆適應各種工作流程。這篇文章不涉及Git的基本使用,而是介紹了一些高階卻有用的小技巧。讓我們一起來看看高富帥們的Git技巧,準備好逆襲吧!

以“塊”形式暫存你的改動

你肯定已經很熟悉的使用git add命令來將改動暫存到暫存區(staging area)了。你可能也會偶然因為兩個不同的原因而做了一次改動,卻沒有分別提交(僅僅提交了一次),所以,當你執行git log時,會看到諸如這樣的提交資訊:“修改X,改動無關的Y”。如果這看起來像是你的工作方式,互動式add將是你的有力工具。

互動式add(或者叫add塊),將會一個塊一個快的迴圈你的改動。使用命令git add -p時,你可以在每個改動“塊”(即:連續的改動會被組織到一起)時進行一些選擇,比如:切分當前塊為更小的塊、跳過一個改動塊、甚至手動的編輯該塊,你可以敲入?來檢視所有該命令提供的選項。

開始以“塊”形式暫存改動簡單到只需一條命令(括號部分替換為特定檔案):

git add -p (path/file)

譯者注:感覺這條命令平常用的較少,我遇到需要分別提交的情況時,都是手動來add然後提交,該命令是這種方法的高階版本。我們平常可能對提交歷史的重視比較低,常常出現一些無用的、無意義的提交資訊,可以試試這條命令。

切換到最後所在分支

作為一個善良的碼農,當你需要快速做些修正或是清理工作時,你都應該花些時間來對待。如果你的工作流程是十分依賴分支的話(譯者注:強烈建議如此),你可能不希望無關的修正影響到現在正在進行功能開發的分支。這意味著,你應該使用git stash命令來暫時存放你的改動,然後切到master分支(譯者注:或是其它啥分支,我一般是取名為fix),在那個分支進行修正。(譯者注:修正完了,可以切回正在進行功能開發的分支,執行git stash pop來彈出之前暫存的改動,繼續進行開發)。在不同分支間切換很乏味,幸好這裡有個快捷命令可以切換到你最後所在的分支:

git checkout -

這個語法對於使用linux的高富帥們來說一定不陌生,cd命令有個類似的縮寫cd -,表示切換到你最後所在的目錄。當你需要切回功能開發分支時,你根本不用關心那個分支是啥名,只需git checkout -。

譯者注:感覺tab鍵的自動補全也挺好用的,不過這條命令可以少敲點字。有了這條命令,媽媽再也不用擔心我的分支切換了。

顯示哪些分支被合併了(或是哪些沒有被合併)

在使用git時,你可能會建立許多分支,導致執行git branch命令列出分支時變得有些雜亂。於是,你想處理那些已經合併到master分支的無用分支,但是,當你執行git checkout -d 來刪除分支時可能會遇到“麻煩”(譯者注:git會拒絕刪除未合併的分支並提示你),如果使用以下命令,你將不再需要三思而後刪,可以自信的處理那些已經合併了的分支。

如果你想要看看你的本地分支裡哪些分支是已經合併進你當前所在的分支時,可以使用:

git branch --merged

反過來,如果需要檢視哪些分支還沒有合併進當前所在的分支,可以使用:

git branch --no-merged

結合高富帥的UNIX工具,你可以輕鬆的刪除那些已經合併了的分支:

git branch --merged | xargs git branch -d

譯者注:xargs是UNIX平臺的一個工具,它的作用是將引數列表轉換成小塊分段傳遞給其他命令,以避免引數列表過長的問題。如果git branch –merged顯示的是a,b,c三個分支已經合併,上面的命令會轉換為:git branch -d a b c。更多xargs的資訊:http://zh.wikipedia.org/wiki/Xargs

從另一分支獲取檔案內容而不用切換分支

設想你正在進行重構,你建立了好幾個分支並在各分支下進行改動。這時,你想把另一個分支裡某一個檔案的改動引入到當前工作的分支裡,為了達到目的你可能需要好幾步:git stash你的改動;切換到那個分支;獲取檔案的改動;切回工作分支(當然是使用git checkout -);繼續進行編輯(譯者注:別忘了git stash pop)。但是,你也可以直接檢出另一分支的檔案,並且合併到你當前所在的工作分支,使用命令(括號部分替換為對應的分支和檔案):

git checkout (branch) -- (path/file)

以最後提交排序的Git分支

想必你已經使用上面的tip處理了雜亂的分支,有一些是用–merged選項標誌來清理的吧。那其它的分支咋辦呢?你咋知道哪些是有用的,哪些是完全過期無用的呢?git for-each-ref命令可以列印出一個列表,該列表顯示每個分支最後一次提交的引用(reference)資訊。我們可以自定義輸出來包含一些有用的資訊,更重要的是我們還可以按日期排序。可以使用下面的命令來輸出一個列表,該表將顯示按時間先後排序的每個分支的最後提交資訊、提交者等資訊:

git for-each-ref --sort=-committerdate --format="%(committername)@%(refname:short) [%(committerdate:short)] %(contents)"

還可以把它定義在gitconfig裡:

[alias]
  latest = for-each-ref --sort=-committerdate --format="%(committername)@%(refname:short) [%(committerdate:short)] %(contents)"

譯者注:定義後就只需執行git latest了。注意雙引號需要轉義!

在玻璃房內的人們別用git blame

或者說,在玻璃房內的人們不應該直接使用git blame而不帶下文的選項標誌。(譯者注:玻璃房內的人是完全能被別人看到的人。這裡的意思應該是想說,你每一次提交的變動都會被記錄到git倉庫的歷史,對於git倉庫來說,你就像是住在玻璃房裡的人,沒有任何祕密,你根本逃不過git的”責問“)git blame是很有用的命令,它就像使用科學來證明你是正確的!但是請注意,許多檔案的變動是很表面的,發現問題的來源需要更多的探索。像是移除空白、移動內容到新行、移動內容到另一檔案等動作都可以使用選項來忽略掉,以便更容易的找到程式碼變動的始作俑者。

在你blame(責備)他人前,記得用以下命令看看結果:

git blame -w  # 忽略移除空白這類改動
git blame -M  # 忽略移動文字內容這類改動
git blame -C  # 忽略移動文字內容到其它檔案這類改動

譯者注:git blame用來顯示一份檔案每一行的最近一次提交的提交hash值和提交者。當你跟別人說“我真的沒改過這個檔案啊”之前,就得git blame下。

在整個git倉庫提交歷史中找尋內容(然後刪掉它)

你有時可能需要查詢一行你寫的程式碼,但是就是無法找到。它可能安放在了一些已經被遺忘的分支,或是刪除了很久,又或是就在那顯而易見的地方。無論哪種方式,你都可以通過一些命令在整個git倉庫的歷史中搜尋特定的字串。

首先,我們需要拿到所有的提交,然後,使用git grep來搜尋特定的字串。如下:

git rev-list --all | xargs git grep -F `搜尋的字串`

你可能有一個粗心的朋友不小心在倉庫裡提交了諸如,使用者名稱、密碼、外婆的大蒜食譜等敏感資訊。首先,他們得更改使用者名稱、密碼(並向外婆道歉)。然後,你需要搜尋這些得罪人的檔案,並將他們從整個倉庫的歷史裡抹去(這聽起來好像很容易)。經過這個處理,那些執行git pull的夥計們就會發現所有提交中包含的敏感資訊都被清理乾淨了,而那些沒有合併你的遠端改動的傢伙還是擁有敏感資訊(所以,千萬別忘記先改使用者名稱和密碼)。我們來看看怎麼操作。

首先,重寫每個分支的歷史,移除敏感資訊:

git filter-branch --index-filter `git rm --cached --ignore-unmatch (filename)` --prune-empty --tag-name-filter cat -- --all

然後,將記錄敏感資訊的檔案增加到.gitignore檔案,並提交(括號部分替換為對應檔名):

echo (filename) >> .gitignore
git add .gitignore
git commit -m "Add sensitive (filename) file to gitignore"

接著,由於我們改寫了歷史,我們需要“強制”的將改動推到遠端:

git push origin master --force
# 譯者注:還可以使用命令
git push origin +master

最後,這個檔案還在你的本地倉庫裡,還需要將它完全抹除:

rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --prune=now
git gc --aggressive --prune=now

你這粗心的朋友從敏感檔案的危機中解脫,而你用你高超的git知識成功逆襲,成為了他的英雄!

譯者注:一天,妹子叫我去她家幫她把她的三圍資訊從git倉庫的歷史裡完全刪除,我研究了很久不得要領。妹子說,不如我們做點其它的事吧。我覺得我的git知識被她鄙視了,堅定的說,我一定要把它刪掉!然後,就沒有然後了… …

忽略檔案跟蹤

在和他人合作時可能常常意味著你需要更改一些配置才能讓應用在環境裡跑起來,這時,常常會不小心把這些只對你有意義的配置檔案也給提交了。為了不再常常關注這些檔案,看著它們在git status時放肆的顯示“modified”,你可以告訴git忽略它們的改動。這種方式,可以看成是一種和倉庫繫結的gitignore檔案(括號部分替換為對應檔案):

  git update-index --assume-unchanged (path/file)

譯者注:感覺,.gitignore檔案更方便和好理解。

讓分支的歷史歸零

不管出於啥理由,有時從頭開始正是你需要的。也許是你接手了一個不確信能安全開源的程式碼倉庫;也許是你要著手做些全新的事情;也許是你想建立用於其它目的一個新分支,又希望繼續在倉庫裡維護它(比如:github頁面,專案的文件一類的東西)。上述的情形下,你可以非常簡單的建立一個沒有提交歷史的分支(括號部分替換為對應分支):

  git checkout --orphan (branch)

譯者注:我們知道,分支只是對提交的一個引用,所以,每當從當前分支建立一個分支時,被建立的分支都會延續之前的歷史,但是這種方式卻不會,是一個完完全全乾淨的git分支,沒有任何的提交!

你一定離不開的別名

不討論能節省大量敲擊時間的“git別名(git alias)”技巧的git文章一定都是在耍流氓。停止輸入冗長的命令,使用超級有用的別名吧!git別名可以加到.gitconfig檔案裡,或是使用命令(譯者注:本質就是改寫.gitconfig命令)來增加(括號部分替換為別名和對應的命令):

    git config --global alias.(name) "(command)"
  1. 在依賴分支的工作流程中,你常常要在不同分支間切換,每次敲擊節約你6個字母。

    co = checkout
    
  2. 在提交前瞧瞧你將要提交的都有什麼改動是一個好習慣,這可以幫助你發現拼寫錯誤、不小心的提交敏感資訊、將程式碼組織成符合邏輯的組。使用git add暫存你的改動,然後使用git ds檢視你將要提交的改動動。

    ds = diff --staged
    
  3. 你可能十分熟悉git輸出的詳細狀態資訊了,當到達一定境界時,你可能需要忽略所有那些描述,直擊問題的核心。這個別名輸出將輸出git status的簡短形式和分支的詳細資訊。

    st = status -sb
    
  4. 你是否在提交後才發現忘記git add某個檔案了,或是提交了才想再改動些啥?amend(修正)暫存區到最近的一次提交吧。(譯者注:這個命令不太好理解,–amend是重寫提交歷史,-C是重用某次提交的提交資訊。場景是當你提交完了發現還有些改動沒提交,又不想寫什麼“改動了X,再次提交”這種狗血的提交資訊。重新git add並git amend後,重用上次的提交資訊再次提交,替換上次的不完整提交。特別注意–amend重寫了提交,如果你已經push到遠端了,慎用這條命令!)

    amend = commit --amend -C HEAD
    
  5. 有時上面的修正可能不好使了,你需要undo(撤銷)。undo會回退到上次提交,暫存區也會回退到那次提交時的狀態。你可以進行額外的改動,用新的提交資訊來再次進行提交。

    undo = reset --soft HEAD^
    
  6. 維護一個多人編輯的程式碼倉庫常常意味著試著發現何人在改動什麼,這個別名可以輸出提交者和提交日期的log資訊。

    ls = log --pretty=format:`%C(yellow)%h %C(blue)%ad %C(red)%d %C(reset)%s %C(green) [%cn]` --decorate --date=short
    
  7. 這個別名用來在一天的開啟時回顧你昨天做了啥,或是在早晨重新整理你的記憶(括號內替換為自己的email)。

    standup = log --since `1 day ago` --oneline --author (YOUREMAIL)
    
  8. 一個複雜的倉庫可能很難用直線式的輸出來檢視,這個別名可以用圖表的形式向你展示提交是怎樣及何時被加到當前分支的。

    graph = log --graph --pretty=format:`%C(yellow)%h %C(blue)%d %C(reset)%s %C(white)%an, %ar%C(reset)`
    

譯者注:我根據上面的別名進行了一些整理修改,這是我現在的.gitconfig裡的別名配置:

[alias]
  st = status -sb
  co = checkout
  br = branch
  mg = merge
  ci = commit
  ds = diff --staged
  dt = difftool
  mt = mergetool
  last = log -1 HEAD
  latest = for-each-ref --sort=-committerdate --format="%(committername)@%(refname:short) [%(committerdate:short)] %(contents)"
  ls = log --pretty=format:"%C(yellow)%h %C(blue)%ad %C(red)%d %C(reset)%s %C(green)[%cn]" --decorate --date=short
  hist = log --pretty=format:"%C(yellow)%h %C(red)%d %C(reset)%s %C(green)[%an] %C(blue)%ad" --topo-order --graph --date=short
  type = cat-file -t
  dump = cat-file -p

via alimama mux
作者:Chris Kelly 譯者:棲邀
英文原文

相關文章