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))
60 class CellRendererGraph(gtk.GenericCellRenderer):
61 """Cell renderer for directed graph.
63 This module contains the implementation of a custom GtkCellRenderer that
64 draws part of the directed graph based on the lines suggested by the code
67 Because we're shiny, we use Cairo to do this, and because we're naughty
68 we cheat and draw over the bits of the TreeViewColumn that are supposed to
69 just be for the background.
72 node (column, colour, [ names ]) tuple to draw revision node,
73 in_lines (start, end, colour) tuple list to draw inward lines,
74 out_lines (start, end, colour) tuple list to draw outward lines.
78 "node": ( gobject.TYPE_PYOBJECT, "node",
79 "revision node instruction",
80 gobject.PARAM_WRITABLE
82 "in-lines": ( gobject.TYPE_PYOBJECT, "in-lines",
83 "instructions to draw lines into the cell",
84 gobject.PARAM_WRITABLE
86 "out-lines": ( gobject.TYPE_PYOBJECT, "out-lines",
87 "instructions to draw lines out of the cell",
88 gobject.PARAM_WRITABLE
92 def do_set_property(self, property, value):
93 """Set properties from GObject properties."""
94 if property.name == "node":
96 elif property.name == "in-lines":
98 elif property.name == "out-lines":
99 self.out_lines = value
101 raise AttributeError, "no such property: '%s'" % property.name
103 def box_size(self, widget):
104 """Calculate box size based on widget's font.
106 Cache this as it's probably expensive to get. It ensures that we
107 draw the graph at least as large as the text.
110 return self._box_size
111 except AttributeError:
112 pango_ctx = widget.get_pango_context()
113 font_desc = widget.get_style().font_desc
114 metrics = pango_ctx.get_metrics(font_desc)
116 ascent = pango.PIXELS(metrics.get_ascent())
117 descent = pango.PIXELS(metrics.get_descent())
119 self._box_size = ascent + descent + 6
120 return self._box_size
122 def set_colour(self, ctx, colour, bg, fg):
123 """Set the context source colour.
125 Picks a distinct colour based on an internal wheel; the bg
126 parameter provides the value that should be assigned to the 'zero'
127 colours and the fg parameter provides the multiplier that should be
128 applied to the foreground colours.
139 colour %= len(colours)
140 red = (colours[colour][0] * fg) or bg
141 green = (colours[colour][1] * fg) or bg
142 blue = (colours[colour][2] * fg) or bg
144 ctx.set_source_rgb(red, green, blue)
146 def on_get_size(self, widget, cell_area):
147 """Return the size we need for this cell.
149 Each cell is drawn individually and is only as wide as it needs
150 to be, we let the TreeViewColumn take care of making them all
153 box_size = self.box_size(widget)
156 for start, end, colour in self.in_lines + self.out_lines:
157 cols = max(cols, start, end)
159 (column, colour, names) = self.node
161 if (len(names) != 0):
163 names_len += len(item)/3
165 width = box_size * (cols + 1 + names_len )
168 # FIXME I have no idea how to use cell_area properly
169 return (0, 0, width, height)
171 def on_render(self, window, widget, bg_area, cell_area, exp_area, flags):
172 """Render an individual cell.
174 Draws the cell contents using cairo, taking care to clip what we
175 do to within the background area so we don't draw over other cells.
176 Note that we're a bit naughty there and should really be drawing
177 in the cell_area (or even the exposed area), but we explicitly don't
180 We try and be a little clever, if the line we need to draw is going
181 to cross other columns we actually draw it as in the .---' style
182 instead of a pure diagonal ... this reduces confusion by an
185 ctx = window.cairo_create()
186 ctx.rectangle(bg_area.x, bg_area.y, bg_area.width, bg_area.height)
189 box_size = self.box_size(widget)
191 ctx.set_line_width(box_size / 8)
192 ctx.set_line_cap(cairo.LINE_CAP_SQUARE)
194 # Draw lines into the cell
195 for start, end, colour in self.in_lines:
196 ctx.move_to(cell_area.x + box_size * start + box_size / 2,
197 bg_area.y - bg_area.height / 2)
200 ctx.line_to(cell_area.x + box_size * start, bg_area.y)
201 ctx.line_to(cell_area.x + box_size * end + box_size, bg_area.y)
202 elif start - end < -1:
203 ctx.line_to(cell_area.x + box_size * start + box_size,
205 ctx.line_to(cell_area.x + box_size * end, bg_area.y)
207 ctx.line_to(cell_area.x + box_size * end + box_size / 2,
208 bg_area.y + bg_area.height / 2)
210 self.set_colour(ctx, colour, 0.0, 0.65)
213 # Draw lines out of the cell
214 for start, end, colour in self.out_lines:
215 ctx.move_to(cell_area.x + box_size * start + box_size / 2,
216 bg_area.y + bg_area.height / 2)
219 ctx.line_to(cell_area.x + box_size * start,
220 bg_area.y + bg_area.height)
221 ctx.line_to(cell_area.x + box_size * end + box_size,
222 bg_area.y + bg_area.height)
223 elif start - end < -1:
224 ctx.line_to(cell_area.x + box_size * start + box_size,
225 bg_area.y + bg_area.height)
226 ctx.line_to(cell_area.x + box_size * end,
227 bg_area.y + bg_area.height)
229 ctx.line_to(cell_area.x + box_size * end + box_size / 2,
230 bg_area.y + bg_area.height / 2 + bg_area.height)
232 self.set_colour(ctx, colour, 0.0, 0.65)
235 # Draw the revision node in the right column
236 (column, colour, names) = self.node
237 ctx.arc(cell_area.x + box_size * column + box_size / 2,
238 cell_area.y + cell_area.height / 2,
239 box_size / 4, 0, 2 * math.pi)
242 if (len(names) != 0):
245 name = name + item + " "
249 self.set_colour(ctx, colour, 0.0, 0.5)
250 ctx.stroke_preserve()
252 self.set_colour(ctx, colour, 0.5, 1.0)
256 """ This represent a commit object obtained after parsing the git-rev-list
261 def __init__(self, commit_lines):
266 self.commit_date = ""
267 self.commit_sha1 = ""
268 self.parent_sha1 = [ ]
269 self.parse_commit(commit_lines)
272 def parse_commit(self, commit_lines):
274 # First line is the sha1 lines
275 line = string.strip(commit_lines[0])
276 sha1 = re.split(" ", line)
277 self.commit_sha1 = sha1[0]
278 self.parent_sha1 = sha1[1:]
280 #build the child list
281 for parent_id in self.parent_sha1:
283 Commit.children_sha1[parent_id].append(self.commit_sha1)
285 Commit.children_sha1[parent_id] = [self.commit_sha1]
287 # IF we don't have parent
288 if (len(self.parent_sha1) == 0):
289 self.parent_sha1 = [0]
291 for line in commit_lines[1:]:
292 m = re.match("^ ", line)
294 # First line of the commit message used for short log
295 if self.message == "":
296 self.message = string.strip(line)
299 m = re.match("tree", line)
303 m = re.match("parent", line)
307 m = re_ident.match(line)
309 date = show_date(m.group('epoch'), m.group('tz'))
310 if m.group(1) == "author":
311 self.author = m.group('ident')
313 elif m.group(1) == "committer":
314 self.committer = m.group('ident')
315 self.commit_date = date
319 def get_message(self, with_diff=0):
321 message = self.diff_tree()
323 fp = os.popen("git cat-file commit " + self.commit_sha1)
330 fp = os.popen("git diff-tree --pretty --cc -v -p --always " + self.commit_sha1)
337 This object represents and manages a single window containing the
338 differences between two revisions on a branch.
342 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
343 self.window.set_border_width(0)
344 self.window.set_title("Git repository browser diff window")
346 # Use two thirds of the screen by default
347 screen = self.window.get_screen()
348 monitor = screen.get_monitor_geometry(0)
349 width = int(monitor.width * 0.66)
350 height = int(monitor.height * 0.66)
351 self.window.set_default_size(width, height)
356 """Construct the window contents."""
358 self.window.add(vbox)
361 menu_bar = gtk.MenuBar()
362 save_menu = gtk.ImageMenuItem(gtk.STOCK_SAVE)
363 save_menu.connect("activate", self.save_menu_response, "save")
365 menu_bar.append(save_menu)
366 vbox.pack_start(menu_bar, False, False, 2)
369 scrollwin = gtk.ScrolledWindow()
370 scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
371 scrollwin.set_shadow_type(gtk.SHADOW_IN)
372 vbox.pack_start(scrollwin, expand=True, fill=True)
375 if have_gtksourceview:
376 self.buffer = gtksourceview.SourceBuffer()
377 slm = gtksourceview.SourceLanguagesManager()
378 gsl = slm.get_language_from_mime_type("text/x-patch")
379 self.buffer.set_highlight(True)
380 self.buffer.set_language(gsl)
381 sourceview = gtksourceview.SourceView(self.buffer)
383 self.buffer = gtk.TextBuffer()
384 sourceview = gtk.TextView(self.buffer)
386 sourceview.set_editable(False)
387 sourceview.modify_font(pango.FontDescription("Monospace"))
388 scrollwin.add(sourceview)
392 def set_diff(self, commit_sha1, parent_sha1):
393 """Set the differences showed by this window.
394 Compares the two trees and populates the window with the
397 # Diff with the first commit or the last commit shows nothing
398 if (commit_sha1 == 0 or parent_sha1 == 0 ):
401 fp = os.popen("git diff-tree -p " + parent_sha1 + " " + commit_sha1)
402 self.buffer.set_text(fp.read())
406 def save_menu_response(self, widget, string):
407 dialog = gtk.FileChooserDialog("Save..", None, gtk.FILE_CHOOSER_ACTION_SAVE,
408 (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
409 gtk.STOCK_SAVE, gtk.RESPONSE_OK))
410 dialog.set_default_response(gtk.RESPONSE_OK)
411 response = dialog.run()
412 if response == gtk.RESPONSE_OK:
413 patch_buffer = self.buffer.get_text(self.buffer.get_start_iter(),
414 self.buffer.get_end_iter())
415 fp = open(dialog.get_filename(), "w")
416 fp.write(patch_buffer)
421 """ This is the main class
425 def __init__(self, with_diff=0):
426 self.with_diff = with_diff
427 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
428 self.window.set_border_width(0)
429 self.window.set_title("Git repository browser")
433 # Use three-quarters of the screen by default
434 screen = self.window.get_screen()
435 monitor = screen.get_monitor_geometry(0)
436 width = int(monitor.width * 0.75)
437 height = int(monitor.height * 0.75)
438 self.window.set_default_size(width, height)
441 icon = self.window.render_icon(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)
442 self.window.set_icon(icon)
444 self.accel_group = gtk.AccelGroup()
445 self.window.add_accel_group(self.accel_group)
449 def get_bt_sha1(self):
450 """ Update the bt_sha1 dictionary with the
451 respective sha1 details """
454 ls_remote = re.compile('^(.{40})\trefs/([^^]+)(?:\\^(..))?$');
455 git_dir = os.getenv("GIT_DIR")
456 if (git_dir == None):
459 fp = os.popen('git ls-remote ' + git_dir)
461 line = string.strip(fp.readline())
464 m = ls_remote.match(line)
467 (sha1, name) = (m.group(1), m.group(2))
468 if not self.bt_sha1.has_key(sha1):
469 self.bt_sha1[sha1] = []
470 self.bt_sha1[sha1].append(name)
475 """Construct the window contents."""
477 paned.pack1(self.construct_top(), resize=False, shrink=True)
478 paned.pack2(self.construct_bottom(), resize=False, shrink=True)
479 self.window.add(paned)
483 def construct_top(self):
484 """Construct the top-half of the window."""
485 vbox = gtk.VBox(spacing=6)
486 vbox.set_border_width(12)
489 menu_bar = gtk.MenuBar()
490 menu_bar.set_pack_direction(gtk.PACK_DIRECTION_RTL)
491 help_menu = gtk.MenuItem("Help")
493 about_menu = gtk.MenuItem("About")
494 menu.append(about_menu)
495 about_menu.connect("activate", self.about_menu_response, "about")
497 help_menu.set_submenu(menu)
499 menu_bar.append(help_menu)
500 vbox.pack_start(menu_bar, False, False, 2)
503 scrollwin = gtk.ScrolledWindow()
504 scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
505 scrollwin.set_shadow_type(gtk.SHADOW_IN)
506 vbox.pack_start(scrollwin, expand=True, fill=True)
509 self.treeview = gtk.TreeView()
510 self.treeview.set_rules_hint(True)
511 self.treeview.set_search_column(4)
512 self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
513 scrollwin.add(self.treeview)
516 cell = CellRendererGraph()
517 column = gtk.TreeViewColumn()
518 column.set_resizable(False)
519 column.pack_start(cell, expand=False)
520 column.add_attribute(cell, "node", 1)
521 column.add_attribute(cell, "in-lines", 2)
522 column.add_attribute(cell, "out-lines", 3)
523 self.treeview.append_column(column)
525 cell = gtk.CellRendererText()
526 cell.set_property("width-chars", 65)
527 cell.set_property("ellipsize", pango.ELLIPSIZE_END)
528 column = gtk.TreeViewColumn("Message")
529 column.set_resizable(True)
530 column.pack_start(cell, expand=True)
531 column.add_attribute(cell, "text", 4)
532 self.treeview.append_column(column)
534 cell = gtk.CellRendererText()
535 cell.set_property("width-chars", 40)
536 cell.set_property("ellipsize", pango.ELLIPSIZE_END)
537 column = gtk.TreeViewColumn("Author")
538 column.set_resizable(True)
539 column.pack_start(cell, expand=True)
540 column.add_attribute(cell, "text", 5)
541 self.treeview.append_column(column)
543 cell = gtk.CellRendererText()
544 cell.set_property("ellipsize", pango.ELLIPSIZE_END)
545 column = gtk.TreeViewColumn("Date")
546 column.set_resizable(True)
547 column.pack_start(cell, expand=True)
548 column.add_attribute(cell, "text", 6)
549 self.treeview.append_column(column)
553 def about_menu_response(self, widget, string):
554 dialog = gtk.AboutDialog()
555 dialog.set_name("Gitview")
556 dialog.set_version(GitView.version)
557 dialog.set_authors(["Aneesh Kumar K.V <aneesh.kumar@hp.com>"])
558 dialog.set_website("http://www.kernel.org/pub/software/scm/git/")
559 dialog.set_copyright("Use and distribute under the terms of the GNU General Public License")
560 dialog.set_wrap_license(True)
565 def construct_bottom(self):
566 """Construct the bottom half of the window."""
567 vbox = gtk.VBox(False, spacing=6)
568 vbox.set_border_width(12)
569 (width, height) = self.window.get_size()
570 vbox.set_size_request(width, int(height / 2.5))
573 self.table = gtk.Table(rows=4, columns=4)
574 self.table.set_row_spacings(6)
575 self.table.set_col_spacings(6)
576 vbox.pack_start(self.table, expand=False, fill=True)
579 align = gtk.Alignment(0.0, 0.5)
581 label.set_markup("<b>Revision:</b>")
583 self.table.attach(align, 0, 1, 0, 1, gtk.FILL, gtk.FILL)
587 align = gtk.Alignment(0.0, 0.5)
588 self.revid_label = gtk.Label()
589 self.revid_label.set_selectable(True)
590 align.add(self.revid_label)
591 self.table.attach(align, 1, 2, 0, 1, gtk.EXPAND | gtk.FILL, gtk.FILL)
592 self.revid_label.show()
595 align = gtk.Alignment(0.0, 0.5)
597 label.set_markup("<b>Committer:</b>")
599 self.table.attach(align, 0, 1, 1, 2, gtk.FILL, gtk.FILL)
603 align = gtk.Alignment(0.0, 0.5)
604 self.committer_label = gtk.Label()
605 self.committer_label.set_selectable(True)
606 align.add(self.committer_label)
607 self.table.attach(align, 1, 2, 1, 2, gtk.EXPAND | gtk.FILL, gtk.FILL)
608 self.committer_label.show()
611 align = gtk.Alignment(0.0, 0.5)
613 label.set_markup("<b>Timestamp:</b>")
615 self.table.attach(align, 0, 1, 2, 3, gtk.FILL, gtk.FILL)
619 align = gtk.Alignment(0.0, 0.5)
620 self.timestamp_label = gtk.Label()
621 self.timestamp_label.set_selectable(True)
622 align.add(self.timestamp_label)
623 self.table.attach(align, 1, 2, 2, 3, gtk.EXPAND | gtk.FILL, gtk.FILL)
624 self.timestamp_label.show()
627 align = gtk.Alignment(0.0, 0.5)
629 label.set_markup("<b>Parents:</b>")
631 self.table.attach(align, 0, 1, 3, 4, gtk.FILL, gtk.FILL)
634 self.parents_widgets = []
636 align = gtk.Alignment(0.0, 0.5)
638 label.set_markup("<b>Children:</b>")
640 self.table.attach(align, 2, 3, 3, 4, gtk.FILL, gtk.FILL)
643 self.children_widgets = []
645 scrollwin = gtk.ScrolledWindow()
646 scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
647 scrollwin.set_shadow_type(gtk.SHADOW_IN)
648 vbox.pack_start(scrollwin, expand=True, fill=True)
651 if have_gtksourceview:
652 self.message_buffer = gtksourceview.SourceBuffer()
653 slm = gtksourceview.SourceLanguagesManager()
654 gsl = slm.get_language_from_mime_type("text/x-patch")
655 self.message_buffer.set_highlight(True)
656 self.message_buffer.set_language(gsl)
657 sourceview = gtksourceview.SourceView(self.message_buffer)
659 self.message_buffer = gtk.TextBuffer()
660 sourceview = gtk.TextView(self.message_buffer)
662 sourceview.set_editable(False)
663 sourceview.modify_font(pango.FontDescription("Monospace"))
664 scrollwin.add(sourceview)
669 def _treeview_cursor_cb(self, *args):
670 """Callback for when the treeview cursor changes."""
671 (path, col) = self.treeview.get_cursor()
672 commit = self.model[path][0]
674 if commit.committer is not None:
675 committer = commit.committer
676 timestamp = commit.commit_date
677 message = commit.get_message(self.with_diff)
678 revid_label = commit.commit_sha1
685 self.revid_label.set_text(revid_label)
686 self.committer_label.set_text(committer)
687 self.timestamp_label.set_text(timestamp)
688 self.message_buffer.set_text(message)
690 for widget in self.parents_widgets:
691 self.table.remove(widget)
693 self.parents_widgets = []
694 self.table.resize(4 + len(commit.parent_sha1) - 1, 4)
695 for idx, parent_id in enumerate(commit.parent_sha1):
696 self.table.set_row_spacing(idx + 3, 0)
698 align = gtk.Alignment(0.0, 0.0)
699 self.parents_widgets.append(align)
700 self.table.attach(align, 1, 2, idx + 3, idx + 4,
701 gtk.EXPAND | gtk.FILL, gtk.FILL)
704 hbox = gtk.HBox(False, 0)
708 label = gtk.Label(parent_id)
709 label.set_selectable(True)
710 hbox.pack_start(label, expand=False, fill=True)
714 image.set_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU)
717 button = gtk.Button()
719 button.set_relief(gtk.RELIEF_NONE)
720 button.connect("clicked", self._go_clicked_cb, parent_id)
721 hbox.pack_start(button, expand=False, fill=True)
725 image.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU)
728 button = gtk.Button()
730 button.set_relief(gtk.RELIEF_NONE)
731 button.set_sensitive(True)
732 button.connect("clicked", self._show_clicked_cb,
733 commit.commit_sha1, parent_id)
734 hbox.pack_start(button, expand=False, fill=True)
737 # Populate with child details
738 for widget in self.children_widgets:
739 self.table.remove(widget)
741 self.children_widgets = []
743 child_sha1 = Commit.children_sha1[commit.commit_sha1]
745 # We don't have child
748 if ( len(child_sha1) > len(commit.parent_sha1)):
749 self.table.resize(4 + len(child_sha1) - 1, 4)
751 for idx, child_id in enumerate(child_sha1):
752 self.table.set_row_spacing(idx + 3, 0)
754 align = gtk.Alignment(0.0, 0.0)
755 self.children_widgets.append(align)
756 self.table.attach(align, 3, 4, idx + 3, idx + 4,
757 gtk.EXPAND | gtk.FILL, gtk.FILL)
760 hbox = gtk.HBox(False, 0)
764 label = gtk.Label(child_id)
765 label.set_selectable(True)
766 hbox.pack_start(label, expand=False, fill=True)
770 image.set_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU)
773 button = gtk.Button()
775 button.set_relief(gtk.RELIEF_NONE)
776 button.connect("clicked", self._go_clicked_cb, child_id)
777 hbox.pack_start(button, expand=False, fill=True)
781 image.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU)
784 button = gtk.Button()
786 button.set_relief(gtk.RELIEF_NONE)
787 button.set_sensitive(True)
788 button.connect("clicked", self._show_clicked_cb,
789 child_id, commit.commit_sha1)
790 hbox.pack_start(button, expand=False, fill=True)
793 def _destroy_cb(self, widget):
794 """Callback for when a window we manage is destroyed."""
799 """Stop the GTK+ main loop."""
803 self.set_branch(args)
804 self.window.connect("destroy", self._destroy_cb)
808 def set_branch(self, args):
809 """Fill in different windows with info from the reposiroty"""
810 fp = os.popen("git rev-parse --sq --default HEAD " + list_to_string(args, 1))
811 git_rev_list_cmd = fp.read()
813 fp = os.popen("git rev-list --header --topo-order --parents " + git_rev_list_cmd)
814 self.update_window(fp)
816 def update_window(self, fp):
819 self.model = gtk.ListStore(gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,
820 gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, str, str, str)
822 # used for cursor positioning
827 self.incomplete_line = {}
833 input_line = fp.readline()
834 while (input_line != ""):
835 # The commit header ends with '\0'
836 # This NULL is immediately followed by the sha1 of the
838 if (input_line[0] != '\0'):
839 commit_lines.append(input_line)
840 input_line = fp.readline()
843 commit = Commit(commit_lines)
844 if (commit != None ):
845 (out_line, last_colour, last_nodepos) = self.draw_graph(commit,
849 self.index[commit.commit_sha1] = index
854 commit_lines.append(input_line[1:])
855 input_line = fp.readline()
859 self.treeview.set_model(self.model)
862 def draw_graph(self, commit, index, out_line, last_colour, last_nodepos):
870 if (last_nodepos > 5):
873 # Add the incomplete lines of the last cell in this
874 for sha1 in self.incomplete_line.keys():
875 if ( sha1 != commit.commit_sha1):
876 for pos in self.incomplete_line[sha1]:
877 in_line.append((pos, pos, self.colours[sha1]))
879 del self.incomplete_line[sha1]
882 colour = self.colours[commit.commit_sha1]
885 self.colours[commit.commit_sha1] = last_colour
888 node_pos = self.nodepos[commit.commit_sha1]
891 self.nodepos[commit.commit_sha1] = last_nodepos
892 node_pos = last_nodepos
894 #The first parent always continue on the same line
896 # check we alreay have the value
897 tmp_node_pos = self.nodepos[commit.parent_sha1[0]]
899 self.colours[commit.parent_sha1[0]] = colour
900 self.nodepos[commit.parent_sha1[0]] = node_pos
902 in_line.append((node_pos, self.nodepos[commit.parent_sha1[0]],
903 self.colours[commit.parent_sha1[0]]))
905 self.add_incomplete_line(commit.parent_sha1[0], index+1)
907 if (len(commit.parent_sha1) > 1):
908 for parent_id in commit.parent_sha1[1:]:
910 tmp_node_pos = self.nodepos[parent_id]
913 self.colours[parent_id] = last_colour
915 self.nodepos[parent_id] = last_nodepos
917 in_line.append((node_pos, self.nodepos[parent_id],
918 self.colours[parent_id]))
919 self.add_incomplete_line(parent_id, index+1)
923 branch_tag = self.bt_sha1[commit.commit_sha1]
928 node = (node_pos, colour, branch_tag)
930 self.model.append([commit, node, out_line, in_line,
931 commit.message, commit.author, commit.date])
933 return (in_line, last_colour, last_nodepos)
935 def add_incomplete_line(self, sha1, index):
937 self.incomplete_line[sha1].append(self.nodepos[sha1])
939 self.incomplete_line[sha1] = [self.nodepos[sha1]]
942 def _go_clicked_cb(self, widget, revid):
943 """Callback for when the go button for a parent is clicked."""
945 self.treeview.set_cursor(self.index[revid])
947 print "Revision %s not present in the list" % revid
948 # revid == 0 is the parent of the first commit
950 print "Try running gitview without any options"
952 self.treeview.grab_focus()
954 def _show_clicked_cb(self, widget, commit_sha1, parent_sha1):
955 """Callback for when the show button for a parent is clicked."""
956 window = DiffWindow()
957 window.set_diff(commit_sha1, parent_sha1)
958 self.treeview.grab_focus()
960 if __name__ == "__main__":
963 if (len(sys.argv) > 1 ):
964 if (sys.argv[1] == "--without-diff"):
967 view = GitView( without_diff != 1)
968 view.run(sys.argv[without_diff:])