示例#1
0
    def get_body(self):
        if self.repo.ui.configbool('tortoisehg', 'copyhash'):
            sel = (os.name == 'nt') and 'CLIPBOARD' or 'PRIMARY'
            self.clipboard = gtk.Clipboard(selection=sel)
        else:
            self.clipboard = None
        self._filemenu = self.file_context_menu()

        details_frame = gtk.Frame()
        details_frame.set_shadow_type(gtk.SHADOW_ETCHED_IN)
        scroller = gtk.ScrolledWindow()
        scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        details_frame.add(scroller)
        
        details_text = gtk.TextView()
        details_text.set_wrap_mode(gtk.WRAP_NONE)
        details_text.set_editable(False)
        details_text.modify_font(pango.FontDescription(self.fontcomment))
        scroller.add(details_text)

        self._buffer = gtk.TextBuffer()
        self.setup_tags()
        details_text.set_buffer(self._buffer)
        self.textview = details_text

        filelist_tree = gtk.TreeView()
        filesel = filelist_tree.get_selection()
        filesel.connect("changed", self._filelist_rowchanged)
        filelist_tree.connect('button-release-event',
                self._file_button_release)
        filelist_tree.connect('popup-menu', self._file_popup_menu)
        filelist_tree.connect('row-activated', self._file_row_act)

        self._filelist = gtk.ListStore(
                gobject.TYPE_STRING,   # MAR status
                gobject.TYPE_STRING,   # filename (utf-8 encoded)
                gobject.TYPE_PYOBJECT, # mark
                gobject.TYPE_PYOBJECT, # give cmenu
                gobject.TYPE_PYOBJECT, # diffstats
                )
        filelist_tree.set_model(self._filelist)
        column = gtk.TreeViewColumn('Stat', gtk.CellRendererText(), text=0)
        filelist_tree.append_column(column)
        column = gtk.TreeViewColumn('Files', gtk.CellRendererText(), text=1)
        filelist_tree.append_column(column)

        list_frame = gtk.Frame()
        list_frame.set_shadow_type(gtk.SHADOW_ETCHED_IN)
        scroller = gtk.ScrolledWindow()
        scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        scroller.add(filelist_tree)
        list_frame.add(scroller)

        self._hpaned = gtk.HPaned()
        self._hpaned.pack1(list_frame, True, True)
        self._hpaned.pack2(details_frame, True, True)
        self._hpaned.set_position(self._setting_hpos)

        if self.stbar:
            # embedded by changelog browser
            return self._hpaned
        else:
            # add status bar for main app
            vbox = gtk.VBox()
            vbox.pack_start(self._hpaned, True, True)
            self.stbar = StatusBar()
            self.stbar.show()
            vbox.pack_start(gtk.HSeparator(), False, False)
            vbox.pack_start(self.stbar, False, False)
            return vbox
