vextoly/gitman
Fork of patx/gitman.
Order profile repositories by latest update across push transports
Commit 44519f2 · patx · 2026-11-02T22:15:46-05:00
Comments
No comments yet.
Diff
diff --git a/app.py b/app.py
index fc0e6a9..357ddc2 100644
--- a/app.py
+++ b/app.py
@@ -10,6 +10,7 @@ import mimetypes
import os
import re
import secrets
+import shlex
import shutil
import sqlite3
import subprocess
@@ -1588,7 +1589,7 @@ def list_owned_repos(owner_id, limit=None, offset=0):
LEFT JOIN repo_stars ON repo_stars.repo_id = repositories.id
WHERE repositories.owner_id = ?
GROUP BY repositories.id
- ORDER BY repositories.updated_at DESC
+ ORDER BY repositories.updated_at DESC, repositories.id DESC
{pagination}
""",
params,
@@ -1625,7 +1626,7 @@ def list_starred_repos(user_id, limit=None, offset=0):
JOIN repositories ON repositories.id = repo_stars.repo_id
JOIN users ON users.id = repositories.owner_id
WHERE repo_stars.user_id = ?
- ORDER BY repo_stars.created_at DESC
+ ORDER BY repositories.updated_at DESC, repositories.id DESC
{pagination}
""",
params,
@@ -1747,7 +1748,10 @@ def install_repo_hooks(path):
hooks_dir = path / "hooks"
hooks_dir.mkdir(exist_ok=True)
post_receive = hooks_dir / "post-receive"
- post_receive.write_text(POST_RECEIVE_HOOK, encoding="utf-8")
+ activity_command = " ".join(shlex.quote(str(value)) for value in (
+ sys.executable, BASE_DIR / "repo_activity.py", DB_PATH.resolve(), path.parent.name, path.name,
+ ))
+ post_receive.write_text(POST_RECEIVE_HOOK.replace("#!/bin/sh\n", "#!/bin/sh\n" + activity_command + "\n", 1), encoding="utf-8")
post_receive.chmod(0o755)
install_quota_hook(path, MAX_REPO_BYTES)
diff --git a/repo_activity.py b/repo_activity.py
new file mode 100644
index 0000000..bbcc55e
--- /dev/null
+++ b/repo_activity.py
@@ -0,0 +1,22 @@
+"""Record accepted pushes, including those outside the HTTP application."""
+import datetime
+from pathlib import Path
+import sqlite3
+import sys
+
+
+def main():
+ database, owner, name = sys.argv[1:]
+ if not any(line.strip() for line in sys.stdin):
+ return
+ now = datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
+ # Never create a new database if a deployment points at a missing store.
+ with sqlite3.connect(Path(database).as_uri() + "?mode=rw", uri=True, timeout=30) as connection:
+ connection.execute(
+ "UPDATE repositories SET updated_at=? WHERE name=? AND owner_id=(SELECT id FROM users WHERE username=?)",
+ (now, name, owner),
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tests/test_app.py b/tests/test_app.py
index ed8d206..53275e6 100644
--- a/tests/test_app.py
+++ b/tests/test_app.py
@@ -1622,7 +1622,7 @@ def test_profile_repositories_paginate_owned_and_starred_tabs(isolated_app, monk
conn.execute("UPDATE repositories SET updated_at = ? WHERE id = ?", (timestamp, repo["id"]))
conn.execute(
"INSERT INTO repo_stars (repo_id, user_id, created_at) VALUES (?, ?, ?)",
- (repo["id"], viewer["id"], timestamp),
+ (repo["id"], viewer["id"], f"2030-01-01T00:00:0{2-index}Z"),
)
client = WsgiClient(isolated_app.app)
@@ -2764,3 +2764,18 @@ def test_session_persists_fourteen_days_but_expires_on_server(isolated_app, monk
clock.setattr(gitman.time, 'time', lambda: issued + 14 * 86400 + 1)
# Replay the signed cookie even after browser expiry: server rejects it.
assert client.get('/settings/profile').status_code == 303
+
+
+def test_direct_push_moves_repository_to_top_of_profile(isolated_app):
+ owner = create_user('alice')
+ isolated_app.create_repository(owner, 'older', '')
+ isolated_app.create_repository(owner, 'newer', '')
+ with isolated_app.db_connect() as conn:
+ conn.execute("UPDATE repositories SET updated_at='2000-01-01T00:00:00Z' WHERE name='older'")
+ conn.execute("UPDATE repositories SET updated_at='2001-01-01T00:00:00Z' WHERE name='newer'")
+ client = WsgiClient(isolated_app.app)
+ before = client.get('/alice').text
+ assert before.index('<strong>alice/newer') < before.index('<strong>alice/older')
+ commit_file(isolated_app.repo_path('alice', 'older'), 'README.md', 'New work')
+ after = client.get('/alice').text
+ assert after.index('<strong>alice/older') < after.index('<strong>alice/newer')