【问题标题】:precommit mercurial hook to stop commits to the wrong branch预先提交 mercurial 钩子以停止提交到错误的分支
【发布时间】:2013-10-14 14:17:00
【问题描述】:

我在 Mercurial 存储库中有一个软件。

我将我的软件项目打包为 Debian 软件包。 似乎执行此操作的标准方法是 Debian 软件包文件的单独分支, 位于debian 子目录中。

我一直遇到的一个问题是我忘记了我是哪个分支 打开并意外提交到错误的分支。有时候是这样的 经常,真的很烦。发生这种情况时,我通常 在意识到问题之前推送到远程,然后有 手动修复本地和远程存储库,即 痛。

我能想到的唯一选择是有一个预提交挂钩 如果我尝试向错误的分支提交,则会中止。

具体来说,假设主分支称为default,分支 包含 Debian 文件的名称为 debian。然后我想提交到default 只有当提交中的文件都不是来自debian 时,分支才会成功 目录。我希望提交到 debian 目录只有在所有 提交中的文件位于debian 目录中。

我花了一些时间阅读有关 Mercurial Hooks 的章节并浏览了示例 在 Hg Book 中,但仍然不知道如何去做。我确实得到了强烈的印象 对于这样的事情,我应该调用外部 Python 脚本,可能 在.hg/

