Mercurial > evolve
comparison hgext3rd/topic/__init__.py @ 6360:e959390490c2
branching: merge with stable
author | Anton Shestakov <av6@dwimlabs.net> |
---|---|
date | Fri, 09 Dec 2022 15:01:59 +0400 |
parents | 453861da6922 |
children | 573174ef1bbf |
comparison
equal
deleted
inserted
replaced
6359:cb9e77506cbc | 6360:e959390490c2 |
---|---|
166 from mercurial import ( | 166 from mercurial import ( |
167 bookmarks, | 167 bookmarks, |
168 changelog, | 168 changelog, |
169 cmdutil, | 169 cmdutil, |
170 commands, | 170 commands, |
171 configitems, | |
171 context, | 172 context, |
173 encoding, | |
172 error, | 174 error, |
173 exchange, | 175 exchange, |
174 extensions, | 176 extensions, |
175 hg, | 177 hg, |
176 localrepo, | 178 localrepo, |
229 # default color to help log output and thg | 231 # default color to help log output and thg |
230 # (first pick I could think off, update as needed | 232 # (first pick I could think off, update as needed |
231 b'log.topic': b'green_background', | 233 b'log.topic': b'green_background', |
232 } | 234 } |
233 | 235 |
234 __version__ = b'0.24.3.dev' | 236 __version__ = b'0.25.0.dev' |
235 | 237 |
236 testedwith = b'4.8 4.9 5.0 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9 6.0 6.1 6.2 6.3' | 238 testedwith = b'4.8 4.9 5.0 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9 6.0 6.1 6.2' |
237 minimumhgversion = b'4.8' | 239 minimumhgversion = b'4.8' |
238 buglink = b'https://bz.mercurial-scm.org/' | 240 buglink = b'https://bz.mercurial-scm.org/' |
239 | 241 |
240 if util.safehasattr(registrar, 'configitem'): | 242 configtable = {} |
241 | 243 configitem = registrar.configitem(configtable) |
242 from mercurial import configitems | 244 |
243 | 245 configitem(b'experimental', b'enforce-topic', |
244 configtable = {} | 246 default=False, |
245 configitem = registrar.configitem(configtable) | 247 ) |
246 | 248 configitem(b'experimental', b'enforce-single-head', |
247 configitem(b'experimental', b'enforce-topic', | 249 default=False, |
248 default=False, | 250 ) |
249 ) | 251 configitem(b'experimental', b'topic-mode', |
250 configitem(b'experimental', b'enforce-single-head', | 252 default=None, |
251 default=False, | 253 ) |
252 ) | 254 configitem(b'experimental', b'topic.publish-bare-branch', |
253 configitem(b'experimental', b'topic-mode', | 255 default=False, |
254 default=None, | 256 ) |
255 ) | 257 configitem(b'experimental', b'topic.allow-publish', |
256 configitem(b'experimental', b'topic.publish-bare-branch', | 258 default=configitems.dynamicdefault, |
257 default=False, | 259 ) |
258 ) | 260 configitem(b'_internal', b'keep-topic', |
259 configitem(b'experimental', b'topic.allow-publish', | 261 default=False, |
260 default=configitems.dynamicdefault, | 262 ) |
261 ) | 263 # used for signaling that ctx.branch() shouldn't return fqbn even if topic is |
262 configitem(b'_internal', b'keep-topic', | 264 # enabled for local repo |
263 default=False, | 265 configitem(b'_internal', b'tns-disable-fqbn', |
264 ) | 266 default=False, |
265 configitem(b'experimental', b'topic-mode.server', | 267 ) |
266 default=configitems.dynamicdefault, | 268 # used for signaling that push will publish changesets |
267 ) | 269 configitem(b'_internal', b'tns-publish', |
268 configitem(b'experimental', b'topic.server-gate-topic-changesets', | 270 default=False, |
269 default=False, | 271 ) |
270 ) | 272 configitem(b'experimental', b'topic-mode.server', |
271 configitem(b'experimental', b'topic.linear-merge', | 273 default=configitems.dynamicdefault, |
272 default="reject", | 274 ) |
273 ) | 275 configitem(b'experimental', b'topic.server-gate-topic-changesets', |
274 | 276 default=False, |
275 def extsetup(ui): | 277 ) |
276 # register config that strictly belong to other code (thg, core, etc) | 278 configitem(b'experimental', b'topic.linear-merge', |
277 # | 279 default="reject", |
278 # To ensure all config items we used are registered, we register them if | 280 ) |
279 # nobody else did so far. | 281 |
280 from mercurial import configitems | 282 def extsetup(ui): |
281 extraitem = functools.partial(configitems._register, ui._knownconfig) | 283 # register config that strictly belong to other code (thg, core, etc) |
282 if (b'experimental' not in ui._knownconfig | 284 # |
283 or not ui._knownconfig[b'experimental'].get(b'thg.displaynames')): | 285 # To ensure all config items we used are registered, we register them if |
284 extraitem(b'experimental', b'thg.displaynames', | 286 # nobody else did so far. |
285 default=None, | 287 extraitem = functools.partial(configitems._register, ui._knownconfig) |
286 ) | 288 if (b'experimental' not in ui._knownconfig |
287 if (b'devel' not in ui._knownconfig | 289 or not ui._knownconfig[b'experimental'].get(b'thg.displaynames')): |
288 or not ui._knownconfig[b'devel'].get(b'random')): | 290 extraitem(b'experimental', b'thg.displaynames', |
289 extraitem(b'devel', b'randomseed', | 291 default=None, |
290 default=None, | 292 ) |
291 ) | 293 if (b'devel' not in ui._knownconfig |
294 or not ui._knownconfig[b'devel'].get(b'random')): | |
295 extraitem(b'devel', b'randomseed', | |
296 default=None, | |
297 ) | |
298 | |
299 def _contexttns(self, force=False): | |
300 if not force and not self.mutable(): | |
301 return b'default' | |
302 cache = getattr(self._repo, '_tnscache', None) | |
303 # topic loaded, but not enabled (eg: multiple repo in the same process) | |
304 if cache is None: | |
305 return b'default' | |
306 if self.rev() is None: | |
307 # don't cache volatile ctx instances that aren't stored on-disk yet | |
308 return self.extra().get(b'topic-namespace', b'default') | |
309 tns = cache.get(self.rev()) | |
310 if tns is None: | |
311 tns = self.extra().get(b'topic-namespace', b'default') | |
312 self._repo._tnscache[self.rev()] = tns | |
313 return tns | |
314 | |
315 context.basectx.topic_namespace = _contexttns | |
292 | 316 |
293 def _contexttopic(self, force=False): | 317 def _contexttopic(self, force=False): |
294 if not (force or self.mutable()): | 318 if not (force or self.mutable()): |
295 return b'' | 319 return b'' |
296 cache = getattr(self._repo, '_topiccache', None) | 320 cache = getattr(self._repo, '_topiccache', None) |
319 return revlist.index(self.rev()) | 343 return revlist.index(self.rev()) |
320 except IndexError: | 344 except IndexError: |
321 # Lets move to the last ctx of the current topic | 345 # Lets move to the last ctx of the current topic |
322 return None | 346 return None |
323 context.basectx.topicidx = _contexttopicidx | 347 context.basectx.topicidx = _contexttopicidx |
348 | |
349 def _contextfqbn(self): | |
350 """return branch//namespace/topic of the changeset, also known as fully | |
351 qualified branch name | |
352 """ | |
353 branch = encoding.tolocal(self.extra()[b'branch']) | |
354 return common.formatfqbn(branch, self.topic_namespace(), self.topic()) | |
355 | |
356 context.basectx.fqbn = _contextfqbn | |
324 | 357 |
325 stackrev = re.compile(br'^s\d+$') | 358 stackrev = re.compile(br'^s\d+$') |
326 topicrev = re.compile(br'^t\d+$') | 359 topicrev = re.compile(br'^t\d+$') |
327 | 360 |
328 hastopicext = common.hastopicext | 361 hastopicext = common.hastopicext |
473 hastopicext = True | 506 hastopicext = True |
474 | 507 |
475 def _restrictcapabilities(self, caps): | 508 def _restrictcapabilities(self, caps): |
476 caps = super(topicrepo, self)._restrictcapabilities(caps) | 509 caps = super(topicrepo, self)._restrictcapabilities(caps) |
477 caps.add(b'topics') | 510 caps.add(b'topics') |
511 caps.add(b'topics-namespaces') | |
478 if self.ui.configbool(b'phases', b'publish'): | 512 if self.ui.configbool(b'phases', b'publish'): |
479 mode = b'all' | 513 mode = b'all' |
480 elif self.ui.configbool(b'experimental', | 514 elif self.ui.configbool(b'experimental', |
481 b'topic.publish-bare-branch'): | 515 b'topic.publish-bare-branch'): |
482 mode = b'auto' | 516 mode = b'auto' |
499 if isinstance(ctx, context.workingcommitctx): | 533 if isinstance(ctx, context.workingcommitctx): |
500 current = self.currenttopic | 534 current = self.currenttopic |
501 if current: | 535 if current: |
502 ctx.extra()[constants.extrakey] = current | 536 ctx.extra()[constants.extrakey] = current |
503 return super(topicrepo, self).commitctx(ctx, *args, **kwargs) | 537 return super(topicrepo, self).commitctx(ctx, *args, **kwargs) |
538 | |
539 @util.propertycache | |
540 def _tnscache(self): | |
541 return {} | |
542 | |
543 @property | |
544 def topic_namespaces(self): | |
545 if self._topic_namespaces is not None: | |
546 return self._topic_namespaces | |
547 namespaces = set([self.currenttns]) | |
548 for c in self.set(b'not public()'): | |
549 namespaces.add(c.topic_namespace()) | |
550 self._topic_namespaces = namespaces | |
551 return namespaces | |
552 | |
553 @property | |
554 def currenttns(self): | |
555 return self.vfs.tryread(b'topic-namespace') or b'default' | |
504 | 556 |
505 @util.propertycache | 557 @util.propertycache |
506 def _topiccache(self): | 558 def _topiccache(self): |
507 return {} | 559 return {} |
508 | 560 |
522 return self.vfs.tryread(b'topic') | 574 return self.vfs.tryread(b'topic') |
523 | 575 |
524 # overwritten at the instance level by topicmap.py | 576 # overwritten at the instance level by topicmap.py |
525 _autobranchmaptopic = True | 577 _autobranchmaptopic = True |
526 | 578 |
527 def branchmap(self, topic=None): | 579 def branchmap(self, topic=None, convertbm=False): |
580 if topic is None: | |
581 topic = getattr(self, '_autobranchmaptopic', False) | |
582 topicfilter = topicmap.topicfilter(self.filtername) | |
583 if not topic or topicfilter == self.filtername: | |
584 return super(topicrepo, self).branchmap() | |
585 bm = self.filtered(topicfilter).branchmap() | |
586 if convertbm: | |
587 entries = compat.bcentries(bm) | |
588 for key in list(entries): | |
589 branch, tns, topic = common.parsefqbn(key) | |
590 if topic: | |
591 value = entries.pop(key) | |
592 # we lose namespace when converting to ":" format | |
593 key = b'%s:%s' % (branch, topic) | |
594 entries[key] = value | |
595 return bm | |
596 | |
597 def branchmaptns(self, topic=None): | |
598 """branchmap using fqbn as keys""" | |
528 if topic is None: | 599 if topic is None: |
529 topic = getattr(self, '_autobranchmaptopic', False) | 600 topic = getattr(self, '_autobranchmaptopic', False) |
530 topicfilter = topicmap.topicfilter(self.filtername) | 601 topicfilter = topicmap.topicfilter(self.filtername) |
531 if not topic or topicfilter == self.filtername: | 602 if not topic or topicfilter == self.filtername: |
532 return super(topicrepo, self).branchmap() | 603 return super(topicrepo, self).branchmap() |
533 return self.filtered(topicfilter).branchmap() | 604 return self.filtered(topicfilter).branchmap() |
534 | 605 |
535 def branchheads(self, branch=None, start=None, closed=False): | 606 def branchheads(self, branch=None, start=None, closed=False): |
536 if branch is None: | 607 if branch is None: |
537 branch = self[None].branch() | 608 branch = self[None].branch() |
538 if self.currenttopic: | 609 branch = common.formatfqbn(branch, self.currenttns, self.currenttopic) |
539 branch = b"%s:%s" % (branch, self.currenttopic) | |
540 return super(topicrepo, self).branchheads(branch=branch, | 610 return super(topicrepo, self).branchheads(branch=branch, |
541 start=start, | 611 start=start, |
542 closed=closed) | 612 closed=closed) |
543 | 613 |
544 def invalidatecaches(self): | 614 def invalidatecaches(self): |
546 super(topicrepo, self).invalidatecaches() | 616 super(topicrepo, self).invalidatecaches() |
547 | 617 |
548 def invalidatevolatilesets(self): | 618 def invalidatevolatilesets(self): |
549 # XXX we might be able to move this to something invalidated less often | 619 # XXX we might be able to move this to something invalidated less often |
550 super(topicrepo, self).invalidatevolatilesets() | 620 super(topicrepo, self).invalidatevolatilesets() |
621 self._topic_namespaces = None | |
551 self._topics = None | 622 self._topics = None |
552 | 623 |
553 def peer(self, *args, **kwargs): | 624 def peer(self, *args, **kwargs): |
554 peer = super(topicrepo, self).peer(*args, **kwargs) | 625 peer = super(topicrepo, self).peer(*args, **kwargs) |
555 if getattr(peer, '_repo', None) is not None: # localpeer | 626 if getattr(peer, '_repo', None) is not None: # localpeer |
556 class topicpeer(peer.__class__): | 627 class topicpeer(peer.__class__): |
557 def branchmap(self): | 628 def branchmap(self): |
558 usetopic = not self._repo.publishing() | 629 usetopic = not self._repo.publishing() |
559 return self._repo.branchmap(topic=usetopic) | 630 return self._repo.branchmap(topic=usetopic, convertbm=usetopic) |
631 | |
632 def branchmaptns(self): | |
633 usetopic = not self._repo.publishing() | |
634 return self._repo.branchmaptns(topic=usetopic) | |
560 peer.__class__ = topicpeer | 635 peer.__class__ = topicpeer |
561 return peer | 636 return peer |
562 | 637 |
563 def transaction(self, desc, *a, **k): | 638 def transaction(self, desc, *a, **k): |
564 ctr = self.currenttransaction() | 639 ctr = self.currenttransaction() |
566 if desc in (b'strip', b'repair') or ctr is not None: | 641 if desc in (b'strip', b'repair') or ctr is not None: |
567 return tr | 642 return tr |
568 | 643 |
569 reporef = weakref.ref(self) | 644 reporef = weakref.ref(self) |
570 if self.ui.configbool(b'experimental', b'enforce-single-head'): | 645 if self.ui.configbool(b'experimental', b'enforce-single-head'): |
571 if util.safehasattr(tr, 'validator'): # hg <= 4.7 (ebbba3ba3f66) | 646 if util.safehasattr(tr, '_validator'): |
572 origvalidator = tr.validator | |
573 elif util.safehasattr(tr, '_validator'): | |
574 # hg <= 5.3 (36f08ae87ef6) | 647 # hg <= 5.3 (36f08ae87ef6) |
575 origvalidator = tr._validator | 648 origvalidator = tr._validator |
576 else: | |
577 origvalidator = None | |
578 | 649 |
579 def _validate(tr2): | 650 def _validate(tr2): |
580 repo = reporef() | 651 repo = reporef() |
581 flow.enforcesinglehead(repo, tr2) | 652 flow.enforcesinglehead(repo, tr2) |
582 | 653 |
583 def validator(tr2): | 654 def validator(tr2): |
584 _validate(tr2) | 655 _validate(tr2) |
585 origvalidator(tr2) | 656 return origvalidator(tr2) |
586 | 657 |
587 if util.safehasattr(tr, 'validator'): # hg <= 4.7 (ebbba3ba3f66) | 658 if util.safehasattr(tr, '_validator'): |
588 tr.validator = validator | |
589 elif util.safehasattr(tr, '_validator'): | |
590 # hg <= 5.3 (36f08ae87ef6) | 659 # hg <= 5.3 (36f08ae87ef6) |
591 tr._validator = validator | 660 tr._validator = validator |
592 else: | 661 else: |
593 tr.addvalidator(b'000-enforce-single-head', _validate) | 662 tr.addvalidator(b'000-enforce-single-head', _validate) |
594 | 663 |
596 b'topic-mode.server', b'ignore') | 665 b'topic-mode.server', b'ignore') |
597 publishbare = self.ui.configbool(b'experimental', | 666 publishbare = self.ui.configbool(b'experimental', |
598 b'topic.publish-bare-branch') | 667 b'topic.publish-bare-branch') |
599 ispush = desc.startswith((b'push', b'serve')) | 668 ispush = desc.startswith((b'push', b'serve')) |
600 if (topicmodeserver != b'ignore' and ispush): | 669 if (topicmodeserver != b'ignore' and ispush): |
601 if util.safehasattr(tr, 'validator'): # hg <= 4.7 (ebbba3ba3f66) | 670 if util.safehasattr(tr, '_validator'): |
602 origvalidator = tr.validator | |
603 elif util.safehasattr(tr, '_validator'): | |
604 # hg <= 5.3 (36f08ae87ef6) | 671 # hg <= 5.3 (36f08ae87ef6) |
605 origvalidator = tr._validator | 672 origvalidator = tr._validator |
606 else: | |
607 origvalidator = None | |
608 | 673 |
609 def _validate(tr2): | 674 def _validate(tr2): |
610 repo = reporef() | 675 repo = reporef() |
611 flow.rejectuntopicedchangeset(repo, tr2) | 676 flow.rejectuntopicedchangeset(repo, tr2) |
612 | 677 |
613 def validator(tr2): | 678 def validator(tr2): |
614 _validate(tr2) | 679 _validate(tr2) |
615 return origvalidator(tr2) | 680 return origvalidator(tr2) |
616 | 681 |
617 if util.safehasattr(tr, 'validator'): # hg <= 4.7 (ebbba3ba3f66) | 682 if util.safehasattr(tr, '_validator'): |
618 tr.validator = validator | |
619 elif util.safehasattr(tr, '_validator'): | |
620 # hg <= 5.3 (36f08ae87ef6) | 683 # hg <= 5.3 (36f08ae87ef6) |
621 tr._validator = validator | 684 tr._validator = validator |
622 else: | 685 else: |
623 tr.addvalidator(b'000-reject-untopiced', _validate) | 686 tr.addvalidator(b'000-reject-untopiced', _validate) |
624 | 687 |
634 tr.close = close | 697 tr.close = close |
635 allow_publish = self.ui.configbool(b'experimental', | 698 allow_publish = self.ui.configbool(b'experimental', |
636 b'topic.allow-publish', | 699 b'topic.allow-publish', |
637 True) | 700 True) |
638 if not allow_publish: | 701 if not allow_publish: |
639 if util.safehasattr(tr, 'validator'): # hg <= 4.7 (ebbba3ba3f66) | 702 if util.safehasattr(tr, '_validator'): |
640 origvalidator = tr.validator | |
641 elif util.safehasattr(tr, '_validator'): | |
642 # hg <= 5.3 (36f08ae87ef6) | 703 # hg <= 5.3 (36f08ae87ef6) |
643 origvalidator = tr._validator | 704 origvalidator = tr._validator |
644 else: | |
645 origvalidator = None | |
646 | 705 |
647 def _validate(tr2): | 706 def _validate(tr2): |
648 repo = reporef() | 707 repo = reporef() |
649 flow.reject_publish(repo, tr2) | 708 flow.reject_publish(repo, tr2) |
650 | 709 |
651 def validator(tr2): | 710 def validator(tr2): |
652 _validate(tr2) | 711 _validate(tr2) |
653 return origvalidator(tr2) | 712 return origvalidator(tr2) |
654 | 713 |
655 if util.safehasattr(tr, 'validator'): # hg <= 4.7 (ebbba3ba3f66) | 714 if util.safehasattr(tr, '_validator'): |
656 tr.validator = validator | |
657 elif util.safehasattr(tr, '_validator'): | |
658 # hg <= 5.3 (36f08ae87ef6) | 715 # hg <= 5.3 (36f08ae87ef6) |
659 tr._validator = validator | 716 tr._validator = validator |
660 else: | 717 else: |
661 tr.addvalidator(b'000-reject-publish', _validate) | 718 tr.addvalidator(b'000-reject-publish', _validate) |
662 | 719 |
692 | 749 |
693 tr.addpostclose(b'signalcurrenttopicempty', currenttopicempty) | 750 tr.addpostclose(b'signalcurrenttopicempty', currenttopicempty) |
694 return tr | 751 return tr |
695 | 752 |
696 repo.__class__ = topicrepo | 753 repo.__class__ = topicrepo |
754 repo._topic_namespaces = None | |
697 repo._topics = None | 755 repo._topics = None |
698 if util.safehasattr(repo, 'names'): | 756 if util.safehasattr(repo, 'names'): |
699 repo.names.addnamespace(namespaces.namespace( | 757 repo.names.addnamespace(namespaces.namespace( |
700 b'topics', b'topic', namemap=_namemap, nodemap=_nodemap, | 758 b'topics', b'topic', namemap=_namemap, nodemap=_nodemap, |
701 listnames=lambda repo: repo.topics)) | 759 listnames=lambda repo: repo.topics)) |
712 def topicidxkw(context, mapping): | 770 def topicidxkw(context, mapping): |
713 """:topicidx: Integer. Index of the changeset as a stack alias""" | 771 """:topicidx: Integer. Index of the changeset as a stack alias""" |
714 ctx = context.resource(mapping, b'ctx') | 772 ctx = context.resource(mapping, b'ctx') |
715 return ctx.topicidx() | 773 return ctx.topicidx() |
716 | 774 |
775 @templatekeyword(b'topic_namespace', requires={b'ctx'}) | |
776 def topicnamespacekw(context, mapping): | |
777 """:topic_namespace: String. The topic namespace of the changeset""" | |
778 ctx = context.resource(mapping, b'ctx') | |
779 return ctx.topic_namespace() | |
780 | |
781 @templatekeyword(b'fqbn', requires={b'ctx'}) | |
782 def fqbnkw(context, mapping): | |
783 """:fqbn: String. The branch//namespace/topic of the changeset""" | |
784 ctx = context.resource(mapping, b'ctx') | |
785 return ctx.fqbn() | |
786 | |
717 def wrapinit(orig, self, repo, *args, **kwargs): | 787 def wrapinit(orig, self, repo, *args, **kwargs): |
718 orig(self, repo, *args, **kwargs) | 788 orig(self, repo, *args, **kwargs) |
719 if not hastopicext(repo): | 789 if not hastopicext(repo): |
720 return | 790 return |
791 if b'topic-namespace' not in self._extra: | |
792 if getattr(repo, 'currenttns', b''): | |
793 self._extra[b'topic-namespace'] = repo.currenttns | |
794 else: | |
795 # Default value will be dropped from extra by another hack at the changegroup level | |
796 self._extra[b'topic-namespace'] = b'default' | |
721 if constants.extrakey not in self._extra: | 797 if constants.extrakey not in self._extra: |
722 if getattr(repo, 'currenttopic', b''): | 798 if getattr(repo, 'currenttopic', b''): |
723 self._extra[constants.extrakey] = repo.currenttopic | 799 self._extra[constants.extrakey] = repo.currenttopic |
724 else: | 800 else: |
725 # Empty key will be dropped from extra by another hack at the changegroup level | 801 # Empty key will be dropped from extra by another hack at the changegroup level |
726 self._extra[constants.extrakey] = b'' | 802 self._extra[constants.extrakey] = b'' |
727 | 803 |
728 def wrapadd(orig, cl, manifest, files, desc, transaction, p1, p2, user, | 804 def wrapadd(orig, cl, manifest, files, desc, transaction, p1, p2, user, |
729 date=None, extra=None, p1copies=None, p2copies=None, | 805 date=None, extra=None, p1copies=None, p2copies=None, |
730 filesadded=None, filesremoved=None): | 806 filesadded=None, filesremoved=None): |
807 if b'topic-namespace' in extra and extra[b'topic-namespace'] == b'default': | |
808 extra = extra.copy() | |
809 del extra[b'topic-namespace'] | |
731 if constants.extrakey in extra and not extra[constants.extrakey]: | 810 if constants.extrakey in extra and not extra[constants.extrakey]: |
732 extra = extra.copy() | 811 extra = extra.copy() |
733 del extra[constants.extrakey] | 812 del extra[constants.extrakey] |
734 # hg <= 4.9 (0e41f40b01cc) | 813 # hg <= 4.9 (0e41f40b01cc) |
735 kwargs = {} | 814 kwargs = {} |
765 (b'l', b'list', False, b'show the stack of changeset in the topic'), | 844 (b'l', b'list', False, b'show the stack of changeset in the topic'), |
766 (b'', b'age', False, b'show when you last touched the topics'), | 845 (b'', b'age', False, b'show when you last touched the topics'), |
767 (b'', b'current', None, b'display the current topic only'), | 846 (b'', b'current', None, b'display the current topic only'), |
768 ] + commands.formatteropts, | 847 ] + commands.formatteropts, |
769 _(b'hg topics [OPTION]... [-r REV]... [TOPIC]'), | 848 _(b'hg topics [OPTION]... [-r REV]... [TOPIC]'), |
770 **compat.helpcategorykwargs('CATEGORY_CHANGE_ORGANIZATION')) | 849 helpcategory=registrar.command.CATEGORY_CHANGE_ORGANIZATION, |
850 ) | |
771 def topics(ui, repo, topic=None, **opts): | 851 def topics(ui, repo, topic=None, **opts): |
772 """View current topic, set current topic, change topic for a set of revisions, or see all topics. | 852 """View current topic, set current topic, change topic for a set of revisions, or see all topics. |
773 | 853 |
774 Clear topic on existing topiced revisions:: | 854 Clear topic on existing topiced revisions:: |
775 | 855 |
810 rev = opts.get('rev') | 890 rev = opts.get('rev') |
811 current = opts.get('current') | 891 current = opts.get('current') |
812 age = opts.get('age') | 892 age = opts.get('age') |
813 | 893 |
814 if current and topic: | 894 if current and topic: |
815 raise error.Abort(_(b"cannot use --current when setting a topic")) | 895 raise compat.InputError(_(b"cannot use --current when setting a topic")) |
816 if current and clear: | 896 if current and clear: |
817 raise error.Abort(_(b"cannot use --current and --clear")) | 897 raise compat.InputError(_(b"cannot use --current and --clear")) |
818 if clear and topic: | 898 if clear and topic: |
819 raise error.Abort(_(b"cannot use --clear when setting a topic")) | 899 raise compat.InputError(_(b"cannot use --clear when setting a topic")) |
820 if age and topic: | 900 if age and topic: |
821 raise error.Abort(_(b"cannot use --age while setting a topic")) | 901 raise compat.InputError(_(b"cannot use --age while setting a topic")) |
902 | |
903 compat.check_incompatible_arguments(opts, 'list', ('clear', 'rev')) | |
822 | 904 |
823 touchedrevs = set() | 905 touchedrevs = set() |
824 if rev: | 906 if rev: |
825 touchedrevs = scmutil.revrange(repo, rev) | 907 touchedrevs = scmutil.revrange(repo, rev) |
826 | 908 |
827 if topic: | 909 if topic: |
828 topic = topic.strip() | 910 topic = topic.strip() |
829 if not topic: | 911 if not topic: |
830 raise error.Abort(_(b"topic name cannot consist entirely of whitespaces")) | 912 raise compat.InputError(_(b"topic names cannot consist entirely of whitespace")) |
831 # Have some restrictions on the topic name just like bookmark name | 913 # Have some restrictions on the topic name just like bookmark name |
832 scmutil.checknewlabel(repo, topic, b'topic') | 914 scmutil.checknewlabel(repo, topic, b'topic') |
833 | 915 |
834 rmatch = re.match(br'[-_.\w]+', topic) | 916 helptxt = _(b"topic names can only consist of alphanumeric, '-'" |
835 if not rmatch or rmatch.group(0) != topic: | 917 b" '_' and '.' characters") |
836 helptxt = _(b"topic names can only consist of alphanumeric, '-'" | 918 try: |
837 b" '_' and '.' characters") | 919 utopic = encoding.unifromlocal(topic) |
838 raise error.Abort(_(b"invalid topic name: '%s'") % topic, hint=helptxt) | 920 except error.Abort: |
921 # Maybe we should allow these topic names as well, as long as they | |
922 # don't break any other rules | |
923 utopic = '' | |
924 rmatch = re.match(r'[-_.\w]+', utopic, re.UNICODE) | |
925 if not utopic or not rmatch or rmatch.group(0) != utopic: | |
926 raise compat.InputError(_(b"invalid topic name: '%s'") % topic, hint=helptxt) | |
839 | 927 |
840 if list: | 928 if list: |
841 ui.pager(b'topics') | 929 ui.pager(b'topics') |
842 if clear or rev: | |
843 raise error.Abort(_(b"cannot use --clear or --rev with --list")) | |
844 if not topic: | 930 if not topic: |
845 topic = repo.currenttopic | 931 topic = repo.currenttopic |
846 if not topic: | 932 if not topic: |
847 raise error.Abort(_(b'no active topic to list')) | 933 raise error.Abort(_(b'no active topic to list')) |
848 return stack.showstack(ui, repo, topic=topic, | 934 return stack.showstack(ui, repo, topic=topic, |
910 @command(b'stack', [ | 996 @command(b'stack', [ |
911 (b'c', b'children', None, | 997 (b'c', b'children', None, |
912 _(b'display data about children outside of the stack')) | 998 _(b'display data about children outside of the stack')) |
913 ] + commands.formatteropts, | 999 ] + commands.formatteropts, |
914 _(b'hg stack [TOPIC]'), | 1000 _(b'hg stack [TOPIC]'), |
915 **compat.helpcategorykwargs('CATEGORY_CHANGE_NAVIGATION')) | 1001 helpcategory=registrar.command.CATEGORY_CHANGE_NAVIGATION, |
1002 ) | |
916 def cmdstack(ui, repo, topic=b'', **opts): | 1003 def cmdstack(ui, repo, topic=b'', **opts): |
917 """list all changesets in a topic and other information | 1004 """list all changesets in a topic and other information |
918 | 1005 |
919 List the current topic by default. | 1006 List the current topic by default. |
920 | 1007 |
1056 def _changecurrenttopic(repo, newtopic): | 1143 def _changecurrenttopic(repo, newtopic): |
1057 """changes the current topic.""" | 1144 """changes the current topic.""" |
1058 | 1145 |
1059 if newtopic: | 1146 if newtopic: |
1060 with repo.wlock(): | 1147 with repo.wlock(): |
1061 with repo.vfs.open(b'topic', b'w') as f: | 1148 repo.vfs.write(b'topic', newtopic) |
1062 f.write(newtopic) | |
1063 else: | 1149 else: |
1064 if repo.vfs.exists(b'topic'): | 1150 if repo.vfs.exists(b'topic'): |
1065 repo.vfs.unlink(b'topic') | 1151 repo.vfs.unlink(b'topic') |
1066 | 1152 |
1067 def _changetopics(ui, repo, revs, newtopic): | 1153 def _changetopics(ui, repo, revs, newtopic): |
1319 maywarn = False | 1405 maywarn = False |
1320 | 1406 |
1321 hint = _(b"see 'hg help -e topic.topic-mode' for details") | 1407 hint = _(b"see 'hg help -e topic.topic-mode' for details") |
1322 if opts.get('topic'): | 1408 if opts.get('topic'): |
1323 t = opts['topic'] | 1409 t = opts['topic'] |
1324 with repo.vfs.open(b'topic', b'w') as f: | 1410 repo.vfs.write(b'topic', t) |
1325 f.write(t) | |
1326 elif opts.get('amend'): | 1411 elif opts.get('amend'): |
1327 pass | 1412 pass |
1328 elif notopic and mayabort: | 1413 elif notopic and mayabort: |
1329 msg = _(b"no active topic") | 1414 msg = _(b"no active topic") |
1330 raise error.Abort(msg, hint=hint) | 1415 raise error.Abort(msg, hint=hint) |
1331 elif notopic and maywarn: | 1416 elif notopic and maywarn: |
1332 ui.warn(_(b"warning: new draft commit without topic\n")) | 1417 ui.warn(_(b"warning: new draft commit without topic\n")) |
1333 if not ui.quiet: | 1418 if not ui.quiet: |
1334 ui.warn((b"(%s)\n") % hint) | 1419 ui.warn((b"(%s)\n") % hint) |
1335 elif notopic and mayrandom: | 1420 elif notopic and mayrandom: |
1336 with repo.vfs.open(b'topic', b'w') as f: | 1421 repo.vfs.write(b'topic', randomname.randomtopicname(ui)) |
1337 f.write(randomname.randomtopicname(ui)) | |
1338 return orig(ui, repo, *args, **opts) | 1422 return orig(ui, repo, *args, **opts) |
1339 | 1423 |
1340 def committextwrap(orig, repo, ctx, subs, extramsg): | 1424 def committextwrap(orig, repo, ctx, subs, extramsg): |
1341 ret = orig(repo, ctx, subs, extramsg) | 1425 ret = orig(repo, ctx, subs, extramsg) |
1342 if hastopicext(repo): | 1426 if hastopicext(repo): |
1375 class overridebranch(old): | 1459 class overridebranch(old): |
1376 def __getitem__(self, rev): | 1460 def __getitem__(self, rev): |
1377 ret = super(overridebranch, self).__getitem__(rev) | 1461 ret = super(overridebranch, self).__getitem__(rev) |
1378 if rev == node: | 1462 if rev == node: |
1379 b = ret.branch() | 1463 b = ret.branch() |
1464 tns = ret.topic_namespace() | |
1380 t = ret.topic() | 1465 t = ret.topic() |
1466 # topic is required for merging from bare branch | |
1381 if t: | 1467 if t: |
1382 ret.branch = lambda: b'%s//%s' % (b, t) | 1468 ret.branch = lambda: common.formatfqbn(b, tns, t) |
1383 return ret | 1469 return ret |
1384 unfi.__class__ = overridebranch | 1470 unfi.__class__ = overridebranch |
1385 if repo.filtername is not None: | 1471 if repo.filtername is not None: |
1386 repo = unfi.filtered(repo.filtername) | 1472 repo = unfi.filtered(repo.filtername) |
1387 | 1473 |
1399 # The mergeupdatewrap function makes the destination's topic as the | 1485 # The mergeupdatewrap function makes the destination's topic as the |
1400 # current topic. This is right for merge but wrong for rebase. We check | 1486 # current topic. This is right for merge but wrong for rebase. We check |
1401 # if rebase is running and update the currenttopic to topic of new | 1487 # if rebase is running and update the currenttopic to topic of new |
1402 # rebased commit. We have explicitly stored in config if rebase is | 1488 # rebased commit. We have explicitly stored in config if rebase is |
1403 # running. | 1489 # running. |
1490 otns = repo.currenttns | |
1404 ot = repo.currenttopic | 1491 ot = repo.currenttopic |
1405 if repo.ui.hasconfig(b'experimental', b'topicrebase'): | 1492 if repo.ui.hasconfig(b'experimental', b'topicrebase'): |
1406 isrebase = True | 1493 isrebase = True |
1407 if repo.ui.configbool(b'_internal', b'keep-topic'): | 1494 if repo.ui.configbool(b'_internal', b'keep-topic'): |
1408 ist0 = True | 1495 ist0 = True |
1409 if ((not partial and not branchmerge) or isrebase) and not ist0: | 1496 if ((not partial and not branchmerge) or isrebase) and not ist0: |
1497 tns = b'default' | |
1410 t = b'' | 1498 t = b'' |
1411 pctx = repo[node] | 1499 pctx = repo[node] |
1412 if pctx.phase() > phases.public: | 1500 if pctx.phase() > phases.public: |
1501 tns = pctx.topic_namespace() | |
1413 t = pctx.topic() | 1502 t = pctx.topic() |
1414 with repo.vfs.open(b'topic', b'w') as f: | 1503 repo.vfs.write(b'topic-namespace', tns) |
1415 f.write(t) | 1504 if tns != b'default' and tns != otns: |
1505 repo.ui.status(_(b"switching to topic-namespace %s\n") % tns) | |
1506 repo.vfs.write(b'topic', t) | |
1416 if t and t != ot: | 1507 if t and t != ot: |
1417 repo.ui.status(_(b"switching to topic %s\n") % t) | 1508 repo.ui.status(_(b"switching to topic %s\n") % t) |
1418 if ot and not t: | 1509 if ot and not t: |
1419 st = stack.stack(repo, topic=ot) | 1510 st = stack.stack(repo, topic=ot) |
1420 if not st: | 1511 if not st: |
1499 original(repo, ui, prev, ctx) | 1590 original(repo, ui, prev, ctx) |
1500 | 1591 |
1501 # Restore the topic if need | 1592 # Restore the topic if need |
1502 if topic: | 1593 if topic: |
1503 _changecurrenttopic(repo, topic) | 1594 _changecurrenttopic(repo, topic) |
1595 | |
1596 def _changecurrenttns(repo, tns): | |
1597 if tns: | |
1598 with repo.wlock(): | |
1599 repo.vfs.write(b'topic-namespace', tns) | |
1600 else: | |
1601 repo.vfs.unlinkpath(b'topic-namespace', ignoremissing=True) | |
1602 | |
1603 @command(b'debug-topic-namespace', [ | |
1604 (b'', b'clear', False, b'clear active topic namespace if any'), | |
1605 ], | |
1606 _(b'[NAMESPACE|--clear]')) | |
1607 def debugtopicnamespace(ui, repo, tns=None, **opts): | |
1608 """set or show the current topic namespace""" | |
1609 if opts.get('clear'): | |
1610 if tns: | |
1611 raise error.Abort(_(b"cannot use --clear when setting a topic namespace")) | |
1612 tns = None | |
1613 elif not tns: | |
1614 ui.write(b'%s\n' % repo.currenttns) | |
1615 return | |
1616 if tns: | |
1617 tns = tns.strip() | |
1618 if not tns: | |
1619 raise error.Abort(_(b"topic namespace cannot consist entirely of whitespace")) | |
1620 if b'/' in tns: | |
1621 raise error.Abort(_(b"topic namespace cannot contain '/' character")) | |
1622 scmutil.checknewlabel(repo, tns, b'topic namespace') | |
1623 ctns = repo.currenttns | |
1624 _changecurrenttns(repo, tns) | |
1625 if ctns == b'default' and tns: | |
1626 repo.ui.status(_(b'marked working directory as topic namespace: %s\n') | |
1627 % tns) | |
1628 | |
1629 @command(b'debug-topic-namespaces', []) | |
1630 def debugtopicnamespaces(ui, repo, **opts): | |
1631 """list repository namespaces""" | |
1632 for tns in repo.topic_namespaces: | |
1633 ui.write(b'%s\n' % (tns,)) | |
1634 | |
1635 @command(b'debug-parse-fqbn', commands.formatteropts, _(b'FQBN'), optionalrepo=True) | |
1636 def debugparsefqbn(ui, repo, fqbn, **opts): | |
1637 """parse branch//namespace/topic string into its components""" | |
1638 branch, tns, topic = common.parsefqbn(fqbn) | |
1639 opts = pycompat.byteskwargs(opts) | |
1640 fm = ui.formatter(b'debug-parse-namespace', opts) | |
1641 fm.startitem() | |
1642 fm.write(b'branch', b'branch: %s\n', branch) | |
1643 fm.write(b'topic_namespace', b'namespace: %s\n', tns) | |
1644 fm.write(b'topic', b'topic: %s\n', topic) | |
1645 fm.end() | |
1646 | |
1647 @command(b'debug-format-fqbn', [ | |
1648 (b'b', b'branch', b'', b'branch'), | |
1649 (b'n', b'topic-namespace', b'', b'topic namespace'), | |
1650 (b't', b'topic', b'', b'topic'), | |
1651 (b's', b'short', False, b'short format'), | |
1652 ], optionalrepo=True) | |
1653 def debugformatfqbn(ui, repo, **opts): | |
1654 """format branch, namespace and topic into branch//namespace/topic string""" | |
1655 short = common.formatfqbn(opts.get('branch'), opts.get('topic_namespace'), opts.get('topic'), opts.get('short')) | |
1656 ui.write(b'%s\n' % short) |