{% extends "repository/repository.html" %}
{% block page_title %}Commit: {{ commit.id }}{% endblock %}
{% block content %}
<div class="commit">
<table>
<tbody>
<tr>
<td>
<b>Author</b>
</td>
<td>
{{ commit.author }}
{{ commit_time(commit.author.time, commit.author.offset) }}
</td>
</tr>
<tr>
<td>
<b>Committer</b>
</td>
<td>
{{ commit.committer }}
{{ commit_time(commit.committer.time, commit.committer.offset) }}
</td>
</tr>
<tr>
<td>
<b>Commit</b>
</td>
<td>
<a href="{{ url('commit', repository=repository[:-4], commit_id=commit.id) }}">{{ commit.short_id }}</a>
(<a href="{{ url('commit_patch', repository=repository[:-4], commit_id=commit.id, **request.query) }}">patch</a>)
</td>
</tr>
<tr>
<td>
<b>Tree</b>
</td>
<td>
<a href="{{ url('tree', repository=repository[:-4], revision=commit.tree.id) }}">{{ commit.tree.short_id }}</a>
</td>
</tr>
<tr>
<td>
<b>Parent(s)</b>
</td>
<td>
{% for parent in commit.parents %}
<div>
<a href="{{ url('commit', repository=repository[:-4], commit_id=parent.id) }}">{{ parent.short_id }}</a>
(<a href="{{ url('commit2', repository=repository[:-4], commit_id=commit.id, commit_id2=parent.id) }}">diff</a>)
</div>
{% endfor %}
</td>
</tr>
</tbody>
</table>
<br />
<div class="message">{{ commit.message }}</div>
<br /><br />
<div class="separator">
commits diff:
<b>{{ commit2.short_id if commit2 else '0000000' }}</b>..<b>{{ commit.short_id }}</b>
</div>
{# pygit2 appears to recompute all the stats every time we use diff.stats,
# therefore we set this variable in order to compute it only once.
#}
{% set diff_stats = diff.stats %}
<div class="accumulated">
<b>{{ diff_stats.files_changed }}</b> file{{ 's' if diff_stats.files_changed != 1 }} changed,
<span class="insertions"><b>{{ diff_stats.insertions }}</b> insertion{{ 's' if diff_stats.insertions != 1 }}</span>,
<span class="deletions"><b>{{ diff_stats.deletions }}</b> deletion{{ 's' if diff_stats.deletions != 1 }}</span>
โ
{% if commit2 %}
<a href="{{ url('commit_diff2', repository=repository[:-4], commit_id=commit.id, commit_id2=commit2.id, **request.query) }}">download</a>
{% else %}
<a href="{{ url('commit_diff', repository=repository[:-4], commit_id=commit.id, **request.query) }}">download</a>
{% endif %}
</div>
<br /><br />
<details class="diffstat" {{ 'open' if defaults.DIFF_EXPAND_DIFFSTAT }}>
<summary>Diffstat</summary>
<table>
{% for patch in diff %}
<tr>
<td class="filemode">{{ patch.delta.new_file.mode|filemode }}</td>
<td class="path">
<a href="#{{ (patch.delta.new_file.id|string)[:7] }}">{{ patch.delta.new_file.path }}</a>
</td>
<td class="lines">{{ patch.line_stats[1] + patch.line_stats[2] }}</td>
<td class="histogram">
{% if diff_stats.insertions + diff_stats.deletions > 0 %}
<span title="+{{ patch.line_stats[1] }}" class="insertions" style="width:{{ patch.line_stats[1] / (diff_stats.insertions + diff_stats.deletions) * 100 }}%"></span
><span title="-{{ patch.line_stats[2] }}" class="deletions" style="width:{{ patch.line_stats[2] / (diff_stats.insertions + diff_stats.deletions) * 100 }}%"></span>
{% endif %}
</td>
<td></td>
</tr>
{% endfor %}
</table>
</details>
<br />
<details class="diff_options" {{ 'open' if defaults.DIFF_EXPAND_DIFFOPTIONS }}>
<summary>Diff options</summary>
<form action="" method="get">
<table>
<tr>
<td>
View
</td>
<td>
<label><input type="radio" name="mode" value="udiff" {{ 'checked' if mode == 'udiff' }}>Unified</label>
<label><input type="radio" name="mode" value="udiff_raw" {{ 'checked' if mode == 'udiff_raw' }}>Unified (raw)</label>
<label><input type="radio" name="mode" value="ssdiff" {{ 'checked' if mode == 'ssdiff' }}>Side by side</label>
</td>
</tr>
<tr>
<td>
Side
</td>
<td>
<label><input type="radio" name="side" value="normal" {{ 'checked' if side == 'normal' }}>Normal</label>
<label><input type="radio" name="side" value="reverse" {{ 'checked' if side == 'reverse' }}>Reverse</label>
</td>
</tr>
<tr>
<td>
Whitespace
</td>
<td>
<label><input type="radio" name="whitespace" value="include" {{ 'checked' if whitespace == 'include' }}>Include</label>
<label><input type="radio" name="whitespace" value="ignore_all" {{ 'checked' if whitespace == 'ignore_all' }}>Ignore all</label>
<label><input type="radio" name="whitespace" value="ignore_change" {{ 'checked' if whitespace == 'ignore_change' }}>Ignore amount changes</label>
<label><input type="radio" name="whitespace" value="ignore_eol" {{ 'checked' if whitespace == 'ignore_eol' }}>Ignore at end of line</label>
</td>
</tr>
<tr>
<td>
Context lines
</td>
<td>
<input type="number" min=0 max=1000 name="context_lines" value="{{ context_lines }}" />
</td>
</tr>
<tr>
<td>
Inter-hunk lines
</td>
<td>
<input type="number" min=0 max=1000 name="inter_hunk_lines" value="{{ inter_hunk_lines }}" />
</td>
</tr>
</table>
<input type="submit" value="Update" />
</form>
</details>
{% if mode == 'udiff_raw' %}
<div class="raw_diff">{{ diff.patch|highlight_udiff|safe }}</div>
{% else %}
{% 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
# Looks like pygit2 also has a pygit2.DiffDelta.status_char() functions that
# returns the single-char abbreviation of the delta status, so we can use this
# instead of the raw integer.
# 0 = UNCHANGED
# 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
# ... (there are other codes that we don't use)
#}
{% set delta_char = patch.delta.status_char() %}
<details class="diff_view" id="{{ (patch.delta.new_file.id|string)[:7] }}" {{ 'open' if defaults.DIFF_EXPAND }}>
<summary>
{% if patch.line_stats[1] + patch.line_stats[2] > 0 %}
{% set color_border = patch.line_stats[1] / ( patch.line_stats[1] + patch.line_stats[2] ) * 100 %}
{% else %}
{% set color_border = 0 %}
{% endif %}
<span class="histogram" style="border-image: linear-gradient(to right, lightgreen {{ color_border }}%, red {{ color_border }}%) 1;">
+{{ patch.line_stats[1] }}/-{{ patch.line_stats[2] }}
</span>
{% if delta_char == 'A' %}
<b title="Added">A</b> {{ patch.delta.new_file.path }}
{% endif %}
{% if delta_char == 'D' %}
<b title="Deleted">D</b> {{ patch.delta.old_file.path }}
{% endif %}
{% if delta_char == 'M' %}
<b title="Modified">M</b> {{ patch.delta.new_file.path }}
{% endif %}
{% if delta_char == 'R' %}
<b title="Renamed">R</b> {{ patch.delta.old_file.path }} -> {{ patch.delta.new_file.path }}
{% endif %}
{% if delta_char == 'C' %}
<b title="Copied">C</b> {{ patch.delta.old_file.path }} -> {{ patch.delta.new_file.path }}
{% endif %}
<table>
<tr>
<td>
index
{% if delta_char == 'A' %}
{{ (patch.delta.old_file.id|string)[:7] }}
{%- else -%}
<a href="{{ url('tree_path', repository=repository[:-4], revision=commit2.id, tree_path=patch.delta.old_file.path) }}">{{ (patch.delta.old_file.id|string)[:7] }}</a>
{%- endif -%}
..
{%- if delta_char == 'D' -%}
{{ (patch.delta.new_file.id|string)[:7] }}
{%- else -%}
<a href="{{ url('tree_path', repository=repository[:-4], revision=commit.id, tree_path=patch.delta.new_file.path) }}">{{ (patch.delta.new_file.id|string)[:7] }}</a>
{% endif %}
</td>
</tr>
<tr>
<td colspan="4">
old size: {{ patch.delta.old_file.size|human_size(B=true) }}
-
new size: {{ patch.delta.new_file.size|human_size(B=true) }}
</td>
</tr>
<tr>
<td colspan="4">
{% if delta_char == 'A' %}
new file mode: {{ patch.delta.new_file.mode|filemode }}
{% elif delta_char == 'D' %}
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 }}
<br />
new mode: {{ patch.delta.new_file.mode|filemode }}
{% endif %}
</td>
</tr>
</table>
</summary>
<table class="diff">
{% if patch.delta.is_binary %}
<tr>
<td colspan="4">
<i>Binary file</i>
</td>
</tr>
{% endif %}
{% for hunk in patch.hunks if not patch.delta.is_binary %}
{#### UDIFF mode ####}
{# In this mode the lines are printed one after the other. #}
{% if mode == 'udiff' %}
<tr class="header">
<td></td>
<td></td>
<td></td>
<td>{{ hunk.header }}</td>
</tr>
{% for line in hunk.lines %}
<tr class="udiff {{ 'insertion' if line.old_lineno < 0 }} {{ 'deletion' if line.new_lineno < 0 }}">
<td class="linenos">
{% if line.old_lineno >= 0 %}
<a href="{{ url('tree_path', repository=repository[:-4], revision=commit2.id, tree_path=patch.delta.old_file.path) }}#line-{{ line.old_lineno }}">{{ line.old_lineno }}</a>
{% endif %}
</td>
<td class="linenos">
{% if line.new_lineno >= 0 %}
<a href="{{ url('tree_path', repository=repository[:-4], revision=commit.id, tree_path=patch.delta.new_file.path) }}#line-{{ line.new_lineno }}">{{ line.new_lineno }}</a>
{% endif %}
</td>
<td class="origin">{{ line.origin }}</td>
<td class="content">{{ line.content }}</td>
</tr>
{% endfor %}
{% endif %}
{#### SSDIFF mode ####}
{# In this mode, changed lines are buffered until we find an
# unchanged line. When an unchanged line has been found, the
# buffer is emptied.
#}
{% if mode == 'ssdiff' %}
{% set buffer_deletions = [] %}
{% set buffer_insertions = [] %}
{% macro print_buffer(buffer_deletions, buffer_insertions) %}
{% for buffer_del, buffer_ins in zip_longest(buffer_deletions, buffer_insertions) %}
<tr class="ssdiff">
{% if buffer_del %}
<td class="linenos">
<a href="{{ url('tree_path', repository=repository[:-4], revision=commit2.id, tree_path=patch.delta.old_file.path) }}#line-{{ buffer_del.old_lineno }}">{{ buffer_del.old_lineno }}</a>
</td>
<td class="content {{ 'deletion' if buffer_insertions|length == 0 else 'change' }}">{{ buffer_del.content }}</td>
{% else %}
<td class="linenos">
</td>
<td class="content"></td>
{% endif %}
{% if buffer_ins %}
<td class="linenos">
<a href="{{ url('tree_path', repository=repository[:-4], revision=commit.id, tree_path=patch.delta.new_file.path) }}#line-{{ buffer_ins.new_lineno }}">{{ buffer_ins.new_lineno }}</a>
</td>
<td class="content {{ 'insertion' if buffer_deletions|length == 0 else 'change' }}">{{ buffer_ins.content }}</td>
{% else %}
<td class="linenos">
</td>
<td class="content"></td>
{% endif %}
</tr>
{% endfor %}
{# .clear() empties the buffer. Since this requires {{}}
# brackets instead of {%%}, this line will print the value
# of "buffer_deletions" which is "None".
# "or ''" is a hack for printing an empty line instead of "None".
#}
{{ buffer_deletions.clear() or '' }}
{{ buffer_insertions.clear() or '' }}
{% endmacro %}
<tr class="header">
<td colspan=4>{{ hunk.header }}</td>
</tr>
{% for line in hunk.lines %}
{% if line.old_lineno < 0 %}
{{ buffer_insertions.append(line) or '' }}
{% endif %}
{% if line.new_lineno < 0 %}
{{ buffer_deletions.append(line) or '' }}
{% endif %}
{% if line.old_lineno >= 0 and line.new_lineno >= 0 %}
{# Unload buffer #}
{{ print_buffer(buffer_deletions, buffer_insertions) }}
{# Insert the unchanged line. #}
<tr class="ssdiff">
<td class="linenos">
<a href="{{ url('tree_path', repository=repository[:-4], revision=commit2.id, tree_path=patch.delta.old_file.path) }}#line-{{ line.old_lineno }}">{{ line.old_lineno }}</a>
</td>
<td class="content">{{ line.content }}</td>
<td class="linenos">
<a href="{{ url('tree_path', repository=repository[:-4], revision=commit.id, tree_path=patch.delta.new_file.path) }}#line-{{ line.new_lineno }}">{{ line.new_lineno }}</a>
</td>
<td class="content">{{ line.content }}</td>
</tr>
{% endif %}
{% endfor %}
{# Empty remaining buffer. There is a non-empty buffer if we did
# not find an unchanged line, for example when context_lines=0.
#}
{{ print_buffer(buffer_deletions, buffer_insertions) }}
{% endif %}
{% endfor %}
</table>
</details>
<hr/>
{% endfor %}
{% endif %}
</div>
{% endblock %}