diff --git a/README b/README
index 6daad1c..325dc7f 100644
--- a/README
+++ b/README
Fast, minimal git webview. Using Libgit2 as git backend
-- Browse bare repos with commit history, refs, trees, blobs, diffs, patches, and blame views
\ No newline at end of file
+- Browse bare repos with commit history, refs, trees, blobs, diffs, and patches
\ No newline at end of file
diff --git a/app.py b/app.py
index 022c370..b585b1d 100644
--- a/app.py
+++ b/app.py
import os
import subprocess
-from flask import Flask, render_template, request, abort
+from flask import Flask, render_template, request, abort, redirect, url_for
from dotenv import load_dotenv
from git.repository import get_bare_repos
from git.blob import get_blob
from git.misc import get_version, validate_repo_name, validate_ref, validate_ref_as_commit, sanitize_path
from git.diff import get_diff
-from git.blame import get_blame
from highlight import highlight_diff
from filters import register_filters
ref = request.args.get('ref', 'HEAD').strip()
if not validate_ref_as_commit(f"{repo_path}/{repo_name}", ref):
abort(400, "Invalid ref")
- commits = get_commits(f"{repo_path}/{repo_name}", ref=ref, max_count=10)
- refs = get_references(f"{repo_path}/{repo_name}")
- readme = None
- for filename in ['README.md', 'README']:
- try:
- readme_blob = get_blob(f"{repo_path}/{repo_name}", ref, filename)
- if readme_blob:
- readme = readme_blob['content']
- break
- except:
- pass
- return render_template("overview.html", repo_name=repo_name, refs=refs, commits=commits, readme=readme)
+ return redirect(url_for('repo_commits', repo_name=repo_name, ref=ref))
@app.route("/<repo_name>/commits")
def repo_commits(repo_name):
blob = get_blob(f"{repo_path}/{repo_name}", ref, path)
return render_template("blob.html", repo_name=repo_name, ref=ref, path=path, blob=blob, refs=refs)
-@app.route("/<repo_name>/blame/<path:path>")
-def repo_blame_path(repo_name, path):
- if not validate_repo_name(repo_name):
- abort(404)
- ref = request.args.get('ref', 'HEAD').strip()
- if not validate_ref(f"{repo_path}/{repo_name}", ref):
- abort(400, "Invalid ref")
- try:
- path = sanitize_path(path)
- except ValueError:
- abort(400, "Invalid path")
- refs = get_references(f"{repo_path}/{repo_name}")
-
- # if ajax (for loading)
- if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
- blame, style = get_blame(f"{repo_path}/{repo_name}", ref, path)
- return {'blame': blame, 'style': style}
-
- # initial
- return render_template("blame.html", repo_name=repo_name, ref=ref, path=path, refs=refs)
-
@app.route("/<repo_name>/diff")
def repo_diff(repo_name):
if not validate_repo_name(repo_name):
diff --git a/git/blame.py b/git/blame.py
deleted file mode 100644
index e49463f..0000000
--- a/git/blame.py
+++ /dev/null
-import pygit2 as git
-from highlight import get_highlight_blame_style, highlight_line
-
-# discourage using blame because its very expensive, especially on repos with long commits history
-# retrieves blame information for a file at given ref and path
-def get_blame(repo_path, ref="HEAD", file_path=""):
- repo = git.Repository(repo_path)
- obj = repo.revparse_single(ref)
- # TODO: doesnt work with tree refs
- if obj.type == git.GIT_OBJECT_COMMIT:
- commit = obj
- else:
- commit = obj.peel(git.GIT_OBJECT_COMMIT)
-
- # traverse to the blob path
- # TODO: make this common across more modules
- tree = commit.tree
- blob = None
- if file_path:
- parts = file_path.rstrip('/').split('/')
- for part in parts:
- found = False
- for entry in tree:
- if entry.name == part:
- if entry.type == git.GIT_OBJECT_BLOB:
- blob = repo.get(entry.id)
- found = True
- break
- elif entry.type == git.GIT_OBJECT_TREE:
- tree = repo.get(entry.id)
- found = True
- break
- if not found:
- return None # path not found
- if blob is None:
- return None
-
- blame = repo.blame(file_path)
-
- # get blob content lines directly. maybe later use lines_in_hunk
- content_lines = blob.data.decode('utf-8', errors='replace').splitlines()
-
- # create a list to hold blame info per line
- blame_lines = [None] * len(content_lines)
- for hunk in blame:
- # https://libgit2.org/docs/reference/main/blame/git_blame_hunk.html
- start = hunk.final_start_line_number - 1 # to 0 index, since using python lists
- end = start + hunk.lines_in_hunk
- commit = repo.get(hunk.final_commit_id) # last commit oid
- # TODO: more info if needed
- info = {
- 'commit_id': str(hunk.final_commit_id),
- 'author': {
- 'name': commit.author.name,
- 'time': commit.author.time,
- },
- }
- # fill premade info for lines in this hunk
- for i in range(start, min(end, len(blame_lines))): # prevent index overflow, with min
- blame_lines[i] = info
-
- # combine content lines with their blame info
- result = []
- for i, line in enumerate(content_lines):
- result.append({
- 'line_num': i + 1,
- 'content': line,
- 'blame': blame_lines[i],
- # highlight every line individually
- 'highlighted': highlight_line(line, file_path)
- })
-
- return result, get_highlight_blame_style()
\ No newline at end of file
diff --git a/git/commit.py b/git/commit.py
index 9792037..d30b3cf 100644
--- a/git/commit.py
+++ b/git/commit.py
repo = git.Repository(path)
commits = []
# TODO: accept blob oids to filter commits that touch specific blobs
- obj = repo.revparse_single(ref)
- if obj.type == git.GIT_OBJECT_COMMIT:
- commit = obj
- else:
- commit = obj.peel(git.GIT_OBJECT_COMMIT)
- walker = repo.walk(commit.id, git.GIT_SORT_TIME)
+ try:
+ obj = repo.revparse_single(ref)
+ except Exception:
+ # PYGIT DOES NOT REURN LIBGIT ERRORS!??! >:(
+ # only generic exception....
+ return [], f"invalid reference"
+
+ # revwalk
+ walker = repo.walk(obj.id, git.GIT_SORT_TIME)
n = 0
for commit in walker:
diff --git a/highlight.py b/highlight.py
index c6eaa8a..8c974ad 100644
--- a/highlight.py
+++ b/highlight.py
return f"<style>{css}</style>{highlighted}"
-# get CSS styles for blame view
-def get_highlight_blame_style():
- formatter = _formatter(cssclass="blame-code")
- return formatter.get_style_defs(".blame-code")
-
-
-# highlight a single line of code with filename-based lexer
-def highlight_line(line, filename):
- # Use nowrap to avoid wrapping in <div><pre>...</pre></div>
- formatter = _formatter(cssclass="blame-code", nowrap=True)
- return highlight(line, _safe_lexer(line, filename), formatter)
-
-
# highlight diff with DiffLexer
def highlight_diff(data):
formatter = _formatter()
diff --git a/templates/base.html b/templates/base.html
index 076527f..a003975 100644
--- a/templates/base.html
+++ b/templates/base.html
<a href="/">Home</a>
{% set repo_name = request.view_args.get('repo_name') %}
{% if repo_name %}
- | <a href="{{ url_for('repo_detail', repo_name=repo_name) }}?ref={{ current_ref }}">Overview</a>
| <a href="{{ url_for('repo_commits', repo_name=repo_name) }}?ref={{ current_ref }}">Commits</a>
| <a href="{{ url_for('repo_refs', repo_name=repo_name) }}">Refs</a>
| <a href="{{ url_for('repo_tree_path', repo_name=repo_name) }}?ref={{ current_ref }}">Tree</a>
diff --git a/templates/blame.html b/templates/blame.html
deleted file mode 100644
index ddca83c..0000000
--- a/templates/blame.html
+++ /dev/null
-{% extends "base.html" %}
-
-{% block content %}
-<style id="blame-style"></style>
-<h2>Blame: {{ path }}</h2>
-<div id="loading">Loading blame information, this may take a while for large files...</div>
-<div id="pomeranian" style="display: none;">
- <p>Server is taking its time... anyways, here's a tug of war match between two toasted pomeranians:</p>
- <iframe width="560" height="315" src="https://www.youtube.com/embed/Q-KciIbk_oA?si=c_DqdaVbYMlfhxW7&controls=0&autoplay=1" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
-</div>
-<div id="blame-content" style="display: none;">
-</div>
-<script>
-document.addEventListener('DOMContentLoaded', function() {
- const repoName = window.location.pathname.split('/')[1];
- const loading = document.getElementById('loading');
- const pomeranian = document.getElementById('pomeranian');
- const pomeranianIframe = pomeranian.querySelector('iframe');
- const blameContent = document.getElementById('blame-content');
-
- const showPomeranian = () => {
- loading.style.display = 'none';
- pomeranian.style.display = 'block';
- };
-
- const hidePomeranian = () => {
- pomeranian.style.display = 'none';
- if (pomeranianIframe) {
- pomeranianIframe.src = 'about:blank';
- }
- };
-
- const pomeranianTimeout = setTimeout(showPomeranian, 5000);
-
- fetch(window.location.href, {
- headers: {
- // let server know that this is ajax
- 'X-Requested-With': 'XMLHttpRequest'
- }
- })
- .then(response => response.json())
- .then(data => {
- clearTimeout(pomeranianTimeout);
- document.getElementById('blame-style').innerHTML = data.style;
-
- // add blame content, previosly templating
- let content = '<div class="blame-code"><pre>';
- if (data.blame && data.blame.length > 0) {
- data.blame.forEach(line => {
- content += `${String(line.line_num).padStart(4)} `;
- if (line.blame) {
- content += `<a href="/${repoName}/commits/${line.blame.commit_id}">${line.blame.commit_id.substring(0, 8)}</a> ${line.blame.author.name.substring(0, 15).padEnd(15)} ${new Date(line.blame.author.time * 1000).toISOString().slice(0, 19).replace('T', ' ')}`;
- }
- content += ` | ${line.highlighted}\n`;
- });
- } else {
- content += 'No blame info';
- }
- content += '</pre></div>';
-
- blameContent.innerHTML = content;
- loading.style.display = 'none';
- hidePomeranian();
- blameContent.style.display = 'block';
- })
- .catch(error => {
- clearTimeout(pomeranianTimeout);
- loading.innerHTML = 'Error loading blame';
- hidePomeranian();
- });
-});
-</script>
-{% endblock %}
\ No newline at end of file
diff --git a/templates/blob.html b/templates/blob.html
index f7e675f..54bfbf6 100644
--- a/templates/blob.html
+++ b/templates/blob.html
<h2>Blob: {{ blob.name }}</h2>
<p>Blob id: {{ blob.id }}</p>
<p>Size: {{ blob.size | size }}</p>
-<p><a href="{{ url_for('repo_blame_path', repo_name=repo_name, path=path) }}?ref={{ ref }}">Blame</a></p>
<div>
{% if blob.is_binary %}
<pre>Binary...</pre>
diff --git a/templates/index.html b/templates/index.html
index fc1eb28..042b713 100644
--- a/templates/index.html
+++ b/templates/index.html
{% for repo in repos %}
<tr>
<td>
- <a href="{{ url_for('repo_detail', repo_name=repo.name) }}">
+ <a href="{{ url_for('repo_commits', repo_name=repo.name) }}">
{{ repo.name }}
</a>
</td>
diff --git a/templates/overview.html b/templates/overview.html
deleted file mode 100644
index 2a83efb..0000000
--- a/templates/overview.html
+++ /dev/null
-{% extends "base.html" %}
-
-{% block content %}
-<h2>{{ repo_name }}</h2>
-<h3>Latest commits</h3>
-<table>
- <thead>
- <tr style="font-weight: bold;">
- <td>Commit</td>
- <td>Message</td>
- <td>Author</td>
- <td>Age</td>
- <td>Changes</td>
- </tr>
- </thead>
- <tbody>
- {% for commit in commits %}
- <tr>
- <td valign="top" nowrap><a href="{{ url_for('commit_detail', repo_name=repo_name, commit_id=commit.id) }}">{{ commit.id[:8] }}</a></td>
- <td valign="top">{{ commit.message }}</td>
- <td valign="top" nowrap>{{ commit.author.name }}</td>
- <td valign="top" nowrap>{{ commit.date | age }}</td>
- <td valign="top" nowrap>{{ commit.diff_stats.files_changed }} file{% if commit.diff_stats.files_changed != 1 %}s{% endif %}, <span style="color: green;">+{{ commit.diff_stats.insertions }}</span>, <span style="color: red;">-{{ commit.diff_stats.deletions }}</span></td>
- </tr>
- {% endfor %}
- </tbody>
- </table>
-{% if readme %}
-<h3>README</h3>
-<pre>{{ readme }}</pre>
-{% endif %}
-{% endblock %}
\ No newline at end of file