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 |
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 |
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) |