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.
9 GUI browser for git repository
10 This program is based on bzrk by Scott James Remnant <scott@ubuntu.com>
12 __copyright__ = "Copyright (C) 2006 Hewlett-Packard Development Company, L.P."
13 __author__ = "Aneesh Kumar K.V <aneesh.kumar@hp.com>"
30 have_gtksourceview = True
32 have_gtksourceview = False
33 print "Running without gtksourceview module"
35 re_ident = re.compile('(author|committer) (?P<ident>.*) (?P<epoch>\d+) (?P<tz>[+-]\d{4})')
37 def list_to_string(args, skip):
42 str_arg = str_arg + args[i]
43 str_arg = str_arg + " "
48 def show_date(epoch, tz):
50 tzsecs = float(tz[1:3]) * 3600
51 tzsecs += float(tz[3:5]) * 60
57 return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(secs))
59 def get_sha1_from_tags(line):
60 fp = os.popen("git cat-file -t " + line)
61 entry = string.strip(fp.readline())
63 if (entry == "commit"):
65 elif (entry == "tag"):
66 fp = os.popen("git cat-file tag "+ line)
67 entry = string.strip(fp.readline())
69 obj = re.split(" ", entry)
70 if (obj[0] == "object"):
74 class CellRendererGraph(gtk.GenericCellRenderer):
75 """Cell renderer for directed graph.
77 This module contains the implementation of a custom GtkCellRenderer that
78 draws part of the directed graph based on the lines suggested by the code
81 Because we're shiny, we use Cairo to do this, and because we're naughty
82 we cheat and draw over the bits of the TreeViewColumn that are supposed to
83 just be for the background.
86 node (column, colour, [ names ]) tuple to draw revision node,
87 in_lines (start, end, colour) tuple list to draw inward lines,
88 out_lines (start, end, colour) tuple list to draw outward lines.
92 "node": ( gobject.TYPE_PYOBJECT, "node",
93 "revision node instruction",
94 gobject.PARAM_WRITABLE
96 "in-lines": ( gobject.TYPE_PYOBJECT, "in-lines",
97 "instructions to draw lines into the cell",
98 gobject.PARAM_WRITABLE
100 "out-lines": ( gobject.TYPE_PYOBJECT, "out-lines",
101 "instructions to draw lines out of the cell",
102 gobject.PARAM_WRITABLE
106 def do_set_property(self, property, value):
107 """Set properties from GObject properties."""
108 if property.name == "node":
110 elif property.name == "in-lines":
111 self.in_lines = value
112 elif property.name == "out-lines":
113 self.out_lines = value
115 raise AttributeError, "no such property: '%s'" % property.name
117 def box_size(self, widget):
118 """Calculate box size based on widget's font.
120 Cache this as it's probably expensive to get. It ensures that we
121 draw the graph at least as large as the text.
124 return self._box_size
125 except AttributeError:
126 pango_ctx = widget.get_pango_context()
127 font_desc = widget.get_style().font_desc
128 metrics = pango_ctx.get_metrics(font_desc)
130 ascent = pango.PIXELS(metrics.get_ascent())
131 descent = pango.PIXELS(metrics.get_descent())
133 self._box_size = ascent + descent + 6
134 return self._box_size
136 def set_colour(self, ctx, colour, bg, fg):
137 """Set the context source colour.
139 Picks a distinct colour based on an internal wheel; the bg
140 parameter provides the value that should be assigned to the 'zero'
141 colours and the fg parameter provides the multiplier that should be
142 applied to the foreground colours.
153 colour %= len(colours)
154 red = (colours[colour][0] * fg) or bg
155 green = (colours[colour][1] * fg) or bg
156 blue = (colours[colour][2] * fg) or bg
158 ctx.set_source_rgb(red, green, blue)
160 def on_get_size(self, widget, cell_area):
161 """Return the size we need for this cell.
163 Each cell is drawn individually and is only as wide as it needs
164 to be, we let the TreeViewColumn take care of making them all
167 box_size = self.box_size(widget)
170 for start, end, colour in self.in_lines + self.out_lines:
171 cols = max(cols, start, end)
173 (column, colour, names) = self.node
175 if (len(names) != 0):
177 names_len += len(item)/3
179 width = box_size * (cols + 1 + names_len )
182 # FIXME I have no idea how to use cell_area properly
183 return (0, 0, width, height)
185 def on_render(self, window, widget, bg_area, cell_area, exp_area, flags):
186 """Render an individual cell.
188 Draws the cell contents using cairo, taking care to clip what we
189 do to within the background area so we don't draw over other cells.
190 Note that we're a bit naughty there and should really be drawing
191 in the cell_area (or even the exposed area), but we explicitly don't
194 We try and be a little clever, if the line we need to draw is going
195 to cross other columns we actually draw it as in the .---' style
196 instead of a pure diagonal ... this reduces confusion by an
199 ctx = window.cairo_create()
200 ctx.rectangle(bg_area.x, bg_area.y, bg_area.width, bg_area.height)
203 box_size = self.box_size(widget)
205 ctx.set_line_width(box_size / 8)
206 ctx.set_line_cap(cairo.LINE_CAP_SQUARE)
208 # Draw lines into the cell
209 for start, end, colour in self.in_lines:
210 ctx.move_to(cell_area.x + box_size * start + box_size / 2,
211 bg_area.y - bg_area.height / 2)
214 ctx.line_to(cell_area.x + box_size * start, bg_area.y)
215 ctx.line_to(cell_area.x + box_size * end + box_size, bg_area.y)
216 elif start - end < -1:
217 ctx.line_to(cell_area.x + box_size * start + box_size,
219 ctx.line_to(cell_area.x + box_size * end, bg_area.y)
221 ctx.line_to(cell_area.x + box_size * end + box_size / 2,
222 bg_area.y + bg_area.height / 2)
224 self.set_colour(ctx, colour, 0.0, 0.65)
227 # Draw lines out of the cell
228 for start, end, colour in self.out_lines:
229 ctx.move_to(cell_area.x + box_size * start + box_size / 2,
230 bg_area.y + bg_area.height / 2)
233 ctx.line_to(cell_area.x + box_size * start,
234 bg_area.y + bg_area.height)
235 ctx.line_to(cell_area.x + box_size * end + box_size,
236 bg_area.y + bg_area.height)
237 elif start - end < -1:
238 ctx.line_to(cell_area.x + box_size * start + box_size,
239 bg_area.y + bg_area.height)
240 ctx.line_to(cell_area.x + box_size * end,
241 bg_area.y + bg_area.height)
243 ctx.line_to(cell_area.x + box_size * end + box_size / 2,
244 bg_area.y + bg_area.height / 2 + bg_area.height)
246 self.set_colour(ctx, colour, 0.0, 0.65)
249 # Draw the revision node in the right column
250 (column, colour, names) = self.node
251 ctx.arc(cell_area.x + box_size * column + box_size / 2,
252 cell_area.y + cell_area.height / 2,
253 box_size / 4, 0, 2 * math.pi)
256 if (len(names) != 0):
259 name = name + item + " "
263 self.set_colour(ctx, colour, 0.0, 0.5)
264 ctx.stroke_preserve()
266 self.set_colour(ctx, colour, 0.5, 1.0)
270 """ This represent a commit object obtained after parsing the git-rev-list
275 def __init__(self, commit_lines):
280 self.commit_date = ""
281 self.commit_sha1 = ""
282 self.parent_sha1 = [ ]
283 self.parse_commit(commit_lines)
286 def parse_commit(self, commit_lines):
288 # First line is the sha1 lines
289 line = string.strip(commit_lines[0])
290 sha1 = re.split(" ", line)
291 self.commit_sha1 = sha1[0]
292 self.parent_sha1 = sha1[1:]
294 #build the child list
295 for parent_id in self.parent_sha1:
297 Commit.children_sha1[parent_id].append(self.commit_sha1)
299 Commit.children_sha1[parent_id] = [self.commit_sha1]
301 # IF we don't have parent
302 if (len(self.parent_sha1) == 0):
303 self.parent_sha1 = [0]
305 for line in commit_lines[1:]:
306 m = re.match("^ ", line)
308 # First line of the commit message used for short log
309 if self.message == "":
310 self.message = string.strip(line)
313 m = re.match("tree", line)
317 m = re.match("parent", line)
321 m = re_ident.match(line)
323 date = show_date(m.group('epoch'), m.group('tz'))
324 if m.group(1) == "author":
325 self.author = m.group('ident')
327 elif m.group(1) == "committer":
328 self.committer = m.group('ident')
329 self.commit_date = date
333 def get_message(self, with_diff=0):
335 message = self.diff_tree()
337 fp = os.popen("git cat-file commit " + self.commit_sha1)
344 fp = os.popen("git diff-tree --pretty --cc -v -p --always " + self.commit_sha1)
351 This object represents and manages a single window containing the
352 differences between two revisions on a branch.
356 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
357 self.window.set_border_width(0)
358 self.window.set_title("Git repository browser diff window")
360 # Use two thirds of the screen by default
361 screen = self.window.get_screen()
362 monitor = screen.get_monitor_geometry(0)
363 width = int(monitor.width * 0.66)
364 height = int(monitor.height * 0.66)
365 self.window.set_default_size(width, height)
370 """Construct the window contents."""
372 self.window.add(vbox)
375 menu_bar = gtk.MenuBar()
376 save_menu = gtk.ImageMenuItem(gtk.STOCK_SAVE)
377 save_menu.connect("activate", self.save_menu_response, "save")
379 menu_bar.append(save_menu)
380 vbox.pack_start(menu_bar, False, False, 2)
383 scrollwin = gtk.ScrolledWindow()
384 scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
385 scrollwin.set_shadow_type(gtk.SHADOW_IN)
386 vbox.pack_start(scrollwin, expand=True, fill=True)
389 if have_gtksourceview:
390 self.buffer = gtksourceview.SourceBuffer()
391 slm = gtksourceview.SourceLanguagesManager()
392 gsl = slm.get_language_from_mime_type("text/x-patch")
393 self.buffer.set_highlight(True)
394 self.buffer.set_language(gsl)
395 sourceview = gtksourceview.SourceView(self.buffer)
397 self.buffer = gtk.TextBuffer()
398 sourceview = gtk.TextView(self.buffer)
400 sourceview.set_editable(False)
401 sourceview.modify_font(pango.FontDescription("Monospace"))
402 scrollwin.add(sourceview)
406 def set_diff(self, commit_sha1, parent_sha1):
407 """Set the differences showed by this window.
408 Compares the two trees and populates the window with the
411 # Diff with the first commit or the last commit shows nothing
412 if (commit_sha1 == 0 or parent_sha1 == 0 ):
415 fp = os.popen("git diff-tree -p " + parent_sha1 + " " + commit_sha1)
416 self.buffer.set_text(fp.read())
420 def save_menu_response(self, widget, string):
421 dialog = gtk.FileChooserDialog("Save..", None, gtk.FILE_CHOOSER_ACTION_SAVE,
422 (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
423 gtk.STOCK_SAVE, gtk.RESPONSE_OK))
424 dialog.set_default_response(gtk.RESPONSE_OK)
425 response = dialog.run()
426 if response == gtk.RESPONSE_OK:
427 patch_buffer = self.buffer.get_text(self.buffer.get_start_iter(),
428 self.buffer.get_end_iter())
429 fp = open(dialog.get_filename(), "w")
430 fp.write(patch_buffer)
435 """ This is the main class
439 def __init__(self, with_diff=0):
440 self.with_diff = with_diff
441 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
442 self.window.set_border_width(0)
443 self.window.set_title("Git repository browser")
447 # Use three-quarters of the screen by default
448 screen = self.window.get_screen()
449 monitor = screen.get_monitor_geometry(0)
450 width = int(monitor.width * 0.75)
451 height = int(monitor.height * 0.75)
452 self.window.set_default_size(width, height)
455 icon = self.window.render_icon(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)
456 self.window.set_icon(icon)
458 self.accel_group = gtk.AccelGroup()
459 self.window.add_accel_group(self.accel_group)
463 def get_bt_sha1(self):
464 """ Update the bt_sha1 dictionary with the
465 respective sha1 details """
468 git_dir = os.getenv("GIT_DIR")
469 if (git_dir == None):
472 #FIXME the path seperator
473 ref_files = os.listdir(git_dir + "/refs/tags")
474 for file in ref_files:
475 fp = open(git_dir + "/refs/tags/"+file)
476 sha1 = get_sha1_from_tags(string.strip(fp.readline()))
478 self.bt_sha1[sha1].append(file)
480 self.bt_sha1[sha1] = [file]
484 #FIXME the path seperator
485 ref_files = os.listdir(git_dir + "/refs/heads")
486 for file in ref_files:
487 fp = open(git_dir + "/refs/heads/" + file)
488 sha1 = get_sha1_from_tags(string.strip(fp.readline()))
490 self.bt_sha1[sha1].append(file)
492 self.bt_sha1[sha1] = [file]
497 """Construct the window contents."""
499 paned.pack1(self.construct_top(), resize=False, shrink=True)
500 paned.pack2(self.construct_bottom(), resize=False, shrink=True)
501 self.window.add(paned)
505 def construct_top(self):
506 """Construct the top-half of the window."""
507 vbox = gtk.VBox(spacing=6)
508 vbox.set_border_width(12)
511 menu_bar = gtk.MenuBar()
512 menu_bar.set_pack_direction(gtk.PACK_DIRECTION_RTL)
513 help_menu = gtk.MenuItem("Help")
515 about_menu = gtk.MenuItem("About")
516 menu.append(about_menu)
517 about_menu.connect("activate", self.about_menu_response, "about")
519 help_menu.set_submenu(menu)
521 menu_bar.append(help_menu)
522 vbox.pack_start(menu_bar, False, False, 2)
525 scrollwin = gtk.ScrolledWindow()
526 scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
527 scrollwin.set_shadow_type(gtk.SHADOW_IN)
528 vbox.pack_start(scrollwin, expand=True, fill=True)
531 self.treeview = gtk.TreeView()
532 self.treeview.set_rules_hint(True)
533 self.treeview.set_search_column(4)
534 self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
535 scrollwin.add(self.treeview)
538 cell = CellRendererGraph()
539 column = gtk.TreeViewColumn()
540 column.set_resizable(False)
541 column.pack_start(cell, expand=False)
542 column.add_attribute(cell, "node", 1)
543 column.add_attribute(cell, "in-lines", 2)
544 column.add_attribute(cell, "out-lines", 3)
545 self.treeview.append_column(column)
547 cell = gtk.CellRendererText()
548 cell.set_property("width-chars", 65)
549 cell.set_property("ellipsize", pango.ELLIPSIZE_END)
550 column = gtk.TreeViewColumn("Message")
551 column.set_resizable(True)
552 column.pack_start(cell, expand=True)
553 column.add_attribute(cell, "text", 4)
554 self.treeview.append_column(column)
556 cell = gtk.CellRendererText()
557 cell.set_property("width-chars", 40)
558 cell.set_property("ellipsize", pango.ELLIPSIZE_END)
559 column = gtk.TreeViewColumn("Author")
560 column.set_resizable(True)
561 column.pack_start(cell, expand=True)
562 column.add_attribute(cell, "text", 5)
563 self.treeview.append_column(column)
565 cell = gtk.CellRendererText()
566 cell.set_property("ellipsize", pango.ELLIPSIZE_END)
567 column = gtk.TreeViewColumn("Date")
568 column.set_resizable(True)
569 column.pack_start(cell, expand=True)
570 column.add_attribute(cell, "text", 6)
571 self.treeview.append_column(column)
575 def about_menu_response(self, widget, string):
576 dialog = gtk.AboutDialog()
577 dialog.set_name("Gitview")
578 dialog.set_version(GitView.version)
579 dialog.set_authors(["Aneesh Kumar K.V <aneesh.kumar@hp.com>"])
580 dialog.set_website("http://www.kernel.org/pub/software/scm/git/")
581 dialog.set_copyright("Use and distribute under the terms of the GNU General Public License")
582 dialog.set_wrap_license(True)
587 def construct_bottom(self):
588 """Construct the bottom half of the window."""
589 vbox = gtk.VBox(False, spacing=6)
590 vbox.set_border_width(12)
591 (width, height) = self.window.get_size()
592 vbox.set_size_request(width, int(height / 2.5))
595 self.table = gtk.Table(rows=4, columns=4)
596 self.table.set_row_spacings(6)
597 self.table.set_col_spacings(6)
598 vbox.pack_start(self.table, expand=False, fill=True)
601 align = gtk.Alignment(0.0, 0.5)
603 label.set_markup("<b>Revision:</b>")
605 self.table.attach(align, 0, 1, 0, 1, gtk.FILL, gtk.FILL)
609 align = gtk.Alignment(0.0, 0.5)
610 self.revid_label = gtk.Label()
611 self.revid_label.set_selectable(True)
612 align.add(self.revid_label)
613 self.table.attach(align, 1, 2, 0, 1, gtk.EXPAND | gtk.FILL, gtk.FILL)
614 self.revid_label.show()
617 align = gtk.Alignment(0.0, 0.5)
619 label.set_markup("<b>Committer:</b>")
621 self.table.attach(align, 0, 1, 1, 2, gtk.FILL, gtk.FILL)
625 align = gtk.Alignment(0.0, 0.5)
626 self.committer_label = gtk.Label()
627 self.committer_label.set_selectable(True)
628 align.add(self.committer_label)
629 self.table.attach(align, 1, 2, 1, 2, gtk.EXPAND | gtk.FILL, gtk.FILL)
630 self.committer_label.show()
633 align = gtk.Alignment(0.0, 0.5)
635 label.set_markup("<b>Timestamp:</b>")
637 self.table.attach(align, 0, 1, 2, 3, gtk.FILL, gtk.FILL)
641 align = gtk.Alignment(0.0, 0.5)
642 self.timestamp_label = gtk.Label()
643 self.timestamp_label.set_selectable(True)
644 align.add(self.timestamp_label)
645 self.table.attach(align, 1, 2, 2, 3, gtk.EXPAND | gtk.FILL, gtk.FILL)
646 self.timestamp_label.show()
649 align = gtk.Alignment(0.0, 0.5)
651 label.set_markup("<b>Parents:</b>")
653 self.table.attach(align, 0, 1, 3, 4, gtk.FILL, gtk.FILL)
656 self.parents_widgets = []
658 align = gtk.Alignment(0.0, 0.5)
660 label.set_markup("<b>Children:</b>")
662 self.table.attach(align, 2, 3, 3, 4, gtk.FILL, gtk.FILL)
665 self.children_widgets = []
667 scrollwin = gtk.ScrolledWindow()
668 scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
669 scrollwin.set_shadow_type(gtk.SHADOW_IN)
670 vbox.pack_start(scrollwin, expand=True, fill=True)
673 if have_gtksourceview:
674 self.message_buffer = gtksourceview.SourceBuffer()
675 slm = gtksourceview.SourceLanguagesManager()
676 gsl = slm.get_language_from_mime_type("text/x-patch")
677 self.message_buffer.set_highlight(True)
678 self.message_buffer.set_language(gsl)
679 sourceview = gtksourceview.SourceView(self.message_buffer)
681 self.message_buffer = gtk.TextBuffer()
682 sourceview = gtk.TextView(self.message_buffer)
684 sourceview.set_editable(False)
685 sourceview.modify_font(pango.FontDescription("Monospace"))
686 scrollwin.add(sourceview)
691 def _treeview_cursor_cb(self, *args):
692 """Callback for when the treeview cursor changes."""
693 (path, col) = self.treeview.get_cursor()
694 commit = self.model[path][0]
696 if commit.committer is not None:
697 committer = commit.committer
698 timestamp = commit.commit_date
699 message = commit.get_message(self.with_diff)
700 revid_label = commit.commit_sha1
707 self.revid_label.set_text(revid_label)
708 self.committer_label.set_text(committer)
709 self.timestamp_label.set_text(timestamp)
710 self.message_buffer.set_text(message)
712 for widget in self.parents_widgets:
713 self.table.remove(widget)
715 self.parents_widgets = []
716 self.table.resize(4 + len(commit.parent_sha1) - 1, 4)
717 for idx, parent_id in enumerate(commit.parent_sha1):
718 self.table.set_row_spacing(idx + 3, 0)
720 align = gtk.Alignment(0.0, 0.0)
721 self.parents_widgets.append(align)
722 self.table.attach(align, 1, 2, idx + 3, idx + 4,
723 gtk.EXPAND | gtk.FILL, gtk.FILL)
726 hbox = gtk.HBox(False, 0)
730 label = gtk.Label(parent_id)
731 label.set_selectable(True)
732 hbox.pack_start(label, expand=False, fill=True)
736 image.set_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU)
739 button = gtk.Button()
741 button.set_relief(gtk.RELIEF_NONE)
742 button.connect("clicked", self._go_clicked_cb, parent_id)
743 hbox.pack_start(button, expand=False, fill=True)
747 image.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU)
750 button = gtk.Button()
752 button.set_relief(gtk.RELIEF_NONE)
753 button.set_sensitive(True)
754 button.connect("clicked", self._show_clicked_cb,
755 commit.commit_sha1, parent_id)
756 hbox.pack_start(button, expand=False, fill=True)
759 # Populate with child details
760 for widget in self.children_widgets:
761 self.table.remove(widget)
763 self.children_widgets = []
765 child_sha1 = Commit.children_sha1[commit.commit_sha1]
767 # We don't have child
770 if ( len(child_sha1) > len(commit.parent_sha1)):
771 self.table.resize(4 + len(child_sha1) - 1, 4)
773 for idx, child_id in enumerate(child_sha1):
774 self.table.set_row_spacing(idx + 3, 0)
776 align = gtk.Alignment(0.0, 0.0)
777 self.children_widgets.append(align)
778 self.table.attach(align, 3, 4, idx + 3, idx + 4,
779 gtk.EXPAND | gtk.FILL, gtk.FILL)
782 hbox = gtk.HBox(False, 0)
786 label = gtk.Label(child_id)
787 label.set_selectable(True)
788 hbox.pack_start(label, expand=False, fill=True)
792 image.set_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU)
795 button = gtk.Button()
797 button.set_relief(gtk.RELIEF_NONE)
798 button.connect("clicked", self._go_clicked_cb, child_id)
799 hbox.pack_start(button, expand=False, fill=True)
803 image.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU)
806 button = gtk.Button()
808 button.set_relief(gtk.RELIEF_NONE)
809 button.set_sensitive(True)
810 button.connect("clicked", self._show_clicked_cb,
811 child_id, commit.commit_sha1)
812 hbox.pack_start(button, expand=False, fill=True)
815 def _destroy_cb(self, widget):
816 """Callback for when a window we manage is destroyed."""
821 """Stop the GTK+ main loop."""
825 self.set_branch(args)
826 self.window.connect("destroy", self._destroy_cb)
830 def set_branch(self, args):
831 """Fill in different windows with info from the reposiroty"""
832 fp = os.popen("git rev-parse --sq --default HEAD " + list_to_string(args, 1))
833 git_rev_list_cmd = fp.read()
835 fp = os.popen("git rev-list --header --topo-order --parents " + git_rev_list_cmd)
836 self.update_window(fp)
838 def update_window(self, fp):
841 self.model = gtk.ListStore(gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,
842 gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, str, str, str)
844 # used for cursor positioning
849 self.incomplete_line = {}
855 input_line = fp.readline()
856 while (input_line != ""):
857 # The commit header ends with '\0'
858 # This NULL is immediately followed by the sha1 of the
860 if (input_line[0] != '\0'):
861 commit_lines.append(input_line)
862 input_line = fp.readline()
865 commit = Commit(commit_lines)
866 if (commit != None ):
867 (out_line, last_colour, last_nodepos) = self.draw_graph(commit,
871 self.index[commit.commit_sha1] = index
876 commit_lines.append(input_line[1:])
877 input_line = fp.readline()
881 self.treeview.set_model(self.model)
884 def draw_graph(self, commit, index, out_line, last_colour, last_nodepos):
892 if (last_nodepos > 5):
895 # Add the incomplete lines of the last cell in this
896 for sha1 in self.incomplete_line.keys():
897 if ( sha1 != commit.commit_sha1):
898 for pos in self.incomplete_line[sha1]:
899 in_line.append((pos, pos, self.colours[sha1]))
901 del self.incomplete_line[sha1]
904 colour = self.colours[commit.commit_sha1]
907 self.colours[commit.commit_sha1] = last_colour
910 node_pos = self.nodepos[commit.commit_sha1]
913 self.nodepos[commit.commit_sha1] = last_nodepos
914 node_pos = last_nodepos
916 #The first parent always continue on the same line
918 # check we alreay have the value
919 tmp_node_pos = self.nodepos[commit.parent_sha1[0]]
921 self.colours[commit.parent_sha1[0]] = colour
922 self.nodepos[commit.parent_sha1[0]] = node_pos
924 in_line.append((node_pos, self.nodepos[commit.parent_sha1[0]],
925 self.colours[commit.parent_sha1[0]]))
927 self.add_incomplete_line(commit.parent_sha1[0], index+1)
929 if (len(commit.parent_sha1) > 1):
930 for parent_id in commit.parent_sha1[1:]:
932 tmp_node_pos = self.nodepos[parent_id]
935 self.colours[parent_id] = last_colour
937 self.nodepos[parent_id] = last_nodepos
939 in_line.append((node_pos, self.nodepos[parent_id],
940 self.colours[parent_id]))
941 self.add_incomplete_line(parent_id, index+1)
945 branch_tag = self.bt_sha1[commit.commit_sha1]
950 node = (node_pos, colour, branch_tag)
952 self.model.append([commit, node, out_line, in_line,
953 commit.message, commit.author, commit.date])
955 return (in_line, last_colour, last_nodepos)
957 def add_incomplete_line(self, sha1, index):
959 self.incomplete_line[sha1].append(self.nodepos[sha1])
961 self.incomplete_line[sha1] = [self.nodepos[sha1]]
964 def _go_clicked_cb(self, widget, revid):
965 """Callback for when the go button for a parent is clicked."""
967 self.treeview.set_cursor(self.index[revid])
969 print "Revision %s not present in the list" % revid
970 # revid == 0 is the parent of the first commit
972 print "Try running gitview without any options"
974 self.treeview.grab_focus()
976 def _show_clicked_cb(self, widget, commit_sha1, parent_sha1):
977 """Callback for when the show button for a parent is clicked."""
978 window = DiffWindow()
979 window.set_diff(commit_sha1, parent_sha1)
980 self.treeview.grab_focus()
982 if __name__ == "__main__":
985 if (len(sys.argv) > 1 ):
986 if (sys.argv[1] == "--without-diff"):
989 view = GitView( without_diff != 1)
990 view.run(sys.argv[without_diff:])