hgext3rd/topic/__init__.py
branchmercurial-4.5
changeset 4075 dc247e648f43
parent 4073 d54a4b9c1d01
child 4078 da6ce6d446b9
equal deleted inserted replaced
4031:9a2db13b2e99 4075:dc247e648f43
    27 
    27 
    28 The 'stack' will show you information about the stack of commit belonging to
    28 The 'stack' will show you information about the stack of commit belonging to
    29 your current topic.
    29 your current topic.
    30 
    30 
    31 Topic is offering you aliases reference to changeset in your current topic
    31 Topic is offering you aliases reference to changeset in your current topic
    32 stack as 't#'. For example, 't1' refers to the root of your stack, 't2' to the
    32 stack as 's#'. For example, 's1' refers to the root of your stack, 's2' to the
    33 second commits, etc. The 'hg stack' command show these number.
    33 second commits, etc. The 'hg stack' command show these number. 's0' can be used
       
    34 to refer to the parent of the topic root. Updating using `hg up s0` will keep
       
    35 the topic active.
    34 
    36 
    35 Push behavior will change a bit with topic. When pushing to a publishing
    37 Push behavior will change a bit with topic. When pushing to a publishing
    36 repository the changesets will turn public and the topic data on them will fade
    38 repository the changesets will turn public and the topic data on them will fade
    37 away. The logic regarding pushing new heads will behave has before, ignore any
    39 away. The logic regarding pushing new heads will behave has before, ignore any
    38 topic related data. When pushing to a non-publishing repository (supporting
    40 topic related data. When pushing to a non-publishing repository (supporting
   130     patch,
   132     patch,
   131     phases,
   133     phases,
   132     registrar,
   134     registrar,
   133     scmutil,
   135     scmutil,
   134     templatefilters,
   136     templatefilters,
   135     templatekw,
       
   136     util,
   137     util,
   137 )
   138 )
   138 
   139 
   139 from . import (
   140 from . import (
   140     compat,
   141     compat,
   147     revset as topicrevset,
   148     revset as topicrevset,
   148     stack,
   149     stack,
   149     topicmap,
   150     topicmap,
   150 )
   151 )
   151 
   152 
   152 if util.safehasattr(registrar, 'command'):
       
   153     commandfunc = registrar.command
       
   154 else: # compat with hg < 4.3
       
   155     commandfunc = cmdutil.command
       
   156 
       
   157 cmdtable = {}
   153 cmdtable = {}
   158 command = commandfunc(cmdtable)
   154 command = registrar.command(cmdtable)
   159 colortable = {'topic.active': 'green',
   155 colortable = {'topic.active': 'green',
   160               'topic.list.troubledcount': 'red',
   156               'topic.list.troubledcount': 'red',
   161               'topic.list.headcount.multiple': 'yellow',
   157               'topic.list.headcount.multiple': 'yellow',
   162               'topic.list.behindcount': 'cyan',
   158               'topic.list.behindcount': 'cyan',
   163               'topic.list.behinderror': 'red',
   159               'topic.list.behinderror': 'red',
   179               # (first pick I could think off, update as needed
   175               # (first pick I could think off, update as needed
   180               'log.topic': 'green_background',
   176               'log.topic': 'green_background',
   181               'topic.active': 'green',
   177               'topic.active': 'green',
   182              }
   178              }
   183 
   179 
   184 __version__ = '0.10.1.dev'
   180 __version__ = '0.11.0.dev'
   185 
   181 
   186 testedwith = '4.3.3 4.4.2 4.5.2 4.6.2 4.7'
   182 testedwith = '4.3.3 4.4.2 4.5.2 4.6.2 4.7'
   187 minimumhgversion = '4.3'
   183 minimumhgversion = '4.3'
   188 buglink = 'https://bz.mercurial-scm.org/'
   184 buglink = 'https://bz.mercurial-scm.org/'
   189 
   185 
   229                 or not ui._knownconfig['devel'].get('random')):
   225                 or not ui._knownconfig['devel'].get('random')):
   230             extraitem('devel', 'randomseed',
   226             extraitem('devel', 'randomseed',
   231                       default=None,
   227                       default=None,
   232             )
   228             )
   233 
   229 
       
   230 templatekeyword = registrar.templatekeyword()
       
   231 
   234 def _contexttopic(self, force=False):
   232 def _contexttopic(self, force=False):
   235     if not (force or self.mutable()):
   233     if not (force or self.mutable()):
   236         return ''
   234         return ''
   237     return self.extra().get(constants.extrakey, '')
   235     return self.extra().get(constants.extrakey, '')
   238 context.basectx.topic = _contexttopic
   236 context.basectx.topic = _contexttopic
   239 
   237 
   240 def _contexttopicidx(self):
   238 def _contexttopicidx(self):
   241     topic = self.topic()
   239     topic = self.topic()
   242     if not topic:
   240     if not topic:
   243         # XXX we might want to include t0 here,
   241         # XXX we might want to include s0 here,
   244         # however t0 is related to  'currenttopic' which has no place here.
   242         # however s0 is related to  'currenttopic' which has no place here.
   245         return None
   243         return None
   246     revlist = stack.stack(self._repo, topic=topic)
   244     revlist = stack.stack(self._repo, topic=topic)
   247     try:
   245     try:
   248         return revlist.index(self.rev())
   246         return revlist.index(self.rev())
   249     except ValueError:
   247     except ValueError:
   255     except IndexError:
   253     except IndexError:
   256         # Lets move to the last ctx of the current topic
   254         # Lets move to the last ctx of the current topic
   257         return None
   255         return None
   258 context.basectx.topicidx = _contexttopicidx
   256 context.basectx.topicidx = _contexttopicidx
   259 
   257 
       
   258 stackrev = re.compile(r'^s\d+$')
   260 topicrev = re.compile(r'^t\d+$')
   259 topicrev = re.compile(r'^t\d+$')
   261 branchrev = re.compile(r'^b\d+$')
   260 branchrev = re.compile(r'^b\d+$')
   262 
   261 
   263 def _namemap(repo, name):
   262 def _namemap(repo, name):
   264     revs = None
   263     revs = None
   265     if topicrev.match(name):
   264     if stackrev.match(name):
       
   265         idx = int(name[1:])
       
   266         tname = topic = repo.currenttopic
       
   267         if topic:
       
   268             ttype = 'topic'
       
   269             revs = list(stack.stack(repo, topic=topic))
       
   270         else:
       
   271             ttype = 'branch'
       
   272             tname = branch = repo[None].branch()
       
   273             revs = list(stack.stack(repo, branch=branch))
       
   274     elif topicrev.match(name):
   266         idx = int(name[1:])
   275         idx = int(name[1:])
   267         ttype = 'topic'
   276         ttype = 'topic'
   268         tname = topic = repo.currenttopic
   277         tname = topic = repo.currenttopic
   269         if not tname:
   278         if not tname:
   270             raise error.Abort(_('cannot resolve "%s": no active topic') % name)
   279             raise error.Abort(_('cannot resolve "%s": no active topic') % name)
   277 
   286 
   278     if revs is not None:
   287     if revs is not None:
   279         try:
   288         try:
   280             r = revs[idx]
   289             r = revs[idx]
   281         except IndexError:
   290         except IndexError:
   282             msg = _('cannot resolve "%s": %s "%s" has only %d changesets')
   291             if ttype == 'topic':
       
   292                 msg = _('cannot resolve "%s": %s "%s" has only %d changesets')
       
   293             elif ttype == 'branch':
       
   294                 msg = _('cannot resolve "%s": %s "%s" has only %d non-public changesets')
   283             raise error.Abort(msg % (name, ttype, tname, len(revs) - 1))
   295             raise error.Abort(msg % (name, ttype, tname, len(revs) - 1))
   284         # b0 or t0 can be None
   296         # b0 or t0 or s0 can be None
   285         if r == -1 and idx == 0:
   297         if r == -1 and idx == 0:
   286             msg = _('the %s "%s" has no %s')
   298             msg = _('the %s "%s" has no %s')
   287             raise error.Abort(msg % (ttype, tname, name))
   299             raise error.Abort(msg % (ttype, tname, name))
   288         return [repo[r].node()]
   300         return [repo[r].node()]
   289     if name not in repo.topics:
   301     if name not in repo.topics:
   321     entry[1].append(('t', 'topic', '',
   333     entry[1].append(('t', 'topic', '',
   322                      _("topic to push"), _('TOPIC')))
   334                      _("topic to push"), _('TOPIC')))
   323 
   335 
   324     extensions.wrapfunction(cmdutil, 'buildcommittext', committextwrap)
   336     extensions.wrapfunction(cmdutil, 'buildcommittext', committextwrap)
   325     extensions.wrapfunction(merge, 'update', mergeupdatewrap)
   337     extensions.wrapfunction(merge, 'update', mergeupdatewrap)
   326     # We need to check whether t0 or b0 is passed to override the default update
   338     # We need to check whether t0 or b0 or s0 is passed to override the default update
   327     # behaviour of changing topic and I can't find a better way
   339     # behaviour of changing topic and I can't find a better way
   328     # to do that as scmutil.revsingle returns the rev number and hence we can't
   340     # to do that as scmutil.revsingle returns the rev number and hence we can't
   329     # plug into logic for this into mergemod.update().
   341     # plug into logic for this into mergemod.update().
   330     extensions.wrapcommand(commands.table, 'update', checkt0)
   342     extensions.wrapcommand(commands.table, 'update', checkt0)
   331 
   343 
   336     except (KeyError, AttributeError):
   348     except (KeyError, AttributeError):
   337         pass
   349         pass
   338 
   350 
   339     cmdutil.summaryhooks.add('topic', summaryhook)
   351     cmdutil.summaryhooks.add('topic', summaryhook)
   340 
   352 
   341     templatekw.keywords['topic'] = topickw
       
   342     # Wrap workingctx extra to return the topic name
   353     # Wrap workingctx extra to return the topic name
   343     extensions.wrapfunction(context.workingctx, '__init__', wrapinit)
   354     extensions.wrapfunction(context.workingctx, '__init__', wrapinit)
   344     # Wrap changelog.add to drop empty topic
   355     # Wrap changelog.add to drop empty topic
   345     extensions.wrapfunction(changelog.changelog, 'add', wrapadd)
   356     extensions.wrapfunction(changelog.changelog, 'add', wrapadd)
   346 
   357 
   508     if util.safehasattr(repo, 'names'):
   519     if util.safehasattr(repo, 'names'):
   509         repo.names.addnamespace(namespaces.namespace(
   520         repo.names.addnamespace(namespaces.namespace(
   510             'topics', 'topic', namemap=_namemap, nodemap=_nodemap,
   521             'topics', 'topic', namemap=_namemap, nodemap=_nodemap,
   511             listnames=lambda repo: repo.topics))
   522             listnames=lambda repo: repo.topics))
   512 
   523 
   513 def topickw(**args):
   524 @templatekeyword('topic', requires={'ctx'})
       
   525 def topickw(context, mapping):
   514     """:topic: String. The topic of the changeset"""
   526     """:topic: String. The topic of the changeset"""
   515     return args['ctx'].topic()
   527     ctx = context.resource(mapping, 'ctx')
       
   528     return ctx.topic()
   516 
   529 
   517 def wrapinit(orig, self, repo, *args, **kwargs):
   530 def wrapinit(orig, self, repo, *args, **kwargs):
   518     orig(self, repo, *args, **kwargs)
   531     orig(self, repo, *args, **kwargs)
   519     if getattr(repo, 'currenttopic', ''):
   532     if getattr(repo, 'currenttopic', ''):
   520         self._extra[constants.extrakey] = repo.currenttopic
   533         self._extra[constants.extrakey] = repo.currenttopic
   645             repo.invalidate()
   658             repo.invalidate()
   646         return
   659         return
   647 
   660 
   648     ct = repo.currenttopic
   661     ct = repo.currenttopic
   649     if clear:
   662     if clear:
   650         empty = stack.stack(repo, topic=ct).changesetcount == 0
   663         if ct:
   651         if empty:
   664             empty = stack.stack(repo, topic=ct).changesetcount == 0
   652             if ct:
   665             if empty:
   653                 ui.status(_('clearing empty topic "%s"\n') % ct)
   666                 ui.status(_('clearing empty topic "%s"\n') % ct)
   654         return _changecurrenttopic(repo, None)
   667         return _changecurrenttopic(repo, None)
   655 
   668 
   656     if topic:
   669     if topic:
   657         if not ct:
   670         if not ct:
  1150         # current topic. This is right for merge but wrong for rebase. We check
  1163         # current topic. This is right for merge but wrong for rebase. We check
  1151         # if rebase is running and update the currenttopic to topic of new
  1164         # if rebase is running and update the currenttopic to topic of new
  1152         # rebased commit. We have explicitly stored in config if rebase is
  1165         # rebased commit. We have explicitly stored in config if rebase is
  1153         # running.
  1166         # running.
  1154         ot = repo.currenttopic
  1167         ot = repo.currenttopic
  1155         empty = stack.stack(repo, topic=ot).changesetcount == 0
       
  1156         if repo.ui.hasconfig('experimental', 'topicrebase'):
  1168         if repo.ui.hasconfig('experimental', 'topicrebase'):
  1157             isrebase = True
  1169             isrebase = True
  1158         if repo.ui.configbool('_internal', 'keep-topic'):
  1170         if repo.ui.configbool('_internal', 'keep-topic'):
  1159             ist0 = True
  1171             ist0 = True
  1160         if ((not partial and not branchmerge) or isrebase) and not ist0:
  1172         if ((not partial and not branchmerge) or isrebase) and not ist0:
  1164                 t = pctx.topic()
  1176                 t = pctx.topic()
  1165             with repo.vfs.open('topic', 'w') as f:
  1177             with repo.vfs.open('topic', 'w') as f:
  1166                 f.write(t)
  1178                 f.write(t)
  1167             if t and t != ot:
  1179             if t and t != ot:
  1168                 repo.ui.status(_("switching to topic %s\n") % t)
  1180                 repo.ui.status(_("switching to topic %s\n") % t)
  1169             if ot and not t and empty:
  1181             if ot and not t:
  1170                 repo.ui.status(_('clearing empty topic "%s"\n') % ot)
  1182                 empty = stack.stack(repo, topic=ot).changesetcount == 0
       
  1183                 if empty:
       
  1184                     repo.ui.status(_('clearing empty topic "%s"\n') % ot)
  1171         elif ist0:
  1185         elif ist0:
  1172             repo.ui.status(_("preserving the current topic '%s'\n") % ot)
  1186             repo.ui.status(_("preserving the current topic '%s'\n") % ot)
  1173         return ret
  1187         return ret
  1174     finally:
  1188     finally:
  1175         wlock.release()
  1189         wlock.release()
  1176 
  1190 
  1177 def checkt0(orig, ui, repo, node=None, rev=None, *args, **kwargs):
  1191 def checkt0(orig, ui, repo, node=None, rev=None, *args, **kwargs):
  1178 
  1192 
  1179     thezeros = set(['t0', 'b0'])
  1193     thezeros = set(['t0', 'b0', 's0'])
  1180     backup = repo.ui.backupconfig('_internal', 'keep-topic')
  1194     backup = repo.ui.backupconfig('_internal', 'keep-topic')
  1181     try:
  1195     try:
  1182         if node in thezeros or rev in thezeros:
  1196         if node in thezeros or rev in thezeros:
  1183             repo.ui.setconfig('_internal', 'keep-topic', 'yes',
  1197             repo.ui.setconfig('_internal', 'keep-topic', 'yes',
  1184                               source='topic-extension')
  1198                               source='topic-extension')