diff --git a/app.py b/app.py
index b3b7c4b..d483772 100644
--- a/app.py
+++ b/app.py
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()
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
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
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
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
{% 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