@@ -0,0 +1,371 @@
1 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """JSON API endpoints for programmatic access to Fossil repositories.
2 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
3 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All endpoints live under /projects/<slug>/fossil/api/.
4 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Auth: Bearer token (APIToken or PersonalAccessToken) or session cookie.
5 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All responses are JSON. All read endpoints check can_read_project.
6 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """
7 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
8 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import math
9 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
10 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from djan
11 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.shortcuts import getviews.decorators.csrf import csrf_exempt
12 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.views.decorators.http import require_GET
13 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
14 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.api_auth import authenticate_request
15 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.models import FossilRepository
16 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.reader import FossilReader
17 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from projects.access import can_ort can_admindpoints fo"""J {
18 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "GET",
19 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/tickets",
20 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Ticket list (paginated, filterable)",
21 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "params": "page, per_page, status",
22 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
23 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/tickets/<uuid>", "description": "Single ticket detail with comments"},
24 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/wiki", "description": "Wiki page list"},
25 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/wiki/<name>", "description": "Single wiki page with content"},
26 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/branches", "description": "Branch list"},
27 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/tags", "description": "Tag list"},
28 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/releases", "description": "Release list"},
29 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/search", "description": "Search across checkins, tickets, wiki", "params": "q"},
30 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
31 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "POST",
32 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/batch",
33 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Execute multiple API calls in a single request (max 25)",
34 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "body": '{"requests": [{"method": "GET", "path": "/api/timeline", "params": {}}]}',
35 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
36 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/workspaces", "description": "List agent workspaces", "params": "status"},
37 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
38 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "POST",
39 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/workspaces/create",
40 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Create an isolated agent workspace",
41 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "body": '{"name": "...", "description": "...", "agent_id": "..."}',
42 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
43 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/workspaces/<name>", "description": "Get workspace details"},
44 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
45 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "POST",
46 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/workspaces/<name>/commit",
47 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Commit changes in a workspace",
48 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "body": '{"message": "...", "files": []}',
49 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
50 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
51 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "POST",
52 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/workspaces/<name>/merge",
53 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Merge workspace branch back to trunk",
54 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "body": '{"target_branch": "trunk"}',
55 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
56 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
57 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "DELETE",
58 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/workspaces/<name>/abandon",
59 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Abandon and clean up a workspace",
60 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
61 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
62 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "POST",
63 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/tickets/<uuid>/claim",
64 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Claim a ticket for exclusive agent work",
65 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "body": '{"agent_id": "...", "workspace": "..."}',
66 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
67 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
68 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "POST",
69 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/tickets/<uuid>/release",
70 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Release a ticket claim",
71 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
72 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
73 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "POST",
74 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/tickets/<uuid>/submit",
75 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Submit completed work for a claimed ticket",
76 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "body": '{"summary": "...", "files_changed": [...]}',
77 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
78 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
79 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "GET",
80 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/tickets/unclaimed",
81 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "List tickets not claimed by any agent",
82 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "params": "status, limit",
83 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
84 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/events", "description": "Server-Sent Events stream for real-time events"},
85 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
86 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "POST",
87 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/reviews/create",
88 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Submit code changes for review",
89 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "body": '{"title": "...", "diff": "...", "files_changed": [...], "agent_id": "..."}',
90 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
91 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
92 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "GET",
93 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/reviews",
94 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "List code reviews",
95 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "params": "status, page, per_page",
96 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
97 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/reviews/<id>", "description": "Get review with comments"},
98 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
99 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "POST",
100 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/reviews/<id>/comment",
101 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Add a comment to a review",
102 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "body": '{"body": "...", "file_path": "...", "line_number": 42, "author": "..."}',
103 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
104 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "POST", "path": f"{base}/reviews/<id>/approve", "description": "Approve a review"},
105 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "POST", "path": f"{base}/reviews/<id>/request-changes", "description": "Request changes on a review"},
106 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "POST", "path": f"{base}/reviews/<id>/merge", "description": "Merge an approved review"},
107 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ],
108 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "auth": "Bearer token (Authorization: Bearer <token>) or session cookie",
109 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
110 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
111 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
112 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
113 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # --- Project Metadata ---
114 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
115 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
116 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @csrf_exempt
117 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @require_GET
118 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def api_project(request, slug):
119 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Return project metadata as JSON."""
120 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project, repo = _get_repo(slug)
121 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ user, token, err = _check_api_auth(request, project, repo)
122 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err is not None:
123 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return err
124 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
125 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse(
126 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
127 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "name": project.name,
128 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "slug": project.slug,
129 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": project.description or "",
130 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "visibility": project.visibility,
131 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "star_count": project.star_count,
132 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
133 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
134 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
135 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
136 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # --- Timeline ---
137 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
138 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
139 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @csrf_exempt
140 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @require_GET
141 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def api_timeline(request, slug):
142 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Return recent checkins as JSON, paginated."""
143 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project, repo = _get_repo(slug)
144 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ user, token, err = _check_api_auth(request, project, repo)
145 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err is not None:
146 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return err
147 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
148 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ page, per_page = _paginate_params(request)
149 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ branch_filter = request.GET.get("branch", "").strip()
150 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ offset = (page - 1) * per_page
151 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
152 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ reader = FossilReader(repo.full_path)
153 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ with reader:
154 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ entries = reader.get_timeline(limit=per_page, offset=offset, event_type="ci")
155 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ total = reader.get_checkin_count()
156 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
157 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ checkins = []
158 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for e in entries:
159 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ entry = {
160 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "uuid": e.uuid,
161 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "timestamp": _isoformat(e.timestamp),
162 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "user": e.user,
163 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "comment": e.comment,
164 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "branch": e.branch,
165 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
166 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ checkins.append(entry)
167 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
168 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # If branch filter is set, filter in Python (Fossil's timeline query
169 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # doesn't support branch filtering at the SQL level without extra joins).
170 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if branch_filter:
171 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ checkins = [c for c in checkins if c["branch"] == branch_filter]
172 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
173 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ total_pages = max(1, math.ceil(total / per_page))
174 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
175 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse(
176 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
177 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "checkins": checkins,
178 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "total": total,
179 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "page": page,
180 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "per_page": per_page,
181 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "total_pages": total_pages,
182 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
183 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
184 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
185 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
186 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # --- Tickets ---
187 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
188 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
189 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @csrf_exempt
190 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @require_GET
191 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def api_tickets(request, slug):
192 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Return ticket list as JSON, paginated and filterable by status."""
193 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project, repo = _get_repo(slug)
194 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ user, token, err = _check_api_auth(request, project, repo)
195 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err is not None:
196 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return err
197 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
198 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ page, per_page = _paginate_params(request)
199 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ status_filter = request.GET.get("status", "").strip() or Noethod": "GET", "pogger(__name__)
200 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
201 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
202 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _get_repo(slug):
203 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Look up project and repository by slug, or return 404 JSON."""
204 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project = get_object_or_404(Project, slug=slug, deleted_at__isnull=True)
205 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ repo = get_object_or_404(FossilRepository, project=project, deleted_at__isnull=True)
206 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return project, repo
207 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
208 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
209 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _check_api_api_auth(requeread acces "write" — enforced on both API tokens and PAT scopes.
210 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
211 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Returns (user, token, error_response). If error_response is not None,
212 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ the caller should return it immediately.
213 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """
214 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ user, tokepoints for pro"""JSON API endpoints for programmatic access to Fossil repositories.
215 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
216 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All endpointsalreadyuser), the token itself gr. StreamingHttpResy checked.
217 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if token is not None and user is None:
218 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return user, token, None
219 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
220 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # For user-scoped auth (PAT or session), check project visibil andd"}, status=403)
221 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ and not can_writAccess denid.
222 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if token is not Non.views.decorators.http import require_GET
223 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
224 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.api_auth import authenticate_request
225 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.models import FossilRepository
226 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.reader import FossilReader
227 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from projects.access import can_ort can_admin_project, can_read_project, can_write_project
228 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from projects.models import Project
229 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
230 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ logger = logging.getLogger(__name__)
231 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
232 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
233 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _get_repo(slug):
234 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Look up project and repository by slug, or return 404 JSON."""
235 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project = get_object_or_404(Project, slug=slug, deleted_at__isnull=True)
236 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ repo = get_object_or_404(FossilRepository, project=project, deleted_at__isn"path": f"{base}/tickets/unclaimed",
237 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "List tickets not claimed by any agent",
238 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "params": "status, limit",
239 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
240 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/events", "description": "Server-Sent Events stream for real-time events"},
241 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
242 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "POST",
243 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/reviews/create",
244 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Submit code changes for review",
245 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "body": '{"title": "...", "diff": "...", "files_changed": [...], "agent_id": "..."}',
246 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
247 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
248 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "GET",
249 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/reviews",
250 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "List code reviews",
251 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "params": "status, page, per_page",
252 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
253 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/reviews/<id>", "description": "Get review with comments"},
254 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
255 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "POST",
256 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/reviews/<id>/comment",
257 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Add a comment to a review",
258 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "body": '{"body": "...", "file_path": "...", "line_number": 42, "author": "..."}',
259 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
260 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "POST", "path": f"{base}/reviews/<id>/approve", "description": "Approve a review"},
261 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "POST", "path": f"{base}/reviews/<id>/request-changes", "description": "Request changes on a review"},
262 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "POST", "path": f"{base}/reviews/<id>/merge", "description": "Merge an approved review"},
263 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ],
264 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "auth": "Bearer token (Authorization: Bearer <token>) or session cookie",
265 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
266 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
267 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
268 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
269 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # --- Project Metadata ---
270 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
271 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
272 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @csrf_exempt
273 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @require_GET
274 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def api_project(request, slug):
275 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Return project metadata as JSON."""
276 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project, repo = _get_repo(slug)
277 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ user, token, err = _check_api_auth(request, project, repo)
278 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err is not None:
279 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return err
280 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
281 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse(
282 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
283 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "name": project.name,
284 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "slug": project.slug,
285 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": project.description or "",
286 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "visibility": project.visibility,
287 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "star_count": project.star_count,
288 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
289 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
290 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
291 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
292 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # --- Timeline ---
293 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
294 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
295 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @csrf_exempt
296 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @require_GET
297 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def api_timeline(request, slug):
298 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Return recent checkins as JSON, paginated."""
299 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project, repo = _get_repo(slug)
300 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ user, token, err = _check_api_auth(request, project, repo)
301 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err is not None:
302 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return err
303 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
304 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ page, per_page = _paginate_params(request)
305 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ branch_filter = request.GET.get("branch", "").strip()
306 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ offset = (page - 1) * per_page
307 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
308 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ reader = FossilReader(repo.full_path)
309 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ with reader:
310 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ entries = reader.get_timeline(limit=per_page, offset=offset, event_type="ci")
311 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ total = reader.get_checkin_count()
312 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
313 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ checkins = []
314 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for e in entries:
315 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ entry = {
316 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "uuid": e.uuid,
317 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "timestamp": _isoformat(e.timestamp),
318 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "user": e.user,
319 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "comment": e.comment,
320 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "branch": e.branch,
321 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
322 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ checkins.append(entry)
323 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
324 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # If branch filter is set, filter in Python (Fossil's timeline query
325 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # doesn't support branch filtering at the SQL level without extra joins).
326 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if branch_filter:
327 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ checkins = [c for c in checkins if c["branch"] == branch_filter]
328 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
329 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ total_pages = max(1, math.ceil(total / per_page))
330 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
331 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse(
332 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
333 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "checkins": checkins,
334 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "total": total,
335 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "page": page,
336 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "per_page": per_page,
337 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "total_pages": total_pages,
338 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
339 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
340 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
341 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
342 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # --- Tickets ---
343 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
344 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
345 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @csrf_exempt
346 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @require_GET
347 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def api_tickets(request, slug):
348 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Return ticket list as JSON, paginated and filterable by status."""
349 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project, repo = _get_repo(slug)
350 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ user, token, err = _check_api_auth(request, project, repo)
351 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err is not None:
352 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return err
353 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
354 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ page, per_page = _paginate_params(request)
355 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ status_filter = request.GET.get("status", "").strip() or None
356 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
357 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ reader = FossilReader(repo.full_path)
358 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ with reader:
359 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ all_tickets = reader.get_tickets(status=status_filter, limit=1000)
360 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
361 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ total = len(all_tickets)
362 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ total_pages = max(1, math.ceil(total / per_page))
363 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ page = min(page, total_pages)
364 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ page_tickets = all_tickets[(page - 1) * per_page : page * per_page]
365 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
366 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ tickets = []
367 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for t in page_tickets:
368 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ tickets.append(
369 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
370 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "uuid": t.uuid,
371 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "title": t.tit