日常使用 Git 的 19 個建議

zaishaoyi發表於2015-12-16

我使用Git已經有4年之久,在這裡想分享一些實用的小技巧,希望能對大家有所幫助。

如果你對git一無所知,那麼我建議先去讀一下Git 常用命令速查。本篇文章主要適合有一定 git 使用基礎的人群。

目錄:

  1. 日誌輸出引數
  2. 檢視檔案的詳細變更
  3. 檢視檔案中指定位置的變更
  4. 檢視尚未合併(merge)的變更
  5. 檢視其他分支中的檔案
  6. 關於變更基線(rebase)的幾點說明
  7. 本地合併之後保留分支結構
  8. 修復而非新建提交
  9. 的三種狀態以及它們的相互轉換
  10. 優雅地回退
  11. 使用第三方工具檢視整個專案(而非單獨檔案)的變更
  12. 忽略空格變更
  13. 追加檔案中的部分變更
  14. 發現並清理無用分支
  15. 暫存部分檔案
  16. 如何寫好提交資訊
  17. 自動補全
  18. 建立常用命令的別名
  19. 快速定位問題版本

1. 日誌輸出引數

命令示例:

也許你用過git log。它支援很多命令列引數,將這些引數結合起來使用,功能尤為強大。下面是我經常使用的一些引數:

  • –author=“Alex Kras” ——只顯示某個使用者的提交任務
  • –name-only ——只顯示變更檔案的名稱
  • –oneline——將提交資訊壓縮到一行顯示
  • –graph ——顯示所有提交的依賴樹
  • –reverse ——按照逆序顯示提交記錄(最先提交的在最前面)
  • –after ——顯示某個日期之後發生的提交
  • –before ——顯示發生某個日期之前的提交

例如,曾經有位主管要求在每週五提交週報。所以我每週五都執行一下這個指令: git log --author="Alex Kras" --after="1 week ago" --oneline,然後將輸出結果編輯一下,傳送給主管稽核。

Git有很多命令列引數,使用起來非常方便。執行 man git log ,來看一下這些引數的作用。

如果這些都不好用,git還有一個 --pretty 引數,可以用來建立高階自定義輸出。

git-log-oneline

2. 檢視檔案的詳細變更

命令示例:

git log -p 或者 git log -p filename 不僅顯示提交說明、提交者以及提交日期,還會顯示這每次提交實際修改的內容。

然後你就可以使用 less 中常用的檢索命令即“斜槓”後面加檢索詞/{{在此處新增你的檢索詞}}),在變更內容日誌中檢索指定的關鍵詞(使用小寫的n跳到下一條檢索結果,大寫的N跳到上一條檢索結果)。

git-log-search

3. 檢視檔案中指定位置的變更

命令示例:

你可以使用 git blame filename 追查出檔案中每一行是由誰變更的。

git-blame

git blame 是一個非常強大的工具,但是又是無法提供足夠的資訊。

git log 提供了一個 -L 的選項。這個選項允許指定檔案中的某些行。Git只會輸出與這些行的變更日誌。這有點像帶焦點的 git log -p

git-log-lines

4. 檢視尚未合併的變更

命令示例:

如果你曾經與很多小夥伴工作在同一個持久分支上,也許會有這樣的經歷,父分支(例如:master)上的大量合併同步到你當前的分支。這使得我們很難分辨哪些變更時發生主分支,哪些變更發生在當前分支,尚未合併到master分支。

git log --no-merges master..可以解決這個問題。注意--no-merges 標誌意味著只顯示沒有合併到任何分支的變更,master..選項,意思是指顯示沒有合併到master分支的變更(在master後面必須有..)。

你也可以執行 git show --no-merges master.. 或者 git log -p --no-merges master.. 命令(輸出結果相同)來檢視一下尚未合併的檔案變更。

5. 檢視其他分支中的檔案

示例:

用這個命令可以很方便地檢視其他分支上的檔案而無需切換到那個分支。

當然你也可以通過 git show some-branch-name:some-file-name.js 命令在終端中顯示指定的檔案.

你還可以將輸出重定向到一個臨時檔案,這樣你就可以再指定的編輯器中,以並排檢視來檢視它了。

如果你想檢視另一個分支上檔案與當前分支上檔案的差異,只要執行下面的命令就可以了:

6.關於變更基線的幾點說明

示例:

之前我們說過在遠端分支上工作會有大量的合併提交。使用 git rebase 可以避免這些提交。

總的來說我認為變更基線是高階特徵,最好是留到另一篇文章中詳細介紹。

甚至在git book中也有這樣的論述:

但是,令人狂喜的變更基線並不是任何情況下都適用,一言以蔽之:

若是工作區中存在尚未提交到倉庫的變更,請不要使用變更基線。

