contrib / gitview / gitviewon commit Add clean.requireForce option, and add -f option to git-clean to override it (2122591)
   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:
 263        """ This represent a commit object obtained after parsing the git-rev-list
 264        output """
 265
 266        children_sha1 = {}
 267
 268        def __init__(self, commit_lines):
 269                self.message            = ""
 270                self.author             = ""
 271                self.date               = ""
 272                self.committer          = ""
 273                self.commit_date        = ""
 274                self.commit_sha1        = ""
 275                self.parent_sha1        = [ ]
 276                self.parse_commit(commit_lines)
 277
 278
 279        def parse_commit(self, commit_lines):
 280
 281                # First line is the sha1 lines
 282                line = string.strip(commit_lines[0])
 283                sha1 = re.split(" ", line)
 284                self.commit_sha1 = sha1[0]
 285                self.parent_sha1 = sha1[1:]
 286
 287                #build the child list
 288                for parent_id in self.parent_sha1:
 289                        try:
 290                                Commit.children_sha1[parent_id].append(self.commit_sha1)
 291                        except KeyError:
 292                                Commit.children_sha1[parent_id] = [self.commit_sha1]
 293
 294                # IF we don't have parent
 295                if (len(self.parent_sha1) == 0):
 296                        self.parent_sha1 = [0]
 297
 298                for line in commit_lines[1:]:
 299                        m = re.match("^ ", line)
 300                        if (m != None):
 301                                # First line of the commit message used for short log
 302                                if self.message == "":
 303                                        self.message = string.strip(line)
 304                                continue
 305
 306                        m = re.match("tree", line)
 307                        if (m != None):
 308                                continue
 309
 310                        m = re.match("parent", line)
 311                        if (m != None):
 312                                continue
 313
 314                        m = re_ident.match(line)
 315                        if (m != None):
 316                                date = show_date(m.group('epoch'), m.group('tz'))
 317                                if m.group(1) == "author":
 318                                        self.author = m.group('ident')
 319                                        self.date = date
 320                                elif m.group(1) == "committer":
 321                                        self.committer = m.group('ident')
 322                                        self.commit_date = date
 323
 324                                continue
 325
 326        def get_message(self, with_diff=0):
 327                if (with_diff == 1):
 328                        message = self.diff_tree()
 329                else:
 330                        fp = os.popen("git cat-file commit " + self.commit_sha1)
 331                        message = fp.read()
 332                        fp.close()
 333
 334                return message
 335
 336        def diff_tree(self):
 337                fp = os.popen("git diff-tree --pretty --cc  -v -p --always " +  self.commit_sha1)
 338                diff = fp.read()
 339                fp.close()
 340                return diff
 341
 342class AnnotateWindow:
 343        """Annotate window.
 344        This object represents and manages a single window containing the
 345        annotate information of the file
 346        """
 347
 348        def __init__(self):
 349                self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
 350                self.window.set_border_width(0)
 351                self.window.set_title("Git repository browser annotation window")
 352
 353                # Use two thirds of the screen by default
 354                screen = self.window.get_screen()
 355                monitor = screen.get_monitor_geometry(0)
 356                width = int(monitor.width * 0.66)
 357                height = int(monitor.height * 0.66)
 358                self.window.set_default_size(width, height)
 359
 360        def add_file_data(self, filename, commit_sha1, line_num):
 361                fp = os.popen("git cat-file blob " + commit_sha1 +":"+filename)
 362                i = 1;
 363                for line in fp.readlines():
 364                        line = string.rstrip(line)
 365                        self.model.append(None, ["HEAD", filename, line, i])
 366                        i = i+1
 367                fp.close()
 368
 369                # now set the cursor position
 370                self.treeview.set_cursor(line_num-1)
 371                self.treeview.grab_focus()
 372
 373        def _treeview_cursor_cb(self, *args):
 374                """Callback for when the treeview cursor changes."""
 375                (path, col) = self.treeview.get_cursor()
 376                commit_sha1 = self.model[path][0]
 377                commit_msg = ""
 378                fp = os.popen("git cat-file commit " + commit_sha1)
 379                for line in fp.readlines():
 380                        commit_msg =  commit_msg + line
 381                fp.close()
 382
 383                self.commit_buffer.set_text(commit_msg)
 384
 385        def _treeview_row_activated(self, *args):
 386                """Callback for when the treeview row gets selected."""
 387                (path, col) = self.treeview.get_cursor()
 388                commit_sha1 = self.model[path][0]
 389                filename    = self.model[path][1]
 390                line_num    = self.model[path][3]
 391
 392                window = AnnotateWindow();
 393                fp = os.popen("git rev-parse "+ commit_sha1 + "~1")
 394                commit_sha1 = string.strip(fp.readline())
 395                fp.close()
 396                window.annotate(filename, commit_sha1, line_num)
 397
 398        def data_ready(self, source, condition):
 399                while (1):
 400                        try :
 401                                buffer = source.read(8192)
 402                        except:
 403                                # resource temporary not available
 404                                return True
 405
 406                        if (len(buffer) == 0):
 407                                gobject.source_remove(self.io_watch_tag)
 408                                source.close()
 409                                return False
 410
 411                        for buff in buffer.split("\n"):
 412                                annotate_line = re.compile('^([0-9a-f]{40}) (.+) (.+) (.+)$')
 413                                m = annotate_line.match(buff)
 414                                if not m:
 415                                        annotate_line = re.compile('^(filename) (.+)$')
 416                                        m = annotate_line.match(buff)
 417                                        if not m:
 418                                                continue
 419                                        filename = m.group(2)
 420                                else:
 421                                        self.commit_sha1 = m.group(1)
 422                                        self.source_line = int(m.group(2))
 423                                        self.result_line = int(m.group(3))
 424                                        self.count          = int(m.group(4))
 425                                        #set the details only when we have the file name
 426                                        continue
 427
 428                                while (self.count > 0):
 429                                        # set at result_line + count-1 the sha1 as commit_sha1
 430                                        self.count = self.count - 1
 431                                        iter = self.model.iter_nth_child(None, self.result_line + self.count-1)
 432                                        self.model.set(iter, 0, self.commit_sha1, 1, filename, 3, self.source_line)
 433
 434
 435        def annotate(self, filename, commit_sha1, line_num):
 436                # verify the commit_sha1 specified has this filename
 437
 438                fp = os.popen("git ls-tree "+ commit_sha1 + " -- " + filename)
 439                line = string.strip(fp.readline())
 440                if line == '':
 441                        # pop up the message the file is not there as a part of the commit
 442                        fp.close()
 443                        dialog = gtk.MessageDialog(parent=None, flags=0,
 444                                        type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_CLOSE,
 445                                        message_format=None)
 446                        dialog.set_markup("The file %s is not present in the parent commit %s" % (filename, commit_sha1))
 447                        dialog.run()
 448                        dialog.destroy()
 449                        return
 450
 451                fp.close()
 452
 453                vpan = gtk.VPaned();
 454                self.window.add(vpan);
 455                vpan.show()
 456
 457                scrollwin = gtk.ScrolledWindow()
 458                scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 459                scrollwin.set_shadow_type(gtk.SHADOW_IN)
 460                vpan.pack1(scrollwin, True, True);
 461                scrollwin.show()
 462
 463                self.model = gtk.TreeStore(str, str, str, int)
 464                self.treeview = gtk.TreeView(self.model)
 465                self.treeview.set_rules_hint(True)
 466                self.treeview.set_search_column(0)
 467                self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
 468                self.treeview.connect("row-activated", self._treeview_row_activated)
 469                scrollwin.add(self.treeview)
 470                self.treeview.show()
 471
 472                cell = gtk.CellRendererText()
 473                cell.set_property("width-chars", 10)
 474                cell.set_property("ellipsize", pango.ELLIPSIZE_END)
 475                column = gtk.TreeViewColumn("Commit")
 476                column.set_resizable(True)
 477                column.pack_start(cell, expand=True)
 478                column.add_attribute(cell, "text", 0)
 479                self.treeview.append_column(column)
 480
 481                cell = gtk.CellRendererText()
 482                cell.set_property("width-chars", 20)
 483                cell.set_property("ellipsize", pango.ELLIPSIZE_END)
 484                column = gtk.TreeViewColumn("File Name")
 485                column.set_resizable(True)
 486                column.pack_start(cell, expand=True)
 487                column.add_attribute(cell, "text", 1)
 488                self.treeview.append_column(column)
 489
 490                cell = gtk.CellRendererText()
 491                cell.set_property("width-chars", 20)
 492                cell.set_property("ellipsize", pango.ELLIPSIZE_END)
 493                column = gtk.TreeViewColumn("Data")
 494                column.set_resizable(True)
 495                column.pack_start(cell, expand=True)
 496                column.add_attribute(cell, "text", 2)
 497                self.treeview.append_column(column)
 498
 499                # The commit message window
 500                scrollwin = gtk.ScrolledWindow()
 501                scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 502                scrollwin.set_shadow_type(gtk.SHADOW_IN)
 503                vpan.pack2(scrollwin, True, True);
 504                scrollwin.show()
 505
 506                commit_text = gtk.TextView()
 507                self.commit_buffer = gtk.TextBuffer()
 508                commit_text.set_buffer(self.commit_buffer)
 509                scrollwin.add(commit_text)
 510                commit_text.show()
 511
 512                self.window.show()
 513
 514                self.add_file_data(filename, commit_sha1, line_num)
 515
 516                fp = os.popen("git blame --incremental -- " + filename + " " + commit_sha1)
 517                flags = fcntl.fcntl(fp.fileno(), fcntl.F_GETFL)
 518                fcntl.fcntl(fp.fileno(), fcntl.F_SETFL, flags | os.O_NONBLOCK)
 519                self.io_watch_tag = gobject.io_add_watch(fp, gobject.IO_IN, self.data_ready)
 520
 521
 522class DiffWindow:
 523        """Diff window.
 524        This object represents and manages a single window containing the
 525        differences between two revisions on a branch.
 526        """
 527
 528        def __init__(self):
 529                self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
 530                self.window.set_border_width(0)
 531                self.window.set_title("Git repository browser diff window")
 532
 533                # Use two thirds of the screen by default
 534                screen = self.window.get_screen()
 535                monitor = screen.get_monitor_geometry(0)
 536                width = int(monitor.width * 0.66)
 537                height = int(monitor.height * 0.66)
 538                self.window.set_default_size(width, height)
 539
 540
 541                self.construct()
 542
 543        def construct(self):
 544                """Construct the window contents."""
 545                vbox = gtk.VBox()
 546                self.window.add(vbox)
 547                vbox.show()
 548
 549                menu_bar = gtk.MenuBar()
 550                save_menu = gtk.ImageMenuItem(gtk.STOCK_SAVE)
 551                save_menu.connect("activate", self.save_menu_response, "save")
 552                save_menu.show()
 553                menu_bar.append(save_menu)
 554                vbox.pack_start(menu_bar, expand=False, fill=True)
 555                menu_bar.show()
 556
 557                hpan = gtk.HPaned()
 558
 559                scrollwin = gtk.ScrolledWindow()
 560                scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 561                scrollwin.set_shadow_type(gtk.SHADOW_IN)
 562                hpan.pack1(scrollwin, True, True)
 563                scrollwin.show()
 564
 565                if have_gtksourceview:
 566                        self.buffer = gtksourceview.SourceBuffer()
 567                        slm = gtksourceview.SourceLanguagesManager()
 568                        gsl = slm.get_language_from_mime_type("text/x-patch")
 569                        self.buffer.set_highlight(True)
 570                        self.buffer.set_language(gsl)
 571                        sourceview = gtksourceview.SourceView(self.buffer)
 572                else:
 573                        self.buffer = gtk.TextBuffer()
 574                        sourceview = gtk.TextView(self.buffer)
 575
 576
 577                sourceview.set_editable(False)
 578                sourceview.modify_font(pango.FontDescription("Monospace"))
 579                scrollwin.add(sourceview)
 580                sourceview.show()
 581
 582                # The file hierarchy: a scrollable treeview
 583                scrollwin = gtk.ScrolledWindow()
 584                scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 585                scrollwin.set_shadow_type(gtk.SHADOW_IN)
 586                scrollwin.set_size_request(20, -1)
 587                hpan.pack2(scrollwin, True, True)
 588                scrollwin.show()
 589
 590                self.model = gtk.TreeStore(str, str, str)
 591                self.treeview = gtk.TreeView(self.model)
 592                self.treeview.set_search_column(1)
 593                self.treeview.connect("cursor-changed", self._treeview_clicked)
 594                scrollwin.add(self.treeview)
 595                self.treeview.show()
 596
 597                cell = gtk.CellRendererText()
 598                cell.set_property("width-chars", 20)
 599                column = gtk.TreeViewColumn("Select to annotate")
 600                column.pack_start(cell, expand=True)
 601                column.add_attribute(cell, "text", 0)
 602                self.treeview.append_column(column)
 603
 604                vbox.pack_start(hpan, expand=True, fill=True)
 605                hpan.show()
 606
 607        def _treeview_clicked(self, *args):
 608                """Callback for when the treeview cursor changes."""
 609                (path, col) = self.treeview.get_cursor()
 610                specific_file = self.model[path][1]
 611                commit_sha1 =  self.model[path][2]
 612                if specific_file ==  None :
 613                        return
 614                elif specific_file ==  "" :
 615                        specific_file =  None
 616
 617                window = AnnotateWindow();
 618                window.annotate(specific_file, commit_sha1, 1)
 619
 620
 621        def commit_files(self, commit_sha1, parent_sha1):
 622                self.model.clear()
 623                add  = self.model.append(None, [ "Added", None, None])
 624                dele = self.model.append(None, [ "Deleted", None, None])
 625                mod  = self.model.append(None, [ "Modified", None, None])
 626                diff_tree = re.compile('^(:.{6}) (.{6}) (.{40}) (.{40}) (A|D|M)\s(.+)$')
 627                fp = os.popen("git diff-tree -r --no-commit-id " + parent_sha1 + " " + commit_sha1)
 628                while 1:
 629                        line = string.strip(fp.readline())
 630                        if line == '':
 631                                break
 632                        m = diff_tree.match(line)
 633                        if not m:
 634                                continue
 635
 636                        attr = m.group(5)
 637                        filename = m.group(6)
 638                        if attr == "A":
 639                                self.model.append(add,  [filename, filename, commit_sha1])
 640                        elif attr == "D":
 641                                self.model.append(dele, [filename, filename, commit_sha1])
 642                        elif attr == "M":
 643                                self.model.append(mod,  [filename, filename, commit_sha1])
 644                fp.close()
 645
 646                self.treeview.expand_all()
 647
 648        def set_diff(self, commit_sha1, parent_sha1, encoding):
 649                """Set the differences showed by this window.
 650                Compares the two trees and populates the window with the
 651                differences.
 652                """
 653                # Diff with the first commit or the last commit shows nothing
 654                if (commit_sha1 == 0 or parent_sha1 == 0 ):
 655                        return
 656
 657                fp = os.popen("git diff-tree -p " + parent_sha1 + " " + commit_sha1)
 658                self.buffer.set_text(unicode(fp.read(), encoding).encode('utf-8'))
 659                fp.close()
 660                self.commit_files(commit_sha1, parent_sha1)
 661                self.window.show()
 662
 663        def save_menu_response(self, widget, string):
 664                dialog = gtk.FileChooserDialog("Save..", None, gtk.FILE_CHOOSER_ACTION_SAVE,
 665                                (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
 666                                        gtk.STOCK_SAVE, gtk.RESPONSE_OK))
 667                dialog.set_default_response(gtk.RESPONSE_OK)
 668                response = dialog.run()
 669                if response == gtk.RESPONSE_OK:
 670                        patch_buffer = self.buffer.get_text(self.buffer.get_start_iter(),
 671                                        self.buffer.get_end_iter())
 672                        fp = open(dialog.get_filename(), "w")
 673                        fp.write(patch_buffer)
 674                        fp.close()
 675                dialog.destroy()
 676
 677class GitView:
 678        """ This is the main class
 679        """
 680        version = "0.9"
 681
 682        def __init__(self, with_diff=0):
 683                self.with_diff = with_diff
 684                self.window =   gtk.Window(gtk.WINDOW_TOPLEVEL)
 685                self.window.set_border_width(0)
 686                self.window.set_title("Git repository browser")
 687
 688                self.get_encoding()
 689                self.get_bt_sha1()
 690
 691                # Use three-quarters of the screen by default
 692                screen = self.window.get_screen()
 693                monitor = screen.get_monitor_geometry(0)
 694                width = int(monitor.width * 0.75)
 695                height = int(monitor.height * 0.75)
 696                self.window.set_default_size(width, height)
 697
 698                # FIXME AndyFitz!
 699                icon = self.window.render_icon(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)
 700                self.window.set_icon(icon)
 701
 702                self.accel_group = gtk.AccelGroup()
 703                self.window.add_accel_group(self.accel_group)
 704                self.accel_group.connect_group(0xffc2, 0, gtk.ACCEL_LOCKED, self.refresh);
 705                self.accel_group.connect_group(0xffc1, 0, gtk.ACCEL_LOCKED, self.maximize);
 706                self.accel_group.connect_group(0xffc8, 0, gtk.ACCEL_LOCKED, self.fullscreen);
 707                self.accel_group.connect_group(0xffc9, 0, gtk.ACCEL_LOCKED, self.unfullscreen);
 708
 709                self.window.add(self.construct())
 710
 711        def refresh(self, widget, event=None, *arguments, **keywords):
 712                self.get_encoding()
 713                self.get_bt_sha1()
 714                Commit.children_sha1 = {}
 715                self.set_branch(sys.argv[without_diff:])
 716                self.window.show()
 717                return True
 718
 719        def maximize(self, widget, event=None, *arguments, **keywords):
 720                self.window.maximize()
 721                return True
 722
 723        def fullscreen(self, widget, event=None, *arguments, **keywords):
 724                self.window.fullscreen()
 725                return True
 726
 727        def unfullscreen(self, widget, event=None, *arguments, **keywords):
 728                self.window.unfullscreen()
 729                return True
 730
 731        def get_bt_sha1(self):
 732                """ Update the bt_sha1 dictionary with the
 733                respective sha1 details """
 734
 735                self.bt_sha1 = { }
 736                ls_remote = re.compile('^(.{40})\trefs/([^^]+)(?:\\^(..))?$');
 737                fp = os.popen('git ls-remote "${GIT_DIR-.git}"')
 738                while 1:
 739                        line = string.strip(fp.readline())
 740                        if line == '':
 741                                break
 742                        m = ls_remote.match(line)
 743                        if not m:
 744                                continue
 745                        (sha1, name) = (m.group(1), m.group(2))
 746                        if not self.bt_sha1.has_key(sha1):
 747                                self.bt_sha1[sha1] = []
 748                        self.bt_sha1[sha1].append(name)
 749                fp.close()
 750
 751        def get_encoding(self):
 752                fp = os.popen("git config --get i18n.commitencoding")
 753                self.encoding=string.strip(fp.readline())
 754                fp.close()
 755                if (self.encoding == ""):
 756                        self.encoding = "utf-8"
 757
 758
 759        def construct(self):
 760                """Construct the window contents."""
 761                vbox = gtk.VBox()
 762                paned = gtk.VPaned()
 763                paned.pack1(self.construct_top(), resize=False, shrink=True)
 764                paned.pack2(self.construct_bottom(), resize=False, shrink=True)
 765                menu_bar = gtk.MenuBar()
 766                menu_bar.set_pack_direction(gtk.PACK_DIRECTION_RTL)
 767                help_menu = gtk.MenuItem("Help")
 768                menu = gtk.Menu()
 769                about_menu = gtk.MenuItem("About")
 770                menu.append(about_menu)
 771                about_menu.connect("activate", self.about_menu_response, "about")
 772                about_menu.show()
 773                help_menu.set_submenu(menu)
 774                help_menu.show()
 775                menu_bar.append(help_menu)
 776                menu_bar.show()
 777                vbox.pack_start(menu_bar, expand=False, fill=True)
 778                vbox.pack_start(paned, expand=True, fill=True)
 779                paned.show()
 780                vbox.show()
 781                return vbox
 782
 783
 784        def construct_top(self):
 785                """Construct the top-half of the window."""
 786                vbox = gtk.VBox(spacing=6)
 787                vbox.set_border_width(12)
 788                vbox.show()
 789
 790
 791                scrollwin = gtk.ScrolledWindow()
 792                scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 793                scrollwin.set_shadow_type(gtk.SHADOW_IN)
 794                vbox.pack_start(scrollwin, expand=True, fill=True)
 795                scrollwin.show()
 796
 797                self.treeview = gtk.TreeView()
 798                self.treeview.set_rules_hint(True)
 799                self.treeview.set_search_column(4)
 800                self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
 801                scrollwin.add(self.treeview)
 802                self.treeview.show()
 803
 804                cell = CellRendererGraph()
 805                column = gtk.TreeViewColumn()
 806                column.set_resizable(True)
 807                column.pack_start(cell, expand=True)
 808                column.add_attribute(cell, "node", 1)
 809                column.add_attribute(cell, "in-lines", 2)
 810                column.add_attribute(cell, "out-lines", 3)
 811                self.treeview.append_column(column)
 812
 813                cell = gtk.CellRendererText()
 814                cell.set_property("width-chars", 65)
 815                cell.set_property("ellipsize", pango.ELLIPSIZE_END)
 816                column = gtk.TreeViewColumn("Message")
 817                column.set_resizable(True)
 818                column.pack_start(cell, expand=True)
 819                column.add_attribute(cell, "text", 4)
 820                self.treeview.append_column(column)
 821
 822                cell = gtk.CellRendererText()
 823                cell.set_property("width-chars", 40)
 824                cell.set_property("ellipsize", pango.ELLIPSIZE_END)
 825                column = gtk.TreeViewColumn("Author")
 826                column.set_resizable(True)
 827                column.pack_start(cell, expand=True)
 828                column.add_attribute(cell, "text", 5)
 829                self.treeview.append_column(column)
 830
 831                cell = gtk.CellRendererText()
 832                cell.set_property("ellipsize", pango.ELLIPSIZE_END)
 833                column = gtk.TreeViewColumn("Date")
 834                column.set_resizable(True)
 835                column.pack_start(cell, expand=True)
 836                column.add_attribute(cell, "text", 6)
 837                self.treeview.append_column(column)
 838
 839                return vbox
 840
 841        def about_menu_response(self, widget, string):
 842                dialog = gtk.AboutDialog()
 843                dialog.set_name("Gitview")
 844                dialog.set_version(GitView.version)
 845                dialog.set_authors(["Aneesh Kumar K.V <aneesh.kumar@gmail.com>"])
 846                dialog.set_website("http://www.kernel.org/pub/software/scm/git/")
 847                dialog.set_copyright("Use and distribute under the terms of the GNU General Public License")
 848                dialog.set_wrap_license(True)
 849                dialog.run()
 850                dialog.destroy()
 851
 852
 853        def construct_bottom(self):
 854                """Construct the bottom half of the window."""
 855                vbox = gtk.VBox(False, spacing=6)
 856                vbox.set_border_width(12)
 857                (width, height) = self.window.get_size()
 858                vbox.set_size_request(width, int(height / 2.5))
 859                vbox.show()
 860
 861                self.table = gtk.Table(rows=4, columns=4)
 862                self.table.set_row_spacings(6)
 863                self.table.set_col_spacings(6)
 864                vbox.pack_start(self.table, expand=False, fill=True)
 865                self.table.show()
 866
 867                align = gtk.Alignment(0.0, 0.5)
 868                label = gtk.Label()
 869                label.set_markup("<b>Revision:</b>")
 870                align.add(label)
 871                self.table.attach(align, 0, 1, 0, 1, gtk.FILL, gtk.FILL)
 872                label.show()
 873                align.show()
 874
 875                align = gtk.Alignment(0.0, 0.5)
 876                self.revid_label = gtk.Label()
 877                self.revid_label.set_selectable(True)
 878                align.add(self.revid_label)
 879                self.table.attach(align, 1, 2, 0, 1, gtk.EXPAND | gtk.FILL, gtk.FILL)
 880                self.revid_label.show()
 881                align.show()
 882
 883                align = gtk.Alignment(0.0, 0.5)
 884                label = gtk.Label()
 885                label.set_markup("<b>Committer:</b>")
 886                align.add(label)
 887                self.table.attach(align, 0, 1, 1, 2, gtk.FILL, gtk.FILL)
 888                label.show()
 889                align.show()
 890
 891                align = gtk.Alignment(0.0, 0.5)
 892                self.committer_label = gtk.Label()
 893                self.committer_label.set_selectable(True)
 894                align.add(self.committer_label)
 895                self.table.attach(align, 1, 2, 1, 2, gtk.EXPAND | gtk.FILL, gtk.FILL)
 896                self.committer_label.show()
 897                align.show()
 898
 899                align = gtk.Alignment(0.0, 0.5)
 900                label = gtk.Label()
 901                label.set_markup("<b>Timestamp:</b>")
 902                align.add(label)
 903                self.table.attach(align, 0, 1, 2, 3, gtk.FILL, gtk.FILL)
 904                label.show()
 905                align.show()
 906
 907                align = gtk.Alignment(0.0, 0.5)
 908                self.timestamp_label = gtk.Label()
 909                self.timestamp_label.set_selectable(True)
 910                align.add(self.timestamp_label)
 911                self.table.attach(align, 1, 2, 2, 3, gtk.EXPAND | gtk.FILL, gtk.FILL)
 912                self.timestamp_label.show()
 913                align.show()
 914
 915                align = gtk.Alignment(0.0, 0.5)
 916                label = gtk.Label()
 917                label.set_markup("<b>Parents:</b>")
 918                align.add(label)
 919                self.table.attach(align, 0, 1, 3, 4, gtk.FILL, gtk.FILL)
 920                label.show()
 921                align.show()
 922                self.parents_widgets = []
 923
 924                align = gtk.Alignment(0.0, 0.5)
 925                label = gtk.Label()
 926                label.set_markup("<b>Children:</b>")
 927                align.add(label)
 928                self.table.attach(align, 2, 3, 3, 4, gtk.FILL, gtk.FILL)
 929                label.show()
 930                align.show()
 931                self.children_widgets = []
 932
 933                scrollwin = gtk.ScrolledWindow()
 934                scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 935                scrollwin.set_shadow_type(gtk.SHADOW_IN)
 936                vbox.pack_start(scrollwin, expand=True, fill=True)
 937                scrollwin.show()
 938
 939                if have_gtksourceview:
 940                        self.message_buffer = gtksourceview.SourceBuffer()
 941                        slm = gtksourceview.SourceLanguagesManager()
 942                        gsl = slm.get_language_from_mime_type("text/x-patch")
 943                        self.message_buffer.set_highlight(True)
 944                        self.message_buffer.set_language(gsl)
 945                        sourceview = gtksourceview.SourceView(self.message_buffer)
 946                else:
 947                        self.message_buffer = gtk.TextBuffer()
 948                        sourceview = gtk.TextView(self.message_buffer)
 949
 950                sourceview.set_editable(False)
 951                sourceview.modify_font(pango.FontDescription("Monospace"))
 952                scrollwin.add(sourceview)
 953                sourceview.show()
 954
 955                return vbox
 956
 957        def _treeview_cursor_cb(self, *args):
 958                """Callback for when the treeview cursor changes."""
 959                (path, col) = self.treeview.get_cursor()
 960                commit = self.model[path][0]
 961
 962                if commit.committer is not None:
 963                        committer = commit.committer
 964                        timestamp = commit.commit_date
 965                        message   =  commit.get_message(self.with_diff)
 966                        revid_label = commit.commit_sha1
 967                else:
 968                        committer = ""
 969                        timestamp = ""
 970                        message = ""
 971                        revid_label = ""
 972
 973                self.revid_label.set_text(revid_label)
 974                self.committer_label.set_text(committer)
 975                self.timestamp_label.set_text(timestamp)
 976                self.message_buffer.set_text(unicode(message, self.encoding).encode('utf-8'))
 977
 978                for widget in self.parents_widgets:
 979                        self.table.remove(widget)
 980
 981                self.parents_widgets = []
 982                self.table.resize(4 + len(commit.parent_sha1) - 1, 4)
 983                for idx, parent_id in enumerate(commit.parent_sha1):
 984                        self.table.set_row_spacing(idx + 3, 0)
 985
 986                        align = gtk.Alignment(0.0, 0.0)
 987                        self.parents_widgets.append(align)
 988                        self.table.attach(align, 1, 2, idx + 3, idx + 4,
 989                                        gtk.EXPAND | gtk.FILL, gtk.FILL)
 990                        align.show()
 991
 992                        hbox = gtk.HBox(False, 0)
 993                        align.add(hbox)
 994                        hbox.show()
 995
 996                        label = gtk.Label(parent_id)
 997                        label.set_selectable(True)
 998                        hbox.pack_start(label, expand=False, fill=True)
 999                        label.show()
