[PATCH] Proposed implementaiton of .hgbranches
Andrew Beekhof
beekhof at gmail.com
Mon Jul 31 09:31:12 UTC 2006
Under Chris Mason's direction I have implemented .hgbranches support.
Possible points of interest:
* I chose to add a -b option to tag and tags rather than add branch
and branches
* I added a -b option to identify
* Performance - the use of reachable() might be suboptimal
It's a rather long patch so I will also attach it.
It can also be obtained by pulling from http://hg.beekhof.net/hg/mercurial
For people using Chris' hg-cvs-import, you should pull from
http://hg.beekhof.net/hg/cvs-import to obtain a version that works
with this patch.
regards,
andrew
Exporting patch:
# HG changeset patch
# User Andrew Beekhof <beekhof at gmail.com>
# Date 1154337087 -7200
# Node ID 20dabd5b7d2ce1c5183c3dc39a1fcfed6b0d7a1c
# Parent ad4155e757da149262b6596928404b9aaf5a7e1e
Initial .hgbranches support
* Adds -b options to tags, tag, and id
* Changed the implementation of -b for heads() and doupdate()
* Includes list of branches for cmd=tags in web interface
diff -r ad4155e757da149262b6596928404b9aaf5a7e1e -r
20dabd5b7d2ce1c5183c3dc39a1fcfed6b0d7a1c mercurial/commands.py
--- a/mercurial/commands.py Mon Jul 31 00:47:43 2006 -0500
+++ b/mercurial/commands.py Mon Jul 31 11:11:27 2006 +0200
@@ -461,7 +461,7 @@ class changeset_printer(object):
self.ui = ui
self.repo = repo
- def show(self, rev=0, changenode=None, brinfo=None):
+ def show(self, rev=0, changenode=None, branch=False):
'''show a single changeset or file revision'''
log = self.repo.changelog
if changenode is None:
@@ -493,8 +493,13 @@ class changeset_printer(object):
for parent in parents:
self.ui.write(_("parent: %d:%s\n") % parent)
- if brinfo and changenode in brinfo:
- br = brinfo[changenode]
+ if branch:
+ br = []
+ brinfo = self.repo.nodebranches(changenode)
+ brinfo.reverse()
+ for b_name, cs in brinfo:
+ br.append(b_name)
+
self.ui.write(_("branch: %s\n") % " ".join(br))
self.ui.debug(_("manifest: %d:%s\n") %
@@ -1657,14 +1662,11 @@ def heads(ui, repo, **opts):
heads = repo.heads(repo.lookup(opts['rev']))
else:
heads = repo.heads()
- br = None
- if opts['branches']:
- br = repo.branchlookup(heads)
displayer = show_changeset(ui, repo, opts)
for n in heads:
- displayer.show(changenode=n, brinfo=br)
-
-def identify(ui, repo):
+ displayer.show(changenode=n, branch=opts['branches'])
+
+def identify(ui, repo, **opts):
"""print information about the working copy
Print a short summary of the current state of the repo.
@@ -1680,14 +1682,34 @@ def identify(ui, repo):
hexfunc = ui.verbose and hex or short
modified, added, removed, deleted, unknown = repo.changes()
- output = ["%s%s" %
- ('+'.join([hexfunc(parent) for parent in parents]),
- (modified or added or removed or deleted) and "+" or "")]
+ for parent in parents:
+ p_entries = []
+ if opts['cs']:
+ p_entries.append("%d:%s" % (repo.changelog.rev(parent),
+ hexfunc(parent)))
+ else:
+ p_entries.append(hexfunc(parent))
+
+ output = ["%s%s" %
+ ('+'.join(p_entries),
+ (modified or added or removed or deleted) and "+" or "")]
if not ui.quiet:
# multiple tags for a single parent separated by '/'
parenttags = ['/'.join(tags)
for tags in map(repo.nodetags, parents) if tags]
+
+ if opts['branches']:
+ for parent in parents:
+ brinfo = repo.nodebranches(parent)
+ brinfo.reverse()
+
+ # All branches, or just the last one?
+ if brinfo:
+ parenttags.append(brinfo[0][0])
+ #for br in brinfo:
+ # parenttags.append(br[0])
+
# tags for multiple parents separated by ' + '
if parenttags:
output.append(' + '.join(parenttags))
@@ -2002,11 +2024,7 @@ def log(ui, repo, *pats, **opts):
if miss:
continue
- br = None
- if opts['branches']:
- br = repo.branchlookup([repo.changelog.node(rev)])
-
- displayer.show(rev, brinfo=br)
+ displayer.show(rev, opts['branches'])
if opts['patch']:
prev = (parents and parents[0]) or nullid
dodiff(du, du, repo, prev, changenode, match=matchfn)
@@ -2116,13 +2134,10 @@ def parents(ui, repo, file_=None, rev=No
else:
p = repo.dirstate.parents()
- br = None
- if branches is not None:
- br = repo.branchlookup(p)
displayer = show_changeset(ui, repo, opts)
for n in p:
if n != nullid:
- displayer.show(changenode=n, brinfo=br)
+ displayer.show(changenode=n, branch=branches)
def paths(ui, repo, search=None):
"""show definition of symbolic path names
@@ -2703,10 +2718,13 @@ def tag(ui, repo, name, rev_=None, **opt
raise util.Abort(_('outstanding uncommitted merges'))
r = hex(p1)
- repo.tag(name, r, opts['local'], opts['message'], opts['user'],
- opts['date'])
-
-def tags(ui, repo):
+ if opts['branch']:
+ repo.branch(name, r, opts['message'], opts['user'], opts['date'])
+ else:
+ repo.tag(name, r, opts['local'], opts['message'], opts['user'],
+ opts['date'])
+
+def tags(ui, repo, **opts):
"""list repository tags
List the repository tags.
@@ -2714,7 +2732,11 @@ def tags(ui, repo):
This lists both regular and local tags.
"""
- l = repo.tagslist()
+ if opts['branches']:
+ l = repo.branchlist()
+ else:
+ l = repo.tagslist()
+
l.reverse()
for t, n in l:
try:
@@ -2732,10 +2754,7 @@ def tip(ui, repo, **opts):
Show the tip revision.
"""
n = repo.changelog.tip()
- br = None
- if opts['branches']:
- br = repo.branchlookup([n])
- show_changeset(ui, repo, opts).show(changenode=n, brinfo=br)
+ show_changeset(ui, repo, opts).show(changenode=n, branch=opts['branches'])
if opts['patch']:
dodiff(ui, ui, repo, repo.changelog.parents(n)[0], n)
@@ -2805,24 +2824,40 @@ def doupdate(ui, repo, node=None, merge=
def doupdate(ui, repo, node=None, merge=False, clean=False, force=None,
branch=None, **opts):
if branch:
- br = repo.branchlookup(branch=branch)
- found = []
- for x in br:
- if branch in br[x]:
- found.append(x)
- if len(found) > 1:
- ui.warn(_("Found multiple heads for %s\n") % branch)
- for x in found:
- show_changeset(ui, repo, opts).show(changenode=x, brinfo=br)
- return 1
- if len(found) == 1:
- node = found[0]
- ui.warn(_("Using head %s for branch %s\n") % (short(node), branch))
- else:
+ cs_match = None
+ brinfo = repo.branchlist()
+ for b_name, cs in brinfo:
+ if b_name == branch:
+ cs_match = cs
+
+ # Fall back to regular tags?
+ #if not cs_match:
+ # brinfo = repo.tagslist()
+ # for b_name, cs in brinfo:
+ # if b_name == branch:
+ # cs_match = cs
+
+ if not cs_match:
ui.warn(_("branch %s not found\n") % (branch))
return 1
+
+ heads = repo.heads(cs_match)
+ if not heads:
+ ui.warn(_("No heads found for branch %s\n") % (branch))
+ return 1
+
+ elif len(heads) > 1:
+ ui.warn(_("Found multiple heads for %s\n") % branch)
+ for x in heads:
+ show_changeset(ui, repo, opts).show(changenode=x)
+ return 1
+
+ node = heads[0]
+ ui.warn(_("Using head %s for branch %s\n") % (short(node), branch))
+
else:
node = node and repo.lookup(node) or repo.changelog.tip()
+
return repo.update(node, allow=merge, force=clean, forcemerge=force)
def verify(ui, repo):
@@ -2997,7 +3032,11 @@ table = {
('', 'template', '', _('display with template'))],
_('hg heads [-b] [-r <rev>]')),
"help": (help_, [], _('hg help [COMMAND]')),
- "identify|id": (identify, [], _('hg identify')),
+ "identify|id":
+ (identify,
+ [('b', 'branches', None, _('show branches')),
+ ('c', 'cs', None, _('show changeset'))],
+ _('hg identify [-b] [-c]')),
"import|patch":
(import_,
[('p', 'strip', 1,
@@ -3176,9 +3215,13 @@ table = {
('m', 'message', '', _('message for tag commit log entry')),
('d', 'date', '', _('record datecode as commit date')),
('u', 'user', '', _('record user as commiter')),
- ('r', 'rev', '', _('revision to tag'))],
- _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
- "tags": (tags, [], _('hg tags')),
+ ('r', 'rev', '', _('revision to tag')),
+ ('b', 'branch', None, _('create tag as a branch'))],
+ _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] [-b] NAME')),
+ "tags":
+ (tags,
+ [('b', 'branches', None, _('show branches'))],
+ _('hg tags [-b]')),
"tip":
(tip,
[('b', 'branches', None, _('show branches')),
diff -r ad4155e757da149262b6596928404b9aaf5a7e1e -r
20dabd5b7d2ce1c5183c3dc39a1fcfed6b0d7a1c mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py Mon Jul 31 00:47:43 2006 -0500
+++ b/mercurial/hgweb/hgweb_mod.py Mon Jul 31 11:11:27 2006 +0200
@@ -528,8 +528,17 @@ class hgweb(object):
cl = self.repo.changelog
mf = cl.read(cl.tip())[0]
- i = self.repo.tagslist()
- i.reverse()
+ # Include any branches (newer ones first) before regular tags
+ branches = self.repo.branchlist()
+ tags = self.repo.tagslist()
+ tags.reverse()
+
+ if branches:
+ branches.reverse()
+ branches.extend(tags)
+ i = branches
+ else:
+ i = tags
def entries(notip=False, **map):
parity = 0
diff -r ad4155e757da149262b6596928404b9aaf5a7e1e -r
20dabd5b7d2ce1c5183c3dc39a1fcfed6b0d7a1c mercurial/localrepo.py
--- a/mercurial/localrepo.py Mon Jul 31 00:47:43 2006 -0500
+++ b/mercurial/localrepo.py Mon Jul 31 11:11:27 2006 +0200
@@ -74,6 +74,9 @@ class localrepository(repo.repository):
self.encodepats = None
self.decodepats = None
self.transhandle = None
+
+ self.branchcache = None
+ self.branchcachelist = None
if create:
if not os.path.exists(path):
@@ -213,31 +216,31 @@ class localrepository(repo.repository):
self.commit(['.hgtags'], message, user, date)
self.hook('tag', node=node, tag=name, local=local)
+ def parsetag(self, line, context, cache):
+ if not line:
+ return
+ s = line.split(" ", 1)
+ if len(s) != 2:
+ self.ui.warn(_("%s: cannot parse entry\n") % context)
+ return
+ node, key = s
+ key = key.strip()
+ try:
+ bin_n = bin(node)
+ except TypeError:
+ self.ui.warn(_("%s: node '%s' is not well formed\n") %
+ (context, node))
+ return
+ if bin_n not in self.changelog.nodemap:
+ self.ui.warn(_("%s: branch/tag '%s' refers to unknown node\n") %
+ (context, key))
+ return
+ cache[key] = bin_n
+
def tags(self):
'''return a mapping of tag to node'''
if not self.tagscache:
self.tagscache = {}
-
- def parsetag(line, context):
- if not line:
- return
- s = l.split(" ", 1)
- if len(s) != 2:
- self.ui.warn(_("%s: cannot parse entry\n") % context)
- return
- node, key = s
- key = key.strip()
- try:
- bin_n = bin(node)
- except TypeError:
- self.ui.warn(_("%s: node '%s' is not well formed\n") %
- (context, node))
- return
- if bin_n not in self.changelog.nodemap:
- self.ui.warn(_("%s: tag '%s' refers to unknown node\n") %
- (context, key))
- return
- self.tagscache[key] = bin_n
# read the tags file from each head, ending with the tip,
# and add each tag found to the map, with "newer" ones
@@ -253,14 +256,15 @@ class localrepository(repo.repository):
count = 0
for l in fl.read(fn).splitlines():
count += 1
- parsetag(l, _(".hgtags (rev %d:%s), line %d") %
- (rev, short(node), count))
+ self.parsetag(l, _(".hgtags (rev %d:%s), line %d") %
+ (rev, short(node), count), self.tagscache)
try:
f = self.opener("localtags")
count = 0
for l in f:
count += 1
- parsetag(l, _("localtags, line %d") % count)
+ self.parsetag(l, _("localtags, line %d") % count,
+ self.tagscache)
except IOError:
pass
@@ -287,6 +291,98 @@ class localrepository(repo.repository):
for t, n in self.tags().items():
self.nodetagscache.setdefault(n, []).append(t)
return self.nodetagscache.get(node, [])
+
+ def branch(self, name, node, message=None, user=None, date=None):
+ '''create a branch based on a revision.
+
+ the branch is stored in the .hgbranches file, and a new
+ changeset is committed with the change.
+
+ keyword arguments:
+
+ message: commit message to use if committing
+
+ user: name of user to use if committing
+
+ date: date tuple to use if committing'''
+
+ for c in self.tag_disallowed:
+ if c in name:
+ raise util.Abort(_('%r cannot be used in a branch name') % c)
+
+ for x in self.changes():
+ if '.hgbranches' in x:
+ raise util.Abort(_('working copy of .hgbranches is changed '
+ '(please commit .hgbranches manually)'))
+
+ self.hook('prebranch', throw=True, node=node, tag=name)
+
+ self.wfile('.hgbranches', 'ab').write('%s %s\n' % (node, name))
+ if self.dirstate.state('.hgbranches') == '?':
+ self.add(['.hgbranches'])
+
+ if not message:
+ message = _('Added branch %s for changeset %s') % (name, node)
+
+ self.commit(['.hgbranches'], message, user, date)
+ self.hook('branch', node=node, tag=name)
+
+ def all_branches(self):
+ '''return a mapping of tag to node'''
+
+ # read the branch file from each head, ending with the tip,
+ # and add each branch found to the map, with "newer" ones
+ # taking precedence
+
+ if self.branchcache != None:
+ return self.branchcache
+
+ self.branchcache = {}
+ heads = self.heads()
+ heads.reverse()
+
+ fl = self.file(".hgbranches")
+ for node in heads:
+ change = self.changelog.read(node)
+ rev = self.changelog.rev(node)
+ fn, ff = self.manifest.find(change[0], '.hgbranches')
+ if fn is None: continue
+ count = 0
+ for l in fl.read(fn).splitlines():
+ count += 1
+ self.parsetag(l, _(".hgbranches (rev %d:%s), line %d") %
+ (rev, short(node), count), self.branchcache)
+
+ return self.branchcache
+
+ def branchlist(self):
+ '''return a list of tags ordered by revision'''
+ if self.branchcachelist != None:
+ return self.branchcachelist
+
+ l = []
+ for t, n in self.all_branches().items():
+ try:
+ r = self.changelog.rev(n)
+ except:
+ r = -2 # sort to the beginning of the list if unknown
+ l.append((r, t, n))
+
+ l.sort()
+ self.branchcachelist = [(t, n) for r, t, n in l]
+ return self.branchcachelist
+
+ def nodebranches(self, node):
+ if not node:
+ return []
+
+ l = []
+ brinfo = self.branchlist()
+ reachable = self.changelog.reachable(node)
+ for t, n in brinfo:
+ if n in reachable:
+ l.append((r, t, n))
+ return l
def lookup(self, key):
try:
@@ -845,115 +941,10 @@ class localrepository(repo.repository):
heads.sort()
return [n for (r, n) in heads]
- # branchlookup returns a dict giving a list of branches for
- # each head. A branch is defined as the tag of a node or
- # the branch of the node's parents. If a node has multiple
- # branch tags, tags are eliminated if they are visible from other
- # branch tags.
- #
- # So, for this graph: a->b->c->d->e
- # \ /
- # aa -----/
- # a has tag 2.6.12
- # d has tag 2.6.13
- # e would have branch tags for 2.6.12 and 2.6.13. Because the node
- # for 2.6.12 can be reached from the node 2.6.13, that is eliminated
- # from the list.
- #
- # It is possible that more than one head will have the same branch tag.
- # callers need to check the result for multiple heads under the same
- # branch tag if that is a problem for them (ie checkout of a specific
- # branch).
- #
- # passing in a specific branch will limit the depth of the search
- # through the parents. It won't limit the branches returned in the
- # result though.
- def branchlookup(self, heads=None, branch=None):
- if not heads:
- heads = self.heads()
- headt = [ h for h in heads ]
- chlog = self.changelog
- branches = {}
- merges = []
- seenmerge = {}
-
- # traverse the tree once for each head, recording in the branches
- # dict which tags are visible from this head. The branches
- # dict also records which tags are visible from each tag
- # while we traverse.
- while headt or merges:
- if merges:
- n, found = merges.pop()
- visit = [n]
- else:
- h = headt.pop()
- visit = [h]
- found = [h]
- seen = {}
- while visit:
- n = visit.pop()
- if n in seen:
- continue
- pp = chlog.parents(n)
- tags = self.nodetags(n)
- if tags:
- for x in tags:
- if x == 'tip':
- continue
- for f in found:
- branches.setdefault(f, {})[n] = 1
- branches.setdefault(n, {})[n] = 1
- break
- if n not in found:
- found.append(n)
- if branch in tags:
- continue
- seen[n] = 1
- if pp[1] != nullid and n not in seenmerge:
- merges.append((pp[1], [x for x in found]))
- seenmerge[n] = 1
- if pp[0] != nullid:
- visit.append(pp[0])
- # traverse the branches dict, eliminating branch tags from each
- # head that are visible from another branch tag for that head.
- out = {}
- viscache = {}
- for h in heads:
- def visible(node):
- if node in viscache:
- return viscache[node]
- ret = {}
- visit = [node]
- while visit:
- x = visit.pop()
- if x in viscache:
- ret.update(viscache[x])
- elif x not in ret:
- ret[x] = 1
- if x in branches:
- visit[len(visit):] = branches[x].keys()
- viscache[node] = ret
- return ret
- if h not in branches:
- continue
- # O(n^2), but somewhat limited. This only searches the
- # tags visible from a specific head, not all the tags in the
- # whole repo.
- for b in branches[h]:
- vis = False
- for bb in branches[h].keys():
- if b != bb:
- if b in visible(bb):
- vis = True
- break
- if not vis:
- l = out.setdefault(h, [])
- l[len(l):] = self.nodetags(b)
- return out
-
def branches(self, nodes):
if not nodes:
nodes = [self.changelog.tip()]
+
b = []
for n in nodes:
t = n
-------------- next part --------------
A non-text attachment was scrubbed...
Name: hg-branches.export
Type: application/octet-stream
Size: 21025 bytes
Desc: not available
URL: <http://lists.mercurial-scm.org/pipermail/mercurial/attachments/20060731/5fd43ddc/attachment-0001.obj>
More information about the Mercurial
mailing list