gitview: annotation support
List files modifed as a part of the commit in the diff window Support annotation of the file listed in the diff window Support history browsing in the annotation window. Signed-off-by: Aneesh Kumar K.V <aneesh.kumar@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net>
This commit is contained in:
parent
ad57cbca61
commit
2c9750cc8b
@ -10,7 +10,8 @@ GUI browser for git repository
|
||||
This program is based on bzrk by Scott James Remnant <scott@ubuntu.com>
|
||||
"""
|
||||
__copyright__ = "Copyright (C) 2006 Hewlett-Packard Development Company, L.P."
|
||||
__author__ = "Aneesh Kumar K.V <aneesh.kumar@hp.com>"
|
||||
__copyright__ = "Copyright (C) 2007 Aneesh Kumar K.V <aneesh.kumar@gmail.com"
|
||||
__author__ = "Aneesh Kumar K.V <aneesh.kumar@gmail.com>"
|
||||
|
||||
|
||||
import sys
|
||||
@ -24,6 +25,7 @@ import gobject
|
||||
import cairo
|
||||
import math
|
||||
import string
|
||||
import fcntl
|
||||
|
||||
try:
|
||||
import gtksourceview
|
||||
@ -337,6 +339,186 @@ class Commit:
|
||||
fp.close()
|
||||
return diff
|
||||
|
||||
class AnnotateWindow:
|
||||
"""Annotate window.
|
||||
This object represents and manages a single window containing the
|
||||
annotate information of the file
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
|
||||
self.window.set_border_width(0)
|
||||
self.window.set_title("Git repository browser annotation window")
|
||||
|
||||
# Use two thirds of the screen by default
|
||||
screen = self.window.get_screen()
|
||||
monitor = screen.get_monitor_geometry(0)
|
||||
width = int(monitor.width * 0.66)
|
||||
height = int(monitor.height * 0.66)
|
||||
self.window.set_default_size(width, height)
|
||||
|
||||
def add_file_data(self, filename, commit_sha1, line_num):
|
||||
fp = os.popen("git cat-file blob " + commit_sha1 +":"+filename)
|
||||
i = 1;
|
||||
for line in fp.readlines():
|
||||
line = string.rstrip(line)
|
||||
self.model.append(None, ["HEAD", filename, line, i])
|
||||
i = i+1
|
||||
fp.close()
|
||||
|
||||
# now set the cursor position
|
||||
self.treeview.set_cursor(line_num-1)
|
||||
self.treeview.grab_focus()
|
||||
|
||||
def _treeview_cursor_cb(self, *args):
|
||||
"""Callback for when the treeview cursor changes."""
|
||||
(path, col) = self.treeview.get_cursor()
|
||||
commit_sha1 = self.model[path][0]
|
||||
commit_msg = ""
|
||||
fp = os.popen("git cat-file commit " + commit_sha1)
|
||||
for line in fp.readlines():
|
||||
commit_msg = commit_msg + line
|
||||
fp.close()
|
||||
|
||||
self.commit_buffer.set_text(commit_msg)
|
||||
|
||||
def _treeview_row_activated(self, *args):
|
||||
"""Callback for when the treeview row gets selected."""
|
||||
(path, col) = self.treeview.get_cursor()
|
||||
commit_sha1 = self.model[path][0]
|
||||
filename = self.model[path][1]
|
||||
line_num = self.model[path][3]
|
||||
|
||||
window = AnnotateWindow();
|
||||
fp = os.popen("git rev-parse "+ commit_sha1 + "~1")
|
||||
commit_sha1 = string.strip(fp.readline())
|
||||
fp.close()
|
||||
window.annotate(filename, commit_sha1, line_num)
|
||||
|
||||
def data_ready(self, source, condition):
|
||||
while (1):
|
||||
try :
|
||||
buffer = source.read(8192)
|
||||
except:
|
||||
# resource temporary not available
|
||||
return True
|
||||
|
||||
if (len(buffer) == 0):
|
||||
gobject.source_remove(self.io_watch_tag)
|
||||
source.close()
|
||||
return False
|
||||
|
||||
for buff in buffer.split("\n"):
|
||||
annotate_line = re.compile('^([0-9a-f]{40}) (.+) (.+) (.+)$')
|
||||
m = annotate_line.match(buff)
|
||||
if not m:
|
||||
annotate_line = re.compile('^(filename) (.+)$')
|
||||
m = annotate_line.match(buff)
|
||||
if not m:
|
||||
continue
|
||||
filename = m.group(2)
|
||||
else:
|
||||
self.commit_sha1 = m.group(1)
|
||||
self.source_line = int(m.group(2))
|
||||
self.result_line = int(m.group(3))
|
||||
self.count = int(m.group(4))
|
||||
#set the details only when we have the file name
|
||||
continue
|
||||
|
||||
while (self.count > 0):
|
||||
# set at result_line + count-1 the sha1 as commit_sha1
|
||||
self.count = self.count - 1
|
||||
iter = self.model.iter_nth_child(None, self.result_line + self.count-1)
|
||||
self.model.set(iter, 0, self.commit_sha1, 1, filename, 3, self.source_line)
|
||||
|
||||
|
||||
def annotate(self, filename, commit_sha1, line_num):
|
||||
# verify the commit_sha1 specified has this filename
|
||||
|
||||
fp = os.popen("git ls-tree "+ commit_sha1 + " -- " + filename)
|
||||
line = string.strip(fp.readline())
|
||||
if line == '':
|
||||
# pop up the message the file is not there as a part of the commit
|
||||
fp.close()
|
||||
dialog = gtk.MessageDialog(parent=None, flags=0,
|
||||
type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_CLOSE,
|
||||
message_format=None)
|
||||
dialog.set_markup("The file %s is not present in the parent commit %s" % (filename, commit_sha1))
|
||||
dialog.run()
|
||||
dialog.destroy()
|
||||
return
|
||||
|
||||
fp.close()
|
||||
|
||||
vpan = gtk.VPaned();
|
||||
self.window.add(vpan);
|
||||
vpan.show()
|
||||
|
||||
scrollwin = gtk.ScrolledWindow()
|
||||
scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
|
||||
scrollwin.set_shadow_type(gtk.SHADOW_IN)
|
||||
vpan.pack1(scrollwin, True, True);
|
||||
scrollwin.show()
|
||||
|
||||
self.model = gtk.TreeStore(str, str, str, int)
|
||||
self.treeview = gtk.TreeView(self.model)
|
||||
self.treeview.set_rules_hint(True)
|
||||
self.treeview.set_search_column(0)
|
||||
self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
|
||||
self.treeview.connect("row-activated", self._treeview_row_activated)
|
||||
scrollwin.add(self.treeview)
|
||||
self.treeview.show()
|
||||
|
||||
cell = gtk.CellRendererText()
|
||||
cell.set_property("width-chars", 10)
|
||||
cell.set_property("ellipsize", pango.ELLIPSIZE_END)
|
||||
column = gtk.TreeViewColumn("Commit")
|
||||
column.set_resizable(True)
|
||||
column.pack_start(cell, expand=True)
|
||||
column.add_attribute(cell, "text", 0)
|
||||
self.treeview.append_column(column)
|
||||
|
||||
cell = gtk.CellRendererText()
|
||||
cell.set_property("width-chars", 20)
|
||||
cell.set_property("ellipsize", pango.ELLIPSIZE_END)
|
||||
column = gtk.TreeViewColumn("File Name")
|
||||
column.set_resizable(True)
|
||||
column.pack_start(cell, expand=True)
|
||||
column.add_attribute(cell, "text", 1)
|
||||
self.treeview.append_column(column)
|
||||
|
||||
cell = gtk.CellRendererText()
|
||||
cell.set_property("width-chars", 20)
|
||||
cell.set_property("ellipsize", pango.ELLIPSIZE_END)
|
||||
column = gtk.TreeViewColumn("Data")
|
||||
column.set_resizable(True)
|
||||
column.pack_start(cell, expand=True)
|
||||
column.add_attribute(cell, "text", 2)
|
||||
self.treeview.append_column(column)
|
||||
|
||||
# The commit message window
|
||||
scrollwin = gtk.ScrolledWindow()
|
||||
scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
|
||||
scrollwin.set_shadow_type(gtk.SHADOW_IN)
|
||||
vpan.pack2(scrollwin, True, True);
|
||||
scrollwin.show()
|
||||
|
||||
commit_text = gtk.TextView()
|
||||
self.commit_buffer = gtk.TextBuffer()
|
||||
commit_text.set_buffer(self.commit_buffer)
|
||||
scrollwin.add(commit_text)
|
||||
commit_text.show()
|
||||
|
||||
self.window.show()
|
||||
|
||||
self.add_file_data(filename, commit_sha1, line_num)
|
||||
|
||||
fp = os.popen("git blame --incremental -- " + filename + " " + commit_sha1)
|
||||
flags = fcntl.fcntl(fp.fileno(), fcntl.F_GETFL)
|
||||
fcntl.fcntl(fp.fileno(), fcntl.F_SETFL, flags | os.O_NONBLOCK)
|
||||
self.io_watch_tag = gobject.io_add_watch(fp, gobject.IO_IN, self.data_ready)
|
||||
|
||||
|
||||
class DiffWindow:
|
||||
"""Diff window.
|
||||
This object represents and manages a single window containing the
|
||||
@ -355,6 +537,7 @@ class DiffWindow:
|
||||
height = int(monitor.height * 0.66)
|
||||
self.window.set_default_size(width, height)
|
||||
|
||||
|
||||
self.construct()
|
||||
|
||||
def construct(self):
|
||||
@ -371,10 +554,12 @@ class DiffWindow:
|
||||
vbox.pack_start(menu_bar, expand=False, fill=True)
|
||||
menu_bar.show()
|
||||
|
||||
hpan = gtk.HPaned()
|
||||
|
||||
scrollwin = gtk.ScrolledWindow()
|
||||
scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
|
||||
scrollwin.set_shadow_type(gtk.SHADOW_IN)
|
||||
vbox.pack_start(scrollwin, expand=True, fill=True)
|
||||
hpan.pack1(scrollwin, True, True)
|
||||
scrollwin.show()
|
||||
|
||||
if have_gtksourceview:
|
||||
@ -388,11 +573,77 @@ class DiffWindow:
|
||||
self.buffer = gtk.TextBuffer()
|
||||
sourceview = gtk.TextView(self.buffer)
|
||||
|
||||
|
||||
sourceview.set_editable(False)
|
||||
sourceview.modify_font(pango.FontDescription("Monospace"))
|
||||
scrollwin.add(sourceview)
|
||||
sourceview.show()
|
||||
|
||||
# The file hierarchy: a scrollable treeview
|
||||
scrollwin = gtk.ScrolledWindow()
|
||||
scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
|
||||
scrollwin.set_shadow_type(gtk.SHADOW_IN)
|
||||
scrollwin.set_size_request(20, -1)
|
||||
hpan.pack2(scrollwin, True, True)
|
||||
scrollwin.show()
|
||||
|
||||
self.model = gtk.TreeStore(str, str, str)
|
||||
self.treeview = gtk.TreeView(self.model)
|
||||
self.treeview.set_search_column(1)
|
||||
self.treeview.connect("cursor-changed", self._treeview_clicked)
|
||||
scrollwin.add(self.treeview)
|
||||
self.treeview.show()
|
||||
|
||||
cell = gtk.CellRendererText()
|
||||
cell.set_property("width-chars", 20)
|
||||
column = gtk.TreeViewColumn("Select to annotate")
|
||||
column.pack_start(cell, expand=True)
|
||||
column.add_attribute(cell, "text", 0)
|
||||
self.treeview.append_column(column)
|
||||
|
||||
vbox.pack_start(hpan, expand=True, fill=True)
|
||||
hpan.show()
|
||||
|
||||
def _treeview_clicked(self, *args):
|
||||
"""Callback for when the treeview cursor changes."""
|
||||
(path, col) = self.treeview.get_cursor()
|
||||
specific_file = self.model[path][1]
|
||||
commit_sha1 = self.model[path][2]
|
||||
if specific_file == None :
|
||||
return
|
||||
elif specific_file == "" :
|
||||
specific_file = None
|
||||
|
||||
window = AnnotateWindow();
|
||||
window.annotate(specific_file, commit_sha1, 1)
|
||||
|
||||
|
||||
def commit_files(self, commit_sha1, parent_sha1):
|
||||
self.model.clear()
|
||||
add = self.model.append(None, [ "Added", None, None])
|
||||
dele = self.model.append(None, [ "Deleted", None, None])
|
||||
mod = self.model.append(None, [ "Modified", None, None])
|
||||
diff_tree = re.compile('^(:.{6}) (.{6}) (.{40}) (.{40}) (A|D|M)\s(.+)$')
|
||||
fp = os.popen("git diff-tree -r --no-commit-id " + parent_sha1 + " " + commit_sha1)
|
||||
while 1:
|
||||
line = string.strip(fp.readline())
|
||||
if line == '':
|
||||
break
|
||||
m = diff_tree.match(line)
|
||||
if not m:
|
||||
continue
|
||||
|
||||
attr = m.group(5)
|
||||
filename = m.group(6)
|
||||
if attr == "A":
|
||||
self.model.append(add, [filename, filename, commit_sha1])
|
||||
elif attr == "D":
|
||||
self.model.append(dele, [filename, filename, commit_sha1])
|
||||
elif attr == "M":
|
||||
self.model.append(mod, [filename, filename, commit_sha1])
|
||||
fp.close()
|
||||
|
||||
self.treeview.expand_all()
|
||||
|
||||
def set_diff(self, commit_sha1, parent_sha1, encoding):
|
||||
"""Set the differences showed by this window.
|
||||
@ -406,6 +657,7 @@ class DiffWindow:
|
||||
fp = os.popen("git diff-tree -p " + parent_sha1 + " " + commit_sha1)
|
||||
self.buffer.set_text(unicode(fp.read(), encoding).encode('utf-8'))
|
||||
fp.close()
|
||||
self.commit_files(commit_sha1, parent_sha1)
|
||||
self.window.show()
|
||||
|
||||
def save_menu_response(self, widget, string):
|
||||
@ -425,7 +677,7 @@ class DiffWindow:
|
||||
class GitView:
|
||||
""" This is the main class
|
||||
"""
|
||||
version = "0.8"
|
||||
version = "0.9"
|
||||
|
||||
def __init__(self, with_diff=0):
|
||||
self.with_diff = with_diff
|
||||
@ -590,7 +842,7 @@ class GitView:
|
||||
dialog = gtk.AboutDialog()
|
||||
dialog.set_name("Gitview")
|
||||
dialog.set_version(GitView.version)
|
||||
dialog.set_authors(["Aneesh Kumar K.V <aneesh.kumar@hp.com>"])
|
||||
dialog.set_authors(["Aneesh Kumar K.V <aneesh.kumar@gmail.com>"])
|
||||
dialog.set_website("http://www.kernel.org/pub/software/scm/git/")
|
||||
dialog.set_copyright("Use and distribute under the terms of the GNU General Public License")
|
||||
dialog.set_wrap_license(True)
|
||||
|
Loading…
Reference in New Issue
Block a user