1. 程式人生 > >在Git上如何強推代碼規範

在Git上如何強推代碼規範

我們 後綴 強制 繼續 編譯 會有 ranch 當前 想要

引言

最近參加了“前端規範制定topic”小組,小組成員一起制定了html、css、js、es6、vue和react等規範,但規範制定好了怎麽進行推廣去強制執行呢,已知我們的項目都是用git做管理的,所以馬上想到在git上做文章,本文講述的就是如何在git上推行代碼強校驗。

git知識點

說到git,我們先來了解下平時項目中不是很關註的幾個git概念.

git倉庫

git是分布式版本管理系統,可以有多個代碼倉庫,所有參與項目的開發者都可以擁有自己的本地倉庫,每一個本地倉庫都是一個完整的版本庫,即使不聯網,也可以任意的進行開發、創建分支、commit和查看版本的歷史提交記錄等。

但每個人都在自己的本地倉庫開發,怎麽做到代碼共享和同步呢?在我們的項目中,通常都會建立一個大家都可以訪問的共享倉庫,這個共享倉庫放在一個專門的線上服務器上,我們也叫它遠程倉庫。遠程倉庫和本地倉庫的唯一差別就在於它是裸倉庫,就是不包含任何工作目錄,僅僅是由 “.git” 目錄組成的。它作為服務器倉庫供各開發者push、pull數據,實現數據共享和同步,不保存文件,只保存歷史提交的版本信息等。

技術分享

git存儲方式

說完git倉庫,我們來了解下git的存儲方式。git存儲數據更像是把數據看作是對小型文件系統的一組快照,每次提交更新或者是保存項目狀態時,它主要對現有的文件制作一個快照並保存這個快照的索引。這個“快照”就是git對象,而“快照的索引”就是對象名。所有用來表示項目歷史信息的文件,都是通過一個40字符的(40-digit)“對象名”來索引的。對象名看起來像這樣的:26e5847434caa7597c4088de8ecab9cd567957d1。

在git裏,每一個“對象名”都是對“對象”內容做 SHA-1哈希計算得來的, SHA-1是一種密碼學的hash算法,這樣在每個倉庫中不同內容的對象就會有不同的對象名。git有四種類型的對象:"blob"、"tree"、 "commit" 和”tag”:

  • “blob”用來存儲文件數據,沒有文件名,只有內容
  • “tree”有一串指向blob對象或是其它tree對象的指針,它一般用來表示內容之間的目錄層次關系
  • “commit” 指向一個"tree對象", 並且帶有相關的描述信息,如提交者、提交時間、註釋等等
  • “tag”用來標記某一個提交(commit) 的方法

所以下面所描述的獲取文件信息基本上都是通過各種git命令操作hash對象名來獲取的,這也是為什麽在這裏介紹“git存儲方式”的原因,方便大家更深刻的了解git命令。

作用域

由git倉庫的定義可以看出,本地倉庫因為是本地的,任何能接觸得到倉庫的人都可以進行修改、刪除等,所以本地倉庫不適合做代碼強校驗。那只能考慮遠程倉庫了,可以當用戶push推送的時候,校驗代碼,如果不符合規範,就拒絕這次提交,雖然你不能阻止開發者寫出糟糕的代碼,但可以防止這些代碼流入官方的代碼庫。

git鉤子

好了,確定了規範在服務端倉庫上來強推行,但怎麽做到當用戶push動作觸發的時候去做一下代碼強校驗呢,經過調研發現,git鉤子正好解決了我們的這個問題。

什麽是git鉤子呢?git鉤子是在git倉庫中特定重要動作發生時自動運行的腳本,它可以讓你自定義git內部的行為,在開發周期中的關鍵點觸發自定義的行為。這樣來說,git鉤子就可以幫我們來推行規範了。

git鉤子到底什麽樣呢,它存在於每個git倉庫的.git/hooks目錄中,當你觀察.git/hooks時,你會看到下面這樣的文件:

技術分享

hooks目錄下展示的鉤子並不全,這裏帶simple後綴的只是git的大部分鉤子,.sample拓展名防止它們默認被執行,想要運行一個鉤子,去掉後綴名或者在git官網查看相應鉤子名稱添加新文件即可。

  • 鉤子分類

    按照鉤子的定義,hooks下的鉤子可分為本地鉤子和服務端鉤子兩類。本地鉤子基本由提交和合並這樣的操作所調用,而服務器端鉤子主要用於接收推送這樣的聯網操作。上面“作用域”部分已經提到,我們只能在服務端倉庫做代碼強校驗,所以接下來主要研究服務端鉤子。

  • 鉤子語言

    這些鉤子就是git內置的一些腳本,內置的腳本大多是shell和perl語言的,但你可以使用任何腳本語言,只要它們最後能編譯到可執行文件。本人對python比較熟,所以整個的開發用的是python語言。

git服務端鉤子

