topic: add some initial support for using stack on named branch
authorPierre-Yves David <pierre-yves.david@octobus.net>
Wed, 28 Jun 2017 02:45:57 +0200
changeset 2669 b933a8068c17
parent 2668 1d2c66dc4ee3
child 2670 f5d52fa1cd55
topic: add some initial support for using stack on named branch Stack is a useful command that can make sense in other context. The current support is hacky and awful, but we have something!
README
hgext3rd/topic/__init__.py
hgext3rd/topic/revset.py
hgext3rd/topic/stack.py
tests/test-stack-branch.t
tests/test-topic-stack.t
--- a/README	Wed Jun 28 01:58:09 2017 +0200
+++ b/README	Wed Jun 28 02:45:57 2017 +0200
@@ -129,6 +129,7 @@
 
  - stack: also show the unstable status for the current changeset (issue5553)
  - stack: properly abort when and unknown topic is requested,
+ - stack: add basic and raw support for named branches
  - topic: changing topic on revs no longer adds extra instability (issue5441)
  - topic: topics: rename '--change' flag to '--rev' flag,
  - topic: multiple large performance improvements,
--- a/hgext3rd/topic/__init__.py	Wed Jun 28 01:58:09 2017 +0200
+++ b/hgext3rd/topic/__init__.py	Wed Jun 28 02:45:57 2017 +0200
@@ -122,19 +122,29 @@
 context.basectx.topic = _contexttopic
 
 topicrev = re.compile(r'^t\d+$')
+branchrev = re.compile(r'^b\d+$')
 
 def _namemap(repo, name):
+    revs = None
     if topicrev.match(name):
         idx = int(name[1:])
-        topic = repo.currenttopic
-        if not topic:
+        ttype = 'topic'
+        tname = topic = repo.currenttopic
+        if not tname:
             raise error.Abort(_('cannot resolve "%s": no active topic') % name)
         revs = list(stack.getstack(repo, topic=topic))
+    elif branchrev.match(name):
+        ttype = 'branch'
+        idx = int(name[1:])
+        tname = branch = repo[None].branch()
+        revs = list(stack.getstack(repo, branch=branch))
+
+    if revs is not None:
         try:
             r = revs[idx - 1]
         except IndexError:
-            msg = _('cannot resolve "%s": topic "%s" has only %d changesets')
-            raise error.Abort(msg % (name, topic, len(revs)))
+            msg = _('cannot resolve "%s": %s "%s" has only %d changesets')
+            raise error.Abort(msg % (name, ttype, tname, len(revs)))
         return [repo[r].node()]
     if name not in repo.topics:
         return []
@@ -282,7 +292,7 @@
             topic = repo.currenttopic
         if not topic:
             raise error.Abort(_('no active topic to list'))
-        return stack.showstack(ui, repo, topic, opts)
+        return stack.showstack(ui, repo, topic=topic, opts=opts)
 
     if rev:
         if not obsolete.isenabled(repo, obsolete.createmarkersopt):