如果遵照這條指南,不會有什麼問題。不然,你可能會招致厭惡與謾罵。

https://git-scm.com/book/en/v2/Git-Branching-Rebasing#The-Perils-of-Rebasing

也就是說,變更基線本身並不可怕,關鍵在於使用方式。

或許,最好的方法是使用互動式變更基線,呼叫命令為 git rebase -i {{某個提交序列號}}。執行這條命令,會開啟一個帶有自解釋指令的編輯器。由於變更基線不在本文的敘述範圍之內,我們就此而止,不再深究。

git-rebase-i

變更基線一個非常有用的特殊用法 git pull –rebase。

舉個例子,假設你正在master分支的一個本地版本上工作,你已經向倉庫提交了一小部分變更。與此同時,也有人向master分支提交了他一週的工作成果。當你嘗試推送本地變更時,git提示你需要先執行一下 git pull , 來解決衝突。作為一個稱職的工程師,你執行了一下 git pull ,並且git自動生成了如下的提交資訊。

Merge remote-tracking branch ‘origin/master’

儘管這不是什麼大問題,也完全安全,但是不太有利於歷史記錄的聚合。

這種情況下,git pull --rebase 是一個不錯的選擇。

這個命令會迫使git將遠端分支上的變更同步到本地,然後將尚未推送的提交重新應用到這個最新版本,就好象它們剛剛發生一樣。這樣就可以避免合併以及隨之而來的醜陋的合併資訊了。

7. 本地合併之後保留分支結構

示例:

我喜歡為每一個bug或者特徵建立一個新的分支。最大的好處就是,可以清楚地知道一系列的提交與某個任務的關係。如果你曾經合併過github 或者類似工具上的同步請求,那麼可以通過執行 git log --oneline --graph 顯示的檢視,清楚地看到合併分支的歷史。

如果你試圖合併一個本地分支到另一個本地分支,也許會注意到git將兩個分支平滑地銜接在一起,在git歷史中看到的是一條直線。。

如果你想強迫git儲存分支的歷史,與合併同步請求的狀況類似,你可以加一個 --no-ff 標誌, 最後可以看到非常清楚的提交歷史樹。

git merge –no-ff some-branch-name

git-merge-no-ff

8. 修復而非新建提交

示例:

這個指令顧名思義。

假設提交之後,你意識到自己犯了一個拼寫錯誤。你可以重新提交一次,並附上描述你的錯誤的提交資訊。但是,還有一個更好的方法:

如果提交尚未推送到遠端分支,那麼按照下面步驟簡單操作一下就可以了:

  1. 修復你的拼寫錯誤
  2. 將修正過的檔案暫存,通過git add some-fixed-file.js
  3. 執行 git commit –amend 命令,將會把最近一次的變更追加到你最新的提交。同時也會給你一個編輯提交資訊的機會。
  4. 準備好之後,將乾淨的分支推送到遠端分支。

如果你工作在自己的分支,甚至可以在已經推送之後修正提交,你需要使用 git push -f (-f 代表強制執行),這條指令可以重寫歷史。但是,不要試圖在一個很多人共同工作的分支(正如我們在變更基線那一部分討論的分支)上這樣做。此時,新建一次提交,在提交資訊中描述錯誤,應該是最好的補救措施。

9. Git 中的三種狀態以及它們之間的轉換

示例:

目前你或許已經瞭解,git中的檔案可以有三種不同的狀態:

  1. 沒有暫存
  2. 暫存並準備提交
  3. 已經提交

通過執行 git status可以看到關於檔案的描述以及檔案的狀態。 執行 git add filename.js 命令可以將檔案從未暫存狀態移動到暫存並準備提交的狀態, 或者使用 git add . 命令一次性暫存所有的檔案。

通過執行 git status -s 命令可以看到狀態圖,其中 -s 是簡短(short)的意思(個人認為),最終輸出結果如圖所示:

git-stages

顯然,git status 不顯示已經提交了的檔案,你可以使用 git log 命令來檢視。

若要將檔案在不同階段之間轉換,有很多可以用的命令供你選擇。

重置檔案

在git中,有3種型別的重置。重置是讓檔案回到git歷史中的一個特定版本。

  1. git reset –hard {{some-commit-hash}} —— 回退到一個特定的歷史版本。丟棄這次提交之後的所有變更。
  2. git reset {{some-commit-hash}}—— 回滾到一個特定的歷史版本。將這個版本之後的所有變更移動到“未暫存”的階段。這也就意味著你需要執行 git add . 和 git commit 才能把這些變更提交到倉庫.
  3. git reset –soft {{some-commit-hash}} ——回滾到一個特定的歷史版本。將這次提交之後所有的變更移動到暫存並準備提交階段。意味著你只需要執行 git commit 就可以把這些變更提交到倉庫。

