Git Hook 应用

Posted on January 28, 2016

背景

  1. 自动提取分支号需求

    • 团队git分支要求和工单号对应, 格式feature/rm102345_...
    • 团队要求分支commit message中要包含工单号, 便于今后代码追踪
    • .git/hooks/prepare-commit-msg 可以做这个事情, 但是hooks不属于版本库(无法提交供团队使用)
  2. nodejs 代码增加commit前jshint检测


rails 方案

  1. config/environments/development.rb 最后增加

     # 执行脚本添加prepare-commit-msg
     require(File.join(Rails.root, 'script/symbol_link_prepare_commit_msg.rb'))
    
  2. script/symbol_link_prepare_commit_msg.rb, 用于添加链接文件, 之所以使用链接文件, 是便于项目今后方便修改prepare-commit-msg hook

     #!/usr/bin/env ruby
     # encoding: utf-8
    
     require 'fileutils'
    
     old = File.expand_path('../prepare-commit-msg', __FILE__)
     new = File.expand_path('../../.git/hooks/prepare-commit-msg', __FILE__)
    
     unless File.exist?(new)
       FileUtils.ln_s(old, new).
       puts "成功添加链接文件 #{new}"
     end
    
  3. script/prepare-commit-msg 想放到hook里的shell文件, 用于分析提取准备工单号

     #!/bin/sh
    
     BRANCH=`git rev-parse --abbrev-ref HEAD`
     RM_NUMBER=`echo $BRANCH | grep -o -e 'rm[0-9]*' || echo $BRANCH`
    
     echo -n "[$RM_NUMBER] " > "$1.msg"
     cat "$1" >> "$1.msg"
     cat "$1.msg" > "$1"
    

node 方案

  1. 启动脚本中

     if (app.isDevelopment) {
       //将script/pre-commit 链接到.git/hooks/pre-commit
       //这个脚本是处理pre-commit, 实现commit时 jshint检测
       app.requireModule('script/symbol_link_pre-commit');
    
       //将script/prepare-commit-msg 链接到.git/hooks/prepare-commit-msg
       //这个脚本是处理prepare-commit-msg, 实现提取工单号
       app.requireModule('script/symbol_link_prepare-commit-msg.js');
     }
    
  2. script/symbol_link_pre-commit.js

     #!/usr/bin/env node
    
     var fs         = require('fs');
     var path       = require('path');
     var srcFile    = path.join(__dirname, 'pre-commit');
     var targetFile = path.join(__dirname, '../.git/hooks/pre-commit');
     fs.exists(targetFile, function (exists) {
       if (!exists) {
         fs.symlink(srcFile, targetFile, function(err) {
           if (err) {
             console.log("添加链接文件失败: " + targetFile);
           }
           else {
             console.log("成功添加链接文件: " + targetFile);
           }
         });
       }
     });
    
  3. script/symbol_link_prepare_commit_msg.rb

     #!/usr/bin/env ruby
     # encoding: utf-8
    
     require 'fileutils'
    
     old = File.expand_path('../prepare-commit-msg', __FILE__)
     new = File.expand_path('../../.git/hooks/prepare-commit-msg', __FILE__)
    
     unless File.exist?(new)
       FileUtils.ln_s(old, new).
       puts "成功添加链接文件 #{new}"
     end
    
  4. script/prepare-commit-msg 同 ruby

  5. script/pre-commit

     #!/usr/bin/env bash
    
     files=$(git diff --cached --name-only --diff-filter=ACM | grep -v "^test\|^node_modules\|^public/assets\|^app/assets" | grep ".js$")
     if [ "$files" = "" ]; then
       exit 0
     fi
    
     PARENT_COMMAND="$(ps -o args= $PPID)" #获得父命令以及参数, 目的是检测提交message中是否指示跳过jshint验证
     if [[ $PARENT_COMMAND == *escape_jshint* ]]; then #特殊用途!!! 如合并代码
       exit 0
     fi
    
     if ! type "jshint" > /dev/null; then
       echo -e "\t请按照此命令安装指定版本的jshint: \033[31m npm install [email protected] -g \033[0m"
       exit 1
     fi
    
     version=$((jshint -v) 2>&1)
     if ! [[ "$version" == 'jshint v2.4.4' ]]; then
       echo -e "\tjshint版本不对, 请卸载后安装版本2.4.4: \033[31m npm install [email protected] -g \033[0m"
       exit 1
     fi
    
     pass=true
    
     echo -e "\nValidating JavaScript:\n"
    
     for file in ${files}; do
       result=$(jshint ${file})
       if [ "$result" = "" ]; then
         echo -e "\t\033[32mJSHint Passed: ${file}\033[0m"
       else
         echo -e "\t\033[31mJSHint Failed: ${file}\033[0m"
         pass=false
       fi
     done
    
     echo -e "\nJavaScript validation complete\n"
    
     if ! $pass; then
       echo -e "\033[41mCOMMIT FAILED:\033[0m Your commit contains files that should pass JSHint but do not. Please fix the JSHint errors and try again.\n"
       exit 1
     else
       echo -e "\033[42mCOMMIT SUCCEEDED\033[0m\n"
     fi
    

