@@ 6,7 6,7 @@ import pygments
import subprocess
import sys
from datetime import timedelta
-from flask import Blueprint, render_template, abort, send_file, request
+from flask import Blueprint, render_template, abort, current_app, send_file, request
from flask import Response, url_for, session, redirect
from gitsrht.editorconfig import EditorConfig
from gitsrht.git import Repository as GitRepository, commit_time, annotate_tree
@@ 245,41 245,81 @@ def tree(owner, repo, ref, path):
return render_template("tree.html", view="tree", owner=owner, repo=repo,
ref=ref, commit=commit, tree=tree, path=path)
+def resolve_blob(git_repo, ref, path):
+ commit, ref, path = lookup_ref(git_repo, ref, path)
+ if not isinstance(commit, pygit2.Commit):
+ abort(404)
+
+ blob = None
+ entry = None
+ tree = commit.tree
+ orig_commit = commit
+ path = path.split("/")
+ for part in path:
+ if part == "":
+ continue
+ if part not in tree:
+ abort(404)
+ entry = tree[part]
+ etype = (entry.type_str
+ if hasattr(entry, "type_str") else entry.type)
+ if etype == "blob":
+ tree = annotate_tree(git_repo, tree, commit)
+ commit = next(e.commit for e in tree if e.name == entry.name)
+ blob = git_repo.get(entry.id)
+ break
+ tree = git_repo.get(entry.id)
+
+ if not blob:
+ abort(404)
+
+ return orig_commit, ref, path, blob, entry
+
@repo.route("/<owner>/<repo>/blob/<path:ref>/<path:path>")
def raw_blob(owner, repo, ref, path):
owner, repo = get_repo_or_redir(owner, repo)
with GitRepository(repo.path) as git_repo:
- commit, ref, path = lookup_ref(git_repo, ref, path)
- if not isinstance(commit, pygit2.Commit):
- abort(404)
-
- blob = None
- entry = None
- tree = commit.tree
- path = path.split("/")
- for part in path:
- if part == "":
- continue
- if part not in tree:
- abort(404)
- entry = tree[part]
- etype = (entry.type_str
- if hasattr(entry, "type_str") else entry.type)
- if etype == "blob":
- tree = annotate_tree(git_repo, tree, commit)
- commit = next(e.commit for e in tree if e.name == entry.name)
- blob = git_repo.get(entry.id)
- break
- tree = git_repo.get(entry.id)
-
- if not blob:
- abort(404)
+ orig_commit, ref, path, blob, entry = resolve_blob(git_repo, ref, path)
return send_file(BytesIO(blob.data),
as_attachment=blob.is_binary,
attachment_filename=entry.name,
mimetype="text/plain" if not blob.is_binary else None)
+def _lookup_user(email, cache):
+ if email not in cache:
+ cache[email] = current_app.lookup_user(email)
+ return cache[email]
+
+def lookup_user():
+ cache = {}
+ return lambda email: _lookup_user(email, cache)
+
+@repo.route("/<owner>/<repo>/blame/<path:ref>", defaults={"path": ""})
+@repo.route("/<owner>/<repo>/blame/<ref>/<path:path>")
+def blame(owner, repo, ref, path):
+ owner, repo = get_repo_or_redir(owner, repo)
+ with GitRepository(repo.path) as git_repo:
+ orig_commit, ref, path, blob, entry = resolve_blob(git_repo, ref, path)
+ if blob.is_binary:
+ return redirect(url_for("repo.log",
+ owner=repo.owner.canonical_name, repo=repo.name, ref=ref,
+ path="/".join(path)))
+
+ try:
+ blame = git_repo.blame("/".join(path), newest_commit=orig_commit.oid)
+ except KeyError as ke: # Path not in the tree
+ abort(404)
+ except ValueError:
+ # ValueError: object at path 'hubsrht/' is not of the asked-for type 3
+ abort(400)
+
+ return render_template("blame.html", view="blame", owner=owner,
+ repo=repo, ref=ref, path=path, entry=entry, blob=blob,
+ blame=blame, commit=orig_commit, highlight_file=_highlight_file,
+ editorconfig=EditorConfig(git_repo, orig_commit.tree, path),
+ lookup_user=lookup_user())
+
@repo.route("/<owner>/<repo>/archive/<path:ref>.tar.gz")
def archive(owner, repo, ref):
owner, repo = get_repo_or_redir(owner, repo)
@@ 0,0 1,117 @@
+{% extends "repo-full.html" %}
+{% import "utils.html" as utils %}
+{% block title %}
+<title>{{repo.owner.canonical_name}}/{{repo.name}}: {{path_join(*path)}} blame - {{cfg("sr.ht", "site-name")}} git</title>
+{% endblock %}
+{% block head %}
+<style>
+pre {
+ tab-size: {{editorconfig.tab_width()}}
+}
+pre, body {
+ padding-bottom: 0;
+ margin-bottom: 0;
+}
+</style>
+{% endblock %}
+{% block content %}
+<div class="header-extension" style="margin-bottom: 0;">
+ <div class="blob container-fluid">
+ <span>
+ {{ utils.breadcrumb(ref, repo, path, path_join) }}
+ <span class="text-muted" style="margin-left: 1rem">
+ <span title="{{"{0:0o}".format(entry.filemode)}}">
+ {{stat.filemode(entry.filemode)}}
+ </span>
+ </span>
+ <span class="text-muted" style="margin-left: 1rem">
+ <span title="{{ blob.size }} bytes">
+ {{humanize.naturalsize(blob.size,
+ binary=True).replace("Byte", "byte")}}
+ </span>
+ </span>
+ <span class="text-muted" style="margin-left: 1rem">
+ <a href="{{url_for("repo.raw_blob", owner=repo.owner.canonical_name,
+ repo=repo.name, ref=ref, path=path_join(*path))}}">
+ View raw
+ </a>
+ </span>
+ <span class="text-muted" style="margin-left: 1rem">
+ <a href="{{url_for("repo.tree", owner=repo.owner.canonical_name,
+ repo=repo.name, ref=ref, path=path_join(*path))}}">
+ Unblame
+ </a>
+ </span>
+ </span>
+ <div class="commit">
+ <a
+ href="{{url_for("repo.commit",
+ owner=repo.owner.canonical_name,
+ repo=repo.name,
+ ref=ref)}}"
+ >{{commit.id.hex[:8]}}</a> —
+ {% set author_user = lookup_user(commit.author.email) %}
+ {% if author_user %}
+ <a href="{{url_for("public.user_index",
+ username=author_user.username)}}">{{commit.author.name}}</a>
+ {% else %}
+ {{commit.author.name}}
+ {% endif %}
+ {{trim_commit(commit.message)}}
+ <span class="text-muted">
+ {{ commit_time(commit) | date }}
+ </span>
+ </div>
+ <div class="clearfix"></div>
+ </div>
+</div>
+<div class="container-fluid" style="padding-left: 0; padding-right: 0;">
+ {#
+ This row has some weird styles going on. This prevents the page from
+ scrolling horizontally
+ #}
+ <div class="row" style="margin-right: 0;">
+ <div class="col-md-12 code-view">
+ <pre class="ruler"><span>{% for i in range(
+ editorconfig.max_line_length()) %} {% endfor %}</span></pre>
+ {% set lns = namespace(line_count=0) %}
+ <pre class="blame-user">
+ {%- for hunk in blame %}
+ {%- set lns.line_count = lns.line_count + hunk.lines_in_hunk -%}
+ {%- set final_user = lookup_user(hunk.final_committer.email) -%}
+ <div class="hunk">{#
+ #}<a href="{{url_for("repo.commit",
+ owner=repo.owner.canonical_name, repo=repo.name,
+ ref=hunk.final_commit_id.hex)}}"
+ >{{hunk.final_commit_id.hex[:8]}}</a> {% if final_user -%}
+ <a href="{{url_for("public.user_index", username=final_user.username)}}">{{hunk.final_committer.name}}</a>{% else -%}
+ {{hunk.final_committer.name}}{% endif %} {{ "\n" * hunk.lines_in_hunk -}}
+ </div>
+ {%- endfor -%}
+ </pre>
+ <pre class="blame-time">
+ {%- for hunk in blame -%}
+ <div class="hunk">{#
+ #}<a
+ href="{{url_for("repo.blame", owner=repo.owner.canonical_name,
+ repo=repo.name, ref=hunk.final_commit_id,
+ path=path_join(*path))}}"
+ >{{ commit_time(repo.git_repo[hunk.final_commit_id]) | date }}</a>{{ "\n" * hunk.lines_in_hunk -}}
+ </div>
+ {%- endfor -%}
+ </pre>
+ <pre class="lines">{% for l in range(lns.line_count) %}<a
+ href="#L{{loop.index}}"
+ id="L{{loop.index}}"
+ >{{loop.index}}</a>
+{% endfor %}</pre>
+ {{ highlight_file(repo, ref, entry.name,
+ blob.data, blob.id.hex, commit.id.hex) }}
+ </div>
+ </div>
+</div>
+{% endblock %}
+
+{% block scripts %}
+<script src="/static/linelight.js"></script>
+{% endblock %}