這些命令似乎並沒有什麼用處,但當你嘗試著將檔案在不同版本間移動時,使用它們會非常方便。

我平時使用重置的一些用例如下:

  1. 如果想清除變更記錄,可以使用清理命令——git reset –hard HEAD (最常用)
  2. 如果想編輯,以不同的順序,重新暫存,重新提交檔案—— git reset {{some-start-point-hash}}
  3. git reset –soft {{some-start-point-hash}}如果想把之前3次的提交,作為一次提交 git reset –soft {{some-start-point-hash}}

簽出部分檔案

如果你想取消某些檔案在本地的變更,而同時保留另外一些檔案在本地的變更,一個比較簡單的方法是通過 git checkout forget-my-changes.js簽出那些你想取消本地的變更的檔案。

正如前面提到的那樣,你也可以從其他分支或者之前的提交中籤出檔案的不同版本。

git checkout some-branch-name file-name.js 和 git checkout {{some-commit-hash}} file-name.js

你應該注意到了簽出的檔案處於“暫存並準備提交”的狀態。如果想回到未暫存的狀態,需要執行一下 git reset HEAD file-name.js。然後再次執行 git checkout file-name.js,檔案回到了初始狀態。

注意,執行 git reset –hard HEAD file-name.js 不起作用。總而言之,在git的不同階段之間移動有點複雜,沒有一個清晰的模式,我希望能通過這一部分有所改觀。

10. 撤銷而不產生提交資訊

示例:

如果打算撤銷之前一次或者兩次的提交,檢視這些提交都做了哪些變更,哪些變更又有可能引發問題,這個命令非常方便。

通常,git revert 會自動將回退的檔案提交到倉庫,需要你寫一個新的提交資訊。-n 標誌告訴git先別急著提交,因為我只是想看一眼罷了。

11.用第三方差異工具檢視整個工程而非單個目錄的差異

示例:

我最喜歡的差異工具是Meld。我在使用Linux的時候就開始使用它,並且一直持續到現在。

我並不是要推銷Meld。假設你已經選好了比較工具,並且git能夠將它作為一個合併和差異工具使用。接下來需要執行一下下面的命令,注意用你選擇的差異工具的名字代替Meld:

之後你就可以執行s run git difftool some-file.js 來檢視檔案的差異了。

但是,有些比較工具(例如meld)支援全路徑比較。

如果你呼叫 git difftool 時加 -d 標誌,將會對整個資料夾進行比較。有時會非常有用。

git difftool -d

git-difftool-d

12. 忽略空格變更

示例:

你是否遇到這樣的情況:直到git blame 顯示你應該為檔案中的一切負責時才意識到自己重新調整了檔案的縮排或者格式?

結果證明,git足夠聰明來分辨檔案中不同型別的變更。你可以呼叫許多命令(例如::git diff, git blame),加一個-w 標誌,git將會忽略空白的變更。

13. 追加檔案中的部分變更

示例:

在git上一定有些人非常喜歡-p 標誌,因為它總是帶來某些非常方便的功能。

使用 git add,允許你互動地選擇你想要提交的內容。這樣你就可以以簡單易讀的方式按照一定的邏輯組織提交。

git-add-p

14. 發現並清理無用分支

示例:

倉庫中存在大量遠端分支的現象非常常見,甚至其中某些分支已經被合併到了master分支。如果你跟我一樣有潔癖(至少有程式碼潔癖),這些分支可能會令你難以忍受。

你可以通過執行git branch檢視所有的遠端分支,還可以帶有 -a 標誌(顯示所有的分支),或者帶上 –merged標誌 只顯示那些完全合併到master分支的分支。

你或許首先想到的是執行git fetch -p (獲取和清除舊資料),來確保你的資料是最新的。

git-branch-a-merged

如果你要獲取真正的fancy,你可以得到一個所有遠端分支的列表,以及這些分支最後一次提交的列表,通過執行:

git for-each-ref –sort=committerdate –format=’%(refname:short) * %(authorname) * %(committerdate:relative)’ refs/remotes/ | column -t -s ‘*’.

fancy-branch-view

不幸地是,沒有簡單的方法(至少我不知道)可能只顯示合併過的分支。所以你可能不得不比較兩個輸出或者寫一個指令碼來做這些事情。

15. 暫存部分檔案

示例:

如果你還不瞭解 git stash 的功能,只是把當前工作區中的變更儲存到一個有序的“git stack”。之後你可以用 git stash pop ,恢復你的變更。你也可以使用 git stash list 檢視git棧裡面你做的所有備份。通過 man git stash 檢視更多可以用的選項。

常規 git stash 的一個限制是它會一下暫存所有的檔案。有時,只備份某些檔案更為方便,讓另外一些與程式碼庫保持一致。

