Git 的基本操作、開發流程、實用技巧總結

weixin_33860722發表於2017-08-24

Git 是什麼?

Git 是一個分散式的程式碼管理容器,本地和遠端都保有一份相同的程式碼。
Git 倉庫主要是由是三部分組成:原生程式碼,快取區,提交歷史,這幾乎是所有操作的本質,但是為了文章更加簡單易懂,就不圍繞這塊展開了,有興趣的可以去了解下。
開門見山,我們直接來說說 Git 有哪些常見的操作。

Git 有哪些常規操作?

我們簡單說說Git有哪些常規操作,能夠讓我們應付簡單的開發需求。

克隆程式碼

✦ 克隆遠端程式碼

git clone http://git.code.oa.com/QCFE/sqlserver.git

✦ 檢視本地的程式碼狀態

// 可以明確的呈現出本地倉庫的狀態
// 哪些檔案發生改動,哪些檔案已經提交到本機
// 以及一些操作指示。
git status
1158202-6b77354e1ee48f03.png
git status

✦ 同步遠端分支變化

// 拉取指定分支的變化
git fetch origin master 
// 拉取所有分支的變化
git fetch 
// 拉取所有分支的變化,並且將遠端不存在的分支同步移除【推薦】
git fetch -p 

✦ 同步遠端程式碼變化。

// 都是先 git fetch,然後執行合併操作
// 不同的是,git pull 執行的是 git merge,git pull -r 執行的是git rebase
git pull origin master 
git pull -r origin master

關於 git merge 和 git rebase 各自的優劣,後文會詳細介紹。

這部分主要介紹了關於程式碼克隆,同步遠端程式碼變化的相關操作。接下來,我們看看關於原生程式碼的一些操作。

操作 commit

首先我們要明確一個概念:就是每個 commit 都是一份完整的程式碼狀態,用一個 commitID 來唯一標誌。


1158202-247c4b51426d3113.png
git log --stat 可以讓你的 commit 記錄更清晰

從某個角度上來說,Git維護的就是一個commitID樹,分別儲存著不同狀態下的程式碼。
所以你對程式碼的任何修改,最終都會反映到 commit 上面去。

✦ 新增 commit

// 新增檔案到快取區,然後提交到本地倉庫
git add files
git commit -m '提交備註'

✦ 撤銷 commit

// 會將提交記錄回滾,程式碼不回滾
git reset b14bb52

// 會將提交記錄和程式碼全部回滾
git reset --hard b14bb52

// 將部分程式碼檔案回滾
git checkout -- files

✦ 合併 commit
合併 commit,本質上合併兩份不同狀態下的程式碼。

// Git 提供了兩種合併 commit 的方式
git merge master
git rebase master

那麼 git rebase 和 git merge 到底有什麼區別呢?
merge是兩個分支處理衝突後,新增一個 commit 追加到master上。
rebase是將someFeature分支上的commit記錄追加到主分支上,值得注意的是,這個時候他的commit其實已經發生變化。


1158202-889d7dc1e9cd70b6.png
image.png

相對來說,git merge 處理衝突更直接,而git rebase 能夠保證清晰的 commit 記錄。

合併 commit 的時候,通常會發生衝突。
可以全域性搜尋特殊字元比如<<<,找到需要處理的程式碼位置,然後認真分析應該保留哪一部分程式碼。

1158202-431686e7ab15dc21.png
處理衝突

在團隊協作的時候,分支是必不可少的。那麼應該如何對分支進行操作呢?

操作分支

所謂的分支其實就是一個指向 commitID 的指標,你可以去.git/refs/heads裡去看看。

1158202-99c3056f2994b0e1.png
cat .git/refs/heads/master

通常情況下,我們建議分支至少能夠明確的標記功能名稱,如果能標記使用者就更好了,比如qixiu/feature

✦ 檢視分支


1158202-c2e08c238c4f8adb.png
git branch -a

可以同時看到本地分支和遠端分支,配合上前文介紹的 git fetch -p 可以第一時間檢視到最新的分支資訊。

