예제 #1
0
def run(ui, *pats, **opts):
    fname, target = '', ''
    cwd = os.getcwd()
    root = paths.find_root(cwd)
    try:
        fname = util.canonpath(root, cwd, pats[0])
        target = util.canonpath(root, cwd, pats[1])
    except util.Abort, e:
        return gdialog.Prompt(_('Invalid path'), str(e), None)
예제 #2
0
def graphlog(ui, repo, path=None, **opts):
    """show revision history alongside an ASCII revision graph

    Print a revision history alongside a revision graph drawn with
    ASCII characters.

    Nodes printed as an @ character are parents of the working
    directory.
    """

    check_unsupported_flags(opts)
    limit = cmdutil.loglimit(opts)
    start, stop = get_revs(repo, opts["rev"])
    if start == nullrev:
        return

    if path:
        path = util.canonpath(repo.root, os.getcwd(), path)
    if path: # could be reset in canonpath
        revdag = graphmod.filerevs(repo, path, start, stop, limit)
    else:
        if limit is not None:
            stop = max(stop, start - limit + 1)
        revdag = graphmod.revisions(repo, start, stop)

    displayer = show_changeset(ui, repo, opts, buffered=True)
    showparents = [ctx.node() for ctx in repo[None].parents()]
    generate(ui, revdag, displayer, showparents, asciiedges)
예제 #3
0
def parsedefinitions(ui, repo, svnroot, exts):
    """Return (targetdir, revision, source) tuples. Fail if nested
    targetdirs are detected. source is an svn project URL.
    """
    defs = []
    for base in sorted(exts):
        for line in exts[base]:
            if not line.strip() or line.lstrip().startswith('#'):
                # Ignore comments and blank lines
                continue
            try:
                path, rev, source, pegrev, norevline = parsedefinition(line)
            except BadDefinition:
                ui.warn(_('ignoring invalid external definition: %r\n' % line))
                continue
            source = resolvesource(ui, svnroot, source)
            if source is None:
                continue
            wpath = hgutil.pconvert(os.path.join(base, path))
            wpath = hgutil.canonpath(repo.root, '', wpath)
            defs.append((wpath, rev, source, pegrev, norevline, base))
    # Check target dirs are not nested
    defs.sort()
    for i, d in enumerate(defs):
        for d2 in defs[i+1:]:
            if d2[0].startswith(d[0] + '/'):
                raise hgutil.Abort(_('external directories cannot nest:\n%s\n%s')
                                   % (d[0], d2[0]))
    return defs
예제 #4
0
def parsedefinitions(ui, repo, svnroot, exts):
    """Return (targetdir, revision, source) tuples. Fail if nested
    targetdirs are detected. source is an svn project URL.
    """
    defs = []
    for base in sorted(exts):
        for line in exts[base]:
            try:
                path, rev, source, pegrev = parsedefinition(line)
            except BadDefinition:
                ui.warn(_('ignoring invalid external definition: %r' % line))
                continue
            if re_scheme.search(source):
                pass
            elif source.startswith('^/'):
                source = svnroot + source[1:]
            else:
                ui.warn(_('ignoring unsupported non-fully qualified external: %r' % source))
                continue
            wpath = hgutil.pconvert(os.path.join(base, path))
            wpath = hgutil.canonpath(repo.root, '', wpath)
            defs.append((wpath, rev, source, pegrev))
    # Check target dirs are not nested
    defs.sort()
    for i, d in enumerate(defs):
        for d2 in defs[i+1:]:
            if d2[0].startswith(d[0] + '/'):
                raise hgutil.Abort(_('external directories cannot nest:\n%s\n%s')
                                   % (d[0], d2[0]))
    return defs
예제 #5
0
def graphlog(ui, repo, path=None, **opts):
    """show revision history alongside an ASCII revision graph

    Print a revision history alongside a revision graph drawn with
    ASCII characters.

    Nodes printed as an @ character are parents of the working
    directory.
    """

    check_unsupported_flags(opts)
    limit = cmdutil.loglimit(opts)
    start, stop = get_revs(repo, opts["rev"])
    stop = max(stop, start - limit + 1)
    if start == nullrev:
        return

    if path:
        path = util.canonpath(repo.root, os.getcwd(), path)
    if path: # could be reset in canonpath
        revdag = graphmod.filerevs(repo, path, start, stop)
    else:
        revdag = graphmod.revisions(repo, start, stop)

    fmtdag = asciiformat(ui, repo, revdag, opts)
    ascii(ui, asciiedges(fmtdag))