git commit规范

Why 为什么要有git commit规范?

  • 加快 Reviewing Code 的过程
  • 方便代码git blame时进行工单追踪
  • 5年后帮你快速想起来某个分支,tag 或者 commit 增加了什么功能,改变了哪些代码
  • 好的提交信息能帮助提高项目整体质量

commit 规范

  1. 使用git add时,请使用-p参数,这样可以强制检查要提交的修改,例如: git add -p .

  2. commit 消息一律使用中文, 提高可读性,大家英文水平不一,容易写错,也不易读懂。

  3. commit 消息前统一加上工单号, 格式为 “[rm工单号] 具体说明” 如 “[rm12345] 重构XXX代码”

    关于这点, 我们已经要求了很久, 但是执行结果还不是很理想,最近我们增加了一个脚本来自动完成这个工作, 请大家合并最新的develop获得该功能, 说明:

    原理: git 提供了若干hooks, 其中 prepare-commit-msg 可以修改提交信息. 唯一的问题是这些hooks不属于版本库(无法提交供团队使用)

    我们rails项目 中的解决办法是: 在加载rails development模式时,检查有没有.git/hooks/prepare-commit-msg如果没 有的话将软连接script/symbol_link_prepare_commit_msg.rb到.git/hooks/prepare-commit-msg

    具体代码见config/environments/development.rb 中require(File.join(Rails.root, ‘script/symbol_link_prepare_commit_msg.rb’))

    使用: 请 大家合并最新的develop, 然后rails c 或者手动执行./script/symbol_link_prepare_commit_msg.rb, 完成之后大家提交时无需再手动写入’rm工单号’, 这一步就自动了, 这对于git commit -m XXX(inline提交) 和git commit(vi编辑提交内容) 都有效

  4. 在完成同一个工单时,可能会多次commit同一个模块的内容,这 样会造成git日志的重复以及日志里代码修改的分裂显示,这时候大家可以用 git commit –amend来解决

    –amend这个参数可以将本次提交追加到上次提交里,并可以修 改上次提交的message。

    请注意:只能对没有push的commit使用 git commit –amend命令,如果一个commit已经push到remote server,使用git commit –amend就会有非常大的概率造成代码冲突,所以请大家不要对已经push的代 码使用git commit –amend命令。

  5. 对于一个大的模块提交, 要是用完整提交模式(即 git commit 然后vi编辑message模式), 并注意格式:

    • 第一行是本次提交的简介,需要加上工单号,如feature分支应 该是 [rm12345], 然后空格(脚本自动),然后加上简介。应该少于50个字
    • 第二行是空行
    • 第三行开始是本次修改的详细情况,主要要说明这些问题:
    • 为什么这次修改是必要的?
    • 如何解决的问题?
    • 这些变化可能影响哪些地方?