Mercurial > evolve
view hgext3rd/topic/__init__.py @ 1925:8f8a48a2e97d
stack: whitespace
author | Sean Farley <sean@farley.io> |
---|---|
date | Mon, 21 Mar 2016 21:30:14 -0700 |
parents | a840c5b5bbaf |
children | 880aac9dbfa6 |
line wrap: on
line source
# __init__.py - topic extension # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. """support for topic branches Topic branches are lightweight branches which disappear when changes are finalized. This is sort of similar to a bookmark, but it applies to a whole series instead of a single revision. """ import contextlib import re from mercurial.i18n import _ from mercurial import branchmap from mercurial import bundle2 from mercurial import changegroup from mercurial import cmdutil from mercurial import commands from mercurial import context from mercurial import discovery as discoverymod from mercurial import error from mercurial import exchange from mercurial import extensions from mercurial import localrepo from mercurial import lock from mercurial import merge from mercurial import namespaces from mercurial import node from mercurial import obsolete from mercurial import patch from mercurial import phases from mercurial import util from mercurial import wireproto from . import constants from . import revset as topicrevset from . import destination from . import stack from . import topicmap from . import discovery cmdtable = {} command = cmdutil.command(cmdtable) colortable = {'topic.stack.index': 'yellow', 'topic.stack.state.base': 'dim', 'topic.stack.state.clean': 'green', 'topic.stack.index.current': 'cyan', # random pick 'topic.stack.state.current': 'cyan bold', # random pick 'topic.stack.desc.current': 'cyan', # random pick 'topic.stack.state.unstable': 'red', } testedwith = '3.7' def _contexttopic(self): return self.extra().get(constants.extrakey, '') context.basectx.topic = _contexttopic topicrev = re.compile(r'^t\d+$') def _namemap(repo, name): if topicrev.match(name): idx = int(name[1:]) topic = repo.currenttopic if not topic: raise error.Abort(_('cannot resolve "%s": no active topic') % name) revs = list(stack.getstack(repo, topic)) try: r = revs[idx] except IndexError: msg = _('cannot resolve "%s": topic "%s" has only %d changesets') raise error.Abort(msg % (name, topic, len(revs))) return [repo[r].node()] return [ctx.node() for ctx in repo.set('not public() and extra(topic, %s)', name)] def _nodemap(repo, node): ctx = repo[node] t = ctx.topic() if t and ctx.phase() > phases.public: return [t] return [] def uisetup(ui): destination.setupdest() @contextlib.contextmanager def usetopicmap(repo): """use awful monkey patching to update the topic cache""" oldbranchcache = branchmap.branchcache oldfilename = branchmap._filename oldread = branchmap.read oldcaches = getattr(repo, '_branchcaches', {}) try: branchmap.branchcache = topicmap.topiccache branchmap._filename = topicmap._filename branchmap.read = topicmap.readtopicmap repo._branchcaches = getattr(repo, '_topiccaches', {}) yield repo._topiccaches = repo._branchcaches finally: repo._branchcaches = oldcaches branchmap.branchcache = oldbranchcache branchmap._filename = oldfilename branchmap.read = oldread def cgapply(orig, repo, *args, **kwargs): with usetopicmap(repo): return orig(repo, *args, **kwargs) def reposetup(ui, repo): orig = repo.__class__ if not isinstance(repo, localrepo.localrepository): return # this can be a peer in the ssh case (puzzling) class topicrepo(repo.__class__): def _restrictcapabilities(self, caps): caps = super(topicrepo, self)._restrictcapabilities(caps) caps.add('topics') return caps def commit(self, *args, **kwargs): backup = self.ui.backupconfig('ui', 'allowemptycommit') try: if repo.currenttopic != repo['.'].topic(): # bypass the core "nothing changed" logic self.ui.setconfig('ui', 'allowemptycommit', True) return orig.commit(self, *args, **kwargs) finally: self.ui.restoreconfig(backup) def commitctx(self, ctx, error=None): if isinstance(ctx, context.workingcommitctx): current = self.currenttopic if current: ctx.extra()[constants.extrakey] = current if (isinstance(ctx, context.memctx) and ctx.extra().get('amend_source') and ctx.topic() and not self.currenttopic): # we are amending and need to remove a topic del ctx.extra()[constants.extrakey] with usetopicmap(self): return orig.commitctx(self, ctx, error=error) @property def topics(self): topics = set(['', self.currenttopic]) for c in self.set('not public()'): topics.add(c.topic()) topics.remove('') return topics @property def currenttopic(self): return self.vfs.tryread('topic') def branchmap(self, topic=True): if not topic: super(topicrepo, self).branchmap() with usetopicmap(self): branchmap.updatecache(self) return self._topiccaches[self.filtername] def destroyed(self, *args, **kwargs): with usetopicmap(self): return super(topicrepo, self).destroyed(*args, **kwargs) def invalidatecaches(self): super(topicrepo, self).invalidatecaches() if '_topiccaches' in vars(self.unfiltered()): self.unfiltered()._topiccaches.clear() def peer(self): peer = super(topicrepo, self).peer() if getattr(peer, '_repo', None) is not None: # localpeer class topicpeer(peer.__class__): def branchmap(self): usetopic = not self._repo.publishing() return self._repo.branchmap(topic=usetopic) peer.__class__ = topicpeer return peer repo.__class__ = topicrepo if util.safehasattr(repo, 'names'): repo.names.addnamespace(namespaces.namespace( 'topics', 'topic', namemap=_namemap, nodemap=_nodemap, listnames=lambda repo: repo.topics)) @command('topics [TOPIC]', [ ('', 'clear', False, 'clear active topic if any'), ('', 'change', '', 'revset of existing revisions to change topic'), ('l', 'list', False, 'show the stack of changeset in the topic'), ] + commands.formatteropts) def topics(ui, repo, topic='', clear=False, change=None, list=False, **opts): """View current topic, set current topic, or see all topics.""" if list: if clear or change: raise error.Abort(_("cannot use --clear or --change with --list")) return stack.showstack(ui, repo, topic, opts) if change: if not obsolete.isenabled(repo, obsolete.createmarkersopt): raise error.Abort(_('must have obsolete enabled to use --change')) if not topic and not clear: raise error.Abort('changing topic requires a topic name or --clear') if any(not c.mutable() for c in repo.set('%r and public()', change)): raise error.Abort("can't change topic of a public change") rewrote = 0 needevolve = False l = repo.lock() txn = repo.transaction('rewrite-topics') try: for c in repo.set('%r', change): def filectxfn(repo, ctx, path): try: return c[path] except error.ManifestLookupError: return None fixedextra = dict(c.extra()) ui.debug('old node id is %s\n' % node.hex(c.node())) ui.debug('origextra: %r\n' % fixedextra) newtopic = None if clear else topic oldtopic = fixedextra.get(constants.extrakey, None) if oldtopic == newtopic: continue if clear: del fixedextra[constants.extrakey] else: fixedextra[constants.extrakey] = topic if 'amend_source' in fixedextra: # TODO: right now the commitctx wrapper in # topicrepo overwrites the topic in extra if # amend_source is set to support 'hg commit # --amend'. Support for amend should be adjusted # to not be so invasive. del fixedextra['amend_source'] ui.debug('changing topic of %s from %s to %s\n' % ( c, oldtopic, newtopic)) ui.debug('fixedextra: %r\n' % fixedextra) mc = context.memctx( repo, (c.p1().node(), c.p2().node()), c.description(), c.files(), filectxfn, user=c.user(), date=c.date(), extra=fixedextra) newnode = repo.commitctx(mc) ui.debug('new node id is %s\n' % node.hex(newnode)) needevolve = needevolve or (len(c.children()) > 0) obsolete.createmarkers(repo, [(c, (repo[newnode],))]) rewrote += 1 txn.close() except: try: txn.abort() finally: repo.invalidate() raise finally: lock.release(txn, l) ui.status('changed topic on %d changes\n' % rewrote) if needevolve: evolvetarget = 'topic(%s)' % topic if topic else 'not topic()' ui.status('please run hg evolve --rev "%s" now\n' % evolvetarget) if clear: if repo.vfs.exists('topic'): repo.vfs.unlink('topic') return if topic: with repo.vfs.open('topic', 'w') as f: f.write(topic) return current = repo.currenttopic for t in sorted(repo.topics): marker = '*' if t == current else ' ' ui.write(' %s %s\n' % (marker, t)) def summaryhook(ui, repo): t = repo.currenttopic if not t: return # i18n: column positioning for "hg summary" ui.write(_("topic: %s\n") % t) def commitwrap(orig, ui, repo, *args, **opts): if opts.get('topic'): t = opts['topic'] with repo.vfs.open('topic', 'w') as f: f.write(t) return orig(ui, repo, *args, **opts) def committextwrap(orig, repo, ctx, subs, extramsg): ret = orig(repo, ctx, subs, extramsg) t = repo.currenttopic if t: ret = ret.replace("\nHG: branch", "\nHG: topic '%s'\nHG: branch" % t) return ret def mergeupdatewrap(orig, repo, node, branchmerge, force, *args, **kwargs): partial = bool(len(args)) or 'matcher' in kwargs wlock = repo.wlock() try: ret = orig(repo, node, branchmerge, force, *args, **kwargs) if not partial and not branchmerge: ot = repo.currenttopic t = '' pctx = repo[node] if pctx.phase() > phases.public: t = pctx.topic() with repo.vfs.open('topic', 'w') as f: f.write(t) if t and t != ot: repo.ui.status(_("switching to topic %s\n") % t) return ret finally: wlock.release() def _fixrebase(loaded): if not loaded: return def savetopic(ctx, extra): if ctx.topic(): extra[constants.extrakey] = ctx.topic() def newmakeextrafn(orig, copiers): return orig(copiers + [savetopic]) rebase = extensions.find("rebase") extensions.wrapfunction(rebase, '_makeextrafn', newmakeextrafn) def _exporttopic(seq, ctx): topic = ctx.topic() if topic: return 'EXP-Topic %s' % topic return None def _importtopic(repo, patchdata, extra, opts): if 'topic' in patchdata: extra['topic'] = patchdata['topic'] extensions.afterloaded('rebase', _fixrebase) entry = extensions.wrapcommand(commands.table, 'commit', commitwrap) entry[1].append(('t', 'topic', '', _("use specified topic"), _('TOPIC'))) extensions.wrapfunction(cmdutil, 'buildcommittext', committextwrap) extensions.wrapfunction(merge, 'update', mergeupdatewrap) extensions.wrapfunction(discoverymod, '_headssummary', discovery._headssummary) extensions.wrapfunction(wireproto, 'branchmap', discovery.wireprotobranchmap) extensions.wrapfunction(wireproto, '_capabilities', discovery.wireprotocaps) extensions.wrapfunction(bundle2, 'handlecheckheads', discovery.handlecheckheads) # we need a proper wrape b2 part stuff bundle2.handlecheckheads.params = frozenset() bundle2.parthandlermapping['check:heads'] = bundle2.handlecheckheads extensions.wrapfunction(exchange, '_pushb2phases', discovery._pushb2phases) extensions.wrapfunction(changegroup.cg1unpacker, 'apply', cgapply) exchange.b2partsgenmapping['phase'] = exchange._pushb2phases topicrevset.modsetup() cmdutil.summaryhooks.add('topic', summaryhook) if util.safehasattr(cmdutil, 'extraexport'): cmdutil.extraexport.append('topic') cmdutil.extraexportmap['topic'] = _exporttopic if util.safehasattr(cmdutil, 'extrapreimport'): cmdutil.extrapreimport.append('topic') cmdutil.extrapreimportmap['topic'] = _importtopic if util.safehasattr(patch, 'patchheadermap'): patch.patchheadermap.append(('EXP-Topic', 'topic'))