我使用Git已經有4年之久,在這里想分享一些實用的小技巧,希望能對大家有所幫助。
如果你對git一無所知,那么我建議先去讀一下Git 常用命令速查。本篇文章主要適合有一定 git 使用基礎的人群。
目錄:
- 日志輸出參數
- 查看文件的詳細變更
- 查看文件中指定位置的變更
- 查看尚未合並(merge)的變更
- 查看其他分支中的文件
- 關於變更基線(rebase)的幾點說明
- 本地合並之后保留分支結構
- 修復而非新建提交
- 的三種狀態以及它們的相互轉換
- 優雅地回退
- 使用第三方工具查看整個項目(而非單獨文件)的變更
- 忽略空格變更
- 追加文件中的部分變更
- 發現並清理無用分支
- 暫存部分文件
- 如何寫好提交信息
- 自動補全
- 創建常用命令的別名
- 快速定位問題版本
1. 日志輸出參數
命令示例:
git log --oneline --graph
也許你用過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 參數,可以用來創建高級自定義輸出。
2. 查看文件的詳細變更
命令示例:
git -log -p filename
git log -p 或者 git log -p filename 不僅顯示提交說明、提交者以及提交日期,還會顯示這每次提交實際修改的內容。
然后你就可以使用 less 中常用的檢索命令即“斜杠”后面加檢索詞/{{在此處添加你的檢索詞}}),在變更內容日志中檢索指定的關鍵詞(使用小寫的n跳到下一條檢索結果,大寫的N跳到上一條檢索結果)。
3. 查看文件中指定位置的變更
命令示例:
你可以使用 git blame filename 追查出文件中每一行是由誰變更的。
git blame 是一個非常強大的工具,但是又是無法提供足夠的信息。
git log 提供了一個
-L 的選項。這個選項允許指定文件中的某些行。Git只會輸出與這些行的變更日志。這有點像帶焦點的
git log -p 。
4. 查看尚未合並的變更
命令示例:
git log --no-merges master..
如果你曾經與很多小伙伴工作在同一個持久分支上,也許會有這樣的經歷,父分支(例如: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:some-file.js
用這個命令可以很方便地查看其他分支上的文件而無需切換到那個分支。
當然你也可以通過 git show some-branch-name:some-file-name.js 命令在終端中顯示指定的文件.
你還可以將輸出重定向到一個臨時文件,這樣你就可以再指定的編輯器中,以並排視圖來查看它了。
如果你想查看另一個分支上文件與當前分支上文件的差異,只要運行下面的命令就可以了:
git diff some-branch some-filename.js
示例:
之前我們說過在遠程分支上工作會有大量的合並提交。使用 git rebase 可以避免這些提交。
總的來說我認為變更基線是高級特征,最好是留到另一篇文章中詳細介紹。
甚至在git book中也有這樣的論述:
但是,令人狂喜的變更基線並不是任何情況下都適用,一言以蔽之:
若是工作區中存在尚未提交到倉庫的變更,請不要使用變更基線。
如果遵照這條指南,不會有什么問題。不然,你可能會招致厭惡與謾罵。
https://git-scm.com/book/en/v2/Git-Branching-Rebasing#The-Perils-of-Rebasing
也就是說,變更基線本身並不可怕,關鍵在於使用方式。
或許,最好的方法是使用交互式變更基線,調用命令為 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
8. 修復而非新建提交
示例:
這個指令顧名思義。
假設提交之后,你意識到自己犯了一個拼寫錯誤。你可以重新提交一次,並附上描述你的錯誤的提交信息。但是,還有一個更好的方法:
如果提交尚未推送到遠程分支,那么按照下面步驟簡單操作一下就可以了:
- 修復你的拼寫錯誤
- 將修正過的文件暫存,通過git add some-fixed-file.js
- 運行 git commit –amend 命令,將會把最近一次的變更追加到你最新的提交。同時也會給你一個編輯提交信息的機會。
- 准備好之后,將干凈的分支推送到遠程分支。
如果你工作在自己的分支,甚至可以在已經推送之后修正提交,你需要使用 git push -f (-f 代表強制執行),這條指令可以重寫歷史。但是,不要試圖在一個很多人共同工作的分支(正如我們在變更基線那一部分討論的分支)上這樣做。此時,新建一次提交,在提交信息中描述錯誤,應該是最好的補救措施。
9. Git 中的三種狀態以及它們之間的轉換
示例:
目前你或許已經了解,git中的文件可以有三種不同的狀態:
- 沒有暫存
- 暫存並准備提交
- 已經提交
通過運行 git status可以看到關於文件的描述以及文件的狀態。 運行 git add filename.js 命令可以將文件從未暫存狀態移動到暫存並准備提交的狀態, 或者使用 git add . 命令一次性暫存所有的文件。
通過運行 git status -s 命令可以看到狀態圖,其中 -s 是簡短(short)的意思(個人認為),最終輸出結果如圖所示:
顯然,git status 不顯示已經提交了的文件,你可以使用 git log 命令來查看。
若要將文件在不同階段之間轉換,有很多可以用的命令供你選擇。
重置文件
在git中,有3種類型的重置。重置是讓文件回到git歷史中的一個特定版本。
- git reset –hard {{some-commit-hash}} —— 回退到一個特定的歷史版本。丟棄這次提交之后的所有變更。
- git reset {{some-commit-hash}}—— 回滾到一個特定的歷史版本。將這個版本之后的所有變更移動到“未暫存”的階段。這也就意味着你需要運行 git add . 和 git commit 才能把這些變更提交到倉庫.
- git reset –soft {{some-commit-hash}} ——回滾到一個特定的歷史版本。將這次提交之后所有的變更移動到暫存並准備提交階段。意味着你只需要運行 git commit 就可以把這些變更提交到倉庫。
這些命令似乎並沒有什么用處,但當你嘗試着將文件在不同版本間移動時,使用它們會非常方便。
我平時使用重置的一些用例如下:
- 如果想清除變更記錄,可以使用清理命令——git reset –hard HEAD (最常用)
- 如果想編輯,以不同的順序,重新暫存,重新提交文件—— git reset {{some-start-point-hash}}
- 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
12. 忽略空格變更
示例:
你是否遇到這樣的情況:直到git blame 顯示你應該為文件中的一切負責時才意識到自己重新調整了文件的縮進或者格式?
結果證明,git足夠聰明來分辨文件中不同類型的變更。你可以調用許多命令(例如::git diff, git blame),加一個-w 標志,git將會忽略空白的變更。
13. 追加文件中的部分變更
示例:
在git上一定有些人非常喜歡-p 標志,因為它總是帶來某些非常方便的功能。
使用 git add,允許你交互地選擇你想要提交的內容。這樣你就可以以簡單易讀的方式按照一定的邏輯組織提交。
14. 發現並清理無用分支
示例:
倉庫中存在大量遠程分支的現象非常常見,甚至其中某些分支已經被合並到了master分支。如果你跟我一樣有潔癖(至少有代碼潔癖),這些分支可能會令你難以忍受。
你可以通過運行git branch查看所有的遠程分支,還可以帶有 -a 標志(顯示所有的分支),或者帶上 –merged標志 只顯示那些完全合並到master分支的分支。
你或許首先想到的是運行git fetch -p (獲取和清除舊數據),來確保你的數據是最新的。
如果你要獲取真正的fancy,你可以得到一個所有遠程分支的列表,以及這些分支最后一次提交的列表,通過運行:
git for-each-ref –sort=committerdate –format=’%(refname:short) * %(authorname) * %(committerdate:relative)’ refs/remotes/ | column -t -s ‘*’.
不幸地是,沒有簡單的方法(至少我不知道)可能只顯示合並過的分支。所以你可能不得不比較兩個輸出或者寫一個腳本來做這些事情。
15. 暫存部分文件
示例:
如果你還不了解 git stash 的功能,只是把當前工作區中的變更保存到一個有序的“git stack”。之后你可以用 git stash pop ,恢復你的變更。你也可以使用 git stash list 查看git棧里面你做的所有備份。通過 man git stash 查看更多可以用的選項。
常規 git stash 的一個限制是它會一下暫存所有的文件。有時,只備份某些文件更為方便,讓另外一些與代碼庫保持一致。
還記得神奇的 -p命令嗎?是的,它與 git stash 一起用會非常方便。你現在或許已經猜到了,它會詢問你想備份哪些文件的變更。
為了確認一下,點擊 ? 你可以看到所有可用的選項。
另一個非常有用的技巧,用來備份部分文件:
- add 那些你不想備份的文件(例如: git add file1.js, file2.js)
- 調用 git stash –keep-index。只會備份那些沒有被add的文件。
- 調用 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 log –oneline –graph。
注意你可以在alias后面附加其他的參數(例如:git l –author =“Alex”)
其他的選項,是好的就得Bash別名
例如,在我的.bashrc文件中有下面的詞條:
Alias gil=”git log –oneline -graph”,允許我使用gil代替長命令,甚至比git l還要少兩個字母
19. 快速定位故障版本
示例:
假設休假一周回來,你看了一下最新代碼,發現走之前完全正常的代碼現在出問題了。
你查看了一下休假之前最后一次提交的代碼,功能尚且正常。不幸的是,你離開的這段時間,已經有上百次提交記錄,你無法找到那一次提交導致了這個問題。
這時你或許想找到破壞功能的bug,然后對該文件使用git blame 命令,找出並指責破壞者。
如果bug很難定位,那么或許你可以去看一下提交歷史,試一下看能不能找到出問題的版本。
另一種快捷的方式則是使用git bisect,可以快速找到出問題的版本。
那么git bitsect是如何做的呢?
指定了已知的正常版本和問題版本之后,git bisectit bisect會把指定范圍內的提交信息從中間一分為二,並會根據最中間的提交信息創建一個新的分支, 你可以檢查這個版本是否有問題。
假設這個中間版本依然可以正常運行。你可以通過git bisect good命令告訴git。然后,你就只有剩下的一半的版本需要測試。
Git會繼續分割剩下的版本,將中間版本再次到處讓你測試。
Git bisect會繼續用相似的方式縮小版本查找范圍,直到第一個出問題的版本被找到。
因為你每次將版本分為兩半,所以可以用log(n)次查找到問題版本(時間復雜度為“big O”,非常快)。
運行整個git bisect的過程中你會用到的所有命令如下:
- git bisect start ——通知git你開始二分查找。
- git bisect good {{some-commit-hash}} ——反饋給git 這個版本是沒有問題的(例如:你休假之前的最后一個版本)。
- git bisect bad {{some-commit-hash}} ——告訴git 已知的有問題的版本(例如master分支中的HEAD)。git bisect bad HEAD (HEAD 代表最新版本)。
- 這時git 會簽出中間版本,並告訴你去測試這個版本。
- git bisect bad ——通知git當前版本是問題版本。
- git bisect good ——通知git當前簽出的版本沒有問題。
- 當找到第一個問題版本后,git會告訴你。這時, git bisect 結束了。
- git bisect reset——返回到 git bisect進程的初始狀態(例如,master分支的HEAD版本)。
- git bisect log ——顯示最后一次完全成功的 git bisect日志。
你也可以給git bisect提供一個腳本,自動執行這一過程。詳細內容請點擊: http://git-scm.com/docs/git-bisect#_bisect_run