##// END OF EJS Templates
drawdag: abide by new createmarkers() API...
Martin von Zweigbergk -
r44864:dda2341d default
parent child Browse files
Show More
@@ -1,450 +1,450
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 pycompat,
96 registrar,
96 registrar,
97 scmutil,
97 scmutil,
98 tags as tagsmod,
98 tags as tagsmod,
99 )
99 )
100
100
101 cmdtable = {}
101 cmdtable = {}
102 command = registrar.command(cmdtable)
102 command = registrar.command(cmdtable)
103
103
104 _pipechars = b'\\/+-|'
104 _pipechars = b'\\/+-|'
105 _nonpipechars = b''.join(
105 _nonpipechars = b''.join(
106 pycompat.bytechr(i)
106 pycompat.bytechr(i)
107 for i in range(33, 127)
107 for i in range(33, 127)
108 if pycompat.bytechr(i) not in _pipechars
108 if pycompat.bytechr(i) not in _pipechars
109 )
109 )
110
110
111
111
112 def _isname(ch):
112 def _isname(ch):
113 """char -> bool. return True if ch looks like part of a name, False
113 """char -> bool. return True if ch looks like part of a name, False
114 otherwise"""
114 otherwise"""
115 return ch in _nonpipechars
115 return ch in _nonpipechars
116
116
117
117
118 def _parseasciigraph(text):
118 def _parseasciigraph(text):
119 r"""str -> {str : [str]}. convert the ASCII graph to edges
119 r"""str -> {str : [str]}. convert the ASCII graph to edges
120
120
121 >>> import pprint
121 >>> import pprint
122 >>> pprint.pprint({pycompat.sysstr(k): [pycompat.sysstr(vv) for vv in v]
122 >>> pprint.pprint({pycompat.sysstr(k): [pycompat.sysstr(vv) for vv in v]
123 ... for k, v in _parseasciigraph(br'''
123 ... for k, v in _parseasciigraph(br'''
124 ... G
124 ... G
125 ... |
125 ... |
126 ... I D C F # split: B -> E, F, G
126 ... I D C F # split: B -> E, F, G
127 ... \ \| | # replace: C -> D -> H
127 ... \ \| | # replace: C -> D -> H
128 ... H B E # prune: F, I
128 ... H B E # prune: F, I
129 ... \|/
129 ... \|/
130 ... A
130 ... A
131 ... ''').items()})
131 ... ''').items()})
132 {'A': [],
132 {'A': [],
133 'B': ['A'],
133 'B': ['A'],
134 'C': ['B'],
134 'C': ['B'],
135 'D': ['B'],
135 'D': ['B'],
136 'E': ['A'],
136 'E': ['A'],
137 'F': ['E'],
137 'F': ['E'],
138 'G': ['F'],
138 'G': ['F'],
139 'H': ['A'],
139 'H': ['A'],
140 'I': ['H']}
140 'I': ['H']}
141 >>> pprint.pprint({pycompat.sysstr(k): [pycompat.sysstr(vv) for vv in v]
141 >>> pprint.pprint({pycompat.sysstr(k): [pycompat.sysstr(vv) for vv in v]
142 ... for k, v in _parseasciigraph(br'''
142 ... for k, v in _parseasciigraph(br'''
143 ... o foo
143 ... o foo
144 ... |\
144 ... |\
145 ... +---o bar
145 ... +---o bar
146 ... | | |
146 ... | | |
147 ... | o | baz
147 ... | o | baz
148 ... | /
148 ... | /
149 ... +---o d
149 ... +---o d
150 ... | |
150 ... | |
151 ... +---o c
151 ... +---o c
152 ... | |
152 ... | |
153 ... o | b
153 ... o | b
154 ... |/
154 ... |/
155 ... o a
155 ... o a
156 ... ''').items()})
156 ... ''').items()})
157 {'a': [],
157 {'a': [],
158 'b': ['a'],
158 'b': ['a'],
159 'bar': ['b', 'a'],
159 'bar': ['b', 'a'],
160 'baz': [],
160 'baz': [],
161 'c': ['b'],
161 'c': ['b'],
162 'd': ['b'],
162 'd': ['b'],
163 'foo': ['baz', 'b']}
163 'foo': ['baz', 'b']}
164 """
164 """
165 lines = text.splitlines()
165 lines = text.splitlines()
166 edges = collections.defaultdict(list) # {node: []}
166 edges = collections.defaultdict(list) # {node: []}
167
167
168 def get(y, x):
168 def get(y, x):
169 """(int, int) -> char. give a coordinate, return the char. return a
169 """(int, int) -> char. give a coordinate, return the char. return a
170 space for anything out of range"""
170 space for anything out of range"""
171 if x < 0 or y < 0:
171 if x < 0 or y < 0:
172 return b' '
172 return b' '
173 try:
173 try:
174 return lines[y][x : x + 1] or b' '
174 return lines[y][x : x + 1] or b' '
175 except IndexError:
175 except IndexError:
176 return b' '
176 return b' '
177
177
178 def getname(y, x):
178 def getname(y, x):
179 """(int, int) -> str. like get(y, x) but concatenate left and right
179 """(int, int) -> str. like get(y, x) but concatenate left and right
180 parts. if name is an 'o', try to replace it to the right"""
180 parts. if name is an 'o', try to replace it to the right"""
181 result = b''
181 result = b''
182 for i in itertools.count(0):
182 for i in itertools.count(0):
183 ch = get(y, x - i)
183 ch = get(y, x - i)
184 if not _isname(ch):
184 if not _isname(ch):
185 break
185 break
186 result = ch + result
186 result = ch + result
187 for i in itertools.count(1):
187 for i in itertools.count(1):
188 ch = get(y, x + i)
188 ch = get(y, x + i)
189 if not _isname(ch):
189 if not _isname(ch):
190 break
190 break
191 result += ch
191 result += ch
192 if result == b'o':
192 if result == b'o':
193 # special handling, find the name to the right
193 # special handling, find the name to the right
194 result = b''
194 result = b''
195 for i in itertools.count(2):
195 for i in itertools.count(2):
196 ch = get(y, x + i)
196 ch = get(y, x + i)
197 if ch == b' ' or ch in _pipechars:
197 if ch == b' ' or ch in _pipechars:
198 if result or x + i >= len(lines[y]):
198 if result or x + i >= len(lines[y]):
199 break
199 break
200 else:
200 else:
201 result += ch
201 result += ch
202 return result or b'o'
202 return result or b'o'
203 return result
203 return result
204
204
205 def parents(y, x):
205 def parents(y, x):
206 """(int, int) -> [str]. follow the ASCII edges at given position,
206 """(int, int) -> [str]. follow the ASCII edges at given position,
207 return a list of parents"""
207 return a list of parents"""
208 visited = {(y, x)}
208 visited = {(y, x)}
209 visit = []
209 visit = []
210 result = []
210 result = []
211
211
212 def follow(y, x, expected):
212 def follow(y, x, expected):
213 """conditionally append (y, x) to visit array, if it's a char
213 """conditionally append (y, x) to visit array, if it's a char
214 in excepted. 'o' in expected means an '_isname' test.
214 in excepted. 'o' in expected means an '_isname' test.
215 if '-' (or '+') is not in excepted, and get(y, x) is '-' (or '+'),
215 if '-' (or '+') is not in excepted, and get(y, x) is '-' (or '+'),
216 the next line (y + 1, x) will be checked instead."""
216 the next line (y + 1, x) will be checked instead."""
217 ch = get(y, x)
217 ch = get(y, x)
218 if any(ch == c and c not in expected for c in (b'-', b'+')):
218 if any(ch == c and c not in expected for c in (b'-', b'+')):
219 y += 1
219 y += 1
220 return follow(y + 1, x, expected)
220 return follow(y + 1, x, expected)
221 if ch in expected or (b'o' in expected and _isname(ch)):
221 if ch in expected or (b'o' in expected and _isname(ch)):
222 visit.append((y, x))
222 visit.append((y, x))
223
223
224 # -o- # starting point:
224 # -o- # starting point:
225 # /|\ # follow '-' (horizontally), and '/|\' (to the bottom)
225 # /|\ # follow '-' (horizontally), and '/|\' (to the bottom)
226 follow(y + 1, x, b'|')
226 follow(y + 1, x, b'|')
227 follow(y + 1, x - 1, b'/')
227 follow(y + 1, x - 1, b'/')
228 follow(y + 1, x + 1, b'\\')
228 follow(y + 1, x + 1, b'\\')
229 follow(y, x - 1, b'-')
229 follow(y, x - 1, b'-')
230 follow(y, x + 1, b'-')
230 follow(y, x + 1, b'-')
231
231
232 while visit:
232 while visit:
233 y, x = visit.pop()
233 y, x = visit.pop()
234 if (y, x) in visited:
234 if (y, x) in visited:
235 continue
235 continue
236 visited.add((y, x))
236 visited.add((y, x))
237 ch = get(y, x)
237 ch = get(y, x)
238 if _isname(ch):
238 if _isname(ch):
239 result.append(getname(y, x))
239 result.append(getname(y, x))
240 continue
240 continue
241 elif ch == b'|':
241 elif ch == b'|':
242 follow(y + 1, x, b'/|o')
242 follow(y + 1, x, b'/|o')
243 follow(y + 1, x - 1, b'/')
243 follow(y + 1, x - 1, b'/')
244 follow(y + 1, x + 1, b'\\')
244 follow(y + 1, x + 1, b'\\')
245 elif ch == b'+':
245 elif ch == b'+':
246 follow(y, x - 1, b'-')
246 follow(y, x - 1, b'-')
247 follow(y, x + 1, b'-')
247 follow(y, x + 1, b'-')
248 follow(y + 1, x - 1, b'/')
248 follow(y + 1, x - 1, b'/')
249 follow(y + 1, x + 1, b'\\')
249 follow(y + 1, x + 1, b'\\')
250 follow(y + 1, x, b'|')
250 follow(y + 1, x, b'|')
251 elif ch == b'\\':
251 elif ch == b'\\':
252 follow(y + 1, x + 1, b'\\|o')
252 follow(y + 1, x + 1, b'\\|o')
253 elif ch == b'/':
253 elif ch == b'/':
254 follow(y + 1, x - 1, b'/|o')
254 follow(y + 1, x - 1, b'/|o')
255 elif ch == b'-':
255 elif ch == b'-':
256 follow(y, x - 1, b'-+o')
256 follow(y, x - 1, b'-+o')
257 follow(y, x + 1, b'-+o')
257 follow(y, x + 1, b'-+o')
258 return result
258 return result
259
259
260 for y, line in enumerate(lines):
260 for y, line in enumerate(lines):
261 for x, ch in enumerate(pycompat.bytestr(line)):
261 for x, ch in enumerate(pycompat.bytestr(line)):
262 if ch == b'#': # comment
262 if ch == b'#': # comment
263 break
263 break
264 if _isname(ch):
264 if _isname(ch):
265 edges[getname(y, x)] += parents(y, x)
265 edges[getname(y, x)] += parents(y, x)
266
266
267 return dict(edges)
267 return dict(edges)
268
268
269
269
270 class simplefilectx(object):
270 class simplefilectx(object):
271 def __init__(self, path, data):
271 def __init__(self, path, data):
272 self._data = data
272 self._data = data
273 self._path = path
273 self._path = path
274
274
275 def data(self):
275 def data(self):
276 return self._data
276 return self._data
277
277
278 def filenode(self):
278 def filenode(self):
279 return None
279 return None
280
280
281 def path(self):
281 def path(self):
282 return self._path
282 return self._path
283
283
284 def copysource(self):
284 def copysource(self):
285 return None
285 return None
286
286
287 def flags(self):
287 def flags(self):
288 return b''
288 return b''
289
289
290
290
291 class simplecommitctx(context.committablectx):
291 class simplecommitctx(context.committablectx):
292 def __init__(self, repo, name, parentctxs, added):
292 def __init__(self, repo, name, parentctxs, added):
293 opts = {
293 opts = {
294 'changes': scmutil.status([], list(added), [], [], [], [], []),
294 'changes': scmutil.status([], list(added), [], [], [], [], []),
295 'date': b'0 0',
295 'date': b'0 0',
296 'extra': {b'branch': b'default'},
296 'extra': {b'branch': b'default'},
297 }
297 }
298 super(simplecommitctx, self).__init__(repo, name, **opts)
298 super(simplecommitctx, self).__init__(repo, name, **opts)
299 self._added = added
299 self._added = added
300 self._parents = parentctxs
300 self._parents = parentctxs
301 while len(self._parents) < 2:
301 while len(self._parents) < 2:
302 self._parents.append(repo[node.nullid])
302 self._parents.append(repo[node.nullid])
303
303
304 def filectx(self, key):
304 def filectx(self, key):
305 return simplefilectx(key, self._added[key])
305 return simplefilectx(key, self._added[key])
306
306
307 def commit(self):
307 def commit(self):
308 return self._repo.commitctx(self)
308 return self._repo.commitctx(self)
309
309
310 def p1copies(self):
310 def p1copies(self):
311 return {}
311 return {}
312
312
313 def p2copies(self):
313 def p2copies(self):
314 return {}
314 return {}
315
315
316
316
317 def _walkgraph(edges):
317 def _walkgraph(edges):
318 """yield node, parents in topologically order"""
318 """yield node, parents in topologically order"""
319 visible = set(edges.keys())
319 visible = set(edges.keys())
320 remaining = {} # {str: [str]}
320 remaining = {} # {str: [str]}
321 for k, vs in edges.items():
321 for k, vs in edges.items():
322 for v in vs:
322 for v in vs:
323 if v not in remaining:
323 if v not in remaining:
324 remaining[v] = []
324 remaining[v] = []
325 remaining[k] = vs[:]
325 remaining[k] = vs[:]
326 while remaining:
326 while remaining:
327 leafs = [k for k, v in remaining.items() if not v]
327 leafs = [k for k, v in remaining.items() if not v]
328 if not leafs:
328 if not leafs:
329 raise error.Abort(_('the graph has cycles'))
329 raise error.Abort(_('the graph has cycles'))
330 for leaf in sorted(leafs):
330 for leaf in sorted(leafs):
331 if leaf in visible:
331 if leaf in visible:
332 yield leaf, edges[leaf]
332 yield leaf, edges[leaf]
333 del remaining[leaf]
333 del remaining[leaf]
334 for k, v in remaining.items():
334 for k, v in remaining.items():
335 if leaf in v:
335 if leaf in v:
336 v.remove(leaf)
336 v.remove(leaf)
337
337
338
338
339 def _getcomments(text):
339 def _getcomments(text):
340 r"""
340 r"""
341 >>> [pycompat.sysstr(s) for s in _getcomments(br'''
341 >>> [pycompat.sysstr(s) for s in _getcomments(br'''
342 ... G
342 ... G
343 ... |
343 ... |
344 ... I D C F # split: B -> E, F, G
344 ... I D C F # split: B -> E, F, G
345 ... \ \| | # replace: C -> D -> H
345 ... \ \| | # replace: C -> D -> H
346 ... H B E # prune: F, I
346 ... H B E # prune: F, I
347 ... \|/
347 ... \|/
348 ... A
348 ... A
349 ... ''')]
349 ... ''')]
350 ['split: B -> E, F, G', 'replace: C -> D -> H', 'prune: F, I']
350 ['split: B -> E, F, G', 'replace: C -> D -> H', 'prune: F, I']
351 """
351 """
352 for line in text.splitlines():
352 for line in text.splitlines():
353 if b' # ' not in line:
353 if b' # ' not in line:
354 continue
354 continue
355 yield line.split(b' # ', 1)[1].split(b' # ')[0].strip()
355 yield line.split(b' # ', 1)[1].split(b' # ')[0].strip()
356
356
357
357
358 @command(b'debugdrawdag', [])
358 @command(b'debugdrawdag', [])
359 def debugdrawdag(ui, repo, **opts):
359 def debugdrawdag(ui, repo, **opts):
360 r"""read an ASCII graph from stdin and create changesets
360 r"""read an ASCII graph from stdin and create changesets
361
361
362 The ASCII graph is like what :hg:`log -G` outputs, with each `o` replaced
362 The ASCII graph is like what :hg:`log -G` outputs, with each `o` replaced
363 to the name of the node. The command will create dummy changesets and local
363 to the name of the node. The command will create dummy changesets and local
364 tags with those names to make the dummy changesets easier to be referred
364 tags with those names to make the dummy changesets easier to be referred
365 to.
365 to.
366
366
367 If the name of a node is a single character 'o', It will be replaced by the
367 If the name of a node is a single character 'o', It will be replaced by the
368 word to the right. This makes it easier to reuse
368 word to the right. This makes it easier to reuse
369 :hg:`log -G -T '{desc}'` outputs.
369 :hg:`log -G -T '{desc}'` outputs.
370
370
371 For root (no parents) nodes, revset can be used to query existing repo.
371 For root (no parents) nodes, revset can be used to query existing repo.
372 Note that the revset cannot have confusing characters which can be seen as
372 Note that the revset cannot have confusing characters which can be seen as
373 the part of the graph edges, like `|/+-\`.
373 the part of the graph edges, like `|/+-\`.
374 """
374 """
375 text = ui.fin.read()
375 text = ui.fin.read()
376
376
377 # parse the graph and make sure len(parents) <= 2 for each node
377 # parse the graph and make sure len(parents) <= 2 for each node
378 edges = _parseasciigraph(text)
378 edges = _parseasciigraph(text)
379 for k, v in edges.items():
379 for k, v in edges.items():
380 if len(v) > 2:
380 if len(v) > 2:
381 raise error.Abort(_('%s: too many parents: %s') % (k, b' '.join(v)))
381 raise error.Abort(_('%s: too many parents: %s') % (k, b' '.join(v)))
382
382
383 # parse comments to get extra file content instructions
383 # parse comments to get extra file content instructions
384 files = collections.defaultdict(dict) # {(name, path): content}
384 files = collections.defaultdict(dict) # {(name, path): content}
385 comments = list(_getcomments(text))
385 comments = list(_getcomments(text))
386 filere = re.compile(br'^(\w+)/([\w/]+)\s*=\s*(.*)$', re.M)
386 filere = re.compile(br'^(\w+)/([\w/]+)\s*=\s*(.*)$', re.M)
387 for name, path, content in filere.findall(b'\n'.join(comments)):
387 for name, path, content in filere.findall(b'\n'.join(comments)):
388 content = content.replace(br'\n', b'\n').replace(br'\1', b'\1')
388 content = content.replace(br'\n', b'\n').replace(br'\1', b'\1')
389 files[name][path] = content
389 files[name][path] = content
390
390
391 committed = {None: node.nullid} # {name: node}
391 committed = {None: node.nullid} # {name: node}
392
392
393 # for leaf nodes, try to find existing nodes in repo
393 # for leaf nodes, try to find existing nodes in repo
394 for name, parents in edges.items():
394 for name, parents in edges.items():
395 if len(parents) == 0:
395 if len(parents) == 0:
396 try:
396 try:
397 committed[name] = scmutil.revsingle(repo, name)
397 committed[name] = scmutil.revsingle(repo, name)
398 except error.RepoLookupError:
398 except error.RepoLookupError:
399 pass
399 pass
400
400
401 # commit in topological order
401 # commit in topological order
402 for name, parents in _walkgraph(edges):
402 for name, parents in _walkgraph(edges):
403 if name in committed:
403 if name in committed:
404 continue
404 continue
405 pctxs = [repo[committed[n]] for n in parents]
405 pctxs = [repo[committed[n]] for n in parents]
406 pctxs.sort(key=lambda c: c.node())
406 pctxs.sort(key=lambda c: c.node())
407 added = {}
407 added = {}
408 if len(parents) > 1:
408 if len(parents) > 1:
409 # If it's a merge, take the files and contents from the parents
409 # If it's a merge, take the files and contents from the parents
410 for f in pctxs[1].manifest():
410 for f in pctxs[1].manifest():
411 if f not in pctxs[0].manifest():
411 if f not in pctxs[0].manifest():
412 added[f] = pctxs[1][f].data()
412 added[f] = pctxs[1][f].data()
413 else:
413 else:
414 # If it's not a merge, add a single file
414 # If it's not a merge, add a single file
415 added[name] = name
415 added[name] = name
416 # add extra file contents in comments
416 # add extra file contents in comments
417 for path, content in files.get(name, {}).items():
417 for path, content in files.get(name, {}).items():
418 added[path] = content
418 added[path] = content
419 ctx = simplecommitctx(repo, name, pctxs, added)
419 ctx = simplecommitctx(repo, name, pctxs, added)
420 n = ctx.commit()
420 n = ctx.commit()
421 committed[name] = n
421 committed[name] = n
422 tagsmod.tag(
422 tagsmod.tag(
423 repo, [name], n, message=None, user=None, date=None, local=True
423 repo, [name], n, message=None, user=None, date=None, local=True
424 )
424 )
425
425
426 # handle special comments
426 # handle special comments
427 with repo.wlock(), repo.lock(), repo.transaction(b'drawdag'):
427 with repo.wlock(), repo.lock(), repo.transaction(b'drawdag'):
428 getctx = lambda x: repo.unfiltered()[committed[x.strip()]]
428 getctx = lambda x: repo.unfiltered()[committed[x.strip()]]
429 for comment in comments:
429 for comment in comments:
430 rels = [] # obsolete relationships
430 rels = [] # obsolete relationships
431 args = comment.split(b':', 1)
431 args = comment.split(b':', 1)
432 if len(args) <= 1:
432 if len(args) <= 1:
433 continue
433 continue
434
434
435 cmd = args[0].strip()
435 cmd = args[0].strip()
436 arg = args[1].strip()
436 arg = args[1].strip()
437
437
438 if cmd in (b'replace', b'rebase', b'amend'):
438 if cmd in (b'replace', b'rebase', b'amend'):
439 nodes = [getctx(m) for m in arg.split(b'->')]
439 nodes = [getctx(m) for m in arg.split(b'->')]
440 for i in range(len(nodes) - 1):
440 for i in range(len(nodes) - 1):
441 rels.append((nodes[i], (nodes[i + 1],)))
441 rels.append(((nodes[i],), (nodes[i + 1],)))
442 elif cmd in (b'split',):
442 elif cmd in (b'split',):
443 pre, succs = arg.split(b'->')
443 pre, succs = arg.split(b'->')
444 succs = succs.split(b',')
444 succs = succs.split(b',')
445 rels.append((getctx(pre), (getctx(s) for s in succs)))
445 rels.append(((getctx(pre),), (getctx(s) for s in succs)))
446 elif cmd in (b'prune',):
446 elif cmd in (b'prune',):
447 for n in arg.split(b','):
447 for n in arg.split(b','):
448 rels.append((getctx(n), ()))
448 rels.append(((getctx(n),), ()))
449 if rels:
449 if rels:
450 obsolete.createmarkers(repo, rels, date=(0, 0), operation=cmd)
450 obsolete.createmarkers(repo, rels, date=(0, 0), operation=cmd)
General Comments 0
You need to be logged in to leave comments. Login now