[譯] 相見恨晚的 Git 命令

我輩開發者使用最多的技術既不是 JavaScript, 也不是 Python 或者 HTML。 它甚至在面試中都不多被提到,也不多被列入工做的必備技術棧。html

沒錯,我說的正是 Git 和版本控制。node

長久以來,咱們大部分開發人員只學過一點點 Git 的概念。 這些知識僅僅能讓咱們擁有在一個小團隊內使用簡單功能分支工做流的能力。 若是你也像我同樣,這種狀態將會伴隨你的職業生涯好久。git

是時候再訪 Git,從新審視一下掌握它對咱們職業生涯的提高有多麼重要。 本指南能夠做爲一篇參考,它包含了一些我認爲非常重要但可能不爲人知的概念。 掌握 Git 以後,你管理代碼的方式以及天天的工做流將會發生巨大的改變。 因爲 Git 命令有些陳舊而且難以記憶,所以本文將會按照概念和預期表現進行分解。github

若是你對 Git 的基本概念掌握的不夠牢固,好比工做目錄、本地倉庫和遠程倉庫之間的區別, 那麼建議你能夠先閱讀這篇指南。 一樣,若是沒有掌握 Git 的基本命令,能夠從官方文檔開始學習。 本文並不意味着會帶你從一個完全的新手變成專業人員, 而是默認你已經熟練掌握如何使用 Git。面試

Git 命令

日誌 (Logging)

我剛乾哈了

$ git log
$ git log --oneline # 更爲精煉的輸出
$ git log --graph # 以分支的可視化圖顯示
複製代碼

查看你的撤銷歷史

$ git reflag
複製代碼

由於有時 git log 命令沒法捕捉到撤銷的命令, 特別是對於那些沒法在 commit 歷史裏顯示的命令。shell

在你運行了相似 git rebase 這樣的「危害型」命令後, reflag 基本上能夠算是一層安全網。 你不只能夠看到以前所作的 commit, 並且還將看到致使 commit 的每個過程。 看這篇 Atlassian 上的文章 來了解更多關於 refs 運做方式。json

查看當前狀態 + 任何合併衝突

$ git status
複製代碼

雖然 git status 是一個很是基礎的命令,咱們很早以前就學過, 可是,因爲它做爲 Git 內部基本原理的學習工具,其重要程度仍然值得 咱們重複學習。 它還能夠幫助你瀏覽複雜的 rebase 和 merge 過程。安全

對比 staged (或者 unstaged) 中的異同

$ git diff --staged # staged 的改變
$ git diff # unstaged 的改變
複製代碼

對比兩個分支之間的異同

$ git diff branch1..branch2
複製代碼

導航 (Navigation)

我想看看我以前幹哈了

$ git reset <commit-sha>
複製代碼

這條命令將會撤銷對應 commit,而且取消那次 commit 中的 stage 操做, 可是那些文件仍然保留在工做目錄。bash

我想切換到別的分支

$ git switch branch-name # Git 2.23 中的新語法
$ git checkout branch-name # 經典語法
複製代碼

git checkout 可能會有些讓人難以理解,由於他既能夠工做在文件層級, 也能夠工做在分支層級。 從 Git 2.23 開始,咱們擁有了兩個新的命令:app

  • git restore 用來 checkout 文件
  • git switch 用來 checkout 分支

(譯者注:詳細可訪問 官方文檔Stack Overflow 相關提問)

若是你想避免 git checkout 形成的困擾,上面兩個命令很是適合你。

我想回到以前我在的分支

$ git switch -
複製代碼

修改 (Modifications)

我把本身挖進了兔子洞,讓咱們從新開始

(譯者注: get 不到這個梗。。)

$ git reset --hard HEAD
複製代碼

這條命令將重置本地目錄到最近一次 commit 的狀態,而且會放棄全部 unstage 的文件。

我想把一個文件重置到以前的樣子

$ git restore <filename> # Git 2.23 新語法
$ git checkout -- <filename> # 經典語法
複製代碼

我想撤銷上一次 commit 而且重寫歷史

$ git reset --hard HEAD~1
複製代碼