1000
1001                        image = gtk.Image()
1002                        image.set_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU)
1003                        image.show()
1004
1005                        button = gtk.Button()
1006                        button.add(image)
1007                        button.set_relief(gtk.RELIEF_NONE)
1008                        button.connect("clicked", self._go_clicked_cb, parent_id)
1009                        hbox.pack_start(button, expand=False, fill=True)
1010                        button.show()
1011
1012                        image = gtk.Image()
1013                        image.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU)
1014                        image.show()
1015
1016                        button = gtk.Button()
1017                        button.add(image)
1018                        button.set_relief(gtk.RELIEF_NONE)
1019                        button.set_sensitive(True)
1020                        button.connect("clicked", self._show_clicked_cb,
1021                                        commit.commit_sha1, parent_id, self.encoding)
1022                        hbox.pack_start(button, expand=False, fill=True)
1023                        button.show()
1024
1025                # Populate with child details
1026                for widget in self.children_widgets:
1027                        self.table.remove(widget)
1028
1029                self.children_widgets = []
1030                try:
1031                        child_sha1 = Commit.children_sha1[commit.commit_sha1]
1032                except KeyError:
1033                        # We don't have child
1034                        child_sha1 = [ 0 ]
1035
1036                if ( len(child_sha1) > len(commit.parent_sha1)):
1037                        self.table.resize(4 + len(child_sha1) - 1, 4)
1038
1039                for idx, child_id in enumerate(child_sha1):
1040                        self.table.set_row_spacing(idx + 3, 0)
1041
1042                        align = gtk.Alignment(0.0, 0.0)
1043                        self.children_widgets.append(align)
1044                        self.table.attach(align, 3, 4, idx + 3, idx + 4,
1045                                        gtk.EXPAND | gtk.FILL, gtk.FILL)
1046                        align.show()
1047
1048                        hbox = gtk.HBox(False, 0)
1049                        align.add(hbox)
1050                        hbox.show()
1051
1052                        label = gtk.Label(child_id)
1053                        label.set_selectable(True)
1054                        hbox.pack_start(label, expand=False, fill=True)
1055                        label.show()
1056
1057                        image = gtk.Image()
1058                        image.set_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU)
1059                        image.show()
1060
1061                        button = gtk.Button()
1062                        button.add(image)
1063                        button.set_relief(gtk.RELIEF_NONE)
1064                        button.connect("clicked", self._go_clicked_cb, child_id)
1065                        hbox.pack_start(button, expand=False, fill=True)
1066                        button.show()
1067
1068                        image = gtk.Image()
1069                        image.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU)
1070                        image.show()
1071
1072                        button = gtk.Button()
1073                        button.add(image)
1074                        button.set_relief(gtk.RELIEF_NONE)
1075                        button.set_sensitive(True)
1076                        button.connect("clicked", self._show_clicked_cb,
1077                                        child_id, commit.commit_sha1, self.encoding)
1078                        hbox.pack_start(button, expand=False, fill=True)
1079                        button.show()
1080
1081        def _destroy_cb(self, widget):
1082                """Callback for when a window we manage is destroyed."""
1083                self.quit()
1084
1085
1086        def quit(self):
1087                """Stop the GTK+ main loop."""
1088                gtk.main_quit()
1089
1090        def run(self, args):
1091                self.set_branch(args)
1092                self.window.connect("destroy", self._destroy_cb)
1093                self.window.show()
1094                gtk.main()
1095
1096        def set_branch(self, args):
1097                """Fill in different windows with info from the reposiroty"""
1098                fp = os.popen("git rev-parse --sq --default HEAD " + list_to_string(args, 1))
1099                git_rev_list_cmd = fp.read()
1100                fp.close()
1101                fp = os.popen("git rev-list  --header --topo-order --parents " + git_rev_list_cmd)
1102                self.update_window(fp)
1103
1104        def update_window(self, fp):
1105                commit_lines = []
1106
1107                self.model = gtk.ListStore(gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,
1108                                gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, str, str, str)
1109
1110                # used for cursor positioning
1111                self.index = {}
1112
1113                self.colours = {}
1114                self.nodepos = {}
1115                self.incomplete_line = {}
1116                self.commits = []
1117
1118                index = 0
1119                last_colour = 0
1120                last_nodepos = -1
1121                out_line = []
1122                input_line = fp.readline()
1123                while (input_line != ""):
1124                        # The commit header ends with '\0'
1125                        # This NULL is immediately followed by the sha1 of the
1126                        # next commit
1127                        if (input_line[0] != '\0'):
1128                                commit_lines.append(input_line)
1129                                input_line = fp.readline()
1130                                continue;
1131
1132                        commit = Commit(commit_lines)
1133                        if (commit != None ):
1134                                self.commits.append(commit)
1135
1136                        # Skip the '\0
1137                        commit_lines = []
1138                        commit_lines.append(input_line[1:])
1139                        input_line = fp.readline()
1140
1141                fp.close()
1142
1143                for commit in self.commits:
1144                        (out_line, last_colour, last_nodepos) = self.draw_graph(commit,
1145                                                                                index, out_line,
1146                                                                                last_colour,
1147                                                                                last_nodepos)
1148                        self.index[commit.commit_sha1] = index
1149                        index += 1
1150
1151                self.treeview.set_model(self.model)
1152                self.treeview.show()
1153
1154        def draw_graph(self, commit, index, out_line, last_colour, last_nodepos):
1155                in_line=[]
1156
1157                #   |   -> outline
1158                #   X
1159                #   |\  <- inline
1160
1161                # Reset nodepostion
1162                if (last_nodepos > 5):
1163                        last_nodepos = -1
1164
1165                # Add the incomplete lines of the last cell in this
1166                try:
1167                        colour = self.colours[commit.commit_sha1]
1168                except KeyError:
1169                        self.colours[commit.commit_sha1] = last_colour+1
1170                        last_colour = self.colours[commit.commit_sha1]
1171                        colour =   self.colours[commit.commit_sha1]
1172
1173                try:
1174                        node_pos = self.nodepos[commit.commit_sha1]
1175                except KeyError:
1176                        self.nodepos[commit.commit_sha1] = last_nodepos+1
1177                        last_nodepos = self.nodepos[commit.commit_sha1]
1178                        node_pos =  self.nodepos[commit.commit_sha1]
1179
1180                #The first parent always continue on the same line
1181                try:
1182                        # check we alreay have the value
1183                        tmp_node_pos = self.nodepos[commit.parent_sha1[0]]
1184                except KeyError:
1185                        self.colours[commit.parent_sha1[0]] = colour
1186                        self.nodepos[commit.parent_sha1[0]] = node_pos
1187
1188                for sha1 in self.incomplete_line.keys():
1189                        if (sha1 != commit.commit_sha1):
1190                                self.draw_incomplete_line(sha1, node_pos,
1191                                                out_line, in_line, index)
1192                        else:
1193                                del self.incomplete_line[sha1]
1194
1195
1196                for parent_id in commit.parent_sha1:
1197                        try:
1198                                tmp_node_pos = self.nodepos[parent_id]
1199                        except KeyError:
1200                                self.colours[parent_id] = last_colour+1
1201                                last_colour = self.colours[parent_id]
1202                                self.nodepos[parent_id] = last_nodepos+1
1203                                last_nodepos = self.nodepos[parent_id]
1204
1205                        in_line.append((node_pos, self.nodepos[parent_id],
1206                                                self.colours[parent_id]))
1207                        self.add_incomplete_line(parent_id)
1208
1209                try:
1210                        branch_tag = self.bt_sha1[commit.commit_sha1]
1211                except KeyError:
1212                        branch_tag = [ ]
1213
1214
1215                node = (node_pos, colour, branch_tag)
1216
1217                self.model.append([commit, node, out_line, in_line,
1218                                commit.message, commit.author, commit.date])
1219
1220                return (in_line, last_colour, last_nodepos)
1221
1222        def add_incomplete_line(self, sha1):
1223                try:
1224                        self.incomplete_line[sha1].append(self.nodepos[sha1])
1225                except KeyError:
1226                        self.incomplete_line[sha1] = [self.nodepos[sha1]]
1227
1228        def draw_incomplete_line(self, sha1, node_pos, out_line, in_line, index):
1229                for idx, pos in enumerate(self.incomplete_line[sha1]):
1230                        if(pos == node_pos):
1231                                #remove the straight line and add a slash
1232                                if ((pos, pos, self.colours[sha1]) in out_line):
1233                                        out_line.remove((pos, pos, self.colours[sha1]))
1234                                out_line.append((pos, pos+0.5, self.colours[sha1]))
1235                                self.incomplete_line[sha1][idx] = pos = pos+0.5
1236                        try:
1237                                next_commit = self.commits[index+1]
1238                                if (next_commit.commit_sha1 == sha1 and pos != int(pos)):
1239                                # join the line back to the node point
1240                                # This need to be done only if we modified it
1241                                        in_line.append((pos, pos-0.5, self.colours[sha1]))
1242                                        continue;
1243                        except IndexError:
1244                                pass
1245                        in_line.append((pos, pos, self.colours[sha1]))
1246
1247
1248        def _go_clicked_cb(self, widget, revid):
1249                """Callback for when the go button for a parent is clicked."""
1250                try:
1251                        self.treeview.set_cursor(self.index[revid])
1252                except KeyError:
1253                        dialog = gtk.MessageDialog(parent=None, flags=0,
1254                                        type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_CLOSE,
1255                                        message_format=None)
1256                        dialog.set_markup("Revision <b>%s</b> not present in the list" % revid)
1257                        # revid == 0 is the parent of the first commit
1258                        if (revid != 0 ):
1259                                dialog.format_secondary_text("Try running gitview without any options")
1260                        dialog.run()
1261                        dialog.destroy()
1262
1263                self.treeview.grab_focus()
1264
1265        def _show_clicked_cb(self, widget,  commit_sha1, parent_sha1, encoding):
1266                """Callback for when the show button for a parent is clicked."""
1267                window = DiffWindow()
1268                window.set_diff(commit_sha1, parent_sha1, encoding)
1269                self.treeview.grab_focus()
1270
1271without_diff = 0
1272if __name__ == "__main__":
1273
1274        if (len(sys.argv) > 1 ):
1275                if (sys.argv[1] == "--without-diff"):
1276                        without_diff = 1
1277
1278        view = GitView( without_diff != 1)
1279        view.run(sys.argv[without_diff:])
1280
1281