Show More
@@ -4,15 +4,22 b'' | |||||
4 | # |
|
4 | # | |
5 | # This software may be used and distributed according to the terms of |
|
5 | # This software may be used and distributed according to the terms of | |
6 | # the GNU General Public License, incorporated herein by reference. |
|
6 | # the GNU General Public License, incorporated herein by reference. | |
7 |
'''show revision graphs in terminal windows |
|
7 | '''show revision graphs in terminal windows | |
|
8 | ||||
|
9 | This extension adds a --graph option to the incoming, outgoing and log | |||
|
10 | commands. When this options is given, an ascii representation of the | |||
|
11 | revision graph is also shown. | |||
|
12 | ''' | |||
8 |
|
13 | |||
9 | import os |
|
14 | import os | |
10 | import sys |
|
15 | import sys | |
11 | from mercurial.cmdutil import revrange, show_changeset |
|
16 | from mercurial.cmdutil import revrange, show_changeset | |
12 | from mercurial.commands import templateopts |
|
17 | from mercurial.commands import templateopts, logopts, remoteopts | |
13 | from mercurial.i18n import _ |
|
18 | from mercurial.i18n import _ | |
14 | from mercurial.node import nullrev |
|
19 | from mercurial.node import nullrev | |
15 | from mercurial.util import Abort, canonpath |
|
20 | from mercurial.util import Abort, canonpath | |
|
21 | from mercurial import bundlerepo, changegroup, cmdutil, commands, extensions | |||
|
22 | from mercurial import hg, ui, url | |||
16 |
|
23 | |||
17 | def revisions(repo, start, stop): |
|
24 | def revisions(repo, start, stop): | |
18 | """cset DAG generator yielding (rev, node, [parents]) tuples |
|
25 | """cset DAG generator yielding (rev, node, [parents]) tuples | |
@@ -257,6 +264,14 b' def get_revs(repo, rev_opt):' | |||||
257 | else: |
|
264 | else: | |
258 | return (len(repo) - 1, 0) |
|
265 | return (len(repo) - 1, 0) | |
259 |
|
266 | |||
|
267 | def check_unsupported_flags(opts): | |||
|
268 | for op in ["follow", "follow_first", "date", "copies", "keyword", "remove", | |||
|
269 | "only_merges", "user", "only_branch", "prune", "newest_first", | |||
|
270 | "no_merges", "include", "exclude"]: | |||
|
271 | if op in opts and opts[op]: | |||
|
272 | raise Abort(_("--graph option is incompatible with --%s") % op) | |||
|
273 | ||||
|
274 | ||||
260 | def graphlog(ui, repo, path=None, **opts): |
|
275 | def graphlog(ui, repo, path=None, **opts): | |
261 | """show revision history alongside an ASCII revision graph |
|
276 | """show revision history alongside an ASCII revision graph | |
262 |
|
277 | |||
@@ -267,6 +282,7 b' def graphlog(ui, repo, path=None, **opts' | |||||
267 | directory. |
|
282 | directory. | |
268 | """ |
|
283 | """ | |
269 |
|
284 | |||
|
285 | check_unsupported_flags(opts) | |||
270 | limit = get_limit(opts["limit"]) |
|
286 | limit = get_limit(opts["limit"]) | |
271 | start, stop = get_revs(repo, opts["rev"]) |
|
287 | start, stop = get_revs(repo, opts["rev"]) | |
272 | stop = max(stop, start - limit + 1) |
|
288 | stop = max(stop, start - limit + 1) | |
@@ -293,6 +309,165 b' def graphlog(ui, repo, path=None, **opts' | |||||
293 |
|
309 | |||
294 | ascii(ui, grapher(graphabledag())) |
|
310 | ascii(ui, grapher(graphabledag())) | |
295 |
|
311 | |||
|
312 | def outgoing_revs(ui, repo, dest, opts): | |||
|
313 | """cset DAG generator yielding (node, [parents]) tuples | |||
|
314 | ||||
|
315 | This generator function walks through the revisions not found | |||
|
316 | in the destination | |||
|
317 | """ | |||
|
318 | limit = cmdutil.loglimit(opts) | |||
|
319 | dest, revs, checkout = hg.parseurl( | |||
|
320 | ui.expandpath(dest or 'default-push', dest or 'default'), | |||
|
321 | opts.get('rev')) | |||
|
322 | cmdutil.setremoteconfig(ui, opts) | |||
|
323 | if revs: | |||
|
324 | revs = [repo.lookup(rev) for rev in revs] | |||
|
325 | other = hg.repository(ui, dest) | |||
|
326 | ui.status(_('comparing with %s\n') % url.hidepassword(dest)) | |||
|
327 | o = repo.findoutgoing(other, force=opts.get('force')) | |||
|
328 | if not o: | |||
|
329 | ui.status(_("no changes found\n")) | |||
|
330 | return | |||
|
331 | o = repo.changelog.nodesbetween(o, revs)[0] | |||
|
332 | o.reverse() | |||
|
333 | revdict = {} | |||
|
334 | for n in o: | |||
|
335 | revdict[repo.changectx(n).rev()]=True | |||
|
336 | count = 0 | |||
|
337 | for n in o: | |||
|
338 | if count >= limit: | |||
|
339 | break | |||
|
340 | ctx = repo.changectx(n) | |||
|
341 | parents = [p.rev() for p in ctx.parents() if p.rev() in revdict] | |||
|
342 | parents.sort() | |||
|
343 | yield (ctx, parents) | |||
|
344 | count += 1 | |||
|
345 | ||||
|
346 | def goutgoing(ui, repo, dest=None, **opts): | |||
|
347 | """show the outgoing changesets alongside an ASCII revision graph | |||
|
348 | ||||
|
349 | Print the outgoing changesets alongside a revision graph drawn with | |||
|
350 | ASCII characters. | |||
|
351 | ||||
|
352 | Nodes printed as an @ character are parents of the working | |||
|
353 | directory. | |||
|
354 | """ | |||
|
355 | check_unsupported_flags(opts) | |||
|
356 | revdag = outgoing_revs(ui, repo, dest, opts) | |||
|
357 | repo_parents = repo.dirstate.parents() | |||
|
358 | displayer = show_changeset(ui, repo, opts, buffered=True) | |||
|
359 | def graphabledag(): | |||
|
360 | for (ctx, parents) in revdag: | |||
|
361 | # log_strings is the list of all log strings to draw alongside | |||
|
362 | # the graph. | |||
|
363 | displayer.show(ctx) | |||
|
364 | lines = displayer.hunk.pop(ctx.rev()).split("\n")[:-1] | |||
|
365 | char = ctx.node() in repo_parents and '@' or 'o' | |||
|
366 | yield (ctx.rev(), parents, char, lines) | |||
|
367 | ||||
|
368 | ascii(ui, grapher(graphabledag())) | |||
|
369 | ||||
|
370 | def incoming_revs(other, chlist, opts): | |||
|
371 | """cset DAG generator yielding (node, [parents]) tuples | |||
|
372 | ||||
|
373 | This generator function walks through the revisions of the destination | |||
|
374 | not found in repo | |||
|
375 | """ | |||
|
376 | limit = cmdutil.loglimit(opts) | |||
|
377 | chlist.reverse() | |||
|
378 | revdict = {} | |||
|
379 | for n in chlist: | |||
|
380 | revdict[other.changectx(n).rev()]=True | |||
|
381 | count = 0 | |||
|
382 | for n in chlist: | |||
|
383 | if count >= limit: | |||
|
384 | break | |||
|
385 | ctx = other.changectx(n) | |||
|
386 | parents = [p.rev() for p in ctx.parents() if p.rev() in revdict] | |||
|
387 | parents.sort() | |||
|
388 | yield (ctx, parents) | |||
|
389 | count += 1 | |||
|
390 | ||||
|
391 | def gincoming(ui, repo, source="default", **opts): | |||
|
392 | """show the incoming changesets alongside an ASCII revision graph | |||
|
393 | ||||
|
394 | Print the incoming changesets alongside a revision graph drawn with | |||
|
395 | ASCII characters. | |||
|
396 | ||||
|
397 | Nodes printed as an @ character are parents of the working | |||
|
398 | directory. | |||
|
399 | """ | |||
|
400 | ||||
|
401 | check_unsupported_flags(opts) | |||
|
402 | source, revs, checkout = hg.parseurl(ui.expandpath(source), opts.get('rev')) | |||
|
403 | cmdutil.setremoteconfig(ui, opts) | |||
|
404 | ||||
|
405 | other = hg.repository(ui, source) | |||
|
406 | ui.status(_('comparing with %s\n') % url.hidepassword(source)) | |||
|
407 | if revs: | |||
|
408 | revs = [other.lookup(rev) for rev in revs] | |||
|
409 | incoming = repo.findincoming(other, heads=revs, force=opts["force"]) | |||
|
410 | if not incoming: | |||
|
411 | try: | |||
|
412 | os.unlink(opts["bundle"]) | |||
|
413 | except: | |||
|
414 | pass | |||
|
415 | ui.status(_("no changes found\n")) | |||
|
416 | return | |||
|
417 | ||||
|
418 | cleanup = None | |||
|
419 | try: | |||
|
420 | fname = opts["bundle"] | |||
|
421 | if fname or not other.local(): | |||
|
422 | # create a bundle (uncompressed if other repo is not local) | |||
|
423 | if revs is None: | |||
|
424 | cg = other.changegroup(incoming, "incoming") | |||
|
425 | else: | |||
|
426 | cg = other.changegroupsubset(incoming, revs, 'incoming') | |||
|
427 | bundletype = other.local() and "HG10BZ" or "HG10UN" | |||
|
428 | fname = cleanup = changegroup.writebundle(cg, fname, bundletype) | |||
|
429 | # keep written bundle? | |||
|
430 | if opts["bundle"]: | |||
|
431 | cleanup = None | |||
|
432 | if not other.local(): | |||
|
433 | # use the created uncompressed bundlerepo | |||
|
434 | other = bundlerepo.bundlerepository(ui, repo.root, fname) | |||
|
435 | ||||
|
436 | chlist = other.changelog.nodesbetween(incoming, revs)[0] | |||
|
437 | revdag = incoming_revs(other, chlist, opts) | |||
|
438 | other_parents = other.dirstate.parents() | |||
|
439 | displayer = show_changeset(ui, other, opts, buffered=True) | |||
|
440 | def graphabledag(): | |||
|
441 | for (ctx, parents) in revdag: | |||
|
442 | # log_strings is the list of all log strings to draw alongside | |||
|
443 | # the graph. | |||
|
444 | displayer.show(ctx) | |||
|
445 | lines = displayer.hunk.pop(ctx.rev()).split("\n")[:-1] | |||
|
446 | char = ctx.node() in other_parents and '@' or 'o' | |||
|
447 | yield (ctx.rev(), parents, char, lines) | |||
|
448 | ||||
|
449 | ascii(ui, grapher(graphabledag())) | |||
|
450 | finally: | |||
|
451 | if hasattr(other, 'close'): | |||
|
452 | other.close() | |||
|
453 | if cleanup: | |||
|
454 | os.unlink(cleanup) | |||
|
455 | ||||
|
456 | def uisetup(ui): | |||
|
457 | '''Initialize the extension.''' | |||
|
458 | _wrapcmd(ui, 'log', commands.table, graphlog) | |||
|
459 | _wrapcmd(ui, 'incoming', commands.table, gincoming) | |||
|
460 | _wrapcmd(ui, 'outgoing', commands.table, goutgoing) | |||
|
461 | ||||
|
462 | def _wrapcmd(ui, cmd, table, wrapfn): | |||
|
463 | '''wrap the command''' | |||
|
464 | def graph(orig, *args, **kwargs): | |||
|
465 | if kwargs['graph']: | |||
|
466 | return wrapfn(*args, **kwargs) | |||
|
467 | return orig(*args, **kwargs) | |||
|
468 | entry = extensions.wrapcommand(table, cmd, graph) | |||
|
469 | entry[1].append(('g', 'graph', None, _("show the revision DAG"))) | |||
|
470 | ||||
296 | cmdtable = { |
|
471 | cmdtable = { | |
297 | "glog": |
|
472 | "glog": | |
298 | (graphlog, |
|
473 | (graphlog, |
General Comments 0
You need to be logged in to leave comments.
Login now