{"thread":{"id":"44770","subject":"Gitview Shell Injection Vulnerability","startedAt":"2016-12-27T08:34:21Z","lastAt":"2017-01-02T06:46:44Z","messageCount":8,"participants":["Javantea","Stefan Beller","Jeff King","Junio C Hamano","Aneesh Kumar K.V"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"308367","messageId":"20161227082922.8B7A813893D@mail.altsci.com","threadId":"44770","inReplyTo":null,"subject":"Gitview Shell Injection Vulnerability","fromName":"Javantea","fromEmail":"jvoss@altsci.com","sentAt":null,"receivedAt":"2016-12-27T08:34:21Z","isPatch":false,"sender":{"key":"jvoss@altsci.com","avatar":null},"body":"I have found a shell injection vulnerability in contrib/gitview/gitview.\n\nGitview Shell Injection Vulnerability\n\nVersions affected: 8cb711c8a5-1d1bdafd64 (<=2.11.0)\n\nGitview executes shell commands using string concatenation with user supplied data, filenames and branch names. Running Gitview and interacting with the user interface with a malicious filename or branch name in the current repository results in malicious commands being executed as the current user.\n\nAnnotateWindow.add_file_data(self, filename, commit_sha1, line_num):\n    fp = os.popen(\"git cat-file blob \" + commit_sha1 +\":\"+filename)\n\nAnnotateWindow.annotate(self, filename, commit_sha1, line_num):\n    fp = os.popen(\"git ls-tree \"+ commit_sha1 + \" -- \" + filename)\n    fp = os.popen(\"git blame --incremental -C -C -- \" + filename + \" \" + commit_sha1)\n\nGitView.set_branch(self, args):\n    fp = os.popen(\"git rev-parse --sq --default HEAD \" + list_to_string(args, 1))\n    fp = os.popen(\"git rev-list  --header --topo-order --parents \" + git_rev_list_cmd)\n\nThe program also has other uses of os.popen but none use values that the user can manipulate. However, the fix should definitely replace these instances so that the code might one day pass pylint and manual code review easier.\n\nThe function os.popen has been replaced by safer functions in the subprocess module. The code can be improved easily because it requires very little change to convert the code to work with arrays of strings instead of strings.\n\nIf you have any questions or would like a patch, please let me know.\n\nRegards,\nJavantea\n"},{"id":"308399","messageId":"CAGZ79kbOjpi4SBLPVaKXJ+L8VN+0N=ZqgDcHeYydkKsS20yR1Q@mail.gmail.com","threadId":"44770","inReplyTo":"20161227082922.8B7A813893D@mail.altsci.com","subject":"Re: Gitview Shell Injection Vulnerability","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-12-27T18:45:58Z","receivedAt":"2016-12-27T18:54:29Z","isPatch":false,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"+cc the author of gitview\n\nOn Tue, Dec 27, 2016 at 12:29 AM, Javantea <jvoss@altsci.com> wrote:\n> I have found a shell injection vulnerability in contrib/gitview/gitview.\n>\n> Gitview Shell Injection Vulnerability\n>\n> Versions affected: 8cb711c8a5-1d1bdafd64 (<=2.11.0)\n>\n> Gitview executes shell commands using string concatenation with user supplied data, filenames and branch names. Running Gitview and interacting with the user interface with a malicious filename or branch name in the current repository results in malicious commands being executed as the current user.\n>\n> AnnotateWindow.add_file_data(self, filename, commit_sha1, line_num):\n>     fp = os.popen(\"git cat-file blob \" + commit_sha1 +\":\"+filename)\n>\n> AnnotateWindow.annotate(self, filename, commit_sha1, line_num):\n>     fp = os.popen(\"git ls-tree \"+ commit_sha1 + \" -- \" + filename)\n>     fp = os.popen(\"git blame --incremental -C -C -- \" + filename + \" \" + commit_sha1)\n>\n> GitView.set_branch(self, args):\n>     fp = os.popen(\"git rev-parse --sq --default HEAD \" + list_to_string(args, 1))\n>     fp = os.popen(\"git rev-list  --header --topo-order --parents \" + git_rev_list_cmd)\n>\n> The program also has other uses of os.popen but none use values that the user can manipulate. However, the fix should definitely replace these instances so that the code might one day pass pylint and manual code review easier.\n>\n> The function os.popen has been replaced by safer functions in the subprocess module. The code can be improved easily because it requires very little change to convert the code to work with arrays of strings instead of strings.\n>\n> If you have any questions or would like a patch, please let me know.\n>\n\nI guess you could send a patch to fix it. It is unclear to me\nhow the patch submission process for these work, though.\nPlease see contrib/README to see why it is unclear to me.\n\n> I expect that things that start their life in the contrib/ area\n> to graduate out of contrib/ once they mature, either by becoming\n> projects on their own, or moving to the toplevel directory.  On\n> the other hand, I expect I'll be proposing removal of disused\n> and inactive ones from time to time.\n\nMaybe it's time for a spring cleanup and remove some old (dead?)\nprojects from contrib?\n\nThanks,\nStefan\n"},{"id":"308436","messageId":"20161228064255.f4akjdsq24r2hqn7@sigill.intra.peff.net","threadId":"44770","inReplyTo":"CAGZ79kbOjpi4SBLPVaKXJ+L8VN+0N=ZqgDcHeYydkKsS20yR1Q@mail.gmail.com","subject":"Re: Gitview Shell Injection Vulnerability","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2016-12-28T06:42:55Z","receivedAt":"2016-12-28T06:43:06Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Dec 27, 2016 at 10:45:58AM -0800, Stefan Beller wrote:\n\n> > I expect that things that start their life in the contrib/ area\n> > to graduate out of contrib/ once they mature, either by becoming\n> > projects on their own, or moving to the toplevel directory.  On\n> > the other hand, I expect I'll be proposing removal of disused\n> > and inactive ones from time to time.\n> \n> Maybe it's time for a spring cleanup and remove some old (dead?)\n> projects from contrib?\n\nYeah, that was my thought on reading the vulnerability report: \"that's\nstill around?\". Looks like it hasn't had a substantive commit since\n2007. But I dunno, maybe it's just finished, and doesn't need any more\nchanges. :)\n\n-Peff\n"},{"id":"308453","messageId":"20161228172837.24395-1-sbeller@google.com","threadId":"44770","inReplyTo":"20161228064255.f4akjdsq24r2hqn7@sigill.intra.peff.net","subject":"[PATCH] contrib: remove gitview","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-12-28T17:28:37Z","receivedAt":"2016-12-28T17:29:35Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"gitview did not have meaningful contributions since 2007, which gives the\nimpression it is either a mature or dead project.\n\nIn both cases we should not carry it in git.git as the README for contrib\nstates we only want to carry experimental things to give early exposure.\n\nRecently a security vulnerability was reported by Javantea, so the decision\nto either fix the issue or remove the code in question becomes a bit\nmore urgent.\n\nReported-by: Javantea <jvoss@altsci.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n contrib/gitview/gitview     | 1305 -------------------------------------------\n contrib/gitview/gitview.txt |   57 --\n 2 files changed, 1362 deletions(-)\n delete mode 100755 contrib/gitview/gitview\n delete mode 100644 contrib/gitview/gitview.txt\n\ndiff --git a/contrib/gitview/gitview b/contrib/gitview/gitview\ndeleted file mode 100755\nindex 4e23c650fe..0000000000\n--- a/contrib/gitview/gitview\n+++ /dev/null\n@@ -1,1305 +0,0 @@\n-#! /usr/bin/env python\n-\n-# This program is free software; you can redistribute it and/or modify\n-# it under the terms of the GNU General Public License as published by\n-# the Free Software Foundation; either version 2 of the License, or\n-# (at your option) any later version.\n-\n-\"\"\" gitview\n-GUI browser for git repository\n-This program is based on bzrk by Scott James Remnant <scott@ubuntu.com>\n-\"\"\"\n-__copyright__ = \"Copyright (C) 2006 Hewlett-Packard Development Company, L.P.\"\n-__copyright__ = \"Copyright (C) 2007 Aneesh Kumar K.V <aneesh.kumar@gmail.com\"\n-__author__    = \"Aneesh Kumar K.V <aneesh.kumar@gmail.com>\"\n-\n-\n-import sys\n-import os\n-import gtk\n-import pygtk\n-import pango\n-import re\n-import time\n-import gobject\n-import cairo\n-import math\n-import string\n-import fcntl\n-\n-have_gtksourceview2 = False\n-have_gtksourceview = False\n-try:\n-    import gtksourceview2\n-    have_gtksourceview2 = True\n-except ImportError:\n-    try:\n-        import gtksourceview\n-        have_gtksourceview = True\n-    except ImportError:\n-        print \"Running without gtksourceview2 or gtksourceview module\"\n-\n-re_ident = re.compile('(author|committer) (?P<ident>.*) (?P<epoch>\\d+) (?P<tz>[+-]\\d{4})')\n-\n-def list_to_string(args, skip):\n-\tcount = len(args)\n-\ti = skip\n-\tstr_arg=\" \"\n-\twhile (i < count ):\n-\t\tstr_arg = str_arg + args[i]\n-\t\tstr_arg = str_arg + \" \"\n-\t\ti = i+1\n-\n-\treturn str_arg\n-\n-def show_date(epoch, tz):\n-\tsecs = float(epoch)\n-\ttzsecs = float(tz[1:3]) * 3600\n-\ttzsecs += float(tz[3:5]) * 60\n-\tif (tz[0] == \"+\"):\n-\t\tsecs += tzsecs\n-\telse:\n-\t\tsecs -= tzsecs\n-\n-\treturn time.strftime(\"%Y-%m-%d %H:%M:%S\", time.gmtime(secs))\n-\n-def get_source_buffer_and_view():\n-\tif have_gtksourceview2:\n-\t\tbuffer = gtksourceview2.Buffer()\n-\t\tslm = gtksourceview2.LanguageManager()\n-\t\tgsl = slm.get_language(\"diff\")\n-\t\tbuffer.set_highlight_syntax(True)\n-\t\tbuffer.set_language(gsl)\n-\t\tview = gtksourceview2.View(buffer)\n-\telif have_gtksourceview:\n-\t\tbuffer = gtksourceview.SourceBuffer()\n-\t\tslm = gtksourceview.SourceLanguagesManager()\n-\t\tgsl = slm.get_language_from_mime_type(\"text/x-patch\")\n-\t\tbuffer.set_highlight(True)\n-\t\tbuffer.set_language(gsl)\n-\t\tview = gtksourceview.SourceView(buffer)\n-\telse:\n-\t\tbuffer = gtk.TextBuffer()\n-\t\tview = gtk.TextView(buffer)\n-\treturn (buffer, view)\n-\n-\n-class CellRendererGraph(gtk.GenericCellRenderer):\n-\t\"\"\"Cell renderer for directed graph.\n-\n-\tThis module contains the implementation of a custom GtkCellRenderer that\n-\tdraws part of the directed graph based on the lines suggested by the code\n-\tin graph.py.\n-\n-\tBecause we're shiny, we use Cairo to do this, and because we're naughty\n-\twe cheat and draw over the bits of the TreeViewColumn that are supposed to\n-\tjust be for the background.\n-\n-\tProperties:\n-\tnode              (column, colour, [ names ]) tuple to draw revision node,\n-\tin_lines          (start, end, colour) tuple list to draw inward lines,\n-\tout_lines         (start, end, colour) tuple list to draw outward lines.\n-\t\"\"\"\n-\n-\t__gproperties__ = {\n-\t\"node\":         ( gobject.TYPE_PYOBJECT, \"node\",\n-\t\t\t  \"revision node instruction\",\n-\t\t\t  gobject.PARAM_WRITABLE\n-\t\t\t),\n-\t\"in-lines\":     ( gobject.TYPE_PYOBJECT, \"in-lines\",\n-\t\t\t  \"instructions to draw lines into the cell\",\n-\t\t\t  gobject.PARAM_WRITABLE\n-\t\t\t),\n-\t\"out-lines\":    ( gobject.TYPE_PYOBJECT, \"out-lines\",\n-\t\t\t  \"instructions to draw lines out of the cell\",\n-\t\t\t  gobject.PARAM_WRITABLE\n-\t\t\t),\n-\t}\n-\n-\tdef do_set_property(self, property, value):\n-\t\t\"\"\"Set properties from GObject properties.\"\"\"\n-\t\tif property.name == \"node\":\n-\t\t\tself.node = value\n-\t\telif property.name == \"in-lines\":\n-\t\t\tself.in_lines = value\n-\t\telif property.name == \"out-lines\":\n-\t\t\tself.out_lines = value\n-\t\telse:\n-\t\t\traise AttributeError, \"no such property: '%s'\" % property.name\n-\n-\tdef box_size(self, widget):\n-\t\t\"\"\"Calculate box size based on widget's font.\n-\n-\t\tCache this as it's probably expensive to get.  It ensures that we\n-\t\tdraw the graph at least as large as the text.\n-\t\t\"\"\"\n-\t\ttry:\n-\t\t\treturn self._box_size\n-\t\texcept AttributeError:\n-\t\t\tpango_ctx = widget.get_pango_context()\n-\t\t\tfont_desc = widget.get_style().font_desc\n-\t\t\tmetrics = pango_ctx.get_metrics(font_desc)\n-\n-\t\t\tascent = pango.PIXELS(metrics.get_ascent())\n-\t\t\tdescent = pango.PIXELS(metrics.get_descent())\n-\n-\t\t\tself._box_size = ascent + descent + 6\n-\t\t\treturn self._box_size\n-\n-\tdef set_colour(self, ctx, colour, bg, fg):\n-\t\t\"\"\"Set the context source colour.\n-\n-\t\tPicks a distinct colour based on an internal wheel; the bg\n-\t\tparameter provides the value that should be assigned to the 'zero'\n-\t\tcolours and the fg parameter provides the multiplier that should be\n-\t\tapplied to the foreground colours.\n-\t\t\"\"\"\n-\t\tcolours = [\n-\t\t    ( 1.0, 0.0, 0.0 ),\n-\t\t    ( 1.0, 1.0, 0.0 ),\n-\t\t    ( 0.0, 1.0, 0.0 ),\n-\t\t    ( 0.0, 1.0, 1.0 ),\n-\t\t    ( 0.0, 0.0, 1.0 ),\n-\t\t    ( 1.0, 0.0, 1.0 ),\n-\t\t    ]\n-\n-\t\tcolour %= len(colours)\n-\t\tred   = (colours[colour][0] * fg) or bg\n-\t\tgreen = (colours[colour][1] * fg) or bg\n-\t\tblue  = (colours[colour][2] * fg) or bg\n-\n-\t\tctx.set_source_rgb(red, green, blue)\n-\n-\tdef on_get_size(self, widget, cell_area):\n-\t\t\"\"\"Return the size we need for this cell.\n-\n-\t\tEach cell is drawn individually and is only as wide as it needs\n-\t\tto be, we let the TreeViewColumn take care of making them all\n-\t\tline up.\n-\t\t\"\"\"\n-\t\tbox_size = self.box_size(widget)\n-\n-\t\tcols = self.node[0]\n-\t\tfor start, end, colour in self.in_lines + self.out_lines:\n-\t\t\tcols = int(max(cols, start, end))\n-\n-\t\t(column, colour, names) = self.node\n-\t\tnames_len = 0\n-\t\tif (len(names) != 0):\n-\t\t\tfor item in names:\n-\t\t\t\tnames_len += len(item)\n-\n-\t\twidth = box_size * (cols + 1 ) + names_len\n-\t\theight = box_size\n-\n-\t\t# FIXME I have no idea how to use cell_area properly\n-\t\treturn (0, 0, width, height)\n-\n-\tdef on_render(self, window, widget, bg_area, cell_area, exp_area, flags):\n-\t\t\"\"\"Render an individual cell.\n-\n-\t\tDraws the cell contents using cairo, taking care to clip what we\n-\t\tdo to within the background area so we don't draw over other cells.\n-\t\tNote that we're a bit naughty there and should really be drawing\n-\t\tin the cell_area (or even the exposed area), but we explicitly don't\n-\t\twant any gutter.\n-\n-\t\tWe try and be a little clever, if the line we need to draw is going\n-\t\tto cross other columns we actually draw it as in the .---' style\n-\t\tinstead of a pure diagonal ... this reduces confusion by an\n-\t\tincredible amount.\n-\t\t\"\"\"\n-\t\tctx = window.cairo_create()\n-\t\tctx.rectangle(bg_area.x, bg_area.y, bg_area.width, bg_area.height)\n-\t\tctx.clip()\n-\n-\t\tbox_size = self.box_size(widget)\n-\n-\t\tctx.set_line_width(box_size / 8)\n-\t\tctx.set_line_cap(cairo.LINE_CAP_SQUARE)\n-\n-\t\t# Draw lines into the cell\n-\t\tfor start, end, colour in self.in_lines:\n-\t\t\tctx.move_to(cell_area.x + box_size * start + box_size / 2,\n-\t\t\t\t\tbg_area.y - bg_area.height / 2)\n-\n-\t\t\tif start - end > 1:\n-\t\t\t\tctx.line_to(cell_area.x + box_size * start, bg_area.y)\n-\t\t\t\tctx.line_to(cell_area.x + box_size * end + box_size, bg_area.y)\n-\t\t\telif start - end < -1:\n-\t\t\t\tctx.line_to(cell_area.x + box_size * start + box_size,\n-\t\t\t\t\t\tbg_area.y)\n-\t\t\t\tctx.line_to(cell_area.x + box_size * end, bg_area.y)\n-\n-\t\t\tctx.line_to(cell_area.x + box_size * end + box_size / 2,\n-\t\t\t\t\tbg_area.y + bg_area.height / 2)\n-\n-\t\t\tself.set_colour(ctx, colour, 0.0, 0.65)\n-\t\t\tctx.stroke()\n-\n-\t\t# Draw lines out of the cell\n-\t\tfor start, end, colour in self.out_lines:\n-\t\t\tctx.move_to(cell_area.x + box_size * start + box_size / 2,\n-\t\t\t\t\tbg_area.y + bg_area.height / 2)\n-\n-\t\t\tif start - end > 1:\n-\t\t\t\tctx.line_to(cell_area.x + box_size * start,\n-\t\t\t\t\t\tbg_area.y + bg_area.height)\n-\t\t\t\tctx.line_to(cell_area.x + box_size * end + box_size,\n-\t\t\t\t\t\tbg_area.y + bg_area.height)\n-\t\t\telif start - end < -1:\n-\t\t\t\tctx.line_to(cell_area.x + box_size * start + box_size,\n-\t\t\t\t\t\tbg_area.y + bg_area.height)\n-\t\t\t\tctx.line_to(cell_area.x + box_size * end,\n-\t\t\t\t\t\tbg_area.y + bg_area.height)\n-\n-\t\t\tctx.line_to(cell_area.x + box_size * end + box_size / 2,\n-\t\t\t\t\tbg_area.y + bg_area.height / 2 + bg_area.height)\n-\n-\t\t\tself.set_colour(ctx, colour, 0.0, 0.65)\n-\t\t\tctx.stroke()\n-\n-\t\t# Draw the revision node in the right column\n-\t\t(column, colour, names) = self.node\n-\t\tctx.arc(cell_area.x + box_size * column + box_size / 2,\n-\t\t\t\tcell_area.y + cell_area.height / 2,\n-\t\t\t\tbox_size / 4, 0, 2 * math.pi)\n-\n-\n-\t\tself.set_colour(ctx, colour, 0.0, 0.5)\n-\t\tctx.stroke_preserve()\n-\n-\t\tself.set_colour(ctx, colour, 0.5, 1.0)\n-\t\tctx.fill_preserve()\n-\n-\t\tif (len(names) != 0):\n-\t\t\tname = \" \"\n-\t\t\tfor item in names:\n-\t\t\t\tname = name + item + \" \"\n-\n-\t\t\tctx.set_font_size(13)\n-\t\t\tif (flags & 1):\n-\t\t\t\tself.set_colour(ctx, colour, 0.5, 1.0)\n-\t\t\telse:\n-\t\t\t\tself.set_colour(ctx, colour, 0.0, 0.5)\n-\t\t\tctx.show_text(name)\n-\n-class Commit(object):\n-\t\"\"\" This represent a commit object obtained after parsing the git-rev-list\n-\toutput \"\"\"\n-\n-\t__slots__ = ['children_sha1', 'message', 'author', 'date', 'committer',\n-\t\t\t\t 'commit_date', 'commit_sha1', 'parent_sha1']\n-\n-\tchildren_sha1 = {}\n-\n-\tdef __init__(self, commit_lines):\n-\t\tself.message\t\t= \"\"\n-\t\tself.author\t\t= \"\"\n-\t\tself.date\t\t= \"\"\n-\t\tself.committer\t\t= \"\"\n-\t\tself.commit_date\t= \"\"\n-\t\tself.commit_sha1\t= \"\"\n-\t\tself.parent_sha1\t= [ ]\n-\t\tself.parse_commit(commit_lines)\n-\n-\n-\tdef parse_commit(self, commit_lines):\n-\n-\t\t# First line is the sha1 lines\n-\t\tline = string.strip(commit_lines[0])\n-\t\tsha1 = re.split(\" \", line)\n-\t\tself.commit_sha1 = sha1[0]\n-\t\tself.parent_sha1 = sha1[1:]\n-\n-\t\t#build the child list\n-\t\tfor parent_id in self.parent_sha1:\n-\t\t\ttry:\n-\t\t\t\tCommit.children_sha1[parent_id].append(self.commit_sha1)\n-\t\t\texcept KeyError:\n-\t\t\t\tCommit.children_sha1[parent_id] = [self.commit_sha1]\n-\n-\t\t# IF we don't have parent\n-\t\tif (len(self.parent_sha1) == 0):\n-\t\t\tself.parent_sha1 = [0]\n-\n-\t\tfor line in commit_lines[1:]:\n-\t\t\tm = re.match(\"^ \", line)\n-\t\t\tif (m != None):\n-\t\t\t\t# First line of the commit message used for short log\n-\t\t\t\tif self.message == \"\":\n-\t\t\t\t\tself.message = string.strip(line)\n-\t\t\t\tcontinue\n-\n-\t\t\tm = re.match(\"tree\", line)\n-\t\t\tif (m != None):\n-\t\t\t\tcontinue\n-\n-\t\t\tm = re.match(\"parent\", line)\n-\t\t\tif (m != None):\n-\t\t\t\tcontinue\n-\n-\t\t\tm = re_ident.match(line)\n-\t\t\tif (m != None):\n-\t\t\t\tdate = show_date(m.group('epoch'), m.group('tz'))\n-\t\t\t\tif m.group(1) == \"author\":\n-\t\t\t\t\tself.author = m.group('ident')\n-\t\t\t\t\tself.date = date\n-\t\t\t\telif m.group(1) == \"committer\":\n-\t\t\t\t\tself.committer = m.group('ident')\n-\t\t\t\t\tself.commit_date = date\n-\n-\t\t\t\tcontinue\n-\n-\tdef get_message(self, with_diff=0):\n-\t\tif (with_diff == 1):\n-\t\t\tmessage = self.diff_tree()\n-\t\telse:\n-\t\t\tfp = os.popen(\"git cat-file commit \" + self.commit_sha1)\n-\t\t\tmessage = fp.read()\n-\t\t\tfp.close()\n-\n-\t\treturn message\n-\n-\tdef diff_tree(self):\n-\t\tfp = os.popen(\"git diff-tree --pretty --cc  -v -p --always \" +  self.commit_sha1)\n-\t\tdiff = fp.read()\n-\t\tfp.close()\n-\t\treturn diff\n-\n-class AnnotateWindow(object):\n-\t\"\"\"Annotate window.\n-\tThis object represents and manages a single window containing the\n-\tannotate information of the file\n-\t\"\"\"\n-\n-\tdef __init__(self):\n-\t\tself.window = gtk.Window(gtk.WINDOW_TOPLEVEL)\n-\t\tself.window.set_border_width(0)\n-\t\tself.window.set_title(\"Git repository browser annotation window\")\n-\t\tself.prev_read = \"\"\n-\n-\t\t# Use two thirds of the screen by default\n-\t\tscreen = self.window.get_screen()\n-\t\tmonitor = screen.get_monitor_geometry(0)\n-\t\twidth = int(monitor.width * 0.66)\n-\t\theight = int(monitor.height * 0.66)\n-\t\tself.window.set_default_size(width, height)\n-\n-\tdef add_file_data(self, filename, commit_sha1, line_num):\n-\t\tfp = os.popen(\"git cat-file blob \" + commit_sha1 +\":\"+filename)\n-\t\ti = 1;\n-\t\tfor line in fp.readlines():\n-\t\t\tline = string.rstrip(line)\n-\t\t\tself.model.append(None, [\"HEAD\", filename, line, i])\n-\t\t\ti = i+1\n-\t\tfp.close()\n-\n-\t\t# now set the cursor position\n-\t\tself.treeview.set_cursor(line_num-1)\n-\t\tself.treeview.grab_focus()\n-\n-\tdef _treeview_cursor_cb(self, *args):\n-\t\t\"\"\"Callback for when the treeview cursor changes.\"\"\"\n-\t\t(path, col) = self.treeview.get_cursor()\n-\t\tcommit_sha1 = self.model[path][0]\n-\t\tcommit_msg = \"\"\n-\t\tfp = os.popen(\"git cat-file commit \" + commit_sha1)\n-\t\tfor line in fp.readlines():\n-\t\t\tcommit_msg =  commit_msg + line\n-\t\tfp.close()\n-\n-\t\tself.commit_buffer.set_text(commit_msg)\n-\n-\tdef _treeview_row_activated(self, *args):\n-\t\t\"\"\"Callback for when the treeview row gets selected.\"\"\"\n-\t\t(path, col) = self.treeview.get_cursor()\n-\t\tcommit_sha1 = self.model[path][0]\n-\t\tfilename    = self.model[path][1]\n-\t\tline_num    = self.model[path][3]\n-\n-\t\twindow = AnnotateWindow();\n-\t\tfp = os.popen(\"git rev-parse \"+ commit_sha1 + \"~1\")\n-\t\tcommit_sha1 = string.strip(fp.readline())\n-\t\tfp.close()\n-\t\twindow.annotate(filename, commit_sha1, line_num)\n-\n-\tdef data_ready(self, source, condition):\n-\t\twhile (1):\n-\t\t\ttry :\n-\t\t\t\t# A simple readline doesn't work\n-\t\t\t\t# a readline bug ??\n-\t\t\t\tbuffer = source.read(100)\n-\n-\t\t\texcept:\n-\t\t\t\t# resource temporary not available\n-\t\t\t\treturn True\n-\n-\t\t\tif (len(buffer) == 0):\n-\t\t\t\tgobject.source_remove(self.io_watch_tag)\n-\t\t\t\tsource.close()\n-\t\t\t\treturn False\n-\n-\t\t\tif (self.prev_read != \"\"):\n-\t\t\t\tbuffer = self.prev_read + buffer\n-\t\t\t\tself.prev_read = \"\"\n-\n-\t\t\tif (buffer[len(buffer) -1] != '\\n'):\n-\t\t\t\ttry:\n-\t\t\t\t\tnewline_index = buffer.rindex(\"\\n\")\n-\t\t\t\texcept ValueError:\n-\t\t\t\t\tnewline_index = 0\n-\n-\t\t\t\tself.prev_read = buffer[newline_index:(len(buffer))]\n-\t\t\t\tbuffer = buffer[0:newline_index]\n-\n-\t\t\tfor buff in buffer.split(\"\\n\"):\n-\t\t\t\tannotate_line = re.compile('^([0-9a-f]{40}) (.+) (.+) (.+)$')\n-\t\t\t\tm = annotate_line.match(buff)\n-\t\t\t\tif not m:\n-\t\t\t\t\tannotate_line = re.compile('^(filename) (.+)$')\n-\t\t\t\t\tm = annotate_line.match(buff)\n-\t\t\t\t\tif not m:\n-\t\t\t\t\t\tcontinue\n-\t\t\t\t\tfilename = m.group(2)\n-\t\t\t\telse:\n-\t\t\t\t\tself.commit_sha1 = m.group(1)\n-\t\t\t\t\tself.source_line = int(m.group(2))\n-\t\t\t\t\tself.result_line = int(m.group(3))\n-\t\t\t\t\tself.count\t    = int(m.group(4))\n-\t\t\t\t\t#set the details only when we have the file name\n-\t\t\t\t\tcontinue\n-\n-\t\t\t\twhile (self.count > 0):\n-\t\t\t\t\t# set at result_line + count-1 the sha1 as commit_sha1\n-\t\t\t\t\tself.count = self.count - 1\n-\t\t\t\t\titer = self.model.iter_nth_child(None, self.result_line + self.count-1)\n-\t\t\t\t\tself.model.set(iter, 0, self.commit_sha1, 1, filename, 3, self.source_line)\n-\n-\n-\tdef annotate(self, filename, commit_sha1, line_num):\n-\t\t# verify the commit_sha1 specified has this filename\n-\n-\t\tfp = os.popen(\"git ls-tree \"+ commit_sha1 + \" -- \" + filename)\n-\t\tline = string.strip(fp.readline())\n-\t\tif line == '':\n-\t\t\t# pop up the message the file is not there as a part of the commit\n-\t\t\tfp.close()\n-\t\t\tdialog = gtk.MessageDialog(parent=None, flags=0,\n-\t\t\t\t\ttype=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_CLOSE,\n-\t\t\t\t\tmessage_format=None)\n-\t\t\tdialog.set_markup(\"The file %s is not present in the parent commit %s\" % (filename, commit_sha1))\n-\t\t\tdialog.run()\n-\t\t\tdialog.destroy()\n-\t\t\treturn\n-\n-\t\tfp.close()\n-\n-\t\tvpan = gtk.VPaned();\n-\t\tself.window.add(vpan);\n-\t\tvpan.show()\n-\n-\t\tscrollwin = gtk.ScrolledWindow()\n-\t\tscrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)\n-\t\tscrollwin.set_shadow_type(gtk.SHADOW_IN)\n-\t\tvpan.pack1(scrollwin, True, True);\n-\t\tscrollwin.show()\n-\n-\t\tself.model = gtk.TreeStore(str, str, str, int)\n-\t\tself.treeview = gtk.TreeView(self.model)\n-\t\tself.treeview.set_rules_hint(True)\n-\t\tself.treeview.set_search_column(0)\n-\t\tself.treeview.connect(\"cursor-changed\", self._treeview_cursor_cb)\n-\t\tself.treeview.connect(\"row-activated\", self._treeview_row_activated)\n-\t\tscrollwin.add(self.treeview)\n-\t\tself.treeview.show()\n-\n-\t\tcell = gtk.CellRendererText()\n-\t\tcell.set_property(\"width-chars\", 10)\n-\t\tcell.set_property(\"ellipsize\", pango.ELLIPSIZE_END)\n-\t\tcolumn = gtk.TreeViewColumn(\"Commit\")\n-\t\tcolumn.set_resizable(True)\n-\t\tcolumn.pack_start(cell, expand=True)\n-\t\tcolumn.add_attribute(cell, \"text\", 0)\n-\t\tself.treeview.append_column(column)\n-\n-\t\tcell = gtk.CellRendererText()\n-\t\tcell.set_property(\"width-chars\", 20)\n-\t\tcell.set_property(\"ellipsize\", pango.ELLIPSIZE_END)\n-\t\tcolumn = gtk.TreeViewColumn(\"File Name\")\n-\t\tcolumn.set_resizable(True)\n-\t\tcolumn.pack_start(cell, expand=True)\n-\t\tcolumn.add_attribute(cell, \"text\", 1)\n-\t\tself.treeview.append_column(column)\n-\n-\t\tcell = gtk.CellRendererText()\n-\t\tcell.set_property(\"width-chars\", 20)\n-\t\tcell.set_property(\"ellipsize\", pango.ELLIPSIZE_END)\n-\t\tcolumn = gtk.TreeViewColumn(\"Data\")\n-\t\tcolumn.set_resizable(True)\n-\t\tcolumn.pack_start(cell, expand=True)\n-\t\tcolumn.add_attribute(cell, \"text\", 2)\n-\t\tself.treeview.append_column(column)\n-\n-\t\t# The commit message window\n-\t\tscrollwin = gtk.ScrolledWindow()\n-\t\tscrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)\n-\t\tscrollwin.set_shadow_type(gtk.SHADOW_IN)\n-\t\tvpan.pack2(scrollwin, True, True);\n-\t\tscrollwin.show()\n-\n-\t\tcommit_text = gtk.TextView()\n-\t\tself.commit_buffer = gtk.TextBuffer()\n-\t\tcommit_text.set_buffer(self.commit_buffer)\n-\t\tscrollwin.add(commit_text)\n-\t\tcommit_text.show()\n-\n-\t\tself.window.show()\n-\n-\t\tself.add_file_data(filename, commit_sha1, line_num)\n-\n-\t\tfp = os.popen(\"git blame --incremental -C -C -- \" + filename + \" \" + commit_sha1)\n-\t\tflags = fcntl.fcntl(fp.fileno(), fcntl.F_GETFL)\n-\t\tfcntl.fcntl(fp.fileno(), fcntl.F_SETFL, flags | os.O_NONBLOCK)\n-\t\tself.io_watch_tag = gobject.io_add_watch(fp, gobject.IO_IN, self.data_ready)\n-\n-\n-class DiffWindow(object):\n-\t\"\"\"Diff window.\n-\tThis object represents and manages a single window containing the\n-\tdifferences between two revisions on a branch.\n-\t\"\"\"\n-\n-\tdef __init__(self):\n-\t\tself.window = gtk.Window(gtk.WINDOW_TOPLEVEL)\n-\t\tself.window.set_border_width(0)\n-\t\tself.window.set_title(\"Git repository browser diff window\")\n-\n-\t\t# Use two thirds of the screen by default\n-\t\tscreen = self.window.get_screen()\n-\t\tmonitor = screen.get_monitor_geometry(0)\n-\t\twidth = int(monitor.width * 0.66)\n-\t\theight = int(monitor.height * 0.66)\n-\t\tself.window.set_default_size(width, height)\n-\n-\n-\t\tself.construct()\n-\n-\tdef construct(self):\n-\t\t\"\"\"Construct the window contents.\"\"\"\n-\t\tvbox = gtk.VBox()\n-\t\tself.window.add(vbox)\n-\t\tvbox.show()\n-\n-\t\tmenu_bar = gtk.MenuBar()\n-\t\tsave_menu = gtk.ImageMenuItem(gtk.STOCK_SAVE)\n-\t\tsave_menu.connect(\"activate\", self.save_menu_response, \"save\")\n-\t\tsave_menu.show()\n-\t\tmenu_bar.append(save_menu)\n-\t\tvbox.pack_start(menu_bar, expand=False, fill=True)\n-\t\tmenu_bar.show()\n-\n-\t\thpan = gtk.HPaned()\n-\n-\t\tscrollwin = gtk.ScrolledWindow()\n-\t\tscrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)\n-\t\tscrollwin.set_shadow_type(gtk.SHADOW_IN)\n-\t\thpan.pack1(scrollwin, True, True)\n-\t\tscrollwin.show()\n-\n-\t\t(self.buffer, sourceview) = get_source_buffer_and_view()\n-\n-\t\tsourceview.set_editable(False)\n-\t\tsourceview.modify_font(pango.FontDescription(\"Monospace\"))\n-\t\tscrollwin.add(sourceview)\n-\t\tsourceview.show()\n-\n-\t\t# The file hierarchy: a scrollable treeview\n-\t\tscrollwin = gtk.ScrolledWindow()\n-\t\tscrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)\n-\t\tscrollwin.set_shadow_type(gtk.SHADOW_IN)\n-\t\tscrollwin.set_size_request(20, -1)\n-\t\thpan.pack2(scrollwin, True, True)\n-\t\tscrollwin.show()\n-\n-\t\tself.model = gtk.TreeStore(str, str, str)\n-\t\tself.treeview = gtk.TreeView(self.model)\n-\t\tself.treeview.set_search_column(1)\n-\t\tself.treeview.connect(\"cursor-changed\", self._treeview_clicked)\n-\t\tscrollwin.add(self.treeview)\n-\t\tself.treeview.show()\n-\n-\t\tcell = gtk.CellRendererText()\n-\t\tcell.set_property(\"width-chars\", 20)\n-\t\tcolumn = gtk.TreeViewColumn(\"Select to annotate\")\n-\t\tcolumn.pack_start(cell, expand=True)\n-\t\tcolumn.add_attribute(cell, \"text\", 0)\n-\t\tself.treeview.append_column(column)\n-\n-\t\tvbox.pack_start(hpan, expand=True, fill=True)\n-\t\thpan.show()\n-\n-\tdef _treeview_clicked(self, *args):\n-\t\t\"\"\"Callback for when the treeview cursor changes.\"\"\"\n-\t\t(path, col) = self.treeview.get_cursor()\n-\t\tspecific_file = self.model[path][1]\n-\t\tcommit_sha1 =  self.model[path][2]\n-\t\tif specific_file ==  None :\n-\t\t\treturn\n-\t\telif specific_file ==  \"\" :\n-\t\t\tspecific_file =  None\n-\n-\t\twindow = AnnotateWindow();\n-\t\twindow.annotate(specific_file, commit_sha1, 1)\n-\n-\n-\tdef commit_files(self, commit_sha1, parent_sha1):\n-\t\tself.model.clear()\n-\t\tadd  = self.model.append(None, [ \"Added\", None, None])\n-\t\tdele = self.model.append(None, [ \"Deleted\", None, None])\n-\t\tmod  = self.model.append(None, [ \"Modified\", None, None])\n-\t\tdiff_tree = re.compile('^(:.{6}) (.{6}) (.{40}) (.{40}) (A|D|M)\\s(.+)$')\n-\t\tfp = os.popen(\"git diff-tree -r --no-commit-id \" + parent_sha1 + \" \" + commit_sha1)\n-\t\twhile 1:\n-\t\t\tline = string.strip(fp.readline())\n-\t\t\tif line == '':\n-\t\t\t\tbreak\n-\t\t\tm = diff_tree.match(line)\n-\t\t\tif not m:\n-\t\t\t\tcontinue\n-\n-\t\t\tattr = m.group(5)\n-\t\t\tfilename = m.group(6)\n-\t\t\tif attr == \"A\":\n-\t\t\t\tself.model.append(add,  [filename, filename, commit_sha1])\n-\t\t\telif attr == \"D\":\n-\t\t\t\tself.model.append(dele, [filename, filename, commit_sha1])\n-\t\t\telif attr == \"M\":\n-\t\t\t\tself.model.append(mod,  [filename, filename, commit_sha1])\n-\t\tfp.close()\n-\n-\t\tself.treeview.expand_all()\n-\n-\tdef set_diff(self, commit_sha1, parent_sha1, encoding):\n-\t\t\"\"\"Set the differences showed by this window.\n-\t\tCompares the two trees and populates the window with the\n-\t\tdifferences.\n-\t\t\"\"\"\n-\t\t# Diff with the first commit or the last commit shows nothing\n-\t\tif (commit_sha1 == 0 or parent_sha1 == 0 ):\n-\t\t\treturn\n-\n-\t\tfp = os.popen(\"git diff-tree -p \" + parent_sha1 + \" \" + commit_sha1)\n-\t\tself.buffer.set_text(unicode(fp.read(), encoding).encode('utf-8'))\n-\t\tfp.close()\n-\t\tself.commit_files(commit_sha1, parent_sha1)\n-\t\tself.window.show()\n-\n-\tdef save_menu_response(self, widget, string):\n-\t\tdialog = gtk.FileChooserDialog(\"Save..\", None, gtk.FILE_CHOOSER_ACTION_SAVE,\n-\t\t\t\t(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,\n-\t\t\t\t\tgtk.STOCK_SAVE, gtk.RESPONSE_OK))\n-\t\tdialog.set_default_response(gtk.RESPONSE_OK)\n-\t\tresponse = dialog.run()\n-\t\tif response == gtk.RESPONSE_OK:\n-\t\t\tpatch_buffer = self.buffer.get_text(self.buffer.get_start_iter(),\n-\t\t\t\t\tself.buffer.get_end_iter())\n-\t\t\tfp = open(dialog.get_filename(), \"w\")\n-\t\t\tfp.write(patch_buffer)\n-\t\t\tfp.close()\n-\t\tdialog.destroy()\n-\n-class GitView(object):\n-\t\"\"\" This is the main class\n-\t\"\"\"\n-\tversion = \"0.9\"\n-\n-\tdef __init__(self, with_diff=0):\n-\t\tself.with_diff = with_diff\n-\t\tself.window =\tgtk.Window(gtk.WINDOW_TOPLEVEL)\n-\t\tself.window.set_border_width(0)\n-\t\tself.window.set_title(\"Git repository browser\")\n-\n-\t\tself.get_encoding()\n-\t\tself.get_bt_sha1()\n-\n-\t\t# Use three-quarters of the screen by default\n-\t\tscreen = self.window.get_screen()\n-\t\tmonitor = screen.get_monitor_geometry(0)\n-\t\twidth = int(monitor.width * 0.75)\n-\t\theight = int(monitor.height * 0.75)\n-\t\tself.window.set_default_size(width, height)\n-\n-\t\t# FIXME AndyFitz!\n-\t\ticon = self.window.render_icon(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)\n-\t\tself.window.set_icon(icon)\n-\n-\t\tself.accel_group = gtk.AccelGroup()\n-\t\tself.window.add_accel_group(self.accel_group)\n-\t\tself.accel_group.connect_group(0xffc2, 0, gtk.ACCEL_LOCKED, self.refresh);\n-\t\tself.accel_group.connect_group(0xffc1, 0, gtk.ACCEL_LOCKED, self.maximize);\n-\t\tself.accel_group.connect_group(0xffc8, 0, gtk.ACCEL_LOCKED, self.fullscreen);\n-\t\tself.accel_group.connect_group(0xffc9, 0, gtk.ACCEL_LOCKED, self.unfullscreen);\n-\n-\t\tself.window.add(self.construct())\n-\n-\tdef refresh(self, widget, event=None, *arguments, **keywords):\n-\t\tself.get_encoding()\n-\t\tself.get_bt_sha1()\n-\t\tCommit.children_sha1 = {}\n-\t\tself.set_branch(sys.argv[without_diff:])\n-\t\tself.window.show()\n-\t\treturn True\n-\n-\tdef maximize(self, widget, event=None, *arguments, **keywords):\n-\t\tself.window.maximize()\n-\t\treturn True\n-\n-\tdef fullscreen(self, widget, event=None, *arguments, **keywords):\n-\t\tself.window.fullscreen()\n-\t\treturn True\n-\n-\tdef unfullscreen(self, widget, event=None, *arguments, **keywords):\n-\t\tself.window.unfullscreen()\n-\t\treturn True\n-\n-\tdef get_bt_sha1(self):\n-\t\t\"\"\" Update the bt_sha1 dictionary with the\n-\t\trespective sha1 details \"\"\"\n-\n-\t\tself.bt_sha1 = { }\n-\t\tls_remote = re.compile('^(.{40})\\trefs/([^^]+)(?:\\\\^(..))?$');\n-\t\tfp = os.popen('git ls-remote \"${GIT_DIR-.git}\"')\n-\t\twhile 1:\n-\t\t\tline = string.strip(fp.readline())\n-\t\t\tif line == '':\n-\t\t\t\tbreak\n-\t\t\tm = ls_remote.match(line)\n-\t\t\tif not m:\n-\t\t\t\tcontinue\n-\t\t\t(sha1, name) = (m.group(1), m.group(2))\n-\t\t\tif not self.bt_sha1.has_key(sha1):\n-\t\t\t\tself.bt_sha1[sha1] = []\n-\t\t\tself.bt_sha1[sha1].append(name)\n-\t\tfp.close()\n-\n-\tdef get_encoding(self):\n-\t\tfp = os.popen(\"git config --get i18n.commitencoding\")\n-\t\tself.encoding=string.strip(fp.readline())\n-\t\tfp.close()\n-\t\tif (self.encoding == \"\"):\n-\t\t\tself.encoding = \"utf-8\"\n-\n-\n-\tdef construct(self):\n-\t\t\"\"\"Construct the window contents.\"\"\"\n-\t\tvbox = gtk.VBox()\n-\t\tpaned = gtk.VPaned()\n-\t\tpaned.pack1(self.construct_top(), resize=False, shrink=True)\n-\t\tpaned.pack2(self.construct_bottom(), resize=False, shrink=True)\n-\t\tmenu_bar = gtk.MenuBar()\n-\t\tmenu_bar.set_pack_direction(gtk.PACK_DIRECTION_RTL)\n-\t\thelp_menu = gtk.MenuItem(\"Help\")\n-\t\tmenu = gtk.Menu()\n-\t\tabout_menu = gtk.MenuItem(\"About\")\n-\t\tmenu.append(about_menu)\n-\t\tabout_menu.connect(\"activate\", self.about_menu_response, \"about\")\n-\t\tabout_menu.show()\n-\t\thelp_menu.set_submenu(menu)\n-\t\thelp_menu.show()\n-\t\tmenu_bar.append(help_menu)\n-\t\tmenu_bar.show()\n-\t\tvbox.pack_start(menu_bar, expand=False, fill=True)\n-\t\tvbox.pack_start(paned, expand=True, fill=True)\n-\t\tpaned.show()\n-\t\tvbox.show()\n-\t\treturn vbox\n-\n-\n-\tdef construct_top(self):\n-\t\t\"\"\"Construct the top-half of the window.\"\"\"\n-\t\tvbox = gtk.VBox(spacing=6)\n-\t\tvbox.set_border_width(12)\n-\t\tvbox.show()\n-\n-\n-\t\tscrollwin = gtk.ScrolledWindow()\n-\t\tscrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)\n-\t\tscrollwin.set_shadow_type(gtk.SHADOW_IN)\n-\t\tvbox.pack_start(scrollwin, expand=True, fill=True)\n-\t\tscrollwin.show()\n-\n-\t\tself.treeview = gtk.TreeView()\n-\t\tself.treeview.set_rules_hint(True)\n-\t\tself.treeview.set_search_column(4)\n-\t\tself.treeview.connect(\"cursor-changed\", self._treeview_cursor_cb)\n-\t\tscrollwin.add(self.treeview)\n-\t\tself.treeview.show()\n-\n-\t\tcell = CellRendererGraph()\n-\t\tcolumn = gtk.TreeViewColumn()\n-\t\tcolumn.set_resizable(True)\n-\t\tcolumn.pack_start(cell, expand=True)\n-\t\tcolumn.add_attribute(cell, \"node\", 1)\n-\t\tcolumn.add_attribute(cell, \"in-lines\", 2)\n-\t\tcolumn.add_attribute(cell, \"out-lines\", 3)\n-\t\tself.treeview.append_column(column)\n-\n-\t\tcell = gtk.CellRendererText()\n-\t\tcell.set_property(\"width-chars\", 65)\n-\t\tcell.set_property(\"ellipsize\", pango.ELLIPSIZE_END)\n-\t\tcolumn = gtk.TreeViewColumn(\"Message\")\n-\t\tcolumn.set_resizable(True)\n-\t\tcolumn.pack_start(cell, expand=True)\n-\t\tcolumn.add_attribute(cell, \"text\", 4)\n-\t\tself.treeview.append_column(column)\n-\n-\t\tcell = gtk.CellRendererText()\n-\t\tcell.set_property(\"width-chars\", 40)\n-\t\tcell.set_property(\"ellipsize\", pango.ELLIPSIZE_END)\n-\t\tcolumn = gtk.TreeViewColumn(\"Author\")\n-\t\tcolumn.set_resizable(True)\n-\t\tcolumn.pack_start(cell, expand=True)\n-\t\tcolumn.add_attribute(cell, \"text\", 5)\n-\t\tself.treeview.append_column(column)\n-\n-\t\tcell = gtk.CellRendererText()\n-\t\tcell.set_property(\"ellipsize\", pango.ELLIPSIZE_END)\n-\t\tcolumn = gtk.TreeViewColumn(\"Date\")\n-\t\tcolumn.set_resizable(True)\n-\t\tcolumn.pack_start(cell, expand=True)\n-\t\tcolumn.add_attribute(cell, \"text\", 6)\n-\t\tself.treeview.append_column(column)\n-\n-\t\treturn vbox\n-\n-\tdef about_menu_response(self, widget, string):\n-\t\tdialog = gtk.AboutDialog()\n-\t\tdialog.set_name(\"Gitview\")\n-\t\tdialog.set_version(GitView.version)\n-\t\tdialog.set_authors([\"Aneesh Kumar K.V <aneesh.kumar@gmail.com>\"])\n-\t\tdialog.set_website(\"http://www.kernel.org/pub/software/scm/git/\")\n-\t\tdialog.set_copyright(\"Use and distribute under the terms of the GNU General Public License\")\n-\t\tdialog.set_wrap_license(True)\n-\t\tdialog.run()\n-\t\tdialog.destroy()\n-\n-\n-\tdef construct_bottom(self):\n-\t\t\"\"\"Construct the bottom half of the window.\"\"\"\n-\t\tvbox = gtk.VBox(False, spacing=6)\n-\t\tvbox.set_border_width(12)\n-\t\t(width, height) = self.window.get_size()\n-\t\tvbox.set_size_request(width, int(height / 2.5))\n-\t\tvbox.show()\n-\n-\t\tself.table = gtk.Table(rows=4, columns=4)\n-\t\tself.table.set_row_spacings(6)\n-\t\tself.table.set_col_spacings(6)\n-\t\tvbox.pack_start(self.table, expand=False, fill=True)\n-\t\tself.table.show()\n-\n-\t\talign = gtk.Alignment(0.0, 0.5)\n-\t\tlabel = gtk.Label()\n-\t\tlabel.set_markup(\"<b>Revision:</b>\")\n-\t\talign.add(label)\n-\t\tself.table.attach(align, 0, 1, 0, 1, gtk.FILL, gtk.FILL)\n-\t\tlabel.show()\n-\t\talign.show()\n-\n-\t\talign = gtk.Alignment(0.0, 0.5)\n-\t\tself.revid_label = gtk.Label()\n-\t\tself.revid_label.set_selectable(True)\n-\t\talign.add(self.revid_label)\n-\t\tself.table.attach(align, 1, 2, 0, 1, gtk.EXPAND | gtk.FILL, gtk.FILL)\n-\t\tself.revid_label.show()\n-\t\talign.show()\n-\n-\t\talign = gtk.Alignment(0.0, 0.5)\n-\t\tlabel = gtk.Label()\n-\t\tlabel.set_markup(\"<b>Committer:</b>\")\n-\t\talign.add(label)\n-\t\tself.table.attach(align, 0, 1, 1, 2, gtk.FILL, gtk.FILL)\n-\t\tlabel.show()\n-\t\talign.show()\n-\n-\t\talign = gtk.Alignment(0.0, 0.5)\n-\t\tself.committer_label = gtk.Label()\n-\t\tself.committer_label.set_selectable(True)\n-\t\talign.add(self.committer_label)\n-\t\tself.table.attach(align, 1, 2, 1, 2, gtk.EXPAND | gtk.FILL, gtk.FILL)\n-\t\tself.committer_label.show()\n-\t\talign.show()\n-\n-\t\talign = gtk.Alignment(0.0, 0.5)\n-\t\tlabel = gtk.Label()\n-\t\tlabel.set_markup(\"<b>Timestamp:</b>\")\n-\t\talign.add(label)\n-\t\tself.table.attach(align, 0, 1, 2, 3, gtk.FILL, gtk.FILL)\n-\t\tlabel.show()\n-\t\talign.show()\n-\n-\t\talign = gtk.Alignment(0.0, 0.5)\n-\t\tself.timestamp_label = gtk.Label()\n-\t\tself.timestamp_label.set_selectable(True)\n-\t\talign.add(self.timestamp_label)\n-\t\tself.table.attach(align, 1, 2, 2, 3, gtk.EXPAND | gtk.FILL, gtk.FILL)\n-\t\tself.timestamp_label.show()\n-\t\talign.show()\n-\n-\t\talign = gtk.Alignment(0.0, 0.5)\n-\t\tlabel = gtk.Label()\n-\t\tlabel.set_markup(\"<b>Parents:</b>\")\n-\t\talign.add(label)\n-\t\tself.table.attach(align, 0, 1, 3, 4, gtk.FILL, gtk.FILL)\n-\t\tlabel.show()\n-\t\talign.show()\n-\t\tself.parents_widgets = []\n-\n-\t\talign = gtk.Alignment(0.0, 0.5)\n-\t\tlabel = gtk.Label()\n-\t\tlabel.set_markup(\"<b>Children:</b>\")\n-\t\talign.add(label)\n-\t\tself.table.attach(align, 2, 3, 3, 4, gtk.FILL, gtk.FILL)\n-\t\tlabel.show()\n-\t\talign.show()\n-\t\tself.children_widgets = []\n-\n-\t\tscrollwin = gtk.ScrolledWindow()\n-\t\tscrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)\n-\t\tscrollwin.set_shadow_type(gtk.SHADOW_IN)\n-\t\tvbox.pack_start(scrollwin, expand=True, fill=True)\n-\t\tscrollwin.show()\n-\n-\t\t(self.message_buffer, sourceview) = get_source_buffer_and_view()\n-\n-\t\tsourceview.set_editable(False)\n-\t\tsourceview.modify_font(pango.FontDescription(\"Monospace\"))\n-\t\tscrollwin.add(sourceview)\n-\t\tsourceview.show()\n-\n-\t\treturn vbox\n-\n-\tdef _treeview_cursor_cb(self, *args):\n-\t\t\"\"\"Callback for when the treeview cursor changes.\"\"\"\n-\t\t(path, col) = self.treeview.get_cursor()\n-\t\tcommit = self.model[path][0]\n-\n-\t\tif commit.committer is not None:\n-\t\t\tcommitter = commit.committer\n-\t\t\ttimestamp = commit.commit_date\n-\t\t\tmessage   =  commit.get_message(self.with_diff)\n-\t\t\trevid_label = commit.commit_sha1\n-\t\telse:\n-\t\t\tcommitter = \"\"\n-\t\t\ttimestamp = \"\"\n-\t\t\tmessage = \"\"\n-\t\t\trevid_label = \"\"\n-\n-\t\tself.revid_label.set_text(revid_label)\n-\t\tself.committer_label.set_text(committer)\n-\t\tself.timestamp_label.set_text(timestamp)\n-\t\tself.message_buffer.set_text(unicode(message, self.encoding).encode('utf-8'))\n-\n-\t\tfor widget in self.parents_widgets:\n-\t\t\tself.table.remove(widget)\n-\n-\t\tself.parents_widgets = []\n-\t\tself.table.resize(4 + len(commit.parent_sha1) - 1, 4)\n-\t\tfor idx, parent_id in enumerate(commit.parent_sha1):\n-\t\t\tself.table.set_row_spacing(idx + 3, 0)\n-\n-\t\t\talign = gtk.Alignment(0.0, 0.0)\n-\t\t\tself.parents_widgets.append(align)\n-\t\t\tself.table.attach(align, 1, 2, idx + 3, idx + 4,\n-\t\t\t\t\tgtk.EXPAND | gtk.FILL, gtk.FILL)\n-\t\t\talign.show()\n-\n-\t\t\thbox = gtk.HBox(False, 0)\n-\t\t\talign.add(hbox)\n-\t\t\thbox.show()\n-\n-\t\t\tlabel = gtk.Label(parent_id)\n-\t\t\tlabel.set_selectable(True)\n-\t\t\thbox.pack_start(label, expand=False, fill=True)\n-\t\t\tlabel.show()\n-\n-\t\t\timage = gtk.Image()\n-\t\t\timage.set_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU)\n-\t\t\timage.show()\n-\n-\t\t\tbutton = gtk.Button()\n-\t\t\tbutton.add(image)\n-\t\t\tbutton.set_relief(gtk.RELIEF_NONE)\n-\t\t\tbutton.connect(\"clicked\", self._go_clicked_cb, parent_id)\n-\t\t\thbox.pack_start(button, expand=False, fill=True)\n-\t\t\tbutton.show()\n-\n-\t\t\timage = gtk.Image()\n-\t\t\timage.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU)\n-\t\t\timage.show()\n-\n-\t\t\tbutton = gtk.Button()\n-\t\t\tbutton.add(image)\n-\t\t\tbutton.set_relief(gtk.RELIEF_NONE)\n-\t\t\tbutton.set_sensitive(True)\n-\t\t\tbutton.connect(\"clicked\", self._show_clicked_cb,\n-\t\t\t\t\tcommit.commit_sha1, parent_id, self.encoding)\n-\t\t\thbox.pack_start(button, expand=False, fill=True)\n-\t\t\tbutton.show()\n-\n-\t\t# Populate with child details\n-\t\tfor widget in self.children_widgets:\n-\t\t\tself.table.remove(widget)\n-\n-\t\tself.children_widgets = []\n-\t\ttry:\n-\t\t\tchild_sha1 = Commit.children_sha1[commit.commit_sha1]\n-\t\texcept KeyError:\n-\t\t\t# We don't have child\n-\t\t\tchild_sha1 = [ 0 ]\n-\n-\t\tif ( len(child_sha1) > len(commit.parent_sha1)):\n-\t\t\tself.table.resize(4 + len(child_sha1) - 1, 4)\n-\n-\t\tfor idx, child_id in enumerate(child_sha1):\n-\t\t\tself.table.set_row_spacing(idx + 3, 0)\n-\n-\t\t\talign = gtk.Alignment(0.0, 0.0)\n-\t\t\tself.children_widgets.append(align)\n-\t\t\tself.table.attach(align, 3, 4, idx + 3, idx + 4,\n-\t\t\t\t\tgtk.EXPAND | gtk.FILL, gtk.FILL)\n-\t\t\talign.show()\n-\n-\t\t\thbox = gtk.HBox(False, 0)\n-\t\t\talign.add(hbox)\n-\t\t\thbox.show()\n-\n-\t\t\tlabel = gtk.Label(child_id)\n-\t\t\tlabel.set_selectable(True)\n-\t\t\thbox.pack_start(label, expand=False, fill=True)\n-\t\t\tlabel.show()\n-\n-\t\t\timage = gtk.Image()\n-\t\t\timage.set_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU)\n-\t\t\timage.show()\n-\n-\t\t\tbutton = gtk.Button()\n-\t\t\tbutton.add(image)\n-\t\t\tbutton.set_relief(gtk.RELIEF_NONE)\n-\t\t\tbutton.connect(\"clicked\", self._go_clicked_cb, child_id)\n-\t\t\thbox.pack_start(button, expand=False, fill=True)\n-\t\t\tbutton.show()\n-\n-\t\t\timage = gtk.Image()\n-\t\t\timage.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU)\n-\t\t\timage.show()\n-\n-\t\t\tbutton = gtk.Button()\n-\t\t\tbutton.add(image)\n-\t\t\tbutton.set_relief(gtk.RELIEF_NONE)\n-\t\t\tbutton.set_sensitive(True)\n-\t\t\tbutton.connect(\"clicked\", self._show_clicked_cb,\n-\t\t\t\t\tchild_id, commit.commit_sha1, self.encoding)\n-\t\t\thbox.pack_start(button, expand=False, fill=True)\n-\t\t\tbutton.show()\n-\n-\tdef _destroy_cb(self, widget):\n-\t\t\"\"\"Callback for when a window we manage is destroyed.\"\"\"\n-\t\tself.quit()\n-\n-\n-\tdef quit(self):\n-\t\t\"\"\"Stop the GTK+ main loop.\"\"\"\n-\t\tgtk.main_quit()\n-\n-\tdef run(self, args):\n-\t\tself.set_branch(args)\n-\t\tself.window.connect(\"destroy\", self._destroy_cb)\n-\t\tself.window.show()\n-\t\tgtk.main()\n-\n-\tdef set_branch(self, args):\n-\t\t\"\"\"Fill in different windows with info from the reposiroty\"\"\"\n-\t\tfp = os.popen(\"git rev-parse --sq --default HEAD \" + list_to_string(args, 1))\n-\t\tgit_rev_list_cmd = fp.read()\n-\t\tfp.close()\n-\t\tfp = os.popen(\"git rev-list  --header --topo-order --parents \" + git_rev_list_cmd)\n-\t\tself.update_window(fp)\n-\n-\tdef update_window(self, fp):\n-\t\tcommit_lines = []\n-\n-\t\tself.model = gtk.ListStore(gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,\n-\t\t\t\tgobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, str, str, str)\n-\n-\t\t# used for cursor positioning\n-\t\tself.index = {}\n-\n-\t\tself.colours = {}\n-\t\tself.nodepos = {}\n-\t\tself.incomplete_line = {}\n-\t\tself.commits = []\n-\n-\t\tindex = 0\n-\t\tlast_colour = 0\n-\t\tlast_nodepos = -1\n-\t\tout_line = []\n-\t\tinput_line = fp.readline()\n-\t\twhile (input_line != \"\"):\n-\t\t\t# The commit header ends with '\\0'\n-\t\t\t# This NULL is immediately followed by the sha1 of the\n-\t\t\t# next commit\n-\t\t\tif (input_line[0] != '\\0'):\n-\t\t\t\tcommit_lines.append(input_line)\n-\t\t\t\tinput_line = fp.readline()\n-\t\t\t\tcontinue;\n-\n-\t\t\tcommit = Commit(commit_lines)\n-\t\t\tif (commit != None ):\n-\t\t\t\tself.commits.append(commit)\n-\n-\t\t\t# Skip the '\\0\n-\t\t\tcommit_lines = []\n-\t\t\tcommit_lines.append(input_line[1:])\n-\t\t\tinput_line = fp.readline()\n-\n-\t\tfp.close()\n-\n-\t\tfor commit in self.commits:\n-\t\t\t(out_line, last_colour, last_nodepos) = self.draw_graph(commit,\n-\t\t\t\t\t\t\t\t\t\tindex, out_line,\n-\t\t\t\t\t\t\t\t\t\tlast_colour,\n-\t\t\t\t\t\t\t\t\t\tlast_nodepos)\n-\t\t\tself.index[commit.commit_sha1] = index\n-\t\t\tindex += 1\n-\n-\t\tself.treeview.set_model(self.model)\n-\t\tself.treeview.show()\n-\n-\tdef draw_graph(self, commit, index, out_line, last_colour, last_nodepos):\n-\t\tin_line=[]\n-\n-\t\t#   |   -> outline\n-\t\t#   X\n-\t\t#   |\\  <- inline\n-\n-\t\t# Reset nodepostion\n-\t\tif (last_nodepos > 5):\n-\t\t\tlast_nodepos = -1\n-\n-\t\t# Add the incomplete lines of the last cell in this\n-\t\ttry:\n-\t\t\tcolour = self.colours[commit.commit_sha1]\n-\t\texcept KeyError:\n-\t\t\tself.colours[commit.commit_sha1] = last_colour+1\n-\t\t\tlast_colour = self.colours[commit.commit_sha1]\n-\t\t\tcolour =   self.colours[commit.commit_sha1]\n-\n-\t\ttry:\n-\t\t\tnode_pos = self.nodepos[commit.commit_sha1]\n-\t\texcept KeyError:\n-\t\t\tself.nodepos[commit.commit_sha1] = last_nodepos+1\n-\t\t\tlast_nodepos = self.nodepos[commit.commit_sha1]\n-\t\t\tnode_pos =  self.nodepos[commit.commit_sha1]\n-\n-\t\t#The first parent always continue on the same line\n-\t\ttry:\n-\t\t\t# check we already have the value\n-\t\t\ttmp_node_pos = self.nodepos[commit.parent_sha1[0]]\n-\t\texcept KeyError:\n-\t\t\tself.colours[commit.parent_sha1[0]] = colour\n-\t\t\tself.nodepos[commit.parent_sha1[0]] = node_pos\n-\n-\t\tfor sha1 in self.incomplete_line.keys():\n-\t\t\tif (sha1 != commit.commit_sha1):\n-\t\t\t\tself.draw_incomplete_line(sha1, node_pos,\n-\t\t\t\t\t\tout_line, in_line, index)\n-\t\t\telse:\n-\t\t\t\tdel self.incomplete_line[sha1]\n-\n-\n-\t\tfor parent_id in commit.parent_sha1:\n-\t\t\ttry:\n-\t\t\t\ttmp_node_pos = self.nodepos[parent_id]\n-\t\t\texcept KeyError:\n-\t\t\t\tself.colours[parent_id] = last_colour+1\n-\t\t\t\tlast_colour = self.colours[parent_id]\n-\t\t\t\tself.nodepos[parent_id] = last_nodepos+1\n-\t\t\t\tlast_nodepos = self.nodepos[parent_id]\n-\n-\t\t\tin_line.append((node_pos, self.nodepos[parent_id],\n-\t\t\t\t\t\tself.colours[parent_id]))\n-\t\t\tself.add_incomplete_line(parent_id)\n-\n-\t\ttry:\n-\t\t\tbranch_tag = self.bt_sha1[commit.commit_sha1]\n-\t\texcept KeyError:\n-\t\t\tbranch_tag = [ ]\n-\n-\n-\t\tnode = (node_pos, colour, branch_tag)\n-\n-\t\tself.model.append([commit, node, out_line, in_line,\n-\t\t\t\tcommit.message, commit.author, commit.date])\n-\n-\t\treturn (in_line, last_colour, last_nodepos)\n-\n-\tdef add_incomplete_line(self, sha1):\n-\t\ttry:\n-\t\t\tself.incomplete_line[sha1].append(self.nodepos[sha1])\n-\t\texcept KeyError:\n-\t\t\tself.incomplete_line[sha1] = [self.nodepos[sha1]]\n-\n-\tdef draw_incomplete_line(self, sha1, node_pos, out_line, in_line, index):\n-\t\tfor idx, pos in enumerate(self.incomplete_line[sha1]):\n-\t\t\tif(pos == node_pos):\n-\t\t\t\t#remove the straight line and add a slash\n-\t\t\t\tif ((pos, pos, self.colours[sha1]) in out_line):\n-\t\t\t\t\tout_line.remove((pos, pos, self.colours[sha1]))\n-\t\t\t\tout_line.append((pos, pos+0.5, self.colours[sha1]))\n-\t\t\t\tself.incomplete_line[sha1][idx] = pos = pos+0.5\n-\t\t\ttry:\n-\t\t\t\tnext_commit = self.commits[index+1]\n-\t\t\t\tif (next_commit.commit_sha1 == sha1 and pos != int(pos)):\n-\t\t\t\t# join the line back to the node point\n-\t\t\t\t# This need to be done only if we modified it\n-\t\t\t\t\tin_line.append((pos, pos-0.5, self.colours[sha1]))\n-\t\t\t\t\tcontinue;\n-\t\t\texcept IndexError:\n-\t\t\t\tpass\n-\t\t\tin_line.append((pos, pos, self.colours[sha1]))\n-\n-\n-\tdef _go_clicked_cb(self, widget, revid):\n-\t\t\"\"\"Callback for when the go button for a parent is clicked.\"\"\"\n-\t\ttry:\n-\t\t\tself.treeview.set_cursor(self.index[revid])\n-\t\texcept KeyError:\n-\t\t\tdialog = gtk.MessageDialog(parent=None, flags=0,\n-\t\t\t\t\ttype=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_CLOSE,\n-\t\t\t\t\tmessage_format=None)\n-\t\t\tdialog.set_markup(\"Revision <b>%s</b> not present in the list\" % revid)\n-\t\t\t# revid == 0 is the parent of the first commit\n-\t\t\tif (revid != 0 ):\n-\t\t\t\tdialog.format_secondary_text(\"Try running gitview without any options\")\n-\t\t\tdialog.run()\n-\t\t\tdialog.destroy()\n-\n-\t\tself.treeview.grab_focus()\n-\n-\tdef _show_clicked_cb(self, widget,  commit_sha1, parent_sha1, encoding):\n-\t\t\"\"\"Callback for when the show button for a parent is clicked.\"\"\"\n-\t\twindow = DiffWindow()\n-\t\twindow.set_diff(commit_sha1, parent_sha1, encoding)\n-\t\tself.treeview.grab_focus()\n-\n-without_diff = 0\n-if __name__ == \"__main__\":\n-\n-\tif (len(sys.argv) > 1 ):\n-\t\tif (sys.argv[1] == \"--without-diff\"):\n-\t\t\twithout_diff = 1\n-\n-\tview = GitView( without_diff != 1)\n-\tview.run(sys.argv[without_diff:])\ndiff --git a/contrib/gitview/gitview.txt b/contrib/gitview/gitview.txt\ndeleted file mode 100644\nindex 7b5f9002b9..0000000000\n--- a/contrib/gitview/gitview.txt\n+++ /dev/null\n@@ -1,57 +0,0 @@\n-gitview(1)\n-==========\n-\n-NAME\n-----\n-gitview - A GTK based repository browser for git\n-\n-SYNOPSIS\n---------\n-[verse]\n-'gitview' [options] [args]\n-\n-DESCRIPTION\n----------\n-\n-Dependencies:\n-\n-* Python 2.4\n-* PyGTK 2.8 or later\n-* PyCairo 1.0 or later\n-\n-OPTIONS\n--------\n---without-diff::\n-\n-\tIf the user doesn't want to list the commit diffs in the main window.\n-\tThis may speed up the repository browsing.\n-\n-<args>::\n-\n-\tAll the valid option for linkgit:git-rev-list[1].\n-\n-Key Bindings\n-------------\n-F4::\n-\tTo maximize the window\n-\n-F5::\n-\tTo reread references.\n-\n-F11::\n-\tFull screen\n-\n-F12::\n-\tLeave full screen\n-\n-EXAMPLES\n---------\n-\n-gitview v2.6.12.. include/scsi drivers/scsi::\n-\n-\tShow as the changes since version v2.6.12 that changed any file in the\n-\tinclude/scsi or drivers/scsi subdirectories\n-\n-gitview --since=2.weeks.ago::\n-\n-\tShow the changes during the last two weeks\n-- \n2.11.0.259.ga95e92af08\n\n"},{"id":"308471","messageId":"20161228192839.4C570138286@mail.altsci.com","threadId":"44770","inReplyTo":"20161228172837.24395-1-sbeller@google.com","subject":"Re: [PATCH] contrib: remove gitview","fromName":"Javantea","fromEmail":"jvoss@altsci.com","sentAt":null,"receivedAt":"2016-12-28T19:28:19Z","isPatch":true,"sender":{"key":"jvoss@altsci.com","avatar":null},"body":"Dear Stefan,\n\nThank you for commenting on my report and getting the ball rolling on this. \nThis response sounds good to me.\n\nRegards,\nJavantea\n\nOn Wed, 28 Dec 2016 09:28:37 -0800, Stefan Beller <sbeller@google.com> wrote:\n>gitview did not have meaningful contributions since 2007, which gives the\n>impression it is either a mature or dead project.\n>\n>In both cases we should not carry it in git.git as the README for contrib\n>states we only want to carry experimental things to give early exposure.\n>\n>Recently a security vulnerability was reported by Javantea, so the decision\n>to either fix the issue or remove the code in question becomes a bit\n>more urgent.\n>\n>Reported-by: Javantea <jvoss@altsci.com>\n>Signed-off-by: Stefan Beller <sbeller@google.com>\n>---\n> contrib/gitview/gitview     | 1305 -------------------------------------------\n> contrib/gitview/gitview.txt |   57 --\n> 2 files changed, 1362 deletions(-)\n> delete mode 100755 contrib/gitview/gitview\n> delete mode 100644 contrib/gitview/gitview.txt\n>\n>diff --git a/contrib/gitview/gitview b/contrib/gitview/gitview\n>deleted file mode 100755\n>index 4e23c650fe..0000000000\n>--- a/contrib/gitview/gitview\n>+++ /dev/null\n>@@ -1,1305 +0,0 @@\n>-#! /usr/bin/env python\n>-\n>-# This program is free software; you can redistribute it and/or modify\n>-# it under the terms of the GNU General Public License as published by\n>-# the Free Software Foundation; either version 2 of the License, or\n>-# (at your option) any later version.\n>-\n>-\"\"\" gitview\n>-GUI browser for git repository\n>-This program is based on bzrk by Scott James Remnant <scott@ubuntu.com>\n>-\"\"\"\n>-__copyright__ = \"Copyright (C) 2006 Hewlett-Packard Development Company, L.P.\"\n>-__copyright__ = \"Copyright (C) 2007 Aneesh Kumar K.V <aneesh.kumar@gmail.com\"\n>-__author__    = \"Aneesh Kumar K.V <aneesh.kumar@gmail.com>\"\n>-\n>-\n>-import sys\n>-import os\n>-import gtk\n>-import pygtk\n>-import pango\n>-import re\n>-import time\n>-import gobject\n>-import cairo\n>-import math\n>-import string\n>-import fcntl\n>-\n>-have_gtksourceview2 = False\n>-have_gtksourceview = False\n>-try:\n>-    import gtksourceview2\n>-    have_gtksourceview2 = True\n>-except ImportError:\n>-    try:\n>-        import gtksourceview\n>-        have_gtksourceview = True\n>-    except ImportError:\n>-        print \"Running without gtksourceview2 or gtksourceview module\"\n>-\n>-re_ident = re.compile('(author|committer) (?P<ident>.*) (?P<epoch>\\d+) (?P<tz>[+-]\\d{4})')\n>-\n>-def list_to_string(args, skip):\n>-\tcount = len(args)\n>-\ti = skip\n>-\tstr_arg=\" \"\n>-\twhile (i < count ):\n>-\t\tstr_arg = str_arg + args[i]\n>-\t\tstr_arg = str_arg + \" \"\n>-\t\ti = i+1\n>-\n>-\treturn str_arg\n>-\n>-def show_date(epoch, tz):\n>-\tsecs = float(epoch)\n>-\ttzsecs = float(tz[1:3]) * 3600\n>-\ttzsecs += float(tz[3:5]) * 60\n>-\tif (tz[0] == \"+\"):\n>-\t\tsecs += tzsecs\n>-\telse:\n>-\t\tsecs -= tzsecs\n>-\n>-\treturn time.strftime(\"%Y-%m-%d %H:%M:%S\", time.gmtime(secs))\n>-\n>-def get_source_buffer_and_view():\n>-\tif have_gtksourceview2:\n>-\t\tbuffer = gtksourceview2.Buffer()\n>-\t\tslm = gtksourceview2.LanguageManager()\n>-\t\tgsl = slm.get_language(\"diff\")\n>-\t\tbuffer.set_highlight_syntax(True)\n>-\t\tbuffer.set_language(gsl)\n>-\t\tview = gtksourceview2.View(buffer)\n>-\telif have_gtksourceview:\n>-\t\tbuffer = gtksourceview.SourceBuffer()\n>-\t\tslm = gtksourceview.SourceLanguagesManager()\n>-\t\tgsl = slm.get_language_from_mime_type(\"text/x-patch\")\n>-\t\tbuffer.set_highlight(True)\n>-\t\tbuffer.set_language(gsl)\n>-\t\tview = gtksourceview.SourceView(buffer)\n>-\telse:\n>-\t\tbuffer = gtk.TextBuffer()\n>-\t\tview = gtk.TextView(buffer)\n>-\treturn (buffer, view)\n>-\n>-\n>-class CellRendererGraph(gtk.GenericCellRenderer):\n>-\t\"\"\"Cell renderer for directed graph.\n>-\n>-\tThis module contains the implementation of a custom GtkCellRenderer that\n>-\tdraws part of the directed graph based on the lines suggested by the code\n>-\tin graph.py.\n>-\n>-\tBecause we're shiny, we use Cairo to do this, and because we're naughty\n>-\twe cheat and draw over the bits of the TreeViewColumn that are supposed to\n>-\tjust be for the background.\n>-\n>-\tProperties:\n>-\tnode              (column, colour, [ names ]) tuple to draw revision node,\n>-\tin_lines          (start, end, colour) tuple list to draw inward lines,\n>-\tout_lines         (start, end, colour) tuple list to draw outward lines.\n>-\t\"\"\"\n>-\n>-\t__gproperties__ = {\n>-\t\"node\":         ( gobject.TYPE_PYOBJECT, \"node\",\n>-\t\t\t  \"revision node instruction\",\n>-\t\t\t  gobject.PARAM_WRITABLE\n>-\t\t\t),\n>-\t\"in-lines\":     ( gobject.TYPE_PYOBJECT, \"in-lines\",\n>-\t\t\t  \"instructions to draw lines into the cell\",\n>-\t\t\t  gobject.PARAM_WRITABLE\n>-\t\t\t),\n>-\t\"out-lines\":    ( gobject.TYPE_PYOBJECT, \"out-lines\",\n>-\t\t\t  \"instructions to draw lines out of the cell\",\n>-\t\t\t  gobject.PARAM_WRITABLE\n>-\t\t\t),\n>-\t}\n>-\n>-\tdef do_set_property(self, property, value):\n>-\t\t\"\"\"Set properties from GObject properties.\"\"\"\n>-\t\tif property.name == \"node\":\n>-\t\t\tself.node = value\n>-\t\telif property.name == \"in-lines\":\n>-\t\t\tself.in_lines = value\n>-\t\telif property.name == \"out-lines\":\n>-\t\t\tself.out_lines = value\n>-\t\telse:\n>-\t\t\traise AttributeError, \"no such property: '%s'\" % property.name\n>-\n>-\tdef box_size(self, widget):\n>-\t\t\"\"\"Calculate box size based on widget's font.\n>-\n>-\t\tCache this as it's probably expensive to get.  It ensures that we\n>-\t\tdraw the graph at least as large as the text.\n>-\t\t\"\"\"\n>-\t\ttry:\n>-\t\t\treturn self._box_size\n>-\t\texcept AttributeError:\n>-\t\t\tpango_ctx = widget.get_pango_context()\n>-\t\t\tfont_desc = widget.get_style().font_desc\n>-\t\t\tmetrics = pango_ctx.get_metrics(font_desc)\n>-\n>-\t\t\tascent = pango.PIXELS(metrics.get_ascent())\n>-\t\t\tdescent = pango.PIXELS(metrics.get_descent())\n>-\n>-\t\t\tself._box_size = ascent + descent + 6\n>-\t\t\treturn self._box_size\n>-\n>-\tdef set_colour(self, ctx, colour, bg, fg):\n>-\t\t\"\"\"Set the context source colour.\n>-\n>-\t\tPicks a distinct colour based on an internal wheel; the bg\n>-\t\tparameter provides the value that should be assigned to the 'zero'\n>-\t\tcolours and the fg parameter provides the multiplier that should be\n>-\t\tapplied to the foreground colours.\n>-\t\t\"\"\"\n>-\t\tcolours = [\n>-\t\t    ( 1.0, 0.0, 0.0 ),\n>-\t\t    ( 1.0, 1.0, 0.0 ),\n>-\t\t    ( 0.0, 1.0, 0.0 ),\n>-\t\t    ( 0.0, 1.0, 1.0 ),\n>-\t\t    ( 0.0, 0.0, 1.0 ),\n>-\t\t    ( 1.0, 0.0, 1.0 ),\n>-\t\t    ]\n>-\n>-\t\tcolour %= len(colours)\n>-\t\tred   = (colours[colour][0] * fg) or bg\n>-\t\tgreen = (colours[colour][1] * fg) or bg\n>-\t\tblue  = (colours[colour][2] * fg) or bg\n>-\n>-\t\tctx.set_source_rgb(red, green, blue)\n>-\n>-\tdef on_get_size(self, widget, cell_area):\n>-\t\t\"\"\"Return the size we need for this cell.\n>-\n>-\t\tEach cell is drawn individually and is only as wide as it needs\n>-\t\tto be, we let the TreeViewColumn take care of making them all\n>-\t\tline up.\n>-\t\t\"\"\"\n>-\t\tbox_size = self.box_size(widget)\n>-\n>-\t\tcols = self.node[0]\n>-\t\tfor start, end, colour in self.in_lines + self.out_lines:\n>-\t\t\tcols = int(max(cols, start, end))\n>-\n>-\t\t(column, colour, names) = self.node\n>-\t\tnames_len = 0\n>-\t\tif (len(names) != 0):\n>-\t\t\tfor item in names:\n>-\t\t\t\tnames_len += len(item)\n>-\n>-\t\twidth = box_size * (cols + 1 ) + names_len\n>-\t\theight = box_size\n>-\n>-\t\t# FIXME I have no idea how to use cell_area properly\n>-\t\treturn (0, 0, width, height)\n>-\n>-\tdef on_render(self, window, widget, bg_area, cell_area, exp_area, flags):\n>-\t\t\"\"\"Render an individual cell.\n>-\n>-\t\tDraws the cell contents using cairo, taking care to clip what we\n>-\t\tdo to within the background area so we don't draw over other cells.\n>-\t\tNote that we're a bit naughty there and should really be drawing\n>-\t\tin the cell_area (or even the exposed area), but we explicitly don't\n>-\t\twant any gutter.\n>-\n>-\t\tWe try and be a little clever, if the line we need to draw is going\n>-\t\tto cross other columns we actually draw it as in the .---' style\n>-\t\tinstead of a pure diagonal ... this reduces confusion by an\n>-\t\tincredible amount.\n>-\t\t\"\"\"\n>-\t\tctx = window.cairo_create()\n>-\t\tctx.rectangle(bg_area.x, bg_area.y, bg_area.width, bg_area.height)\n>-\t\tctx.clip()\n>-\n>-\t\tbox_size = self.box_size(widget)\n>-\n>-\t\tctx.set_line_width(box_size / 8)\n>-\t\tctx.set_line_cap(cairo.LINE_CAP_SQUARE)\n>-\n>-\t\t# Draw lines into the cell\n>-\t\tfor start, end, colour in self.in_lines:\n>-\t\t\tctx.move_to(cell_area.x + box_size * start + box_size / 2,\n>-\t\t\t\t\tbg_area.y - bg_area.height / 2)\n>-\n>-\t\t\tif start - end > 1:\n>-\t\t\t\tctx.line_to(cell_area.x + box_size * start, bg_area.y)\n>-\t\t\t\tctx.line_to(cell_area.x + box_size * end + box_size, bg_area.y)\n>-\t\t\telif start - end < -1:\n>-\t\t\t\tctx.line_to(cell_area.x + box_size * start + box_size,\n>-\t\t\t\t\t\tbg_area.y)\n>-\t\t\t\tctx.line_to(cell_area.x + box_size * end, bg_area.y)\n>-\n>-\t\t\tctx.line_to(cell_area.x + box_size * end + box_size / 2,\n>-\t\t\t\t\tbg_area.y + bg_area.height / 2)\n>-\n>-\t\t\tself.set_colour(ctx, colour, 0.0, 0.65)\n>-\t\t\tctx.stroke()\n>-\n>-\t\t# Draw lines out of the cell\n>-\t\tfor start, end, colour in self.out_lines:\n>-\t\t\tctx.move_to(cell_area.x + box_size * start + box_size / 2,\n>-\t\t\t\t\tbg_area.y + bg_area.height / 2)\n>-\n>-\t\t\tif start - end > 1:\n>-\t\t\t\tctx.line_to(cell_area.x + box_size * start,\n>-\t\t\t\t\t\tbg_area.y + bg_area.height)\n>-\t\t\t\tctx.line_to(cell_area.x + box_size * end + box_size,\n>-\t\t\t\t\t\tbg_area.y + bg_area.height)\n>-\t\t\telif start - end < -1:\n>-\t\t\t\tctx.line_to(cell_area.x + box_size * start + box_size,\n>-\t\t\t\t\t\tbg_area.y + bg_area.height)\n>-\t\t\t\tctx.line_to(cell_area.x + box_size * end,\n>-\t\t\t\t\t\tbg_area.y + bg_area.height)\n>-\n>-\t\t\tctx.line_to(cell_area.x + box_size * end + box_size / 2,\n>-\t\t\t\t\tbg_area.y + bg_area.height / 2 + bg_area.height)\n>-\n>-\t\t\tself.set_colour(ctx, colour, 0.0, 0.65)\n>-\t\t\tctx.stroke()\n>-\n>-\t\t# Draw the revision node in the right column\n>-\t\t(column, colour, names) = self.node\n>-\t\tctx.arc(cell_area.x + box_size * column + box_size / 2,\n>-\t\t\t\tcell_area.y + cell_area.height / 2,\n>-\t\t\t\tbox_size / 4, 0, 2 * math.pi)\n>-\n>-\n>-\t\tself.set_colour(ctx, colour, 0.0, 0.5)\n>-\t\tctx.stroke_preserve()\n>-\n>-\t\tself.set_colour(ctx, colour, 0.5, 1.0)\n>-\t\tctx.fill_preserve()\n>-\n>-\t\tif (len(names) != 0):\n>-\t\t\tname = \" \"\n>-\t\t\tfor item in names:\n>-\t\t\t\tname = name + item + \" \"\n>-\n>-\t\t\tctx.set_font_size(13)\n>-\t\t\tif (flags & 1):\n>-\t\t\t\tself.set_colour(ctx, colour, 0.5, 1.0)\n>-\t\t\telse:\n>-\t\t\t\tself.set_colour(ctx, colour, 0.0, 0.5)\n>-\t\t\tctx.show_text(name)\n>-\n>-class Commit(object):\n>-\t\"\"\" This represent a commit object obtained after parsing the git-rev-list\n>-\toutput \"\"\"\n>-\n>-\t__slots__ = ['children_sha1', 'message', 'author', 'date', 'committer',\n>-\t\t\t\t 'commit_date', 'commit_sha1', 'parent_sha1']\n>-\n>-\tchildren_sha1 = {}\n>-\n>-\tdef __init__(self, commit_lines):\n>-\t\tself.message\t\t= \"\"\n>-\t\tself.author\t\t= \"\"\n>-\t\tself.date\t\t= \"\"\n>-\t\tself.committer\t\t= \"\"\n>-\t\tself.commit_date\t= \"\"\n>-\t\tself.commit_sha1\t= \"\"\n>-\t\tself.parent_sha1\t= [ ]\n>-\t\tself.parse_commit(commit_lines)\n>-\n>-\n>-\tdef parse_commit(self, commit_lines):\n>-\n>-\t\t# First line is the sha1 lines\n>-\t\tline = string.strip(commit_lines[0])\n>-\t\tsha1 = re.split(\" \", line)\n>-\t\tself.commit_sha1 = sha1[0]\n>-\t\tself.parent_sha1 = sha1[1:]\n>-\n>-\t\t#build the child list\n>-\t\tfor parent_id in self.parent_sha1:\n>-\t\t\ttry:\n>-\t\t\t\tCommit.children_sha1[parent_id].append(self.commit_sha1)\n>-\t\t\texcept KeyError:\n>-\t\t\t\tCommit.children_sha1[parent_id] = [self.commit_sha1]\n>-\n>-\t\t# IF we don't have parent\n>-\t\tif (len(self.parent_sha1) == 0):\n>-\t\t\tself.parent_sha1 = [0]\n>-\n>-\t\tfor line in commit_lines[1:]:\n>-\t\t\tm = re.match(\"^ \", line)\n>-\t\t\tif (m != None):\n>-\t\t\t\t# First line of the commit message used for short log\n>-\t\t\t\tif self.message == \"\":\n>-\t\t\t\t\tself.message = string.strip(line)\n>-\t\t\t\tcontinue\n>-\n>-\t\t\tm = re.match(\"tree\", line)\n>-\t\t\tif (m != None):\n>-\t\t\t\tcontinue\n>-\n>-\t\t\tm = re.match(\"parent\", line)\n>-\t\t\tif (m != None):\n>-\t\t\t\tcontinue\n>-\n>-\t\t\tm = re_ident.match(line)\n>-\t\t\tif (m != None):\n>-\t\t\t\tdate = show_date(m.group('epoch'), m.group('tz'))\n>-\t\t\t\tif m.group(1) == \"author\":\n>-\t\t\t\t\tself.author = m.group('ident')\n>-\t\t\t\t\tself.date = date\n>-\t\t\t\telif m.group(1) == \"committer\":\n>-\t\t\t\t\tself.committer = m.group('ident')\n>-\t\t\t\t\tself.commit_date = date\n>-\n>-\t\t\t\tcontinue\n>-\n>-\tdef get_message(self, with_diff=0):\n>-\t\tif (with_diff == 1):\n>-\t\t\tmessage = self.diff_tree()\n>-\t\telse:\n>-\t\t\tfp = os.popen(\"git cat-file commit \" + self.commit_sha1)\n>-\t\t\tmessage = fp.read()\n>-\t\t\tfp.close()\n>-\n>-\t\treturn message\n>-\n>-\tdef diff_tree(self):\n>-\t\tfp = os.popen(\"git diff-tree --pretty --cc  -v -p --always \" +  self.commit_sha1)\n>-\t\tdiff = fp.read()\n>-\t\tfp.close()\n>-\t\treturn diff\n>-\n>-class AnnotateWindow(object):\n>-\t\"\"\"Annotate window.\n>-\tThis object represents and manages a single window containing the\n>-\tannotate information of the file\n>-\t\"\"\"\n>-\n>-\tdef __init__(self):\n>-\t\tself.window = gtk.Window(gtk.WINDOW_TOPLEVEL)\n>-\t\tself.window.set_border_width(0)\n>-\t\tself.window.set_title(\"Git repository browser annotation window\")\n>-\t\tself.prev_read = \"\"\n>-\n>-\t\t# Use two thirds of the screen by default\n>-\t\tscreen = self.window.get_screen()\n>-\t\tmonitor = screen.get_monitor_geometry(0)\n>-\t\twidth = int(monitor.width * 0.66)\n>-\t\theight = int(monitor.height * 0.66)\n>-\t\tself.window.set_default_size(width, height)\n>-\n>-\tdef add_file_data(self, filename, commit_sha1, line_num):\n>-\t\tfp = os.popen(\"git cat-file blob \" + commit_sha1 +\":\"+filename)\n>-\t\ti = 1;\n>-\t\tfor line in fp.readlines():\n>-\t\t\tline = string.rstrip(line)\n>-\t\t\tself.model.append(None, [\"HEAD\", filename, line, i])\n>-\t\t\ti = i+1\n>-\t\tfp.close()\n>-\n>-\t\t# now set the cursor position\n>-\t\tself.treeview.set_cursor(line_num-1)\n>-\t\tself.treeview.grab_focus()\n>-\n>-\tdef _treeview_cursor_cb(self, *args):\n>-\t\t\"\"\"Callback for when the treeview cursor changes.\"\"\"\n>-\t\t(path, col) = self.treeview.get_cursor()\n>-\t\tcommit_sha1 = self.model[path][0]\n>-\t\tcommit_msg = \"\"\n>-\t\tfp = os.popen(\"git cat-file commit \" + commit_sha1)\n>-\t\tfor line in fp.readlines():\n>-\t\t\tcommit_msg =  commit_msg + line\n>-\t\tfp.close()\n>-\n>-\t\tself.commit_buffer.set_text(commit_msg)\n>-\n>-\tdef _treeview_row_activated(self, *args):\n>-\t\t\"\"\"Callback for when the treeview row gets selected.\"\"\"\n>-\t\t(path, col) = self.treeview.get_cursor()\n>-\t\tcommit_sha1 = self.model[path][0]\n>-\t\tfilename    = self.model[path][1]\n>-\t\tline_num    = self.model[path][3]\n>-\n>-\t\twindow = AnnotateWindow();\n>-\t\tfp = os.popen(\"git rev-parse \"+ commit_sha1 + \"~1\")\n>-\t\tcommit_sha1 = string.strip(fp.readline())\n>-\t\tfp.close()\n>-\t\twindow.annotate(filename, commit_sha1, line_num)\n>-\n>-\tdef data_ready(self, source, condition):\n>-\t\twhile (1):\n>-\t\t\ttry :\n>-\t\t\t\t# A simple readline doesn't work\n>-\t\t\t\t# a readline bug ??\n>-\t\t\t\tbuffer = source.read(100)\n>-\n>-\t\t\texcept:\n>-\t\t\t\t# resource temporary not available\n>-\t\t\t\treturn True\n>-\n>-\t\t\tif (len(buffer) == 0):\n>-\t\t\t\tgobject.source_remove(self.io_watch_tag)\n>-\t\t\t\tsource.close()\n>-\t\t\t\treturn False\n>-\n>-\t\t\tif (self.prev_read != \"\"):\n>-\t\t\t\tbuffer = self.prev_read + buffer\n>-\t\t\t\tself.prev_read = \"\"\n>-\n>-\t\t\tif (buffer[len(buffer) -1] != '\\n'):\n>-\t\t\t\ttry:\n>-\t\t\t\t\tnewline_index = buffer.rindex(\"\\n\")\n>-\t\t\t\texcept ValueError:\n>-\t\t\t\t\tnewline_index = 0\n>-\n>-\t\t\t\tself.prev_read = buffer[newline_index:(len(buffer))]\n>-\t\t\t\tbuffer = buffer[0:newline_index]\n>-\n>-\t\t\tfor buff in buffer.split(\"\\n\"):\n>-\t\t\t\tannotate_line = re.compile('^([0-9a-f]{40}) (.+) (.+) (.+)$')\n>-\t\t\t\tm = annotate_line.match(buff)\n>-\t\t\t\tif not m:\n>-\t\t\t\t\tannotate_line = re.compile('^(filename) (.+)$')\n>-\t\t\t\t\tm = annotate_line.match(buff)\n>-\t\t\t\t\tif not m:\n>-\t\t\t\t\t\tcontinue\n>-\t\t\t\t\tfilename = m.group(2)\n>-\t\t\t\telse:\n>-\t\t\t\t\tself.commit_sha1 = m.group(1)\n>-\t\t\t\t\tself.source_line = int(m.group(2))\n>-\t\t\t\t\tself.result_line = int(m.group(3))\n>-\t\t\t\t\tself.count\t    = int(m.group(4))\n>-\t\t\t\t\t#set the details only when we have the file name\n>-\t\t\t\t\tcontinue\n>-\n>-\t\t\t\twhile (self.count > 0):\n>-\t\t\t\t\t# set at result_line + count-1 the sha1 as commit_sha1\n>-\t\t\t\t\tself.count = self.count - 1\n>-\t\t\t\t\titer = self.model.iter_nth_child(None, self.result_line + self.count-1)\n>-\t\t\t\t\tself.model.set(iter, 0, self.commit_sha1, 1, filename, 3, self.source_line)\n>-\n>-\n>-\tdef annotate(self, filename, commit_sha1, line_num):\n>-\t\t# verify the commit_sha1 specified has this filename\n>-\n>-\t\tfp = os.popen(\"git ls-tree \"+ commit_sha1 + \" -- \" + filename)\n>-\t\tline = string.strip(fp.readline())\n>-\t\tif line == '':\n>-\t\t\t# pop up the message the file is not there as a part of the commit\n>-\t\t\tfp.close()\n>-\t\t\tdialog = gtk.MessageDialog(parent=None, flags=0,\n>-\t\t\t\t\ttype=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_CLOSE,\n>-\t\t\t\t\tmessage_format=None)\n>-\t\t\tdialog.set_markup(\"The file %s is not present in the parent commit %s\" % (filename, commit_sha1))\n>-\t\t\tdialog.run()\n>-\t\t\tdialog.destroy()\n>-\t\t\treturn\n>-\n>-\t\tfp.close()\n>-\n>-\t\tvpan = gtk.VPaned();\n>-\t\tself.window.add(vpan);\n>-\t\tvpan.show()\n>-\n>-\t\tscrollwin = gtk.ScrolledWindow()\n>-\t\tscrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)\n>-\t\tscrollwin.set_shadow_type(gtk.SHADOW_IN)\n>-\t\tvpan.pack1(scrollwin, True, True);\n>-\t\tscrollwin.show()\n>-\n>-\t\tself.model = gtk.TreeStore(str, str, str, int)\n>-\t\tself.treeview = gtk.TreeView(self.model)\n>-\t\tself.treeview.set_rules_hint(True)\n>-\t\tself.treeview.set_search_column(0)\n>-\t\tself.treeview.connect(\"cursor-changed\", self._treeview_cursor_cb)\n>-\t\tself.treeview.connect(\"row-activated\", self._treeview_row_activated)\n>-\t\tscrollwin.add(self.treeview)\n>-\t\tself.treeview.show()\n>-\n>-\t\tcell = gtk.CellRendererText()\n>-\t\tcell.set_property(\"width-chars\", 10)\n>-\t\tcell.set_property(\"ellipsize\", pango.ELLIPSIZE_END)\n>-\t\tcolumn = gtk.TreeViewColumn(\"Commit\")\n>-\t\tcolumn.set_resizable(True)\n>-\t\tcolumn.pack_start(cell, expand=True)\n>-\t\tcolumn.add_attribute(cell, \"text\", 0)\n>-\t\tself.treeview.append_column(column)\n>-\n>-\t\tcell = gtk.CellRendererText()\n>-\t\tcell.set_property(\"width-chars\", 20)\n>-\t\tcell.set_property(\"ellipsize\", pango.ELLIPSIZE_END)\n>-\t\tcolumn = gtk.TreeViewColumn(\"File Name\")\n>-\t\tcolumn.set_resizable(True)\n>-\t\tcolumn.pack_start(cell, expand=True)\n>-\t\tcolumn.add_attribute(cell, \"text\", 1)\n>-\t\tself.treeview.append_column(column)\n>-\n>-\t\tcell = gtk.CellRendererText()\n>-\t\tcell.set_property(\"width-chars\", 20)\n>-\t\tcell.set_property(\"ellipsize\", pango.ELLIPSIZE_END)\n>-\t\tcolumn = gtk.TreeViewColumn(\"Data\")\n>-\t\tcolumn.set_resizable(True)\n>-\t\tcolumn.pack_start(cell, expand=True)\n>-\t\tcolumn.add_attribute(cell, \"text\", 2)\n>-\t\tself.treeview.append_column(column)\n>-\n>-\t\t# The commit message window\n>-\t\tscrollwin = gtk.ScrolledWindow()\n>-\t\tscrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)\n>-\t\tscrollwin.set_shadow_type(gtk.SHADOW_IN)\n>-\t\tvpan.pack2(scrollwin, True, True);\n>-\t\tscrollwin.show()\n>-\n>-\t\tcommit_text = gtk.TextView()\n>-\t\tself.commit_buffer = gtk.TextBuffer()\n>-\t\tcommit_text.set_buffer(self.commit_buffer)\n>-\t\tscrollwin.add(commit_text)\n>-\t\tcommit_text.show()\n>-\n>-\t\tself.window.show()\n>-\n>-\t\tself.add_file_data(filename, commit_sha1, line_num)\n>-\n>-\t\tfp = os.popen(\"git blame --incremental -C -C -- \" + filename + \" \" + commit_sha1)\n>-\t\tflags = fcntl.fcntl(fp.fileno(), fcntl.F_GETFL)\n>-\t\tfcntl.fcntl(fp.fileno(), fcntl.F_SETFL, flags | os.O_NONBLOCK)\n>-\t\tself.io_watch_tag = gobject.io_add_watch(fp, gobject.IO_IN, self.data_ready)\n>-\n>-\n>-class DiffWindow(object):\n>-\t\"\"\"Diff window.\n>-\tThis object represents and manages a single window containing the\n>-\tdifferences between two revisions on a branch.\n>-\t\"\"\"\n>-\n>-\tdef __init__(self):\n>-\t\tself.window = gtk.Window(gtk.WINDOW_TOPLEVEL)\n>-\t\tself.window.set_border_width(0)\n>-\t\tself.window.set_title(\"Git repository browser diff window\")\n>-\n>-\t\t# Use two thirds of the screen by default\n>-\t\tscreen = self.window.get_screen()\n>-\t\tmonitor = screen.get_monitor_geometry(0)\n>-\t\twidth = int(monitor.width * 0.66)\n>-\t\theight = int(monitor.height * 0.66)\n>-\t\tself.window.set_default_size(width, height)\n>-\n>-\n>-\t\tself.construct()\n>-\n>-\tdef construct(self):\n>-\t\t\"\"\"Construct the window contents.\"\"\"\n>-\t\tvbox = gtk.VBox()\n>-\t\tself.window.add(vbox)\n>-\t\tvbox.show()\n>-\n>-\t\tmenu_bar = gtk.MenuBar()\n>-\t\tsave_menu = gtk.ImageMenuItem(gtk.STOCK_SAVE)\n>-\t\tsave_menu.connect(\"activate\", self.save_menu_response, \"save\")\n>-\t\tsave_menu.show()\n>-\t\tmenu_bar.append(save_menu)\n>-\t\tvbox.pack_start(menu_bar, expand=False, fill=True)\n>-\t\tmenu_bar.show()\n>-\n>-\t\thpan = gtk.HPaned()\n>-\n>-\t\tscrollwin = gtk.ScrolledWindow()\n>-\t\tscrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)\n>-\t\tscrollwin.set_shadow_type(gtk.SHADOW_IN)\n>-\t\thpan.pack1(scrollwin, True, True)\n>-\t\tscrollwin.show()\n>-\n>-\t\t(self.buffer, sourceview) = get_source_buffer_and_view()\n>-\n>-\t\tsourceview.set_editable(False)\n>-\t\tsourceview.modify_font(pango.FontDescription(\"Monospace\"))\n>-\t\tscrollwin.add(sourceview)\n>-\t\tsourceview.show()\n>-\n>-\t\t# The file hierarchy: a scrollable treeview\n>-\t\tscrollwin = gtk.ScrolledWindow()\n>-\t\tscrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)\n>-\t\tscrollwin.set_shadow_type(gtk.SHADOW_IN)\n>-\t\tscrollwin.set_size_request(20, -1)\n>-\t\thpan.pack2(scrollwin, True, True)\n>-\t\tscrollwin.show()\n>-\n>-\t\tself.model = gtk.TreeStore(str, str, str)\n>-\t\tself.treeview = gtk.TreeView(self.model)\n>-\t\tself.treeview.set_search_column(1)\n>-\t\tself.treeview.connect(\"cursor-changed\", self._treeview_clicked)\n>-\t\tscrollwin.add(self.treeview)\n>-\t\tself.treeview.show()\n>-\n>-\t\tcell = gtk.CellRendererText()\n>-\t\tcell.set_property(\"width-chars\", 20)\n>-\t\tcolumn = gtk.TreeViewColumn(\"Select to annotate\")\n>-\t\tcolumn.pack_start(cell, expand=True)\n>-\t\tcolumn.add_attribute(cell, \"text\", 0)\n>-\t\tself.treeview.append_column(column)\n>-\n>-\t\tvbox.pack_start(hpan, expand=True, fill=True)\n>-\t\thpan.show()\n>-\n>-\tdef _treeview_clicked(self, *args):\n>-\t\t\"\"\"Callback for when the treeview cursor changes.\"\"\"\n>-\t\t(path, col) = self.treeview.get_cursor()\n>-\t\tspecific_file = self.model[path][1]\n>-\t\tcommit_sha1 =  self.model[path][2]\n>-\t\tif specific_file ==  None :\n>-\t\t\treturn\n>-\t\telif specific_file ==  \"\" :\n>-\t\t\tspecific_file =  None\n>-\n>-\t\twindow = AnnotateWindow();\n>-\t\twindow.annotate(specific_file, commit_sha1, 1)\n>-\n>-\n>-\tdef commit_files(self, commit_sha1, parent_sha1):\n>-\t\tself.model.clear()\n>-\t\tadd  = self.model.append(None, [ \"Added\", None, None])\n>-\t\tdele = self.model.append(None, [ \"Deleted\", None, None])\n>-\t\tmod  = self.model.append(None, [ \"Modified\", None, None])\n>-\t\tdiff_tree = re.compile('^(:.{6}) (.{6}) (.{40}) (.{40}) (A|D|M)\\s(.+)$')\n>-\t\tfp = os.popen(\"git diff-tree -r --no-commit-id \" + parent_sha1 + \" \" + commit_sha1)\n>-\t\twhile 1:\n>-\t\t\tline = string.strip(fp.readline())\n>-\t\t\tif line == '':\n>-\t\t\t\tbreak\n>-\t\t\tm = diff_tree.match(line)\n>-\t\t\tif not m:\n>-\t\t\t\tcontinue\n>-\n>-\t\t\tattr = m.group(5)\n>-\t\t\tfilename = m.group(6)\n>-\t\t\tif attr == \"A\":\n>-\t\t\t\tself.model.append(add,  [filename, filename, commit_sha1])\n>-\t\t\telif attr == \"D\":\n>-\t\t\t\tself.model.append(dele, [filename, filename, commit_sha1])\n>-\t\t\telif attr == \"M\":\n>-\t\t\t\tself.model.append(mod,  [filename, filename, commit_sha1])\n>-\t\tfp.close()\n>-\n>-\t\tself.treeview.expand_all()\n>-\n>-\tdef set_diff(self, commit_sha1, parent_sha1, encoding):\n>-\t\t\"\"\"Set the differences showed by this window.\n>-\t\tCompares the two trees and populates the window with the\n>-\t\tdifferences.\n>-\t\t\"\"\"\n>-\t\t# Diff with the first commit or the last commit shows nothing\n>-\t\tif (commit_sha1 == 0 or parent_sha1 == 0 ):\n>-\t\t\treturn\n>-\n>-\t\tfp = os.popen(\"git diff-tree -p \" + parent_sha1 + \" \" + commit_sha1)\n>-\t\tself.buffer.set_text(unicode(fp.read(), encoding).encode('utf-8'))\n>-\t\tfp.close()\n>-\t\tself.commit_files(commit_sha1, parent_sha1)\n>-\t\tself.window.show()\n>-\n>-\tdef save_menu_response(self, widget, string):\n>-\t\tdialog = gtk.FileChooserDialog(\"Save..\", None, gtk.FILE_CHOOSER_ACTION_SAVE,\n>-\t\t\t\t(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,\n>-\t\t\t\t\tgtk.STOCK_SAVE, gtk.RESPONSE_OK))\n>-\t\tdialog.set_default_response(gtk.RESPONSE_OK)\n>-\t\tresponse = dialog.run()\n>-\t\tif response == gtk.RESPONSE_OK:\n>-\t\t\tpatch_buffer = self.buffer.get_text(self.buffer.get_start_iter(),\n>-\t\t\t\t\tself.buffer.get_end_iter())\n>-\t\t\tfp = open(dialog.get_filename(), \"w\")\n>-\t\t\tfp.write(patch_buffer)\n>-\t\t\tfp.close()\n>-\t\tdialog.destroy()\n>-\n>-class GitView(object):\n>-\t\"\"\" This is the main class\n>-\t\"\"\"\n>-\tversion = \"0.9\"\n>-\n>-\tdef __init__(self, with_diff=0):\n>-\t\tself.with_diff = with_diff\n>-\t\tself.window =\tgtk.Window(gtk.WINDOW_TOPLEVEL)\n>-\t\tself.window.set_border_width(0)\n>-\t\tself.window.set_title(\"Git repository browser\")\n>-\n>-\t\tself.get_encoding()\n>-\t\tself.get_bt_sha1()\n>-\n>-\t\t# Use three-quarters of the screen by default\n>-\t\tscreen = self.window.get_screen()\n>-\t\tmonitor = screen.get_monitor_geometry(0)\n>-\t\twidth = int(monitor.width * 0.75)\n>-\t\theight = int(monitor.height * 0.75)\n>-\t\tself.window.set_default_size(width, height)\n>-\n>-\t\t# FIXME AndyFitz!\n>-\t\ticon = self.window.render_icon(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)\n>-\t\tself.window.set_icon(icon)\n>-\n>-\t\tself.accel_group = gtk.AccelGroup()\n>-\t\tself.window.add_accel_group(self.accel_group)\n>-\t\tself.accel_group.connect_group(0xffc2, 0, gtk.ACCEL_LOCKED, self.refresh);\n>-\t\tself.accel_group.connect_group(0xffc1, 0, gtk.ACCEL_LOCKED, self.maximize);\n>-\t\tself.accel_group.connect_group(0xffc8, 0, gtk.ACCEL_LOCKED, self.fullscreen);\n>-\t\tself.accel_group.connect_group(0xffc9, 0, gtk.ACCEL_LOCKED, self.unfullscreen);\n>-\n>-\t\tself.window.add(self.construct())\n>-\n>-\tdef refresh(self, widget, event=None, *arguments, **keywords):\n>-\t\tself.get_encoding()\n>-\t\tself.get_bt_sha1()\n>-\t\tCommit.children_sha1 = {}\n>-\t\tself.set_branch(sys.argv[without_diff:])\n>-\t\tself.window.show()\n>-\t\treturn True\n>-\n>-\tdef maximize(self, widget, event=None, *arguments, **keywords):\n>-\t\tself.window.maximize()\n>-\t\treturn True\n>-\n>-\tdef fullscreen(self, widget, event=None, *arguments, **keywords):\n>-\t\tself.window.fullscreen()\n>-\t\treturn True\n>-\n>-\tdef unfullscreen(self, widget, event=None, *arguments, **keywords):\n>-\t\tself.window.unfullscreen()\n>-\t\treturn True\n>-\n>-\tdef get_bt_sha1(self):\n>-\t\t\"\"\" Update the bt_sha1 dictionary with the\n>-\t\trespective sha1 details \"\"\"\n>-\n>-\t\tself.bt_sha1 = { }\n>-\t\tls_remote = re.compile('^(.{40})\\trefs/([^^]+)(?:\\\\^(..))?$');\n>-\t\tfp = os.popen('git ls-remote \"${GIT_DIR-.git}\"')\n>-\t\twhile 1:\n>-\t\t\tline = string.strip(fp.readline())\n>-\t\t\tif line == '':\n>-\t\t\t\tbreak\n>-\t\t\tm = ls_remote.match(line)\n>-\t\t\tif not m:\n>-\t\t\t\tcontinue\n>-\t\t\t(sha1, name) = (m.group(1), m.group(2))\n>-\t\t\tif not self.bt_sha1.has_key(sha1):\n>-\t\t\t\tself.bt_sha1[sha1] = []\n>-\t\t\tself.bt_sha1[sha1].append(name)\n>-\t\tfp.close()\n>-\n>-\tdef get_encoding(self):\n>-\t\tfp = os.popen(\"git config --get i18n.commitencoding\")\n>-\t\tself.encoding=string.strip(fp.readline())\n>-\t\tfp.close()\n>-\t\tif (self.encoding == \"\"):\n>-\t\t\tself.encoding = \"utf-8\"\n>-\n>-\n>-\tdef construct(self):\n>-\t\t\"\"\"Construct the window contents.\"\"\"\n>-\t\tvbox = gtk.VBox()\n>-\t\tpaned = gtk.VPaned()\n>-\t\tpaned.pack1(self.construct_top(), resize=False, shrink=True)\n>-\t\tpaned.pack2(self.construct_bottom(), resize=False, shrink=True)\n>-\t\tmenu_bar = gtk.MenuBar()\n>-\t\tmenu_bar.set_pack_direction(gtk.PACK_DIRECTION_RTL)\n>-\t\thelp_menu = gtk.MenuItem(\"Help\")\n>-\t\tmenu = gtk.Menu()\n>-\t\tabout_menu = gtk.MenuItem(\"About\")\n>-\t\tmenu.append(about_menu)\n>-\t\tabout_menu.connect(\"activate\", self.about_menu_response, \"about\")\n>-\t\tabout_menu.show()\n>-\t\thelp_menu.set_submenu(menu)\n>-\t\thelp_menu.show()\n>-\t\tmenu_bar.append(help_menu)\n>-\t\tmenu_bar.show()\n>-\t\tvbox.pack_start(menu_bar, expand=False, fill=True)\n>-\t\tvbox.pack_start(paned, expand=True, fill=True)\n>-\t\tpaned.show()\n>-\t\tvbox.show()\n>-\t\treturn vbox\n>-\n>-\n>-\tdef construct_top(self):\n>-\t\t\"\"\"Construct the top-half of the window.\"\"\"\n>-\t\tvbox = gtk.VBox(spacing=6)\n>-\t\tvbox.set_border_width(12)\n>-\t\tvbox.show()\n>-\n>-\n>-\t\tscrollwin = gtk.ScrolledWindow()\n>-\t\tscrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)\n>-\t\tscrollwin.set_shadow_type(gtk.SHADOW_IN)\n>-\t\tvbox.pack_start(scrollwin, expand=True, fill=True)\n>-\t\tscrollwin.show()\n>-\n>-\t\tself.treeview = gtk.TreeView()\n>-\t\tself.treeview.set_rules_hint(True)\n>-\t\tself.treeview.set_search_column(4)\n>-\t\tself.treeview.connect(\"cursor-changed\", self._treeview_cursor_cb)\n>-\t\tscrollwin.add(self.treeview)\n>-\t\tself.treeview.show()\n>-\n>-\t\tcell = CellRendererGraph()\n>-\t\tcolumn = gtk.TreeViewColumn()\n>-\t\tcolumn.set_resizable(True)\n>-\t\tcolumn.pack_start(cell, expand=True)\n>-\t\tcolumn.add_attribute(cell, \"node\", 1)\n>-\t\tcolumn.add_attribute(cell, \"in-lines\", 2)\n>-\t\tcolumn.add_attribute(cell, \"out-lines\", 3)\n>-\t\tself.treeview.append_column(column)\n>-\n>-\t\tcell = gtk.CellRendererText()\n>-\t\tcell.set_property(\"width-chars\", 65)\n>-\t\tcell.set_property(\"ellipsize\", pango.ELLIPSIZE_END)\n>-\t\tcolumn = gtk.TreeViewColumn(\"Message\")\n>-\t\tcolumn.set_resizable(True)\n>-\t\tcolumn.pack_start(cell, expand=True)\n>-\t\tcolumn.add_attribute(cell, \"text\", 4)\n>-\t\tself.treeview.append_column(column)\n>-\n>-\t\tcell = gtk.CellRendererText()\n>-\t\tcell.set_property(\"width-chars\", 40)\n>-\t\tcell.set_property(\"ellipsize\", pango.ELLIPSIZE_END)\n>-\t\tcolumn = gtk.TreeViewColumn(\"Author\")\n>-\t\tcolumn.set_resizable(True)\n>-\t\tcolumn.pack_start(cell, expand=True)\n>-\t\tcolumn.add_attribute(cell, \"text\", 5)\n>-\t\tself.treeview.append_column(column)\n>-\n>-\t\tcell = gtk.CellRendererText()\n>-\t\tcell.set_property(\"ellipsize\", pango.ELLIPSIZE_END)\n>-\t\tcolumn = gtk.TreeViewColumn(\"Date\")\n>-\t\tcolumn.set_resizable(True)\n>-\t\tcolumn.pack_start(cell, expand=True)\n>-\t\tcolumn.add_attribute(cell, \"text\", 6)\n>-\t\tself.treeview.append_column(column)\n>-\n>-\t\treturn vbox\n>-\n>-\tdef about_menu_response(self, widget, string):\n>-\t\tdialog = gtk.AboutDialog()\n>-\t\tdialog.set_name(\"Gitview\")\n>-\t\tdialog.set_version(GitView.version)\n>-\t\tdialog.set_authors([\"Aneesh Kumar K.V <aneesh.kumar@gmail.com>\"])\n>-\t\tdialog.set_website(\"http://www.kernel.org/pub/software/scm/git/\")\n>-\t\tdialog.set_copyright(\"Use and distribute under the terms of the GNU General Public License\")\n>-\t\tdialog.set_wrap_license(True)\n>-\t\tdialog.run()\n>-\t\tdialog.destroy()\n>-\n>-\n>-\tdef construct_bottom(self):\n>-\t\t\"\"\"Construct the bottom half of the window.\"\"\"\n>-\t\tvbox = gtk.VBox(False, spacing=6)\n>-\t\tvbox.set_border_width(12)\n>-\t\t(width, height) = self.window.get_size()\n>-\t\tvbox.set_size_request(width, int(height / 2.5))\n>-\t\tvbox.show()\n>-\n>-\t\tself.table = gtk.Table(rows=4, columns=4)\n>-\t\tself.table.set_row_spacings(6)\n>-\t\tself.table.set_col_spacings(6)\n>-\t\tvbox.pack_start(self.table, expand=False, fill=True)\n>-\t\tself.table.show()\n>-\n>-\t\talign = gtk.Alignment(0.0, 0.5)\n>-\t\tlabel = gtk.Label()\n>-\t\tlabel.set_markup(\"<b>Revision:</b>\")\n>-\t\talign.add(label)\n>-\t\tself.table.attach(align, 0, 1, 0, 1, gtk.FILL, gtk.FILL)\n>-\t\tlabel.show()\n>-\t\talign.show()\n>-\n>-\t\talign = gtk.Alignment(0.0, 0.5)\n>-\t\tself.revid_label = gtk.Label()\n>-\t\tself.revid_label.set_selectable(True)\n>-\t\talign.add(self.revid_label)\n>-\t\tself.table.attach(align, 1, 2, 0, 1, gtk.EXPAND | gtk.FILL, gtk.FILL)\n>-\t\tself.revid_label.show()\n>-\t\talign.show()\n>-\n>-\t\talign = gtk.Alignment(0.0, 0.5)\n>-\t\tlabel = gtk.Label()\n>-\t\tlabel.set_markup(\"<b>Committer:</b>\")\n>-\t\talign.add(label)\n>-\t\tself.table.attach(align, 0, 1, 1, 2, gtk.FILL, gtk.FILL)\n>-\t\tlabel.show()\n>-\t\talign.show()\n>-\n>-\t\talign = gtk.Alignment(0.0, 0.5)\n>-\t\tself.committer_label = gtk.Label()\n>-\t\tself.committer_label.set_selectable(True)\n>-\t\talign.add(self.committer_label)\n>-\t\tself.table.attach(align, 1, 2, 1, 2, gtk.EXPAND | gtk.FILL, gtk.FILL)\n>-\t\tself.committer_label.show()\n>-\t\talign.show()\n>-\n>-\t\talign = gtk.Alignment(0.0, 0.5)\n>-\t\tlabel = gtk.Label()\n>-\t\tlabel.set_markup(\"<b>Timestamp:</b>\")\n>-\t\talign.add(label)\n>-\t\tself.table.attach(align, 0, 1, 2, 3, gtk.FILL, gtk.FILL)\n>-\t\tlabel.show()\n>-\t\talign.show()\n>-\n>-\t\talign = gtk.Alignment(0.0, 0.5)\n>-\t\tself.timestamp_label = gtk.Label()\n>-\t\tself.timestamp_label.set_selectable(True)\n>-\t\talign.add(self.timestamp_label)\n>-\t\tself.table.attach(align, 1, 2, 2, 3, gtk.EXPAND | gtk.FILL, gtk.FILL)\n>-\t\tself.timestamp_label.show()\n>-\t\talign.show()\n>-\n>-\t\talign = gtk.Alignment(0.0, 0.5)\n>-\t\tlabel = gtk.Label()\n>-\t\tlabel.set_markup(\"<b>Parents:</b>\")\n>-\t\talign.add(label)\n>-\t\tself.table.attach(align, 0, 1, 3, 4, gtk.FILL, gtk.FILL)\n>-\t\tlabel.show()\n>-\t\talign.show()\n>-\t\tself.parents_widgets = []\n>-\n>-\t\talign = gtk.Alignment(0.0, 0.5)\n>-\t\tlabel = gtk.Label()\n>-\t\tlabel.set_markup(\"<b>Children:</b>\")\n>-\t\talign.add(label)\n>-\t\tself.table.attach(align, 2, 3, 3, 4, gtk.FILL, gtk.FILL)\n>-\t\tlabel.show()\n>-\t\talign.show()\n>-\t\tself.children_widgets = []\n>-\n>-\t\tscrollwin = gtk.ScrolledWindow()\n>-\t\tscrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)\n>-\t\tscrollwin.set_shadow_type(gtk.SHADOW_IN)\n>-\t\tvbox.pack_start(scrollwin, expand=True, fill=True)\n>-\t\tscrollwin.show()\n>-\n>-\t\t(self.message_buffer, sourceview) = get_source_buffer_and_view()\n>-\n>-\t\tsourceview.set_editable(False)\n>-\t\tsourceview.modify_font(pango.FontDescription(\"Monospace\"))\n>-\t\tscrollwin.add(sourceview)\n>-\t\tsourceview.show()\n>-\n>-\t\treturn vbox\n>-\n>-\tdef _treeview_cursor_cb(self, *args):\n>-\t\t\"\"\"Callback for when the treeview cursor changes.\"\"\"\n>-\t\t(path, col) = self.treeview.get_cursor()\n>-\t\tcommit = self.model[path][0]\n>-\n>-\t\tif commit.committer is not None:\n>-\t\t\tcommitter = commit.committer\n>-\t\t\ttimestamp = commit.commit_date\n>-\t\t\tmessage   =  commit.get_message(self.with_diff)\n>-\t\t\trevid_label = commit.commit_sha1\n>-\t\telse:\n>-\t\t\tcommitter = \"\"\n>-\t\t\ttimestamp = \"\"\n>-\t\t\tmessage = \"\"\n>-\t\t\trevid_label = \"\"\n>-\n>-\t\tself.revid_label.set_text(revid_label)\n>-\t\tself.committer_label.set_text(committer)\n>-\t\tself.timestamp_label.set_text(timestamp)\n>-\t\tself.message_buffer.set_text(unicode(message, self.encoding).encode('utf-8'))\n>-\n>-\t\tfor widget in self.parents_widgets:\n>-\t\t\tself.table.remove(widget)\n>-\n>-\t\tself.parents_widgets = []\n>-\t\tself.table.resize(4 + len(commit.parent_sha1) - 1, 4)\n>-\t\tfor idx, parent_id in enumerate(commit.parent_sha1):\n>-\t\t\tself.table.set_row_spacing(idx + 3, 0)\n>-\n>-\t\t\talign = gtk.Alignment(0.0, 0.0)\n>-\t\t\tself.parents_widgets.append(align)\n>-\t\t\tself.table.attach(align, 1, 2, idx + 3, idx + 4,\n>-\t\t\t\t\tgtk.EXPAND | gtk.FILL, gtk.FILL)\n>-\t\t\talign.show()\n>-\n>-\t\t\thbox = gtk.HBox(False, 0)\n>-\t\t\talign.add(hbox)\n>-\t\t\thbox.show()\n>-\n>-\t\t\tlabel = gtk.Label(parent_id)\n>-\t\t\tlabel.set_selectable(True)\n>-\t\t\thbox.pack_start(label, expand=False, fill=True)\n>-\t\t\tlabel.show()\n>-\n>-\t\t\timage = gtk.Image()\n>-\t\t\timage.set_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU)\n>-\t\t\timage.show()\n>-\n>-\t\t\tbutton = gtk.Button()\n>-\t\t\tbutton.add(image)\n>-\t\t\tbutton.set_relief(gtk.RELIEF_NONE)\n>-\t\t\tbutton.connect(\"clicked\", self._go_clicked_cb, parent_id)\n>-\t\t\thbox.pack_start(button, expand=False, fill=True)\n>-\t\t\tbutton.show()\n>-\n>-\t\t\timage = gtk.Image()\n>-\t\t\timage.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU)\n>-\t\t\timage.show()\n>-\n>-\t\t\tbutton = gtk.Button()\n>-\t\t\tbutton.add(image)\n>-\t\t\tbutton.set_relief(gtk.RELIEF_NONE)\n>-\t\t\tbutton.set_sensitive(True)\n>-\t\t\tbutton.connect(\"clicked\", self._show_clicked_cb,\n>-\t\t\t\t\tcommit.commit_sha1, parent_id, self.encoding)\n>-\t\t\thbox.pack_start(button, expand=False, fill=True)\n>-\t\t\tbutton.show()\n>-\n>-\t\t# Populate with child details\n>-\t\tfor widget in self.children_widgets:\n>-\t\t\tself.table.remove(widget)\n>-\n>-\t\tself.children_widgets = []\n>-\t\ttry:\n>-\t\t\tchild_sha1 = Commit.children_sha1[commit.commit_sha1]\n>-\t\texcept KeyError:\n>-\t\t\t# We don't have child\n>-\t\t\tchild_sha1 = [ 0 ]\n>-\n>-\t\tif ( len(child_sha1) > len(commit.parent_sha1)):\n>-\t\t\tself.table.resize(4 + len(child_sha1) - 1, 4)\n>-\n>-\t\tfor idx, child_id in enumerate(child_sha1):\n>-\t\t\tself.table.set_row_spacing(idx + 3, 0)\n>-\n>-\t\t\talign = gtk.Alignment(0.0, 0.0)\n>-\t\t\tself.children_widgets.append(align)\n>-\t\t\tself.table.attach(align, 3, 4, idx + 3, idx + 4,\n>-\t\t\t\t\tgtk.EXPAND | gtk.FILL, gtk.FILL)\n>-\t\t\talign.show()\n>-\n>-\t\t\thbox = gtk.HBox(False, 0)\n>-\t\t\talign.add(hbox)\n>-\t\t\thbox.show()\n>-\n>-\t\t\tlabel = gtk.Label(child_id)\n>-\t\t\tlabel.set_selectable(True)\n>-\t\t\thbox.pack_start(label, expand=False, fill=True)\n>-\t\t\tlabel.show()\n>-\n>-\t\t\timage = gtk.Image()\n>-\t\t\timage.set_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU)\n>-\t\t\timage.show()\n>-\n>-\t\t\tbutton = gtk.Button()\n>-\t\t\tbutton.add(image)\n>-\t\t\tbutton.set_relief(gtk.RELIEF_NONE)\n>-\t\t\tbutton.connect(\"clicked\", self._go_clicked_cb, child_id)\n>-\t\t\thbox.pack_start(button, expand=False, fill=True)\n>-\t\t\tbutton.show()\n>-\n>-\t\t\timage = gtk.Image()\n>-\t\t\timage.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU)\n>-\t\t\timage.show()\n>-\n>-\t\t\tbutton = gtk.Button()\n>-\t\t\tbutton.add(image)\n>-\t\t\tbutton.set_relief(gtk.RELIEF_NONE)\n>-\t\t\tbutton.set_sensitive(True)\n>-\t\t\tbutton.connect(\"clicked\", self._show_clicked_cb,\n>-\t\t\t\t\tchild_id, commit.commit_sha1, self.encoding)\n>-\t\t\thbox.pack_start(button, expand=False, fill=True)\n>-\t\t\tbutton.show()\n>-\n>-\tdef _destroy_cb(self, widget):\n>-\t\t\"\"\"Callback for when a window we manage is destroyed.\"\"\"\n>-\t\tself.quit()\n>-\n>-\n>-\tdef quit(self):\n>-\t\t\"\"\"Stop the GTK+ main loop.\"\"\"\n>-\t\tgtk.main_quit()\n>-\n>-\tdef run(self, args):\n>-\t\tself.set_branch(args)\n>-\t\tself.window.connect(\"destroy\", self._destroy_cb)\n>-\t\tself.window.show()\n>-\t\tgtk.main()\n>-\n>-\tdef set_branch(self, args):\n>-\t\t\"\"\"Fill in different windows with info from the reposiroty\"\"\"\n>-\t\tfp = os.popen(\"git rev-parse --sq --default HEAD \" + list_to_string(args, 1))\n>-\t\tgit_rev_list_cmd = fp.read()\n>-\t\tfp.close()\n>-\t\tfp = os.popen(\"git rev-list  --header --topo-order --parents \" + git_rev_list_cmd)\n>-\t\tself.update_window(fp)\n>-\n>-\tdef update_window(self, fp):\n>-\t\tcommit_lines = []\n>-\n>-\t\tself.model = gtk.ListStore(gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,\n>-\t\t\t\tgobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, str, str, str)\n>-\n>-\t\t# used for cursor positioning\n>-\t\tself.index = {}\n>-\n>-\t\tself.colours = {}\n>-\t\tself.nodepos = {}\n>-\t\tself.incomplete_line = {}\n>-\t\tself.commits = []\n>-\n>-\t\tindex = 0\n>-\t\tlast_colour = 0\n>-\t\tlast_nodepos = -1\n>-\t\tout_line = []\n>-\t\tinput_line = fp.readline()\n>-\t\twhile (input_line != \"\"):\n>-\t\t\t# The commit header ends with '\\0'\n>-\t\t\t# This NULL is immediately followed by the sha1 of the\n>-\t\t\t# next commit\n>-\t\t\tif (input_line[0] != '\\0'):\n>-\t\t\t\tcommit_lines.append(input_line)\n>-\t\t\t\tinput_line = fp.readline()\n>-\t\t\t\tcontinue;\n>-\n>-\t\t\tcommit = Commit(commit_lines)\n>-\t\t\tif (commit != None ):\n>-\t\t\t\tself.commits.append(commit)\n>-\n>-\t\t\t# Skip the '\\0\n>-\t\t\tcommit_lines = []\n>-\t\t\tcommit_lines.append(input_line[1:])\n>-\t\t\tinput_line = fp.readline()\n>-\n>-\t\tfp.close()\n>-\n>-\t\tfor commit in self.commits:\n>-\t\t\t(out_line, last_colour, last_nodepos) = self.draw_graph(commit,\n>-\t\t\t\t\t\t\t\t\t\tindex, out_line,\n>-\t\t\t\t\t\t\t\t\t\tlast_colour,\n>-\t\t\t\t\t\t\t\t\t\tlast_nodepos)\n>-\t\t\tself.index[commit.commit_sha1] = index\n>-\t\t\tindex += 1\n>-\n>-\t\tself.treeview.set_model(self.model)\n>-\t\tself.treeview.show()\n>-\n>-\tdef draw_graph(self, commit, index, out_line, last_colour, last_nodepos):\n>-\t\tin_line=[]\n>-\n>-\t\t#   |   -> outline\n>-\t\t#   X\n>-\t\t#   |\\  <- inline\n>-\n>-\t\t# Reset nodepostion\n>-\t\tif (last_nodepos > 5):\n>-\t\t\tlast_nodepos = -1\n>-\n>-\t\t# Add the incomplete lines of the last cell in this\n>-\t\ttry:\n>-\t\t\tcolour = self.colours[commit.commit_sha1]\n>-\t\texcept KeyError:\n>-\t\t\tself.colours[commit.commit_sha1] = last_colour+1\n>-\t\t\tlast_colour = self.colours[commit.commit_sha1]\n>-\t\t\tcolour =   self.colours[commit.commit_sha1]\n>-\n>-\t\ttry:\n>-\t\t\tnode_pos = self.nodepos[commit.commit_sha1]\n>-\t\texcept KeyError:\n>-\t\t\tself.nodepos[commit.commit_sha1] = last_nodepos+1\n>-\t\t\tlast_nodepos = self.nodepos[commit.commit_sha1]\n>-\t\t\tnode_pos =  self.nodepos[commit.commit_sha1]\n>-\n>-\t\t#The first parent always continue on the same line\n>-\t\ttry:\n>-\t\t\t# check we already have the value\n>-\t\t\ttmp_node_pos = self.nodepos[commit.parent_sha1[0]]\n>-\t\texcept KeyError:\n>-\t\t\tself.colours[commit.parent_sha1[0]] = colour\n>-\t\t\tself.nodepos[commit.parent_sha1[0]] = node_pos\n>-\n>-\t\tfor sha1 in self.incomplete_line.keys():\n>-\t\t\tif (sha1 != commit.commit_sha1):\n>-\t\t\t\tself.draw_incomplete_line(sha1, node_pos,\n>-\t\t\t\t\t\tout_line, in_line, index)\n>-\t\t\telse:\n>-\t\t\t\tdel self.incomplete_line[sha1]\n>-\n>-\n>-\t\tfor parent_id in commit.parent_sha1:\n>-\t\t\ttry:\n>-\t\t\t\ttmp_node_pos = self.nodepos[parent_id]\n>-\t\t\texcept KeyError:\n>-\t\t\t\tself.colours[parent_id] = last_colour+1\n>-\t\t\t\tlast_colour = self.colours[parent_id]\n>-\t\t\t\tself.nodepos[parent_id] = last_nodepos+1\n>-\t\t\t\tlast_nodepos = self.nodepos[parent_id]\n>-\n>-\t\t\tin_line.append((node_pos, self.nodepos[parent_id],\n>-\t\t\t\t\t\tself.colours[parent_id]))\n>-\t\t\tself.add_incomplete_line(parent_id)\n>-\n>-\t\ttry:\n>-\t\t\tbranch_tag = self.bt_sha1[commit.commit_sha1]\n>-\t\texcept KeyError:\n>-\t\t\tbranch_tag = [ ]\n>-\n>-\n>-\t\tnode = (node_pos, colour, branch_tag)\n>-\n>-\t\tself.model.append([commit, node, out_line, in_line,\n>-\t\t\t\tcommit.message, commit.author, commit.date])\n>-\n>-\t\treturn (in_line, last_colour, last_nodepos)\n>-\n>-\tdef add_incomplete_line(self, sha1):\n>-\t\ttry:\n>-\t\t\tself.incomplete_line[sha1].append(self.nodepos[sha1])\n>-\t\texcept KeyError:\n>-\t\t\tself.incomplete_line[sha1] = [self.nodepos[sha1]]\n>-\n>-\tdef draw_incomplete_line(self, sha1, node_pos, out_line, in_line, index):\n>-\t\tfor idx, pos in enumerate(self.incomplete_line[sha1]):\n>-\t\t\tif(pos == node_pos):\n>-\t\t\t\t#remove the straight line and add a slash\n>-\t\t\t\tif ((pos, pos, self.colours[sha1]) in out_line):\n>-\t\t\t\t\tout_line.remove((pos, pos, self.colours[sha1]))\n>-\t\t\t\tout_line.append((pos, pos+0.5, self.colours[sha1]))\n>-\t\t\t\tself.incomplete_line[sha1][idx] = pos = pos+0.5\n>-\t\t\ttry:\n>-\t\t\t\tnext_commit = self.commits[index+1]\n>-\t\t\t\tif (next_commit.commit_sha1 == sha1 and pos != int(pos)):\n>-\t\t\t\t# join the line back to the node point\n>-\t\t\t\t# This need to be done only if we modified it\n>-\t\t\t\t\tin_line.append((pos, pos-0.5, self.colours[sha1]))\n>-\t\t\t\t\tcontinue;\n>-\t\t\texcept IndexError:\n>-\t\t\t\tpass\n>-\t\t\tin_line.append((pos, pos, self.colours[sha1]))\n>-\n>-\n>-\tdef _go_clicked_cb(self, widget, revid):\n>-\t\t\"\"\"Callback for when the go button for a parent is clicked.\"\"\"\n>-\t\ttry:\n>-\t\t\tself.treeview.set_cursor(self.index[revid])\n>-\t\texcept KeyError:\n>-\t\t\tdialog = gtk.MessageDialog(parent=None, flags=0,\n>-\t\t\t\t\ttype=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_CLOSE,\n>-\t\t\t\t\tmessage_format=None)\n>-\t\t\tdialog.set_markup(\"Revision <b>%s</b> not present in the list\" % revid)\n>-\t\t\t# revid == 0 is the parent of the first commit\n>-\t\t\tif (revid != 0 ):\n>-\t\t\t\tdialog.format_secondary_text(\"Try running gitview without any options\")\n>-\t\t\tdialog.run()\n>-\t\t\tdialog.destroy()\n>-\n>-\t\tself.treeview.grab_focus()\n>-\n>-\tdef _show_clicked_cb(self, widget,  commit_sha1, parent_sha1, encoding):\n>-\t\t\"\"\"Callback for when the show button for a parent is clicked.\"\"\"\n>-\t\twindow = DiffWindow()\n>-\t\twindow.set_diff(commit_sha1, parent_sha1, encoding)\n>-\t\tself.treeview.grab_focus()\n>-\n>-without_diff = 0\n>-if __name__ == \"__main__\":\n>-\n>-\tif (len(sys.argv) > 1 ):\n>-\t\tif (sys.argv[1] == \"--without-diff\"):\n>-\t\t\twithout_diff = 1\n>-\n>-\tview = GitView( without_diff != 1)\n>-\tview.run(sys.argv[without_diff:])\n>diff --git a/contrib/gitview/gitview.txt b/contrib/gitview/gitview.txt\n>deleted file mode 100644\n>index 7b5f9002b9..0000000000\n>--- a/contrib/gitview/gitview.txt\n>+++ /dev/null\n>@@ -1,57 +0,0 @@\n>-gitview(1)\n>-==========\n>-\n>-NAME\n>-----\n>-gitview - A GTK based repository browser for git\n>-\n>-SYNOPSIS\n>---------\n>-[verse]\n>-'gitview' [options] [args]\n>-\n>-DESCRIPTION\n>----------\n>-\n>-Dependencies:\n>-\n>-* Python 2.4\n>-* PyGTK 2.8 or later\n>-* PyCairo 1.0 or later\n>-\n>-OPTIONS\n>--------\n>---without-diff::\n>-\n>-\tIf the user doesn't want to list the commit diffs in the main window.\n>-\tThis may speed up the repository browsing.\n>-\n>-<args>::\n>-\n>-\tAll the valid option for linkgit:git-rev-list[1].\n>-\n>-Key Bindings\n>-------------\n>-F4::\n>-\tTo maximize the window\n>-\n>-F5::\n>-\tTo reread references.\n>-\n>-F11::\n>-\tFull screen\n>-\n>-F12::\n>-\tLeave full screen\n>-\n>-EXAMPLES\n>---------\n>-\n>-gitview v2.6.12.. include/scsi drivers/scsi::\n>-\n>-\tShow as the changes since version v2.6.12 that changed any file in the\n>-\tinclude/scsi or drivers/scsi subdirectories\n>-\n>-gitview --since=2.weeks.ago::\n>-\n>-\tShow the changes during the last two weeks\n>-- \n>2.11.0.259.ga95e92af08\n>\n>\n>"},{"id":"308478","messageId":"20161229015918.jyiqd42z4htjibul@sigill.intra.peff.net","threadId":"44770","inReplyTo":"20161228172837.24395-1-sbeller@google.com","subject":"Re: [PATCH] contrib: remove gitview","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2016-12-29T01:59:19Z","receivedAt":"2016-12-29T01:59:30Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Dec 28, 2016 at 09:28:37AM -0800, Stefan Beller wrote:\n\n> gitview did not have meaningful contributions since 2007, which gives the\n> impression it is either a mature or dead project.\n> \n> In both cases we should not carry it in git.git as the README for contrib\n> states we only want to carry experimental things to give early exposure.\n> \n> Recently a security vulnerability was reported by Javantea, so the decision\n> to either fix the issue or remove the code in question becomes a bit\n> more urgent.\n> \n> Reported-by: Javantea <jvoss@altsci.com>\n> Signed-off-by: Stefan Beller <sbeller@google.com>\n> ---\n>  contrib/gitview/gitview     | 1305 -------------------------------------------\n>  contrib/gitview/gitview.txt |   57 --\n>  2 files changed, 1362 deletions(-)\n>  delete mode 100755 contrib/gitview/gitview\n>  delete mode 100644 contrib/gitview/gitview.txt\n\nThanks for assembling the patch. This seems reasonable to me, though I'd\nlike to get an Ack from Aneesh if we can.\n\n-Peff\n"},{"id":"308557","messageId":"xmqqlguv61kx.fsf@gitster.mtv.corp.google.com","threadId":"44770","inReplyTo":"20161229015918.jyiqd42z4htjibul@sigill.intra.peff.net","subject":"Re: [PATCH] contrib: remove gitview","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-01-01T00:55:26Z","receivedAt":"2017-01-01T00:55:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> On Wed, Dec 28, 2016 at 09:28:37AM -0800, Stefan Beller wrote:\n> ...\n>>  2 files changed, 1362 deletions(-)\n>>  delete mode 100755 contrib/gitview/gitview\n>>  delete mode 100644 contrib/gitview/gitview.txt\n>\n> Thanks for assembling the patch. This seems reasonable to me, though I'd\n> like to get an Ack from Aneesh if we can.\n\nLikewise.\n"},{"id":"308605","messageId":"8760lyc62u.fsf@linux.vnet.ibm.com","threadId":"44770","inReplyTo":"20161229015918.jyiqd42z4htjibul@sigill.intra.peff.net","subject":"Re: [PATCH] contrib: remove gitview","fromName":"Aneesh Kumar K.V","fromEmail":"aneesh.kumar@linux.vnet.ibm.com","sentAt":"2017-01-02T06:46:17Z","receivedAt":"2017-01-02T06:46:44Z","isPatch":true,"sender":{"key":"aneesh.kumar@linux.vnet.ibm.com","avatar":"https://gravatar.com/avatar/a32cbcc7e70c8eb5a4cdbd1ea0b6c2ead382f2529cc43f51fd350767f079d92d?d=mp&s=160"},"body":"Jeff King <peff@peff.net> writes:\n\n> On Wed, Dec 28, 2016 at 09:28:37AM -0800, Stefan Beller wrote:\n>\n>> gitview did not have meaningful contributions since 2007, which gives the\n>> impression it is either a mature or dead project.\n>> \n>> In both cases we should not carry it in git.git as the README for contrib\n>> states we only want to carry experimental things to give early exposure.\n>> \n>> Recently a security vulnerability was reported by Javantea, so the decision\n>> to either fix the issue or remove the code in question becomes a bit\n>> more urgent.\n>> \n>> Reported-by: Javantea <jvoss@altsci.com>\n>> Signed-off-by: Stefan Beller <sbeller@google.com>\n>> ---\n>>  contrib/gitview/gitview     | 1305 -------------------------------------------\n>>  contrib/gitview/gitview.txt |   57 --\n>>  2 files changed, 1362 deletions(-)\n>>  delete mode 100755 contrib/gitview/gitview\n>>  delete mode 100644 contrib/gitview/gitview.txt\n>\n> Thanks for assembling the patch. This seems reasonable to me, though I'd\n> like to get an Ack from Aneesh if we can.\n\nAcked-by: Aneesh Kumar K.V <aneesh.kumar@gmail.com>\n\n-aneesh\n\n"}]}