precommit mercurial hook, чтобы остановить фиксацию на неправильной ветке

У меня есть часть программного обеспечения в репозитории Mercurial.

Я упаковываю свой программный проект как пакет Debian. Кажется, что стандартный способ сделать это - создать отдельную ветку для файлов пакета Debian, которые находятся в подкаталоге debian.

Одна проблема, с которой я продолжаю сталкиваться, заключается в том, что я забываю, в какой ветке я нахожусь, и случайно переключаюсь на неправильную ветку. Такое случается часто и сильно раздражает. Когда это происходит, я обычно нажимаю на удаленный, прежде чем осознаю проблему, а затем мне приходится вручную исправлять локальные и удаленные репозитории, что является проблемой.

Единственный вариант, о котором я могу думать, - это иметь ловушку перед фиксацией, которая прерывается, если я пытаюсь выполнить фиксацию в неправильную ветку.

Чтобы быть конкретным, предположим, что основная ветвь называется default, а ветка, содержащая файлы Debian, называется debian. Затем я хочу, чтобы фиксации в ветке default выполнялись успешно, только если ни один из файлов в фиксации не находится в каталоге debian. Я хочу, чтобы фиксации в каталоге debian выполнялись успешно, только если все файлы в фиксации находятся в каталоге debian.

Я потратил некоторое время, читая главу о Mercurial Hooks и просматривая примеры в Hg Book, но до сих пор не знаю, как это сделать. У меня действительно сложилось сильное впечатление, что для чего-то вроде этого я должен вызывать внешний скрипт Python, вероятно, в .hg/.


person Faheem Mitha    schedule 07.10.2013    source источник


Ответы (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 почти наверняка неверны, но вы понимаете.

person Ry4an Brase    schedule 08.10.2013
comment
Спасибо, Ry4an, это очень полезно. Есть ли преимущества в использовании оболочки вместо Python или наоборот? - person Faheem Mitha; 08.10.2013
comment
python работает в процессе, поэтому он будет более эффективным, но вы будете изучать новый not-an-api, чтобы получить текущее имя ветки и список файлов в фиксации, тогда как вы, вероятно, уже знаете, как это сделать в bash . В конце концов, это, вероятно, больше о том, что вам и вашей команде будет легче настроить позже. Некоторые примеры можно найти здесь: mercurial.selenic.com/wiki/HookExamples - person Ry4an Brase; 08.10.2013
comment
Привет, Ry4an. Не уверен, что вы имеете в виду под словом not-an-api. Вы имеете в виду, что у Mercurial нет фиксированного официального API? - person Faheem Mitha; 08.10.2013
comment
Официальный API - это командная строка. Мэтт Макколл невероятно серьезно относится к обратной совместимости командной строки, но внутреннее устройство говорит, что имена функций в объекте «репо» могут быть изменены в любое время. На практике они не так уж сильно меняются, но всегда возможно, что будущее обновление Mercurial сломает вашу ловушку python, в то время как гарантировано, что она не сломает вашу ловушку bash. - person Ry4an Brase; 09.10.2013

Используя решение @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)
person Faheem Mitha    schedule 10.10.2013

В следующем коде используется метод, использующий внутрипроцессные перехватчики. Эти функции можно использовать в .hgrc репозитория Mercurial следующим образом.

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
person Faheem Mitha    schedule 13.10.2013