问题
I have a piece of software in a Mercurial repository.
I'm packaging my software project as a Debian package.
It seems the standard way to do this is to have a
separate branch for the Debian package files,
which live in the debian
sub-directory.
One problem I keep having is that I forget which branch I am on and accidentally commit to the wrong branch. This happens frequently, and is really annoying. When this happens I usually push to remote before realising the problem, and then have to fix up the local and remote repositories manually, which is a pain.
The only option I can think of is to have a pre-commit hook that aborts if I am trying to make commits to the wrong branch.
To be concrete, let's say the main branch is called default
and the branch
containing Debian files is called debian
. Then I want commits to the default
branch to succeed only if none of the files in the commit are from the debian
directory. I want commits to the debian
directory to succeed only if all the
files in the commit are in the debian
directory.
I spent some time reading the chapter on Mercurial Hooks and going through the examples
in the Hg Book, but still have no idea how to go about this. I did get the strong impression
that for something like this I should be calling out to an external Python script, probably
in .hg/
.
回答1:
Yeah, you're spot on that a precommit
hook can do this. If you wanted to do it in bash you could go with something like:
#!/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
Those if/grep lines are almost certainly wrong, but you get the picture.
回答2:
Using @Ry4an's solution as a starting point, I came up with the following script using the new 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:
A method using in-process hooks is the following code. These
functions can be used in a Mercurial repository's .hgrc
like this.
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
disallows normal commits to the wrong
branch, but allows MQ commits. qfinish_abort_commit_to_wrong_branch
stops qfinish from converting MQ commits on the wrong branch into
regular commits.
I used the function finish
at
https://bitbucket.org/mirror/mercurial/src/tip/hgext/mq.py?at=default#cl-3034
for reference.
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
来源:https://stackoverflow.com/questions/19234473/precommit-mercurial-hook-to-stop-commits-to-the-wrong-branch