Commits

Peter Arrenbrecht committed a6dcece

convert line endings to LF

Comments (0)

Files changed (7)

hggtk/changeset.py

-#
-# changeset.py - Changeset dialog for TortoiseHg
-#
-# Copyright 2008 Steve Borho <steve@borho.org>
-#
-
-import os
-import subprocess
-import sys
-import time
-
-import pygtk
-pygtk.require('2.0')
-import gtk
-import gobject
-import pango
-import StringIO
-
-from mercurial.i18n import _
-from mercurial.node import *
-from mercurial import cmdutil, util, ui, hg, commands
-from mercurial import context, patch, revlog
-from gdialog import *
-from hgcmd import CmdDialog
-from hglib import toutf, fromutf
-from gtklib import StatusBar
-
-
-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())
-
-        flags2 = ctx2.manifest().flags
-
-        # 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 = len(repo.changelog)
-            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(flags):
-                return 'l' in flags or 'x' in flags
-            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(flags2(f))
-                if f in copied:
-                    a = copied[f]
-                    omode = gitmode(man1.flags(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.flags(f))
-                    header.append('deleted file mode %s\n' % mode)
-            else:
-                omode = gitmode(man1.flags(f))
-                nmode = gitmode(flags2(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])
-
-def run(root='', cwd='', files=[], **opts):
-    u = ui.ui()
-    u.updateopts(debug=False, traceback=False)
-    repo = hg.repository(u, path=root)
-
-    dialog = ChangeSet(u, repo, cwd, files, opts, True)
-    dialog.display()
-
-    gtk.gdk.threads_init()
-    gtk.gdk.threads_enter()
-    gtk.main()
-    gtk.gdk.threads_leave()
-
-if __name__ == "__main__":
-    import sys
-    opts = {}
-    opts['root'] = len(sys.argv) > 1 and sys.argv[1] or os.getcwd()
-    opts['rev'] = ['750']
-    run(**opts)
+#
+# changeset.py - Changeset dialog for TortoiseHg
+#
+# Copyright 2008 Steve Borho <steve@borho.org>
+#
+
+import os
+import subprocess
+import sys
+import time
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+import gobject
+import pango
+import StringIO
+
+from mercurial.i18n import _
+from mercurial.node import *
+from mercurial import cmdutil, util, ui, hg, commands
+from mercurial import context, patch, revlog
+from gdialog import *
+from hgcmd import CmdDialog
+from hglib import toutf, fromutf
+from gtklib import StatusBar
+
+
+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())
+
+        flags2 = ctx2.manifest().flags
+
+        # 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 = len(repo.changelog)
+            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(flags):
+                return 'l' in flags or 'x' in flags
+            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(flags2(f))
+                if f in copied:
+                    a = copied[f]
+                    omode = gitmode(man1.flags(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.flags(f))
+                    header.append('deleted file mode %s\n' % mode)
+            else:
+                omode = gitmode(man1.flags(f))
+                nmode = gitmode(flags2(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])
+
+def run(root='', cwd='', files=[], **opts):
+    u = ui.ui()
+    u.updateopts(debug=False, traceback=False)
+    repo = hg.repository(u, path=root)
+
+    dialog = ChangeSet(u, repo, cwd, files, opts, True)
+    dialog.display()
+
+    gtk.gdk.threads_init()
+    gtk.gdk.threads_enter()
+    gtk.main()
+    gtk.gdk.threads_leave()
+
+if __name__ == "__main__":
+    import sys
+    opts = {}
+    opts['root'] = len(sys.argv) > 1 and sys.argv[1] or os.getcwd()
+    opts['rev'] = ['750']
+    run(**opts)
-#
-# history.py - Changelog dialog for TortoiseHg
-#
-# Copyright 2007 Brad Schick, brad at gmail . com
-# Copyright (C) 2007 TK Soh <teekaysoh@gmail.com>
-#
-
-import os
-import pygtk
-pygtk.require('2.0')
-import gtk
-import gobject
-import pango
-import StringIO
-
-from mercurial.node import *
-from mercurial import ui, hg, commands, extensions
-from gdialog import *
-from changeset import ChangeSet
-from logfilter import FilterDialog
-from update import UpdateDialog
-from merge import MergeDialog
-from vis import treemodel
-from vis.treeview import TreeView
-from hglib import toutf
-import gtklib
-
-def create_menu(label, callback):
-    menuitem = gtk.MenuItem(label, True)
-    menuitem.connect('activate', callback)
-    menuitem.set_border_width(1)
-    return menuitem
-
-class GLog(GDialog):
-    """GTK+ based dialog for displaying repository logs
-    """
-    def get_title(self):
-        return os.path.basename(self.repo.root) + ' log' 
-
-    def get_icon(self):
-        return 'menulog.ico'
-
-    def parse_opts(self):
-        # Disable quiet to get full log info
-        self.ui.quiet = False
-
-    def get_tbbuttons(self):
-        return [
-                self.make_toolbutton(gtk.STOCK_REFRESH,
-                    'Re_fresh',
-                    self._refresh_clicked,
-                    tip='Reload revision history'),
-                gtk.SeparatorToolItem(),
-                self.make_toolbutton(gtk.STOCK_INDEX,
-                    '_Filter',
-                    self._filter_clicked,
-                    menu=self._filter_menu(),
-                    tip='Filter revisions for display'),
-                gtk.SeparatorToolItem(),
-                self.make_toolbutton(gtk.STOCK_FIND,
-                    '_DataMine',
-                    self._datamine_clicked,
-                    tip='Search Repository History'),
-                gtk.SeparatorToolItem()
-             ] + self.changeview.get_tbbuttons()
-
-    def toggle_view_column(self, button, property):
-        bool = button.get_active()
-        self.graphview.set_property(property, bool)
-
-    def _more_clicked(self, button):
-        self.graphview.next_revision_batch()
-
-    def _load_all_clicked(self, button):
-        self.graphview.load_all_revisions()
-        self.nextbutton.set_sensitive(False)
-        self.allbutton.set_sensitive(False)
-
-    def revisions_loaded(self, graphview):
-        '''Treeview reports log generator has exited'''
-        if not self.graphview.graphdata:
-            self.changeview._buffer.set_text('')
-            self.changeview._filelist.clear()
-            self._last_rev = None
-        self.nextbutton.set_sensitive(False)
-        self.allbutton.set_sensitive(False)
-
-    def _datamine_clicked(self, toolbutton, data=None):
-        from datamine import DataMineDialog
-        dialog = DataMineDialog(self.ui, self.repo, self.cwd, [], {}, False)
-        dialog.display()
-        dialog.add_search_page()
-
-    def _filter_clicked(self, toolbutton, data=None):
-        if self._filter_dialog:
-            self._filter_dialog.show()
-            self._filter_dialog.present()
-        else:
-            self._show_filter_dialog()
-
-    def _show_filter_dialog(self):
-        '''Launch a modeless filter dialog'''
-        def do_reload(opts):
-            self.custombutton.set_active(True)
-            self.reload_log(opts)
-
-        def close_filter_dialog(dialog, response_id):
-            dialog.hide()
-
-        revs = []
-        if self.currow is not None:
-            revs.append(self.currow[treemodel.REVID])
-        if self.graphview.get_mark_rev() is not None:
-            revs.append(self.graphview.get_mark_rev())
-            
-        dlg = FilterDialog(self.repo.root, revs, self.pats,
-                filterfunc=do_reload)
-        dlg.connect('response', close_filter_dialog)
-        dlg.set_modal(False)
-        dlg.show()
-        
-        self._filter_dialog = dlg
-
-    def _filter_selected(self, widget, data=None):
-        if widget.get_active():
-            self._filter = data
-            self.reload_log()
-
-    def _view_menu(self):
-        menu = gtk.Menu()
-
-        button = gtk.CheckMenuItem("Show Ids")
-        button.connect("toggled", self.toggle_view_column,
-                'rev-column-visible')
-        button.set_active(True)
-        button.set_draw_as_radio(True)
-        menu.append(button)
-        button = gtk.CheckMenuItem("Show Tags")
-        button.connect("toggled", self.toggle_view_column,
-                'tags-column-visible')
-        button.set_active(True)
-        button.set_draw_as_radio(True)
-        menu.append(button)
-        button = gtk.CheckMenuItem("Show Date")
-        button.connect("toggled", self.toggle_view_column,
-                'date-column-visible')
-        button.set_active(True)
-        button.set_draw_as_radio(True)
-        menu.append(button)
-        menu.show_all()
-        return menu
-
-    def _filter_menu(self):
-        menu = gtk.Menu()
-        
-        button = gtk.RadioMenuItem(None, "Show All Revisions")
-        button.set_active(True)
-        button.connect("toggled", self._filter_selected, 'all')
-        menu.append(button)
-        
-        button = gtk.RadioMenuItem(button, "Show Tagged Revisions")
-        button.connect("toggled", self._filter_selected, 'tagged')
-        menu.append(button)
-       
-        button = gtk.RadioMenuItem(button, "Show Parent Revisions")
-        button.connect("toggled", self._filter_selected, 'parents')
-        menu.append(button)
-       
-        button = gtk.RadioMenuItem(button, "Show Head Revisions")
-        button.connect("toggled", self._filter_selected, 'heads')
-        menu.append(button)
-       
-        button = gtk.RadioMenuItem(button, "Show Only Merge Revisions")
-        button.connect("toggled", self._filter_selected, 'only_merges')
-        menu.append(button)
-       
-        button = gtk.RadioMenuItem(button, "Show Non-Merge Revisions")
-        button.connect("toggled", self._filter_selected, 'no_merges')
-        menu.append(button)
-       
-        self.custombutton = gtk.RadioMenuItem(button, "Custom Filter")
-        self.custombutton.set_sensitive(False)
-        menu.append(self.custombutton)
-       
-        menu.show_all()
-        return menu
-
-    def open_with_file(self, file):
-        '''Call this before display() to open with file history'''
-        self.opts['filehist'] = file
-
-    def prepare_display(self):
-        '''Called at end of display() method'''
-        self._last_rev = None
-        self._filter = "all"
-        self.currow = None
-        self.curfile = None
-        self.opts['rev'] = [] # This option is dangerous - used directly by hg
-        self.opts['revs'] = None
-        os.chdir(self.repo.root)  # paths relative to repo root do not work otherwise
-
-        if 'filehist' in self.opts:
-            self.custombutton.set_active(True)
-            self.graphview.refresh(True, None, self.opts)
-            del self.opts['filehist']
-        elif 'revrange' in self.opts:
-            self.custombutton.set_active(True)
-            self.graphview.refresh(True, None, self.opts)
-        elif self.pats == [self.repo.root] or self.pats == ['']:
-            self.pats = []
-            self.reload_log()
-        elif self.pats:
-            self.custombutton.set_active(True)
-            self.graphview.refresh(False, self.pats, self.opts)
-        else:
-            self.reload_log()
-
-    def save_settings(self):
-        settings = GDialog.save_settings(self)
-        settings['glog'] = (self._vpaned.get_position(),
-                self._hpaned.get_position())
-        return settings
-
-    def load_settings(self, settings):
-        '''Called at beginning of display() method'''
-        limit_opt = self.repo.ui.config('tortoisehg', 'graphlimit', '500')
-        if limit_opt:
-            try:
-                limit = int(limit_opt)
-            except ValueError:
-                limit = 0
-            if limit <= 0:
-                limit = None
-        else:
-            limit = None
-
-        # Allocate TreeView instance to use internally
-        self.limit = limit
-        self.stbar = gtklib.StatusBar()
-        self.graphview = TreeView(self.repo, limit, self.stbar)
-
-        # Allocate ChangeSet instance to use internally
-        self.changeview = ChangeSet(self.ui, self.repo, self.cwd, [],
-                self.opts, False, self.stbar)
-        self.changeview.display(False)
-        self.changeview.glog_parent = self
-
-        GDialog.load_settings(self, settings)
-        if settings:
-            set = settings['glog']
-            if type(set) == int:
-                self._setting_vpos = set
-                self._setting_hpos = -1
-            else:
-                (self._setting_vpos, self._setting_hpos) = set
-        else:
-            self._setting_vpos = -1
-            self._setting_hpos = -1
-
-    def reload_log(self, filteropts={}):
-        """Send refresh event to treeview object"""
-        os.chdir(self.repo.root)  # paths relative to repo root do not work otherwise
-        self.nextbutton.set_sensitive(True)
-        self.allbutton.set_sensitive(True)
-        self.opts['rev'] = []
-        self.opts['revs'] = None
-        self.opts['no_merges'] = False
-        self.opts['only_merges'] = False
-        self.opts['revrange'] = filteropts.get('revrange', None)
-        self.opts['date'] = filteropts.get('date', None)
-        self.opts['keyword'] = filteropts.get('keyword', [])
-        revs = []
-        if filteropts:
-            branch = filteropts.get('branch', None)
-            if 'revrange' in filteropts or 'branch' in filteropts:
-                self.graphview.refresh(True, branch, self.opts)
-            else:
-                filter = filteropts.get('pats', [])
-                self.graphview.refresh(False, filter, self.opts)
-        elif self._filter == "all":
-            self.graphview.refresh(True, None, self.opts)
-        elif self._filter == "only_merges":
-            self.opts['only_merges'] = True
-            self.graphview.refresh(False, [], self.opts)
-        elif self._filter == "no_merges":
-            self.opts['no_merges'] = True
-            self.graphview.refresh(False, [], self.opts)
-        elif self._filter == "tagged":
-            tagged = []
-            for t, r in self.repo.tagslist():
-                hr = hex(r)
-                if hr not in tagged:
-                    tagged.insert(0, hr)
-            self.opts['revs'] = tagged
-            self.graphview.refresh(False, [], self.opts)
-        elif self._filter == "parents":
-            repo_parents = [x.rev() for x in self.repo.changectx(None).parents()]
-            self.opts['revs'] = [str(x) for x in repo_parents]
-            self.graphview.refresh(False, [], self.opts)
-        elif self._filter == "heads":
-            heads = [self.repo.changelog.rev(x) for x in self.repo.heads()]
-            self.opts['revs'] = [str(x) for x in heads]
-            self.graphview.refresh(False, [], self.opts)
-
-    def tree_context_menu(self):
-        _menu = gtk.Menu()
-        _menu.append(create_menu('di_splay', self._show_status))
-        _menu.append(create_menu('_checkout', self._checkout))
-        self._cmenu_merge = create_menu('_merge with', self._merge)
-        _menu.append(self._cmenu_merge)
-        _menu.append(create_menu('_export patch', self._export_patch))
-        _menu.append(create_menu('e_mail patch', self._email_patch))
-        _menu.append(create_menu('add/remove _tag', self._add_tag))
-        _menu.append(create_menu('backout revision', self._backout_rev))
-        
-        # need mq extension for strip command
-        extensions.loadall(self.ui)
-        extensions.load(self.ui, 'mq', None)
-        _menu.append(create_menu('strip revision', self._strip_rev))
-        
-        _menu.show_all()
-        return _menu
- 
-    def tree_diff_context_menu(self):
-        _menu = gtk.Menu()
-        _menu.append(create_menu('_diff with selected', self._diff_revs))
-        _menu.append(create_menu('visual diff with selected',
-                self._vdiff_selected))
-        _menu.show_all()
-        return _menu
- 
-    def get_body(self):
-        self._filter_dialog = None
-        self._menu = self.tree_context_menu()
-        self._menu2 = self.tree_diff_context_menu()
-
-        self.tree_frame = gtk.Frame()
-        self.tree_frame.set_shadow_type(gtk.SHADOW_ETCHED_IN)
-
-        # PyGtk 2.6 and below did not automatically register types
-        if gobject.pygtk_version < (2, 8, 0): 
-            gobject.type_register(TreeView)
-
-        self.tree = self.graphview.treeview
-        self.graphview.connect('revision-selected', self.selection_changed)
-        self.graphview.connect('revisions-loaded', self.revisions_loaded)
-
-        #self.tree.connect('button-release-event', self._tree_button_release)
-        self.tree.connect('button-press-event', self._tree_button_press)
-        #self.tree.connect('popup-menu', self._tree_popup_menu)
-        self.tree.connect('row-activated', self._tree_row_act)
-        #self.tree.modify_font(pango.FontDescription(self.fontlist))
-        
-        hbox = gtk.HBox()
-        hbox.pack_start(self.graphview, True, True, 0)
-        vbox = gtk.VBox()
-        self.colmenu = gtk.MenuToolButton('')
-        self.colmenu.set_menu(self._view_menu())
-        # A MenuToolButton has two parts; a Button and a ToggleButton
-        # we want to see the togglebutton, but not the button
-        b = self.colmenu.child.get_children()[0]
-        b.unmap()
-        b.set_sensitive(False)
-        self.nextbutton = gtk.ToolButton(gtk.STOCK_GO_DOWN)
-        self.nextbutton.connect('clicked', self._more_clicked)
-        self.allbutton = gtk.ToolButton(gtk.STOCK_GOTO_BOTTOM)
-        self.allbutton.connect('clicked', self._load_all_clicked)
-        vbox.pack_start(self.colmenu, False, False)
-        vbox.pack_start(gtk.Label(''), True, True) # expanding blank label
-        vbox.pack_start(self.nextbutton, False, False)
-        vbox.pack_start(self.allbutton, False, False)
-
-        self.nextbutton.set_tooltip(self.tooltips,
-                'show next %d revisions' % self.limit)
-        self.allbutton.set_tooltip(self.tooltips,
-                'show all remaining revisions')
-
-        hbox.pack_start(vbox, False, False, 0)
-        self.tree_frame.add(hbox)
-        self.tree_frame.show_all()
-
-        # Add ChangeSet instance to bottom half of vpane
-        self.changeview.graphview = self.graphview
-        self._hpaned = self.changeview.get_body()
-
-        self._vpaned = gtk.VPaned()
-        self._vpaned.pack1(self.tree_frame, True, False)
-        self._vpaned.pack2(self._hpaned)
-        self._vpaned.set_position(self._setting_vpos)
-        self._hpaned.set_position(self._setting_hpos)
-
-        vbox = gtk.VBox()
-        vbox.pack_start(self._vpaned, True, True)
-
-        # Append status bar
-        vbox.pack_start(gtk.HSeparator(), False, False)
-        vbox.pack_start(self.stbar, False, False)
-
-        return vbox
-
-    def _strip_rev(self, menuitem):
-        rev = self.currow[treemodel.REVID]
-        res = Confirm('Strip Revision(s)', [], self,
-                'Remove revision %d and all descendants?' % rev).run()
-        if res != gtk.RESPONSE_YES:
-            return
-        from hgcmd import CmdDialog
-        cmdline = ['hg', 'strip', str(rev)]
-        dlg = CmdDialog(cmdline)
-        dlg.show_all()
-        dlg.run()
-        dlg.hide()
-        self.repo.invalidate()
-        self.reload_log()
-
-    def _backout_rev(self, menuitem):
-        from backout import BackoutDialog
-        rev = self.currow[treemodel.REVID]
-        rev = short(self.repo.changelog.node(rev))
-        parents = [x.node() for x in self.repo.changectx(None).parents()]
-        dialog = BackoutDialog(self.repo.root, rev)
-        dialog.set_transient_for(self)
-        dialog.show_all()
-        dialog.set_notify_func(self.checkout_completed, parents)
-        dialog.present()
-        dialog.set_transient_for(None)
-
-    def _diff_revs(self, menuitem):
-        from status import GStatus
-        from gtools import cmdtable
-        rev0, rev1 = self._revs
-        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, [], statopts, False)
-        dialog.display()
-        return True
-
-    def _vdiff_selected(self, menuitem):
-        rev0, rev1 = self._revs
-        self.opts['rev'] = ["%s:%s" % (rev0, rev1)]
-        self._diff_file(None, '')
-
-    def _mark_rev(self, menuitem):
-        rev = self.currow[treemodel.REVID]
-        self.graphview.set_mark_rev(rev)
-
-    def _add_tag(self, menuitem):
-        from tagadd import TagAddDialog
-
-        rev = self.currow[treemodel.REVID]
-        parents = self.currow[treemodel.PARENTS]
-        
-        # save tag info for detecting new tags added
-        oldtags = self.repo.tagslist()
-        
-        dialog = TagAddDialog(self.repo.root, rev=str(rev))
-        dialog.set_transient_for(self)
-        dialog.show_all()
-        dialog.present()
-        dialog.set_transient_for(None)
-        
-        # refresh if new tags added
-        self.repo.invalidate()
-        newtags = self.repo.tagslist()
-        if newtags != oldtags:
-            self.reload_log()
-
-    def _show_status(self, menuitem):
-        rev = self.currow[treemodel.REVID]
-        statopts = {'rev' : [str(rev)] }
-        dialog = ChangeSet(self.ui, self.repo, self.cwd, [], statopts, False)
-        dialog.display()
-
-    def _export_patch(self, menuitem):
-        rev = self.currow[treemodel.REVID]
-        filename = "%s_rev%s.patch" % (os.path.basename(self.repo.root), rev)
-        fd = NativeSaveFileDialogWrapper(Title = "Save patch to",
-                                         InitialDir=self.repo.root,
-                                         FileName=filename)
-        result = fd.run()
-
-        if result:
-            # In case new export args are added in the future, merge the
-            # hg defaults
-            exportOpts= self.merge_opts(commands.table['^export'][1], ())
-            exportOpts['output'] = result
-            def dohgexport():
-                commands.export(self.ui,self.repo,str(rev),**exportOpts)
-            success, outtext = self._hg_call_wrapper("Export",dohgexport,False)
-
-    def _email_patch(self, menuitem):
-        from hgemail import EmailDialog
-        rev = self.currow[treemodel.REVID]
-        dlg = EmailDialog(self.repo.root, ['--rev', str(rev)])
-        dlg.set_transient_for(self)
-        dlg.show_all()
-        dlg.present()
-        dlg.set_transient_for(None)
-
-    def _checkout(self, menuitem):
-        rev = self.currow[treemodel.REVID]
-        parents = [x.node() for x in self.repo.changectx(None).parents()]
-        dialog = UpdateDialog(self.cwd, rev)
-        dialog.set_transient_for(self)
-        dialog.show_all()
-        dialog.set_notify_func(self.checkout_completed, parents)
-        dialog.present()
-        dialog.set_transient_for(None)
-
-    def checkout_completed(self, oldparents):
-        newparents = [x.node() for x in self.repo.changectx(None).parents()]
-        if not oldparents == newparents:
-            self.reload_log()
-
-    def _merge(self, menuitem):
-        rev = self.currow[treemodel.REVID]
-        parents = [x.node() for x in self.repo.changectx(None).parents()]
-        node = short(self.repo.changelog.node(rev))
-        dialog = MergeDialog(self.repo.root, self.cwd, node)
-        dialog.set_transient_for(self)
-        dialog.show_all()
-        dialog.set_notify_func(self.merge_completed, parents)
-        dialog.present()
-        dialog.set_transient_for(None)
-
-    def merge_completed(self, oldparents):
-        newparents = [x.node() for x in self.repo.changectx(None).parents()]
-        if not oldparents == newparents:
-            self.reload_log()
-
-    def selection_changed(self, treeview):
-        self.currow = self.graphview.get_revision()
-        rev = self.currow[treemodel.REVID]
-        if rev != self._last_rev:
-            self._last_rev = rev
-            self.changeview.opts['rev'] = [str(rev)]
-            self.changeview.load_details(rev)
-        return False
-
-    def _refresh_clicked(self, toolbutton, data=None):
-        self.reload_log()
-        return True
-
-    def _tree_button_release(self, widget, event) :
-        if event.button == 3 and not (event.state & (gtk.gdk.SHIFT_MASK |
-            gtk.gdk.CONTROL_MASK)):
-            self._tree_popup_menu(widget, event.button, event.time)
-        return False
-
-    def _tree_button_press(self, widget, event):
-        if event.button == 3 and not (event.state & (gtk.gdk.SHIFT_MASK |
-            gtk.gdk.CONTROL_MASK)):
-            crow = widget.get_path_at_pos(int(event.x), int(event.y))[0]
-            (model, pathlist) = widget.get_selection().get_selected_rows()
-            if pathlist == []:
-                return False
-            srow = pathlist[0]
-            if srow == crow:
-                self._tree_popup_menu(widget, event.button, event.time)
-            else:
-                self._revs = (int(model[srow][treemodel.REVID]),
-                        int(model[crow][treemodel.REVID]))
-                self._tree_popup_menu_diff(widget, event.button, event.time)
-            return True
-        return False
-
-    def _tree_popup_menu(self, treeview, button=0, time=0) :
-        selrev = self.currow[treemodel.REVID]
-        
-        # disable/enable menus as required
-        parents = [self.repo.changelog.rev(x.node()) for x in
-                   self.repo.changectx(None).parents()]
-        can_merge = selrev not in parents and \
-                    len(self.repo.heads()) > 1 and \
-                    len(parents) < 2
-        self._cmenu_merge.set_sensitive(can_merge)
-
-        # display the context menu
-        self._menu.popup(None, None, None, button, time)
-        return True
-
-    def _tree_popup_menu_diff(self, treeview, button=0, time=0):        
-        # display the context menu
-        self._menu2.popup(None, None, None, button, time)
-        return True
-
-    def _tree_row_act(self, tree, path, column) :
-        """Default action is the first entry in the context menu
-        """
-        self._menu.get_children()[0].activate()
-        return True
-
-def run(root='', cwd='', files=[], **opts):
-    u = ui.ui()
-    u.updateopts(debug=False, traceback=False)
-    repo = hg.repository(u, path=root)
-
-    files = [util.canonpath(root, cwd, f) for f in files]
-
-    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 = GLog(u, repo, cwd, files, cmdoptions, True)
-
-    gtk.gdk.threads_init()
-    gtk.gdk.threads_enter()
-    dialog.display()
-    gtk.main()
-    gtk.gdk.threads_leave()
-
-if __name__ == "__main__":
-    import sys
-    opts = {}
-    opts['root'] = len(sys.argv) > 1 and sys.argv[1] or os.getcwd()
-    opts['files'] = [opts['root']]
-    run(**opts)
+#
+# history.py - Changelog dialog for TortoiseHg
+#
+# Copyright 2007 Brad Schick, brad at gmail . com
+# Copyright (C) 2007 TK Soh <teekaysoh@gmail.com>
+#
+
+import os
+import pygtk
+pygtk.require('2.0')
+import gtk
+import gobject
+import pango
+import StringIO
+
+from mercurial.node import *
+from mercurial import ui, hg, commands, extensions
+from gdialog import *
+from changeset import ChangeSet
+from logfilter import FilterDialog
+from update import UpdateDialog
+from merge import MergeDialog
+from vis import treemodel
+from vis.treeview import TreeView
+from hglib import toutf
+import gtklib
+
+def create_menu(label, callback):
+    menuitem = gtk.MenuItem(label, True)
+    menuitem.connect('activate', callback)
+    menuitem.set_border_width(1)
+    return menuitem
+
+class GLog(GDialog):
+    """GTK+ based dialog for displaying repository logs
+    """
+    def get_title(self):
+        return os.path.basename(self.repo.root) + ' log' 
+
+    def get_icon(self):
+        return 'menulog.ico'
+
+    def parse_opts(self):
+        # Disable quiet to get full log info
+        self.ui.quiet = False
+
+    def get_tbbuttons(self):
+        return [
+                self.make_toolbutton(gtk.STOCK_REFRESH,
+                    'Re_fresh',
+                    self._refresh_clicked,
+                    tip='Reload revision history'),
+                gtk.SeparatorToolItem(),
+                self.make_toolbutton(gtk.STOCK_INDEX,
+                    '_Filter',
+                    self._filter_clicked,
+                    menu=self._filter_menu(),
+                    tip='Filter revisions for display'),
+                gtk.SeparatorToolItem(),
+                self.make_toolbutton(gtk.STOCK_FIND,