【问题讨论】:

    标签: version-control mercurial mercurial-hook


    【解决方案1】:

    使用进程内挂钩的方法是以下代码。这些 函数可以像这样在 Mercurial 存储库的 .hgrc 中使用。

    pretxncommit.foo = python:mercurial_hooks.abort_commit_to_wrong_branch
    pre-qfinish.bar = python:mercurial_hooks.qfinish_abort_commit_to_wrong_branch
    

    abort_commit_to_wrong_branch 不允许正常提交错误 分支,但允许 MQ 提交。 qfinish_abort_commit_to_wrong_branch 阻止 qfinish 将错误分支上的 MQ 提交转换为 定期提交。

    我使用了函数finish https://bitbucket.org/mirror/mercurial/src/tip/hgext/mq.py?at=default#cl-3034 供参考。

    def abort_commit_to_wrong_branch(ui, repo, **kwargs):
        """
        Don't allow commits to 'debian' branch including files not
        contained in the 'debian/' directory. Also don't allow commits to
        non-'debian' branches including files contained in the 'debian/'
        directory. Don't restrict MQ commits.
        """
        # If repo has '_committingpatch' attribute, then it is an mq
        # commit in progress, so return 'False'
        import os
        ctx = repo[kwargs['node']]
        files = ctx.files()
        branch = ctx.branch()
        if hasattr(repo, "_committingpatch"):
            for f in files:
                d = os.path.dirname(f)
                if branch == "debian" and d != "debian":
                    ui.warn("Warning: committing %s (file not in 'debian' directory) to 'debian' branch. Allowed since this ia an MQ commit.\n"%f)
                if branch != "debian" and d == "debian":
                    ui.warn("Warning: committing %s (file in 'debian' directory) to non 'debian' branch. Allowed since this ia an MQ commit.\n"%f)
            return False
        for f in files:
            d = os.path.dirname(f)
            if branch == "debian" and d != "debian":
                ui.warn("Error: cannot commit %s (file not in 'debian' directory) to 'debian' branch\n"%f)
                return True
            if branch != "debian" and d == "debian":
                ui.warn("Error: cannot commit %s (file in 'debian' directory) to non 'debian' branch\n"%f)
                return True
    
    def qfinish_abort_commit_to_wrong_branch(ui, repo, **kwargs):
        """
        Don't allow qfinish on 'debian' branch including files not
        contained in the 'debian/' directory. Also don't allow qfinish on
        non-'debian' branches including files contained in the 'debian/'
        directory. Don't restrict MQ commits.
        """
        from mercurial import scmutil
        import os
        if not repo.mq.applied:
            ui.status(('no patches applied\n'))
            return True
        opts = kwargs['opts']
        # case corresponding to `-a`. no revisions specified.
        if opts.get('applied'):
            revrange = ('qbase::qtip',)
        # case where revision(s) specified
        revrange = kwargs['pats']
        revs = scmutil.revrange(repo, revrange)
        # loop over revisions
        for rev in revs:
            ctx = repo[rev]
            files = ctx.files()
            branch = ctx.branch()
            for f in files:
                d = os.path.dirname(f)
                if branch == "debian" and d != "debian":
                    ui.warn("Error: cannot commit %s (file not in 'debian' directory) to 'debian' branch\n"%f)
                    return True
                if branch != "debian" and d == "debian":
                    ui.warn("Error: cannot commit %s (file in 'debian' directory) to non 'debian' branch\n"%f)
                    return True
    

    【讨论】:

      【解决方案2】:

      以@Ry4an 的解决方案为起点,我使用新的hglib API 提出了以下脚本。

      #!/usr/bin/python                                                                                                                                                         
      
      # Abort commit to the debian branch if it is not contained in a debian                                                                                                    
      # subdirectory                                                                                                                                                            
      
      # Similary abort commit to non-debian branches if it is contained in a                                                                                                    
      # debian subdirectory                                                                                                                                                     
      
      import hglib, os, sys
      client = hglib.open("/home/faheem/hooktest")
      ctx = client['tip']
      files = ctx.files()  
      branch = ctx.branch()
      
      for f in files:
          d = os.path.dirname(f)
          if branch == "debian" and d != "debian":
              sys.exit("cannot commit %s (file not in 'debian' directory) to 'debian' branch"%f)
          if branch != "debian" and d == "debian":
              sys.exit("cannot commit %s (file in 'debian' directory) to non 'debian' branch"%f)
      

      【讨论】:

        【解决方案3】:

        是的,precommit 钩子可以做到这一点。如果您想在 bash 中执行此操作,可以使用以下内容:

        #!/bin/bash
        revs=$(hg log -r "$HG_NODE:tip" --template '{rev} ') #Intentional space after {rev}
        rc=0
        for rev in $revs
        do
            files=$(hg log -r $rev --template '{files}')
            #Above will include discards. So you cannot 'hg cat' them all. So you may want
            #  files=$(hg log -r $rev --template '{file_mods} {file_adds}')
            branch=$(hg log -r $rev --template '{branch}')
            for file in $files
            do
                if [ branch == "debian" ] &&  [ "$(echo $file | grep -v "debian")" != "" ] ; then
                  echo "ERROR: Non debian file in debian branch."
                  exit 1
                fi
                if [ branch != "debian" ] &&  [ "$(echo $file | grep "debian")" != "" ] ; then
                  echo "ERROR: debian file in non-debian branch."
                  exit 1
                fi
            done
        done
        exit $rc
        

        那些 if/grep 行几乎肯定是错误的,但你明白了。

        【讨论】:

        • 感谢 Ry4an,这非常有帮助。使用 shell 而不是 Python 有什么优势,反之亦然?
        • python 在进程中运行,因此效率更高,但您将学习一个新的非 API 来获取当前分支名称和提交中的文件列表,而您可能已经知道如何在 bash 中做到这一点。最后,可能更多的是关于您和您的团队以后会发现更容易定制的内容。可以在此处找到一些示例:mercurial.selenic.com/wiki/HookExamples
        • 嗨 Ry4an。不知道您所说的非 API 是什么意思。你的意思是 Mercurial 没有固定的“官方”API?
        • 官方API命令行。 Matt Mackall 非常重视命令行向后兼容性,但内部结构,比如“repo”对象上的函数名称随时可能更改。在实践中它们并没有太大的变化,但是未来的 Mercurial 升级总是有可能会破坏你的 python 钩子,但它保证不会破坏你的 bash 钩子。
        猜你喜欢
        • 2012-12-07
        • 2015-08-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-05-13
        • 2020-10-28
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多