✦ 新增本地分支
其實就是建立一個指標指向某一個 commitID。

// git branch qixiu/feature + git checkout qixiu/feature
// 從當前分支新增一個新的分支qixiu/feature
// 一般情況下,我們應該從master或者其他穩定分支來新增分支
git checkout -b qixiu/feature // 新建分支
git checkout qixiu/feature // 切換分支

✦ 刪除本地分支
其實就是移除一個指向 commitID 的指標。

// 刪除本地分支,如果本地還有未合併的程式碼,則不能刪除
git branch -d qixiu/feature
// 強制刪除本地分支
git branch -D qixiu/feature 

✦ 新增遠端分支
通常情況下,我們是新建本地分支,然後更新到遠端的方式來新增一個遠端分支

git push origin qixiu/feature

✦ 刪除遠端分支
同樣,我們也是通過更新到遠端的方式來刪除一個遠端分支

// 等同於git push origin -d qixiu/feaure
git push origin :qixiu/feature

簡單彙總一下

上面說的可能有些分散,這兒簡單總結一下有哪些經常使用的操作:

git status // 檢視原生程式碼狀態
git add files // 新增程式碼到快取區
git commit -m '提交內容的備註' // 提交程式碼到本地倉庫
git checkout -b branchName // 不加-b就是普通切換分支
git fetch -p // 同步遠端分支狀態
git pull -r origin branchName // fetch遠端程式碼到本地,並且以rebase的方式合併程式碼
git push origin branchName // 更新原生程式碼到遠端

以上幾條命令已經能夠應付日常的操作,稍微複雜一些的場景後文會介紹

基於基本操作,在實際專案中,我們應該怎麼利用 Git 實現協作呢?

Git 有哪些比較好的實踐?

Git 有一些成熟的開發流程,比較主流的有兩種:基於功能分支的開發流程GitFlow開發流程
相對來時,我更推薦前者,如果是複雜的大型專案,推薦GitFlow開發流程。
接下來,簡單介紹下這兩種協作模式。

基於功能分支的協作模式

基於功能分支的開發流程其實就是一句話:用分支來承載功能開發,開發結束之後就合併到 master 分支。
他的優點是能夠保證master分支的整潔,同時還能讓分支程式碼邏輯集中,也便於 CodeReview。

分支命名規範

推薦使用如下格式:ownerName/featureName。
這樣既便於知道分支覆蓋的功能,也便於找到分支的負責人。以後清理分支的時候也很方便。

開發流程

✦ 從 master 切出一個新分支

git checkout -b qixiu/newFeature

✦ 開發一些新功能,然後提交
建議較多頻次的提交程式碼到本地倉庫,以便能夠更靈活的儲存或撤銷修改。
此外為了保證提交日誌的清晰,建議備註清楚的註釋。

git status
git add files // 挑選需要提交的檔案,或者全部提交
git commit -m '提交備註'
git push origin qixiu/newFeature

✦ 如果功能開發完成,可以發起一個CodeReview流程
✦ 如果程式碼測試通過,合併到 master,然後準備上線

// 冗餘版 合併到 master
git checkout master 
git pull -r origin master
git checkout qixiu/newFeature
git rebase master // 處理衝突
git checkout master
git merge qixiu/newFeature
git push origin master

// 精簡版 合併到 master
git checkout qixiu/newFeature
git pull -r origin master // 將master的程式碼更新下來,並且rebase處理衝突
git push origin master // 將原生程式碼更新到遠端

有幾點需要注意:
不要在master合併程式碼,保證master的可用性很重要。
確保在正確的分支執行正確的操作。
無論是處理衝突還是更新遠端程式碼,請保有敬畏之心。

到此,一個正常的基於功能分支的開發流程就完成了。接下來看看另外一個開發流程。

GitFlow 開發流程

GitFlow 比前文講的基於功能分支的開發流程要複雜得多,它更適合大型的複雜專案。
它圍繞專案釋出流程定義了一個嚴格的分支模型,所有的開發流程都是圍繞這個嚴格的分支模型進行。
而這個模型約定了每個分支的角色,以及他們如何溝通。