예제 #6
0
def parsedefinitions(ui, repo, svnroot, exts):
    """Return (targetdir, revision, source) tuples. Fail if nested
    targetdirs are detected. source is an svn project URL.
    """
    defs = []
    for base in sorted(exts):
        for line in exts[base]:
            try:
                path, rev, source, pegrev, norevline = parsedefinition(line)
            except BadDefinition:
                ui.warn(_('ignoring invalid external definition: %r\n' % line))
                continue
            source = resolvesource(ui, svnroot, source)
            if source is None:
                continue
            wpath = hgutil.pconvert(os.path.join(base, path))
            wpath = hgutil.canonpath(repo.root, '', wpath)
            defs.append((wpath, rev, source, pegrev, norevline, base))
    # Check target dirs are not nested
    defs.sort()
    for i, d in enumerate(defs):
        for d2 in defs[i + 1:]:
            if d2[0].startswith(d[0] + '/'):
                raise hgutil.Abort(
                    _('external directories cannot nest:\n%s\n%s') %
                    (d[0], d2[0]))
    return defs
예제 #7
0
def graphlog(ui, repo, path=None, **opts):
    """show revision history alongside an ASCII revision graph

    Print a revision history alongside a revision graph drawn with
    ASCII characters.

    Nodes printed as an @ character are parents of the working
    directory.
    """

    check_unsupported_flags(opts)
    limit = cmdutil.loglimit(opts)
    start, stop = get_revs(repo, opts["rev"])
    stop = max(stop, start - limit + 1)
    if start == nullrev:
        return

    if path:
        path = util.canonpath(repo.root, os.getcwd(), path)
    if path:  # could be reset in canonpath
        revdag = graphmod.filerevs(repo, path, start, stop)
    else:
        revdag = graphmod.revisions(repo, start, stop)

    fmtdag = asciiformat(ui, repo, revdag, opts)
    ascii(ui, asciiedges(fmtdag))
예제 #8
0
def run(root='', cwd='', files=[], **opts):
    u = ui.ui()
    u.updateopts(debug=False, traceback=False)
    repo = hg.repository(u, path=root)

    cmdoptions = {
        'follow':False, 'follow-first':False, 'copies':False, 'keyword':[],
        'limit':0, 'rev':[], 'removed':False, 'no_merges':False, 'date':None,
        'only_merges':None, 'prune':[], 'git':False, 'verbose':False,
        'include':[], 'exclude':[]
    }

    dialog = DataMineDialog(u, repo, cwd, files, cmdoptions, True)
    dialog.display()
    for f in files:
        if os.path.isfile(f):
            cf = util.canonpath(root, cwd, f) 
            dialog.add_annotate_page(cf, '.')
        elif os.path.isdir(f):
            Prompt('Invalid path', "Can't annotate directory: %s" % f,
                    dialog).run()
                    
    if not dialog.notebook.get_n_pages():
        dialog.add_search_page()

    gtk.gdk.threads_init()
    gtk.gdk.threads_enter()
    gtk.main()
    gtk.gdk.threads_leave()
예제 #9
0
 def haskwsource(dest):
     '''Returns true if dest is a regular file and configured for
     expansion or a symlink which points to a file configured for
     expansion. '''
     source = repo.dirstate.copied(dest)
     if 'l' in wctx.flags(source):
         source = util.canonpath(repo.root, cwd,
                                 os.path.realpath(source))
     return kwt.match(source)
예제 #10
0
 def setPath(self, path):
     spath = hglib.fromunicode(path)
     reporoot = cmdutil.findrepo(os.path.abspath(spath))
     if not reporoot:
         QMessageBox.warning(self, 'Repository Not Found',
                             'Repository not found for path: %s' % path)
     repo = hg.repository(ui.ui(), reporoot)
     canonpath = util.canonpath(repo.root, os.getcwd(), spath)
     self._plot.plot(repo, canonpath)
