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 |
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') |