@@ -309,10 +319,13 @@
 
     List the current topic by default."""
     if not topic:
+        topic = None
+    branch = None
+    if topic is None and repo.currenttopic:
         topic = repo.currenttopic
-    if not topic:
-        raise error.Abort(_('no active topic to list'))
-    return stack.showstack(ui, repo, topic=topic, opts=opts)
+    if topic is None:
+        branch = repo[None].branch()
+    return stack.showstack(ui, repo, branch=branch, topic=topic, opts=opts)
 
 def _changecurrenttopic(repo, newtopic):
     """changes the current topic."""
--- a/hgext3rd/topic/revset.py	Wed Jun 28 01:58:09 2017 +0200
+++ b/hgext3rd/topic/revset.py	Wed Jun 28 02:45:57 2017 +0200
@@ -75,7 +75,13 @@
     if not topic:
         raise error.Abort(_('no active topic to list'))
     # ordering hack, boo
-    return revset.baseset(stack.getstack(repo, topic=topic)) & subset
+    topic = None
+    branch = None
+    if not topic and repo.currenttopic:
+        topic = repo.currenttopic
+    if not topic:
+        branch = repo[None].branch()
+    return revset.baseset(stack.getstack(repo, branch=branch, topic=topic)) & subset
 
 
 def modsetup(ui):
--- a/hgext3rd/topic/stack.py	Wed Jun 28 01:58:09 2017 +0200
+++ b/hgext3rd/topic/stack.py	Wed Jun 28 02:45:57 2017 +0200
@@ -10,9 +10,16 @@
 )
 from .evolvebits import builddependencies, _orderrevs, _singlesuccessor
 
-def getstack(repo, topic=None):
+def getstack(repo, branch=None, topic=None):
     # XXX need sorting
-    trevs = repo.revs("topic(%s) - obsolete()", topic)
+    if topic is not None and branch is not None:
+        raise error.ProgrammingError('both branch and topic specified (not defined yet)')
+    elif topic is not None:
+        trevs = repo.revs("topic(%s) - obsolete()", topic)
+    elif branch is not None:
+        trevs = repo.revs("branch(%s) - obsolete()", branch)
+    else:
+        raise error.ProgrammingError('neither branch and topic specified (not defined yet)')
     return _orderrevs(repo, trevs)
 
 def labelsgen(prefix, labelssuffix):
@@ -21,12 +28,22 @@
     """
     return ' '.join(prefix % suffix for suffix in labelssuffix)
 
-def showstack(ui, repo, topic=None, opts=None):
+def showstack(ui, repo, branch=None, topic=None, opts=None):
     if opts is None:
         opts = {}
 
-    if topic not in repo.topics:
-        raise error.Abort(_('cannot resolve "%s": no such topic found') % topic)
+    if topic is not None and branch is not None:
+        msg = 'both branch and topic specified [%s]{%s}(not defined yet)'
+        msg %= (branch, topic)
+        raise error.ProgrammingError(msg)
+    elif topic is not None:
+        prefix = 't'
+        if topic not in repo.topics:
+            raise error.Abort(_('cannot resolve "%s": no such topic found') % topic)
+    elif branch is not None:
+        prefix = 'b'
+    else:
+        raise error.ProgrammingError('neither branch and topic specified (not defined yet)')
 
     fm = ui.formatter('topicstack', opts)
     prev = None
@@ -37,7 +54,7 @@
     if topic == repo.currenttopic:
         label = 'topic.active'
 
-    data = stackdata(repo, topic=topic)
+    data = stackdata(repo, branch=branch, topic=topic)
     fm.plain(_('### topic: %s') % ui.label(topic, label),
              label='topic.stack.summary.topic')
 
@@ -58,7 +75,7 @@
         fm.plain('%d behind' % data['behindcount'], label='topic.stack.summary.behindcount')
     fm.plain('\n')
 
-    for idx, r in enumerate(getstack(repo, topic=topic), 1):
+    for idx, r in enumerate(getstack(repo, branch=branch, topic=topic), 1):
         ctx = repo[r]
         p1 = ctx.p1()
         if p1.obsolete():
@@ -100,7 +117,7 @@
         if idx is None:
             fm.plain('  ')
         else:
