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 = int(max(cols, start, end))
159 (column, colour, names) = self.node
161 if (len(names) != 0):
163 names_len += len(item)
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 self.set_colour(ctx, colour, 0.0, 0.5)
243 ctx.stroke_preserve()
245 self.set_colour(ctx, colour, 0.5, 1.0)
248 if (len(names) != 0):
251 name = name + item + " "
253 ctx.set_font_size(13)
255 self.set_colour(ctx, colour, 0.5, 1.0)
257 self.set_colour(ctx, colour, 0.0, 0.5)
261 """ This represent a commit object obtained after parsing the git-rev-list
266 def __init__(self, commit_lines):
271 self.commit_date = ""
272 self.commit_sha1 = ""
273 self.parent_sha1 = [ ]
274 self.parse_commit(commit_lines)
277 def parse_commit(self, commit_lines):
279 # First line is the sha1 lines
280 line = string.strip(commit_lines[0])
281 sha1 = re.split(" ", line)
282 self.commit_sha1 = sha1[0]
283 self.parent_sha1 = sha1[1:]
285 #build the child list
286 for parent_id in self.parent_sha1:
288 Commit.children_sha1[parent_id].append(self.commit_sha1)
290 Commit.children_sha1[parent_id] = [self.commit_sha1]
292 # IF we don't have parent
293 if (len(self.parent_sha1) == 0):
294 self.parent_sha1 = [0]
296 for line in commit_lines[1:]:
297 m = re.match("^ ", line)
299 # First line of the commit message used for short log
300 if self.message == "":
301 self.message = string.strip(line)
304 m = re.match("tree", line)
308 m = re.match("parent", line)
312 m = re_ident.match(line)
314 date = show_date(m.group('epoch'), m.group('tz'))
315 if m.group(1) == "author":
316 self.author = m.group('ident')
318 elif m.group(1) == "committer":
319 self.committer = m.group('ident')
320 self.commit_date = date
324 def get_message(self, with_diff=0):
326 message = self.diff_tree()
328 fp = os.popen("git cat-file commit " + self.commit_sha1)
335 fp = os.popen("git diff-tree --pretty --cc -v -p --always " + self.commit_sha1)
342 This object represents and manages a single window containing the
343 differences between two revisions on a branch.
347 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
348 self.window.set_border_width(0)
349 self.window.set_title("Git repository browser diff window")
351 # Use two thirds of the screen by default
352 screen = self.window.get_screen()
353 monitor = screen.get_monitor_geometry(0)
354 width = int(monitor.width * 0.66)
355 height = int(monitor.height * 0.66)
356 self.window.set_default_size(width, height)
361 """Construct the window contents."""
363 self.window.add(vbox)
366 menu_bar = gtk.MenuBar()
367 save_menu = gtk.ImageMenuItem(gtk.STOCK_SAVE)
368 save_menu.connect("activate", self.save_menu_response, "save")
370 menu_bar.append(save_menu)
371 vbox.pack_start(menu_bar, expand=False, fill=True)
374 scrollwin = gtk.ScrolledWindow()
375 scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
376 scrollwin.set_shadow_type(gtk.SHADOW_IN)
377 vbox.pack_start(scrollwin, expand=True, fill=True)
380 if have_gtksourceview:
381 self.buffer = gtksourceview.SourceBuffer()
382 slm = gtksourceview.SourceLanguagesManager()
383 gsl = slm.get_language_from_mime_type("text/x-patch")
384 self.buffer.set_highlight(True)
385 self.buffer.set_language(gsl)
386 sourceview = gtksourceview.SourceView(self.buffer)
388 self.buffer = gtk.TextBuffer()
389 sourceview = gtk.TextView(self.buffer)
391 sourceview.set_editable(False)
392 sourceview.modify_font(pango.FontDescription("Monospace"))
393 scrollwin.add(sourceview)
397 def set_diff(self, commit_sha1, parent_sha1, encoding):
398 """Set the differences showed by this window.
399 Compares the two trees and populates the window with the
402 # Diff with the first commit or the last commit shows nothing
403 if (commit_sha1 == 0 or parent_sha1 == 0 ):
406 fp = os.popen("git diff-tree -p " + parent_sha1 + " " + commit_sha1)
407 self.buffer.set_text(unicode(fp.read(), encoding).encode('utf-8'))
411 def save_menu_response(self, widget, string):
412 dialog = gtk.FileChooserDialog("Save..", None, gtk.FILE_CHOOSER_ACTION_SAVE,
413 (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
414 gtk.STOCK_SAVE, gtk.RESPONSE_OK))
415 dialog.set_default_response(gtk.RESPONSE_OK)
416 response = dialog.run()
417 if response == gtk.RESPONSE_OK:
418 patch_buffer = self.buffer.get_text(self.buffer.get_start_iter(),
419 self.buffer.get_end_iter())
420 fp = open(dialog.get_filename(), "w")
421 fp.write(patch_buffer)
426 """ This is the main class
430 def __init__(self, with_diff=0):
431 self.with_diff = with_diff
432 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
433 self.window.set_border_width(0)
434 self.window.set_title("Git repository browser")
439 # Use three-quarters of the screen by default
440 screen = self.window.get_screen()
441 monitor = screen.get_monitor_geometry(0)
442 width = int(monitor.width * 0.75)
443 height = int(monitor.height * 0.75)
444 self.window.set_default_size(width, height)
447 icon = self.window.render_icon(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)
448 self.window.set_icon(icon)
450 self.accel_group = gtk.AccelGroup()
451 self.window.add_accel_group(self.accel_group)
455 def get_bt_sha1(self):
456 """ Update the bt_sha1 dictionary with the
457 respective sha1 details """
460 ls_remote = re.compile('^(.{40})\trefs/([^^]+)(?:\\^(..))?$');
461 fp = os.popen('git ls-remote "${GIT_DIR-.git}"')
463 line = string.strip(fp.readline())
466 m = ls_remote.match(line)
469 (sha1, name) = (m.group(1), m.group(2))
470 if not self.bt_sha1.has_key(sha1):
471 self.bt_sha1[sha1] = []
472 self.bt_sha1[sha1].append(name)
475 def get_encoding(self):
476 fp = os.popen("git repo-config --get i18n.commitencoding")
477 self.encoding=string.strip(fp.readline())
479 if (self.encoding == ""):
480 self.encoding = "utf-8"
484 """Construct the window contents."""
487 paned.pack1(self.construct_top(), resize=False, shrink=True)
488 paned.pack2(self.construct_bottom(), resize=False, shrink=True)
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)
501 vbox.pack_start(menu_bar, expand=False, fill=True)
502 vbox.pack_start(paned, expand=True, fill=True)
503 self.window.add(vbox)
508 def construct_top(self):
509 """Construct the top-half of the window."""
510 vbox = gtk.VBox(spacing=6)
511 vbox.set_border_width(12)
515 scrollwin = gtk.ScrolledWindow()
516 scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
517 scrollwin.set_shadow_type(gtk.SHADOW_IN)
518 vbox.pack_start(scrollwin, expand=True, fill=True)
521 self.treeview = gtk.TreeView()
522 self.treeview.set_rules_hint(True)
523 self.treeview.set_search_column(4)
524 self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
525 scrollwin.add(self.treeview)
528 cell = CellRendererGraph()
529 column = gtk.TreeViewColumn()
530 column.set_resizable(True)
531 column.pack_start(cell, expand=True)
532 column.add_attribute(cell, "node", 1)
533 column.add_attribute(cell, "in-lines", 2)
534 column.add_attribute(cell, "out-lines", 3)
535 self.treeview.append_column(column)
537 cell = gtk.CellRendererText()
538 cell.set_property("width-chars", 65)
539 cell.set_property("ellipsize", pango.ELLIPSIZE_END)
540 column = gtk.TreeViewColumn("Message")
541 column.set_resizable(True)
542 column.pack_start(cell, expand=True)
543 column.add_attribute(cell, "text", 4)
544 self.treeview.append_column(column)
546 cell = gtk.CellRendererText()
547 cell.set_property("width-chars", 40)
548 cell.set_property("ellipsize", pango.ELLIPSIZE_END)
549 column = gtk.TreeViewColumn("Author")
550 column.set_resizable(True)
551 column.pack_start(cell, expand=True)
552 column.add_attribute(cell, "text", 5)
553 self.treeview.append_column(column)
555 cell = gtk.CellRendererText()
556 cell.set_property("ellipsize", pango.ELLIPSIZE_END)
557 column = gtk.TreeViewColumn("Date")
558 column.set_resizable(True)
559 column.pack_start(cell, expand=True)
560 column.add_attribute(cell, "text", 6)
561 self.treeview.append_column(column)
565 def about_menu_response(self, widget, string):
566 dialog = gtk.AboutDialog()
567 dialog.set_name("Gitview")
568 dialog.set_version(GitView.version)
569 dialog.set_authors(["Aneesh Kumar K.V <aneesh.kumar@hp.com>"])
570 dialog.set_website("http://www.kernel.org/pub/software/scm/git/")
571 dialog.set_copyright("Use and distribute under the terms of the GNU General Public License")
572 dialog.set_wrap_license(True)
577 def construct_bottom(self):
578 """Construct the bottom half of the window."""
579 vbox = gtk.VBox(False, spacing=6)
580 vbox.set_border_width(12)
581 (width, height) = self.window.get_size()
582 vbox.set_size_request(width, int(height / 2.5))
585 self.table = gtk.Table(rows=4, columns=4)
586 self.table.set_row_spacings(6)
587 self.table.set_col_spacings(6)
588 vbox.pack_start(self.table, expand=False, fill=True)
591 align = gtk.Alignment(0.0, 0.5)
593 label.set_markup("<b>Revision:</b>")
595 self.table.attach(align, 0, 1, 0, 1, gtk.FILL, gtk.FILL)
599 align = gtk.Alignment(0.0, 0.5)
600 self.revid_label = gtk.Label()
601 self.revid_label.set_selectable(True)
602 align.add(self.revid_label)
603 self.table.attach(align, 1, 2, 0, 1, gtk.EXPAND | gtk.FILL, gtk.FILL)
604 self.revid_label.show()
607 align = gtk.Alignment(0.0, 0.5)
609 label.set_markup("<b>Committer:</b>")
611 self.table.attach(align, 0, 1, 1, 2, gtk.FILL, gtk.FILL)
615 align = gtk.Alignment(0.0, 0.5)
616 self.committer_label = gtk.Label()
617 self.committer_label.set_selectable(True)
618 align.add(self.committer_label)
619 self.table.attach(align, 1, 2, 1, 2, gtk.EXPAND | gtk.FILL, gtk.FILL)
620 self.committer_label.show()
623 align = gtk.Alignment(0.0, 0.5)
625 label.set_markup("<b>Timestamp:</b>")
627 self.table.attach(align, 0, 1, 2, 3, gtk.FILL, gtk.FILL)
631 align = gtk.Alignment(0.0, 0.5)
632 self.timestamp_label = gtk.Label()
633 self.timestamp_label.set_selectable(True)
634 align.add(self.timestamp_label)
635 self.table.attach(align, 1, 2, 2, 3, gtk.EXPAND | gtk.FILL, gtk.FILL)
636 self.timestamp_label.show()
639 align = gtk.Alignment(0.0, 0.5)
641 label.set_markup("<b>Parents:</b>")
643 self.table.attach(align, 0, 1, 3, 4, gtk.FILL, gtk.FILL)
646 self.parents_widgets = []
648 align = gtk.Alignment(0.0, 0.5)
650 label.set_markup("<b>Children:</b>")
652 self.table.attach(align, 2, 3, 3, 4, gtk.FILL, gtk.FILL)
655 self.children_widgets = []
657 scrollwin = gtk.ScrolledWindow()
658 scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
659 scrollwin.set_shadow_type(gtk.SHADOW_IN)
660 vbox.pack_start(scrollwin, expand=True, fill=True)
663 if have_gtksourceview:
664 self.message_buffer = gtksourceview.SourceBuffer()
665 slm = gtksourceview.SourceLanguagesManager()
666 gsl = slm.get_language_from_mime_type("text/x-patch")
667 self.message_buffer.set_highlight(True)
668 self.message_buffer.set_language(gsl)
669 sourceview = gtksourceview.SourceView(self.message_buffer)
671 self.message_buffer = gtk.TextBuffer()
672 sourceview = gtk.TextView(self.message_buffer)
674 sourceview.set_editable(False)
675 sourceview.modify_font(pango.FontDescription("Monospace"))
676 scrollwin.add(sourceview)
681 def _treeview_cursor_cb(self, *args):
682 """Callback for when the treeview cursor changes."""
683 (path, col) = self.treeview.get_cursor()
684 commit = self.model[path][0]
686 if commit.committer is not None:
687 committer = commit.committer
688 timestamp = commit.commit_date
689 message = commit.get_message(self.with_diff)
690 revid_label = commit.commit_sha1
697 self.revid_label.set_text(revid_label)
698 self.committer_label.set_text(committer)
699 self.timestamp_label.set_text(timestamp)
700 self.message_buffer.set_text(unicode(message, self.encoding).encode('utf-8'))
702 for widget in self.parents_widgets:
703 self.table.remove(widget)
705 self.parents_widgets = []
706 self.table.resize(4 + len(commit.parent_sha1) - 1, 4)
707 for idx, parent_id in enumerate(commit.parent_sha1):
708 self.table.set_row_spacing(idx + 3, 0)
710 align = gtk.Alignment(0.0, 0.0)
711 self.parents_widgets.append(align)
712 self.table.attach(align, 1, 2, idx + 3, idx + 4,
713 gtk.EXPAND | gtk.FILL, gtk.FILL)
716 hbox = gtk.HBox(False, 0)
720 label = gtk.Label(parent_id)
721 label.set_selectable(True)
722 hbox.pack_start(label, expand=False, fill=True)
726 image.set_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU)
729 button = gtk.Button()
731 button.set_relief(gtk.RELIEF_NONE)
732 button.connect("clicked", self._go_clicked_cb, parent_id)
733 hbox.pack_start(button, expand=False, fill=True)
737 image.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU)
740 button = gtk.Button()
742 button.set_relief(gtk.RELIEF_NONE)
743 button.set_sensitive(True)
744 button.connect("clicked", self._show_clicked_cb,
745 commit.commit_sha1, parent_id, self.encoding)
746 hbox.pack_start(button, expand=False, fill=True)
749 # Populate with child details
750 for widget in self.children_widgets:
751 self.table.remove(widget)
753 self.children_widgets = []
755 child_sha1 = Commit.children_sha1[commit.commit_sha1]
757 # We don't have child
760 if ( len(child_sha1) > len(commit.parent_sha1)):
761 self.table.resize(4 + len(child_sha1) - 1, 4)
763 for idx, child_id in enumerate(child_sha1):
764 self.table.set_row_spacing(idx + 3, 0)
766 align = gtk.Alignment(0.0, 0.0)
767 self.children_widgets.append(align)
768 self.table.attach(align, 3, 4, idx + 3, idx + 4,
769 gtk.EXPAND | gtk.FILL, gtk.FILL)
772 hbox = gtk.HBox(False, 0)
776 label = gtk.Label(child_id)
777 label.set_selectable(True)
778 hbox.pack_start(label, expand=False, fill=True)
782 image.set_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU)
785 button = gtk.Button()
787 button.set_relief(gtk.RELIEF_NONE)
788 button.connect("clicked", self._go_clicked_cb, child_id)
789 hbox.pack_start(button, expand=False, fill=True)
793 image.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU)
796 button = gtk.Button()
798 button.set_relief(gtk.RELIEF_NONE)
799 button.set_sensitive(True)
800 button.connect("clicked", self._show_clicked_cb,
801 child_id, commit.commit_sha1, self.encoding)
802 hbox.pack_start(button, expand=False, fill=True)
805 def _destroy_cb(self, widget):
806 """Callback for when a window we manage is destroyed."""
811 """Stop the GTK+ main loop."""
815 self.set_branch(args)
816 self.window.connect("destroy", self._destroy_cb)
820 def set_branch(self, args):
821 """Fill in different windows with info from the reposiroty"""
822 fp = os.popen("git rev-parse --sq --default HEAD " + list_to_string(args, 1))
823 git_rev_list_cmd = fp.read()
825 fp = os.popen("git rev-list --header --topo-order --parents " + git_rev_list_cmd)
826 self.update_window(fp)
828 def update_window(self, fp):
831 self.model = gtk.ListStore(gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,
832 gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, str, str, str)
834 # used for cursor positioning
839 self.incomplete_line = {}
846 input_line = fp.readline()
847 while (input_line != ""):
848 # The commit header ends with '\0'
849 # This NULL is immediately followed by the sha1 of the
851 if (input_line[0] != '\0'):
852 commit_lines.append(input_line)
853 input_line = fp.readline()
856 commit = Commit(commit_lines)
857 if (commit != None ):
858 self.commits.append(commit)
862 commit_lines.append(input_line[1:])
863 input_line = fp.readline()
867 for commit in self.commits:
868 (out_line, last_colour, last_nodepos) = self.draw_graph(commit,
872 self.index[commit.commit_sha1] = index
875 self.treeview.set_model(self.model)
878 def draw_graph(self, commit, index, out_line, last_colour, last_nodepos):
886 if (last_nodepos > 5):
889 # Add the incomplete lines of the last cell in this
891 colour = self.colours[commit.commit_sha1]
893 self.colours[commit.commit_sha1] = last_colour+1
894 last_colour = self.colours[commit.commit_sha1]
895 colour = self.colours[commit.commit_sha1]
898 node_pos = self.nodepos[commit.commit_sha1]
900 self.nodepos[commit.commit_sha1] = last_nodepos+1
901 last_nodepos = self.nodepos[commit.commit_sha1]
902 node_pos = self.nodepos[commit.commit_sha1]
904 #The first parent always continue on the same line
906 # check we alreay have the value
907 tmp_node_pos = self.nodepos[commit.parent_sha1[0]]
909 self.colours[commit.parent_sha1[0]] = colour
910 self.nodepos[commit.parent_sha1[0]] = node_pos
912 for sha1 in self.incomplete_line.keys():
913 if (sha1 != commit.commit_sha1):
914 self.draw_incomplete_line(sha1, node_pos,
915 out_line, in_line, index)
917 del self.incomplete_line[sha1]
920 for parent_id in commit.parent_sha1:
922 tmp_node_pos = self.nodepos[parent_id]
924 self.colours[parent_id] = last_colour+1
925 last_colour = self.colours[parent_id]
926 self.nodepos[parent_id] = last_nodepos+1
927 last_nodepos = self.nodepos[parent_id]
929 in_line.append((node_pos, self.nodepos[parent_id],
930 self.colours[parent_id]))
931 self.add_incomplete_line(parent_id)
934 branch_tag = self.bt_sha1[commit.commit_sha1]
939 node = (node_pos, colour, branch_tag)
941 self.model.append([commit, node, out_line, in_line,
942 commit.message, commit.author, commit.date])
944 return (in_line, last_colour, last_nodepos)
946 def add_incomplete_line(self, sha1):
948 self.incomplete_line[sha1].append(self.nodepos[sha1])
950 self.incomplete_line[sha1] = [self.nodepos[sha1]]
952 def draw_incomplete_line(self, sha1, node_pos, out_line, in_line, index):
953 for idx, pos in enumerate(self.incomplete_line[sha1]):
955 #remove the straight line and add a slash
956 if ((pos, pos, self.colours[sha1]) in out_line):
957 out_line.remove((pos, pos, self.colours[sha1]))
958 out_line.append((pos, pos+0.5, self.colours[sha1]))
959 self.incomplete_line[sha1][idx] = pos = pos+0.5
961 next_commit = self.commits[index+1]
962 if (next_commit.commit_sha1 == sha1 and pos != int(pos)):
963 # join the line back to the node point
964 # This need to be done only if we modified it
965 in_line.append((pos, pos-0.5, self.colours[sha1]))
969 in_line.append((pos, pos, self.colours[sha1]))
972 def _go_clicked_cb(self, widget, revid):
973 """Callback for when the go button for a parent is clicked."""
975 self.treeview.set_cursor(self.index[revid])
977 print "Revision %s not present in the list" % revid
978 # revid == 0 is the parent of the first commit
980 print "Try running gitview without any options"
982 self.treeview.grab_focus()
984 def _show_clicked_cb(self, widget, commit_sha1, parent_sha1, encoding):
985 """Callback for when the show button for a parent is clicked."""
986 window = DiffWindow()
987 window.set_diff(commit_sha1, parent_sha1, encoding)
988 self.treeview.grab_focus()
990 if __name__ == "__main__":
993 if (len(sys.argv) > 1 ):
994 if (sys.argv[1] == "--without-diff"):
997 view = GitView( without_diff != 1)
998 view.run(sys.argv[without_diff:])