contrib / gitview / gitviewon commit remote.c: "git-push frotz" should update what matches at the source. (1ed10b8)
   1#! /usr/bin/env python
   2
   3# This program is free software; you can redistribute it and/or modify
   4# it under the terms of the GNU General Public License as published by
   5# the Free Software Foundation; either version 2 of the License, or
   6# (at your option) any later version.
   7
   8""" gitview
   9GUI browser for git repository
  10This program is based on bzrk by Scott James Remnant <scott@ubuntu.com>
  11"""
  12__copyright__ = "Copyright (C) 2006 Hewlett-Packard Development Company, L.P."
  13__copyright__ = "Copyright (C) 2007 Aneesh Kumar K.V <aneesh.kumar@gmail.com"
  14__author__    = "Aneesh Kumar K.V <aneesh.kumar@gmail.com>"
  15
  16
  17import sys
  18import os
  19import gtk
  20import pygtk
  21import pango
  22import re
  23import time
  24import gobject
  25import cairo
  26import math
  27import string
  28import fcntl
  29
  30try:
  31    import gtksourceview
  32    have_gtksourceview = True
  33except ImportError:
  34    have_gtksourceview = False
  35    print "Running without gtksourceview module"
  36
  37re_ident = re.compile('(author|committer) (?P<ident>.*) (?P<epoch>\d+) (?P<tz>[+-]\d{4})')
  38
  39def list_to_string(args, skip):
  40        count = len(args)
  41        i = skip
  42        str_arg=" "
  43        while (i < count ):
  44                str_arg = str_arg + args[i]
  45                str_arg = str_arg + " "
  46                i = i+1
  47
  48        return str_arg
  49
  50def show_date(epoch, tz):
  51        secs = float(epoch)
  52        tzsecs = float(tz[1:3]) * 3600
  53        tzsecs += float(tz[3:5]) * 60
  54        if (tz[0] == "+"):
  55                secs += tzsecs
  56        else:
  57                secs -= tzsecs
  58
  59        return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(secs))
  60
  61
  62class CellRendererGraph(gtk.GenericCellRenderer):
  63        """Cell renderer for directed graph.
  64
  65        This module contains the implementation of a custom GtkCellRenderer that
  66        draws part of the directed graph based on the lines suggested by the code
  67        in graph.py.
  68
  69        Because we're shiny, we use Cairo to do this, and because we're naughty
  70        we cheat and draw over the bits of the TreeViewColumn that are supposed to
  71        just be for the background.
  72
  73        Properties:
  74        node              (column, colour, [ names ]) tuple to draw revision node,
  75        in_lines          (start, end, colour) tuple list to draw inward lines,
  76        out_lines         (start, end, colour) tuple list to draw outward lines.
  77        """
  78
  79        __gproperties__ = {
  80        "node":         ( gobject.TYPE_PYOBJECT, "node",
  81                          "revision node instruction",
  82                          gobject.PARAM_WRITABLE
  83                        ),
  84        "in-lines":     ( gobject.TYPE_PYOBJECT, "in-lines",
  85                          "instructions to draw lines into the cell",
  86                          gobject.PARAM_WRITABLE
  87                        ),
  88        "out-lines":    ( gobject.TYPE_PYOBJECT, "out-lines",
  89                          "instructions to draw lines out of the cell",
  90                          gobject.PARAM_WRITABLE
  91                        ),
  92        }
  93
  94        def do_set_property(self, property, value):
  95                """Set properties from GObject properties."""
  96                if property.name == "node":
  97                        self.node = value
  98                elif property.name == "in-lines":
  99                        self.in_lines = value
 100                elif property.name == "out-lines":
 101                        self.out_lines = value
 102                else:
 103                        raise AttributeError, "no such property: '%s'" % property.name
 104
 105        def box_size(self, widget):
 106                """Calculate box size based on widget's font.
 107
 108                Cache this as it's probably expensive to get.  It ensures that we
 109                draw the graph at least as large as the text.
 110                """
 111                try:
 112                        return self._box_size
 113                except AttributeError:
 114                        pango_ctx = widget.get_pango_context()
 115                        font_desc = widget.get_style().font_desc
 116                        metrics = pango_ctx.get_metrics(font_desc)
 117
 118                        ascent = pango.PIXELS(metrics.get_ascent())
 119                        descent = pango.PIXELS(metrics.get_descent())
 120
 121                        self._box_size = ascent + descent + 6
 122                        return self._box_size
 123
 124        def set_colour(self, ctx, colour, bg, fg):
 125                """Set the context source colour.
 126
 127                Picks a distinct colour based on an internal wheel; the bg
 128                parameter provides the value that should be assigned to the 'zero'
 129                colours and the fg parameter provides the multiplier that should be
 130                applied to the foreground colours.
 131                """
 132                colours = [
 133                    ( 1.0, 0.0, 0.0 ),
 134                    ( 1.0, 1.0, 0.0 ),
 135                    ( 0.0, 1.0, 0.0 ),
 136                    ( 0.0, 1.0, 1.0 ),
 137                    ( 0.0, 0.0, 1.0 ),
 138                    ( 1.0, 0.0, 1.0 ),
 139                    ]
 140
 141                colour %= len(colours)
 142                red   = (colours[colour][0] * fg) or bg
 143                green = (colours[colour][1] * fg) or bg
 144                blue  = (colours[colour][2] * fg) or bg
 145
 146                ctx.set_source_rgb(red, green, blue)
 147
 148        def on_get_size(self, widget, cell_area):
 149                """Return the size we need for this cell.
 150
 151                Each cell is drawn individually and is only as wide as it needs
 152                to be, we let the TreeViewColumn take care of making them all
 153                line up.
 154                """
 155                box_size = self.box_size(widget)
 156
 157                cols = self.node[0]
 158                for start, end, colour in self.in_lines + self.out_lines:
 159                        cols = int(max(cols, start, end))
 160
 161                (column, colour, names) = self.node
 162                names_len = 0
 163                if (len(names) != 0):
 164                        for item in names:
 165                                names_len += len(item)
 166
 167                width = box_size * (cols + 1 ) + names_len
 168                height = box_size
 169
 170                # FIXME I have no idea how to use cell_area properly
 171                return (0, 0, width, height)
 172
 173        def on_render(self, window, widget, bg_area, cell_area, exp_area, flags):
 174                """Render an individual cell.
 175
 176                Draws the cell contents using cairo, taking care to clip what we
 177                do to within the background area so we don't draw over other cells.
 178                Note that we're a bit naughty there and should really be drawing
 179                in the cell_area (or even the exposed area), but we explicitly don't
 180                want any gutter.
 181
 182                We try and be a little clever, if the line we need to draw is going
 183                to cross other columns we actually draw it as in the .---' style
 184                instead of a pure diagonal ... this reduces confusion by an
 185                incredible amount.
 186                """
 187                ctx = window.cairo_create()
 188                ctx.rectangle(bg_area.x, bg_area.y, bg_area.width, bg_area.height)
 189                ctx.clip()
 190
 191                box_size = self.box_size(widget)
 192
 193                ctx.set_line_width(box_size / 8)
 194                ctx.set_line_cap(cairo.LINE_CAP_SQUARE)
 195
 196                # Draw lines into the cell
 197                for start, end, colour in self.in_lines:
 198                        ctx.move_to(cell_area.x + box_size * start + box_size / 2,
 199                                        bg_area.y - bg_area.height / 2)
 200
 201                        if start - end > 1:
 202                                ctx.line_to(cell_area.x + box_size * start, bg_area.y)
 203                                ctx.line_to(cell_area.x + box_size * end + box_size, bg_area.y)
 204                        elif start - end < -1:
 205                                ctx.line_to(cell_area.x + box_size * start + box_size,
 206                                                bg_area.y)
 207                                ctx.line_to(cell_area.x + box_size * end, bg_area.y)
 208
 209                        ctx.line_to(cell_area.x + box_size * end + box_size / 2,
 210                                        bg_area.y + bg_area.height / 2)
 211
 212                        self.set_colour(ctx, colour, 0.0, 0.65)
 213                        ctx.stroke()
 214
 215                # Draw lines out of the cell
 216                for start, end, colour in self.out_lines:
 217                        ctx.move_to(cell_area.x + box_size * start + box_size / 2,
 218                                        bg_area.y + bg_area.height / 2)
 219
 220                        if start - end > 1:
 221                                ctx.line_to(cell_area.x + box_size * start,
 222                                                bg_area.y + bg_area.height)
 223                                ctx.line_to(cell_area.x + box_size * end + box_size,
 224                                                bg_area.y + bg_area.height)
 225                        elif start - end < -1:
 226                                ctx.line_to(cell_area.x + box_size * start + box_size,
 227                                                bg_area.y + bg_area.height)
 228                                ctx.line_to(cell_area.x + box_size * end,
 229                                                bg_area.y + bg_area.height)
 230
 231                        ctx.line_to(cell_area.x + box_size * end + box_size / 2,
 232                                        bg_area.y + bg_area.height / 2 + bg_area.height)
 233
 234                        self.set_colour(ctx, colour, 0.0, 0.65)
 235                        ctx.stroke()
 236
 237                # Draw the revision node in the right column
 238                (column, colour, names) = self.node
 239                ctx.arc(cell_area.x + box_size * column + box_size / 2,
 240                                cell_area.y + cell_area.height / 2,
 241                                box_size / 4, 0, 2 * math.pi)
 242
 243
 244                self.set_colour(ctx, colour, 0.0, 0.5)
 245                ctx.stroke_preserve()
 246
 247                self.set_colour(ctx, colour, 0.5, 1.0)
 248                ctx.fill_preserve()
 249
 250                if (len(names) != 0):
 251                        name = " "
 252                        for item in names:
 253                                name = name + item + " "
 254
 255                        ctx.set_font_size(13)
 256                        if (flags & 1):
 257                                self.set_colour(ctx, colour, 0.5, 1.0)
 258                        else:
 259                                self.set_colour(ctx, colour, 0.0, 0.5)
 260                        ctx.show_text(name)
 261
 262class Commit(object):
 263        """ This represent a commit object obtained after parsing the git-rev-list
 264        output """
 265
 266        __slots__ = ['children_sha1', 'message', 'author', 'date', 'committer',
 267                                 'commit_date', 'commit_sha1', 'parent_sha1']
 268
 269        children_sha1 = {}
 270
 271        def __init__(self, commit_lines):
 272                self.message            = ""
 273                self.author             = ""
 274                self.date               = ""
 275                self.committer          = ""
 276                self.commit_date        = ""
 277                self.commit_sha1        = ""
 278                self.parent_sha1        = [ ]
 279                self.parse_commit(commit_lines)
 280
 281
 282        def parse_commit(self, commit_lines):
 283
 284                # First line is the sha1 lines
 285                line = string.strip(commit_lines[0])
 286                sha1 = re.split(" ", line)
 287                self.commit_sha1 = sha1[0]
 288                self.parent_sha1 = sha1[1:]
 289
 290                #build the child list
 291                for parent_id in self.parent_sha1:
 292                        try:
 293                                Commit.children_sha1[parent_id].append(self.commit_sha1)
 294                        except KeyError:
 295                                Commit.children_sha1[parent_id] = [self.commit_sha1]
 296
 297                # IF we don't have parent
 298                if (len(self.parent_sha1) == 0):
 299                        self.parent_sha1 = [0]
 300
 301                for line in commit_lines[1:]:
 302                        m = re.match("^ ", line)
 303                        if (m != None):
 304                                # First line of the commit message used for short log
 305                                if self.message == "":
 306                                        self.message = string.strip(line)
 307                                continue
 308
 309                        m = re.match("tree", line)
 310                        if (m != None):
 311                                continue
 312
 313                        m = re.match("parent", line)
 314                        if (m != None):
 315                                continue
 316
 317                        m = re_ident.match(line)
 318                        if (m != None):
 319                                date = show_date(m.group('epoch'), m.group('tz'))
 320                                if m.group(1) == "author":
 321                                        self.author = m.group('ident')
 322                                        self.date = date
 323                                elif m.group(1) == "committer":
 324                                        self.committer = m.group('ident')
 325                                        self.commit_date = date
 326
 327                                continue
 328
 329        def get_message(self, with_diff=0):
 330                if (with_diff == 1):
 331                        message = self.diff_tree()
 332                else:
 333                        fp = os.popen("git cat-file commit " + self.commit_sha1)
 334                        message = fp.read()
 335                        fp.close()
 336
 337                return message
 338
 339        def diff_tree(self):
 340                fp = os.popen("git diff-tree --pretty --cc  -v -p --always " +  self.commit_sha1)
 341                diff = fp.read()
 342                fp.close()
 343                return diff
 344
 345class AnnotateWindow(object):
 346        """Annotate window.
 347        This object represents and manages a single window containing the
 348        annotate information of the file
 349        """
 350
 351        def __init__(self):
 352                self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
 353                self.window.set_border_width(0)
 354                self.window.set_title("Git repository browser annotation window")
 355
 356                # Use two thirds of the screen by default
 357                screen = self.window.get_screen()
 358                monitor = screen.get_monitor_geometry(0)
 359                width = int(monitor.width * 0.66)
 360                height = int(monitor.height * 0.66)
 361                self.window.set_default_size(width, height)
 362
 363        def add_file_data(self, filename, commit_sha1, line_num):
 364                fp = os.popen("git cat-file blob " + commit_sha1 +":"+filename)
 365                i = 1;
 366                for line in fp.readlines():
 367                        line = string.rstrip(line)
 368                        self.model.append(None, ["HEAD", filename, line, i])
 369                        i = i+1
 370                fp.close()
 371
 372                # now set the cursor position
 373                self.treeview.set_cursor(line_num-1)
 374                self.treeview.grab_focus()
 375
 376        def _treeview_cursor_cb(self, *args):
 377                """Callback for when the treeview cursor changes."""
 378                (path, col) = self.treeview.get_cursor()
 379                commit_sha1 = self.model[path][0]
 380                commit_msg = ""
 381                fp = os.popen("git cat-file commit " + commit_sha1)
 382                for line in fp.readlines():
 383                        commit_msg =  commit_msg + line
 384                fp.close()
 385
 386                self.commit_buffer.set_text(commit_msg)
 387
 388        def _treeview_row_activated(self, *args):
 389                """Callback for when the treeview row gets selected."""
 390                (path, col) = self.treeview.get_cursor()
 391                commit_sha1 = self.model[path][0]
 392                filename    = self.model[path][1]
 393                line_num    = self.model[path][3]
 394
 395                window = AnnotateWindow();
 396                fp = os.popen("git rev-parse "+ commit_sha1 + "~1")
 397                commit_sha1 = string.strip(fp.readline())
 398                fp.close()
 399                window.annotate(filename, commit_sha1, line_num)
 400
 401        def data_ready(self, source, condition):
 402                while (1):
 403                        try :
 404                                buffer = source.read(8192)
 405                        except:
 406                                # resource temporary not available
 407                                return True
 408
 409                        if (len(buffer) == 0):
 410                                gobject.source_remove(self.io_watch_tag)
 411                                source.close()
 412                                return False
 413
 414                        for buff in buffer.split("\n"):
 415                                annotate_line = re.compile('^([0-9a-f]{40}) (.+) (.+) (.+)$')
 416                                m = annotate_line.match(buff)
 417                                if not m:
 418                                        annotate_line = re.compile('^(filename) (.+)$')
 419                                        m = annotate_line.match(buff)
 420                                        if not m:
 421                                                continue
 422                                        filename = m.group(2)
 423                                else:
 424                                        self.commit_sha1 = m.group(1)
 425                                        self.source_line = int(m.group(2))
 426                                        self.result_line = int(m.group(3))
 427                                        self.count          = int(m.group(4))
 428                                        #set the details only when we have the file name
 429                                        continue
 430
 431                                while (self.count > 0):
 432                                        # set at result_line + count-1 the sha1 as commit_sha1
 433                                        self.count = self.count - 1
 434                                        iter = self.model.iter_nth_child(None, self.result_line + self.count-1)
 435                                        self.model.set(iter, 0, self.commit_sha1, 1, filename, 3, self.source_line)
 436
 437
 438        def annotate(self, filename, commit_sha1, line_num):
 439                # verify the commit_sha1 specified has this filename
 440
 441                fp = os.popen("git ls-tree "+ commit_sha1 + " -- " + filename)
 442                line = string.strip(fp.readline())
 443                if line == '':
 444                        # pop up the message the file is not there as a part of the commit
 445                        fp.close()
 446                        dialog = gtk.MessageDialog(parent=None, flags=0,
 447                                        type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_CLOSE,
 448                                        message_format=None)
 449                        dialog.set_markup("The file %s is not present in the parent commit %s" % (filename, commit_sha1))
 450                        dialog.run()
 451                        dialog.destroy()
 452                        return
 453
 454                fp.close()
 455
 456                vpan = gtk.VPaned();
 457                self.window.add(vpan);
 458                vpan.show()
 459
 460                scrollwin = gtk.ScrolledWindow()
 461                scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 462                scrollwin.set_shadow_type(gtk.SHADOW_IN)
 463                vpan.pack1(scrollwin, True, True);
 464                scrollwin.show()
 465
 466                self.model = gtk.TreeStore(str, str, str, int)
 467                self.treeview = gtk.TreeView(self.model)
 468                self.treeview.set_rules_hint(True)
 469                self.treeview.set_search_column(0)
 470                self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
 471                self.treeview.connect("row-activated", self._treeview_row_activated)
 472                scrollwin.add(self.treeview)
 473                self.treeview.show()
 474
 475                cell = gtk.CellRendererText()
 476                cell.set_property("width-chars", 10)
 477                cell.set_property("ellipsize", pango.ELLIPSIZE_END)
 478                column = gtk.TreeViewColumn("Commit")
 479                column.set_resizable(True)
 480                column.pack_start(cell, expand=True)
 481                column.add_attribute(cell, "text", 0)
 482                self.treeview.append_column(column)
 483
 484                cell = gtk.CellRendererText()
 485                cell.set_property("width-chars", 20)
 486                cell.set_property("ellipsize", pango.ELLIPSIZE_END)
 487                column = gtk.TreeViewColumn("File Name")
 488                column.set_resizable(True)
 489                column.pack_start(cell, expand=True)
 490                column.add_attribute(cell, "text", 1)
 491                self.treeview.append_column(column)
 492
 493                cell = gtk.CellRendererText()
 494                cell.set_property("width-chars", 20)
 495                cell.set_property("ellipsize", pango.ELLIPSIZE_END)
 496                column = gtk.TreeViewColumn("Data")
 497                column.set_resizable(True)
 498                column.pack_start(cell, expand=True)
 499                column.add_attribute(cell, "text", 2)
 500                self.treeview.append_column(column)
 501
 502                # The commit message window
 503                scrollwin = gtk.ScrolledWindow()
 504                scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 505                scrollwin.set_shadow_type(gtk.SHADOW_IN)
 506                vpan.pack2(scrollwin, True, True);
 507                scrollwin.show()
 508
 509                commit_text = gtk.TextView()
 510                self.commit_buffer = gtk.TextBuffer()
 511                commit_text.set_buffer(self.commit_buffer)
 512                scrollwin.add(commit_text)
 513                commit_text.show()
 514
 515                self.window.show()
 516
 517                self.add_file_data(filename, commit_sha1, line_num)
 518
 519                fp = os.popen("git blame --incremental -- " + filename + " " + commit_sha1)
 520                flags = fcntl.fcntl(fp.fileno(), fcntl.F_GETFL)
 521                fcntl.fcntl(fp.fileno(), fcntl.F_SETFL, flags | os.O_NONBLOCK)
 522                self.io_watch_tag = gobject.io_add_watch(fp, gobject.IO_IN, self.data_ready)
 523
 524
 525class DiffWindow(object):
 526        """Diff window.
 527        This object represents and manages a single window containing the
 528        differences between two revisions on a branch.
 529        """
 530
 531        def __init__(self):
 532                self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
 533                self.window.set_border_width(0)
 534                self.window.set_title("Git repository browser diff window")
 535
 536                # Use two thirds of the screen by default
 537                screen = self.window.get_screen()
 538                monitor = screen.get_monitor_geometry(0)
 539                width = int(monitor.width * 0.66)
 540                height = int(monitor.height * 0.66)
 541                self.window.set_default_size(width, height)
 542
 543
 544                self.construct()
 545
 546        def construct(self):
 547                """Construct the window contents."""
 548                vbox = gtk.VBox()
 549                self.window.add(vbox)
 550                vbox.show()
 551
 552                menu_bar = gtk.MenuBar()
 553                save_menu = gtk.ImageMenuItem(gtk.STOCK_SAVE)
 554                save_menu.connect("activate", self.save_menu_response, "save")
 555                save_menu.show()
 556                menu_bar.append(save_menu)
 557                vbox.pack_start(menu_bar, expand=False, fill=True)
 558                menu_bar.show()
 559
 560                hpan = gtk.HPaned()
 561
 562                scrollwin = gtk.ScrolledWindow()
 563                scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 564                scrollwin.set_shadow_type(gtk.SHADOW_IN)
 565                hpan.pack1(scrollwin, True, True)
 566                scrollwin.show()
 567
 568                if have_gtksourceview:
 569                        self.buffer = gtksourceview.SourceBuffer()
 570                        slm = gtksourceview.SourceLanguagesManager()
 571                        gsl = slm.get_language_from_mime_type("text/x-patch")
 572                        self.buffer.set_highlight(True)
 573                        self.buffer.set_language(gsl)
 574                        sourceview = gtksourceview.SourceView(self.buffer)
 575                else:
 576                        self.buffer = gtk.TextBuffer()
 577                        sourceview = gtk.TextView(self.buffer)
 578
 579
 580                sourceview.set_editable(False)
 581                sourceview.modify_font(pango.FontDescription("Monospace"))
 582                scrollwin.add(sourceview)
 583                sourceview.show()
 584
 585                # The file hierarchy: a scrollable treeview
 586                scrollwin = gtk.ScrolledWindow()
 587                scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 588                scrollwin.set_shadow_type(gtk.SHADOW_IN)
 589                scrollwin.set_size_request(20, -1)
 590                hpan.pack2(scrollwin, True, True)
 591                scrollwin.show()
 592
 593                self.model = gtk.TreeStore(str, str, str)
 594                self.treeview = gtk.TreeView(self.model)
 595                self.treeview.set_search_column(1)
 596                self.treeview.connect("cursor-changed", self._treeview_clicked)
 597                scrollwin.add(self.treeview)
 598                self.treeview.show()
 599
 600                cell = gtk.CellRendererText()
 601                cell.set_property("width-chars", 20)
 602                column = gtk.TreeViewColumn("Select to annotate")
 603                column.pack_start(cell, expand=True)
 604                column.add_attribute(cell, "text", 0)
 605                self.treeview.append_column(column)
 606
 607                vbox.pack_start(hpan, expand=True, fill=True)
 608                hpan.show()
 609
 610        def _treeview_clicked(self, *args):
 611                """Callback for when the treeview cursor changes."""
 612                (path, col) = self.treeview.get_cursor()
 613                specific_file = self.model[path][1]
 614                commit_sha1 =  self.model[path][2]
 615                if specific_file ==  None :
 616                        return
 617                elif specific_file ==  "" :
 618                        specific_file =  None
 619
 620                window = AnnotateWindow();
 621                window.annotate(specific_file, commit_sha1, 1)
 622
 623
 624        def commit_files(self, commit_sha1, parent_sha1):
 625                self.model.clear()
 626                add  = self.model.append(None, [ "Added", None, None])
 627                dele = self.model.append(None, [ "Deleted", None, None])
 628                mod  = self.model.append(None, [ "Modified", None, None])
 629                diff_tree = re.compile('^(:.{6}) (.{6}) (.{40}) (.{40}) (A|D|M)\s(.+)$')
 630                fp = os.popen("git diff-tree -r --no-commit-id " + parent_sha1 + " " + commit_sha1)
 631                while 1:
 632                        line = string.strip(fp.readline())
 633                        if line == '':
 634                                break
 635                        m = diff_tree.match(line)
 636                        if not m:
 637                                continue
 638
 639                        attr = m.group(5)
 640                        filename = m.group(6)
 641                        if attr == "A":
 642                                self.model.append(add,  [filename, filename, commit_sha1])
 643                        elif attr == "D":
 644                                self.model.append(dele, [filename, filename, commit_sha1])
 645                        elif attr == "M":
 646                                self.model.append(mod,  [filename, filename, commit_sha1])
 647                fp.close()
 648
 649                self.treeview.expand_all()
 650
 651        def set_diff(self, commit_sha1, parent_sha1, encoding):
 652                """Set the differences showed by this window.
 653                Compares the two trees and populates the window with the
 654                differences.
 655                """
 656                # Diff with the first commit or the last commit shows nothing
 657                if (commit_sha1 == 0 or parent_sha1 == 0 ):
 658                        return
 659
 660                fp = os.popen("git diff-tree -p " + parent_sha1 + " " + commit_sha1)
 661                self.buffer.set_text(unicode(fp.read(), encoding).encode('utf-8'))
 662                fp.close()
 663                self.commit_files(commit_sha1, parent_sha1)
 664                self.window.show()
 665
 666        def save_menu_response(self, widget, string):
 667                dialog = gtk.FileChooserDialog("Save..", None, gtk.FILE_CHOOSER_ACTION_SAVE,
 668                                (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
 669                                        gtk.STOCK_SAVE, gtk.RESPONSE_OK))
 670                dialog.set_default_response(gtk.RESPONSE_OK)
 671                response = dialog.run()
 672                if response == gtk.RESPONSE_OK:
 673                        patch_buffer = self.buffer.get_text(self.buffer.get_start_iter(),
 674                                        self.buffer.get_end_iter())
 675                        fp = open(dialog.get_filename(), "w")
 676                        fp.write(patch_buffer)
 677                        fp.close()
 678                dialog.destroy()
 679
 680class GitView(object):
 681        """ This is the main class
 682        """
 683        version = "0.9"
 684
 685        def __init__(self, with_diff=0):
 686                self.with_diff = with_diff
 687                self.window =   gtk.Window(gtk.WINDOW_TOPLEVEL)
 688                self.window.set_border_width(0)
 689                self.window.set_title("Git repository browser")
 690
 691                self.get_encoding()
 692                self.get_bt_sha1()
 693
 694                # Use three-quarters of the screen by default
 695                screen = self.window.get_screen()
 696                monitor = screen.get_monitor_geometry(0)
 697                width = int(monitor.width * 0.75)
 698                height = int(monitor.height * 0.75)
 699                self.window.set_default_size(width, height)
 700
 701                # FIXME AndyFitz!
 702                icon = self.window.render_icon(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)
 703                self.window.set_icon(icon)
 704
 705                self.accel_group = gtk.AccelGroup()
 706                self.window.add_accel_group(self.accel_group)
 707                self.accel_group.connect_group(0xffc2, 0, gtk.ACCEL_LOCKED, self.refresh);
 708                self.accel_group.connect_group(0xffc1, 0, gtk.ACCEL_LOCKED, self.maximize);
 709                self.accel_group.connect_group(0xffc8, 0, gtk.ACCEL_LOCKED, self.fullscreen);
 710                self.accel_group.connect_group(0xffc9, 0, gtk.ACCEL_LOCKED, self.unfullscreen);
 711
 712                self.window.add(self.construct())
 713
 714        def refresh(self, widget, event=None, *arguments, **keywords):
 715                self.get_encoding()
 716                self.get_bt_sha1()
 717                Commit.children_sha1 = {}
 718                self.set_branch(sys.argv[without_diff:])
 719                self.window.show()
 720                return True
 721
 722        def maximize(self, widget, event=None, *arguments, **keywords):
 723                self.window.maximize()
 724                return True
 725
 726        def fullscreen(self, widget, event=None, *arguments, **keywords):
 727                self.window.fullscreen()
 728                return True
 729
 730        def unfullscreen(self, widget, event=None, *arguments, **keywords):
 731                self.window.unfullscreen()
 732                return True
 733
 734        def get_bt_sha1(self):
 735                """ Update the bt_sha1 dictionary with the
 736                respective sha1 details """
 737
 738                self.bt_sha1 = { }
 739                ls_remote = re.compile('^(.{40})\trefs/([^^]+)(?:\\^(..))?$');
 740                fp = os.popen('git ls-remote "${GIT_DIR-.git}"')
 741                while 1:
 742                        line = string.strip(fp.readline())
 743                        if line == '':
 744                                break
 745                        m = ls_remote.match(line)
 746                        if not m:
 747                                continue
 748                        (sha1, name) = (m.group(1), m.group(2))
 749                        if not self.bt_sha1.has_key(sha1):
 750                                self.bt_sha1[sha1] = []
 751                        self.bt_sha1[sha1].append(name)
 752                fp.close()
 753
 754        def get_encoding(self):
 755                fp = os.popen("git config --get i18n.commitencoding")
 756                self.encoding=string.strip(fp.readline())
 757                fp.close()
 758                if (self.encoding == ""):
 759                        self.encoding = "utf-8"
 760
 761
 762        def construct(self):
 763                """Construct the window contents."""
 764                vbox = gtk.VBox()
 765                paned = gtk.VPaned()
 766                paned.pack1(self.construct_top(), resize=False, shrink=True)
 767                paned.pack2(self.construct_bottom(), resize=False, shrink=True)
 768                menu_bar = gtk.MenuBar()
 769                menu_bar.set_pack_direction(gtk.PACK_DIRECTION_RTL)
 770                help_menu = gtk.MenuItem("Help")
 771                menu = gtk.Menu()
 772                about_menu = gtk.MenuItem("About")
 773                menu.append(about_menu)
 774                about_menu.connect("activate", self.about_menu_response, "about")
 775                about_menu.show()
 776                help_menu.set_submenu(menu)
 777                help_menu.show()
 778                menu_bar.append(help_menu)
 779                menu_bar.show()
 780                vbox.pack_start(menu_bar, expand=False, fill=True)
 781                vbox.pack_start(paned, expand=True, fill=True)
 782                paned.show()
 783                vbox.show()
 784                return vbox
 785
 786
 787        def construct_top(self):
 788                """Construct the top-half of the window."""
 789                vbox = gtk.VBox(spacing=6)
 790                vbox.set_border_width(12)
 791                vbox.show()
 792
 793
 794                scrollwin = gtk.ScrolledWindow()
 795                scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 796                scrollwin.set_shadow_type(gtk.SHADOW_IN)
 797                vbox.pack_start(scrollwin, expand=True, fill=True)
 798                scrollwin.show()
 799
 800                self.treeview = gtk.TreeView()
 801                self.treeview.set_rules_hint(True)
 802                self.treeview.set_search_column(4)
 803                self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
 804                scrollwin.add(self.treeview)
 805                self.treeview.show()
 806
 807                cell = CellRendererGraph()
 808                column = gtk.TreeViewColumn()
 809                column.set_resizable(True)
 810                column.pack_start(cell, expand=True)
 811                column.add_attribute(cell, "node", 1)
 812                column.add_attribute(cell, "in-lines", 2)
 813                column.add_attribute(cell, "out-lines", 3)
 814                self.treeview.append_column(column)
 815
 816                cell = gtk.CellRendererText()
 817                cell.set_property("width-chars", 65)
 818                cell.set_property("ellipsize", pango.ELLIPSIZE_END)
 819                column = gtk.TreeViewColumn("Message")
 820                column.set_resizable(True)
 821                column.pack_start(cell, expand=True)
 822                column.add_attribute(cell, "text", 4)
 823                self.treeview.append_column(column)
 824
 825                cell = gtk.CellRendererText()
 826                cell.set_property("width-chars", 40)
 827                cell.set_property("ellipsize", pango.ELLIPSIZE_END)
 828                column = gtk.TreeViewColumn("Author")
 829                column.set_resizable(True)
 830                column.pack_start(cell, expand=True)
 831                column.add_attribute(cell, "text", 5)
 832                self.treeview.append_column(column)
 833
 834                cell = gtk.CellRendererText()
 835                cell.set_property("ellipsize", pango.ELLIPSIZE_END)
 836                column = gtk.TreeViewColumn("Date")
 837                column.set_resizable(True)
 838                column.pack_start(cell, expand=True)
 839                column.add_attribute(cell, "text", 6)
 840                self.treeview.append_column(column)
 841
 842                return vbox
 843
 844        def about_menu_response(self, widget, string):
 845                dialog = gtk.AboutDialog()
 846                dialog.set_name("Gitview")
 847                dialog.set_version(GitView.version)
 848                dialog.set_authors(["Aneesh Kumar K.V <aneesh.kumar@gmail.com>"])
 849                dialog.set_website("http://www.kernel.org/pub/software/scm/git/")
 850                dialog.set_copyright("Use and distribute under the terms of the GNU General Public License")
 851                dialog.set_wrap_license(True)
 852                dialog.run()
 853                dialog.destroy()
 854
 855
 856        def construct_bottom(self):
 857                """Construct the bottom half of the window."""
 858                vbox = gtk.VBox(False, spacing=6)
 859                vbox.set_border_width(12)
 860                (width, height) = self.window.get_size()
 861                vbox.set_size_request(width, int(height / 2.5))
 862                vbox.show()
 863
 864                self.table = gtk.Table(rows=4, columns=4)
 865                self.table.set_row_spacings(6)
 866                self.table.set_col_spacings(6)
 867                vbox.pack_start(self.table, expand=False, fill=True)
 868                self.table.show()
 869
 870                align = gtk.Alignment(0.0, 0.5)
 871                label = gtk.Label()
 872                label.set_markup("<b>Revision:</b>")
 873                align.add(label)
 874                self.table.attach(align, 0, 1, 0, 1, gtk.FILL, gtk.FILL)
 875                label.show()
 876                align.show()
 877
 878                align = gtk.Alignment(0.0, 0.5)
 879                self.revid_label = gtk.Label()
 880                self.revid_label.set_selectable(True)
 881                align.add(self.revid_label)
 882                self.table.attach(align, 1, 2, 0, 1, gtk.EXPAND | gtk.FILL, gtk.FILL)
 883                self.revid_label.show()
 884                align.show()
 885
 886                align = gtk.Alignment(0.0, 0.5)
 887                label = gtk.Label()
 888                label.set_markup("<b>Committer:</b>")
 889                align.add(label)
 890                self.table.attach(align, 0, 1, 1, 2, gtk.FILL, gtk.FILL)
 891                label.show()
 892                align.show()
 893
 894                align = gtk.Alignment(0.0, 0.5)
 895                self.committer_label = gtk.Label()
 896                self.committer_label.set_selectable(True)
 897                align.add(self.committer_label)
 898                self.table.attach(align, 1, 2, 1, 2, gtk.EXPAND | gtk.FILL, gtk.FILL)
 899                self.committer_label.show()
 900                align.show()
 901
 902                align = gtk.Alignment(0.0, 0.5)
 903                label = gtk.Label()
 904                label.set_markup("<b>Timestamp:</b>")
 905                align.add(label)
 906                self.table.attach(align, 0, 1, 2, 3, gtk.FILL, gtk.FILL)
 907                label.show()
 908                align.show()
 909
 910                align = gtk.Alignment(0.0, 0.5)
 911                self.timestamp_label = gtk.Label()
 912                self.timestamp_label.set_selectable(True)
 913                align.add(self.timestamp_label)
 914                self.table.attach(align, 1, 2, 2, 3, gtk.EXPAND | gtk.FILL, gtk.FILL)
 915                self.timestamp_label.show()
 916                align.show()
 917
 918                align = gtk.Alignment(0.0, 0.5)
 919                label = gtk.Label()
 920                label.set_markup("<b>Parents:</b>")
 921                align.add(label)
 922                self.table.attach(align, 0, 1, 3, 4, gtk.FILL, gtk.FILL)
 923                label.show()
 924                align.show()
 925                self.parents_widgets = []
 926
 927                align = gtk.Alignment(0.0, 0.5)
 928                label = gtk.Label()
 929                label.set_markup("<b>Children:</b>")
 930                align.add(label)
 931                self.table.attach(align, 2, 3, 3, 4, gtk.FILL, gtk.FILL)
 932                label.show()
 933                align.show()
 934                self.children_widgets = []
 935
 936                scrollwin = gtk.ScrolledWindow()
 937                scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 938                scrollwin.set_shadow_type(gtk.SHADOW_IN)
 939                vbox.pack_start(scrollwin, expand=True, fill=True)
 940                scrollwin.show()
 941
 942                if have_gtksourceview:
 943                        self.message_buffer = gtksourceview.SourceBuffer()
 944                        slm = gtksourceview.SourceLanguagesManager()
 945                        gsl = slm.get_language_from_mime_type("text/x-patch")
 946                        self.message_buffer.set_highlight(True)
 947                        self.message_buffer.set_language(gsl)
 948                        sourceview = gtksourceview.SourceView(self.message_buffer)
 949                else:
 950                        self.message_buffer = gtk.TextBuffer()
 951                        sourceview = gtk.TextView(self.message_buffer)
 952
 953                sourceview.set_editable(False)
 954                sourceview.modify_font(pango.FontDescription("Monospace"))
 955                scrollwin.add(sourceview)
 956                sourceview.show()
 957
 958                return vbox
 959
 960        def _treeview_cursor_cb(self, *args):
 961                """Callback for when the treeview cursor changes."""
 962                (path, col) = self.treeview.get_cursor()
 963                commit = self.model[path][0]
 964
 965                if commit.committer is not None:
 966                        committer = commit.committer
 967                        timestamp = commit.commit_date
 968                        message   =  commit.get_message(self.with_diff)
 969                        revid_label = commit.commit_sha1
 970                else:
 971                        committer = ""
 972                        timestamp = ""
 973                        message = ""
 974                        revid_label = ""
 975
 976                self.revid_label.set_text(revid_label)
 977                self.committer_label.set_text(committer)
 978                self.timestamp_label.set_text(timestamp)
 979                self.message_buffer.set_text(unicode(message, self.encoding).encode('utf-8'))
 980
 981                for widget in self.parents_widgets:
 982                        self.table.remove(widget)
 983
 984                self.parents_widgets = []
 985                self.table.resize(4 + len(commit.parent_sha1) - 1, 4)
 986                for idx, parent_id in enumerate(commit.parent_sha1):
 987                        self.table.set_row_spacing(idx + 3, 0)
 988
 989                        align = gtk.Alignment(0.0, 0.0)
 990                        self.parents_widgets.append(align)
 991                        self.table.attach(align, 1, 2, idx + 3, idx + 4,
 992                                        gtk.EXPAND | gtk.FILL, gtk.FILL)
 993                        align.show()
 994
 995                        hbox = gtk.HBox(False, 0)
 996                        align.add(hbox)
 997                        hbox.show()
 998
 999                        label = gtk.Label(parent_id)
