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