summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDrew DeVault <sir@cmpwn.com>2018-10-01 15:21:59 -0400
committerDrew DeVault <sir@cmpwn.com>2018-10-01 15:21:59 -0400
commitc6533c216aa402ad33ca0c9a8534836f8f13034d (patch)
tree6070e3094712f59acdc5b60cd02adf6e223b8b86
parentd40fe4e8f008d10a445c49a5fca3e184469163c8 (diff)
Implement commit view
-rw-r--r--gitsrht/blueprints/repo.py90
-rw-r--r--gitsrht/git.py51
-rw-r--r--gitsrht/templates/commit.html100
-rw-r--r--gitsrht/templates/log.html4
-rw-r--r--gitsrht/templates/summary.html4
-rw-r--r--gitsrht/templates/utils.html90
-rw-r--r--scss/main.scss21
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:]} =&gt; {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 %} =&gt; {#
+ #}<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> &mdash;
- <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 %}
+ &mdash;
+ <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;
+ }
+}