예제 #11
0
 def setPath(self, path):
     spath = hglib.fromunicode(path)
     reporoot = cmdutil.findrepo(os.path.abspath(spath))
     if not reporoot:
         QMessageBox.warning(self, 'Repository Not Found',
                             'Repository not found for path: %s' % path)
     repo = hg.repository(ui.ui(), reporoot)
     canonpath = util.canonpath(repo.root, os.getcwd(), spath)
     self._plot.plot(repo, canonpath)
예제 #12
0
 def haskwsource(dest):
     '''Returns true if dest is a regular file and configured for
     expansion or a symlink which points to a file configured for
     expansion. '''
     source = repo.dirstate.copied(dest)
     if 'l' in wctx.flags(source):
         source = util.canonpath(repo.root, cwd,
                                 os.path.realpath(source))
     return kwt.match(source)
예제 #13
0
 def prepare_display(self):
     root = self.repo.root
     cf = []
     for f in self.pats:
         try:
             if os.path.isfile(f):
                 cf.append(util.canonpath(root, self.cwd, f))
             elif os.path.isdir(f):
                 for fn in os.listdir(f):
                     fname = os.path.join(f, fn)
                     if not os.path.isfile(fname):
                         continue
                     cf.append(util.canonpath(root, self.cwd, fname))
         except util.Abort:
             pass
     for f in cf:
         self.add_annotate_page(f, '.')
     if not self.notebook.get_n_pages():
         self.add_search_page()
     os.chdir(root)
예제 #14
0
 def _save_file_rev(self, menuitem):
     file = util.localpath(self.curfile)
     file, ext = os.path.splitext(os.path.basename(file))
     filename = "%s@%d%s" % (file, self.currev, ext)
     fd = NativeSaveFileDialogWrapper(Title = "Save file to",
                                      InitialDir=self.cwd,
                                      FileName=filename)
     result = fd.run()
     if result:
         import Queue
         import hglib
         q = Queue.Queue()
         cpath = util.canonpath(self.repo.root, self.cwd, self.curfile)
         hglib.hgcmd_toq(self.repo.root, q, 'cat', '--rev',
             str(self.currev), '--output', result, cpath)
예제 #15
0
def rename_resp(dlg, response):
    if response != gtk.RESPONSE_OK:
        dlg.destroy()
        return
    try:
        root = paths.find_root()
        repo = hg.repository(ui.ui(), root)
    except (ImportError, error.RepoError):
        dlg.destroy()
        return

    new_name = hglib.fromutf(dlg.entry.get_text())
    opts = {}
    opts['force'] = False # Checkbox? Nah.
    opts['after'] = True
    opts['dry_run'] = False

    saved = sys.stderr
    errors = cStringIO.StringIO()
    toquit = False
    try:
        sys.stderr = errors
        repo.ui.pushbuffer()
        repo.ui.quiet = True
        try:
            new_name = util.canonpath(root, root, new_name)
            targetdir = os.path.dirname(new_name) or '.'
            if dlg.orig.lower() == new_name.lower() and os.path.isdir(dlg.orig):
                os.rename(dlg.orig, new_name)
            else:
                if not os.path.isdir(targetdir):
                    os.makedirs(targetdir)
                shutil.move(dlg.orig, new_name)
            commands.rename(repo.ui, repo, dlg.orig, new_name, **opts)
            toquit = True
        except (OSError, IOError, util.Abort, error.RepoError), inst:
            dialog.error_dialog(None, _('rename error'), str(inst))
            toquit = False
    finally:
        sys.stderr = saved
        textout = errors.getvalue() + repo.ui.popbuffer()
        errors.close()
        if len(textout) > 1:
            dialog.error_dialog(None, _('rename error'), textout)
        elif toquit:
            dlg.destroy()
