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