##// END OF EJS Templates
localrepo: use context manager for lock and transaction in commitctx()...
localrepo: use context manager for lock and transaction in commitctx() Differential Revision: https://phab.mercurial-scm.org/D5624

File last commit:

r39473:9a813e4c default
r41400:0132221c default
Show More
drawdag.py
434 lines | 12.8 KiB | text/x-python | PythonLexer
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449 # drawdag.py - convert ASCII revision DAG to actual changesets
#
# Copyright 2016 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
"""
create changesets from an ASCII graph for testing purpose.
For example, given the following input::
c d
|/
b
|
a
4 changesets and 4 local tags will be created.
`hg log -G -T "{rev} {desc} (tag: {tags})"` will output::
o 3 d (tag: d tip)
|
| o 2 c (tag: c)
|/
o 1 b (tag: b)
|
o 0 a (tag: a)
For root nodes (nodes without parents) in the graph, they can be revsets
pointing to existing nodes. The ASCII graph could also have disconnected
components with same names referring to the same changeset.
Therefore, given the repo having the 4 changesets (and tags) above, with the
following ASCII graph as input::
foo bar bar foo
| / | |
ancestor(c,d) a baz
The result (`hg log -G -T "{desc}"`) will look like::
o foo
|\
+---o bar
| | |
| o | baz
| /
+---o d
| |
+---o c
| |
o | b
|/
o a
Note that if you take the above `hg log` output directly as input. It will work
as expected - the result would be an isomorphic graph::
o foo
|\
| | o d
| |/
| | o c
| |/
| | o bar
| |/|
| o | b
| |/
o / baz
/
o a
This is because 'o' is specially handled in the input: instead of using 'o' as
the node name, the word to the right will be used.
Jun Wu
drawdag: support obsmarker creation in comments...
r33154
Some special comments could have side effects:
- Create obsmarkers
# replace: A -> B -> C -> D # chained 1 to 1 replacements
# split: A -> B, C # 1 to many
# prune: A, B, C # many to nothing
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449 """
from __future__ import absolute_import, print_function
import collections
import itertools
Jun Wu
drawdag: allow override file contents via comments...
r33785 import re
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449
from mercurial.i18n import _
from mercurial import (
context,
error,
node,
Jun Wu
drawdag: support obsmarker creation in comments...
r33154 obsolete,
Augie Fackler
drawdag: port to python 3
r34204 pycompat,
Yuya Nishihara
registrar: move cmdutil.command to registrar module (API)...
r32337 registrar,
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449 scmutil,
Pierre-Yves David
drawdag: use 'tagsmod.tag' instead of 'repo.tag'...
r31671 tags as tagsmod,
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449 )
cmdtable = {}
Yuya Nishihara
registrar: move cmdutil.command to registrar module (API)...
r32337 command = registrar.command(cmdtable)
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449
Augie Fackler
drawdag: port to python 3
r34204 _pipechars = b'\\/+-|'
_nonpipechars = b''.join(pycompat.bytechr(i) for i in range(33, 127)
if pycompat.bytechr(i) not in _pipechars)
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449
def _isname(ch):
"""char -> bool. return True if ch looks like part of a name, False
otherwise"""
return ch in _nonpipechars
def _parseasciigraph(text):
Augie Fackler
drawdag: add a couple of doctests to help with python3 porting
r34203 r"""str -> {str : [str]}. convert the ASCII graph to edges
>>> import pprint
Augie Fackler
drawdag: port to python 3
r34204 >>> pprint.pprint({pycompat.sysstr(k): [pycompat.sysstr(vv) for vv in v]
Augie Fackler
drawdag: add a couple of doctests to help with python3 porting
r34203 ... for k, v in _parseasciigraph(br'''
... G
... |
... I D C F # split: B -> E, F, G
... \ \| | # replace: C -> D -> H
... H B E # prune: F, I
... \|/
... A
... ''').items()})
{'A': [],
'B': ['A'],
'C': ['B'],
'D': ['B'],
'E': ['A'],
'F': ['E'],
'G': ['F'],
'H': ['A'],
'I': ['H']}
Augie Fackler
drawdag: port to python 3
r34204 >>> pprint.pprint({pycompat.sysstr(k): [pycompat.sysstr(vv) for vv in v]
Augie Fackler
drawdag: add a couple of doctests to help with python3 porting
r34203 ... for k, v in _parseasciigraph(br'''
... o foo
... |\
... +---o bar
... | | |
... | o | baz
... | /
... +---o d
... | |
... +---o c
... | |
... o | b
... |/
... o a
... ''').items()})
{'a': [],
'b': ['a'],
'bar': ['b', 'a'],
'baz': [],
'c': ['b'],
'd': ['b'],
'foo': ['baz', 'b']}
"""
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449 lines = text.splitlines()
edges = collections.defaultdict(list) # {node: []}
def get(y, x):
"""(int, int) -> char. give a coordinate, return the char. return a
space for anything out of range"""
if x < 0 or y < 0:
Augie Fackler
drawdag: port to python 3
r34204 return b' '
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449 try:
Augie Fackler
drawdag: port to python 3
r34204 return lines[y][x:x + 1] or b' '
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449 except IndexError:
Augie Fackler
drawdag: port to python 3
r34204 return b' '
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449
def getname(y, x):
"""(int, int) -> str. like get(y, x) but concatenate left and right
parts. if name is an 'o', try to replace it to the right"""
Augie Fackler
drawdag: port to python 3
r34204 result = b''
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449 for i in itertools.count(0):
ch = get(y, x - i)
if not _isname(ch):
break
result = ch + result
for i in itertools.count(1):
ch = get(y, x + i)
if not _isname(ch):
break
result += ch
Augie Fackler
drawdag: port to python 3
r34204 if result == b'o':
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449 # special handling, find the name to the right
Augie Fackler
drawdag: port to python 3
r34204 result = b''
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449 for i in itertools.count(2):
ch = get(y, x + i)
Augie Fackler
drawdag: port to python 3
r34204 if ch == b' ' or ch in _pipechars:
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449 if result or x + i >= len(lines[y]):
break
else:
result += ch
Augie Fackler
drawdag: port to python 3
r34204 return result or b'o'
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449 return result
def parents(y, x):
"""(int, int) -> [str]. follow the ASCII edges at given position,
return a list of parents"""
Martin von Zweigbergk
cleanup: use set literals...
r32291 visited = {(y, x)}
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449 visit = []
result = []
def follow(y, x, expected):
"""conditionally append (y, x) to visit array, if it's a char
in excepted. 'o' in expected means an '_isname' test.
if '-' (or '+') is not in excepted, and get(y, x) is '-' (or '+'),
the next line (y + 1, x) will be checked instead."""
ch = get(y, x)
Augie Fackler
drawdag: port to python 3
r34204 if any(ch == c and c not in expected for c in (b'-', b'+')):
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449 y += 1
return follow(y + 1, x, expected)
Augie Fackler
drawdag: port to python 3
r34204 if ch in expected or (b'o' in expected and _isname(ch)):
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449 visit.append((y, x))
# -o- # starting point:
# /|\ # follow '-' (horizontally), and '/|\' (to the bottom)
Augie Fackler
drawdag: port to python 3
r34204 follow(y + 1, x, b'|')
follow(y + 1, x - 1, b'/')
follow(y + 1, x + 1, b'\\')
follow(y, x - 1, b'-')
follow(y, x + 1, b'-')
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449
while visit:
y, x = visit.pop()
if (y, x) in visited:
continue
visited.add((y, x))
ch = get(y, x)
if _isname(ch):
result.append(getname(y, x))
continue
Augie Fackler
drawdag: port to python 3
r34204 elif ch == b'|':
follow(y + 1, x, b'/|o')
follow(y + 1, x - 1, b'/')
follow(y + 1, x + 1, b'\\')
elif ch == b'+':
follow(y, x - 1, b'-')
follow(y, x + 1, b'-')
follow(y + 1, x - 1, b'/')
follow(y + 1, x + 1, b'\\')
follow(y + 1, x, b'|')
elif ch == b'\\':
follow(y + 1, x + 1, b'\\|o')
elif ch == b'/':
follow(y + 1, x - 1, b'/|o')
elif ch == b'-':
follow(y, x - 1, b'-+o')
follow(y, x + 1, b'-+o')
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449 return result
for y, line in enumerate(lines):
Augie Fackler
drawdag: port to python 3
r34204 for x, ch in enumerate(pycompat.bytestr(line)):
if ch == b'#': # comment
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449 break
if _isname(ch):
edges[getname(y, x)] += parents(y, x)
return dict(edges)
class simplefilectx(object):
def __init__(self, path, data):
self._data = data
self._path = path
def data(self):
return self._data
Jun Wu
drawdag: provide filenode for its dummy filectx...
r32305 def filenode(self):
return None
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449 def path(self):
return self._path
def renamed(self):
return None
def flags(self):
Augie Fackler
drawdag: port to python 3
r34204 return b''
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449
class simplecommitctx(context.committablectx):
Martin von Zweigbergk
drawdag: include files from both parents in merge commits...
r33558 def __init__(self, repo, name, parentctxs, added):
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449 opts = {
Martin von Zweigbergk
drawdag: include files from both parents in merge commits...
r33558 'changes': scmutil.status([], list(added), [], [], [], [], []),
Augie Fackler
drawdag: port to python 3
r34204 'date': b'0 0',
'extra': {b'branch': b'default'},
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449 }
Martin von Zweigbergk
drawdag: correctly pass repo to super constructor...
r39473 super(simplecommitctx, self).__init__(repo, name, **opts)
Martin von Zweigbergk
drawdag: include files from both parents in merge commits...
r33558 self._added = added
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449 self._parents = parentctxs
while len(self._parents) < 2:
self._parents.append(repo[node.nullid])
def filectx(self, key):
Martin von Zweigbergk
drawdag: include files from both parents in merge commits...
r33558 return simplefilectx(key, self._added[key])
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449
def commit(self):
return self._repo.commitctx(self)
def _walkgraph(edges):
"""yield node, parents in topologically order"""
visible = set(edges.keys())
remaining = {} # {str: [str]}
Augie Fackler
drawdag: port to python 3
r34204 for k, vs in edges.items():
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449 for v in vs:
if v not in remaining:
remaining[v] = []
remaining[k] = vs[:]
while remaining:
leafs = [k for k, v in remaining.items() if not v]
if not leafs:
raise error.Abort(_('the graph has cycles'))
for leaf in sorted(leafs):
if leaf in visible:
yield leaf, edges[leaf]
del remaining[leaf]
Augie Fackler
drawdag: port to python 3
r34204 for k, v in remaining.items():
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449 if leaf in v:
v.remove(leaf)
Jun Wu
drawdag: allow override file contents via comments...
r33785 def _getcomments(text):
Augie Fackler
drawdag: add a couple of doctests to help with python3 porting
r34203 """
Augie Fackler
drawdag: port to python 3
r34204 >>> [pycompat.sysstr(s) for s in _getcomments(br'''
Augie Fackler
drawdag: add a couple of doctests to help with python3 porting
r34203 ... G
... |
... I D C F # split: B -> E, F, G
... \ \| | # replace: C -> D -> H
... H B E # prune: F, I
... \|/
... A
... ''')]
['split: B -> E, F, G', 'replace: C -> D -> H', 'prune: F, I']
"""
Jun Wu
drawdag: allow override file contents via comments...
r33785 for line in text.splitlines():
Augie Fackler
drawdag: port to python 3
r34204 if b' # ' not in line:
Jun Wu
drawdag: allow override file contents via comments...
r33785 continue
Augie Fackler
drawdag: port to python 3
r34204 yield line.split(b' # ', 1)[1].split(b' # ')[0].strip()
Jun Wu
drawdag: allow override file contents via comments...
r33785
Augie Fackler
drawdag: port to python 3
r34204 @command(b'debugdrawdag', [])
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449 def debugdrawdag(ui, repo, **opts):
"""read an ASCII graph from stdin and create changesets
The ASCII graph is like what :hg:`log -G` outputs, with each `o` replaced
to the name of the node. The command will create dummy changesets and local
tags with those names to make the dummy changesets easier to be referred
to.
If the name of a node is a single character 'o', It will be replaced by the
word to the right. This makes it easier to reuse
:hg:`log -G -T '{desc}'` outputs.
For root (no parents) nodes, revset can be used to query existing repo.
Note that the revset cannot have confusing characters which can be seen as
the part of the graph edges, like `|/+-\`.
"""
text = ui.fin.read()
# parse the graph and make sure len(parents) <= 2 for each node
edges = _parseasciigraph(text)
Augie Fackler
drawdag: port to python 3
r34204 for k, v in edges.items():
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449 if len(v) > 2:
raise error.Abort(_('%s: too many parents: %s')
Augie Fackler
drawdag: port to python 3
r34204 % (k, b' '.join(v)))
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449
Jun Wu
drawdag: allow override file contents via comments...
r33785 # parse comments to get extra file content instructions
files = collections.defaultdict(dict) # {(name, path): content}
comments = list(_getcomments(text))
Augie Fackler
drawdag: port to python 3
r34204 filere = re.compile(br'^(\w+)/([\w/]+)\s*=\s*(.*)$', re.M)
for name, path, content in filere.findall(b'\n'.join(comments)):
Jun Wu
lfs: add a test showing bundle application could be broken...
r36763 content = content.replace(br'\n', b'\n').replace(br'\1', b'\1')
files[name][path] = content
Jun Wu
drawdag: allow override file contents via comments...
r33785
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449 committed = {None: node.nullid} # {name: node}
# for leaf nodes, try to find existing nodes in repo
Augie Fackler
drawdag: port to python 3
r34204 for name, parents in edges.items():
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449 if len(parents) == 0:
try:
committed[name] = scmutil.revsingle(repo, name)
except error.RepoLookupError:
pass
# commit in topological order
for name, parents in _walkgraph(edges):
if name in committed:
continue
pctxs = [repo[committed[n]] for n in parents]
Martin von Zweigbergk
drawdag: include files from both parents in merge commits...
r33558 pctxs.sort(key=lambda c: c.node())
added = {}
if len(parents) > 1:
# If it's a merge, take the files and contents from the parents
for f in pctxs[1].manifest():
if f not in pctxs[0].manifest():
added[f] = pctxs[1][f].data()
else:
# If it's not a merge, add a single file
added[name] = name
Jun Wu
drawdag: allow override file contents via comments...
r33785 # add extra file contents in comments
for path, content in files.get(name, {}).items():
added[path] = content
Martin von Zweigbergk
drawdag: include files from both parents in merge commits...
r33558 ctx = simplecommitctx(repo, name, pctxs, added)
Jun Wu
drawdag: update test repos by drawing the changelog DAG in ASCII...
r30449 n = ctx.commit()
committed[name] = n
Augie Fackler
drawdag: tagsmod.tag() takes a list of names, not a single name...
r34202 tagsmod.tag(repo, [name], n, message=None, user=None, date=None,
Pierre-Yves David
drawdag: use 'tagsmod.tag' instead of 'repo.tag'...
r31671 local=True)
Jun Wu
drawdag: support obsmarker creation in comments...
r33154
# handle special comments
Augie Fackler
drawdag: port to python 3
r34204 with repo.wlock(), repo.lock(), repo.transaction(b'drawdag'):
Jun Wu
drawdag: support obsmarker creation in comments...
r33154 getctx = lambda x: repo.unfiltered()[committed[x.strip()]]
Jun Wu
drawdag: allow override file contents via comments...
r33785 for comment in comments:
Jun Wu
drawdag: support obsmarker creation in comments...
r33154 rels = [] # obsolete relationships
Augie Fackler
drawdag: port to python 3
r34204 args = comment.split(b':', 1)
Jun Wu
drawdag: support obsmarker creation in comments...
r33154 if len(args) <= 1:
continue
cmd = args[0].strip()
arg = args[1].strip()
Augie Fackler
drawdag: port to python 3
r34204 if cmd in (b'replace', b'rebase', b'amend'):
nodes = [getctx(m) for m in arg.split(b'->')]
Jun Wu
drawdag: support obsmarker creation in comments...
r33154 for i in range(len(nodes) - 1):
rels.append((nodes[i], (nodes[i + 1],)))
Augie Fackler
drawdag: port to python 3
r34204 elif cmd in (b'split',):
pre, succs = arg.split(b'->')
succs = succs.split(b',')
Jun Wu
drawdag: support obsmarker creation in comments...
r33154 rels.append((getctx(pre), (getctx(s) for s in succs)))
Augie Fackler
drawdag: port to python 3
r34204 elif cmd in (b'prune',):
for n in arg.split(b','):
Jun Wu
drawdag: support obsmarker creation in comments...
r33154 rels.append((getctx(n), ()))
if rels:
obsolete.createmarkers(repo, rels, date=(0, 0), operation=cmd)