首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Git钩子,它检查被推送到这个存储库的分支。

Git钩子,它检查被推送到这个存储库的分支。
EN

Code Review用户
提问于 2014-08-09 23:30:23
回答 1查看 487关注 0票数 10

描述

如果在存储库中安装此钩子并将分支foo-automerge推到此存储库,则此钩子将合并到工作副本中,如果:

  • 目前签出的分支名为foo
  • 工作目录是干净的
  • 合并将是快速转发。

我需要它的原因是在我对此的疑问上概述的,所以我只想请你在这里信任我。

我主要是寻求来自git人员的建议,如果和如何这将爆炸和搞乱我的存储库。然而,对python的任何批评也是受欢迎的。

这将用于简单的情况,其中有两个存储库AB,一个在我的笔记本电脑上,一个在网格上。我将配置网格存储库,以便在每次推送时签出。这两个存储库都是我的,存储库中的程序不需要任何安装后步骤。

已知问题

我知道这里有很多问题,但我真的不知道如何解决:

  • 如果我正在推送的存储库实际上是一个子模块(因此GIT_DIRother_repo/.git/modules/something),那么这将需要我手动设置FORCE_WORKING_DIR。我不知道找到子模块的工作副本在哪里有多容易。
  • 包含非ascii字符的分支名称可能被错误地对待。

我加入了github专家,但这里也有:

代码语言:javascript
复制
#!/usr/bin/env python
"""

Description
-----------

Update hook that automatically merges changes inside the repository.
It's creation was sparked by
`my question on SO <https://stackoverflow.com/q/25207942/7918>`_.
Especially by `this answer <https://stackoverflow.com/a/25209198/7918>`_.

Logic is as follows:
--------------------

If repository recleives a push we will merge pushed branch to curren branch if
following conditions are met:

* This repo is not bare. It is bare **push will fail**
* pushed branch has a special suffix (by default ``-automerge``). If
 pushed branch has no suffix push will not be aborted.
* Currently checked out branch is named as branch we push to without suffix.
 so if push was to ``foo-automerge``, we will merge iff checked out branch
 is ``foo``. If other branch is checked out push will not be aborted.
* Working directory is clean. If working directory is dirty (and previous
 condition are met) **push will fail**.
* We can guess working copy directory. If not can't  **push will fail**.
* Merge is fast-forward if not **push will fail**.

Installation:
-------------

Copy this file to .git/hooks, and link it to **both**, ``update``
and ``pre-update``. You need to add it to both hooks because ``pre-update`` is
needed to fail push when error conditions are met, and ``update`` is needed
to actually do merge.

Add executable permissions to both hooks.



"""
from __future__ import unicode_literals, print_function

import os, sys, subprocess, logging

logging.basicConfig(level=logging.INFO)
"""
Set do ``DEBUG`` for debug info, or to ``ERROR`` to get error conditions only.
"""

AUTOMERGE_SUFFIX = "-automerge"

FORCE_WORKING_DIR = None
"""
You may override working copy by setting this to a path
"""

CAN_GUESS_GIT_WORK_TREE = True
"""
If false we will not quess working tree directory, so either set ``GIT_WORK_TREE``
envvar or ``FORCE_WORKING_DIR`` python variable.
"""


def get_working_copy_dir():
    """
    Tries to guess and returns working copy directory.
    """

    if FORCE_WORKING_DIR:
        return FORCE_WORKING_DIR

    working_copy = os.environ.get("GIT_WORK_TREE", None)
    if working_copy is not None:
        return working_copy
    if not CAN_GUESS_GIT_WORK_TREE:
        logging.error("Can't quess working copy dir and 'GIT_WORK_TREE' was "
                      "not set")
        sys.exit(1)
    path = os.path.abspath(os.environ['GIT_DIR'])
    working_copy, git_dir = os.path.split(path)
    git_dir = git_dir.strip()
    if git_dir != ".git":
        logging.error("Can't guess working copy dir, because GIT_DIR does not "
                      "point to directory named '.git'")
        sys.exit(1)
    return working_copy


def strip_branch(branch):
    """
    Removes git decorations from branch name.

    >>> strip_branch("refs/heads/feature/master-automerge")
    'feature/master-automerge'
    >>> strip_branch("refs/heads/master-automerge")
    'master-automerge'
    >>> strip_branch('refs/heads/master-automerge ')
    'master-automerge'
    """
    return "/".join(branch.split("/")[2:]).strip()


def checked_out_branch_is_valid(pushed_branch):
    """
    Check whether checked out branch mathes branch we are pushing to.

    """
    pushed_branch_sans_automerge = pushed_branch[:-len(AUTOMERGE_SUFFIX)]
    checked_out_branch = get_checked_out_branch()

    if checked_out_branch != pushed_branch_sans_automerge:
        logging.info("Other branch is checked out, will not merge working copy")
        sys.exit(0)


def is_this_repo_bare():
    """
    :return: True if this repository is bare, False otherwise.
    """
    result = subprocess.check_output("git rev-parse --is-bare-repository".split()).strip()
    if result == "true":
        return True
    if result == "false":
        return False
    raise ValueError("Can't guess whether this repository is bare")


