git hook 异常合并检测

vi project_name/hooks/pre-receive
加入以下脚本

# 我用的 git 软件是 gitea 
PUSHER_NAME=${GITEA_PUSHER_NAME}
DINGDING_ACCESS_TOKEN="xxx"
GIT_MANAGER=(
"http://lukachen.com"
)

# 钉钉报警
funNotify(){
    curl 'https://oapi.dingtalk.com/robot/send?access_token='"${DINGDING_ACCESS_TOKEN}"'' -s -H 'Content-Type: application/json' -d '{"msgtype":"text","text":{"content":"'"$1"'"},"at":{"isAtAll":true}}' > /dev/null 2>&1
}

# 判断字符串是否在数组中,是返回1,否返回0
funInArray(){
    local e match="$1"
    shift
    for e; do [[ "$e" == "$match" ]] && return 1; done
    return 0
}

while read oldrev newrev refname; do
    # echo ${oldrev} ${newrev} ${refname}; exit 1;

    if [[ $refname == "refs/heads/master" || $refname == "refs/heads/develop" ]]; then

        # 稳定分支上的 commit list
        span=$(git rev-list $oldrev..$newrev $EXCLUDE_EXISTING)
        for commit in $span; do
            # develop、master 分支上最后一个 merge 点
            last_merge_commit=$commit
            break;
        done

        # 检查稳定分支上的节点,需为 merge|revert 节点
        for commit in $span; do
            msg=$(git log --format=%s -n 1 $commit);
            is_ignore=$(echo $msg | grep -v grep | grep -Ei 'revert|merge' | wc -l);
            if [[ $is_ignore -gt 0 ]]; then
                continue
            fi

            is_merge=$(git show --no-patch --format="%P" $commit | awk '{print NF}');
            if [[ $is_merge -lt 2 ]]; then
                funNotify "repo: $REPO_NAME\nsha1: $commit\ncommit remark: $msg\nerror: 不允许直接在 $refname 提交代码\nhook: verify_merge\ncmd: git rev-list $oldrev..$newrev $EXCLUDE_EXISTING\npush user: $PUSHER_NAME\n"
                echo "=========================提示========================="
                echo "hook: ${BASH_SOURCE[0]}"
                echo "sha1: $commit"
                echo "commit message: $msg"
                echo "不允许直接在 $refname 提交代码, 请使用 git merge --no-ff origin/feature/storyNUM 合并代码"
                echo "======================================================"
                exit 1
            fi
        done

        # story 分支上的最后一个 commit, merge 节点 Parent2 是 story 分支
        last_commit=$(git show --no-patch --format=%P $newrev | awk '{print $2}')
        if [[ $last_commit == "" ]]; then
            # is revert commit
            continue
        fi
        last_commit_msg=$(git log --format=%s -n 1 $last_commit)

        # 检查要上线的 last_commit 是否上过外测了
        if [[ $refname == "refs/heads/master" ]]; then
            in_develop=$(git branch -a --contains $last_commit | grep -v grep | grep develop | wc -l)
            last_commit_is_revert=$(echo $last_commit_msg | grep -v grep | grep -Ei 'revert' | wc -l)
            if [[ $last_commit_is_revert -eq 0 && $in_develop -eq 0 ]]; then
                funNotify "repo: $REPO_NAME\nhook: verify_merge\nsha1: $last_commit\ncommit msg: $last_commit_msg\nerror: 节点未通过 develop(外测) 验收\npush user: $PUSHER_NAME\n"
                echo "=========================提示========================="
                echo "hook: ${BASH_SOURCE[0]}"
                echo "sha1: $last_commit"
                echo "commit msg: $last_commit_msg"
                echo "error: 节点未经过 develop(外测) 验收"
                echo "======================================================"
                exit 1
            fi
       fi

        # 检查要上 develop 的代码的 story 分支代码,是否有异常合并
        ## step1.获取最后一个 commit 所在的 story 分支 (如果存在分支间的合并,这个可能会获取失误。这里放行管理员操作)
        ## step2.获取 story 分支的 fork point:
        ##   2.1 first_point=$(git log --reverse --format=format:%H master..feature/story51805 | head -1)
        ##   2.2 fork_point=$(git show --no-patch --pretty=format:%H ${first_point}^)
        ## step3.获取 story 分支的所有普通 commit 节点的文件列表
        ##   3.1 获取仅在 story 分支的 非merge commits
        ##   3.2 获取 3.1 节点的修改文件
        ## step4.获取 git diff --name-only $last_merge_commit..${last_merge_parent1_commit}, 循环列表中的文件判断是不是在 3 的列表中(这些文件是要上 develop 时的修改文件)
        if [[ $refname == "refs/heads/develop" ]]; then
            # step1
            story_branch_num=$(git branch -a --contains $last_commit | grep -v grep | grep -Ei 'feature' | wc -l)
            funInArray "${PUSHER_NAME}" "${GIT_MANAGER[@]}"
            is_git_manager=$?
            if [[ $story_branch_num -gt 1 && $is_git_manager == 0 ]]; then
                # 最后一个节点如果在多个分支,就没办法判断当前是在合并的哪个分支的需求了,所以只能限制一下
                funNotify "repo: $REPO_NAME\nhook: verify_merge\nsha1: $last_commit\ncommit msg: $last_commit_msg\nerror: 存在 story 分支间合并,请联系管理员处理,谢谢\ncmd: git branch -a --contains $last_commit\npush user: $PUSHER_NAME\n"
                echo "=========================提示========================="
                echo "hook: ${BASH_SOURCE[0]}"
                echo "sha1: $last_commit"
                echo "commit msg: $last_commit_msg"
                echo "error: 存在 story 分支间合并,请联系管理员处理,谢谢"
                echo "======================================================"
                exit 1
            fi

            if [[ $story_branch_num -eq 1 ]]; then
                # step2
                story_branch=$(git branch -a --contains $last_commit | grep -v grep | grep -Ei 'feature')
                first_point=$(git log --reverse --format=format:%H master..$story_branch | head -1)
                fork_point=$(git show --no-patch --pretty=format:%H ${first_point}^)

                # step3
                # 3.1
                fork_to_last_commits=$(git log --format=%H --no-merges $fork_point..$last_commit)
                fork_to_master_commits=$(git log --format=%H --no-merges $fork_point..master)

                fork_to_master_commits_array=()
                if [[ $fork_to_master_commits != "" ]]; then
                    for one_commit in $fork_to_master_commits; do
                        fork_to_master_commits_array+=($one_commit)
                    done
                fi

                story_commits=""
                for one_commit in $fork_to_last_commits; do
                    if [[ ${#fork_to_master_commits_array[@]} -eq 0 ]]; then
                        story_commits="$story_commits $one_commit"
                        continue
                    fi
                    funInArray "$one_commit" "${fork_to_master_commits_array[@]}"
                    commit_in_master=$?
                    if [[ $commit_in_master == 0 ]]; then
                        story_commits="$story_commits $one_commit"
                        continue
                    fi
                done

                if [[ $story_commits == "" ]]; then
                    # 没有开发分支节点,可能是 merge master into develop
                    continue
                fi

                # debug 
                # echo $last_commit
                # echo $story_commits

                # 3.2
                story_files=$(git show --pretty="" --name-only $story_commits)
                story_files_array=()
                for tmp in $story_files; do
                    funInArray "$tmp" "${story_files_array[@]}"
                    in_story_files_array=$?
                    if [[ $in_story_files_array == 0 ]]; then
                        story_files_array+=($tmp)
                    fi
                done

                # step4
                merge_files=$(git diff --name-only $last_merge_commit..${last_merge_commit}^)

                is_error_merge=0
                error_files_array=()
                for one_file in $merge_files; do
                    funInArray "$one_file" "${story_files_array[@]}"
                    in_story_files=$?
                    if [[ $in_story_files == 0 ]]; then
                        is_error_merge=1
                        error_files_array+=($one_file)
                    fi
                done

                if [[ $is_error_merge == 1 ]]; then
                    echo "=========================提示========================="
                    echo "hook: ${BASH_SOURCE[0]}"
                    echo "error: $story_branch 分支可能存在异常合并, 请 fgit develop $REPO_NAME 重新尝试合并或联系管理员处理,谢谢"
                    echo "error_files: ${error_files_array[@]}"
                    echo "======================================================"

                    # 阻止普通人员的提交
                    if [[ $is_git_manager == 0 ]]; then
                        funNotify "repo: $REPO_NAME\nhook: verify_merge\nerror: $story_branch 分支可能存在异常合并\nerror_files: ${error_files_array[@]}\npush user: $PUSHER_NAME\n"
                        exit 1
                    fi

                    # 放行管理员操作
                fi
            fi

        fi

    fi

done

exit 0