##// END OF EJS Templates
drawdag: port to python 3
Augie Fackler -
r34204:5a1b4126 default
parent child Browse files
Show More
@@ -1,433 +1,434 b''
1 # drawdag.py - convert ASCII revision DAG to actual changesets
1 # drawdag.py - convert ASCII revision DAG to actual changesets
2 #
2 #
3 # Copyright 2016 Facebook, Inc.
3 # Copyright 2016 Facebook, Inc.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 """
7 """
8 create changesets from an ASCII graph for testing purpose.
8 create changesets from an ASCII graph for testing purpose.
9
9
10 For example, given the following input::
10 For example, given the following input::
11
11
12 c d
12 c d
13 |/
13 |/
14 b
14 b
15 |
15 |
16 a
16 a
17
17
18 4 changesets and 4 local tags will be created.
18 4 changesets and 4 local tags will be created.
19 `hg log -G -T "{rev} {desc} (tag: {tags})"` will output::
19 `hg log -G -T "{rev} {desc} (tag: {tags})"` will output::
20
20
21 o 3 d (tag: d tip)
21 o 3 d (tag: d tip)
22 |
22 |
23 | o 2 c (tag: c)
23 | o 2 c (tag: c)
24 |/
24 |/
25 o 1 b (tag: b)
25 o 1 b (tag: b)
26 |
26 |
27 o 0 a (tag: a)
27 o 0 a (tag: a)
28
28
29 For root nodes (nodes without parents) in the graph, they can be revsets
29 For root nodes (nodes without parents) in the graph, they can be revsets
30 pointing to existing nodes. The ASCII graph could also have disconnected
30 pointing to existing nodes. The ASCII graph could also have disconnected
31 components with same names referring to the same changeset.
31 components with same names referring to the same changeset.
32
32
33 Therefore, given the repo having the 4 changesets (and tags) above, with the
33 Therefore, given the repo having the 4 changesets (and tags) above, with the
34 following ASCII graph as input::
34 following ASCII graph as input::
35
35
36 foo bar bar foo
36 foo bar bar foo
37 | / | |
37 | / | |
38 ancestor(c,d) a baz
38 ancestor(c,d) a baz
39
39
40 The result (`hg log -G -T "{desc}"`) will look like::
40 The result (`hg log -G -T "{desc}"`) will look like::
41
41
42 o foo
42 o foo
43 |\
43 |\
44 +---o bar
44 +---o bar
45 | | |
45 | | |
46 | o | baz
46 | o | baz
47 | /
47 | /
48 +---o d
48 +---o d
49 | |
49 | |
50 +---o c
50 +---o c
51 | |
51 | |
52 o | b
52 o | b
53 |/
53 |/
54 o a
54 o a
55
55
56 Note that if you take the above `hg log` output directly as input. It will work
56 Note that if you take the above `hg log` output directly as input. It will work
57 as expected - the result would be an isomorphic graph::
57 as expected - the result would be an isomorphic graph::
58
58
59 o foo
59 o foo
60 |\
60 |\
61 | | o d
61 | | o d
62 | |/
62 | |/
63 | | o c
63 | | o c
64 | |/
64 | |/
65 | | o bar
65 | | o bar
66 | |/|
66 | |/|
67 | o | b
67 | o | b
68 | |/
68 | |/
69 o / baz
69 o / baz
70 /
70 /
71 o a
71 o a
72
72
73 This is because 'o' is specially handled in the input: instead of using 'o' as
73 This is because 'o' is specially handled in the input: instead of using 'o' as
74 the node name, the word to the right will be used.
74 the node name, the word to the right will be used.
75
75
76 Some special comments could have side effects:
76 Some special comments could have side effects:
77
77
78 - Create obsmarkers
78 - Create obsmarkers
79 # replace: A -> B -> C -> D # chained 1 to 1 replacements
79 # replace: A -> B -> C -> D # chained 1 to 1 replacements
80 # split: A -> B, C # 1 to many
80 # split: A -> B, C # 1 to many
81 # prune: A, B, C # many to nothing
81 # prune: A, B, C # many to nothing
82 """
82 """
83 from __future__ import absolute_import, print_function
83 from __future__ import absolute_import, print_function
84
84
85 import collections
85 import collections
86 import itertools
86 import itertools
87 import re
87 import re
88
88
89 from mercurial.i18n import _
89 from mercurial.i18n import _
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
122 ... H B E # prune: F, I
123 ... H B E # prune: F, I
123 ... \|/
124 ... \|/
124 ... A
125 ... A
125 ... ''').items()})
126 ... ''').items()})
126 {'A': [],
127 {'A': [],
127 'B': ['A'],
128 'B': ['A'],
128 'C': ['B'],
129 'C': ['B'],
129 'D': ['B'],
130 'D': ['B'],
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 ... | | |
141 ... | o | baz
142 ... | o | baz
142 ... | /
143 ... | /
143 ... +---o d
144 ... +---o d
144 ... | |
145 ... | |
145 ... +---o c
146 ... +---o c
146 ... | |
147 ... | |
147 ... o | b
148 ... o | b
148 ... |/
149 ... |/
149 ... o a
150 ... o a
150 ... ''').items()})
151 ... ''').items()})
151 {'a': [],
152 {'a': [],
152 'b': ['a'],
153 'b': ['a'],
153 'bar': ['b', 'a'],
154 'bar': ['b', 'a'],
154 'baz': [],
155 'baz': [],
155 'c': ['b'],
156 'c': ['b'],
156 'd': ['b'],
157 'd': ['b'],
157 'foo': ['baz', 'b']}
158 'foo': ['baz', 'b']}
158 """
159 """
159 lines = text.splitlines()
160 lines = text.splitlines()
160 edges = collections.defaultdict(list) # {node: []}
161 edges = collections.defaultdict(list) # {node: []}
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"""
202 visited = {(y, x)}
203 visited = {(y, x)}
203 visit = []
204 visit = []
204 result = []
205 result = []
205
206
206 def follow(y, x, expected):
207 def follow(y, x, expected):
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)
262
263
263 class simplefilectx(object):
264 class simplefilectx(object):
264 def __init__(self, path, data):
265 def __init__(self, path, data):
265 self._data = data
266 self._data = data
266 self._path = path
267 self._path = path
267
268
268 def data(self):
269 def data(self):
269 return self._data
270 return self._data
270
271
271 def filenode(self):
272 def filenode(self):
272 return None
273 return None
273
274
274 def path(self):
275 def path(self):
275 return self._path
276 return self._path
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
294 while len(self._parents) < 2:
295 while len(self._parents) < 2:
295 self._parents.append(repo[node.nullid])
296 self._parents.append(repo[node.nullid])
296
297
297 def filectx(self, key):
298 def filectx(self, key):
298 return simplefilectx(key, self._added[key])
299 return simplefilectx(key, self._added[key])
299
300
300 def commit(self):
301 def commit(self):
301 return self._repo.commitctx(self)
302 return self._repo.commitctx(self)
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:
313 leafs = [k for k, v in remaining.items() if not v]
314 leafs = [k for k, v in remaining.items() if not v]
314 if not leafs:
315 if not leafs:
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
332 ... \|/
333 ... \|/
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
348 tags with those names to make the dummy changesets easier to be referred
349 tags with those names to make the dummy changesets easier to be referred
349 to.
350 to.
350
351
351 If the name of a node is a single character 'o', It will be replaced by the
352 If the name of a node is a single character 'o', It will be replaced by the
352 word to the right. This makes it easier to reuse
353 word to the right. This makes it easier to reuse
353 :hg:`log -G -T '{desc}'` outputs.
354 :hg:`log -G -T '{desc}'` outputs.
354
355
355 For root (no parents) nodes, revset can be used to query existing repo.
356 For root (no parents) nodes, revset can be used to query existing repo.
356 Note that the revset cannot have confusing characters which can be seen as
357 Note that the revset cannot have confusing characters which can be seen as
357 the part of the graph edges, like `|/+-\`.
358 the part of the graph edges, like `|/+-\`.
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
384
385
385 # commit in topological order
386 # commit in topological order
386 for name, parents in _walkgraph(edges):
387 for name, parents in _walkgraph(edges):
387 if name in committed:
388 if name in committed:
388 continue
389 continue
389 pctxs = [repo[committed[n]] for n in parents]
390 pctxs = [repo[committed[n]] for n in parents]
390 pctxs.sort(key=lambda c: c.node())
391 pctxs.sort(key=lambda c: c.node())
391 added = {}
392 added = {}
392 if len(parents) > 1:
393 if len(parents) > 1:
393 # If it's a merge, take the files and contents from the parents
394 # If it's a merge, take the files and contents from the parents
394 for f in pctxs[1].manifest():
395 for f in pctxs[1].manifest():
395 if f not in pctxs[0].manifest():
396 if f not in pctxs[0].manifest():
396 added[f] = pctxs[1][f].data()
397 added[f] = pctxs[1][f].data()
397 else:
398 else:
398 # If it's not a merge, add a single file
399 # If it's not a merge, add a single file
399 added[name] = name
400 added[name] = name
400 # add extra file contents in comments
401 # add extra file contents in comments
401 for path, content in files.get(name, {}).items():
402 for path, content in files.get(name, {}).items():
402 added[path] = content
403 added[path] = content
403 ctx = simplecommitctx(repo, name, pctxs, added)
404 ctx = simplecommitctx(repo, name, pctxs, added)
404 n = ctx.commit()
405 n = ctx.commit()
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)
General Comments 0
You need to be logged in to leave comments. Login now