def git_subprocess(args):
    """
    Utility function to call git process in the working copy directory.
    :param list args: list of string containing command to call
    """
    new_cwd = get_working_copy_dir()
    logging.debug("Working copy dir %s", new_cwd)

    new_env = dict(os.environ)
    new_env["GIT_DIR"] = os.path.abspath(os.environ['GIT_DIR'])
    return subprocess.check_output(args, cwd=new_cwd, env=new_env)


def get_checked_out_branch():
    """
    :return: String with currently checked out branch
    """
    return git_subprocess("git rev-parse --symbolic-full-name --abbrev-ref HEAD".split()).strip()


def check_working_directory_clean():
    """
    This is adapted from here: https://stackoverflow.com/a/3879077/7918

    Will exit id working copy is dirty.
    """
    try:
        # Update the index
        git_subprocess("git update-index -q --ignore-submodules --refresh".split())
        # Disallow unstaged changes in the working tree
        git_subprocess("git diff-files --quiet --ignore-submodules --".split())
        # Disallow uncommitted changes in the index
        git_subprocess("git diff-index --cached --quiet HEAD --ignore-submodules --".split())
    except subprocess.CalledProcessError:
        logging.error("Working directory here is not clean. Will not merge")
        sys.exit(1)


def validate(pushed_branch):

    """
    If we can't merge ``pushed_branch`` to current branch this function will
    print error and call ``sys.exit`` with apropriate exit code. 
    """

    if is_this_repo_bare():
        logging.error("Sorry this hook will not work on bare repository")
        sys.exit(1)

    logging.debug("Updating branch %s", pushed_branch)
    if not pushed_branch.endswith(AUTOMERGE_SUFFIX):
        logging.debug("Branch has no automerge suffix, will not automatically merge")
        sys.exit(0)

    checked_out_branch_is_valid(pushed_branch)

    check_working_directory_clean()

    logging.debug("OK to merge")


def update_hook(pushed_branch):

    validate(pushed_branch)


def post_update_hook(pushed_branch):

    validate(pushed_branch)

    try:
        git_subprocess(['git', 'merge', '--ff-only', pushed_branch])
    except subprocess.CalledProcessError:
        logging.error("Couldn't merge --- merge was not fast forward")
        sys.exit(1)


def main():

    script_name = sys.argv[0]
    pushed_branch = sys.argv[1]

    pushed_branch = strip_branch(pushed_branch)

    if script_name.endswith('/update'):
        update_hook(pushed_branch)
    if script_name.endswith('/post-update'):

        post_update_hook(pushed_branch)

if __name__ == "__main__":
    main()
EN

回答 1

Code Review用户

回答已采纳

发布于 2015-02-02 21:52:46

我主要是寻求从git的人的建议,如果和如何这将爆炸和搞乱我的储存库。

就Git行动而言,我看不出有什么大问题或危险。这并不是说根本就没有。

这些命令有一个尾随的--,这似乎很奇怪,但是您可能有自己的原因,或者我遗漏了一些东西:

代码语言:javascript
复制
git diff-files --quiet --ignore-submodules --
git diff-index --cached --quiet HEAD --ignore-submodules --

然而,对python的任何批评也是受欢迎的。

总的来说,代码非常好。它也大多通过PEP8。文件写得很好。但有些改进是可能的。

避免在一行上进行多个导入,

而不是这样:

导入os、sys、子进程、日志记录

将其拆分为多行:

代码语言:javascript
复制
import sys
import subprocess
import logging
import os

将注释放在前面的行

我不太熟悉这种评论风格,而且经常使用它:

logging.basicConfig(level=logging.INFO)“”将do DEBUG设置为调试信息,或设置为ERROR仅获取错误条件。

我更希望这样做:

代码语言:javascript
复制
# Set do ``DEBUG`` for debug info, or to ``ERROR`` to get error conditions only.
logging.basicConfig(level=logging.INFO)

简化

这可以写得更简单:

working_copy = os.environ.get("GIT_WORK_TREE",None)如果working_copy不是None:返回working_copy

如下所示:

代码语言:javascript
复制
working_copy = os.environ.get("GIT_WORK_TREE")
if working_copy:
    return working_copy

重复代码

这种代码经常出现:

Logging.error(“一些消息”) sys.exit(1)

我建议添加一个助手方法:

代码语言:javascript
复制
def fatal(message, exit_code=1):
    logging.error(message)
    sys.exit(exit_code)

另一种经常出现的模式:

Git_subprocess(“一些命令”.split()).strip()

我建议改变git_subprocess,以处理分裂和剥离也。

编码风格

script_name = sys.argv0 pushed_branch = sys.argv1 pushed_branch = strip_branch(pushed_branch) if script_name.endswith('/update'):update_hook(pushed_branch) if script_name.endswith(‘/post update’):post_update_hook(pushed_branch)

可以简单地一步创建pushed_branch,而且由于script_name不能同时以/update/post-update结尾,所以最好用elif编写:

代码语言:javascript
复制
script_name = sys.argv[0]
pushed_branch = strip_branch(sys.argv[1])

if script_name.endswith('/update'):
    update_hook(pushed_branch)
elif script_name.endswith('/post-update'):
    post_update_hook(pushed_branch)
票数 4
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/59596

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档