大家好,我是若川。今天分享一篇關于git的好文章。我自己經常用命令行終端和git縮寫。具體可以看我以往的文章。使用 ohmyzsh 打造 windows、ubuntu、mac 系統高效終端命令行工具,用過都說好。
點擊下方卡片關注我、加個星標
學習源碼整體架構系列、年度總結、JS基礎系列
前言
最近在網上有個真實發生的案例比較火,說的是一個新入職的員工,不會用 Git 拉代碼,第二天被開除。由此,可見 Git 對我們工作的重要性,無論是前端后端,都是離不開 Git 的,下面就讓我們一探究竟吧。
上面的案例引申出一個問題,入職一家新公司,你的 leader 給你分配了倉庫的權限后,如何配置本地的 Git 環境并拉取代碼?莫慌,按照下面我講的四個步驟走,保證你可以順利使用 Git 進行拉取代碼!
下載 Git 下載地址 (https://git-scm.com/downloads) ,選擇自己系統對應的版本下載即可。
在你的電腦上生成 ssh 秘鑰,打開終端,執行?
ssh-keygen -t rsa -C "你公司內部郵箱地址"
,如果執行成功,切換到?~/.ssh
?目錄下,此時目錄應該如下所示。復制?id_rsa.pub
?的內容。這里以 Github 為例,如下圖所示,進入?
settings -> SSH and GPG keys
?通過?cat
?命令查看文件?id_rsa.pub
?的內容,然后復制過來,點擊?add ssh key
,這一步等于說把你的公鑰放到了 Github 上進行托管。全局配置 Git 的用戶名和郵箱
git?config?--global?user.name?"xxx"
git?config?--global?user.email?"xxx@xx.com"
完成以上四步,你就可以愉快 pull 代碼開發了。和 https 拉取方式不同的是,https 方式需要每次提交前都手動輸入用戶名和密碼,ssh 的方式配置完畢后 Git 都會使用你本地的私鑰和遠程倉庫的公鑰進行驗證是否是一對秘鑰,從而簡化了操作流程。
Git簡介
在介紹 Git 的相關操作前,我覺得非常有必要了解 Git 的由來,以及 Git 是用來解決什么問題的。Git(讀音為/g?t/)是一個開源的分布式版本控制系統,可以有效、高速地處理從很小到非常大的項目版本管理。Linus Torvalds ,這個人我相信大家都知道吧,開源 Linux 系統的發明人。如今,你看到的大部分服務器其實都是運行在 Linux 系統上,令人感到稱嘆的是,這位大神級別的程序員不僅創造了 Linux 系統。那 Linux 的代碼是如何管理的呢?2002年之前,世界各地的志愿者把源代碼文件通過 diff 的方式發給 Linus,然后由 Linus 本人通過手工方式合并代碼!要知道,當時的 Linux 的代碼量已經很大了,通過人工管理的方式,一是容易出錯,二是效率低。于是 Linus 選擇了一個商業的版本控制系統 BitKeeper,BitKeeper 的東家 BitMover 公司出于人道主義精神,授權 Linux 社區免費使用這個版本控制系統。最后,出于某種原因,BitMover 公司收回了 Linux 社區的免費使用權,于是 Linus 花了兩周時間自己用 C 語言寫了一個分布式版本控制系統,這就是 Git 的由來了。

Git 的工作區域和流程
要想弄懂 Git 是怎么對我們的代碼進行管理的,那首當其沖的是了解 Git 的工作區域是如何構成的。因為,只有徹底弄懂了 Git 工作區域的構成,你才可以在適當的區域使用合適的命令。如下圖所示,此圖包含了 Git 的 4 個工作區和一些常見的操作。

Workspace:工作區,就是平時進行開發改動的地方,是當前看到最新的內容,在開發的過程也就是對工作區的操作。
Index:暫存區,當執行?git add
?的命令后,工作區的文件就會被移入暫存區,暫存區標記了當前工作區中哪些內容是被 Git 管理的,當完成某個需求或者功能后需要提交代碼,第一步就是通過?git add
?先提交到暫存區。
Repository:本地倉庫,位于自己的電腦上,通過?git commit
?提交暫存區的內容,會進入本地倉庫。
Remote:遠程倉庫,用來托管代碼的服務器,遠程倉庫的內容能夠被分布在多個地點的處于協作關系的本地倉庫修改,本地倉庫修改完代碼后通過?git push
?命令同步代碼到遠程倉庫。
一般來說,Git 的工作流程分為以下幾步
在工作區開發,添加,修改文件。
將修改后的文件放入暫存區。
將暫存區域的文件提交到本地倉庫。
將本地倉庫的修改推送到遠程倉庫。
Git 基本操作
git add
添加文件到暫存區
#?添加某個文件到暫存區,后面可以跟多個文件,以空格區分
git?add?xxx
#?添加當前更改的所有文件到暫存區。
git?add?.
git commit
#?提交暫存的更改,會新開編輯器進行編輯
git?commit?
#?提交暫存的更改,并記錄下備注
git?commit?-m?"you?message"
#?等同于?git?add?.?&&?git?commit?-m
git?commit?-am
#?對最近一次的提交的信息進行修改,此操作會修改?commit?的?hash?值
git?commit?--amend
git pull
#?從遠程倉庫拉取代碼并合并到本地,可簡寫為?git?pull?等同于?git?fetch?&&?git?merge?
git?pull?<遠程主機名>?<遠程分支名>:<本地分支名>
#?使用?rebase?的模式進行合并
git?pull?--rebase?<遠程主機名>?<遠程分支名>:<本地分支名>
git fetch
與?git pull
?不同的是?git fetch
?操作僅僅只會拉取遠程的更改,不會自動進行 merge 操作。對你當前的代碼沒有影響
#?獲取遠程倉庫特定分支的更新
git?fetch?<遠程主機名>?<分支名>
#?獲取遠程倉庫所有分支的更新
git?fetch?--all
git branch
#?新建本地分支,但不切換
git?branch?<branch-name>?
#?查看本地分支
git?branch
#?查看遠程分支
git?branch?-r
#?查看本地和遠程分支
git?branch?-a
#?刪除本地分支
git?branch?-D?<branch-nane>
#?重新命名分支
git?branch?-m?<old-branch-name>?<new-branch-name>
工作中使用 Git 解決問題的場景
git rebase 讓你的提交記錄更加清晰可讀
git rebase 的使用
rebase 翻譯為變基,他的作用和 merge 很相似,用于把一個分支的修改合并到當前分支上。
如下圖所示,下圖介紹了經過 rebase 后提交歷史的變化情況。

現在我們來用一個例子來解釋一下上面的過程。
假設我們現在有 2 條分支,一個為 master,一個為 feature/1,他們都基于初始的一個提交 add readme 進行檢出分支,之后,master 分支增加了 3.js ,和 4.js 的文件,分別進行了 2 次提交,feature/1 也增加了 1.js 和 2.js 的文件,分別對應以下 2 條提交記錄。
此時,對應分支的提交記錄如下。
master 分支如下圖:

feature/1 分支如下圖

結合起來看是這樣的

此時,切換到 feature/1 分支下,執行?git rebase master
,成功之后,通過?git log
?查看記錄。
如下圖所示:可以看到先是逐個應用了 mater 分支的更改,然后以 master 分支最后的提交作為基點,再逐個應用 feature/1 的每個更改。

所以,我們的提交記錄就會非常清晰,沒有分叉,上面演示的是比較順利的情況,但是大部分情況下,rebase 的過程中會產生沖突的,此時,就需要手動解決沖突,然后使用依次?git add
?、git rebase --continue
?的方式來處理沖突,完成 rebase 的過程,如果不想要某次 rebase 的結果,那么需要使用?git rebase --skip
?來跳過這次 rebase 操作。
git merge 和 git rebase 的區別
不同于?git rebase
?的是,git merge
?在不是 fast-forward(快速合并)的情況下,會產生一條額外的合并記錄,類似?Merge branch 'xxx' into 'xxx'
?的一條提交信息。

另外,在解決沖突的時候,用 merge 只需要解決一次沖突即可,簡單粗暴,而用 rebase 的時候 ,需要依次解決每次的沖突,才可以提交。
git rebase 交互模式
在開發中,常會遇到在一個分支上產生了很多的無效的提交,這種情況下使用 rebase 的交互式模式可以把已經發生的多次提交壓縮成一次提交,得到了一個干凈的提交歷史,例如某個分支的提交歷史情況如下:

進入交互式模式的方式是執行:
git?rebase?-i?<base-commit>
參數?base-commit
?就是指明操作的基點提交對象,基于這個基點進行 rebase 的操作,對于上述提交歷史的例子,我們要把最后的一個提交對象( ac18084 )之前的提交壓縮成一次提交,我們需要執行的命令格式是:
git?rebase?-i?ac18084
此時會進入一個 vim 的交互式頁面,編輯器列出的信息像下列這樣。

想要合并這一堆更改,我們要使用 Squash 策略進行合并,即把當前的 commit 和它的上一個 commit 內容進行合并, 大概可以表示為下面這樣,在交互模式的 rebase 下,至少保留一個 pick,,否則命令會執行失敗。
pick??...?...
s?????...?...?
s?????...?...?
s?????...?...?
修改文件后 按下?:
?然后?wq
?保存退出,此時又會彈出一個編輯頁面,這個頁面是用來編輯提交的信息,修改為?feat: 更正
,最后保存一下,接著使用?git branch
?查看提交的 commit 信息,rebase 后的提交記錄如下圖所示,是不是清爽了很多?rebase 操作可以讓我們的提交歷史變得更加清晰。

特別注意,只能在自己使用的 feature 分支上進行 rebase 操作,不允許在集成分支上進行 rebase,因為這種操作會修改集成分支的歷史記錄。
使用 git cherry-pick 獲取指定的 commit
git cherry-pick
?可以理解為”挑揀”提交,和 merge 合并一個分支的所有提交不同的是,它會獲取某一個分支的單筆提交,并作為一個新的提交引入到你當前分支上。當我們需要在本地合入其他分支的提交時,如果我們不想對整個分支進行合并,而是只想將某一次提交合入到本地當前分支上,那么就要使用?git cherry-pick
?了。
如下場景,以下有三條分支,feature/cherry-pick1 和 feature/cherry-pick2 都是基于 master 檢出的兩條功能性分支,對應的分支 log 記錄如下


master 分支的提交如下
現在 master 只需要 feature/cherry-pick1 和 feature/cherry-pick2 有關 change 的修改,并不關心有關 fix 內容的修改。此時就可以用 cherry-pick 指令了。
語法:?git cherry-pick [commit-hash]
commit-hash 表示的是某次 commit 的 hash 值。現在,依次執行以下兩條指令?git cherry-pick e0bb7f3
、git cherry-pick c9a3101
,過程中,如果出現沖突,解決沖突后 進行?git add
,接著執行?git cherry-pick --continue
,最后,master 上的提交如下

此時,master 分支上應用了需要的提交,就達到了我們想要的效果。如果需要多個 cherry-pick 需要同步到目標分支,可以簡寫為?git cherry-pick <first-commit-id>...<last-commit-id>
,這是一個左開右閉的區間,也就時說?first-commit-id
?提交帶來的代碼的改動不會被合并過去,如果需要合并過去,可以使用?git cherry-pick <first-commit-id>^...<last-commit-id>
,它表示包含?first-commit-id
?到?last-commit-id
?在內的提交都會被合并過去。
使用 git revert 回滾某次的提交
想象這么一個場景,你的項目最近有2個版本要上線,這兩個版本還伴隨著之前遺留的 bug 的修復,一開始的時候,你將 bug 修復在了第一個版本的 release 分支上,突然在發版前一天,測試那邊反饋,需要把第一個版本修復 bug 的內容改在第二個版本上,這個時候,第一個版本的集成分支的提交應該包括了第一個版本的功能內容,遺留 bug 修復的提交和其他同事提交的內容,想要通過 reset 的方式粗暴摘除之前的關于 bug 修復的 commit 肯定是不行的,同時,這種做法比較危險,此時,我們既不想破壞之前的提交記錄,又想撤回我們遺留 bug 的 commit 記錄應該怎么做呢?git revert 就派上了用場。
git revert
?撤銷某次操作,此操作不會修改原本的提交記錄,而是會新增一條提交記錄來抵消某次操作。
語法:?git revert <commit-id>
?針對普通 commit
git revert <commit-id> -m
?針對 merge 的 commit
下面就用一個案例來理解一下這個命令,如下圖所示,假設被紅框框起來的地方是會引起 bug 的一次提交,在他的提交之后,又進行了 2 次提交,其中包含了其它同事的提交。

此時想把引起提交的 bug 的干掉,執行?git revert 1121932
,執行操作后,再打開查看日志,如下圖所示,可以看到是新增了一條 commit 記錄,這個 commit 的產生的 msg 是自動生成的,Revert 開頭,后面跟撤回的 commit-msg 信息之前的 commit 記錄并沒有消失,此時也達到了代碼回退的效果

此外 git revert 也可以回滾多次的提交
語法:git revert [commit-id1] [commit-id2] ...
?注意這是一個前開后閉區間,即不包括 commit1 ,但包括 commit2 。
回滾我們的提交有二種方式,一種是上文提到的git revert
命令外,還可以使用?git reset
?命令,那么它們兩者有什么區別呢?
git revert
?會新建一條 commit 信息,來撤回之前的修改。
git reset
?會直接將提交記錄退回到指定的 commit 上。
對于個人的 feature 分支而言,可以使用?git reset
?來回退歷史記錄,之后使用?git push --force
?進行推送到遠程,但是如果是在多人協作的集成分支上,不推薦直接使用?git reset
?命令,而是使用更加安全的?git revert
?命令進行撤回提交。這樣,提交的歷史記錄不會被抹去,可以安全的進行撤回。
使用 git stash 來暫存文件
會有這么一個場景,現在你正在用你的 feature 分支上開發新功能。這時,生產環境上出現了一個 bug 需要緊急修復,但是你這部分代碼還沒開發完,不想提交,怎么辦?這個時候可以用?git stash
?命令先把工作區已經修改的文件暫存起來,然后切換到 hotfix 分支上進行 bug 的修復,修復完成后,切換回 feature 分支,從堆棧中恢復剛剛保存的內容。
基本命令如下
git?stash?//把本地的改動暫存起來
git stash save "message"?執行存儲時,添加備注,方便查找。
git?stash?pop?//?應用最近一次暫存的修改,并刪除暫存的記錄
git stash apply ?//?應用某個存儲,但不會把存儲從存儲列表中刪除,默認使用第一個存儲,即 stash@{0},如果要使用其他個,git stash apply stash@{$num}?。
git?stash?list?//?查看?stash?有哪些存儲
git?stash?clear?//?刪除所有緩存的?stash
下面通過幾幅圖對 stash 的命令做進一步了解。
此時,我正在開發一個新功能,修改了 1.js 文件里的內容

還沒開發完成,這個時候,我想切換到 hotfix 分支上修復 bug,得暫停下開發切換到 hotfix 分支,但是現在工作區還有內容,此時如果切換分支 Git 會報出下面的錯誤
error:?Your?local?changes?to?the?following?files?would?be?overwritten?by?checkout:????????1.jsPlease?commit?your?changes?or?stash?them?before?you?switch?branches.Aborting
上面那句話的意思就是說工作區有文件修改,不能提交,需要先進行 commit 或者 stash 操作,執行?git stash
,結果如下
Saved?working?directory?and?index?state?WIP?on?stash:?22e561c?feat:?add?1.js
此時,我們的工作區已經干凈了,可以切換到 hotfix 分支進行 bug 修復的工作,假設我們現在 bug 修復完成了,繼續切回 feature 分支進行原本功能的開發,此時只需要執行?git stash pop
,之前我們暫存的修改就會恢復到工作區,如下圖所示。

當我們想要暫存文件,切換分支做某些事的時候,可以用?git stash
?這種機制幫助開發。
推薦在使用 stash 的相關命令時,每一次暫存的時候,不要直接使用?git stash
?命令進行暫存下來,而是使用?git stash save "message..."
?這種方式,給本次的提交做一個信息的記錄。這樣,想應用更改的時候,先通過?git stash list
?查看一下所有的暫存列表。之后,推薦使用?git stash apply stash@${num}
?的方式進行應用對應的 stash,這樣不會清空已有的 stash 的列表項,并且能應用到當前的工作區,不需要這個暫存的話,再手動清除就可以了。
不同的工作區域撤銷更改
開發中,我們經常需要回退代碼的操作,在不同的工作區域中,回退代碼的方式也是不相同的。如下圖所示,假設現在要在 feature/revoke 分支上進行開發,
首先通過?git status
?查看下現在的狀態。

目前我們的工作區是很干凈的,沒有任何修改的操作,此時,修改一下代碼再次查看狀態,可以看到,1.js 這個文件被修改了。

現在我們想把 1.js 這個文件恢復到修改前的狀態,即撤回工作區的修改,就可以使用?git checkout -- <filename>
?的命令,如果要撤回多個文件的修改,文件之間使用空格隔開,如下圖所示,我們撤回了 1.js 文件的修改,工作區也恢復干凈了。

如果說現在我們對文件進行了修改,并且已經提交到暫存區了,這部分文件我們不想要的話,那么就可以通過?git reset <filename>
?的命令來對特定的文件進行撤銷,git reset
?會撤回所有存在暫存區的文件,如下圖所示,查看前后的狀態可知,文件最后成功撤回到工作區了。

配置 git alias 提升工作效率
一般我們在工作中,接到開發任務后,需要新創建一個分支進行開發 此時需要 用到?git branch
、git checkout
、?git pull
?等命令,在我們一頓操作后,開發完成,到了提交代碼的階段,又要諸如此類?git add
?、git commit
、git push
?等命令,雖然簡單,但是輸入起來也是不夠簡潔,作為一個程序員,開發程序就是為了提高我們的效率的,懶是人類進步的源泉,所以我們可以通過配置別名的方式,簡化這些命令。
它的基本用法是?git config --global alias.<簡化的字符> 原始命令
如下面的例子:
$?git?config?--global?alias.co?checkout
$?git?config?--global?alias.ci?commit
$?git?config?--global?alias.br?branch
這里將 co 表示 checkout,ci 表示 commit,br 表示 branch,以后提交就可以簡寫成

--global
?是全局參數,也就是配置一次后,這些命令可以在這臺電腦下的所有倉庫都適用。這些命令其實是更新你全局的 .gitconfig 文件,該文件用來保存全局的 git 配置,vim ~/.gitconfig
,執行這段命令后,顯示如下,下圖展示了剛才通過?git config --global alias
?添加的?alias
。

除了上面那種直接通過命令的方式外,也可以通過修改這個文件的?alias
?項來設置別名。
這里分享一個我自己常用的別名設置,把以下配置替換到 .gitconfig 文件里的?[alias]
?所屬的區域,然后就可以愉快的使用了~
[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
lg?=?log?--color?--graph?--pretty=format:'%Cred%h%Creset?-%C(yellow)%d%Creset?%s?%Cgreen(%cr)?%C(bold?blue)<%an>%Creset'?--abbrev-commit
這樣,我們每次想查看 Git 的歷史記錄,就不用輸入那么一長串命令 直接使用?git lg
?,下圖是 axios 源碼里的提交記錄,使用封裝后的?git lg
?查看的效果圖

分支之間的關系一眼就很明了,在哪個 commit 上進行的 merge 操作也很清晰,可以幫助我們很好的追溯歷史的提交和解決問題。
總結
本文由淺入深的的講解了 Git 的環境搭建,基本用法,以及工作中使用較為高頻的 Git 命令的用法,無論你是前端后端還是其它端的開發,日常工作中少不了對 Git 的使用,我們不僅要會用,還要用的漂亮,用的靈活,用的穩健。這樣才能在和同事協作項目的時候更加得心應手,學會了本文這些 Git 的使用技巧后,在日常工作中多多練習,相信會給你帶來很大的收獲!
參考文獻
阮一峰的git教程 (https://www.ruanyifeng.com/blog/2014/06/git_remote.html)
Git merge和rebase分支合并命令的區別 (https://juejin.cn/post/6844903603694469134#heading-3)
最近組建了一個湖南人的前端交流群,如果你是湖南人可以加我微信 ruochuan12 拉你進群。
今日話題
略。歡迎分享、收藏、點贊、在看我的公眾號文章~
一個愿景是幫助5年內前端人成長的公眾號
可加我個人微信?ruochuan12,長期交流學習
推薦閱讀
我在阿里招前端,該怎么幫你(可進面試群)
2年前端經驗,做的項目沒技術含量,怎么辦?
點擊上方卡片關注我、加個星標
·················?若川簡介?·················
你好,我是若川,畢業于江西高校。現在是一名前端開發“工程師”。寫有《學習源碼整體架構系列》多篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會寫一篇年度總結,已經寫了7篇,點擊查看年度總結。
同時,活躍在知乎@若川,掘金@若川。致力于分享前端開發經驗,愿景:幫助5年內前端人走向前列。