我想回到 n 次 commit 以前

$ git reset --hard HEAD~n # 回到倒數第 n 次 commit
$ git reset --hard <commit-sha> # 或者回到特定的某次提交
複製代碼

softmixedhard 三種 reset 的不一樣:

  • --soft:撤銷 commit 可是工做目錄中會保留更改
  • --mixed (默認):撤銷 commit,撤銷當次 commit 的 stage,可是工做目錄中會保留更改
  • --hard:撤銷 commit,撤銷當次 commit 的 stage,而且刪除更改

我已經重寫了歷史記錄,如今想把這些改變 push 到遠程倉庫

$ git push -f
複製代碼

只要你的本地倉庫和遠程倉庫有差別,這一步都是必要的。

WARNING:強制 push 須要格外當心。通常來講, 在共享的分支你應該避免任何的強制 push。在開啓一個 pull 請求以前, 你應將強制 push 限制在你本身的分支內,以避免在不經意間弄亂你隊友的 git 歷史。

我想爲上一次 commit 多加一些改變

$ git commit --amend
複製代碼

我想重寫本地的一堆 commit

$ git rebase -i <commit hash> # commit hash 是全部你想改變的 commit 以前的一個 commit 的 hash 
複製代碼

這條命令將開啓一個互動提示,你能夠經過它來選擇保持、壓縮、刪除 哪個 commit。你也能夠在這裏改變 commit message。 好比在清理錯字或者規範化 commit 時,它很是有用。

當深刻學習 Git 以後,我發現 rebasing 是很是使人困惑的主題之一。 查看這個 rebasing 文檔瞭解更多。

這個 rebase 垮了,報廢掉它吧

$ git rebase --abort
複製代碼

你能夠在 rebase 過程當中使用這條命令。

我發現,rebase 帶來的麻煩老是超過他的價值,特別是在 rebase 兩個 有着大量相同更改的分支的時候。在完成整個 rebase 以前, 你均可以讓這個 rebase 流產。

我想從另外一個分支把一個 commit 帶到當前分支

# 將 commit-sha 所指的 commit 帶入當前分支
$ git cherry-pick <commit-sha>
複製代碼

我想從另外一個分支把一個指定的文件帶到當前分支

$ git checkout <branch-name> <filename>
複製代碼

(譯者注:這個彷彿不能用 git restore,git restore 更傾向於重置和恢復)

我想在版本控制中中止追蹤某個文件

$ git rm --cached <file-name>
複製代碼

我須要更換分支,但當前狀態已有更改

$ git stash # 將已有更改保存在 stash 棧的棧頂
$ git stash save "對於更改的信息描述"
$ git stash -u # 同時也 stash 未被追蹤 (untracked) 的文件
複製代碼

我想看看個人 stash 裏有啥

$ git stash list
複製代碼

我想把 stash 裏的東西取出來

$ git stash pop # 彈出最近添加到 stash 棧的項目
$ git stash apply stash@{stash_index} # 申請取出指定項目能夠用 git stash list 查看
複製代碼

我想撤銷一次 commit 而不重寫歷史

$ git revert HEAD # 撤銷最近一次 commit
$ git revert <commit-sha> # 撤銷指定 commit
複製代碼

這條命令將從新運行提交新的 commit 時的逆過程, 從而撤銷你的更改而不會撤銷歷史記錄。 在共享的分支中撤銷 commit 時,重寫歷史記錄會很是的複雜, 因此使用 git revert 是一種很安全的解決方式。

清理 (Cleanup)

我去,咋有這麼多分支

$ git branch --no-color --merged | command grep -vE "^(\+|\*|\s*(master|develop|dev)\s*$)" | command xargs -n 1 git branch -d
複製代碼

這條命令將刪除本地除了 master、develop、dev 以外的全部已合併的分支, 若是你的主分支和 dev 分支有着另外的名字, 你能夠改變相應的 grep 的正則。

這條命令很長,不太好記,但你能夠爲它設置一個別名,就像這樣:

$ alias gbda='git branch --no-color --merged | command grep -vE "^(\+|\*|\s*(master|develop|dev)\s*$)" | command xargs -n 1 git branch -d'
複製代碼

