From 342ae75e7852514dd143ed692259933c1d696a42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Mon, 17 Aug 2020 01:47:01 +0200 Subject: [PATCH] Add blame view Ref: ~sircmpwn/git.sr.ht#157 --- gitsrht/blueprints/repo.py | 92 +++++++++++++++++++-------- gitsrht/templates/blame.html | 117 +++++++++++++++++++++++++++++++++++ gitsrht/templates/blob.html | 8 +++ scss/main.scss | 34 ++++++++-- 4 files changed, 221 insertions(+), 30 deletions(-) create mode 100755 gitsrht/templates/blame.html diff --git a/gitsrht/blueprints/repo.py b/gitsrht/blueprints/repo.py index 0b4b40a..c5ed7bf 100644 --- a/gitsrht/blueprints/repo.py +++ b/gitsrht/blueprints/repo.py @@ -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("///blob//") 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("///blame/", defaults={"path": ""}) +@repo.route("///blame//") +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("///archive/.tar.gz") def archive(owner, repo, ref): owner, repo = get_repo_or_redir(owner, repo) diff --git a/gitsrht/templates/blame.html b/gitsrht/templates/blame.html new file mode 100755 index 0000000..ac6e781 --- /dev/null +++ b/gitsrht/templates/blame.html @@ -0,0 +1,117 @@ +{% extends "repo-full.html" %} +{% import "utils.html" as utils %} +{% block title %} +{{repo.owner.canonical_name}}/{{repo.name}}: {{path_join(*path)}} blame - {{cfg("sr.ht", "site-name")}} git +{% endblock %} +{% block head %} + +{% endblock %} +{% block content %} +
+
+ + {{ utils.breadcrumb(ref, repo, path, path_join) }} + + + {{stat.filemode(entry.filemode)}} + + + + + {{humanize.naturalsize(blob.size, + binary=True).replace("Byte", "byte")}} + + + + + View raw + + + + + Unblame + + + +
+ {{commit.id.hex[:8]}} — + {% set author_user = lookup_user(commit.author.email) %} + {% if author_user %} + {{commit.author.name}} + {% else %} + {{commit.author.name}} + {% endif %} + {{trim_commit(commit.message)}} + + {{ commit_time(commit) | date }} + +
+
+
+
+
+ {# + This row has some weird styles going on. This prevents the page from + scrolling horizontally + #} +
+
+
{% for i in range(
+        editorconfig.max_line_length()) %} {% endfor %}
+ {% set lns = namespace(line_count=0) %} +
+        {%- for hunk in blame %}
+          {%- set lns.line_count = lns.line_count + hunk.lines_in_hunk -%}
+          {%- set final_user = lookup_user(hunk.final_committer.email) -%}
+          
{# + #}{{hunk.final_commit_id.hex[:8]}} {% if final_user -%} + {{hunk.final_committer.name}}{% else -%} + {{hunk.final_committer.name}}{% endif %} {{ "\n" * hunk.lines_in_hunk -}} +
+ {%- endfor -%} +
+
+        {%- for hunk in blame -%}
+          
{# + #}{{ commit_time(repo.git_repo[hunk.final_commit_id]) | date }}{{ "\n" * hunk.lines_in_hunk -}} +
+ {%- endfor -%} +
+
{% for l in range(lns.line_count) %}{{loop.index}}
+{% endfor %}
+ {{ highlight_file(repo, ref, entry.name, + blob.data, blob.id.hex, commit.id.hex) }} +
+
+
+{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/gitsrht/templates/blob.html b/gitsrht/templates/blob.html index 51519a3..8ede517 100644 --- a/gitsrht/templates/blob.html +++ b/gitsrht/templates/blob.html @@ -36,6 +36,14 @@ pre, body { View raw + {% if not blob.is_binary %} + + + Blame + + + {% endif %} {% if commit %}
diff --git a/scss/main.scss b/scss/main.scss index c26f475..b540d7c 100644 --- a/scss/main.scss +++ b/scss/main.scss @@ -81,12 +81,38 @@ dt { .code-view { display: grid; - grid-template-columns: auto 1fr; + grid-template-columns: auto auto auto 1fr; grid-template-rows: auto; - .lines { + .blame-user { grid-column-start: 1; grid-row-start: 1; + background: #ddd; + + .hunk { + padding-left: 0.5rem; + } + } + + .blame-time { + grid-column-start: 2; + grid-row-start: 1; + background: #ddd; + border-right: 1px solid #444; + text-align: right; + + .hunk { + padding-right: 0.5rem; + } + } + + .hunk:nth-child(2n) { + background: #eee; + } + + .lines { + grid-column-start: 3; + grid-row-start: 1; border-right: 1px solid #444; text-align: right; padding-left: 0.5rem; @@ -106,7 +132,7 @@ dt { } .highlight { - grid-column-start: 2; + grid-column-start: 4; grid-row-start: 1; padding-left: 1rem; background: transparent; @@ -119,7 +145,7 @@ dt { .ruler { background: transparent; - grid-column-start: 2; + grid-column-start: 4; grid-row-start: 1; display: block; padding-left: calc(1rem + 4px); -- 2.38.4