天天看點

【Git】1090- 我在工作中是如何使用Git的

本文首發于政采雲前端團隊部落格:我在工作中是如何使用 Git 的
【Git】1090- 我在工作中是如何使用Git的

前言

最近在網上有個真實發生的案例比較火,說的是一個新入職的員工,不會用 Git 拉代碼,第二天被開除。由此,可見 Git 對我們工作的重要性,無論是前端後端,都是離不開 Git 的,下面就讓我們一探究竟吧。

上面的案例引申出一個問題,入職一家新公司,你的 leader 給你配置設定了倉庫的權限後,如何配置本地的 Git 環境并拉取代碼?莫慌,按照下面我講的四個步驟走,保證你可以順利使用 Git 進行拉取代碼!

  1. 下載下傳 Git 下載下傳位址 (https://git-scm.com/downloads) ,選擇自己系統對應的版本下載下傳即可。
  2. 在你的電腦上生成 ssh 秘鑰,打開終端,執行​

    ​ssh-keygen -t rsa -C "你公司内部郵箱位址"​

    ​,如果執行成功,切換到 ​

    ​~/.ssh​

    ​ 目錄下,此時目錄應該如下所示。複制 ​

    ​id_rsa.pub​

    ​ 的内容。
    【Git】1090- 我在工作中是如何使用Git的
  3. 這裡以 Github 為例,如下圖所示,進入​

    ​settings -> SSH and GPG keys​

    ​ 通過 ​

    ​cat​

    ​ 指令檢視檔案 ​

    ​id_rsa.pub​

    ​ 的内容,然後複制過來,點選 ​

    ​add ssh key​

    ​,這一步等于說把你的公鑰放到了 Github 上進行托管。
    【Git】1090- 我在工作中是如何使用Git的
  4. 全局配置 Git 的使用者名和郵箱
git config --global user.name "xxx"
git config --global user.email "[email protected]"      

完成以上四步,你就可以愉快 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】1090- 我在工作中是如何使用Git的

Git 的工作區域和流程

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

【Git】1090- 我在工作中是如何使用Git的

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 後送出曆史的變化情況。

【Git】1090- 我在工作中是如何使用Git的

現在我們來用一個例子來解釋一下上面的過程。

假設我們現在有 2 條分支,一個為 master,一個為 feature/1,他們都基于初始的一個送出 add readme 進行檢出分支,之後,master 分支增加了 3.js ,和 4.js 的檔案,分别進行了 2 次送出,feature/1 也增加了 1.js 和 2.js 的檔案,分别對應以下 2 條送出記錄。

此時,對應分支的送出記錄如下。

master 分支如下圖:

【Git】1090- 我在工作中是如何使用Git的

feature/1 分支如下圖

【Git】1090- 我在工作中是如何使用Git的

結合起來看是這樣的

【Git】1090- 我在工作中是如何使用Git的

此時,切換到 feature/1 分支下,執行 ​

​git rebase master​

​​,成功之後,通過 ​

​git log​

​ 檢視記錄。

如下圖所示:可以看到先是逐個應用了 mater 分支的更改,然後以 master 分支最後的送出作為基點,再逐個應用 feature/1 的每個更改。

【Git】1090- 我在工作中是如何使用Git的

是以,我們的送出記錄就會非常清晰,沒有分叉,上面示範的是比較順利的情況,但是大部分情況下,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'​

​ 的一條送出資訊。

【Git】1090- 我在工作中是如何使用Git的

另外,在解決沖突的時候,用 merge 隻需要解決一次沖突即可,簡單粗暴,而用 rebase 的時候 ,需要依次解決每次的沖突,才可以送出。

git rebase 互動模式

在開發中,常會遇到在一個分支上産生了很多的無效的送出,這種情況下使用 rebase 的互動式模式可以把已經發生的多次送出壓縮成一次送出,得到了一個幹淨的送出曆史,例如某個分支的送出曆史情況如下:

【Git】1090- 我在工作中是如何使用Git的

進入互動式模式的方式是執行:

git rebase -i <base-commit>      

參數 ​

​base-commit​

​ 就是指明操作的基點送出對象,基于這個基點進行 rebase 的操作,對于上述送出曆史的例子,我們要把最後的一個送出對象( ac18084 )之前的送出壓縮成一次送出,我們需要執行的指令格式是:

git rebase -i ac18084      

此時會進入一個 vim 的互動式頁面,編輯器列出的資訊像下列這樣。

【Git】1090- 我在工作中是如何使用Git的

想要合并這一堆更改,我們要使用 Squash 政策進行合并,即把目前的 commit 和它的上一個 commit 内容進行合并, 大概可以表示為下面這樣,在互動模式的 rebase 下,至少保留一個 pick,,否則指令會執行失敗。

pick  ... ...
s     ... ... 
s     ... ... 
s     ... ...       

修改檔案後 按下 ​

​:​

​​ 然後 ​

​wq​

​​ 儲存退出,此時又會彈出一個編輯頁面,這個頁面是用來編輯送出的資訊,修改為 ​

​feat: 更正​

​​,最後儲存一下,接着使用 ​

​git branch​

​ 檢視送出的 commit 資訊,rebase 後的送出記錄如下圖所示,是不是清爽了很多?rebase 操作可以讓我們的送出曆史變得更加清晰。

【Git】1090- 我在工作中是如何使用Git的
特别注意,隻能在自己使用的 feature 分支上進行 rebase 操作,不允許在內建分支上進行 rebase,因為這種操作會修改內建分支的曆史記錄。

使用 git cherry-pick 擷取指定的 commit

​git cherry-pick​

​​ 可以了解為”挑揀”送出,和 merge 合并一個分支的所有送出不同的是,它會擷取某一個分支的單筆送出,并作為一個新的送出引入到你目前分支上。當我們需要在本地合入其他分支的送出時,如果我們不想對整個分支進行合并,而是隻想将某一次送出合入到本地目前分支上,那麼就要使用 ​

​git cherry-pick​

​ 了。

如下場景,以下有三條分支,feature/cherry-pick1 和 feature/cherry-pick2 都是基于 master 檢出的兩條功能性分支,對應的分支 log 記錄如下

【Git】1090- 我在工作中是如何使用Git的
【Git】1090- 我在工作中是如何使用Git的

master 分支的送出如下

【Git】1090- 我在工作中是如何使用Git的

現在 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 上的送出如下

【Git】1090- 我在工作中是如何使用Git的

此時,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 次送出,其中包含了其它同僚的送出。

【Git】1090- 我在工作中是如何使用Git的

此時想把引起送出的 bug 的幹掉,執行 ​

​git revert 1121932​

​,執行操作後,再打開檢視日志,如下圖所示,可以看到是新增了一條 commit 記錄,這個 commit 的産生的 msg 是自動生成的,Revert 開頭,後面跟撤回的 commit-msg 資訊之前的 commit 記錄并沒有消失,此時也達到了代碼回退的效果

【Git】1090- 我在工作中是如何使用Git的

此外 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 檔案裡的内容

【Git】1090- 我在工作中是如何使用Git的

還沒開發完成,這個時候,我想切換到 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】1090- 我在工作中是如何使用Git的

當我們想要暫存檔案,切換分支做某些事的時候,可以用 ​

​git stash​

​ 這種機制幫助開發。

推薦在使用 stash 的相關指令時,每一次暫存的時候,不要直接使用 ​

​git stash​

​​ 指令進行暫存下來,而是使用 ​

​git stash save "message..."​

​​ 這種方式,給本次的送出做一個資訊的記錄。這樣,想應用更改的時候,先通過 ​

​git stash list​

​​ 檢視一下所有的暫存清單。之後,推薦使用 ​

​git stash apply stash@${num}​

​ 的方式進行應用對應的 stash,這樣不會清空已有的 stash 的清單項,并且能應用到目前的工作區,不需要這個暫存的話,再手動清除就可以了。

不同的工作區域撤銷更改

開發中,我們經常需要回退代碼的操作,在不同的工作區域中,回退代碼的方式也是不相同的。如下圖所示,假設現在要在 feature/revoke 分支上進行開發,

首先通過 ​

​git status​

​ 檢視下現在的狀态。

【Git】1090- 我在工作中是如何使用Git的

目前我們的工作區是很幹淨的,沒有任何修改的操作,此時,修改一下代碼再次檢視狀态,可以看到,1.js 這個檔案被修改了。

【Git】1090- 我在工作中是如何使用Git的

現在我們想把 1.js 這個檔案恢複到修改前的狀态,即撤回工作區的修改,就可以使用 ​

​git checkout -- <filename>​

​ 的指令,如果要撤回多個檔案的修改,檔案之間使用空格隔開,如下圖所示,我們撤回了 1.js 檔案的修改,工作區也恢複幹淨了。

【Git】1090- 我在工作中是如何使用Git的

如果說現在我們對檔案進行了修改,并且已經送出到暫存區了,這部分檔案我們不想要的話,那麼就可以通過 ​

​git reset <filename>​

​​ 的指令來對特定的檔案進行撤銷,​

​git reset​

​ 會撤回所有存在暫存區的檔案,如下圖所示,檢視前後的狀态可知,檔案最後成功撤回到工作區了。

【Git】1090- 我在工作中是如何使用Git的

配置 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,以後送出就可以簡寫成

【Git】1090- 我在工作中是如何使用Git的

​--global​

​​ 是全局參數,也就是配置一次後,這些指令可以在這台電腦下的所有倉庫都适用。這些指令其實是更新你全局的 .gitconfig 檔案,該檔案用來儲存全局的 git 配置,​

​vim ~/.gitconfig​

​​,執行這段指令後,顯示如下,下圖展示了剛才通過 ​

​git config --global alias​

​​ 添加的 ​

​alias​

​。

【Git】1090- 我在工作中是如何使用Git的

除了上面那種直接通過指令的方式外,也可以通過修改這個檔案的 ​

​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​

​ 檢視的效果圖

【Git】1090- 我在工作中是如何使用Git的

分支之間的關系一眼就很明了,在哪個 commit 上進行的 merge 操作也很清晰,可以幫助我們很好的追溯曆史的送出和解決問題。

總結

本文由淺入深的的講解了 Git 的環境搭建,基本用法,以及工作中使用較為高頻的 Git 指令的用法,無論你是前端後端還是其它端的開發,日常工作中少不了對 Git 的使用,我們不僅要會用,還要用的漂亮,用的靈活,用的穩健。這樣才能在和同僚協作項目的時候更加得心應手,學會了本文這些 Git 的使用技巧後,在日常工作中多多練習,相信會給你帶來很大的收獲!

參考文獻