-            fm.write('topic.stack.index', 't%d', idx,
+            fm.write('topic.stack.index', '%s%%d' % prefix, idx,
                      label='topic.stack.index ' + labelsgen('topic.stack.index.%s', states))
         fm.write('topic.stack.state.symbol', '%s', symbol,
                  label='topic.stack.state ' + labelsgen('topic.stack.state.%s', states))
@@ -113,7 +130,7 @@
         fm.plain('\n')
     fm.end()
 
-def stackdata(repo, topic=None):
+def stackdata(repo, branch=None, topic=None):
     """get various data about a stack
 
     :changesetcount: number of non-obsolete changesets in the stack
@@ -122,7 +139,7 @@
     :behindcount: number of changeset on rebase destination
     """
     data = {}
-    revs = repo.revs("topic(%s) - obsolete()", topic)
+    revs = getstack(repo, branch, topic)
     data['changesetcount'] = len(revs)
     data['troubledcount'] = len([r for r in revs if repo[r].troubled()])
     deps, rdeps = builddependencies(repo, revs)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-stack-branch.t	Wed Jun 28 02:45:57 2017 +0200
@@ -0,0 +1,257 @@
+
+  $ . "$TESTDIR/testlib/topic_setup.sh"
+
+Initial setup
+
+  $ cat << EOF >> $HGRCPATH
+  > [ui]
+  > logtemplate = {rev} {branch} \{{get(namespaces, "topics")}} {phase} {desc|firstline}\n
+  > [experimental]
+  > evolution=createmarkers,exchange,allowunstable
+  > EOF
+
+  $ hg init main
+  $ cd main
+  $ hg branch other
+  marked working directory as branch other
+  (branches are permanent and global, did you want a bookmark?)
+  $ echo aaa > aaa
+  $ hg add aaa
+  $ hg commit -m c_a
+  $ echo aaa > bbb
+  $ hg add bbb
+  $ hg commit -m c_b
+  $ hg branch foo
+  marked working directory as branch foo
+  $ echo aaa > ccc
+  $ hg add ccc
+  $ hg commit -m c_c
+  $ echo aaa > ddd
+  $ hg add ddd
+  $ hg commit -m c_d
+  $ echo aaa > eee
+  $ hg add eee
+  $ hg commit -m c_e
+  $ echo aaa > fff
+  $ hg add fff
+  $ hg commit -m c_f
+  $ hg log -G
+  @  5 foo {} draft c_f
+  |
+  o  4 foo {} draft c_e
+  |
+  o  3 foo {} draft c_d
+  |
+  o  2 foo {} draft c_c
+  |
+  o  1 other {} draft c_b
+  |
+  o  0 other {} draft c_a
+  
+
+Check that topic without any parent does not crash --list
+---------------------------------------------------------
+
+  $ hg up other
+  0 files updated, 0 files merged, 4 files removed, 0 files unresolved
+  $ hg stack
+  ### topic: None
+  ### branch: other
+  b2@ c_b (current)
+  b1: c_a
+  $ hg phase --public 'branch("other")'
+  $ hg up foo
+  4 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Simple test
+-----------
+
+'hg stack' list all changeset in the topic
+
+  $ hg branch
+  foo
+  $ hg stack
+  ### topic: None
+  ### branch: foo
+  b4@ c_f (current)
+  b3: c_e
+  b2: c_d
+  b1: c_c
+    ^ c_b
+
+Test "t#" reference
+-------------------
+
+  $ hg up b2
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ hg up foo
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg up b42
+  abort: cannot resolve "b42": branch "foo" has only 4 changesets
+  [255]
+  $ hg up b2
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ hg summary
+  parent: 3:f61adbacd17a 
+   c_d
+  branch: foo
+  commit: (clean)
+  update: 2 new changesets (update)
+  phases: 4 draft
+
+Case with some of the branch unstable
+------------------------------------
+
+  $ echo bbb > ddd
+  $ hg commit --amend
+  $ hg log -G
+  @  7 foo {} draft c_d
+  |
+  | o  5 foo {} draft c_f
+  | |
+  | o  4 foo {} draft c_e
+  | |
+  | x  3 foo {} draft c_d
+  |/
+  o  2 foo {} draft c_c
+  |
+  o  1 other {} public c_b
+  |
+  o  0 other {} public c_a
+  
+  $ hg stack
+  ### topic: None
+  ### branch: foo
+  b4$ c_f (unstable)
+  b3$ c_e (unstable)
+  b2@ c_d (current)
+  b1: c_c
+    ^ c_b
+  $ hg up b3
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg stack
+  ### topic: None
+  ### branch: foo
+  b4$ c_f (unstable)
+  b3$ c_e (current unstable)
+  b2: c_d
+  b1: c_c
+    ^ c_b
+  $ hg up b2
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+
+Also test the revset:
+
+  $ hg log -r 'stack()'
+  abort: no active topic to list
+  [255]
+
+Case with multiple heads on the topic
+-------------------------------------
+
+Make things linear again
+
+  $ hg rebase -s 'desc(c_e)' -d 'desc(c_d) - obsolete()'
+  rebasing 4:4f2a69f6d380 "c_e"
+  rebasing 5:913c298d8b0a "c_f"
+  $ hg log -G
+  o  9 foo {} draft c_f
+  |
+  o  8 foo {} draft c_e
+  |
+  @  7 foo {} draft c_d
+  |
+  o  2 foo {} draft c_c
+  |
+  o  1 other {} public c_b
+  |
+  o  0 other {} public c_a
+  
+
+Create the second branch
+
+  $ hg up 'desc(c_d)'
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo aaa > ggg
+  $ hg add ggg
+  $ hg commit -m c_g
+  created new head
+  $ echo aaa > hhh
+  $ hg add hhh
+  $ hg commit -m c_h
+  $ hg log -G
+  @  11 foo {} draft c_h
+  |
+  o  10 foo {} draft c_g
+  |
+  | o  9 foo {} draft c_f
+  | |
+  | o  8 foo {} draft c_e
+  |/
+  o  7 foo {} draft c_d
+  |
+  o  2 foo {} draft c_c
+  |
+  o  1 other {} public c_b
+  |
+  o  0 other {} public c_a
+  
+
+Test output
+
+  $ hg stack
+  ### topic: None (2 heads)
+  ### branch: foo
+  b6: c_f
+  b5: c_e
+  b2^ c_d (base)
+  b4@ c_h (current)
+  b3: c_g
+  b2: c_d
+  b1: c_c
+    ^ c_b
+
+Case with multiple heads on the topic with unstability involved
+---------------------------------------------------------------
+
+We amend the message to make sure the display base pick the right changeset
+
+  $ hg up 'desc(c_d)'
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ echo ccc > ddd
+  $ hg commit --amend -m 'c_D' 
+  $ hg rebase -d . -s 'desc(c_g)'
+  rebasing 10:2ebb6e48ab8a "c_g"
+  rebasing 11:634f38e27a1d "c_h"
+  $ hg log -G
+  o  15 foo {} draft c_h
+  |
+  o  14 foo {} draft c_g
+  |
+  @  13 foo {} draft c_D
+  |
+  | o  9 foo {} draft c_f
+  | |
+  | o  8 foo {} draft c_e
+  | |
+  | x  7 foo {} draft c_d
+  |/
+  o  2 foo {} draft c_c
+  |
+  o  1 other {} public c_b
+  |
+  o  0 other {} public c_a
+  
+
+  $ hg stack
+  ### topic: None (2 heads)
+  ### branch: foo
+  b6$ c_f (unstable)
+  b5$ c_e (unstable)
+  b2^ c_D (base)
+  b4: c_h
+  b3: c_g
+  b2@ c_D (current)
+  b1: c_c
+    ^ c_b
+
--- a/tests/test-topic-stack.t	Wed Jun 28 01:58:09 2017 +0200
+++ b/tests/test-topic-stack.t	Wed Jun 28 02:45:57 2017 +0200
@@ -129,8 +129,14 @@
 
   $ hg topic --clear
   $ hg stack
-  abort: no active topic to list
-  [255]
+  ### topic: None
+  ### branch: default
+  b6@ c_f (current)
+  b5: c_e
+  b4: c_d
+  b3: c_c
+  b2: c_b
+  b1: c_a
 
 Test "t#" reference
 -------------------