我們先來看看 GitFlow 開發流程中幾個約定的分支,以及他們各自承擔的角色是怎麼樣的?


1158202-87339e9210336803.png
GitFlow 開發流程

✦ Master分支:用於存放線上版本程式碼,可以方便的給程式碼打版本號。
✦ Develop分支:用於整合 Feature 分支。
✦ Feature分支:某個功能的分支,從 Develop 分支切出,並且功能完成時又合併回 Develop 分支,不直接和
Master 分支互動。
✦ Release分支:通常對應一個迭代。將一個版本的功能全部合併到 Develop 分支之後,從 Develop 切出一個 Release 分支。這個分支不在追加新需求,可以完成 bug 修復、完善文件等工作。務必記住,程式碼釋出後,需要將其合併到 Master 分支,同時也要合併到 Develop 分支。
✦ Hotfix分支:緊急修復的分支,是唯一可以從 Master 切出的分支,一旦修復了可以合併到 Master 分支和 Develop 分支。

從每個分支的功能和約定可以看出,它流程多約束多,對於小規模應用並不適合。
當然 GitFlow 有一些輔助工具 gitflow 可以自動化的完成這些任務,對於大型專案也很有幫助。

前面講了 Git 有哪些基本操作,然後介紹了兩個主流的工作流程。
接下來我們看看 Git 有哪些特別的技巧值得一提。

Git 有哪些小技巧?

Git 操作除了基本的程式碼管理功能,還有一些小技巧能夠讓你眼前一亮。

git reflog,檢視操作記錄

這個我一定要放在第一個介紹,因為它曾經數次解救了我的程式碼


1158202-e0ad78b7cf16f2c4.png
git reflog

仔細看上圖,reflog 記錄了你所有的 git 命令操作,對於復原某些莫名其妙的場景或者回滾誤操作有極大的幫助。

試想一個場景:你使用 git reset --hard commitID 把本地開發程式碼回滾到了一個之前的版本,而且還沒有推到遠端,怎麼才能找回丟失的程式碼呢?
你如果使用 git log 檢視提交日誌,並不能找回丟棄的那些 commitID。
而 git reflog 卻詳細的記錄了你每個操作的 commitID,可以輕易的讓你復原當時的操作並且找回丟失的程式碼。
當然,如果你丟失的程式碼都沒有提交記錄,那麼恭喜你,你的程式碼真的丟了。

壓縮日誌

這也是一個很實用的功能,前文提過,我們在開發中的時候儘量保持一個較高頻率的程式碼提交,這樣可以避免不小心程式碼丟失。但是真正合並程式碼的時候,我們並不希望有太多冗餘的提交記錄,而且 rebase 合併程式碼的時候,會把每個 commit 都處理一下,有時候會造成冗餘的工作。
所以,壓縮日誌之後不經能讓 commit 記錄非常整潔,同時也便於使用 rebase 合併程式碼。

那麼,如何壓縮commit記錄呢?
✦ 使用 git log 找到起始 commitID
git reset commitID,切記不要用 --hard 引數
✦ 重新 git add && git commit
git push -f origin branchName,因為會有衝突,所以需要強制覆蓋遠端分支,請務必謹慎。
✦ 合併到 master 中,然後更新遠端 master。

此外還有兩種壓縮日誌的辦法:
git commit --amend:追加 commit 到上一個 commit 上。
git rebase -i:通過互動式的 rebase,提供對分支 commit 的控制,從而可以清理混亂的歷史。

1158202-8c6a6b57d46cf85c.png
git rebase -i

從實際應用來說,三種日誌壓縮都很優秀,git reset 更簡單,git rebase -i 更細膩。

git rebase,合併程式碼

