Luke Ross

Colorado

4 releases git clone https://lukeross.name/projects/colorado.git/

Web-based git repository viewer.

Commit ec8a0824a8e877e70c89092829d11b87c15f254a

Avoid explosion when trying to view initial commit to a repo.

Committed 11 Jul 2017 by Luke Ross

setup.py

@@ -4,7 +4,7 @@ from setuptools import setup
 
 setup(
 	name="Colorado",
-	version="0.1",
+	version="0.2",
 	description="Colorado git browser",
 	author="Luke Ross",
 	author_email="luke@lukeross.name",


src/colorado/repo.py

@@ -51,5 +51,7 @@ class Repo(Base):
 		try:
 			return getattr(repo.heads, self.master)
 		except AttributeError:
-			return sorted(repo.heads,
-				key=lambda b: b.commit.committed_date)[-1]
+			return sorted(
+				repo.heads,
+				key=lambda b: b.commit.committed_date
+			)[-1]


src/colorado/templates/repo-blob.xml

@@ -9,7 +9,7 @@
 </head>
 
 <body>
-<h1>LukeRoss</h1>
+<h1><a href="/">Luke Ross</a></h1>
 <h2><a href="#" meld:id="repo-name">Repository</a></h2>
 
 <div meld:id="toolbar">


src/colorado/templates/repo-history.xml

@@ -9,7 +9,7 @@
 </head>
 
 <body>
-<h1>LukeRoss</h1>
+<h1><a href="/">Luke Ross</a></h1>
 <h2><a href="#" meld:id="repo-name">Repository</a></h2>
 
 <div meld:id="toolbar">


src/colorado/templates/repo-home.xml

@@ -9,7 +9,7 @@
 </head>
 
 <body>
-<h1>LukeRoss</h1>
+<h1><a href="/">Luke Ross</a></h1>
 <h2><a href="#" meld:id="repo-name">Repository</a></h2>
 
 <div meld:id="toolbar">


src/colorado/templates/repo-revision.xml

@@ -9,7 +9,7 @@
 </head>
 
 <body>
-<h1>LukeRoss</h1>
+<h1><a href="/">Luke Ross</a></h1>
 <h2><a href="#" meld:id="repo-name">Repository</a></h2>
 
 <div meld:id="toolbar">


src/colorado/templates/repo-trees.xml

@@ -9,7 +9,7 @@
 </head>
 
 <body>
-<h1>LukeRoss</h1>
+<h1><a href="/">Luke Ross</a></h1>
 <h2><a href="#" meld:id="repo-name">Repository</a></h2>
 
 <div meld:id="toolbar">


src/colorado/views.py

@@ -1,6 +1,6 @@
 import arrow
 import chardet
-from flask import abort, Blueprint, g, send_file, url_for
+from flask import abort, Blueprint, make_response, send_file, url_for
 from io import BytesIO
 from itertools import chain
 from lxmlmeld import parse_xml
@@ -21,6 +21,8 @@ doctype = (
 	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
 )
 
+EMPTY_TREE_SHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
+
 
 def nice_author(c):
 	if c.author == c.committer:
@@ -68,15 +70,15 @@ def configure_template(tpl, repo=None):
 			n[0].content(str(len(git.heads))),
 			n.set("href", url_for(
 				".trees_view", slug=repo.slug
-			)
-		)))
+			))
+		))
 		if git.tags:
 			run_on_meld(tpl, "repo-tags", lambda n: (
 				n[0].content(str(len(git.tags))),
 				n.set("href", url_for(
 					".trees_view", slug=repo.slug
-				)
-			)))
+				))
+			))
 		else:
 			run_on_meld(tpl, "repo-tags", lambda n: n.deparent())
 
@@ -181,20 +183,44 @@ def repo_home(slug):
 	return top_level_view(slug, "branch", repo.get_master().name)
 
 