예제 #16
0
def mpatch(ui, rejfile, *pats, **opts):
    """Attempt to resolve conflicts in a .rej file"""
    def abort(err):
        from tortoisehg.hgtk import gdialog
        gdialog.Prompt(_('mpatch error'), err, None).run()        
        return None
    if not rejfile or pats or not rejfile.endswith('.rej'):
        return abort(_('mpatch expects *.rej file argument\n'))
    if not os.path.exists(rejfile):
        return abort(_('%s does not exist\n') % rejfile)
    # Assume patch was made from repo root, and arrange ourselves thusly
    repo = hg.repository(ui, path=paths.find_root())
    rejfile = util.canonpath(repo.root, repo.getcwd(), rejfile)
    os.chdir(repo.root)
    source = rejfile[:-4]
    if not os.path.exists(source):
        return abort(_('%s does not exist\n') % source)
    from tortoisehg.util import prej
    from tortoisehg.hgtk import visdiff
    prej.run(ui, rejfile, source, visdiff.filemerge)
예제 #17
0
 def makestandin(relpath):
     return os.path.join(os.path.relpath('.', repo.getcwd()), bfutil.standin(util.canonpath(repo.root, repo.getcwd(), relpath)))
예제 #18
0
 def cleanpath(self, path):
     path = path.lstrip('/')
     return util.canonpath(self.repo.root, '', path)
예제 #19
0
    def add_search_page(self):
        frame = gtk.Frame()
        frame.set_border_width(10)
        vbox = gtk.VBox()

        search_hbox = gtk.HBox()
        regexp = gtk.Entry()
        includes = gtk.Entry()
        if self.cwd.startswith(self.repo.root):
            try:
                relpath = util.canonpath(self.repo.root, self.cwd, '.')
                includes.set_text(relpath)
            except util.Abort:
                # Some paths inside root are invalid (.hg/*)
                pass
        excludes = gtk.Entry()
        search = gtk.Button(_('Search'))
        search_hbox.pack_start(gtk.Label(_('Regexp:')), False, False, 4)
        search_hbox.pack_start(regexp, True, True, 4)
        search_hbox.pack_start(gtk.Label(_('Includes:')), False, False, 4)
        search_hbox.pack_start(includes, True, True, 4)
        search_hbox.pack_start(gtk.Label(_('Excludes:')), False, False, 4)
        search_hbox.pack_start(excludes, True, True, 4)
        search_hbox.pack_start(search, False, False, 4)
        self.tooltips.set_tip(search, _('Start this search'))
        self.tooltips.set_tip(regexp, _('Regular expression search pattern'))
        self.tooltips.set_tip(includes, _('Comma separated list of '
                'inclusion patterns.  By default, the entire repository '
                'is searched.'))
        self.tooltips.set_tip(excludes, _('Comma separated list of '
                'exclusion patterns.  Exclusion patterns are applied '
                'after inclusion patterns.'))
        vbox.pack_start(search_hbox, False, False, 4)

        hbox = gtk.HBox()
        follow = gtk.CheckButton(_('Follow copies and renames'))
        ignorecase = gtk.CheckButton(_('Ignore case'))
        linenum = gtk.CheckButton(_('Show line numbers'))
        showall = gtk.CheckButton(_('Show all matching revisions'))
        hbox.pack_start(follow, False, False, 4)
        hbox.pack_start(ignorecase, False, False, 4)
        hbox.pack_start(linenum, False, False, 4)
        hbox.pack_start(showall, False, False, 4)
        vbox.pack_start(hbox, False, False, 4)

        treeview = gtk.TreeView()
        treeview.get_selection().set_mode(gtk.SELECTION_SINGLE)
        treeview.set_rules_hint(True)
        treeview.set_property('fixed-height-mode', True)
        treeview.connect("cursor-changed", self.grep_selection_changed)
        treeview.connect('button-release-event', self.grep_button_release)
        treeview.connect('popup-menu', self.grep_popup_menu)
        treeview.connect('row-activated', self.grep_row_act)

        accelgroup = gtk.AccelGroup()
        self.add_accel_group(accelgroup)
        mod = gtklib.get_thg_modifier()
        key, modifier = gtk.accelerator_parse(mod+'d')
        treeview.add_accelerator('thg-diff', accelgroup, key,
                        modifier, gtk.ACCEL_VISIBLE)
        treeview.connect('thg-diff', self.grep_thgdiff)

        results = gtk.ListStore(str, # revision id
                                str, # matched line (utf-8)
                                str, # description (utf-8, escaped)
                                str) # file path (utf-8)
        treeview.set_model(results)
        treeview.set_search_equal_func(self.search_in_grep)
        for title, width, ttype, col, emode in (
                (_('Rev'), 10, 'text', GCOL_REVID, pango.ELLIPSIZE_NONE),
                (_('File'), 25, 'text', GCOL_PATH, pango.ELLIPSIZE_START),
                (_('Matches'), 80, 'markup', GCOL_LINE, pango.ELLIPSIZE_END)):
            cell = gtk.CellRendererText()
            cell.set_property('width-chars', width)
            cell.set_property('ellipsize', emode)
            cell.set_property('family', 'Monospace')
            column = gtk.TreeViewColumn(title)
            column.set_resizable(True)
            column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
            column.set_fixed_width(cell.get_size(treeview)[2])
            column.pack_start(cell, expand=True)
            column.add_attribute(cell, ttype, col)
            treeview.append_column(column)
        if hasattr(treeview, 'set_tooltip_column'):
            treeview.set_tooltip_column(GCOL_DESC)
        scroller = gtk.ScrolledWindow()
        scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        scroller.add(treeview)
        vbox.pack_start(scroller, True, True)
        frame.add(vbox)
        frame.show_all()

        hbox = gtk.HBox()
        lbl = gtk.Label(_('Search %d') % self.newpagecount)
        close = self.create_tab_close_button()
        close.connect('clicked', self.close_page, frame)
        hbox.pack_start(lbl, True, True, 2)
        hbox.pack_start(close, False, False)
        hbox.show_all()
        num = self.notebook.append_page(frame, hbox)

        self.newpagecount += 1
        objs = (treeview.get_model(), frame, regexp, follow, ignorecase,
                excludes, includes, linenum, showall, search_hbox)
        # Clicking 'search' or hitting Enter in any text entry triggers search
        search.connect('clicked', self.trigger_search, objs)
        regexp.connect('activate', self.trigger_search, objs)
        includes.connect('activate', self.trigger_search, objs)
        excludes.connect('activate', self.trigger_search, objs)
        # Includes/excludes must disable following copies
        objs = (includes, excludes, follow)
        includes.connect('changed', self.update_following_possible, objs)
        excludes.connect('changed', self.update_following_possible, objs)
        self.update_following_possible(includes, objs)

        if hasattr(self.notebook, 'set_tab_reorderable'):
            self.notebook.set_tab_reorderable(frame, True)
        self.notebook.set_current_page(num)
        regexp.grab_focus()
