From c6533c216aa402ad33ca0c9a8534836f8f13034d Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Mon, 1 Oct 2018 15:21:59 -0400 Subject: [PATCH] Implement commit view --- gitsrht/blueprints/repo.py | 90 ++++++++++++++++------------- gitsrht/git.py | 51 +++++++++++++++++ gitsrht/templates/commit.html | 100 +++++++++++++++++++++++++++++++++ gitsrht/templates/log.html | 4 +- gitsrht/templates/summary.html | 4 +- gitsrht/templates/utils.html | 90 +++++++++++++++++------------ scss/main.scss | 21 ++++++- 7 files changed, 284 insertions(+), 76 deletions(-) create mode 100644 gitsrht/templates/commit.html diff --git a/gitsrht/blueprints/repo.py b/gitsrht/blueprints/repo.py index 0ae2185..511272d 100644 --- a/gitsrht/blueprints/repo.py +++ b/gitsrht/blueprints/repo.py @@ -9,7 +9,7 @@ from flask_login import current_user from gitsrht.access import get_repo, has_access, UserAccess from gitsrht.editorconfig import EditorConfig from gitsrht.redis import redis -from gitsrht.git import CachedRepository, commit_time, annotate_tree +from gitsrht.git import CachedRepository, commit_time, annotate_tree, diffstat from gitsrht.types import User, Repository from io import BytesIO from pygments import highlight @@ -89,29 +89,6 @@ def summary(owner, repo): clone_urls=clone_urls, latest_tag=latest_tag, default_branch=default_branch) -def resolve_ref(git_repo, ref): - commit = None - if ref is None: - branch = git_repo.default_branch() - ref = branch.name[len("refs/heads/"):] - commit = git_repo.get(branch.target) - else: - if f"refs/heads/{ref}" in git_repo.references: - branch = git_repo.references[f"refs/heads/{ref}"] - commit = git_repo.get(branch.target) - elif f"refs/tags/{ref}" in git_repo.references: - _ref = git_repo.references[f"refs/tags/{ref}"] - tag = git_repo.get(_ref.target) - commit = git_repo.get(tag.target) - else: - try: - ref = git_repo.get(ref) - except: - abort(404) - if not commit: - abort(404) - return ref, commit - @repo.route("///tree", defaults={"ref": None, "path": ""}) @repo.route("///tree/", defaults={"path": ""}) @repo.route("///tree//") @@ -122,7 +99,10 @@ def tree(owner, repo, ref, path): if not has_access(repo, UserAccess.read): abort(401) git_repo = CachedRepository(repo.path) - ref, commit = resolve_ref(git_repo, ref) + ref = ref or git_repo.default_branch().name[len("refs/heads/"):] + commit = git_repo.revparse_single(ref) + if commit is pygit2.Tag: + commit = git_repo.get(commit.target) tree = commit.tree editorconfig = EditorConfig(git_repo, tree, path) @@ -165,7 +145,10 @@ def raw_blob(owner, repo, ref, path): if not has_access(repo, UserAccess.read): abort(401) git_repo = CachedRepository(repo.path) - ref, commit = resolve_ref(git_repo, ref) + ref = ref or git_repo.default_branch().name[len("refs/heads/"):] + commit = git_repo.revparse_single(ref) + if commit is pygit2.Tag: + commit = git_repo.get(commit.target) blob = None entry = None @@ -198,7 +181,10 @@ def archive(owner, repo, ref): if not has_access(repo, UserAccess.read): abort(401) git_repo = CachedRepository(repo.path) - ref, commit = resolve_ref(git_repo, ref) + ref = ref or git_repo.default_branch().name[len("refs/heads/"):] + commit = git_repo.revparse_single(ref) + if commit is pygit2.Tag: + commit = git_repo.get(commit.target) path = f"/tmp/{commit.id.hex}.tar.gz" try: @@ -253,6 +239,17 @@ class _AnnotatedRef: else: self.type = None +def collect_refs(git_repo): + refs = {} + for _ref in git_repo.references: + _ref = _AnnotatedRef(git_repo, git_repo.references[_ref]) + if not _ref.type: + continue + if _ref.commit.id.hex not in refs: + refs[_ref.commit.id.hex] = [] + refs[_ref.commit.id.hex].append(_ref) + return refs + @repo.route("///log", defaults={"ref": None, "path": ""}) @repo.route("///log/", defaults={"path": ""}) @repo.route("///log//") @@ -263,17 +260,11 @@ def log(owner, repo, ref, path): if not has_access(repo, UserAccess.read): abort(401) git_repo = CachedRepository(repo.path) - ref, commit = resolve_ref(git_repo, ref) - - refs = {} - for _ref in git_repo.references: - _ref = _AnnotatedRef(git_repo, git_repo.references[_ref]) - if not _ref.type: - continue - print(_ref.commit.id.hex, _ref.name) - if _ref.commit.id.hex not in refs: - refs[_ref.commit.id.hex] = [] - refs[_ref.commit.id.hex].append(_ref) + ref = ref or git_repo.default_branch().name[len("refs/heads/"):] + commit = git_repo.revparse_single(ref) + if commit is pygit2.Tag: + commit = git_repo.get(commit.target) + refs = collect_refs(git_repo) from_id = request.args.get("from") if from_id: @@ -289,3 +280,26 @@ def log(owner, repo, ref, path): return render_template("log.html", view="log", owner=owner, repo=repo, ref=ref, path=path, commits=commits, refs=refs) + +@repo.route("///commit/") +def commit(owner, repo, ref): + owner, repo = get_repo(owner, repo) + if not repo: + abort(404) + if not has_access(repo, UserAccess.read): + abort(401) + git_repo = CachedRepository(repo.path) + commit = git_repo.revparse_single(ref) + if commit is pygit2.Tag: + ref = git_repo.get(commit.target) + try: + parent = git_repo.revparse_single(ref + "^") + diff = git_repo.diff(parent, ref) + except KeyError: + diff = ref.tree.diff_to_tree() + diff.find_similar(pygit2.GIT_DIFF_FIND_RENAMES) + refs = collect_refs(git_repo) + return render_template("commit.html", view="log", + owner=owner, repo=repo, ref=ref, refs=refs, + commit=commit, parent=parent, + diff=diff, diffstat=diffstat, pygit2=pygit2) diff --git a/gitsrht/git.py b/gitsrht/git.py index ac00fef..257e954 100644 --- a/gitsrht/git.py +++ b/gitsrht/git.py @@ -3,6 +3,8 @@ from datetime import datetime, timedelta, timezone from functools import lru_cache from gitsrht.redis import redis from pygit2 import Repository, Tag +from jinja2 import Markup, escape +from stat import filemode import pygit2 import json @@ -131,3 +133,52 @@ def annotate_tree(repo, tree, commit): redis.setex(key, cache, timedelta(days=30)) return [entry.fetch_blob() for entry in tree.values()] + +def _diffstat_name(delta): + if delta.status == pygit2.GIT_DELTA_DELETED: + return Markup(escape(delta.old_file.path)) + if delta.old_file.path == delta.new_file.path: + return Markup( + f"" + + f"{escape(delta.old_file.path)}" + + f"") + # Based on git/diff.c + pfx_length = 0 + old_path = delta.old_file.path + new_path = delta.new_file.path + for i in range(max(len(old_path), len(new_path))): + if i >= len(old_path) or i >= len(new_path): + break + if old_path[i] == '/': + pfx_length = i + 1 + # TODO: detect common suffix + if pfx_length != 0: + return (f"{delta.old_file.path[:pfx_length]}{{" + + f"{delta.old_file.path[pfx_length:]} => {delta.new_file.path[pfx_length:]}" + + f"}}") + return f"{delta.old_file.path} => {delta.new_file.path}" + +def _diffstat_line(delta, patch): + name = _diffstat_name(delta) + change = "" + if delta.status not in [ + pygit2.GIT_DELTA_ADDED, + pygit2.GIT_DELTA_DELETED, + ]: + if delta.old_file.mode != delta.new_file.mode: + change = Markup( + f" " + + f"{filemode(delta.old_file.mode)} => " + + f"" + + f"{filemode(delta.new_file.mode)}") + return Markup(f"{delta.status_char()} {name}{change}\n") + +def diffstat(diff): + stat = Markup(f"""{diff.stats.files_changed} files changed, {diff.stats.insertions + } insertions(+), {diff.stats.deletions + } deletions(-)\n\n""") + for delta, patch in zip(diff.deltas, diff): + stat += _diffstat_line(delta, patch) + return stat diff --git a/gitsrht/templates/commit.html b/gitsrht/templates/commit.html new file mode 100644 index 0000000..88b9f37 --- /dev/null +++ b/gitsrht/templates/commit.html @@ -0,0 +1,100 @@ +{% extends "repo.html" %} +{% import "utils.html" as utils %} +{% block content %} +
+
+
+
+
+ {{ utils.commit_event(repo, commit, commit_time, trim_commit, + full_body=True, full_id=True, refs=refs, parents=True, + any=any) }} +
+
+
+ +
+
+
+
+
+
{{diffstat(diff)}}
+
+ {# God, working with
 tags is such a fucking mess #}
+        {% for patch in diff %}
+        
{#
+          #}{{patch.delta.status_char()}} {% if parent %}{{patch.delta.old_file.path}}{#
+         #}{% endif %} => {#
+         #}{{patch.delta.new_file.path}}{#
+         #} +{{patch.line_stats[1]}}{#
+         #} -{{patch.line_stats[2]}}{%
+            if patch.delta.old_file.mode != patch.delta.new_file.mode %}{#
+          #}{#
+          #}{% endif %}
+
+
{% for hunk in patch.hunks %}
+{% set hunk_index = loop.index %}@@ {#
+#}{% if parent %}{{hunk.old_start}},{{hunk.old_lines}} {#
+#}{% endif %}{{hunk.new_start}},{{hunk.new_lines}} {#
+#}@@{% if hunk.old_start == 0 %}
+{% endif %}{% for line in hunk.lines
+%}{{line.origin}}{%
+  if loop.first and hunk.old_start != 0
+%}{{line.content.lstrip()}}{%
+  else
+%} {{line.content}}{%
+  endif
+%}{% endfor %}
+{% endfor %}
+
+ {% endfor %} +
+
+
+{% endblock %} diff --git a/gitsrht/templates/log.html b/gitsrht/templates/log.html index 07d8b7a..ae99229 100644 --- a/gitsrht/templates/log.html +++ b/gitsrht/templates/log.html @@ -6,7 +6,9 @@
{% for c in commits[:-1] %} - {{ utils.commit_event(repo, c, commit_time, None, True, refs) }} +
+ {{ utils.commit_event(repo, c, commit_time, None, True, refs) }} +
{% endfor %}
{% for c in commits %} - {{ utils.commit_event(repo, c, commit_time, trim_commit) }} +
+ {{ utils.commit_event(repo, c, commit_time, trim_commit) }} +
{% endfor %}
diff --git a/gitsrht/templates/utils.html b/gitsrht/templates/utils.html index 629d0e2..80cbd76 100644 --- a/gitsrht/templates/utils.html +++ b/gitsrht/templates/utils.html @@ -17,43 +17,63 @@ endif %}{% endfor %} {% endmacro %} -{% macro commit_event(repo, c, commit_time, trim_commit, full_body=False, refs={}) %} -
-
- {{c.id.hex[:8]}} — - {{c.author.name}} - {{ commit_time(c) | date }} - {% if c.id.hex in refs %} - {% for ref in refs[c.id.hex] %} - {{ref.name}} +{% macro commit_event( + repo, c, commit_time, trim_commit, + full_body=False, refs={}, full_id=False, parents=False, + any=None) %} +
+ {% if full_id %} + {{c.id.hex}} + {% else %} + {{c.id.hex[:8]}} + {% endif %} + — + {{c.author.name}} + {{ commit_time(c) | date }} + + {% if parents and any(c.parents) %} + + {{icon('code-branch', cls="sm")}} + {% for parent in c.parents %} + {{parent.short_id}} + {% if not loop.last %} + + + {% endif %} {% endfor %} + + {% endif %} + + {% if c.id.hex in refs %} + {% for ref in refs[c.id.hex] %} + - {% if full_body %} -
{{c.message}}
- {% else %} -
{{ trim_commit(c.message) }}
+ >{{ref.name}}
+ {% endfor %} {% endif %}
+{% if full_body %} +
{{c.message}}
+{% else %} +
{{ trim_commit(c.message) }}
+{% endif %} {% endmacro %} diff --git a/scss/main.scss b/scss/main.scss index 4a2a7cd..4eeb3da 100644 --- a/scss/main.scss +++ b/scss/main.scss @@ -16,6 +16,12 @@ background: #ddd; } +pre { + padding-left: 0; + padding-right: 0; + background: transparent; +} + .tree-list { display: grid; // mode name @@ -72,6 +78,9 @@ grid-row-start: 1; border-right: 1px solid #444; text-align: right; + padding-left: 0.5rem; + padding-right: 0.5rem; + background: #eee; } .highlight { @@ -110,7 +119,7 @@ .ref { border-width: 1px; border-style: solid; - padding: 0.2rem; + padding: 0.1rem 0.2rem; &.branch { border-color: darken($info, 20); @@ -130,3 +139,13 @@ color: $white; } } + +.diff { + .text-danger { + color: darken($danger, 10) !important; + } + + .text-success { + color: darken($success, 10) !important; + } +} -- 2.38.4