前文簡單介紹了 git rebasegit merge 的區別,坦率講,他們各有優劣。
git rebase 能讓你的 commit 記錄非常整潔,無論是線上回滾還是 CodeReview 都更輕鬆;但卻是一個有隱患的操作,使用時務必謹慎。
git merge 操作更安全,同時也更簡單;但卻會增加一些冗餘的 commit 記錄。

這兒簡單說說 rebase 的合併流程和注意事項吧。看下圖


1158202-93a64dad5f9236a1.png
image.png

有三個點需要注意:
✦ rebase 先找出共同的祖先節點
✦ 從祖先節點把 pay 分支的提交記錄摘下來,然後 rebase 到 master 分支
✦ rebase 之後的 commitID 其實已經發生了變化
尤其是第三點,經常會讓人誤操作,所以務必注意。

試想一下,開發過程中,如果我們頻繁的 rebase master 分支,會有什麼後果呢?


1158202-da70108ef73829a7.png
image.png

當你不斷 rebase master 的時候,其實你本地的 d 都變成了 d` ,再要和遠端 pay 分支保持一致,你的本地分支 commit 記錄已經不堪入目了。

另外要注意,絕不要在公共的分支上使用 rebase!!!

1158202-2a895e28c6b7ce02.png
image.png

所以,為了安全,團隊可以考慮採用 merge。

pull request,方便CodeReview

Git 不僅提供了程式碼託管以及程式碼開發的幫助,還提供了程式碼稽核類似的功能。
當我們在功能分支開發完成之後,可以發起一個 pull request 請求,選擇需要對比的兩個分支


1158202-23dfb467411d901f.png
新建 pull request

它會建立一個 pull request,制定相關人員來對程式碼進行 review。
通常情況下,團隊應該鼓勵交叉 review,涉及到公共程式碼的時候,一定要讓相關人 review。
當然這塊如果能結合更優秀的 CodeReview 工具,能夠有更好的體驗。

git hook,Git 的生命週期

這個大多數人應該都,聽說過,git操作有它自身的生命週期,在不同的生命週期,我們可以做一些自動化的事情。

舉兩個簡單的例子:
✦ pre-commit的時候我們可以做 eslint
✦ post-commit的時候,我們可以做利用 jenkins 類似的工具做持續整合

當然還有更多的宣告週期,具體可以參考 Git 鉤子

git submodule && git subtree,管理第三方模組

這兩個命令通常用來管理公用的第三方模組。比如一些通用的底層邏輯、中介軟體、還有一些可能會頻繁變化的通用業務元件。
當然,兩者還是有區別的。
git submodule 主要用來管理一些單向更新的公共模組或底層邏輯。
git subtree 對於部分需要雙向更新的可複用邏輯來說,特別適合管理。比如一些需要複用的業務元件程式碼。在我之前的實踐中,我也曾用subtree來管理構建系統邏輯。

git alias,簡化 Git 命令

我們可以通過配置 git alias 來簡化需要輸入的 Git 命令。
比如前文的 git subtree 需要輸入很長的 Git 命令,我們可以配置 .git/config 檔案來解決。

// git stpull appfe demo/xxx
// git stpush appfe demo/xxx
[alias]
    stpull = !git subtree pull --prefix=$1 appfe $2 \
        && :
    stpush = !git subtree pull --prefix=$1 appfe $2 \
        && git subtree split --rejoin --prefix=$1 $2 \
        && git subtree push --prefix=$1 appfe $2 \
        && :

總結說點啥?

該文首先介紹了 Git 常規操作
✦ 包括克隆程式碼、操作 commit、操作分支等。其實 Git 常規操作的命令並不多,請看第一部分的簡單總結。

其次介紹了 Git 開發流程
✦ 該部分主要介紹了兩種主流的開發模式:比較輕量的 基於功能分支的開發流程 和適合複雜專案的 GitFlow 開發流程 ,兩種模式各有使用的場景,對於常規使用,前者就已經足夠了。

最後介紹了一些 Git 實用技巧
✦ 主要包括:reflog 操作,壓縮日誌,rebase 的注意事項,利用 pull request 做 codeReview,利用 git hook 做一些自動化工作等。

相關文章