예제 #20
0
 def f(path):
     cpath = util.canonpath(repo.root, repo.getcwd(), path)
     return ignfunc(cpath)
예제 #21
0
        sys.stderr.write(_('can not read file "%s". Ignored.\n') % filename)
        return []

    # Convert absolute file paths to repo/cwd canonical
    cwd = os.getcwd()
    root = paths.find_root(cwd)
    if not root:
        return lines
    if cwd == root:
        cwd_rel = ''
    else:
        cwd_rel = cwd[len(root+os.sep):] + os.sep
    files = []
    for f in lines:
        try:
            cpath = util.canonpath(root, cwd, f)
            # canonpath will abort on .hg/ paths
        except util.Abort:
            continue
        if cpath.startswith(cwd_rel):
            cpath = cpath[len(cwd_rel):]
            files.append(cpath)
        else:
            files.append(f)
    return files

def _parse(ui, args):
    options = {}
    cmdoptions = {}

    try:
예제 #22
0
파일: webutil.py 프로젝트: helloandre/cr48
def cleanpath(repo, path):
    path = path.lstrip("/")
    return util.canonpath(repo.root, "", path)
예제 #23
0
    def add_search_page(self):
        frame = gtk.Frame()
        frame.set_border_width(10)
        vbox = gtk.VBox()

        search_hbox = gtk.HBox()
        regexp = gtk.Entry()
        includes = gtk.Entry()
        if self.cwd.startswith(self.repo.root):
            includes.set_text(util.canonpath(self.repo.root, self.cwd, '.'))
        excludes = gtk.Entry()
        search = gtk.Button('Search')
        search_hbox.pack_start(gtk.Label('Regexp:'), False, False, 4)
        search_hbox.pack_start(regexp, True, True, 4)
        search_hbox.pack_start(gtk.Label('Includes:'), False, False, 4)
        search_hbox.pack_start(includes, True, True, 4)
        search_hbox.pack_start(gtk.Label('Excludes:'), False, False, 4)
        search_hbox.pack_start(excludes, True, True, 4)
        search_hbox.pack_start(search, False, False)
        self.tooltips.set_tip(search, 'Start this search')
        self.tooltips.set_tip(regexp, 'Regular expression search pattern')
        self.tooltips.set_tip(includes, 'Comma separated list of'
                ' inclusion patterns.  By default, the entire repository'
                ' is searched.')
        self.tooltips.set_tip(excludes, 'Comma separated list of'
                ' exclusion patterns.  Exclusion patterns are applied'
                ' after inclusion patterns.')
        vbox.pack_start(search_hbox, False, False, 4)

        hbox = gtk.HBox()
        follow = gtk.CheckButton('Follow copies and renames')
        ignorecase = gtk.CheckButton('Ignore case')
        linenum = gtk.CheckButton('Show line numbers')
        showall = gtk.CheckButton('Show all matching revisions')
        hbox.pack_start(follow, False, False, 4)
        hbox.pack_start(ignorecase, False, False, 4)
        hbox.pack_start(linenum, False, False, 4)
        hbox.pack_start(showall, False, False, 4)
        vbox.pack_start(hbox, False, False, 4)

        treeview = gtk.TreeView()
        treeview.get_selection().set_mode(gtk.SELECTION_SINGLE)
        treeview.set_property('fixed-height-mode', True)
        treeview.connect("cursor-changed", self._grep_selection_changed)
        treeview.connect('button-release-event', self._grep_button_release)
        treeview.connect('popup-menu', self._grep_popup_menu)
        treeview.connect('row-activated', self._grep_row_act)

        results = gtk.ListStore(str, str, str, str)
        treeview.set_model(results)
        for title, width, col, emode in (
                ('Rev', 10, self.COL_REVID, pango.ELLIPSIZE_NONE),
                ('File', 25, self.COL_PATH, pango.ELLIPSIZE_START),
                ('Matches', 80, self.COL_TEXT, pango.ELLIPSIZE_END)):
            cell = gtk.CellRendererText()
            cell.set_property("width-chars", width)
            cell.set_property("ellipsize", emode)
            cell.set_property("family", "Monospace")
            column = gtk.TreeViewColumn(title)
            column.set_resizable(True)
            column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
            column.set_fixed_width(cell.get_size(treeview)[2])
            column.pack_start(cell, expand=True)
            column.add_attribute(cell, "text", col)
            treeview.append_column(column)
        if hasattr(treeview, 'set_tooltip_column'):
            treeview.set_tooltip_column(self.COL_TOOLTIP)
        scroller = gtk.ScrolledWindow()
        scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        scroller.add(treeview)
        vbox.pack_start(scroller, True, True)
        frame.add(vbox)
        frame.show_all()

        hbox = gtk.HBox()
        lbl = gtk.Label('Search %d' % self.newpagecount)
        close = self.create_tab_close_button()
        close.connect('clicked', self.close_page, frame)
        hbox.pack_start(lbl, True, True, 2)
        hbox.pack_start(close, False, False)
        hbox.show_all()
        num = self.notebook.append_page(frame, hbox)

        self.newpagecount += 1
        objs = (treeview.get_model(), frame, regexp, follow, ignorecase,
                excludes, includes, linenum, showall, search_hbox)
        # Clicking 'search' or hitting Enter in any text entry triggers search
        search.connect('clicked', self.trigger_search, objs)
        regexp.connect('activate', self.trigger_search, objs)
        includes.connect('activate', self.trigger_search, objs)
        excludes.connect('activate', self.trigger_search, objs)
        if hasattr(self.notebook, 'set_tab_reorderable'):
            self.notebook.set_tab_reorderable(frame, True)
        self.notebook.set_current_page(num)
        regexp.grab_focus()
