FossilRepo

Fix ticket body, clickable stats, sort orders, markdown rendering - Ticket body: fall back to ticketchng.icomment when comment is empty (newer Fossil stores descriptions in change records) - Ticket body rendered as markdown/HTML via _render_fossil_content - Contributor stats are now clickable links to respective sections - Wiki pages sorted by most recently updated (not alphabetical) - Forum already sorted by newest (fmtime DESC)

lmata 2026-04-06 15:23 trunk
Commit 11cb22eea564355189f5901e22cc8d4c9bf9d1f86239e27dc0a1914cc58c58b4
+18 -3
--- fossil/reader.py
+++ fossil/reader.py
@@ -615,16 +615,31 @@
615615
return entries
616616
617617
def get_ticket_detail(self, uuid: str) -> TicketEntry | None:
618618
try:
619619
row = self.conn.execute(
620
- "SELECT tkt_uuid, title, status, type, tkt_ctime, subsystem, priority, severity, resolution, comment "
620
+ "SELECT tkt_id, tkt_uuid, title, status, type, tkt_ctime, subsystem, priority, severity, resolution, comment "
621621
"FROM ticket WHERE tkt_uuid LIKE ?",
622622
(uuid + "%",),
623623
).fetchone()
624624
if not row:
625625
return None
626
+
627
+ body = row["comment"] or ""
628
+
629
+ # If comment is empty, try ticketchng.icomment (newer Fossil stores descriptions there)
630
+ if not body:
631
+ try:
632
+ chng = self.conn.execute(
633
+ "SELECT icomment, login FROM ticketchng WHERE tkt_id=? ORDER BY tkt_mtime ASC LIMIT 1",
634
+ (row["tkt_id"],),
635
+ ).fetchone()
636
+ if chng and chng["icomment"]:
637
+ body = chng["icomment"]
638
+ except sqlite3.OperationalError:
639
+ pass
640
+
626641
return TicketEntry(
627642
uuid=row["tkt_uuid"],
628643
title=row["title"] or "",
629644
status=row["status"] or "",
630645
type=row["type"] or "",
@@ -632,11 +647,11 @@
632647
owner="",
633648
subsystem=row["subsystem"] or "",
634649
priority=row["priority"] or "",
635650
severity=row["severity"] or "",
636651
resolution=row["resolution"] or "",
637
- body=row["comment"] or "",
652
+ body=body,
638653
)
639654
except sqlite3.OperationalError:
640655
return None
641656
642657
# --- Wiki ---
@@ -651,11 +666,11 @@
651666
JOIN tagxref ON tag.tagid = tagxref.tagid
652667
JOIN event ON tagxref.rid = event.objid
653668
WHERE tag.tagname LIKE 'wiki-%' AND event.type = 'w'
654669
GROUP BY tag.tagname
655670
HAVING event.mtime = MAX(event.mtime)
656
- ORDER BY name
671
+ ORDER BY event.mtime DESC
657672
"""
658673
).fetchall()
659674
for row in rows:
660675
pages.append(
661676
WikiPage(
662677
--- fossil/reader.py
+++ fossil/reader.py
@@ -615,16 +615,31 @@
615 return entries
616
617 def get_ticket_detail(self, uuid: str) -> TicketEntry | None:
618 try:
619 row = self.conn.execute(
620 "SELECT tkt_uuid, title, status, type, tkt_ctime, subsystem, priority, severity, resolution, comment "
621 "FROM ticket WHERE tkt_uuid LIKE ?",
622 (uuid + "%",),
623 ).fetchone()
624 if not row:
625 return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
626 return TicketEntry(
627 uuid=row["tkt_uuid"],
628 title=row["title"] or "",
629 status=row["status"] or "",
630 type=row["type"] or "",
@@ -632,11 +647,11 @@
632 owner="",
633 subsystem=row["subsystem"] or "",
634 priority=row["priority"] or "",
635 severity=row["severity"] or "",
636 resolution=row["resolution"] or "",
637 body=row["comment"] or "",
638 )
639 except sqlite3.OperationalError:
640 return None
641
642 # --- Wiki ---
@@ -651,11 +666,11 @@
651 JOIN tagxref ON tag.tagid = tagxref.tagid
652 JOIN event ON tagxref.rid = event.objid
653 WHERE tag.tagname LIKE 'wiki-%' AND event.type = 'w'
654 GROUP BY tag.tagname
655 HAVING event.mtime = MAX(event.mtime)
656 ORDER BY name
657 """
658 ).fetchall()
659 for row in rows:
660 pages.append(
661 WikiPage(
662
--- fossil/reader.py
+++ fossil/reader.py
@@ -615,16 +615,31 @@
615 return entries
616
617 def get_ticket_detail(self, uuid: str) -> TicketEntry | None:
618 try:
619 row = self.conn.execute(
620 "SELECT tkt_id, tkt_uuid, title, status, type, tkt_ctime, subsystem, priority, severity, resolution, comment "
621 "FROM ticket WHERE tkt_uuid LIKE ?",
622 (uuid + "%",),
623 ).fetchone()
624 if not row:
625 return None
626
627 body = row["comment"] or ""
628
629 # If comment is empty, try ticketchng.icomment (newer Fossil stores descriptions there)
630 if not body:
631 try:
632 chng = self.conn.execute(
633 "SELECT icomment, login FROM ticketchng WHERE tkt_id=? ORDER BY tkt_mtime ASC LIMIT 1",
634 (row["tkt_id"],),
635 ).fetchone()
636 if chng and chng["icomment"]:
637 body = chng["icomment"]
638 except sqlite3.OperationalError:
639 pass
640
641 return TicketEntry(
642 uuid=row["tkt_uuid"],
643 title=row["title"] or "",
644 status=row["status"] or "",
645 type=row["type"] or "",
@@ -632,11 +647,11 @@
647 owner="",
648 subsystem=row["subsystem"] or "",
649 priority=row["priority"] or "",
650 severity=row["severity"] or "",
651 resolution=row["resolution"] or "",
652 body=body,
653 )
654 except sqlite3.OperationalError:
655 return None
656
657 # --- Wiki ---
@@ -651,11 +666,11 @@
666 JOIN tagxref ON tag.tagid = tagxref.tagid
667 JOIN event ON tagxref.rid = event.objid
668 WHERE tag.tagname LIKE 'wiki-%' AND event.type = 'w'
669 GROUP BY tag.tagname
670 HAVING event.mtime = MAX(event.mtime)
671 ORDER BY event.mtime DESC
672 """
673 ).fetchall()
674 for row in rows:
675 pages.append(
676 WikiPage(
677
--- fossil/views.py
+++ fossil/views.py
@@ -442,17 +442,20 @@
442442
ticket = reader.get_ticket_detail(ticket_uuid)
443443
444444
if not ticket:
445445
raise Http404("Ticket not found")
446446
447
+ body_html = mark_safe(_render_fossil_content(ticket.body, project_slug=slug)) if ticket.body else ""
448
+
447449
return render(
448450
request,
449451
"fossil/ticket_detail.html",
450452
{
451453
"project": project,
452454
"fossil_repo": fossil_repo,
453455
"ticket": ticket,
456
+ "body_html": body_html,
454457
"active_tab": "tickets",
455458
},
456459
)
457460
458461
459462
--- fossil/views.py
+++ fossil/views.py
@@ -442,17 +442,20 @@
442 ticket = reader.get_ticket_detail(ticket_uuid)
443
444 if not ticket:
445 raise Http404("Ticket not found")
446
 
 
447 return render(
448 request,
449 "fossil/ticket_detail.html",
450 {
451 "project": project,
452 "fossil_repo": fossil_repo,
453 "ticket": ticket,
 
454 "active_tab": "tickets",
455 },
456 )
457
458
459
--- fossil/views.py
+++ fossil/views.py
@@ -442,17 +442,20 @@
442 ticket = reader.get_ticket_detail(ticket_uuid)
443
444 if not ticket:
445 raise Http404("Ticket not found")
446
447 body_html = mark_safe(_render_fossil_content(ticket.body, project_slug=slug)) if ticket.body else ""
448
449 return render(
450 request,
451 "fossil/ticket_detail.html",
452 {
453 "project": project,
454 "fossil_repo": fossil_repo,
455 "ticket": ticket,
456 "body_html": body_html,
457 "active_tab": "tickets",
458 },
459 )
460
461
462
--- templates/fossil/ticket_detail.html
+++ templates/fossil/ticket_detail.html
@@ -56,14 +56,14 @@
5656
</div>
5757
</dl>
5858
</div>
5959
6060
<!-- Body/description -->
61
- {% if ticket.body %}
61
+ {% if body_html %}
6262
<div class="px-6 py-5">
6363
<div class="prose prose-invert prose-gray prose-sm max-w-none">
64
- {{ ticket.body|linebreaksbr }}
64
+ {{ body_html }}
6565
</div>
6666
</div>
6767
{% endif %}
6868
</div>
6969
{% endblock %}
7070
--- templates/fossil/ticket_detail.html
+++ templates/fossil/ticket_detail.html
@@ -56,14 +56,14 @@
56 </div>
57 </dl>
58 </div>
59
60 <!-- Body/description -->
61 {% if ticket.body %}
62 <div class="px-6 py-5">
63 <div class="prose prose-invert prose-gray prose-sm max-w-none">
64 {{ ticket.body|linebreaksbr }}
65 </div>
66 </div>
67 {% endif %}
68 </div>
69 {% endblock %}
70
--- templates/fossil/ticket_detail.html
+++ templates/fossil/ticket_detail.html
@@ -56,14 +56,14 @@
56 </div>
57 </dl>
58 </div>
59
60 <!-- Body/description -->
61 {% if body_html %}
62 <div class="px-6 py-5">
63 <div class="prose prose-invert prose-gray prose-sm max-w-none">
64 {{ body_html }}
65 </div>
66 </div>
67 {% endif %}
68 </div>
69 {% endblock %}
70
--- templates/fossil/user_activity.html
+++ templates/fossil/user_activity.html
@@ -40,39 +40,39 @@
4040
4141
<!-- Sidebar stats -->
4242
<div>
4343
<div class="rounded-lg bg-gray-800 border border-gray-700 p-4">
4444
<h3 class="text-sm font-medium text-gray-300 mb-3">Contributions</h3>
45
- <div class="space-y-2">
46
- <div class="flex items-center justify-between">
45
+ <div class="space-y-1">
46
+ <a href="{% url 'fossil:timeline' slug=project.slug %}" class="flex items-center justify-between rounded-md px-2 py-1.5 hover:bg-gray-700/50">
4747
<span class="flex items-center gap-2 text-sm text-gray-400">
4848
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
4949
Checkins
5050
</span>
5151
<span class="text-sm font-medium text-gray-200">{{ activity.checkin_count }}</span>
52
- </div>
53
- <div class="flex items-center justify-between">
52
+ </a>
53
+ <a href="{% url 'fossil:tickets' slug=project.slug %}" class="flex items-center justify-between rounded-md px-2 py-1.5 hover:bg-gray-700/50">
5454
<span class="flex items-center gap-2 text-sm text-gray-400">
5555
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M16.5 6v.75m0 3v.75m0 3v.75m0 3V18m-9-5.25h5.25M7.5 15h3M3.375 5.25c-.621 0-1.125.504-1.125 1.125v3.026a2.999 2.999 0 010 5.198v3.026c0 .621.504 1.125 1.125 1.125h17.25c.621 0 1.125-.504 1.125-1.125v-3.026a2.999 2.999 0 010-5.198V6.375c0-.621-.504-1.125-1.125-1.125H3.375z" /></svg>
5656
Ticket changes
5757
</span>
5858
<span class="text-sm font-medium text-gray-200">{{ activity.ticket_count }}</span>
59
- </div>
60
- <div class="flex items-center justify-between">
59
+ </a>
60
+ <a href="{% url 'fossil:wiki' slug=project.slug %}" class="flex items-center justify-between rounded-md px-2 py-1.5 hover:bg-gray-700/50">
6161
<span class="flex items-center gap-2 text-sm text-gray-400">
6262
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25" /></svg>
6363
Wiki edits
6464
</span>
6565
<span class="text-sm font-medium text-gray-200">{{ activity.wiki_count }}</span>
66
- </div>
67
- <div class="flex items-center justify-between">
66
+ </a>
67
+ <a href="{% url 'fossil:forum' slug=project.slug %}" class="flex items-center justify-between rounded-md px-2 py-1.5 hover:bg-gray-700/50">
6868
<span class="flex items-center gap-2 text-sm text-gray-400">
6969
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M7.5 8.25h9m-9 3H12m-9.75 1.51c0 1.6 1.123 2.994 2.707 3.227 1.129.166 2.27.293 3.423.379.35.026.67.21.865.501L12 21l2.755-4.133a1.14 1.14 0 01.865-.501 48.172 48.172 0 003.423-.379c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z" /></svg>
7070
Forum posts
7171
</span>
7272
<span class="text-sm font-medium text-gray-200">{{ activity.forum_count }}</span>
73
- </div>
73
+ </a>
7474
</div>
7575
</div>
7676
</div>
7777
</div>
7878
{% endblock %}
7979
--- templates/fossil/user_activity.html
+++ templates/fossil/user_activity.html
@@ -40,39 +40,39 @@
40
41 <!-- Sidebar stats -->
42 <div>
43 <div class="rounded-lg bg-gray-800 border border-gray-700 p-4">
44 <h3 class="text-sm font-medium text-gray-300 mb-3">Contributions</h3>
45 <div class="space-y-2">
46 <div class="flex items-center justify-between">
47 <span class="flex items-center gap-2 text-sm text-gray-400">
48 <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
49 Checkins
50 </span>
51 <span class="text-sm font-medium text-gray-200">{{ activity.checkin_count }}</span>
52 </div>
53 <div class="flex items-center justify-between">
54 <span class="flex items-center gap-2 text-sm text-gray-400">
55 <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M16.5 6v.75m0 3v.75m0 3v.75m0 3V18m-9-5.25h5.25M7.5 15h3M3.375 5.25c-.621 0-1.125.504-1.125 1.125v3.026a2.999 2.999 0 010 5.198v3.026c0 .621.504 1.125 1.125 1.125h17.25c.621 0 1.125-.504 1.125-1.125v-3.026a2.999 2.999 0 010-5.198V6.375c0-.621-.504-1.125-1.125-1.125H3.375z" /></svg>
56 Ticket changes
57 </span>
58 <span class="text-sm font-medium text-gray-200">{{ activity.ticket_count }}</span>
59 </div>
60 <div class="flex items-center justify-between">
61 <span class="flex items-center gap-2 text-sm text-gray-400">
62 <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25" /></svg>
63 Wiki edits
64 </span>
65 <span class="text-sm font-medium text-gray-200">{{ activity.wiki_count }}</span>
66 </div>
67 <div class="flex items-center justify-between">
68 <span class="flex items-center gap-2 text-sm text-gray-400">
69 <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M7.5 8.25h9m-9 3H12m-9.75 1.51c0 1.6 1.123 2.994 2.707 3.227 1.129.166 2.27.293 3.423.379.35.026.67.21.865.501L12 21l2.755-4.133a1.14 1.14 0 01.865-.501 48.172 48.172 0 003.423-.379c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z" /></svg>
70 Forum posts
71 </span>
72 <span class="text-sm font-medium text-gray-200">{{ activity.forum_count }}</span>
73 </div>
74 </div>
75 </div>
76 </div>
77 </div>
78 {% endblock %}
79
--- templates/fossil/user_activity.html
+++ templates/fossil/user_activity.html
@@ -40,39 +40,39 @@
40
41 <!-- Sidebar stats -->
42 <div>
43 <div class="rounded-lg bg-gray-800 border border-gray-700 p-4">
44 <h3 class="text-sm font-medium text-gray-300 mb-3">Contributions</h3>
45 <div class="space-y-1">
46 <a href="{% url 'fossil:timeline' slug=project.slug %}" class="flex items-center justify-between rounded-md px-2 py-1.5 hover:bg-gray-700/50">
47 <span class="flex items-center gap-2 text-sm text-gray-400">
48 <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
49 Checkins
50 </span>
51 <span class="text-sm font-medium text-gray-200">{{ activity.checkin_count }}</span>
52 </a>
53 <a href="{% url 'fossil:tickets' slug=project.slug %}" class="flex items-center justify-between rounded-md px-2 py-1.5 hover:bg-gray-700/50">
54 <span class="flex items-center gap-2 text-sm text-gray-400">
55 <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M16.5 6v.75m0 3v.75m0 3v.75m0 3V18m-9-5.25h5.25M7.5 15h3M3.375 5.25c-.621 0-1.125.504-1.125 1.125v3.026a2.999 2.999 0 010 5.198v3.026c0 .621.504 1.125 1.125 1.125h17.25c.621 0 1.125-.504 1.125-1.125v-3.026a2.999 2.999 0 010-5.198V6.375c0-.621-.504-1.125-1.125-1.125H3.375z" /></svg>
56 Ticket changes
57 </span>
58 <span class="text-sm font-medium text-gray-200">{{ activity.ticket_count }}</span>
59 </a>
60 <a href="{% url 'fossil:wiki' slug=project.slug %}" class="flex items-center justify-between rounded-md px-2 py-1.5 hover:bg-gray-700/50">
61 <span class="flex items-center gap-2 text-sm text-gray-400">
62 <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25" /></svg>
63 Wiki edits
64 </span>
65 <span class="text-sm font-medium text-gray-200">{{ activity.wiki_count }}</span>
66 </a>
67 <a href="{% url 'fossil:forum' slug=project.slug %}" class="flex items-center justify-between rounded-md px-2 py-1.5 hover:bg-gray-700/50">
68 <span class="flex items-center gap-2 text-sm text-gray-400">
69 <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M7.5 8.25h9m-9 3H12m-9.75 1.51c0 1.6 1.123 2.994 2.707 3.227 1.129.166 2.27.293 3.423.379.35.026.67.21.865.501L12 21l2.755-4.133a1.14 1.14 0 01.865-.501 48.172 48.172 0 003.423-.379c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z" /></svg>
70 Forum posts
71 </span>
72 <span class="text-sm font-medium text-gray-200">{{ activity.forum_count }}</span>
73 </a>
74 </div>
75 </div>
76 </div>
77 </div>
78 {% endblock %}
79

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button