示例#2
0
class ChangeSet(GDialog):
    """GTK+ based dialog for displaying repository logs
    """
    def __init__(self, ui, repo, cwd, pats, opts, main, stbar=None):
        GDialog.__init__(self, ui, repo, cwd, pats, opts, main)
        self.stbar = stbar

    def get_title(self):
        title = os.path.basename(self.repo.root) + ' changeset '
        title += self.opts['rev'][0]
        return title

    def get_icon(self):
        return 'menushowchanged.ico'

    def get_tbbuttons(self):
        self.parent_toggle = gtk.ToggleToolButton(gtk.STOCK_UNDO)
        self.parent_toggle.set_use_underline(True)
        self.parent_toggle.set_label('_other parent')
        self.parent_toggle.set_tooltip(self.tooltips, 'diff other parent')
        self.parent_toggle.set_sensitive(False)
        self.parent_toggle.set_active(False)
        self.parent_toggle.connect('toggled', self._parent_toggled)
        return [self.parent_toggle]

    def _parent_toggled(self, button):
        self.load_details(self.currev)

    def prepare_display(self):
        self.currow = None
        self.graphview = None
        self.glog_parent = None
        node0, node1 = cmdutil.revpair(self.repo, self.opts.get('rev'))
        self.load_details(self.repo.changelog.rev(node0))

    def save_settings(self):
        settings = GDialog.save_settings(self)
        settings['changeset'] = self._hpaned.get_position()
        return settings

    def load_settings(self, settings):
        GDialog.load_settings(self, settings)
        if settings and 'changeset' in settings:
            self._setting_hpos = settings['changeset']
        else:
            self._setting_hpos = -1

    def load_details(self, rev):
        '''Load selected changeset details into buffer and filelist'''
        self.currev = rev
        self._buffer.set_text('')
        self._filelist.clear()

        parents = [x for x in self.repo.changelog.parentrevs(rev) \
                if x != nullrev]
        self.parents = parents
        title = self.get_title()
        if len(parents) == 2:
            self.parent_toggle.set_sensitive(True)
            if self.parent_toggle.get_active():
                title += ':' + str(self.parents[1])
            else:
                title += ':' + str(self.parents[0])
        else:
            self.parent_toggle.set_sensitive(False)
            if self.parent_toggle.get_active():
                # Parent button must be pushed out, but this
                # will cause load_details to be called again
                # so we exit out to prevent recursion.
                self.parent_toggle.set_active(False)
                return

        ctx = self.repo.changectx(rev)
        if not ctx:
            self._last_rev = None
            return False
        self.set_title(title)
        self.textview.freeze_child_notify()
        try:
            self._fill_buffer(self._buffer, rev, ctx, self._filelist)
        finally:
            self.textview.thaw_child_notify()

    def _fill_buffer(self, buf, rev, ctx, filelist):
        self.stbar.begin('Retrieving changeset data...')
        
        def title_line(title, text, tag):
            pad = ' ' * (12 - len(title))
            utext = toutf(title + pad + text)
            buf.insert_with_tags_by_name(eob, utext, tag)
            buf.insert(eob, "\n")

        # TODO: Add toggle for gmtime/localtime
        eob = buf.get_end_iter()
        date = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(ctx.date()[0]))
        if self.clipboard:
            self.clipboard.set_text(short(ctx.node()))
        change = str(rev) + ':' + short(ctx.node())
        tags = ' '.join(ctx.tags())
        parents = self.parents

        title_line('changeset:', change, 'changeset')
        if ctx.branch() != 'default':
            title_line('branch:', ctx.branch(), 'greybg')
        title_line('user/date:', ctx.user() + '\t' + date, 'changeset')
        for p in parents:
            pctx = self.repo.changectx(p)
            summary = pctx.description().splitlines()[0]
            summary = toutf(summary)
            change = str(p) + ':' + short(self.repo.changelog.node(p))
            title = 'parent:'
            title += ' ' * (12 - len(title))
            buf.insert_with_tags_by_name(eob, title, 'parent')
            buf.insert_with_tags_by_name(eob, change, 'link')
            buf.insert_with_tags_by_name(eob, ' ' + summary, 'parent')
            buf.insert(eob, "\n")
        for n in self.repo.changelog.children(ctx.node()):
            cctx = self.repo.changectx(n)
            summary = cctx.description().splitlines()[0]
            summary = toutf(summary)
            childrev = self.repo.changelog.rev(n)
            change = str(childrev) + ':' + short(n)
            title = 'child:'
            title += ' ' * (12 - len(title))
            buf.insert_with_tags_by_name(eob, title, 'parent')
            buf.insert_with_tags_by_name(eob, change, 'link')
            buf.insert_with_tags_by_name(eob, ' ' + summary, 'parent')
            buf.insert(eob, "\n")
        for n in self.repo.changelog.children(ctx.node()):
            childrev = self.repo.changelog.rev(n)
        if tags: title_line('tags:', tags, 'tag')

        log = toutf(ctx.description())
        buf.insert(eob, '\n' + log + '\n\n')

        if self.parent_toggle.get_active():
            parent = self.repo.changelog.node(parents[1])
        elif parents:
            parent = self.repo.changelog.node(parents[0])
        else:
            parent = nullid

        buf.create_mark('begmark', buf.get_start_iter())
        filelist.append(('*', '[Description]', 'begmark', False, ()))
        pctx = self.repo.changectx(parent)

        nodes = parent, ctx.node()
        iterator = self.diff_generator(*nodes)
        gobject.idle_add(self.get_diffs, iterator, nodes, pctx, buf, filelist)
        self.curnodes = nodes

    def get_diffs(self, iterator, nodes, pctx, buf, filelist):
        if self.curnodes != nodes:
            return False

        try:
            status, file, txt = iterator.next()
        except StopIteration:
            self.stbar.end()
            return False

        lines = txt.splitlines()
        eob = buf.get_end_iter()
        offset = eob.get_offset()
        fileoffs, tags, lines, statmax = self.prepare_diff(lines, offset, file)
        for l in lines:
            buf.insert(eob, l)

        # inserts the tags
        for name, p0, p1 in tags:
            i0 = buf.get_iter_at_offset(p0)
            i1 = buf.get_iter_at_offset(p1)
            txt = buf.get_text(i0, i1)
            buf.apply_tag_by_name(name, i0, i1)
            
        # inserts the marks
        for mark, offset, stats in fileoffs:
            pos = buf.get_iter_at_offset(offset)
            mark = 'mark_%d' % offset
            buf.create_mark(mark, pos)
            filelist.append((status, toutf(file), mark, True, stats))
        sob, eob = buf.get_bounds()
        buf.apply_tag_by_name("mono", pos, eob)
        return True

    # Hacked up version of mercurial.patch.diff()
    # Use git mode by default (to show copies, renames, permissions) but
    # never show binary diffs.  It operates as a generator, so it can be
    # called iteratively to get file diffs from a changeset
    def diff_generator(self, node1, node2):
        repo = self.repo

        ccache = {}
        def getctx(r):
            if r not in ccache:
                ccache[r] = context.changectx(repo, r)
            return ccache[r]

        flcache = {}
        def getfilectx(f, ctx):
            flctx = ctx.filectx(f, filelog=flcache.get(f))
            if f not in flcache:
                flcache[f] = flctx._filelog
            return flctx

        ctx1 = context.changectx(repo, node1) # parent
        ctx2 = context.changectx(repo, node2) # current

        if node1 == repo.changelog.parents(node2)[0]:
            filelist = ctx2.files()
        else:
            changes = repo.status(node1, node2, None)[:5]
            modified, added, removed, deleted, unknown = changes
            filelist = modified + added + removed


        # force manifest reading
        man1 = ctx1.manifest()
        date1 = util.datestr(ctx1.date())

        execf2 = ctx2.manifest().execf
        linkf2 = ctx2.manifest().linkf

        # returns False if there was no rename between ctx1 and ctx2
        # returns None if the file was created between ctx1 and ctx2
        # returns the (file, node) present in ctx1 that was renamed to f in ctx2
        # This will only really work if c1 is the Nth 1st parent of c2.
        def renamed(c1, c2, man, f):
            startrev = c1.rev()
            c = c2
            crev = c.rev()
            if crev is None:
                crev = repo.changelog.count()
            orig = f
            files = (f,)
            while crev > startrev:
                if f in files:
                    try:
                        src = getfilectx(f, c).renamed()
                    except revlog.LookupError:
                        return None
                    if src:
                        f = src[0]
                crev = c.parents()[0].rev()
                # try to reuse
                c = getctx(crev)
                files = c.files()
            if f not in man:
                return None
            if f == orig:
                return False
            return f

        status = {}
        def filestatus(f):
            if f in status:
                return status[f]
            try:
                # Determine file status by presence in manifests
                s = 'R'
                ctx2.filectx(f)
                s = 'A'
                ctx1.filectx(f)
                s = 'M'
            except revlog.LookupError:
                pass
            status[f] = s
            return s

        copied = {}
        for f in filelist:
            src = renamed(ctx1, ctx2, man1, f)
            if src:
                copied[f] = src

        srcs = [x[1] for x in copied.iteritems() if filestatus(x[0]) == 'A']

        gone = {}
        for f in filelist:
            s = filestatus(f)
            to = None
            tn = None
            dodiff = True
            header = []
            if f in man1:
                to = getfilectx(f, ctx1).data()
            if s != 'R':
                tn = getfilectx(f, ctx2).data()
            a, b = f, f
            def gitmode(x, l):
                return l and '120000' or (x and '100755' or '100644')
            def addmodehdr(header, omode, nmode):
                if omode != nmode:
                    header.append('old mode %s\n' % omode)
                    header.append('new mode %s\n' % nmode)

            if s == 'A':
                mode = gitmode(execf2(f), linkf2(f))
                if f in copied:
                    a = copied[f]
                    omode = gitmode(man1.execf(a), man1.linkf(a))
                    addmodehdr(header, omode, mode)
                    if filestatus(a) == 'R' and a not in gone:
                        op = 'rename'
                        gone[a] = 1
                    else:
                        op = 'copy'
                    header.append('%s from %s\n' % (op, a))
                    header.append('%s to %s\n' % (op, f))
                    to = getfilectx(a, ctx1).data()
                else:
                    header.append('new file mode %s\n' % mode)
                if util.binary(tn):
                    dodiff = 'binary'
            elif s == 'R':
                if f in srcs:
                    dodiff = False
                else:
                    mode = gitmode(man1.execf(f), man1.linkf(f))
                    header.append('deleted file mode %s\n' % mode)
            else:
                omode = gitmode(man1.execf(f), man1.linkf(f))
                nmode = gitmode(execf2(f), linkf2(f))
                addmodehdr(header, omode, nmode)
                if util.binary(to) or util.binary(tn):
                    dodiff = 'binary'
            header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
            if dodiff == 'binary':
                text = 'binary file has changed.\n'
            elif dodiff:
                try:
                    text = patch.mdiff.unidiff(to, date1,
                                    tn, util.datestr(ctx2.date()),
                                    fn1=a, fn2=b, r=None,
                                    opts=patch.mdiff.defaultopts)
                except TypeError:
                    # hg-0.9.5 and before
                    text = patch.mdiff.unidiff(to, date1,
                                    tn, util.datestr(ctx2.date()),
                                    f, None, opts=patch.mdiff.defaultopts)
            else:
                text = ''
            if header or text: yield (s, f, ''.join(header) + text)

    def prepare_diff(self, difflines, offset, fname):
        '''Borrowed from hgview; parses changeset diffs'''
        DIFFHDR = "=== %s ===\n"
        idx = 0
        outlines = []
        tags = []
        filespos = []
        def addtag( name, offset, length ):
            if tags and tags[-1][0] == name and tags[-1][2]==offset:
                tags[-1][2] += length
            else:
                tags.append( [name, offset, offset+length] )
        stats = [0,0]
        statmax = 0
        for i,l1 in enumerate(difflines):
            l = toutf(l1)
            if l.startswith("diff"):
                txt = toutf(DIFFHDR % fname)
                addtag( "greybg", offset, len(txt) )
                outlines.append(txt)
                markname = "file%d" % idx
                idx += 1
                statmax = max( statmax, stats[0]+stats[1] )
                stats = [0,0]
                filespos.append(( markname, offset, stats ))
                offset += len(txt.decode('utf-8'))
                continue
            elif l.startswith("+++"):
                continue
            elif l.startswith("---"):
                continue
            elif l.startswith("+"):
                tag = "green"
                stats[0] += 1
            elif l.startswith("-"):
                stats[1] += 1
                tag = "red"
            elif l.startswith("@@"):
                tag = "blue"
            else:
                tag = "black"
            l = l+"\n"
            length = len(l.decode('utf-8'))
            addtag( tag, offset, length )
            outlines.append( l )
            offset += length
        statmax = max( statmax, stats[0]+stats[1] )
        return filespos, tags, outlines, statmax

    def link_event(self, tag, widget, event, iter):
        if event.type != gtk.gdk.BUTTON_RELEASE:
            return
        text = self.get_link_text(tag, widget, iter)
        if not text:
            return
        linkrev = long(text.split(':')[0])
        if self.graphview:
            self.graphview.set_revision_id(linkrev)
            self.graphview.scroll_to_revision(linkrev)
        else:
            self.load_details(linkrev)

    def get_link_text(self, tag, widget, iter):
        """handle clicking on a link in a textview"""
        text_buffer = widget.get_buffer()
        beg = iter.copy()
        while not beg.begins_tag(tag):
            beg.backward_char()
        end = iter.copy()
        while not end.ends_tag(tag):
            end.forward_char()
        text = text_buffer.get_text(beg, end)
        return text
        
    def file_context_menu(self):
        def create_menu(label, callback):
            menuitem = gtk.MenuItem(label, True)
            menuitem.connect('activate', callback)
            menuitem.set_border_width(1)
            return menuitem
            
        _menu = gtk.Menu()
        _menu.append(create_menu('_view at revision', self._view_file_rev))
        self._save_menu = create_menu('_save at revision', self._save_file_rev)
        _menu.append(self._save_menu)
        _menu.append(create_menu('_file history', self._file_history))
        self._ann_menu = create_menu('_annotate file', self._ann_file)
        _menu.append(self._ann_menu)
        _menu.append(create_menu('_revert file contents', self._revert_file))
        self._file_diff_to_mark_menu = create_menu('_diff file to mark',
                self._diff_file_to_mark)
        self._file_diff_from_mark_menu = create_menu('diff file _from mark',
                self._diff_file_from_mark)
        _menu.append(self._file_diff_to_mark_menu)
        _menu.append(self._file_diff_from_mark_menu)
        _menu.show_all()
        return _menu

    def get_body(self):
        if self.repo.ui.configbool('tortoisehg', 'copyhash'):
            sel = (os.name == 'nt') and 'CLIPBOARD' or 'PRIMARY'
            self.clipboard = gtk.Clipboard(selection=sel)
        else:
            self.clipboard = None
        self._filemenu = self.file_context_menu()

        details_frame = gtk.Frame()
        details_frame.set_shadow_type(gtk.SHADOW_ETCHED_IN)
        scroller = gtk.ScrolledWindow()
        scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        details_frame.add(scroller)
        
        details_text = gtk.TextView()
        details_text.set_wrap_mode(gtk.WRAP_NONE)
        details_text.set_editable(False)
        details_text.modify_font(pango.FontDescription(self.fontcomment))
        scroller.add(details_text)

        self._buffer = gtk.TextBuffer()
        self.setup_tags()
        details_text.set_buffer(self._buffer)
        self.textview = details_text

        filelist_tree = gtk.TreeView()
        filesel = filelist_tree.get_selection()
        filesel.connect("changed", self._filelist_rowchanged)
        filelist_tree.connect('button-release-event',
                self._file_button_release)
        filelist_tree.connect('popup-menu', self._file_popup_menu)
        filelist_tree.connect('row-activated', self._file_row_act)

        self._filelist = gtk.ListStore(
                gobject.TYPE_STRING,   # MAR status
                gobject.TYPE_STRING,   # filename (utf-8 encoded)
                gobject.TYPE_PYOBJECT, # mark
                gobject.TYPE_PYOBJECT, # give cmenu
                gobject.TYPE_PYOBJECT, # diffstats
                )
        filelist_tree.set_model(self._filelist)
        column = gtk.TreeViewColumn('Stat', gtk.CellRendererText(), text=0)
        filelist_tree.append_column(column)
        column = gtk.TreeViewColumn('Files', gtk.CellRendererText(), text=1)
        filelist_tree.append_column(column)

        list_frame = gtk.Frame()
        list_frame.set_shadow_type(gtk.SHADOW_ETCHED_IN)
        scroller = gtk.ScrolledWindow()
        scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        scroller.add(filelist_tree)
        list_frame.add(scroller)

        self._hpaned = gtk.HPaned()
        self._hpaned.pack1(list_frame, True, True)
        self._hpaned.pack2(details_frame, True, True)
        self._hpaned.set_position(self._setting_hpos)

        if self.stbar:
            # embedded by changelog browser
            return self._hpaned
        else:
            # add status bar for main app
            vbox = gtk.VBox()
            vbox.pack_start(self._hpaned, True, True)
            self.stbar = StatusBar()
            self.stbar.show()
            vbox.pack_start(gtk.HSeparator(), False, False)
            vbox.pack_start(self.stbar, False, False)
            return vbox

    def setup_tags(self):
        """Creates the tags to be used inside the TextView"""
        def make_texttag( name, **kwargs ):
            """Helper function generating a TextTag"""
            tag = gtk.TextTag(name)
            for key, value in kwargs.iteritems():
                key = key.replace("_","-")
                try:
                    tag.set_property( key, value )
                except TypeError:
                    print "Warning the property %s is unsupported in" % key
                    print "this version of pygtk"
            return tag

        tag_table = self._buffer.get_tag_table()

        tag_table.add( make_texttag('changeset', foreground='#000090',
                paragraph_background='#F0F0F0'))
        tag_table.add(make_texttag('date', foreground='#000090',
                paragraph_background='#F0F0F0'))
        tag_table.add(make_texttag('tag', foreground='#000090',
                paragraph_background='#F0F0F0'))
        tag_table.add(make_texttag('files', foreground='#5C5C5C',
                paragraph_background='#F0F0F0'))
        tag_table.add(make_texttag('parent', foreground='#000090',
                paragraph_background='#F0F0F0'))

        tag_table.add( make_texttag( "mono", family="Monospace" ))
        tag_table.add( make_texttag( "blue", foreground='blue' ))
        tag_table.add( make_texttag( "red", foreground='red' ))
        tag_table.add( make_texttag( "green", foreground='darkgreen' ))
        tag_table.add( make_texttag( "black", foreground='black' ))
        tag_table.add( make_texttag( "greybg",
                                     paragraph_background='grey',
                                     weight=pango.WEIGHT_BOLD ))
        tag_table.add( make_texttag( "yellowbg", background='yellow' ))
        link_tag = make_texttag( "link", foreground="blue",
                                 underline=pango.UNDERLINE_SINGLE )
        link_tag.connect("event", self.link_event )
        tag_table.add( link_tag )

    def _filelist_rowchanged(self, sel):
        model, iter = sel.get_selected()
        if not iter:
            return
        # scroll to file in details window
        mark = self._buffer.get_mark(model[iter][2])
        self.textview.scroll_to_mark(mark, 0.0, True, 0.0, 0.0)
        if model[iter][3]:
            self.curfile = fromutf(model[iter][1])
        else:
            self.curfile = None

    def _file_button_release(self, widget, event):
        if event.button == 3 and not (event.state & (gtk.gdk.SHIFT_MASK |
            gtk.gdk.CONTROL_MASK)):
            self._file_popup_menu(widget, event.button, event.time)
        return False

    def _file_popup_menu(self, treeview, button=0, time=0):
        if self.curfile is None:
            return
        if self.graphview:
            is_mark = self.graphview.get_mark_rev() is not None
        else:
            is_mark = False
        self._file_diff_to_mark_menu.set_sensitive(is_mark)
        self._file_diff_from_mark_menu.set_sensitive(is_mark)
        self._filemenu.popup(None, None, None, button, time)

        # If the filelog entry this changeset references does not link
        # back to this changeset, it means this changeset did not
        # actually change the contents of this file, and thus the file
        # cannot be annotated at this revision (since this changeset
        # does not appear in the filelog)
        ctx = self.repo.changectx(self.currev)
        try:
            fctx = ctx.filectx(self.curfile)
            has_filelog = fctx.filelog().linkrev(fctx.filenode()) == ctx.rev()
        except revlog.LookupError:
            has_filelog = False
        self._ann_menu.set_sensitive(has_filelog)
        self._save_menu.set_sensitive(has_filelog)
        return True

    def _file_row_act(self, tree, path, column) :
        """Default action is the first entry in the context menu
        """
        self._filemenu.get_children()[0].activate()
        return True

    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)

    def _view_file_rev(self, menuitem):
        '''User selected view file revision from the file list context menu'''
        if not self.curfile:
            # ignore view events for the [Description] row
            return
        rev = self.currev
        parents = self.parents
        if len(parents) == 0:
            parent = rev-1
        else:
            parent = parents[0]
        pair = '%u:%u' % (parent, rev)
        self._node1, self._node2 = cmdutil.revpair(self.repo, [pair])
        self._view_file('M', self.curfile, force_left=False)

    def _diff_file_to_mark(self, menuitem):
        '''User selected diff to mark from the file list context menu'''
        from status import GStatus
        from gtools import cmdtable
        rev0 = self.graphview.get_mark_rev()
        rev1 = self.currev
        statopts = self.merge_opts(cmdtable['gstatus|gst'][1],
                ('include', 'exclude', 'git'))
        statopts['rev'] = ['%u:%u' % (rev1, rev0)]
        statopts['modified'] = True
        statopts['added'] = True
        statopts['removed'] = True
        dialog = GStatus(self.ui, self.repo, self.cwd, [self.curfile],
                statopts, False)
        dialog.display()
        return True

    def _diff_file_from_mark(self, menuitem):
        '''User selected diff from mark from the file list context menu'''
        from status import GStatus
        from gtools import cmdtable
        rev0 = self.graphview.get_mark_rev()
        rev1 = self.currev
        statopts = self.merge_opts(cmdtable['gstatus|gst'][1],
                ('include', 'exclude', 'git'))
        statopts['rev'] = ['%u:%u' % (rev0, rev1)]
        statopts['modified'] = True
        statopts['added'] = True
        statopts['removed'] = True
        dialog = GStatus(self.ui, self.repo, self.cwd, [self.curfile],
                statopts, False)
        dialog.display()

    def _ann_file(self, menuitem):
        '''User selected diff from mark from the file list context menu'''
        from datamine import DataMineDialog
        rev = self.currev
        dialog = DataMineDialog(self.ui, self.repo, self.cwd, [], {}, False)
        dialog.display()
        dialog.add_annotate_page(self.curfile, str(rev))

    def _file_history(self, menuitem):
        '''User selected file history from file list context menu'''
        if self.glog_parent:
            # If this changeset browser is embedded in glog, send
            # send this event to the main app
            opts = {'filehist' : self.curfile}
            self.glog_parent.custombutton.set_active(True)
            self.glog_parent.graphview.refresh(True, None, opts)
        else:
            # Else launch our own GLog instance
            from history import GLog
            dialog = GLog(self.ui, self.repo, self.cwd, [self.repo.root],
                    {}, False)
            dialog.open_with_file(self.curfile)
            dialog.display()

    def _revert_file(self, menuitem):
        '''User selected file revert from the file list context menu'''
        rev = self.currev
        dialog = Confirm('revert file to old revision', [], self,
                'Revert %s to contents at revision %d?' % (self.curfile, rev))
        if dialog.run() == gtk.RESPONSE_NO:
            return
        cmdline = ['hg', 'revert', '--verbose', '--rev', str(rev), self.curfile]
        dlg = CmdDialog(cmdline)
        dlg.run()
        dlg.hide()
        shell_notify([self.curfile])