예제 #24
0
def cleanpath(repo, path):
    path = path.lstrip('/')
    return util.canonpath(repo.root, '', path)
예제 #25
0
def graphlog(ui, repo, path=None, **opts):
    """show revision history alongside an ASCII revision graph

    Print a revision history alongside a revision graph drawn with
    ASCII characters.

    Nodes printed as an @ character are parents of the working
    directory.
    """

    limit = get_limit(opts["limit"])
    (start_rev, stop_rev) = get_revs(repo, opts["rev"])
    stop_rev = max(stop_rev, start_rev - limit + 1)
    if start_rev == nullrev:
        return
    cs_printer = show_changeset(ui, repo, opts)
    if path:
        cpath = canonpath(repo.root, os.getcwd(), path)
        grapher = filelog_grapher(repo, cpath, start_rev, stop_rev)
    else:
        grapher = revision_grapher(repo, start_rev, stop_rev)
    repo_parents = repo.dirstate.parents()
    prev_n_columns_diff = 0
    prev_node_index = 0

    for (rev, node, node_index, edges, n_columns, n_columns_diff) in grapher:
        # log_strings is the list of all log strings to draw alongside
        # the graph.
        ui.pushbuffer()
        cs_printer.show(rev, node)
        log_strings = ui.popbuffer().split("\n")[:-1]

        if n_columns_diff == -1:
            # Transform
            #
            #     | | |        | | |
            #     o | |  into  o---+
            #     |X /         |/ /
            #     | |          | |
            fix_long_right_edges(edges)

        # add_padding_line says whether to rewrite
        #
        #     | | | |        | | | |
        #     | o---+  into  | o---+
        #     |  / /         |   | |  # <--- padding line
        #     o | |          |  / /
        #                    o | |
        add_padding_line = (len(log_strings) > 2 and
                            n_columns_diff == -1 and
                            [x for (x, y) in edges if x + 1 < y])

        # fix_nodeline_tail says whether to rewrite
        #
        #     | | o | |        | | o | |
        #     | | |/ /         | | |/ /
        #     | o | |    into  | o / /   # <--- fixed nodeline tail
        #     | |/ /           | |/ /
        #     o | |            o | |
        fix_nodeline_tail = len(log_strings) <= 2 and not add_padding_line

        # nodeline is the line containing the node character (@ or o).
        nodeline = ["|", " "] * node_index
        if node in repo_parents:
            node_ch = "@"
        else:
            node_ch = "o"
        nodeline.extend([node_ch, " "])

        nodeline.extend(
            get_nodeline_edges_tail(
                node_index, prev_node_index, n_columns, n_columns_diff,
                prev_n_columns_diff, fix_nodeline_tail))

        # shift_interline is the line containing the non-vertical
        # edges between this entry and the next.
        shift_interline = ["|", " "] * node_index
        if n_columns_diff == -1:
            n_spaces = 1
            edge_ch = "/"
        elif n_columns_diff == 0:
            n_spaces = 2
            edge_ch = "|"
        else:
            n_spaces = 3
            edge_ch = "\\"
        shift_interline.extend(n_spaces * [" "])
        shift_interline.extend([edge_ch, " "] * (n_columns - node_index - 1))

        # Draw edges from the current node to its parents.
        draw_edges(edges, nodeline, shift_interline)

        # lines is the list of all graph lines to print.
        lines = [nodeline]
        if add_padding_line:
            lines.append(get_padding_line(node_index, n_columns, edges))
        lines.append(shift_interline)

        # Make sure that there are as many graph lines as there are
        # log strings.
        while len(log_strings) < len(lines):
            log_strings.append("")
        if len(lines) < len(log_strings):
            extra_interline = ["|", " "] * (n_columns + n_columns_diff)
            while len(lines) < len(log_strings):
                lines.append(extra_interline)

        # Print lines.
        indentation_level = max(n_columns, n_columns + n_columns_diff)
        for (line, logstr) in zip(lines, log_strings):
            ui.write(format_line(line, indentation_level, logstr))

        # ...and start over.
        prev_node_index = node_index
        prev_n_columns_diff = n_columns_diff