Diff between 3ca317e7affbe0187785b703a7b86001183939b5 and 602caca00fbc951bda7bd58c78b18f510fb9a5b5

Changed Files

File Additions Deletions Status
app.py +12 -3 modified
git/diff.py +14 -7 modified
highlight.py +22 -0 modified
templates/diff.html +43 -8 modified

Full Patch

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'<style>{css}</style>{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'<style>{css}</style>{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 %}
+<h1>Diff</h1>
+
+{% if error %}
+<p style="color: red;">Error: {{ error }}</p>
+{% endif %}
+
+{% if diff %}
 <h1>Diff between {{ diff.ref1 }} ({{ diff.ref1_id[:7] }}) and {{ diff.ref2 }} ({{ diff.ref2_id[:7] }})</h1>
 
+<form method="get" action="">
+    <label for="id1">From:</label>
+    <input type="text" name="id1" value="{{ diff.ref1 }}" id="id1">
+    <label for="id2">To:</label>
+    <input type="text" name="id2" value="{{ diff.ref2 }}" id="id2">
+    <label for="context_lines">Context Lines:</label>
+    <input type="number" name="context_lines" value="{{ context_lines }}" id="context_lines" min="0">
+    <label for="interhunk_lines">Interhunk Lines:</label>
+    <input type="number" name="interhunk_lines" value="{{ interhunk_lines }}" id="interhunk_lines" min="0">
+    <button type="submit">Update Diff</button>
+</form>
+
 <h2>Changed Files</h2>
-<ul>
-    {% for file in diff.files %}
-    <li>{{ file.file }}: +{{ file.additions }} -{{ file.deletions }} ({{ file.status }})</li>
-    {% endfor %}
-</ul>
+<table>
+    <thead>
+        <tr>
+            <th>File</th>
+            <th>Additions</th>
+            <th>Deletions</th>
+            <th>Status</th>
+        </tr>
+    </thead>
+    <tbody>
+        {% for file in diff.files %}
+        <tr>
+            <td>{{ file.file }}</td>
+            <td style="color: green;">+{{ file.additions }}</td>
+            <td style="color: red;">-{{ file.deletions }}</td>
+            <td>{{ file.status }}</td>
+        </tr>
+        {% endfor %}
+    </tbody>
+</table>
 
 <h2>Full Patch</h2>
-<pre>
-    {{ diff.patch }}
-</pre>
+<div>
+{{ highlighted_patch|safe }}
+</div>
+{% endif %}
 {% endblock %}
\ No newline at end of file