home » zplus/clif.git
Author zPlus <zplus@peers.community> 2022-08-07 09:10:08
Committer zPlus <zplus@peers.community> 2022-08-07 09:10:08
Commit d548a4c (patch)
Tree 7446df3
Parent(s)

Add diff options in commit page. Allow diff options to be changed when viewing a commit: view (udiff, ssdiff), context lines, interhunk lines, side (normal, reverse), whitespace.


commits diff: 8032a8b..d548a4c
3 files changed, 235 insertions, 35 deletionsdownload


Diffstat
-rw-r--r-- static/css/clif.css 9
-rw-r--r-- templates/repository/commit.html 195
-rw-r--r-- web.py 66

Diff options
View
Side
Whitespace
Context lines
Inter-hunk lines
+7/-2 M   static/css/clif.css
index 98a2bf1..20fd6fb
old size: 7K - new size: 8K
@@ -326,7 +326,7 @@ div.commit {
326 326 }
327 327
328 328 div.commit .message {
329 - margin-top: 2rem;
329 + margin: 2rem 0;
330 330 white-space: pre-wrap;
331 331 }
332 332
@@ -376,7 +376,12 @@ div.commit {
376 376 background-color: #e6ffed;
377 377 }
378 378
379 - div.commit .diff .content {
379 + div.commit .diff .change {
380 + background-color: #feffbe;
381 + }
382 +
383 + div.commit .diff .ssdiff .content {
384 + width: 50%;
380 385 white-space: pre-wrap;
381 386 }
382 387

+167/-28 M   templates/repository/commit.html
index 39f2ca7..f814f2d
old size: 7K - new size: 15K
@@ -66,19 +66,76 @@
66 66
67 67 <div class="message">{{ commit.message }}</div>
68 68
69 + <div>
70 + <details>
71 + <summary>Diff options</summary>
72 + <form action="" method="get">
73 + <table>
74 + <tr>
75 + <td>
76 + View
77 + </td>
78 + <td>
79 + <label><input type="radio" name="mode" value="udiff" {{ 'checked' if mode == 'udiff' }}>Unified</label>
80 + <label><input type="radio" name="mode" value="ssdiff" {{ 'checked' if mode == 'ssdiff' }}>Side by side</label>
81 + </td>
82 + </tr>
83 + <tr>
84 + <td>
85 + Context lines
86 + </td>
87 + <td>
88 + <input type="number" min=0 max=1000 name="context_lines" value="{{ context_lines }}" />
89 + </td>
90 + </tr>
91 + <tr>
92 + <td>
93 + Inter-hunk lines
94 + </td>
95 + <td>
96 + <input type="number" min=0 max=1000 name="inter_hunk_lines" value="{{ inter_hunk_lines }}" />
97 + </td>
98 + </tr>
99 + <tr>
100 + <td>
101 + Side
102 + </td>
103 + <td>
104 + <label><input type="radio" name="side" value="normal" {{ 'checked' if side == 'normal' }}>Normal</label>
105 + <label><input type="radio" name="side" value="reverse" {{ 'checked' if side == 'reverse' }}>Reverse</label>
106 + </td>
107 + </tr>
108 + <tr>
109 + <td>
110 + Whitespace
111 + </td>
112 + <td>
113 + <label><input type="radio" name="whitespace" value="include" {{ 'checked' if whitespace == 'include' }}>Include</label>
114 + <label><input type="radio" name="whitespace" value="ignore_all" {{ 'checked' if whitespace == 'ignore_all' }}>Ignore all</label>
115 + <label><input type="radio" name="whitespace" value="ignore_change" {{ 'checked' if whitespace == 'ignore_change' }}>Ignore amount changes</label>
116 + <label><input type="radio" name="whitespace" value="ignore_eol" {{ 'checked' if whitespace == 'ignore_eol' }}>Ignore at end of line</label>
117 + </td>
118 + </tr>
119 + </table>
120 + <input type="submit" value="Update" />
121 + </form>
122 + </details>
123 + </div>
124 +
69 125 {% for patch in diff %}
70 126
71 127 <table class="diff">
72 128
73 129 {# The following status values are defined in the git_delta_t enum
74 130 # in libgit2. See https://github.com/libgit2/libgit2/blob/main/include/git2/diff.h
131 + # 0 = UNCHANGED
132 + # 1 = ADDED (does not exist in old version)
133 + # 2 = DELETED (does not exist in new version)
134 + # 3 = MODIFIED (content changed between old and new versions)
135 + # 4 = RENAMED
136 + # 5 = COPIED
137 + # ... (there are other codes that we don't use)
75 138 #}
76 -
77 - {# 1=ADDED (does not exist in old version) #}
78 - {# 2=DELETED (does not exist in new version) #}
79 - {# 3=MODIFIED (content changed between old and new versions) #}
80 - {# 4=RENAMED #}
81 - {# 5=COPIED #}
82 139 <thead>
83 140 <tr>
84 141 <td colspan="4">
@@ -136,30 +193,112 @@
136 193 {% endif %}
137 194
138 195 {% for hunk in patch.hunks if not patch.delta.is_binary %}
139 -
140 - <tr class="header">
141 - <td></td>
142 - <td></td>
143 - <td></td>
144 - <td>{{ hunk.header }}</td>
145 - </tr>
146 196
147 - {% for line in hunk.lines %}
148 - <tr class="{{ 'insertion' if line.old_lineno < 0 }} {{ 'deletion' if line.new_lineno < 0 }}">
149 - <td class="lineno">
150 - {% if line.old_lineno >= 0 %}
151 - {{ line.old_lineno }}
152 - {% endif %}
153 - </td>
154 - <td class="lineno">
155 - {% if line.new_lineno >= 0 %}
156 - {{ line.new_lineno }}
157 - {% endif %}
158 - </td>
159 - <td class="origin">{{ line.origin }}</td>
160 - <td class="content">{{ line.content }}</td>
197 + {#### UDIFF mode ####}
198 +
199 + {% if mode == 'udiff' %}
200 + <tr class="header">
201 + <td></td>
202 + <td></td>
203 + <td></td>
204 + <td>{{ hunk.header }}</td>
161 205 </tr>
162 - {% endfor %}
206 +
207 + {% for line in hunk.lines %}
208 + <tr class="udiff {{ 'insertion' if line.old_lineno < 0 }} {{ 'deletion' if line.new_lineno < 0 }}">
209 + <td class="lineno">
210 + {% if line.old_lineno >= 0 %}
211 + {{ line.old_lineno }}
212 + {% endif %}
213 + </td>
214 + <td class="lineno">
215 + {% if line.new_lineno >= 0 %}
216 + {{ line.new_lineno }}
217 + {% endif %}
218 + </td>
219 + <td class="origin">{{ line.origin }}</td>
220 + <td class="content">{{ line.content }}</td>
221 + </tr>
222 + {% endfor %}
223 + {% endif %}
224 +
225 + {#### SSDIFF mode ####}
226 +
227 + {% if mode == 'ssdiff' %}
228 + {% macro print_buffer(buffer_deletions, buffer_insertions) %}
229 + {% for buffer_del, buffer_ins in zip_longest(buffer_deletions, buffer_insertions) %}
230 + <tr class="ssdiff">
231 + {% if buffer_del %}
232 + <td class="lineno">
233 + {{ buffer_del.old_lineno }}
234 + </td>
235 + <td class="content {{ 'deletion' if buffer_insertions|length == 0 else 'change' }}">{{ buffer_del.content }}</td>
236 + {% else %}
237 + <td class="lineno">
238 + </td>
239 + <td class="content"></td>
240 + {% endif %}
241 +
242 + {% if buffer_ins %}
243 + <td class="lineno">
244 + {{ buffer_ins.new_lineno }}
245 + </td>
246 + <td class="content {{ 'insertion' if buffer_deletions|length == 0 else 'change' }}">{{ buffer_ins.content }}</td>
247 + {% else %}
248 + <td class="lineno">
249 + </td>
250 + <td class="content"></td>
251 + {% endif %}
252 + </tr>
253 + {% endfor %}
254 +
255 + {# .clear() empties the buffer. However since it requires
256 + # {{}} brackets instead of {%%}, this line will print the
257 + # value of "buffer_deletions" which is "None".
258 + # "or ''" is a hack for printing an empty line instead of "None".
259 + #}
260 + {{ buffer_deletions.clear() or '' }}
261 + {{ buffer_insertions.clear() or '' }}
262 + {% endmacro %}
263 +
264 + <tr class="header">
265 + <td colspan=4>{{ hunk.header }}</td>
266 + </tr>
267 +
268 + {% set buffer_deletions = [] %}
269 + {% set buffer_insertions = [] %}
270 +
271 + {% for line in hunk.lines %}
272 + {% if line.old_lineno < 0 %}
273 + {{ buffer_insertions.append(line) or '' }}
274 + {% endif %}
275 +
276 + {% if line.new_lineno < 0 %}
277 + {{ buffer_deletions.append(line) or '' }}
278 + {% endif %}
279 +
280 + {% if line.old_lineno >= 0 and line.new_lineno >= 0 %}
281 +
282 + {# Unload buffer #}
283 + {{ print_buffer(buffer_deletions, buffer_insertions) }}
284 +
285 + <tr class="ssdiff">
286 + <td class="lineno">
287 + {{ line.old_lineno }}
288 + </td>
289 + <td class="content">{{ line.content }}</td>
290 +
291 + <td class="lineno">
292 + {{ line.new_lineno }}
293 + </td>
294 + <td class="content">{{ line.content }}</td>
295 + </tr>
296 + {% endif %}
297 + {% endfor %}
298 +
299 + {# Empty remaining buffer #}
300 + {{ print_buffer(buffer_deletions, buffer_insertions) }}
301 + {% endif %}
163 302
164 303 {% endfor %}
165 304 </tbody>

+61/-5 M   web.py
index d007ed6..26ac0c5
old size: 25K - new size: 28K
@@ -7,6 +7,7 @@ import email.policy
7 7 import functools
8 8 import glob
9 9 import hashlib
10 + import itertools
10 11 import magic
11 12 import os
12 13 import pathlib
@@ -150,7 +151,7 @@ def humanct(commit_time, commit_time_offset = 0):
150 151 dt = datetime.datetime.fromtimestamp(commit_time, tz)
151 152
152 153 return dt.astimezone(pytz.utc).strftime('%Y-%m-%d %H:%M:%S')
153 -
154 +
154 155 template = functools.partial(template, template_settings = {
155 156 'filters': {
156 157 'ago': timeago.format,
@@ -166,6 +167,7 @@ template = functools.partial(template, template_settings = {
166 167 'now': lambda: datetime.datetime.now(datetime.timezone.utc),
167 168 'request': request,
168 169 'url': application.get_url,
170 + 'zip_longest': itertools.zip_longest
169 171 },
170 172 'autoescape': True
171 173 })
@@ -541,17 +543,71 @@ def commit(repository, commit_id):
541 543 except:
542 544 bottle.abort(404, 'Not a valid commit.')
543 545
544 - # Find diff wih parent
546 + # Diff options
547 +
548 + diff_mode = 'udiff'
549 + if 'mode' in request.query:
550 + if request.query.get('mode') in [ 'udiff', 'ssdiff' ]:
551 + diff_mode = request.query.get('mode')
552 + else:
553 + bottle.abort(400, 'Bad request: mode')
554 +
555 + try: diff_context_lines = int(request.query.get('context_lines', 3))
556 + except: bottle.abort(400, 'Bad request: context_lines')
557 +
558 + try: diff_inter_hunk_lines = int(request.query.get('inter_hunk_lines', 0))
559 + except: bottle.abort(400, 'Bad request: inter_hunk_lines')
560 +
561 + diff_flags = pygit2.GIT_DIFF_NORMAL
562 + diff_side = 'normal'
563 + if 'side' in request.query:
564 + if request.query.get('side') == 'normal':
565 + diff_side = 'normal'
566 + elif request.query.get('side') == 'reverse':
567 + diff_flags |= pygit2.GIT_DIFF_REVERSE
568 + diff_side = 'reverse'
569 + else:
570 + bottle.abort(400, 'Bad request: side')
571 +
572 + diff_whitespace = 'include'
573 + if 'whitespace' in request.query:
574 + if request.query.get('whitespace') == 'include':
575 + diff_whitespace = 'include'
576 + elif request.query.get('whitespace') == 'ignore_all':
577 + diff_flags |= pygit2.GIT_DIFF_IGNORE_WHITESPACE
578 + diff_whitespace = 'ignore_all'
579 + elif request.query.get('whitespace') == 'ignore_change':
580 + diff_flags |= pygit2.GIT_DIFF_IGNORE_WHITESPACE_CHANGE
581 + diff_whitespace = 'ignore_change'
582 + elif request.query.get('whitespace') == 'ignore_eol':
583 + diff_flags |= pygit2.GIT_DIFF_IGNORE_WHITESPACE_EOL
584 + diff_whitespace = 'ignore_eol'
585 + else:
586 + bottle.abort(400, 'Bad request: whitespace')
587 +
588 + # Compute diff with parent
589 +
545 590 if len(commit.parents) > 0:
546 - diff = commit.parents[0].tree.diff_to_tree(commit.tree)
591 + diff = commit.parents[0].tree.diff_to_tree(
592 + commit.tree,
593 + context_lines = diff_context_lines,
594 + interhunk_lines = diff_inter_hunk_lines,
595 + flags = diff_flags)
547 596 else:
548 - diff = commit.tree.diff_to_tree(swap=True)
597 + diff = commit.tree.diff_to_tree(
598 + context_lines = diff_context_lines,
599 + interhunk_lines = diff_inter_hunk_lines,
600 + flags = diff_flags,
601 + swap = True)
549 602
603 + # Compute the similarity index. This is used to decide which files are "renamed".
550 604 diff.find_similar()
551 605
552 606 return template(
553 607 'repository/commit.html',
554 - repository=repository, commit=commit, diff=diff)
608 + repository=repository, commit=commit, diff=diff,
609 + context_lines=diff_context_lines, inter_hunk_lines=diff_inter_hunk_lines,
610 + mode=diff_mode, side=diff_side, whitespace=diff_whitespace)
555 611
556 612 @bottle.get('/<repository:path>.git/raw/<revision>/<tree_path:path>', name='raw')
557 613 def raw(repository, revision, tree_path):