若是你在使用 Oh My Zsh ,這一步它已經爲你完成。 查看 aliases 瞭解更多。

來清理舊分支和無效 commit 吧~

$ git fetch --all --prune
複製代碼

若是你已經爲遠程倉庫設置了在 merge 時刪除分支,這條命令也很是有用。

(譯者注:git fetch --prune 將會在 fetch 前 移除在本地的全部遠程倉庫中再也不存在的遠程跟蹤引用,詳見官方文檔)

Aliases

Git 命令有時會很長,不太容易記住。咱們不想每次都把它們敲一遍或者 花費幾天將它們背下來,所以我強烈建議你爲它們設置 Git 別名。

更方便的方式是,安裝一個像 Z Shell (Zsh) 中的 Oh My Zsh 同樣的工具。 這樣一來,你將擁有一大堆最經常使用的 Git 命令的別名。 你可使用 別名 + tab 來補全他們。我懶得按照個人喜愛設置 shell, 因此我喜歡用一些相似 Oh My Zsh 的開源工具,它們能夠幫我配置好~ 更不用說它們還有這漂亮的外觀了~

我天天用的最多的一些命令:

$ gst - git status
$ gc - git commit
$ gaa - git add --all
$ gco - git checkout
$ gp - git push
$ gl - git pull
$ gcb - git checkout -b
$ gm - git merge
$ grb - git rebase
$ gpsup - git push --set-upstream origin $(current_branch)
$ gbda - git branch --no-color --merged | command grep -vE "^(\+|\*|\s*(master|develop|dev)\s*$)" | command xargs -n 1 git branch -d
$ gfa - git fetch --all --prune
複製代碼

若是你忘記了這些或者其餘你本身設置的別名,能夠運行:

$ alias
複製代碼

或者給出關鍵詞進行搜索:

$ alias grep <alias-name>
複製代碼

其餘 Git 技巧

忽視文件 (Ignoring Files)

不少文件可能不須要存在於版本控制中,你能夠設置全局 gitignore 文件 來忽視掉它們。須要忽視的文件多是一些 node_modules 文件夾, .vscode 或其餘 IDE 的文件,以及一些 Python 的虛擬環境。

對於一些敏感信息,你可使用環境變量文件存放,而後將它們添加到 項目根目錄下的 .gitignore 文件中。

特殊文件 (Special Files)

你可能須要將一些文件標記爲二進制文件,以便於 Git 能夠將其忽視, 而且不用爲它們產生冗長的差別性檢測。Git 有一個 .gitattributes 文件 來實現這一操做。好比在一個 JavaScript 項目中, 你會在 .gitattributes 中添加一個 yarn-lock.json 或者 package-lock.json,這樣一來,在你每次更新時, Git 不用每次都嘗試記錄它們的差別變化。

# .gitattributes
package-lock.json binary
複製代碼

Git 工做流

Rebase vs. Merge

你的團隊可能會從 rebase 和 merge 兩種工做流中二選其一, 兩者都有利弊,我曾見過這兩種方式均可以產生很高的效率。 對於大多數狀況,除非你 真的 瞭解你正在作什麼, 不然選擇 merge 工做流就完事了。

當你主要使用 merge 來爲產品更新迭代時, 仍然也能夠高效的使用 rebase。 最多見的場景是,你正在一個 feature 上工做, 同時另一個開發者 pull 了一個別的 feature 到 master。 你確實可使用 git merge 將那些改變一塊兒帶上, 可是這樣,你會對隊友作的簡單更改有一個額外的 commit。 你真正想作的是將你的提交 從新提交 到最新的 master 的最上面。

$ git rebase master
複製代碼

這條命令將給你一個更乾淨的 commit 歷史。

深度解釋它們之間的不一樣點可能須要一整篇論文來闡述, 所以,我建議你能夠查閱 the Atlassian docs 中有關這些差別的文章。

遠程倉庫設置 (Remote Repository Setting)

我對於 GitHub 和 GitLab 最爲熟悉,可是其餘遠程倉庫管理器也 應該支持這些設置。