1000                        label.set_selectable(True)
1001                        hbox.pack_start(label, expand=False, fill=True)
1002                        label.show()
1003
1004                        image = gtk.Image()
1005                        image.set_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU)
1006                        image.show()
1007
1008                        button = gtk.Button()
1009                        button.add(image)
1010                        button.set_relief(gtk.RELIEF_NONE)
1011                        button.connect("clicked", self._go_clicked_cb, parent_id)
1012                        hbox.pack_start(button, expand=False, fill=True)
1013                        button.show()
1014
1015                        image = gtk.Image()
1016                        image.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU)
1017                        image.show()
1018
1019                        button = gtk.Button()
1020                        button.add(image)
1021                        button.set_relief(gtk.RELIEF_NONE)
1022                        button.set_sensitive(True)
1023                        button.connect("clicked", self._show_clicked_cb,
1024                                        commit.commit_sha1, parent_id, self.encoding)
1025                        hbox.pack_start(button, expand=False, fill=True)
1026                        button.show()
1027
1028                # Populate with child details
1029                for widget in self.children_widgets:
1030                        self.table.remove(widget)
1031
1032                self.children_widgets = []
1033                try:
1034                        child_sha1 = Commit.children_sha1[commit.commit_sha1]
1035                except KeyError:
1036                        # We don't have child
1037                        child_sha1 = [ 0 ]
1038
1039                if ( len(child_sha1) > len(commit.parent_sha1)):
1040                        self.table.resize(4 + len(child_sha1) - 1, 4)
1041
1042                for idx, child_id in enumerate(child_sha1):
1043                        self.table.set_row_spacing(idx + 3, 0)
1044
1045                        align = gtk.Alignment(0.0, 0.0)
1046                        self.children_widgets.append(align)
1047                        self.table.attach(align, 3, 4, idx + 3, idx + 4,
1048                                        gtk.EXPAND | gtk.FILL, gtk.FILL)
1049                        align.show()
1050
1051                        hbox = gtk.HBox(False, 0)
1052                        align.add(hbox)
1053                        hbox.show()
1054
1055                        label = gtk.Label(child_id)
1056                        label.set_selectable(True)
1057                        hbox.pack_start(label, expand=False, fill=True)
1058                        label.show()
1059
1060                        image = gtk.Image()
1061                        image.set_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU)
1062                        image.show()
1063
1064                        button = gtk.Button()
1065                        button.add(image)
1066                        button.set_relief(gtk.RELIEF_NONE)
1067                        button.connect("clicked", self._go_clicked_cb, child_id)
1068                        hbox.pack_start(button, expand=False, fill=True)
1069                        button.show()
1070
1071                        image = gtk.Image()
1072                        image.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU)
1073                        image.show()
1074
1075                        button = gtk.Button()
1076                        button.add(image)
1077                        button.set_relief(gtk.RELIEF_NONE)
1078                        button.set_sensitive(True)
1079                        button.connect("clicked", self._show_clicked_cb,
1080                                        child_id, commit.commit_sha1, self.encoding)
1081                        hbox.pack_start(button, expand=False, fill=True)
1082                        button.show()
1083
1084        def _destroy_cb(self, widget):
1085                """Callback for when a window we manage is destroyed."""
1086                self.quit()
1087
1088
1089        def quit(self):
1090                """Stop the GTK+ main loop."""
1091                gtk.main_quit()
1092
1093        def run(self, args):
1094                self.set_branch(args)
1095                self.window.connect("destroy", self._destroy_cb)
1096                self.window.show()
1097                gtk.main()
1098
1099        def set_branch(self, args):
1100                """Fill in different windows with info from the reposiroty"""
1101                fp = os.popen("git rev-parse --sq --default HEAD " + list_to_string(args, 1))
1102                git_rev_list_cmd = fp.read()
1103                fp.close()
1104                fp = os.popen("git rev-list  --header --topo-order --parents " + git_rev_list_cmd)
1105                self.update_window(fp)
1106
1107        def update_window(self, fp):
1108                commit_lines = []
1109
1110                self.model = gtk.ListStore(gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,
1111                                gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, str, str, str)
1112
1113                # used for cursor positioning
1114                self.index = {}
1115
1116                self.colours = {}
1117                self.nodepos = {}
1118                self.incomplete_line = {}
1119                self.commits = []
1120
1121                index = 0
1122                last_colour = 0
1123                last_nodepos = -1
1124                out_line = []
1125                input_line = fp.readline()
1126                while (input_line != ""):
1127                        # The commit header ends with '\0'
1128                        # This NULL is immediately followed by the sha1 of the
1129                        # next commit
1130                        if (input_line[0] != '\0'):
1131                                commit_lines.append(input_line)
1132                                input_line = fp.readline()
1133                                continue;
1134
1135                        commit = Commit(commit_lines)
1136                        if (commit != None ):
1137                                self.commits.append(commit)
1138
1139                        # Skip the '\0
1140                        commit_lines = []
1141                        commit_lines.append(input_line[1:])
1142                        input_line = fp.readline()
1143
1144                fp.close()
1145
1146                for commit in self.commits:
1147                        (out_line, last_colour, last_nodepos) = self.draw_graph(commit,
1148                                                                                index, out_line,
1149                                                                                last_colour,
1150                                                                                last_nodepos)
1151                        self.index[commit.commit_sha1] = index
1152                        index += 1
1153
1154                self.treeview.set_model(self.model)
1155                self.treeview.show()
1156
1157        def draw_graph(self, commit, index, out_line, last_colour, last_nodepos):
1158                in_line=[]
1159
1160                #   |   -> outline
1161                #   X
1162                #   |\  <- inline
1163
1164                # Reset nodepostion
1165                if (last_nodepos > 5):
1166                        last_nodepos = -1
1167
1168                # Add the incomplete lines of the last cell in this
1169                try:
1170                        colour = self.colours[commit.commit_sha1]
1171                except KeyError:
1172                        self.colours[commit.commit_sha1] = last_colour+1
1173                        last_colour = self.colours[commit.commit_sha1]
1174                        colour =   self.colours[commit.commit_sha1]
1175
1176                try:
1177                        node_pos = self.nodepos[commit.commit_sha1]
1178                except KeyError:
1179                        self.nodepos[commit.commit_sha1] = last_nodepos+1
1180                        last_nodepos = self.nodepos[commit.commit_sha1]
1181                        node_pos =  self.nodepos[commit.commit_sha1]
1182
1183                #The first parent always continue on the same line
1184                try:
1185                        # check we alreay have the value
1186                        tmp_node_pos = self.nodepos[commit.parent_sha1[0]]
1187                except KeyError:
1188                        self.colours[commit.parent_sha1[0]] = colour
1189                        self.nodepos[commit.parent_sha1[0]] = node_pos
1190
1191                for sha1 in self.incomplete_line.keys():
1192                        if (sha1 != commit.commit_sha1):
1193                                self.draw_incomplete_line(sha1, node_pos,
1194                                                out_line, in_line, index)
1195                        else:
1196                                del self.incomplete_line[sha1]
1197
1198
1199                for parent_id in commit.parent_sha1:
1200                        try:
1201                                tmp_node_pos = self.nodepos[parent_id]
1202                        except KeyError:
1203                                self.colours[parent_id] = last_colour+1
1204                                last_colour = self.colours[parent_id]
1205                                self.nodepos[parent_id] = last_nodepos+1
1206                                last_nodepos = self.nodepos[parent_id]
1207
1208                        in_line.append((node_pos, self.nodepos[parent_id],
1209                                                self.colours[parent_id]))
1210                        self.add_incomplete_line(parent_id)
1211
1212                try:
1213                        branch_tag = self.bt_sha1[commit.commit_sha1]
1214                except KeyError:
1215                        branch_tag = [ ]
1216
1217
1218                node = (node_pos, colour, branch_tag)
1219
1220                self.model.append([commit, node, out_line, in_line,
1221                                commit.message, commit.author, commit.date])
1222
1223                return (in_line, last_colour, last_nodepos)
1224
1225        def add_incomplete_line(self, sha1):
1226                try:
1227                        self.incomplete_line[sha1].append(self.nodepos[sha1])
1228                except KeyError:
1229                        self.incomplete_line[sha1] = [self.nodepos[sha1]]
1230
1231        def draw_incomplete_line(self, sha1, node_pos, out_line, in_line, index):
1232                for idx, pos in enumerate(self.incomplete_line[sha1]):
1233                        if(pos == node_pos):
1234                                #remove the straight line and add a slash
1235                                if ((pos, pos, self.colours[sha1]) in out_line):
1236                                        out_line.remove((pos, pos, self.colours[sha1]))
1237                                out_line.append((pos, pos+0.5, self.colours[sha1]))
1238                                self.incomplete_line[sha1][idx] = pos = pos+0.5
1239                        try:
1240                                next_commit = self.commits[index+1]
1241                                if (next_commit.commit_sha1 == sha1 and pos != int(pos)):
1242                                # join the line back to the node point
1243                                # This need to be done only if we modified it
1244                                        in_line.append((pos, pos-0.5, self.colours[sha1]))
1245                                        continue;
1246                        except IndexError:
1247                                pass
1248                        in_line.append((pos, pos, self.colours[sha1]))
1249
1250
1251        def _go_clicked_cb(self, widget, revid):
1252                """Callback for when the go button for a parent is clicked."""
1253                try:
1254                        self.treeview.set_cursor(self.index[revid])
1255                except KeyError:
1256                        dialog = gtk.MessageDialog(parent=None, flags=0,
1257                                        type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_CLOSE,
1258                                        message_format=None)
1259                        dialog.set_markup("Revision <b>%s</b> not present in the list" % revid)
1260                        # revid == 0 is the parent of the first commit
1261                        if (revid != 0 ):
1262                                dialog.format_secondary_text("Try running gitview without any options")
1263                        dialog.run()
1264                        dialog.destroy()
1265
1266                self.treeview.grab_focus()
1267
1268        def _show_clicked_cb(self, widget,  commit_sha1, parent_sha1, encoding):
1269                """Callback for when the show button for a parent is clicked."""
1270                window = DiffWindow()
1271                window.set_diff(commit_sha1, parent_sha1, encoding)
1272                self.treeview.grab_focus()
1273
1274without_diff = 0
1275if __name__ == "__main__":
1276
1277        if (len(sys.argv) > 1 ):
1278                if (sys.argv[1] == "--without-diff"):
1279                        without_diff = 1
1280
1281        view = GitView( without_diff != 1)
1282        view.run(sys.argv[without_diff:])