diff options
author | Drew DeVault <sir@cmpwn.com> | 2018-10-01 15:21:59 -0400 |
---|---|---|
committer | Drew DeVault <sir@cmpwn.com> | 2018-10-01 15:21:59 -0400 |
commit | c6533c216aa402ad33ca0c9a8534836f8f13034d (patch) | |
tree | 6070e3094712f59acdc5b60cd02adf6e223b8b86 | |
parent | d40fe4e8f008d10a445c49a5fca3e184469163c8 (diff) |
Implement commit view
-rw-r--r-- | gitsrht/blueprints/repo.py | 90 | ||||
-rw-r--r-- | gitsrht/git.py | 51 | ||||
-rw-r--r-- | gitsrht/templates/commit.html | 100 | ||||
-rw-r--r-- | gitsrht/templates/log.html | 4 | ||||
-rw-r--r-- | gitsrht/templates/summary.html | 4 | ||||
-rw-r--r-- | gitsrht/templates/utils.html | 90 | ||||
-rw-r--r-- | scss/main.scss | 21 |
7 files changed, 284 insertions, 76 deletions
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("/<owner>/<repo>/tree", defaults={"ref": None, "path": ""}) @repo.route("/<owner>/<repo>/tree/<ref>", defaults={"path": ""}) @repo.route("/<owner>/<repo>/tree/<ref>/<path:path>") @@ -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("/<owner>/<repo>/log", defaults={"ref": None, "path": ""}) @repo.route("/<owner>/<repo>/log/<ref>", defaults={"path": ""}) @repo.route("/<owner>/<repo>/log/<ref>/<path:path>") @@ -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("/<owner>/<repo>/commit/<ref>") +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"<a href='#{escape(delta.old_file.path)}'>" + + f"{escape(delta.old_file.path)}" + + f"</a>") + # 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" <span title='{delta.old_file.mode}'>" + + f"{filemode(delta.old_file.mode)}</span> => " + + f"<span title='{delta.new_file.mode}'>" + + f"{filemode(delta.new_file.mode)}</span>") + return Markup(f"{delta.status_char()} {name}{change}\n") + +def diffstat(diff): + stat = Markup(f"""{diff.stats.files_changed} files changed, <strong + class="text-success">{diff.stats.insertions + }</strong> insertions(+), <strong + class="text-danger">{diff.stats.deletions + }</strong> 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 %} +<div class="container"> + <div class="row"> + <div class="col-md-10"> + <div class="event-list"> + <div class="event"> + {{ utils.commit_event(repo, commit, commit_time, trim_commit, + full_body=True, full_id=True, refs=refs, parents=True, + any=any) }} + </div> + </div> + </div> + <div class="col-md-2"> + <a href="#" class="btn btn-primary btn-block"> + browse {{icon("caret-right")}} + </a> + <a href="#" class="btn btn-default btn-block"> + patch {{icon("caret-right")}} + </a> + </div> + </div> + <div class="row"> + <div class="col-md-12"> + <div class="event-list"> + <div class="event"> + <pre>{{diffstat(diff)}}</pre> + </div> + {# God, working with <pre> tags is such a fucking mess #} + {% for patch in diff %} + <pre style="margin-bottom: 0;" + >{# + #}{{patch.delta.status_char()}} {% if parent %}<a + href="{{url_for("repo.tree", + owner=repo.owner.canonical_name, + repo=repo.name, + ref=parent.id.hex, + path=patch.delta.old_file.path)}}" + id="{{patch.delta.old_file.path}}" + >{{patch.delta.old_file.path}}</a>{# + #}{% endif %} => {# + #}<a + href="{{url_for("repo.tree", + owner=repo.owner.canonical_name, + repo=repo.name, + ref=commit.id.hex, + path=patch.delta.new_file.path)}}" + id="{{patch.delta.new_file.path}}" + >{{patch.delta.new_file.path}}</a>{# + #} <span class="pull-right"><span class="text-success">+{{patch.line_stats[1]}}</span>{# + #} <span class="text-danger">-{{patch.line_stats[2]}}</span></span>{% + if patch.delta.old_file.mode != patch.delta.new_file.mode %}{# + #}{# + #}{% endif %}</pre> + <div class="event diff"> + <pre>{% for hunk in patch.hunks %} +{% set hunk_index = loop.index %}<strong + class="text-info" +>@@ {# +#}{% if parent %}<a + style="text-decoration: underline" + href="{{url_for("repo.tree", + owner=repo.owner.canonical_name, + repo=repo.name, + ref=parent.id.hex, + path=patch.delta.old_file.path)}}#L{{hunk.old_start}}" +>{{hunk.old_start}}</a>,{{hunk.old_lines}} {# +#}{% endif %}<a + style="text-decoration: underline" + href="{{url_for("repo.tree", + owner=repo.owner.canonical_name, + repo=repo.name, + ref=commit.id.hex, + path=patch.delta.new_file.path)}}#L{{hunk.new_start}}" +>{{hunk.new_start}}</a>,{{hunk.new_lines}} {# +#}@@</strong +>{% if hunk.old_start == 0 %} +{% endif %}{% for line in hunk.lines +%}<span class="{{({ + "+":"text-success", + "-":"text-danger", + }).get(line.origin) or ""}}"><a + href="#{{patch.delta.old_file.path}}-{{hunk_index}}-{{loop.index}}" + id="{{patch.delta.old_file.path}}-{{hunk_index}}-{{loop.index}}" + style="color: inherit" +>{{line.origin}}</a>{% + if loop.first and hunk.old_start != 0 +%}{{line.content.lstrip()}}{% + else +%} {{line.content}}{% + endif +%}</span>{% endfor %} +{% endfor %}</pre> + </div> + {% endfor %} + </div> + </div> +</div> +{% 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 @@ <div class="col-md-12"> <div class="event-list"> {% for c in commits[:-1] %} - {{ utils.commit_event(repo, c, commit_time, None, True, refs) }} + <div class="event"> + {{ utils.commit_event(repo, c, commit_time, None, True, refs) }} + </div> {% endfor %} </div> <a diff --git a/gitsrht/templates/summary.html b/gitsrht/templates/summary.html index 89dda4b..19a565d 100644 --- a/gitsrht/templates/summary.html +++ b/gitsrht/templates/summary.html @@ -13,7 +13,9 @@ <div class="col-md-6"> <div class="event-list" style="margin-bottom: 0.5rem"> {% for c in commits %} - {{ utils.commit_event(repo, c, commit_time, trim_commit) }} + <div class="event"> + {{ utils.commit_event(repo, c, commit_time, trim_commit) }} + </div> {% endfor %} </div> </div> 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={}) %} -<div class="event"> - <div> - <a - href="#" - title="{{c.id.hex}}" - >{{c.id.hex[:8]}}</a> — - <a href="#">{{c.author.name}}</a> - <a - id="log-{{c.id}}" - href="#log-{{c.id}}" - class="text-muted pull-right" - >{{ commit_time(c) | date }}</a> - {% if c.id.hex in refs %} - {% for ref in refs[c.id.hex] %} - <a - class="ref {{ref.type}} - {{"annotated" if ref.type == "tag" and ref.tag.message else ""}}" - {% if ref.type == "branch" %} - href="{{url_for("repo.tree", - owner=repo.owner.canonical_name, repo=repo.name, ref=ref.name)}}" - {% else %} - {# TODO: Annotated tag page #} - href="#" - {% endif %} - >{{ref.name}}</a> +{% macro commit_event( + repo, c, commit_time, trim_commit, + full_body=False, refs={}, full_id=False, parents=False, + any=None) %} +<div> + {% if full_id %} + {{c.id.hex}} + {% else %} + <a + href="{{url_for("repo.commit", owner=repo.owner.canonical_name, + repo=repo.name, ref=c.id.hex)}}" + title="{{c.id.hex}}" + >{{c.id.hex[:8]}}</a> + {% endif %} + — + <a href="#">{{c.author.name}}</a> + <a + id="log-{{c.id}}" + href="#log-{{c.id}}" + class="text-muted pull-right" + >{{ commit_time(c) | date }}</a> + + {% if parents and any(c.parents) %} + <span style="margin-left: 0.5rem"> + {{icon('code-branch', cls="sm")}} + {% for parent in c.parents %} + <a href="{{url_for("repo.commit", + owner=repo.owner.canonical_name, + repo=repo.name, + ref=parent.id.hex)}}" + >{{parent.short_id}}</a> + {% if not loop.last %} + + + {% endif %} {% endfor %} + </span> + {% endif %} + + {% if c.id.hex in refs %} + {% for ref in refs[c.id.hex] %} + <a + class="ref {{ref.type}} + {{"annotated" if ref.type == "tag" and ref.tag.message else ""}}" + {% if ref.type == "branch" %} + href="{{url_for("repo.tree", + owner=repo.owner.canonical_name, repo=repo.name, ref=ref.name)}}" + {% else %} + {# TODO: Annotated tag page #} + href="#" {% endif %} - </div> - {% if full_body %} - <pre - style="padding-left: 0; padding-right: 0; background: transparent" - >{{c.message}}</pre> - {% else %} - <pre - style="padding-left: 0; padding-right: 0; background: transparent" - >{{ trim_commit(c.message) }}</pre> + >{{ref.name}}</a> + {% endfor %} {% endif %} </div> +{% if full_body %} +<pre>{{c.message}}</pre> +{% else %} +<pre>{{ trim_commit(c.message) }}</pre> +{% 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; + } +} |