Mercurial: allow merge from a release branch to the default one, but not vice versa
I'm using default
branch for ongoing development, and now going to create a new named branch to mark a release. All further development will be on the default branch, all production bugfixes will be done on the new one (with subsequent merge to default
), like this:
#>hg branches
aristotle 42:dbd...
default 41:da5...
#>hg branch
default
#>echo "Feature #1 for the next release" >> feature1.txt
#>hg add
#>hg commit -m "Implement feature #1 for the next release"
...... eek, need to make an urgent fix on Production .....
#>hg update aristotle
#>echo "Fixed urgent bug #123 on Production" >> fix123.txt
#>hg add
#>hg commit -m "Fixed bug #123 on Production"
created new head
#>hg update default
#>hg merge aristotle
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
(branch merge, dont forget to commit)
#>hg commit -m "Merge in the fix for bug #123"
#>hg push
The above seems the way to go, however it seems easy to mess things up and merge the other way around (from default
to aristotle
which means all the new features will appear in the production branch).
Maybe my fears are groundless because one will notice the mess before pushing the commit to the central repo, but I'd like to see if it's possible to make the approach more foolproof.
So I开发者_Go百科 started looking into hooks:
[hooks]
pretxnchangegroup.branch = hg heads --template "{branches} " | find "aristotle" && exit 1 || exit 0
..but then realized it's not what I need, because this will not allow me to push aristotle changes at all.
So I'm not sure what to do. Ideally, I want developers to see the "wrong way merge" message when they attempt to commit a merge from default
to aristotle
locally (obviously, there should be a double-check on the central repo), while merging from production branch to the default one should be possible.
Thanks!
This should do it. It uses a revset query to find any merges into aristotle
from default
.
hg log -r 'children(p2(::aristotle and ::default and merge()) and branch(default)) and branch(aristotle)'
::aristotle and ::default and merge()
finds all merges that are ancestors of botharistotle
anddefault
branchesp2(...) and branch(default)
grabs the second parent (incoming changeset) that are on thedefault
branch.children(...) and branch(aristotle)
then grabs the actual merge changeset that is on thearistotle
branch.
I recently needed to figure this out myself but also needed to ensure that default
wasn't indirectly merged into my release branch, i.e. default -> feature -> release.
This is pretty much an exact duplicate of a question from a few days ago Ensuring a merge between branches happens in one direction
but I didn't like my answer to that one, so how about this one:
Keep separate clones for past releases. When, in your example above, you decide you need to do an emergency fix on aristotle do this instead:
cd ..
hg clone -r aristotle myrepo myrepo-aristotle
then you have a clone with only aristotle in it and you can't accidentally merge it to default in that clone.
That said, it's still not a great answer. The only real consolation is that if you merge in a direction you don't like you can always re-clone the two heads and merge in the other direction.
I found this question whilst looking for a solution to a related problem, I know its an old question, but thought I would share our solution.
We have release branches called release-x.y.z and ongoing development on default. We use a hook based on the example precommit_badbranch_badmerge found at https://www.mercurial-scm.org/wiki/HookExamples.
We basically extract the version number for each branch involved in any merge and make sure it is going the right way (default is treated as 9999.9999.9999 anything that isn't a release or default branch gets -1,-1,-1 (can be merged into anything).
Note that this only does a simple 'immediate' check, it doesn't catch issues where someone merges from default to the blah branch and then onto a release branch.
As a side note, we also have a policy that the merge to default must be pushed at the same time as the original change on the release branch, this avoids someone else having to do the merge in order to merge their own changes - it is enforcing that that I was looking for a solution for.
Note, you invoke this using the hooks section of the server/central repo
[hooks]
pretxnchangegroup.prevent_bad_merges = python:<path_to_hook>\prevent_bad_merges.py:prevent_bad_merges
python hook below:
# based on precommit_badbranch_badmerge
# at https://www.mercurial-scm.org/wiki/HookExamples
import re
# this isnt a proper compare, it will just return numbers > 0 if source is after target
def compare_versions(source_version, target_version):
# if either side is -1, ignore it
if (source_version[0] == -1) or (target_version[0] == -1):
return 0;
if source_version[0] > target_version[0]:
return 1
elif source_version[0] == target_version[0]:
if source_version[1] > target_version[1]:
return 2
elif source_version[1] == target_version[1]:
if source_version[2] > target_version[2]:
return 3
return 0
def get_version(branch):
if branch == 'default':
major=9999
minor=9999
revision=9999
else:
# note python uses ?P for named matches
match = re.match('(release-(?P<major>\d)\.(?P<minor>\d)\.(?P<revision>\d))', branch)
if match:
major = int(match.group('major'))
minor = int(match.group('minor'))
revision = int(match.group('revision'))
else:
major = -1
minor = -1
revision = -1
return [major,minor,revision]
def prevent_bad_merges(ui, repo, node, **kwargs):
ui.debug("in hook\n")
for rev in xrange(repo[node].rev(), len(repo)):
ui.debug("in loop\n")
# get context (change)
ctx = repo[rev]
ui.debug("got ctx\n")
if len(ctx.parents()) > 1:
ui.debug("got a merge\n")
branch = ctx.branch()
ui.debug(branch +"\n")
parent1 = ctx.parents()[0]
ui.debug("got parent1\n")
parent2 = ctx.parents()[1]
ui.debug("got parent2\n")
target_branch = repo[parent1.node()].branch()
ui.debug("got parent1 branch\n")
target_version = get_version(target_branch)
ui.debug("got parent1 version\n")
source_branch = repo[parent2.node()].branch()
ui.debug("got parent2 branch\n")
source_version = get_version(source_branch)
ui.debug("got parent2 version\n")
# This could happen if someone does
# hg update 1.1-branch
# hg branch 1.2-branch
# hg merge 1.0-branch
# which is a strange thing to do. So disallow it.
if target_branch != branch:
ui.warn('PREVENT BAD MERGE HOOK FAILED : \n'
'merging to a different branch from first parent '
'is just weird: please don\'t do that\n')
return True
ui.debug(source_branch, "\n")
ui.debug(str(source_version[0]), "\n")
#ui.debug("major:", source_version[0], "\n")
#ui.debug("minor:", source_version[1], "\n")
#ui.debug("revn :", source_version[2], "\n")
ui.debug(target_branch, "\n")
ui.debug(str(target_version[0]), "\n")
#ui.debug("major:", target_version[0], "\n")
#ui.debug("minor:", target_version[1], "\n")
#ui.debug("revn :", target_version[2], "\n")
# Check for backwards merge.
if compare_versions(source_version, target_version) > 0:
ui.warn('PREVENT BAD MERGE HOOK FAILED : \n'
'invalid backwards merge from %r to %r\n'
% (source_branch, target_branch))
return True
else:
ui.debug("Not a merge\n")
# Not merging: nothing more to check.
return False
精彩评论