由上面的git知識點一步步看下來,我們就可以確定了代碼校驗合適的地方是在服務端鉤子上,但服務端鉤子也有好多,我們的代碼規範要在哪個鉤子做比較合適呢,接來下就具體介紹下服務端最有用的3個鉤子,並找出一個適合我們的鉤子。

  • pre-receive

這個腳本在git push向遠程倉庫推送操作時,最先被調用。它沒有參數,但是可以從標準輸入獲取一系列的推送引用。如果它以非零值退出,所有的推送內容都不會被接受,所以這是強制推行開發規範的好地方。

#!/usr/bin/python
# -*- coding: utf-8 -*-

import re,fileinput

for file_line in fileinput.input():
    #從標準輸入可以獲取三個值
    #推送前的引用指向的內容的SHA-1值,用戶準備推送的內容的SHA-1值,引用的名字(分支)
    old_hash,new_hash,branch = re.split(r‘\s+‘, file_line.strip(‘\n‘))
# 放棄推送
# sys.exit(1)

  • update

update腳本和pre-receive腳本十分類似,不同之處在於它會為每一個準備更新的分支各運行一次。假如推送者同時向多個分支推送內容,pre-receive只運行一次,相比之下update則會為每一個被推送的分支各運行一次。它不會從標準輸入讀取內容,而是會接受三個參數,這三個參數信息和pre-receive在標準輸入讀取信息相同。如果update腳本以非零值退出,只有相應的那一個引用會被拒絕;其余的依然會被更新。

#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys
#接受三個參數
#引用的名字(分支), 推送前的引用指向的內容的SHA-1值,用戶準備推送的內容的SHA-1值
branch,old_hash,new_hash = sys.argv
# 只放棄當前分支的推送
# sys.exit(1)

  • post-receive

post-receive腳本在成功推送後被調用,可以用來更新其他系統服務或者通知用戶,它接受與 pre-receive相同的標準輸入數據。它的用途包括給某個郵件列表發信,通知持續集成(continous integration)的服務器,或者更新問題追蹤系統(ticket-tracking system)等。

服務端推送成功後調用的鉤子不止post-receive這一個,如上面.git/hooks圖中的post-update也是其中之一,但是為什麽不介紹post-update呢?原因是它的輸入獲取值太單一,post-receive更像是post-update的超級集合,所以推送成功後的調用我們一般用post-receive。

我們經常用到的gitlab中的web hook就綁定了這個鉤子,當然web hook是綁定了好幾個不同的鉤子的,post-receive只是其中一個,如下圖的web hook的中的Push events事件觸發的就是.git/hooks中的post-receive腳本

技術分享

分析這三個鉤子,我們顯然選push成功前調用的鉤子,那到底是選pre-receive還是update呢?考慮到我們的項目很少有一次push多個分支的場景,最終選了pre-receive鉤子來做我們的代碼強校驗。

在pre-receive鉤子裏做代碼校驗

鉤子選擇好了,接下來就是怎麽做了,下面是一個簡單的流程圖,具體為從標準輸入中獲取三個值,分別是推送前的引用指向內容的SHA-1值,用戶準備推送內容的SHA-1值和分支名,我再代碼裏分別用變量old_hash,new_hash和branch來表示這三個值,下面有用到。根據這三個值用git命令分別獲取用戶信息和提交的增量文件,把這些文件推送到eslint服務上進行代碼校驗,校驗成功就直接push通過;不成功則在客戶端返回校驗結果,push不通過。

技術分享

根據上面的描述,重點介紹下怎麽獲取用戶信息和增量文件

  • 用戶信息

獲取用戶信息的目的是傳給eslint服務端,在服務端可以給分析用戶的行為及給用戶發送郵件等 這個命令可以定制化格式只獲取用戶的信息

  • %cn: committer name
  • %ce: committer email
  • %ct: committer date, UNIX timestamp
  • --no-patch: 不顯示提交差異
‘git show --format="%%cn %%ce %%ct" --no-patch %s‘ % new_hash

定制化後運行的結果是下面這樣的,可以獲取提交者的信息:

技術分享

  • 增量文件

增量文件取得是本次推送引用指向內容的SHA-1值和推送前的引用指向內容的SHA-1值中間的差值。命令如下,其中 --name-status是只取差異文件的名字和狀態值

為什麽不直接取本次推送引用指向內容的SHA-1值而取差值呢,原因是上次推送和本次推送中間可能隔了好幾個commit,每一個commit對象都會生成一個唯一的SHA-1哈希字串的。

‘git diff --name-status %s %s‘ %(old_hash,new_hash)

運行後得到的結果是:

技術分享

後面的就是怎麽獲取文件及和eslint服務通訊的問題了,在這裏不是重點就略過了...

總結

本文可以說是我接到任務後,從只了解git的git pull、git clone、git push等幾個常用命令到怎麽在git上實現我們的需求的一個探索過程,希望裏面的介紹對不是很熟悉git的同學有點幫助,另外也簡單介紹了下我們強推規範的大致流程。後續會繼續研究下gitlab和git鉤子的關聯關系。

在Git上如何強推代碼規範