From 2a5d69c5e72f996e5d0df6a6ca8e68a05342e4ea Mon Sep 17 00:00:00 2001 From: zPlus Date: Fri, 5 Aug 2022 21:48:53 +0200 Subject: [PATCH] Add page for showing commits. --- static/css/clif.css | 56 +++++++++- templates/repository/commit.html | 172 +++++++++++++++++++++++++++++++ templates/repository/log.html | 2 +- templates/repository/refs.html | 2 +- web.py | 60 +++++++---- 5 files changed, 269 insertions(+), 23 deletions(-) create mode 100644 templates/repository/commit.html diff --git a/static/css/clif.css b/static/css/clif.css index 3b51603..9380f26 100644 --- a/static/css/clif.css +++ b/static/css/clif.css @@ -320,8 +320,62 @@ div.threads { .thread .info .tag { margin-bottom: 1rem; } - + +/* Commit page */ +div.commit { +} + + div.commit .message { + margin-top: 2rem; + white-space: pre-wrap; + } + + div.commit .diff { + border: 1px solid #d4d4d4; + border-collapse: collapse; + border-spacing: 0; + font-family: monospace; + font-size: 1rem; + margin-top: 2rem; + width: 100%; + } + + div.commit .diff thead { + background-color: #f7f7f7; + border: 1px solid #d4d4d4; + } + + div.commit .diff td { + padding: .1rem .5rem; + } + + div.commit .diff .header { + background-color: #f0f9ff; + color: darkblue; + } + + div.commit .diff .header td { + padding: .5rem .5rem; + } + + div.commit .diff .lineno { + color: gray; + text-align: center; + width: 0; + } + div.commit .diff .origin { + width: 0; + } + + div.commit .diff .deletion { + background-color: #ffeef0; + } + + div.commit .diff .insertion { + background-color: #e6ffed; + } + /* Alternate background color used when displaying table data */ .striped > *:nth-child(even) { background-color: #f8f8f8; diff --git a/templates/repository/commit.html b/templates/repository/commit.html new file mode 100644 index 0000000..39f2ca7 --- /dev/null +++ b/templates/repository/commit.html @@ -0,0 +1,172 @@ +{% extends "repository/repository.html" %} + +{% block page_title %}Commit: {{ commit.id }}{% endblock %} + +{% block content %} + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Author + + {{ commit.author }} + {{ commit_time(commit.author.time, commit.author.offset) }} +
+ Committer + + {{ commit.committer }} + {{ commit_time(commit.committer.time, commit.committer.offset) }} +
+ Commit ID + + {{ commit.id }} +
+ Tree + + {{ commit.tree.id }} +
+ Parent(s) + + {% for parent in commit.parents %} + {{ parent.short_id }} + {% endfor %} +
+ ± + + {{ diff.stats.files_changed }} files changed, + {{ diff.stats.insertions }} insertions, + {{ diff.stats.deletions }} deletions +
+ +
{{ commit.message }}
+ + {% for patch in diff %} + + + + {# The following status values are defined in the git_delta_t enum + # in libgit2. See https://github.com/libgit2/libgit2/blob/main/include/git2/diff.h + #} + + {# 1=ADDED (does not exist in old version) #} + {# 2=DELETED (does not exist in new version) #} + {# 3=MODIFIED (content changed between old and new versions) #} + {# 4=RENAMED #} + {# 5=COPIED #} + + + + + + + + + + + + + {% if patch.delta.is_binary %} + + + + {% endif %} + + {% for hunk in patch.hunks if not patch.delta.is_binary %} + + + + + + + + + {% for line in hunk.lines %} + + + + + + + {% endfor %} + + {% endfor %} + + +
+ -{{ patch.line_stats[2] }}/+{{ patch.line_stats[1] }} + + {% if patch.delta.status == 1 %} + A {{ patch.delta.new_file.path }} + {% endif %} + + {% if patch.delta.status == 2 %} + D {{ patch.delta.old_file.path }} + {% endif %} + + {% if patch.delta.status == 3 %} + M {{ patch.delta.new_file.path }} + {% endif %} + + {% if patch.delta.status == 4 %} + R {{ patch.delta.old_file.path }} -> {{ patch.delta.new_file.path }} + {% endif %} + + {% if patch.delta.status == 5 %} + C {{ patch.delta.old_file.path }} {{ patch.delta.new_file.path }} + {% endif %} +
+ old size: {{ patch.delta.old_file.size|human_size(B=true) }} + - + new size: {{ patch.delta.new_file.size|human_size(B=true) }} +
+ {% if patch.delta.status == 1 %} + new file mode: {{ patch.delta.new_file.mode|filemode }} + {% elif patch.delta.status == 2 %} + deleted file mode: {{ patch.delta.old_file.mode|filemode }} + {% elif patch.delta.old_file.mode != patch.delta.new_file.mode %} + old mode: {{ patch.delta.old_file.mode|filemode }} +
+ new mode: {{ patch.delta.new_file.mode|filemode }} + {% endif %} +
+ Binary file +
{{ hunk.header }}
+ {% if line.old_lineno >= 0 %} + {{ line.old_lineno }} + {% endif %} + + {% if line.new_lineno >= 0 %} + {{ line.new_lineno }} + {% endif %} + {{ line.origin }}{{ line.content }}
+ + {% endfor %} +
+ +{% endblock %} diff --git a/templates/repository/log.html b/templates/repository/log.html index 2ddb8f8..2ec71a7 100644 --- a/templates/repository/log.html +++ b/templates/repository/log.html @@ -56,7 +56,7 @@ {% endif %} - {{ commit.short_id }} + {{ commit.short_id }} {% if commit.message|length <= 100 %} diff --git a/templates/repository/refs.html b/templates/repository/refs.html index e2b4594..ddec0d7 100644 --- a/templates/repository/refs.html +++ b/templates/repository/refs.html @@ -25,7 +25,7 @@ {% endif %} - [{{ head.commit.short_id }}] + {{ head.commit.short_id }} {{ head.commit.message[:80] }}... diff --git a/web.py b/web.py index 772b5eb..d007ed6 100644 --- a/web.py +++ b/web.py @@ -345,18 +345,12 @@ def tree(repository, revision, tree_path=None): if repo.is_empty: return template('repository/tree.html', - repository=repository, revision=revision) - - git_object = None + repository=repository, revision=revision, offset=0) try: git_object = repo.revparse_single(revision) except: - pass - - if not git_object: - return template('repository/tree.html', - repository=repository, revision=revision) + bottle.abort(404) # List all the references. # This is used for allowing the user to switch revision with a selector. @@ -443,6 +437,10 @@ def log(repository, revision): repository += '.git' repository_path = os.path.join(GITOLITE_REPOSITORIES_ROOT, repository) + # Read commits + try: commits_offset = int(request.query.get('offset', 0)) + except: commits_offset = 0 + if not os.path.isdir(repository_path): bottle.abort(404, 'No repository at this path.') @@ -450,18 +448,12 @@ def log(repository, revision): if repo.is_empty: return template('repository/log.html', - repository=repository, revision=revision) - - git_object = None + repository=repository, revision=revision, offset=commits_offset) try: git_object = repo.revparse_single(revision) except: - pass - - if not git_object: - return template('repository/log.html', - repository=repository, revision=revision) + bottle.abort(404) # List all the references. # This is used for allowing the user to switch revision with a selector. @@ -487,10 +479,6 @@ def log(repository, revision): # At this point git_object should be a valid pygit2.GIT_OBJ_COMMIT - # Read commits - try: commits_offset = int(request.query.get('offset', 0)) - except: commits_offset = 0 - commits = [] diff = {} commit_ith = 0 @@ -533,6 +521,38 @@ def log_change(repository): repository=repository, revision=revision)) +@bottle.get('/.git/commit/', name='commit') +def commit(repository, commit_id): + """ + Show a commit. + """ + + repository += '.git' + repository_path = os.path.join(GITOLITE_REPOSITORIES_ROOT, repository) + + if not os.path.isdir(repository_path): + bottle.abort(404, 'No repository at this path.') + + repo = pygit2.Repository(repository_path) + + try: + commit = repo.get(commit_id) + assert commit.type == pygit2.GIT_OBJ_COMMIT + except: + bottle.abort(404, 'Not a valid commit.') + + # Find diff wih parent + if len(commit.parents) > 0: + diff = commit.parents[0].tree.diff_to_tree(commit.tree) + else: + diff = commit.tree.diff_to_tree(swap=True) + + diff.find_similar() + + return template( + 'repository/commit.html', + repository=repository, commit=commit, diff=diff) + @bottle.get('/.git/raw//', name='raw') def raw(repository, revision, tree_path): """