+def get_parent_diff(git, commit, **kwargs):
+	parent = commit.parents[0] \
+		if commit.parents \
+		else git.rev_parse(EMPTY_TREE_SHA)
+	return parent.diff(commit, **kwargs)
+
+
+def get_diff_by_id(slug, id):
+	repo, point, commit = look_up_thing(slug, "commit", id)
+	return repo, commit, get_parent_diff(repo.repo, commit, create_patch=True)
+
+
 @bp.route("/<slug>/commit/<id>/diff/raw", methods=["GET"])
 def raw_diff_view(slug, id):
-	repo, point, commit = look_up_thing(slug, "commit", id)
+	repo, _, diffs = get_diff_by_id(slug, id)
+
+	def make_diff(diff):
+		return b"\n".join((
+			"--- {}".format(
+				"a/" + diff.a_path if diff.a_path else "/dev/null"
+			).encode("utf-8"),
+			"+++ {}".format(
+				"b/" + diff.b_path if diff.b_path else "/dev/null"
+			).encode("utf-8"),
+			diff.diff
+		))
+
+	return make_response((
+		b"".join(make_diff(diff) for diff in diffs if diff.diff),
+		{"Content-Type": "text/plain"}
+	))
 
 
 @bp.route("/<slug>/commit/<id>/diff", methods=["GET"])
 def diff_view(slug, id):
-	repo, point, commit = look_up_thing(slug, "commit", id)
+	repo, commit, diffs = get_diff_by_id(slug, id)
 	tpl = parse_xml_for_template("repo-revision")
 	configure_template(tpl, repo)
-	if commit.parents:
-		diffs = commit.parents[0].diff(commit, create_patch=True)
-	else:
-		diffs = commit.diff(create_patch=True)
 	tpl.findmeld("rev-id").content(commit.hexsha)
 	tpl.findmeld("rev-id").set("href", url_for(
 		".raw_diff_view", slug=slug, id=id
@@ -217,7 +243,7 @@ def diff_view(slug, id):
 			ele.findmeld("file-from").content(diff.a_path)
 			ele.findmeld("file-to").content("(removed)")
 			ele.findmeld("file-to").tag = "em"
-			ele.findmeld("file-to").attributes.pop("href")
+			ele.findmeld("file-to").attrib.pop("href")
 		elif diff.a_path and diff.a_path != diff.b_path:
 			ele.findmeld("file-from").content(diff.a_path)
 			ele.findmeld("file-to").content(diff.b_path)
@@ -231,9 +257,10 @@ def diff_view(slug, id):
 			))
 
 		if not message:
-			for holder, line in ele.findmeld("file-diff-line").repeat(diff.diff.split(b"\n")):
+			parts = diff.diff.split(b"\n")
+			for holder, line in ele.findmeld("file-diff-line").repeat(parts):
 				try:
-		 			holder.content(line + b"\n")
+					holder.content(line + b"\n")
 				except ValueError:
 					message = "This diff cannot be displayed"
 					break
@@ -258,7 +285,8 @@ def history_view(slug, type, id):
 
 	tpl = parse_xml_for_template("repo-history")
 	configure_template(tpl, repo)
