Added nice repo system.
This commit is contained in:
parent
e79abf4a26
commit
e09652413f
992
gitlog.jsonl
Normal file
992
gitlog.jsonl
Normal file
File diff suppressed because one or more lines are too long
289
gitlog.py
Normal file
289
gitlog.py
Normal file
@ -0,0 +1,289 @@
|
||||
import http.server
|
||||
import socketserver
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
import mimetypes
|
||||
import html
|
||||
|
||||
# --- Theme selection (choose one: "light1", "light2", "dark1", "dark2") ---
|
||||
THEME = "dark2" # Change this to "light2", "dark1", or "dark2" as desired
|
||||
|
||||
THEMES = {
|
||||
"light1": """
|
||||
body { font-family: Arial, sans-serif; background: #f6f8fa; margin: 0; padding: 0; color: #222; }
|
||||
.container { max-width: 960px; margin: auto; padding: 2em; }
|
||||
.date-header { font-size: 1.2em; margin-top: 2em; border-bottom: 1px solid #ccc; }
|
||||
.commit { margin: 1em 0; padding: 1em; background: #fff; border-radius: 6px; box-shadow: 0 1px 3px rgba(0,0,0,0.07); }
|
||||
.hash { color: #555; font-family: monospace; }
|
||||
.diff { white-space: pre-wrap; background: #f1f1f1; padding: 1em; border-radius: 4px; overflow-x: auto; }
|
||||
ul { list-style: none; padding-left: 0; }
|
||||
li { margin: 0.3em 0; }
|
||||
a { text-decoration: none; color: #0366d6; }
|
||||
a:hover { text-decoration: underline; }
|
||||
""",
|
||||
"light2": """
|
||||
body { font-family: 'Segoe UI', sans-serif; background: #fdf6e3; margin: 0; padding: 0; color: #333; }
|
||||
.container { max-width: 900px; margin: auto; padding: 2em; }
|
||||
.date-header { font-size: 1.2em; margin-top: 2em; border-bottom: 2px solid #e1c699; color: #b58900; }
|
||||
.commit { margin: 1em 0; padding: 1em; background: #fffbe6; border-radius: 6px; box-shadow: 0 1px 3px rgba(200,180,100,0.08); }
|
||||
.hash { color: #b58900; font-family: monospace; }
|
||||
.diff { white-space: pre-wrap; background: #f5e9c9; padding: 1em; border-radius: 4px; overflow-x: auto; }
|
||||
ul { list-style: none; padding-left: 0; }
|
||||
li { margin: 0.3em 0; }
|
||||
a { text-decoration: none; color: #b58900; }
|
||||
a:hover { text-decoration: underline; color: #cb4b16; }
|
||||
""",
|
||||
"dark1": """
|
||||
body { font-family: Arial, sans-serif; background: #181a1b; margin: 0; padding: 0; color: #eaeaea; }
|
||||
.container { max-width: 960px; margin: auto; padding: 2em; }
|
||||
.date-header { font-size: 1.2em; margin-top: 2em; border-bottom: 1px solid #333; color: #8ab4f8; }
|
||||
.commit { margin: 1em 0; padding: 1em; background: #23272b; border-radius: 6px; box-shadow: 0 1px 3px rgba(0,0,0,0.18); }
|
||||
.hash { color: #8ab4f8; font-family: monospace; }
|
||||
.diff { white-space: pre-wrap; background: #23272b; padding: 1em; border-radius: 4px; overflow-x: auto; }
|
||||
ul { list-style: none; padding-left: 0; }
|
||||
li { margin: 0.3em 0; }
|
||||
a { text-decoration: none; color: #8ab4f8; }
|
||||
a:hover { text-decoration: underline; color: #bb86fc; }
|
||||
""",
|
||||
"dark2": """
|
||||
body { font-family: 'Fira Sans', sans-serif; background: #121212; margin: 0; padding: 0; color: #d0d0d0; }
|
||||
.container { max-width: 900px; margin: auto; padding: 2em; }
|
||||
.date-header { font-size: 1.2em; margin-top: 2em; border-bottom: 2px solid #444; color: #ffb86c; }
|
||||
.commit { margin: 1em 0; padding: 1em; background: #22223b; border-radius: 6px; box-shadow: 0 1px 3px rgba(0,0,0,0.22); }
|
||||
.hash { color: #ffb86c; font-family: monospace; }
|
||||
.diff { white-space: pre-wrap; background: #282a36; padding: 1em; border-radius: 4px; overflow-x: auto; }
|
||||
ul { list-style: none; padding-left: 0; }
|
||||
li { margin: 0.3em 0; }
|
||||
a { text-decoration: none; color: #ffb86c; }
|
||||
a:hover { text-decoration: underline; color: #8be9fd; }
|
||||
""",
|
||||
}
|
||||
|
||||
def HTML_TEMPLATE(content, theme=THEME):
|
||||
return f"""
|
||||
<!DOCTYPE html>
|
||||
<html lang=\"en\">
|
||||
<head>
|
||||
<meta charset=\"UTF-8\">
|
||||
<title>Git Log Viewer</title>
|
||||
<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css\">
|
||||
<style>
|
||||
{THEMES[theme]}
|
||||
</style>
|
||||
<script src=\"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js\"></script>
|
||||
<script>hljs.highlightAll();</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class=\"container\">
|
||||
{content}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
REPO_ROOT = os.path.abspath(".")
|
||||
LOG_FILE = os.path.join(REPO_ROOT, "gitlog.jsonl")
|
||||
|
||||
PORT = 8000
|
||||
|
||||
def format_diff_to_html(diff_text: str) -> str:
|
||||
lines = diff_text.strip().splitlines()
|
||||
html_lines = ['<div style="font-family: monospace; white-space: pre;">']
|
||||
while not lines[3].startswith('diff'):
|
||||
lines.pop(3)
|
||||
lines.insert(3, "")
|
||||
for line in lines:
|
||||
escaped = html.escape(line)
|
||||
if "//" in line:
|
||||
continue
|
||||
if "#" in line:
|
||||
continue
|
||||
if "/*" in line:
|
||||
continue
|
||||
if "*/" in line:
|
||||
continue
|
||||
if line.startswith('+++') or line.startswith('---'):
|
||||
html_lines.append(f'<div style="color: #0000aa;">{escaped}</div>')
|
||||
elif line.startswith('@@'):
|
||||
html_lines.append(f'<div style="background-color: #eaf5ff; color: #005cc5;">{escaped}</div>')
|
||||
elif line.startswith('+'):
|
||||
html_lines.append(f'<div style="background-color: #e6ffed; color: #22863a;">{escaped}</div>')
|
||||
elif line.startswith('-'):
|
||||
html_lines.append(f'<div style="background-color: #ffeef0; color: #b31d28;">{escaped}</div>')
|
||||
elif line.startswith('\\'):
|
||||
html_lines.append(f'<div style="color: #6a737d;">{escaped}</div>')
|
||||
else:
|
||||
html_lines.append(f'<div>{escaped}</div>')
|
||||
html_lines.append('</div>')
|
||||
return '\n'.join(html_lines)
|
||||
|
||||
def parse_logs():
|
||||
logs = []
|
||||
if not os.path.exists(LOG_FILE):
|
||||
return []
|
||||
lines = []
|
||||
with open(LOG_FILE, "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
if line.strip():
|
||||
if line.strip() not in lines:
|
||||
lines.append(line.strip())
|
||||
logs.append(json.loads(line.strip()))
|
||||
return logs
|
||||
|
||||
def group_by_date(logs):
|
||||
grouped = {}
|
||||
for entry in logs:
|
||||
date = entry["date"]
|
||||
grouped.setdefault(date, []).append(entry)
|
||||
return dict(sorted(grouped.items(), reverse=True))
|
||||
|
||||
def get_git_diff(commit_hash):
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "-C", REPO_ROOT, "show", commit_hash, "--no-color"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
return result.stdout
|
||||
except subprocess.CalledProcessError as e:
|
||||
return f"Error retrieving diff: {e.stderr}"
|
||||
|
||||
def list_directory(path, base_url="/browse?path="):
|
||||
try:
|
||||
entries = os.listdir(path)
|
||||
except OSError:
|
||||
return "<div>Cannot access directory.</div>"
|
||||
|
||||
entries.sort()
|
||||
content = "<ul>"
|
||||
# Parent directory link
|
||||
parent = os.path.dirname(path)
|
||||
if os.path.abspath(path) != REPO_ROOT:
|
||||
parent_rel = os.path.relpath(parent, REPO_ROOT)
|
||||
content += f"<li><a href='{base_url}{html.escape(parent_rel)}'>.. (parent directory)</a></li>"
|
||||
|
||||
for entry in entries:
|
||||
full_path = os.path.join(path, entry)
|
||||
rel_path = os.path.relpath(full_path, REPO_ROOT)
|
||||
if os.path.isdir(full_path):
|
||||
content += f"<li>📁 <a href='{base_url}{html.escape(rel_path)}'>{html.escape(entry)}/</a></li>"
|
||||
else:
|
||||
content += f"<li>📄 <a href='{base_url}{html.escape(rel_path)}'>{html.escape(entry)}</a></li>"
|
||||
content += "</ul>"
|
||||
return content
|
||||
|
||||
def read_file_content(path):
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return f.read()
|
||||
except Exception as e:
|
||||
return f"Error reading file: {e}"
|
||||
|
||||
def get_language_class(filename):
|
||||
ext = os.path.splitext(filename)[1].lower()
|
||||
return {
|
||||
'.py': 'python',
|
||||
'.js': 'javascript',
|
||||
'.html': 'html',
|
||||
'.css': 'css',
|
||||
'.json': 'json',
|
||||
'.sh': 'bash',
|
||||
'.md': 'markdown',
|
||||
'.c': 'c',
|
||||
'.cpp': 'cpp',
|
||||
'.h': 'cpp',
|
||||
'.java': 'java',
|
||||
'.rb': 'ruby',
|
||||
'.go': 'go',
|
||||
'.php': 'php',
|
||||
'.rs': 'rust',
|
||||
'.ts': 'typescript',
|
||||
'.xml': 'xml',
|
||||
'.yml': 'yaml',
|
||||
'.yaml': 'yaml',
|
||||
}.get(ext, '')
|
||||
|
||||
class GitLogHandler(http.server.SimpleHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
parsed = urlparse(self.path)
|
||||
if parsed.path == "/":
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "text/html")
|
||||
self.end_headers()
|
||||
logs = parse_logs()
|
||||
grouped = group_by_date(logs)
|
||||
content = "<p><a href='/browse'>Browse Files</a></p>"
|
||||
for date, commits in grouped.items():
|
||||
content += f"<div class='date-header'>{date}</div>"
|
||||
for c in commits:
|
||||
commit_link = f"/diff?hash={c['commit']}"
|
||||
content += f"""
|
||||
<div class='commit'>
|
||||
<div><strong>{c['line'].splitlines()[0]}</strong></div>
|
||||
<div class='hash'><a href='{commit_link}'>{c['commit']}</a></div>
|
||||
</div>
|
||||
"""
|
||||
self.wfile.write(HTML_TEMPLATE(content).encode("utf-8"))
|
||||
elif parsed.path == "/diff":
|
||||
qs = parse_qs(parsed.query)
|
||||
commit = qs.get("hash", [""])[0]
|
||||
diff = format_diff_to_html(get_git_diff(html.escape(commit)))
|
||||
diff_html = f"<h2>Commit: {commit}</h2><div class='diff'>{diff}</div><p><a href='/'>← Back to commits</a></p>"
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "text/html")
|
||||
self.end_headers()
|
||||
self.wfile.write(HTML_TEMPLATE(diff_html).encode("utf-8"))
|
||||
elif parsed.path == "/browse":
|
||||
qs = parse_qs(parsed.query)
|
||||
rel_path = qs.get("path", [""])[0]
|
||||
abs_path = os.path.abspath(os.path.join(REPO_ROOT, rel_path))
|
||||
# Security: prevent escaping the repo root
|
||||
if not abs_path.startswith(REPO_ROOT):
|
||||
self.send_error(403, "Forbidden")
|
||||
return
|
||||
|
||||
if os.path.isdir(abs_path):
|
||||
content = f"<h2>Browsing: /{html.escape(rel_path)}</h2>"
|
||||
content += list_directory(abs_path)
|
||||
content += "<p><a href='/'>← Back to commits</a></p>"
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "text/html")
|
||||
self.end_headers()
|
||||
self.wfile.write(HTML_TEMPLATE(content).encode("utf-8"))
|
||||
elif os.path.isfile(abs_path):
|
||||
file_content = read_file_content(abs_path)
|
||||
lang_class = get_language_class(abs_path)
|
||||
content = f"<h2>File: /{html.escape(rel_path)}</h2>"
|
||||
content += (
|
||||
f"<pre style='background:#f1f1f1; padding:1em; border-radius:4px; overflow-x:auto;'>"
|
||||
f"<code class='{lang_class}'>{html.escape(file_content)}</code></pre>"
|
||||
)
|
||||
content += "<p><a href='{}'>← Back to directory</a></p>".format(
|
||||
f"/browse?path={html.escape(os.path.dirname(rel_path))}"
|
||||
)
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "text/html")
|
||||
self.end_headers()
|
||||
self.wfile.write(HTML_TEMPLATE(content).encode("utf-8"))
|
||||
else:
|
||||
self.send_error(404, "Not found")
|
||||
else:
|
||||
self.send_error(404)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
while True:
|
||||
try:
|
||||
with socketserver.TCPServer(("", PORT), GitLogHandler) as httpd:
|
||||
print(f"Serving at http://localhost:{PORT}")
|
||||
httpd.serve_forever()
|
||||
break
|
||||
except Exception as ex:
|
||||
print(ex)
|
||||
PORT += 1
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user