1. 在 merge 時刪除分支

一旦有分支被 merge,你就不該該在關心這個分支, 由於它的歷史將被映射到你的 master/dev 分支。 這一舉措會顯著的減小你所管理的分支數量。 也能夠經過使用 git fetch -all --prune 更爲高效的保持本地倉庫的乾淨整潔。

2. 防止直接 push 到 master

若是沒有這個設置,很容易在 git push 時,忘記本身正在 master, 這會潛在的破壞你的產品,一點也很差。

3. merge 前至少須要一次確認

取決於團隊的大小,你可能須要在 merge 前須要屢次的確認, 即便只是一個二人團隊,最少也要確認一次。 你不用花費幾個小時每行都看,但通常來講, 你的代碼應該至少有兩我的看過。 反饋 是學習和我的提高的關鍵。

4. merge 前須要經過 CI 測試

(譯者注:CI 即 Continuous Integration,持續集成)

已損壞的改變不該該被 merge 到產品中。 測試人員沒法 100% 的捕捉損壞的更改,所以須要自動執行這些檢測。

Pull 請求 (Pull Requests)

保持 Pull 請求小而簡潔,理想狀況下不超過幾百行。 小而頻繁的 Pull 請求會使得審閱過程更快,從而產生更多的無 bug 代碼, 也會讓你的隊友更輕鬆,從而提高團隊的效率,也更容易分享學習經驗。 團隊內部達成一個共同的承諾,承諾天天都花一些時間來審閱公開 Pull 請求。

咱們都愛這樣的審閱: ![reviewing]res.cloudinary.com/practicalde…)

若是你正在實現的特性在一段時間內都會處於損壞狀態, 請使用特性標籤來在產品中禁用它。 這將會防止你的特性分支和 dev/master 分支產生太大差別, 同時也容許你作更頻繁,更小的 Pull 請求。不 merge 代碼的時間越長, 之後 merge 的難度就越大。

最後,在你的 Pull 請求中放一個細節描述,若是必要的話, 能夠放圖片或者 GIF。若是你使用像 Jira 這樣的工具管理票據 (tickets,譯者注:沒使過不太懂啥意思), 描述中也能夠包括 Pull 請求地址的票據的編號。 Pull 請求的描述和可視化作的越詳細,可能你的隊友就越想審閱你的代碼, 而不是拖延着下次必定。

分支命名 (Branch Naming)

你的團隊可能會提出分支命名的規範,以便於導航。 我喜歡每一個分支以建立人的名字首字母開頭,接着一個左斜槓, 而後是以短橫線鏈接的分支描述。

這可能看起來微不足道,可是配合 tab 補全以及相似 grep 這樣的工具一塊兒使用, 這確實能夠幫你找到並理解可能有的全部分支。

例如,我建立了一個新分支:

$ git checkout -b gabud/implement-important-feature
複製代碼

一週後,當我忘記我以前給它起了什麼名字, 我能夠鍵入 git checkout gabud,而後按 tab 鍵, 個人 Z shell 就會向我展現全部我本地的分支以供選擇, 而不用看我隊友們的分支。

提交信息 (Commit Messages)

語言很重要,通常來講,我發現最好不要以破碎狀態提交東西, 每一次提交都應該有一個簡潔的信息,說明所作的更改。 按照官方 Git 建議,我發現最好使用當前的命令式意義來提交信息。 能夠將每一個提交信息視爲對 計算機/git 的命令, 以致於你能夠將提交信息加到這句話後面:

若是這個 commit 被應用,將會...

在當前命令式意義上,良好的提交示例爲:

$ git commit -m "Add name field to checkout form"
複製代碼

如今能夠這麼讀:「若是這個 commit 被應用,將會在表單中添加姓名域。」

最後的想法

這毫不是已經瞭解了 Git 的所有,建議查閱 官方文檔git help 瞭解更多。 不要懼怕向你的隊友問一些 Git 的問題, 你會驚訝的發現大多數隊友也有許多相同的問題。

那麼你呢?哪一個 Git 命令或者概念在你的工做流中最有用呢~

相關文章
相關標籤/搜索