From 602caca00fbc951bda7bd58c78b18f510fb9a5b5 Mon Sep 17 00:00:00 2001 From: Luka Hietala Date: Thu, 20 Nov 2025 20:52:10 +0200 Subject: [PATCH] many improvements to diff page --- app.py | 15 ++++++++++--- git/diff.py | 21 ++++++++++++------- highlight.py | 22 +++++++++++++++++++ templates/diff.html | 51 ++++++++++++++++++++++++++++++++++++++------- 4 files changed, 91 insertions(+), 18 deletions(-) diff --git a/app.py b/app.py index b3b7c4b..d483772 100644 --- a/app.py +++ b/app.py @@ -11,6 +11,7 @@ from git.blob import get_blob from git.misc import get_version from git.diff import get_diff from git.blame import get_blame +from highlight import highlight_diff load_dotenv() @@ -129,8 +130,16 @@ def repo_diff(repo_name): refs = get_refs(f"{repo_path}/{repo_name}") id1 = request.args.get('id1') id2 = request.args.get('id2') - diff = get_diff(f"{repo_path}/{repo_name}", id1=id1, id2=id2) - return render_template("diff.html", diff=diff, refs=refs) - + context_lines = int(request.args.get('context_lines', 3)) + interhunk_lines = int(request.args.get('interhunk_lines', 0)) + # TODO: ADD ERROR HANDLING EVERYWHERE!! + try: + diff = get_diff(f"{repo_path}/{repo_name}", id1=id1, id2=id2, context_lines=context_lines, interhunk_lines=interhunk_lines) + highlighted_patch = highlight_diff(diff['patch']) + return render_template("diff.html", diff=diff, refs=refs, context_lines=context_lines, interhunk_lines=interhunk_lines, highlighted_patch=highlighted_patch) + except ValueError as e: + return render_template("diff.html", error=str(e), refs=refs, context_lines=context_lines, interhunk_lines=interhunk_lines) + except Exception as e: + return render_template("diff.html", error="Server error", refs=refs, context_lines=context_lines, interhunk_lines=interhunk_lines) if __name__ == "__main__": app.run(debug=True) \ No newline at end of file diff --git a/git/diff.py b/git/diff.py index ea1998c..50dfee0 100644 --- a/git/diff.py +++ b/git/diff.py @@ -1,17 +1,24 @@ import pygit2 as git # compares two refs and return diff stats per file and full patch -def get_diff(path, id1, id2): +def get_diff(path, id1, id2, context_lines=3, interhunk_lines=0, **options): repo = git.Repository(path) if not id1: - id1 = 'HEAD~1' + id1 = 'HEAD~1' # default to previous commit if not id2: id2 = 'HEAD' - ref_one = repo.revparse_single(id1) # older - ref_two = repo.revparse_single(id2) # newer - diff = repo.diff(ref_one, ref_two) - - # TODO: context_lines, interhunk_lines, etc as options + try: + ref_one = repo.revparse_single(id1).peel(git.Commit) # potential older, HOPEFULLY + ref_two = repo.revparse_single(id2).peel(git.Commit) # potential newer, HOPEFULLY + except Exception as e: + raise ValueError(f"Invalid ref: {id1} or {id2}. Error: {str(e)}") + + # make sure that ref_one (from) is older than ref_two (to), swap if necessary + if ref_one.commit_time > ref_two.commit_time: + ref_one, ref_two = ref_two, ref_one + id1, id2 = id2, id1 + + diff = repo.diff(ref_one, ref_two, context_lines=context_lines, interhunk_lines=interhunk_lines, **options) # detect renames and copies, if not they are just deletions and additions # rename and copy thresholds are 50% by default in libgit2 diff --git a/highlight.py b/highlight.py index ca5b64e..626c617 100644 --- a/highlight.py +++ b/highlight.py @@ -3,6 +3,7 @@ from pygments.util import ClassNotFound from pygments.lexers import TextLexer from pygments.lexers import guess_lexer from pygments.lexers import guess_lexer_for_filename +from pygments.lexers import DiffLexer from pygments.formatters import HtmlFormatter # server-side syntax highlighting @@ -22,4 +23,25 @@ def highlight_code(data, filename): lexer = TextLexer() css = formatter.get_style_defs('.highlight') highlighted = highlight(data, lexer, formatter) + return f'{highlighted}' + +# bare diff highlighting +def highlight_diff(data): + formatter = HtmlFormatter(style='sas', nobackground=True) + lexer = DiffLexer() + highlighted = highlight(data, lexer, formatter) + # replace default pygments classes with custom ones + # classes are from pygments DiffLexer, generic tokens + # gd = general deleted, gi = general inserted, gh = general hunk, gu = hunk header? + # https://pygments.org/docs/tokens/ + highlighted = highlighted.replace('class="gd"', 'class="diff-removed"') + highlighted = highlighted.replace('class="gi"', 'class="diff-added"') + highlighted = highlighted.replace('class="gh"', 'class="diff-hunk"') + highlighted = highlighted.replace('class="gu"', 'class="diff-header"') + css = """ + .diff-added { color: green; } + .diff-removed { color: red; } + .diff-hunk { background-color: lightgray; } + .diff-header { color: blue; font-weight: bold; } + """ return f'{highlighted}' \ No newline at end of file diff --git a/templates/diff.html b/templates/diff.html index c23bb9c..bf7d768 100644 --- a/templates/diff.html +++ b/templates/diff.html @@ -1,17 +1,52 @@ {% extends "base.html" %} {% block content %} +

Diff

+ +{% if error %} +

Error: {{ error }}

+{% endif %} + +{% if diff %}

Diff between {{ diff.ref1 }} ({{ diff.ref1_id[:7] }}) and {{ diff.ref2 }} ({{ diff.ref2_id[:7] }})

+
+ + + + + + + + + +
+

Changed Files

- + + + + + + + + + + + {% for file in diff.files %} + + + + + + + {% endfor %} + +
FileAdditionsDeletionsStatus
{{ file.file }}+{{ file.additions }}-{{ file.deletions }}{{ file.status }}

Full Patch

-
-    {{ diff.patch }}
-
+
+{{ highlighted_patch|safe }} +
+{% endif %} {% endblock %} \ No newline at end of file -- 2.47.3