tests/drawdag.py
changeset 34214 5a1b41268b7c
parent 34213 1e71dddc10a2
child 35838 4e41b59633fa
equal deleted inserted replaced
34213:1e71dddc10a2 34214:5a1b41268b7c
    90 from mercurial import (
    90 from mercurial import (
    91     context,
    91     context,
    92     error,
    92     error,
    93     node,
    93     node,
    94     obsolete,
    94     obsolete,
       
    95     pycompat,
    95     registrar,
    96     registrar,
    96     scmutil,
    97     scmutil,
    97     tags as tagsmod,
    98     tags as tagsmod,
    98 )
    99 )
    99 
   100 
   100 cmdtable = {}
   101 cmdtable = {}
   101 command = registrar.command(cmdtable)
   102 command = registrar.command(cmdtable)
   102 
   103 
   103 _pipechars = '\\/+-|'
   104 _pipechars = b'\\/+-|'
   104 _nonpipechars = ''.join(chr(i) for i in xrange(33, 127)
   105 _nonpipechars = b''.join(pycompat.bytechr(i) for i in range(33, 127)
   105                         if chr(i) not in _pipechars)
   106                          if pycompat.bytechr(i) not in _pipechars)
   106 
   107 
   107 def _isname(ch):
   108 def _isname(ch):
   108     """char -> bool. return True if ch looks like part of a name, False
   109     """char -> bool. return True if ch looks like part of a name, False
   109     otherwise"""
   110     otherwise"""
   110     return ch in _nonpipechars
   111     return ch in _nonpipechars
   111 
   112 
   112 def _parseasciigraph(text):
   113 def _parseasciigraph(text):
   113     r"""str -> {str : [str]}. convert the ASCII graph to edges
   114     r"""str -> {str : [str]}. convert the ASCII graph to edges
   114 
   115 
   115     >>> import pprint
   116     >>> import pprint
   116     >>> pprint.pprint({k: [vv for vv in v]
   117     >>> pprint.pprint({pycompat.sysstr(k): [pycompat.sysstr(vv) for vv in v]
   117     ...  for k, v in _parseasciigraph(br'''
   118     ...  for k, v in _parseasciigraph(br'''
   118     ...        G
   119     ...        G
   119     ...        |
   120     ...        |
   120     ...  I D C F   # split: B -> E, F, G
   121     ...  I D C F   # split: B -> E, F, G
   121     ...   \ \| |   # replace: C -> D -> H
   122     ...   \ \| |   # replace: C -> D -> H
   130      'E': ['A'],
   131      'E': ['A'],
   131      'F': ['E'],
   132      'F': ['E'],
   132      'G': ['F'],
   133      'G': ['F'],
   133      'H': ['A'],
   134      'H': ['A'],
   134      'I': ['H']}
   135      'I': ['H']}
   135     >>> pprint.pprint({k: [vv for vv in v]
   136     >>> pprint.pprint({pycompat.sysstr(k): [pycompat.sysstr(vv) for vv in v]
   136     ...  for k, v in _parseasciigraph(br'''
   137     ...  for k, v in _parseasciigraph(br'''
   137     ...  o    foo
   138     ...  o    foo
   138     ...  |\
   139     ...  |\
   139     ...  +---o  bar
   140     ...  +---o  bar
   140     ...  | | |
   141     ...  | | |
   161 
   162 
   162     def get(y, x):
   163     def get(y, x):
   163         """(int, int) -> char. give a coordinate, return the char. return a
   164         """(int, int) -> char. give a coordinate, return the char. return a
   164         space for anything out of range"""
   165         space for anything out of range"""
   165         if x < 0 or y < 0:
   166         if x < 0 or y < 0:
   166             return ' '
   167             return b' '
   167         try:
   168         try:
   168             return lines[y][x]
   169             return lines[y][x:x + 1] or b' '
   169         except IndexError:
   170         except IndexError:
   170             return ' '
   171             return b' '
   171 
   172 
   172     def getname(y, x):
   173     def getname(y, x):
   173         """(int, int) -> str. like get(y, x) but concatenate left and right
   174         """(int, int) -> str. like get(y, x) but concatenate left and right
   174         parts. if name is an 'o', try to replace it to the right"""
   175         parts. if name is an 'o', try to replace it to the right"""
   175         result = ''
   176         result = b''
   176         for i in itertools.count(0):
   177         for i in itertools.count(0):
   177             ch = get(y, x - i)
   178             ch = get(y, x - i)
   178             if not _isname(ch):
   179             if not _isname(ch):
   179                 break
   180                 break
   180             result = ch + result
   181             result = ch + result
   181         for i in itertools.count(1):
   182         for i in itertools.count(1):
   182             ch = get(y, x + i)
   183             ch = get(y, x + i)
   183             if not _isname(ch):
   184             if not _isname(ch):
   184                 break
   185                 break
   185             result += ch
   186             result += ch
   186         if result == 'o':
   187         if result == b'o':
   187             # special handling, find the name to the right
   188             # special handling, find the name to the right
   188             result = ''
   189             result = b''
   189             for i in itertools.count(2):
   190             for i in itertools.count(2):
   190                 ch = get(y, x + i)
   191                 ch = get(y, x + i)
   191                 if ch == ' ' or ch in _pipechars:
   192                 if ch == b' ' or ch in _pipechars:
   192                     if result or x + i >= len(lines[y]):
   193                     if result or x + i >= len(lines[y]):
   193                         break
   194                         break
   194                 else:
   195                 else:
   195                     result += ch
   196                     result += ch
   196             return result or 'o'
   197             return result or b'o'
   197         return result
   198         return result
   198 
   199 
   199     def parents(y, x):
   200     def parents(y, x):
   200         """(int, int) -> [str]. follow the ASCII edges at given position,
   201         """(int, int) -> [str]. follow the ASCII edges at given position,
   201         return a list of parents"""
   202         return a list of parents"""
   207             """conditionally append (y, x) to visit array, if it's a char
   208             """conditionally append (y, x) to visit array, if it's a char
   208             in excepted. 'o' in expected means an '_isname' test.
   209             in excepted. 'o' in expected means an '_isname' test.
   209             if '-' (or '+') is not in excepted, and get(y, x) is '-' (or '+'),
   210             if '-' (or '+') is not in excepted, and get(y, x) is '-' (or '+'),
   210             the next line (y + 1, x) will be checked instead."""
   211             the next line (y + 1, x) will be checked instead."""
   211             ch = get(y, x)
   212             ch = get(y, x)
   212             if any(ch == c and c not in expected for c in '-+'):
   213             if any(ch == c and c not in expected for c in (b'-', b'+')):
   213                 y += 1
   214                 y += 1
   214                 return follow(y + 1, x, expected)
   215                 return follow(y + 1, x, expected)
   215             if ch in expected or ('o' in expected and _isname(ch)):
   216             if ch in expected or (b'o' in expected and _isname(ch)):
   216                 visit.append((y, x))
   217                 visit.append((y, x))
   217 
   218 
   218         #  -o-  # starting point:
   219         #  -o-  # starting point:
   219         #  /|\ # follow '-' (horizontally), and '/|\' (to the bottom)
   220         #  /|\ # follow '-' (horizontally), and '/|\' (to the bottom)
   220         follow(y + 1, x, '|')
   221         follow(y + 1, x, b'|')
   221         follow(y + 1, x - 1, '/')
   222         follow(y + 1, x - 1, b'/')
   222         follow(y + 1, x + 1, '\\')
   223         follow(y + 1, x + 1, b'\\')
   223         follow(y, x - 1, '-')
   224         follow(y, x - 1, b'-')
   224         follow(y, x + 1, '-')
   225         follow(y, x + 1, b'-')
   225 
   226 
   226         while visit:
   227         while visit:
   227             y, x = visit.pop()
   228             y, x = visit.pop()
   228             if (y, x) in visited:
   229             if (y, x) in visited:
   229                 continue
   230                 continue
   230             visited.add((y, x))
   231             visited.add((y, x))
   231             ch = get(y, x)
   232             ch = get(y, x)
   232             if _isname(ch):
   233             if _isname(ch):
   233                 result.append(getname(y, x))
   234                 result.append(getname(y, x))
   234                 continue
   235                 continue
   235             elif ch == '|':
   236             elif ch == b'|':
   236                 follow(y + 1, x, '/|o')
   237                 follow(y + 1, x, b'/|o')
   237                 follow(y + 1, x - 1, '/')
   238                 follow(y + 1, x - 1, b'/')
   238                 follow(y + 1, x + 1, '\\')
   239                 follow(y + 1, x + 1, b'\\')
   239             elif ch == '+':
   240             elif ch == b'+':
   240                 follow(y, x - 1, '-')
   241                 follow(y, x - 1, b'-')
   241                 follow(y, x + 1, '-')
   242                 follow(y, x + 1, b'-')
   242                 follow(y + 1, x - 1, '/')
   243                 follow(y + 1, x - 1, b'/')
   243                 follow(y + 1, x + 1, '\\')
   244                 follow(y + 1, x + 1, b'\\')
   244                 follow(y + 1, x, '|')
   245                 follow(y + 1, x, b'|')
   245             elif ch == '\\':
   246             elif ch == b'\\':
   246                 follow(y + 1, x + 1, '\\|o')
   247                 follow(y + 1, x + 1, b'\\|o')
   247             elif ch == '/':
   248             elif ch == b'/':
   248                 follow(y + 1, x - 1, '/|o')
   249                 follow(y + 1, x - 1, b'/|o')
   249             elif ch == '-':
   250             elif ch == b'-':
   250                 follow(y, x - 1, '-+o')
   251                 follow(y, x - 1, b'-+o')
   251                 follow(y, x + 1, '-+o')
   252                 follow(y, x + 1, b'-+o')
   252         return result
   253         return result
   253 
   254 
   254     for y, line in enumerate(lines):
   255     for y, line in enumerate(lines):
   255         for x, ch in enumerate(line):
   256         for x, ch in enumerate(pycompat.bytestr(line)):
   256             if ch == '#':  # comment
   257             if ch == b'#':  # comment
   257                 break
   258                 break
   258             if _isname(ch):
   259             if _isname(ch):
   259                 edges[getname(y, x)] += parents(y, x)
   260                 edges[getname(y, x)] += parents(y, x)
   260 
   261 
   261     return dict(edges)
   262     return dict(edges)
   276 
   277 
   277     def renamed(self):
   278     def renamed(self):
   278         return None
   279         return None
   279 
   280 
   280     def flags(self):
   281     def flags(self):
   281         return ''
   282         return b''
   282 
   283 
   283 class simplecommitctx(context.committablectx):
   284 class simplecommitctx(context.committablectx):
   284     def __init__(self, repo, name, parentctxs, added):
   285     def __init__(self, repo, name, parentctxs, added):
   285         opts = {
   286         opts = {
   286             'changes': scmutil.status([], list(added), [], [], [], [], []),
   287             'changes': scmutil.status([], list(added), [], [], [], [], []),
   287             'date': '0 0',
   288             'date': b'0 0',
   288             'extra': {'branch': 'default'},
   289             'extra': {b'branch': b'default'},
   289         }
   290         }
   290         super(simplecommitctx, self).__init__(self, name, **opts)
   291         super(simplecommitctx, self).__init__(self, name, **opts)
   291         self._repo = repo
   292         self._repo = repo
   292         self._added = added
   293         self._added = added
   293         self._parents = parentctxs
   294         self._parents = parentctxs
   302 
   303 
   303 def _walkgraph(edges):
   304 def _walkgraph(edges):
   304     """yield node, parents in topologically order"""
   305     """yield node, parents in topologically order"""
   305     visible = set(edges.keys())
   306     visible = set(edges.keys())
   306     remaining = {}  # {str: [str]}
   307     remaining = {}  # {str: [str]}
   307     for k, vs in edges.iteritems():
   308     for k, vs in edges.items():
   308         for v in vs:
   309         for v in vs:
   309             if v not in remaining:
   310             if v not in remaining:
   310                 remaining[v] = []
   311                 remaining[v] = []
   311         remaining[k] = vs[:]
   312         remaining[k] = vs[:]
   312     while remaining:
   313     while remaining:
   315             raise error.Abort(_('the graph has cycles'))
   316             raise error.Abort(_('the graph has cycles'))
   316         for leaf in sorted(leafs):
   317         for leaf in sorted(leafs):
   317             if leaf in visible:
   318             if leaf in visible:
   318                 yield leaf, edges[leaf]
   319                 yield leaf, edges[leaf]
   319             del remaining[leaf]
   320             del remaining[leaf]
   320             for k, v in remaining.iteritems():
   321             for k, v in remaining.items():
   321                 if leaf in v:
   322                 if leaf in v:
   322                     v.remove(leaf)
   323                     v.remove(leaf)
   323 
   324 
   324 def _getcomments(text):
   325 def _getcomments(text):
   325     """
   326     """
   326     >>> [s for s in _getcomments(br'''
   327     >>> [pycompat.sysstr(s) for s in _getcomments(br'''
   327     ...        G
   328     ...        G
   328     ...        |
   329     ...        |
   329     ...  I D C F   # split: B -> E, F, G
   330     ...  I D C F   # split: B -> E, F, G
   330     ...   \ \| |   # replace: C -> D -> H
   331     ...   \ \| |   # replace: C -> D -> H
   331     ...    H B E   # prune: F, I
   332     ...    H B E   # prune: F, I
   333     ...      A
   334     ...      A
   334     ... ''')]
   335     ... ''')]
   335     ['split: B -> E, F, G', 'replace: C -> D -> H', 'prune: F, I']
   336     ['split: B -> E, F, G', 'replace: C -> D -> H', 'prune: F, I']
   336     """
   337     """
   337     for line in text.splitlines():
   338     for line in text.splitlines():
   338         if ' # ' not in line:
   339         if b' # ' not in line:
   339             continue
   340             continue
   340         yield line.split(' # ', 1)[1].split(' # ')[0].strip()
   341         yield line.split(b' # ', 1)[1].split(b' # ')[0].strip()
   341 
   342 
   342 @command('debugdrawdag', [])
   343 @command(b'debugdrawdag', [])
   343 def debugdrawdag(ui, repo, **opts):
   344 def debugdrawdag(ui, repo, **opts):
   344     """read an ASCII graph from stdin and create changesets
   345     """read an ASCII graph from stdin and create changesets
   345 
   346 
   346     The ASCII graph is like what :hg:`log -G` outputs, with each `o` replaced
   347     The ASCII graph is like what :hg:`log -G` outputs, with each `o` replaced
   347     to the name of the node. The command will create dummy changesets and local
   348     to the name of the node. The command will create dummy changesets and local
   358     """
   359     """
   359     text = ui.fin.read()
   360     text = ui.fin.read()
   360 
   361 
   361     # parse the graph and make sure len(parents) <= 2 for each node
   362     # parse the graph and make sure len(parents) <= 2 for each node
   362     edges = _parseasciigraph(text)
   363     edges = _parseasciigraph(text)
   363     for k, v in edges.iteritems():
   364     for k, v in edges.items():
   364         if len(v) > 2:
   365         if len(v) > 2:
   365             raise error.Abort(_('%s: too many parents: %s')
   366             raise error.Abort(_('%s: too many parents: %s')
   366                               % (k, ' '.join(v)))
   367                               % (k, b' '.join(v)))
   367 
   368 
   368     # parse comments to get extra file content instructions
   369     # parse comments to get extra file content instructions
   369     files = collections.defaultdict(dict) # {(name, path): content}
   370     files = collections.defaultdict(dict) # {(name, path): content}
   370     comments = list(_getcomments(text))
   371     comments = list(_getcomments(text))
   371     filere = re.compile(r'^(\w+)/([\w/]+)\s*=\s*(.*)$', re.M)
   372     filere = re.compile(br'^(\w+)/([\w/]+)\s*=\s*(.*)$', re.M)
   372     for name, path, content in filere.findall('\n'.join(comments)):
   373     for name, path, content in filere.findall(b'\n'.join(comments)):
   373         files[name][path] = content.replace(r'\n', '\n')
   374         files[name][path] = content.replace(br'\n', b'\n')
   374 
   375 
   375     committed = {None: node.nullid}  # {name: node}
   376     committed = {None: node.nullid}  # {name: node}
   376 
   377 
   377     # for leaf nodes, try to find existing nodes in repo
   378     # for leaf nodes, try to find existing nodes in repo
   378     for name, parents in edges.iteritems():
   379     for name, parents in edges.items():
   379         if len(parents) == 0:
   380         if len(parents) == 0:
   380             try:
   381             try:
   381                 committed[name] = scmutil.revsingle(repo, name)
   382                 committed[name] = scmutil.revsingle(repo, name)
   382             except error.RepoLookupError:
   383             except error.RepoLookupError:
   383                 pass
   384                 pass
   405         committed[name] = n
   406         committed[name] = n
   406         tagsmod.tag(repo, [name], n, message=None, user=None, date=None,
   407         tagsmod.tag(repo, [name], n, message=None, user=None, date=None,
   407                     local=True)
   408                     local=True)
   408 
   409 
   409     # handle special comments
   410     # handle special comments
   410     with repo.wlock(), repo.lock(), repo.transaction('drawdag'):
   411     with repo.wlock(), repo.lock(), repo.transaction(b'drawdag'):
   411         getctx = lambda x: repo.unfiltered()[committed[x.strip()]]
   412         getctx = lambda x: repo.unfiltered()[committed[x.strip()]]
   412         for comment in comments:
   413         for comment in comments:
   413             rels = [] # obsolete relationships
   414             rels = [] # obsolete relationships
   414             args = comment.split(':', 1)
   415             args = comment.split(b':', 1)
   415             if len(args) <= 1:
   416             if len(args) <= 1:
   416                 continue
   417                 continue
   417 
   418 
   418             cmd = args[0].strip()
   419             cmd = args[0].strip()
   419             arg = args[1].strip()
   420             arg = args[1].strip()
   420 
   421 
   421             if cmd in ('replace', 'rebase', 'amend'):
   422             if cmd in (b'replace', b'rebase', b'amend'):
   422                 nodes = [getctx(m) for m in arg.split('->')]
   423                 nodes = [getctx(m) for m in arg.split(b'->')]
   423                 for i in range(len(nodes) - 1):
   424                 for i in range(len(nodes) - 1):
   424                     rels.append((nodes[i], (nodes[i + 1],)))
   425                     rels.append((nodes[i], (nodes[i + 1],)))
   425             elif cmd in ('split',):
   426             elif cmd in (b'split',):
   426                 pre, succs = arg.split('->')
   427                 pre, succs = arg.split(b'->')
   427                 succs = succs.split(',')
   428                 succs = succs.split(b',')
   428                 rels.append((getctx(pre), (getctx(s) for s in succs)))
   429                 rels.append((getctx(pre), (getctx(s) for s in succs)))
   429             elif cmd in ('prune',):
   430             elif cmd in (b'prune',):
   430                 for n in arg.split(','):
   431                 for n in arg.split(b','):
   431                     rels.append((getctx(n), ()))
   432                     rels.append((getctx(n), ()))
   432             if rels:
   433             if rels:
   433                 obsolete.createmarkers(repo, rels, date=(0, 0), operation=cmd)
   434                 obsolete.createmarkers(repo, rels, date=(0, 0), operation=cmd)