-	for ele, c in tpl.findmeld("rev").repeat(chain([commit], commit.iter_parents())):
+	all_commits = chain([commit], commit.iter_parents())
+	for ele, c in tpl.findmeld("rev").repeat(all_commits):
 		ele.set("id", "rev-" + c.hexsha)
 		ele.findmeld("rev-desc").content(c.message)
 		ele.findmeld("rev-desc").set("href", url_for(
@@ -281,8 +309,8 @@ def history_view(slug, type, id):
 	tpl.findmeld("browse").set("href", url_for(
 		".top_level_view", slug=slug, type=type, id=id
 	))
-	tpl.findmeld("browse")[0].content(str(sum(1 for _ in
-		chain(commit.tree.trees, commit.tree.blobs)
+	tpl.findmeld("browse")[0].content(str(sum(
+		1 for _ in chain(commit.tree.trees, commit.tree.blobs)
 	)))
 
 	return write_template(tpl)
@@ -291,8 +319,8 @@ def history_view(slug, type, id):
 def configure_breadcrumbs(tpl, slug, type, id, commit, path_parts):
 	curr = commit.tree
 	items = chain(
-			[(id, False)],
-			((p, True) for p in path_parts if p)
+		[(id, False)],
+		((p, True) for p in path_parts if p)
 	)
 	for ele, (text, is_part) in tpl.findmeld("tree-part").repeat(items):
 		ele = ele.findmeld("tree-part-name")
@@ -319,9 +347,9 @@ def render_blob(ele, blob, dl_link):
 	if not cannot_render:
 		data = blob.data_stream.read()
 		encoding = chardet.detect(data)
-		if 'encoding' in encoding and encoding.get('confidence', 0) > 0.7:
+		if "encoding" in encoding and encoding.get("confidence", 0) > 0.7:
 			try:
-				data = data.decode(encoding['encoding'])
+				data = data.decode(encoding["encoding"])
 			except UnicodeDecodeError as e:
 				cannot_render = "Failed to convert to text"
 		else:
@@ -353,7 +381,12 @@ def raw_view(slug, type, id, path):
 		abort(404)
 
 	# blob goes out of scope too quickly!
-	return send_file(BytesIO(blob.data_stream.read()), blob.mime_type, True, blob.name)
+	return send_file(
+		BytesIO(blob.data_stream.read()),
+		blob.mime_type,
+		True,
+		blob.name
+	)
 
 
 @bp.route("/<slug>/<type>/<id>/tree/<path:path>", methods=["GET"])
@@ -380,33 +413,23 @@ def tree_view(slug, type, id, path):
 def top_level_view(slug, type, id):
 	repo, point, commit = look_up_thing(slug, type, id)
 	show_download = type in ("branch", "tag")
-	return tree_base_view(slug, type, id, repo, commit, commit.tree, show_download)
+	return tree_base_view(
+		slug, type, id, repo, commit, commit.tree, show_download)
 
 
 def tree_base_view(slug, type, id, repo, commit, tree, show_download=False):
-	seen = {}
 	by_file = {}
 	commit_count = 0
-	pending = [commit]
-	while pending:
-		current = pending.pop(0)
-		if not current:
-			continue
-		elif current.hexsha in seen:
-			continue
-		else:
-			seen[current.hexsha] = True
+	for current in chain([commit], commit.iter_parents()):
 		commit_count += 1
-		for diff in current.diff(current.parents[0]) if current.parents else current.diff():
-			if not diff.b_path:
+		for diff in get_parent_diff(repo.repo, current):
+			if diff.change_type not in "AM":
 				continue
 			if diff.b_path in by_file:
 				if by_file[diff.b_path].committed_date < current.committed_date:
 					by_file[diff.b_path] = current
 			else:
 				by_file[diff.b_path] = current
-		pending.extend(current.parents)
-	seen = None  # save mem
 
 	tpl = parse_xml_for_template("repo-home")
 	configure_template(tpl, repo)
@@ -447,7 +470,8 @@ def tree_base_view(slug, type, id, repo, commit, tree, show_download=False):
 					arrow.get(file_commit.committed_datetime).humanize()
 				)
 				ele.findmeld("file-when").set("href", url_for(
-					".history_view", slug=slug, type=type, id=id, _anchor="rev-" + file_commit.hexsha
+					".history_view", slug=slug, type=type,
+					id=id, _anchor="rev-" + file_commit.hexsha
 				))
 				ele.findmeld("file-when").set(
 					"title", file_commit.committed_datetime.isoformat(" ")
@@ -502,7 +526,7 @@ def download_view(slug, type, id):
 
 	def make_ti(thing, type, mode):
 		ti = TarInfo(thing.path)
-		ti.size = getattr(thing, 'size', 0)
+		ti.size = getattr(thing, "size", 0)
 		ti.mode = mode
 		ti.type = type
 		ti.mtime = commit.committed_date