還記得神奇的 -p命令嗎?是的,它與 git stash 一起用會非常方便。你現在或許已經猜到了,它會詢問你想備份哪些檔案的變更。

為了確認一下,點選 ? 你可以看到所有可用的選項。

git-stash-p

另一個非常有用的技巧,用來備份部分檔案:

  1. add 那些你不想備份的檔案(例如: git add file1.js, file2.js)
  2. 呼叫 git stash –keep-index。只會備份那些沒有被add的檔案。
  3. 呼叫 git reset 取消已經add的檔案的備份,繼續自己的工作。

16. 寫好提交資訊

剛剛讀過一篇很好的文章,關於如何寫好提交資訊,點選這個連結閱讀:How to Write a Git Commit Message

有一個規則真的是為我量身訂做的:“每一個好的提交應該能完善下面的這個句子”

應用到實際中,提交資訊應該是這樣的:{{你的提交資訊}}

例如:

—應用這次提交,可以更新**README檔案**

—應用這次提交:為呼叫GET/user/:id API追加確認

—應用這次提交:會回退到**12345版本**

17. Git 自動補全

某些作業系統(例如:Ubuntu)的git包自帶並且預設開啟自動補全。如果你的作業系統沒有這個功能(Mac就沒有),你可以按照下面的指南為自己新增。

https://git-scm.com/book/en/v1/Git-Basics-Tips-and-Tricks#Auto-Completion

18. 建立常用命令的別名

常用的較長的git命令應該使用git或者bash別名

使用Git最好的方式是通過命令列,學習命令列的最好方式就是先用最困難的方法做每一件事。(把一切都列印出來)。

然而,一段時間之後,最好將你常用的命令總結出來,為它們建立一個簡單的別名。

Git 支援別名,例如,你可以執行一下下面的命令:

這個命令列會建立一個新的git別名l,你可以使用:

git l 代替 git log –oneline –graph。

注意你可以在alias後面附加其他的引數(例如:git l –author =“Alex”)

其他的選項,是好的就得Bash別名

例如,在我的.bashrc檔案中有下面的詞條:

Alias gil=”git log –oneline -graph”,允許我使用gil代替長命令,甚至比git l還要少兩個字母

19. 快速定位故障版本

示例:

git bisect 使用分治演算法查詢出錯版本號。

假設休假一週回來,你看了一下最新程式碼,發現走之前完全正常的程式碼現在出問題了。

你檢視了一下休假之前最後一次提交的程式碼,功能尚且正常。不幸的是,你離開的這段時間,已經有上百次提交記錄,你無法找到那一次提交導致了這個問題。

pulling-out-hair

這時你或許想找到破壞功能的bug,然後對該檔案使用git blame 命令,找出並指責破壞者。

如果bug很難定位,那麼或許你可以去看一下提交歷史,試一下看能不能找到出問題的版本。

另一種快捷的方式則是使用git bisect,可以快速找到出問題的版本。

那麼git bitsect是如何做的呢?

指定了已知的正常版本和問題版本之後,git bisectit bisect會把指定範圍內的提交資訊從中間一分為二,並會根據最中間的提交資訊建立一個新的分支, 你可以檢查這個版本是否有問題。

假設這個中間版本依然可以正常執行。你可以通過git bisect good命令告訴git。然後,你就只有剩下的一半的版本需要測試。

Git會繼續分割剩下的版本,將中間版本再次到處讓你測試。

Git bisect會繼續用相似的方式縮小版本查詢範圍,直到第一個出問題的版本被找到。

因為你每次將版本分為兩半,所以可以用log(n)次查詢到問題版本(時間複雜度為“big O”,非常快)。

執行整個git bisect的過程中你會用到的所有命令如下:

  1. git bisect start ——通知git你開始二分查詢。
  2. git bisect good {{some-commit-hash}} ——反饋給git 這個版本是沒有問題的(例如:你休假之前的最後一個版本)。
  3. git bisect bad {{some-commit-hash}} ——告訴git 已知的有問題的版本(例如master分支中的HEAD)。git bisect bad HEAD (HEAD 代表最新版本)。
  4. 這時git 會簽出中間版本,並告訴你去測試這個版本。
  5. git bisect bad ——通知git當前版本是問題版本。
  6. git bisect good ——通知git當前簽出的版本沒有問題。
  7. 當找到第一個問題版本後,git會告訴你。這時, git bisect 結束了。
  8. git bisect reset——返回到 git bisect程式的初始狀態(例如,master分支的HEAD版本)。
  9. git bisect log ——顯示最後一次完全成功的 git bisect日誌。

你也可以給git bisect提供一個指令碼,自動執行這一過程。詳細內容請點選: http://git-scm.com/docs/git-bisect#_bisect_run

相關文章