@@ -0,0 +1,1761 @@
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 json
9 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import logging
10 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import math
11 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import re
12 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import shutil
13 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import subprocess
14 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import tempfile
15 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import time
16 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
17 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.db import transaction
18 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.http import JsonResponse, StreamingHttpResponse
19 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.shortcuts import get_object_or_404
20 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.test import RequestFactory
21 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.utils import timezone
22 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.views.decorators.csrf import csrf_exempt
23 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.views.decorators.http import require_GET
24 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
25 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.api_auth import authenticate_request
26 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.models import FossilRepository
27 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.reader import FossilReader
28 { 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
29 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from projects.models import Project
30 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
31 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ logger = logging.getLogger(__name__)
32 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
33 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
34 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _get_repo(slug):
35 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Look up project and repository by slug, or return 404 JSON."""
36 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project = get_object_or_404(Project, slug=slug, deleted_at__isnull=True)
37 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ repo = get_object_or_404(FossilRepository, project=project, deleted_at__isnull=True)
38 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return project, repo
39 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
40 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
41 { 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.
42 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
43 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Returns (user, token, error_response). If error_response is not None,
44 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ the caller should return it immediately.
45 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """
46 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ user, tokepoints for pro"""JSON API endpoints for programmatic access to Fossil repositories.
47 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
48 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All endpointsalreadyuser), the token itself gr. StreamingHttpResy checked.
49 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if token is not None and user is None:
50 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return user, token, None
51 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
52 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # For user-scoped auth (PAT or session), check project visibil andd"}, status=403)
53 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ and not can_writAccess denid.
54 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if token is not Non.views.decorators.http import require_GET
55 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
56 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.api_auth import authenticate_request
57 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.models import FossilRepository
58 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.reader import FossilReader
59 { 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
60 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from projects.models import Project
61 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
62 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ logger = logging.getLogger(__name__)
63 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
64 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
65 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _get_repo(slug):
66 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Look up project and repository by slug, or return 404 JSON."""
67 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project = get_object_or_404(Project, slug=slug, deleted_at__isnull=True)
68 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ repo = get_object_or_404(FossilRepository, project=project, deleted_at__isnull=True)
69 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return project, repo
70 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
71 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
72 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _check_api_auth(request, project, repo, required_scope="read"):
73 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Authenticate request and check access.
74 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
75 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Args:
76 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ required_scope: "read" or "write" — enforced on both API tokens and PAT scopes.
77 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
78 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Returns (user, token, error_response). If error_response is not None,
79 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ the caller should return it immediately.
80 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """
81 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ user, token, err = authenticate_request(request, repository=repo, required_scope=required_scope)
82 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err is not None:
83 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return None, None, err
84 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
85 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # For project-scoped APITokens (no user), the token itself grants access
86 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # since it's scoped to this repository and scope was already checked.
87 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if token is not None and user is None:
88 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return user, token, None
89 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
90 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # For user-scoped auth (PAT or session), check project visibility
91 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if user is not None:
92 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if required_scope == "write" and not can_write_project(user, project):
93 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return None, None, JsonResponse({"error": "Write access required"}, status=403)
94 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not can_read_project(user, project):
95 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return None, None, JsonResponse({"error": "Access denied"}, status=403)
96 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
97 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return user, token, None
98 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
99 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
100 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _paginate_params(request, default_per_page=25, max_per_page=100):
101 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Extract and validate page/per_page from query params."""
102 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ try:
103 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ page = max(1, int(request.GET.get("page", "1")))
104 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ except (ValueError, TypeError):
105 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ page = 1
106 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ try:
107 { 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)))))
108 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ except (ValueError, TypeError):
109 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ per_page = default_per_page
110 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return page, per_page
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!
+ def _isoformat(dt):
114 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Safely format a datetime to ISO 8601, or None."""
115 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if dt is None:
116 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return None
117 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return dt.isoformat()
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!
+ # --- API Documentation ---
121 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
122 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
123 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @csrf_exempt
124 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @require_GET
125 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def api_docs(request, slug):
126 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Return JSON listing all available API endpoints with descriptions."""
127 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ base = f"/projects/{slug}/fossil/api"
128 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse(
129 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
130 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "endpoints": [
131 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/project", "description": "Project metadata"},
132 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
133 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "GET",
134 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/timeline",
135 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Recent checkins (paginated)",
136 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "params": "page, per_page, branch",
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!
+ "method": "GET",
140 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/tickets",
141 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Ticket list (paginated, filterable)",
142 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "params": "page, per_page, status",
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", "path": f"{base}/tickets/<uuid>", "description": "Single ticket detail with comments"},
145 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/wiki", "description": "Wiki page list"},
146 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/wiki/<name>", "description": "Single wiki page with content"},
147 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/branches", "description": "Branch list"},
148 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/tags", "description": "Tag list"},
149 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/releases", "description": "Release list"},
150 { 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"},
151 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
152 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "POST",
153 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/batch",
154 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Execute multiple API calls in a single request (max 25)",
155 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "body": '{"requests": [{"method": "GET", "path": "/api/timeline", "params": {}}]}',
156 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
157 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/workspaces", "description": "List agent workspaces", "params": "status"},
158 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
159 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "POST",
160 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/workspaces/create",
161 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Create an isolated agent workspace",
162 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "body": '{"name": "...", "description": "...", "agent_id": "..."}',
163 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
164 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/workspaces/<name>", "description": "Get workspace details"},
165 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
166 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "POST",
167 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/workspaces/<name>/commit",
168 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Commit changes in a workspace",
169 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "body": '{"message": "...", "files": []}',
170 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
171 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
172 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "POST",
173 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/workspaces/<name>/merge",
174 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Merge workspace branch back to trunk",
175 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "body": '{"target_branch": "trunk"}',
176 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
177 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
178 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "DELETE",
179 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/workspaces/<name>/abandon",
180 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Abandon and clean up a workspace",
181 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
182 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
183 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "POST",
184 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/tickets/<uuid>/claim",
185 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Claim a ticket for exclusive agent work",
186 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "body": '{"agent_id": "...", "workspace": "..."}',
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!
+ "method": "POST",
190 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/tickets/<uuid>/release",
191 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Release a ticket claim",
192 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
193 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
194 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "POST",
195 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/tickets/<uuid>/submit",
196 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Submit completed work for a claimed ticket",
197 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "body": '{"summary": "...", "files_changed": [...]}',
198 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
199 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
200 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "GET",
201 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/tickets/unclaimed",
202 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "List tickets not claimed by any agent",
203 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "params": "status, limit",
204 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
205 { 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"},
206 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
207 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "POST",
208 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/reviews/create",
209 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Submit code changes for review",
210 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "body": '{"title": "...", "diff": "...", "files_changed": [...], "agent_id": "..."}',
211 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
212 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
213 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "GET",
214 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/reviews",
215 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "List code reviews",
216 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "params": "status, page, per_page",
217 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
218 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/reviews/<id>", "description": "Get review with comments"},
219 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
220 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "POST",
221 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/reviews/<id>/comment",
222 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Add a comment to a review",
223 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "body": '{"body": "...", "file_path": "...", "line_number": 42, "author": "..."}',
224 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
225 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "POST", "path": f"{base}/reviews/<id>/approve", "description": "Approve a review"},
226 { 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"},
227 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "POST", "path": f"{base}/reviews/<id>/merge", "description": "Merge an approved review"},
228 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ],
229 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "auth": "Bearer token (Authorization: Bearer <token>) or session cookie",
230 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
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!
+
234 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # --- Project Metadata ---
235 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
236 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
237 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @csrf_exempt
238 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @require_GET
239 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def api_project(request, slug):
240 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Return project metadata as JSON."""
241 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project, repo = _get_repo(slug)
242 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ user, token, err = _check_api_auth(request, project, repo)
243 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err is not None:
244 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return err
245 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
246 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse(
247 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
248 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "name": project.name,
249 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "slug": project.slug,
250 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": project.description or "",
251 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "visibility": project.visibility,
252 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "star_count": project.star_count,
253 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
254 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
255 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
256 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
257 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # --- Timeline ---
258 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
259 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
260 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @csrf_exempt
261 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @require_GET
262 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def api_timeline(request, slug):
263 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Return recent checkins as JSON, paginated."""
264 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project, repo = _get_repo(slug)
265 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ user, token, err = _check_api_auth(request, project, repo)
266 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err is not None:
267 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return err
268 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
269 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ page, per_page = _paginate_params(request)
270 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ branch_filter = request.GET.get("branch", "").strip()
271 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ offset = (page - 1) * per_page
272 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
273 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ reader = FossilReader(repo.full_path)
274 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ with reader:
275 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ entries = reader.get_timeline(limit=per_page, offset=offset, event_type="ci")
276 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ total = reader.get_checkin_count()
277 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
278 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ checkins = []
279 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for e in entries:
280 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ entry = {
281 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "uuid": e.uuid,
282 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "timestamp": _isoformat(e.timestamp),
283 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "user": e.user,
284 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "comment": e.comment,
285 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "branch": e.branch,
286 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
287 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ checkins.append(entry)
288 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
289 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # If branch filter is set, filter in Python (Fossil's timeline query
290 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # doesn't support branch filtering at the SQL level without extra joins).
291 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if branch_filter:
292 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ checkins = [c for c in checkins if c["branch"] == branch_filter]
293 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
294 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ total_pages = max(1, math.ceil(total / per_page))
295 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
296 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse(
297 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
298 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "checkins": checkins,
299 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "total": total,
300 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "page": page,
301 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "per_page": per_page,
302 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "total_pages": total_pages,
303 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
304 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
305 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
306 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
307 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # --- Tickets ---
308 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
309 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
310 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @csrf_exempt
311 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @require_GET
312 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def api_tickets(request, slug):
313 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Return ticket list as JSON, paginated and filterable by status."""
314 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project, repo = _get_repo(slug)
315 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ user, token, err = _check_api_auth(request, project, repo)
316 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err is not None:
317 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return err
318 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
319 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ page, per_page = _paginate_params(request)
320 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ status_filter = request.GET.get("status", "").strip() or None
321 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
322 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ reader = FossilReader(repo.full_path)
323 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ with reader:
324 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ all_tickets = reader.get_tickets(status=status_filter, limit=1000)
325 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
326 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ total = len(all_tickets)
327 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ total_pages = max(1, math.ceil(total / per_page))
328 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ page = min(page, total_pages)
329 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ page_tickets = all_tickets[(page - 1) * per_page : page * 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!
+ tickets = []
332 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for t in page_tickets:
333 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ tickets.append(
334 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
335 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "uuid": t.uuid,
336 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "title": t.title,
337 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "status": t.status,
338 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "type": t.type,
339 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "subsystem": t.subsystem,
340 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "priority": t.priority,
341 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "severity": t.severity,
342 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "created": _isoformat(t.created),
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!
+
346 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse(
347 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
348 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "tickets": tickets,
349 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "total": total,
350 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "page": page,
351 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "per_page": per_page,
352 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "total_pages": total_pages,
353 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
354 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
355 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
356 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
357 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @csrf_exempt
358 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @require_GET
359 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def api_ticket_detail(request, slug, ticket_uuid):
360 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Return a single ticket with its comments as JSON."""
361 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project, repo = _get_repo(slug)
362 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ user, token, err = _check_api_auth(request, project, repo)
363 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err is not None:
364 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return err
365 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
366 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ reader = FossilReader(repo.full_path)
367 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ with reader:
368 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ticket = reader.get_ticket_detail(ticket_uuid)
369 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if ticket is None:
370 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "Ticket not found"}, status=404)
371 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ comments = reader.get_ticket_comments(ticket_uuid)
372 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
373 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ comment_list = []
374 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for c in comments:
375 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ comment_list.append(
376 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
377 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "timestamp": _isoformat(c.get("timestamp")),
378 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "user": c.get("user", ""),
379 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "comment": c.get("comment", ""),
380 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "mimetype": c.get("mimetype", "text/plain"),
381 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
382 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
383 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
384 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse(
385 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
386 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "uuid": ticket.uuid,
387 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "title": ticket.title,
388 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "status": ticket.status,
389 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "type": ticket.type,
390 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "subsystem": ticket.subsystem,
391 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "priority": ticket.priority,
392 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "severity": ticket.severity,
393 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "resolution": ticket.resolution,
394 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "body": ticket.body,
395 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "created": _isoformat(ticket.created),
396 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "comments": comment_list,
397 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
398 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
399 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
400 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
401 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # --- Wiki ---
402 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
403 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
404 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @csrf_exempt
405 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @require_GET
406 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def api_wiki_list(request, slug):
407 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Return list of wiki pages as JSON."""
408 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project, repo = _get_repo(slug)
409 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ user, token, err = _check_api_auth(request, project, repo)
410 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err is not None:
411 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return err
412 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
413 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ reader = FossilReader(repo.full_path)
414 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ with reader:
415 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ pages = reader.get_wiki_pages()
416 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
417 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ page_list = []
418 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for p in pages:
419 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ page_list.append(
420 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
421 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "name": p.name,
422 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "last_modified": _isoformat(p.last_modified),
423 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "user": p.user,
424 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
425 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
426 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
427 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"pages": page_list})
428 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
429 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
430 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @csrf_exempt
431 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @require_GET
432 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def api_wiki_page(request, slug, page_name):
433 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Return a single wiki page with its content as JSON."""
434 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project, repo = _get_repo(slug)
435 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ user, token, err = _check_api_auth(request, project, repo)
436 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err is not None:
437 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return err
438 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
439 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ reader = FossilReader(repo.full_path)
440 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ with reader:
441 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ page = reader.get_wiki_page(page_name)
442 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
443 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if page is None:
444 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "Wiki page not found"}, status=404)
445 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
446 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Render content to HTML for convenience
447 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.views import _render_fossil_content
448 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
449 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ content_html = _render_fossil_content(page.content, project_slug=slug) if page.content else ""
450 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
451 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse(
452 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
453 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "name": page.name,
454 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "content": page.content,
455 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "content_html": content_html,
456 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "last_modified": _isoformat(page.last_modified),
457 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "user": page.user,
458 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
459 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
460 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
461 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
462 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # --- Branches ---
463 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
464 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
465 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @csrf_exempt
466 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @require_GET
467 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def api_branches(request, slug):
468 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Return list of branches as JSON."""
469 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project, repo = _get_repo(slug)
470 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ user, token, err = _check_api_auth(request, project, repo)
471 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err is not None:
472 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return err
473 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
474 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ reader = FossilReader(repo.full_path)
475 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ with reader:
476 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ branches = reader.get_branches()
477 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
478 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ branch_list = []
479 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for b in branches:
480 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ branch_list.append(
481 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
482 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "name": b["name"],
483 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "last_checkin": _isoformat(b["last_checkin"]),
484 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "last_user": b["last_user"],
485 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "checkin_count": b["checkin_count"],
486 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "last_uuid": b["last_uuid"],
487 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
488 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
489 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
490 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"branches": branch_list})
491 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
492 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
493 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # --- Tags ---
494 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
495 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
496 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @csrf_exempt
497 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @require_GET
498 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def api_tags(request, slug):
499 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Return list of tags as JSON."""
500 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project, repo = _get_repo(slug)
501 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ user, token, err = _check_api_auth(request, project, repo)
502 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err is not None:
503 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return err
504 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
505 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ reader = FossilReader(repo.full_path)
506 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ with reader:
507 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ tags = reader.get_tags()
508 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
509 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ tag_list = []
510 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for t in tags:
511 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ tag_list.append(
512 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
513 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "name": t["name"],
514 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "timestamp": _isoformat(t["timestamp"]),
515 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "user": t["user"],
516 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "uuid": t["uuid"],
517 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
518 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
519 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
520 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"tags": tag_list})
521 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
522 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
523 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # --- Releases ---
524 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
525 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
526 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @csrf_exempt
527 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @require_GET
528 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def api_releases(request, slug):
529 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Return list of releases as JSON. Drafts excluded for non-writers."""
530 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project, repo = _get_repo(slug)
531 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ user, token, err = _check_api_auth(request, project, repo)
532 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err is not None:
533 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return err
534 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
535 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.releases import Release
536 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from projects.access import can_write_project
537 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
538 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ releases_qs = Release.objects.filter(repository=repo, deleted_at__isnull=True)
539 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
540 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Only show drafts to users with write access
541 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ has_write = False
542 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if user is not None:
543 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ has_write = can_write_project(user, project)
544 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not has_write:
545 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ releases_qs = releases_qs.filter(is_draft=False)
546 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
547 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ release_list = []
548 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for r in releases_qs:
549 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assets = []
550 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for a in r.assets.filter(deleted_at__isnull=True):
551 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ assets.append(
552 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
553 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "name": a.name,
554 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "file_size_bytes": a.file_size_bytes,
555 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "content_type": a.content_type,
556 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "download_count": a.download_count,
557 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
558 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
559 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ release_list.append(
560 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
561 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "tag_name": r.tag_name,
562 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "name": r.name,
563 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "body": r.body,
564 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "is_prerelease": r.is_prerelease,
565 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "is_draft": r.is_draft,
566 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "published_at": _isoformat(r.published_at),
567 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "checkin_uuid": r.checkin_uuid,
568 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "assets": assets,
569 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
570 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
571 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
572 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"releases": release_list})
573 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
574 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
575 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # --- Search ---
576 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
577 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
578 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @csrf_exempt
579 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @require_GET
580 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def api_search(request, slug):
581 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Search across checkins, tickets, and wiki pages. Returns JSON."""
582 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project, repo = _get_repo(slug)
583 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ user, token, err = _check_api_auth(request, project, repo)
584 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err is not None:
585 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return err
586 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
587 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ query = request.GET.get("q", "").strip()
588 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not query:
589 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "Query parameter 'q' is required"}, status=400)
590 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
591 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ reader = FossilReader(repo.full_path)
592 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ with reader:
593 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ results = reader.search(query, limit=50)
594 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
595 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Serialize datetimes in results
596 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for checkin in results.get("checkins", []):
597 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ checkin["timestamp"] = _isoformat(checkin.get("timestamp"))
598 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for ticket in results.get("tickets", []):
599 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ticket["created"] = _isoformat(ticket.get("created"))
600 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
601 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse(results)
602 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
603 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
604 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # --- Batch API ---
605 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
606 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Map API paths to (view_function, extra_path_regex_or_None).
607 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Entries with a regex capture group extract path params (e.g. ticket uuid, wiki page name).
608 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ _BATCH_STATIC_ROUTES = {
609 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "/api/project": api_project,
610 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "/api/timeline": api_timeline,
611 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "/api/tickets": api_tickets,
612 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "/api/wiki": api_wiki_list,
613 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "/api/branches": api_branches,
614 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "/api/tags": api_tags,
615 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "/api/releases": api_releases,
616 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "/api/search": api_search,
617 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
618 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
619 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ _BATCH_DYNAMIC_ROUTES = [
620 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ (re.compile(r"^/api/tickets/([0-9a-fA-F-]+)$"), api_ticket_detail, "ticket_uuid"),
621 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ (re.compile(r"^/api/wiki/(.+)$"), api_wiki_page, "page_name"),
622 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ]
623 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
624 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ _BATCH_MAX_REQUESTS = 25
625 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
626 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
627 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _resolve_batch_route(path):
628 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Resolve a batch sub-request path to (view_func, kwargs) or (None, None)."""
629 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ view_func = _BATCH_STATIC_ROUTES.get(path)
630 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if view_func is not None:
631 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return view_func, {}
632 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
633 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for pattern, view_func, kwarg_name in _BATCH_DYNAMIC_ROUTES:
634 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ m = pattern.match(path)
635 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if m:
636 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return view_func, {kwarg_name: m.group(1)}
637 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
638 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return None, None
639 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
640 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
641 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @csrf_exempt
642 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def api_batch(request, slug):
643 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Execute multiple API calls in a single request.
644 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
645 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ POST /projects/<slug>/fossil/api/batch
646 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
647 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "requests": [
648 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": "/api/timeline", "params": {"per_page": 5}},
649 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": "/api/tickets", "params": {"status": "Open"}},
650 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": "/api/wiki/Home"}
651 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ]
652 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
653 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
654 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Returns:
655 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
656 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "responses": [
657 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"status": 200, "body": {...}},
658 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"status": 200, "body": {...}},
659 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"status": 200, "body": {...}}
660 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ]
661 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
662 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
663 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Auth: same as other API endpoints (Bearer token or session).
664 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Limit: 25 sub-requests per batch.
665 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Only GET sub-requests are supported.
666 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """
667 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if request.method != "POST":
668 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "POST required"}, status=405)
669 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
670 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Auth check -- same as every other API endpoint
671 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project, repo = _get_repo(slug)
672 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ user, token, err = _check_api_auth(request, project, repo)
673 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err is not None:
674 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return err
675 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
676 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ try:
677 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ body = json.loads(request.body)
678 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ except (json.JSONDecodeError, ValueError):
679 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "Invalid JSON body"}, status=400)
680 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
681 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ requests_list = body.get("requests")
682 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not isinstance(requests_list, list):
683 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "'requests' must be a list"}, status=400)
684 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
685 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if len(requests_list) > _BATCH_MAX_REQUESTS:
686 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": f"Maximum {_BATCH_MAX_REQUESTS} requests per batch"}, status=400)
687 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
688 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if len(requests_list) == 0:
689 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"responses": []})
690 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
691 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ factory = RequestFactory()
692 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ responses = []
693 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
694 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for sub in requests_list:
695 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not isinstance(sub, dict):
696 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ responses.append({"status": 400, "body": {"error": "Each request must be an object"}})
697 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ continue
698 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
699 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ method = (sub.get("method") or "GET").upper()
700 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ path = sub.get("path", "")
701 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ params = sub.get("params") or {}
702 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
703 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if method != "GET":
704 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ responses.append({"status": 405, "body": {"error": "Only GET is supported in batch requests"}})
705 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ continue
706 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
707 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not path:
708 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ responses.appendpoints for programmatic"""JSON API endpoints for programmatic access to Fossil repositories.
709 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
710 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All endpoints live under /projects/<slug>/fossil/api/.
711 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Auth: Bearer token (APIToken or PersonalAccessToken) or session cookie.
712 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All responses are JSON. All read endpoints check can_read_project.
713 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """
714 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
715 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import json
716 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import logging
717 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import math
718 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import re
719 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import shutil
720 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import subprocess
721 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import tempfile
722 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import time
723 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
724 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.db import transaction
725 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.http import JsonResponse, StreamingHttpResponse
726 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.shortcuts import get_object_or_404
727 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.test import RequestFactory
728 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.utils import timezone
729 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.views.decorators.csrf import csrf_exempt
730 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.views.decorators.http import require_GET
731 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
732 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.api_auth import authenticate_request
733 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.models import FossilRepository
734 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.reader import FossilReader
735 { 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
736 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from projects.models import Project
737 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
738 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ logger = logging.getLogger(__name__)
739 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
740 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
741 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _get_repo(slug):
742 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Look up project and repository by slug, or return 404 JSON."""
743 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project = get_object_or_404(Project, slug=slug, deleted_at__isnull=True)
744 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ repo = get_object_or_404(FossilRepository, project=project, deleted_at__isnull=True)
745 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return project, repo
746 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
747 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
748 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _check_api_auth(request, project, repo, required_scope="read"):
749 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Authenticate request and check access.
750 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
751 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Args:
752 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ required_scope: "read" or "write" — enforced on both API tokens and PAT scopes.
753 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
754 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Returns (user, token, error_response). If error_response is not None,
755 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ the caller should return it immediately.
756 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """
757 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ user, token, err = authenticate_request(request, repository=repo, required_scope=required_scope)
758 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err is not None:
759 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return None, None, err
760 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
761 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # For project-scoped APITokens (no user), the token itself grants access
762 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # since it's scoped to this repository and scope was already checked.
763 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if token is not None and user is None:
764 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return user, token, None
765 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
766 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # For user-scoped auth (PAT or session), check project visibility
767 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if user is not None:
768 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if required_scope == "write" and not can_write_project(user, project):
769 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return None, None, JsonResponse({"error": "Write access required"}, status=403)
770 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not can_read_project(user, project):
771 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return None, None, JsonResponse({"error": "Access denied"}, status=403)
772 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
773 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return user, token, None
774 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
775 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
776 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _paginate_params(request, default_per_page=25, max_per_page=100):
777 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Extract and validate page/per_page from query params."""
778 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ try:
779 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ page = max(1, int(request.GET.get("page", "1")))
780 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ except (ValueError, TypeError):
781 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ page = 1
782 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ try:
783 { 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)))))
784 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ except (ValueError, TypeError):
785 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ per_page = default_per_page
786 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return page, per_page
787 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
788 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
789 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _isoformat(dt):
790 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Safely format a datetime to ISO 8601, or None."""
791 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if dt is None:
792 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return None
793 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return dt.isoformat()
794 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
795 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
796 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # --- API Documentation ---
797 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
798 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
799 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @csrf_exempt
800 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @require_GET
801 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def api_docs(request, slug):
802 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Return JSON listing all available API endpoints with descriptions."""
803 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ base = f"/projects/{slug}/fossil/api"
804 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse(
805 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
806 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "endpoints": [
807 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/project", "description": "Project metadata"},
808 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
809 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "GET",
810 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/timeline",
811 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Recent checkins (paginated)",
812 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "params": "page, per_page, branch",
813 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
814 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
815 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "GET",
816 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/tickets",
817 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Ticket list (paginated, filterable)",
818 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "params": "page, per_page, status",
819 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
820 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/tickets/<uuid>", "description": "Single ticket detail with comments"},
821 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/wiki", "description": "Wiki page list"},
822 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/wiki/<name>", "description": "Single wiki page with content"},
823 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/branches", "description": "Branch list"},
824 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/tags", "description": "Tag list"},
825 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/releases", "description": "Release list"},
826 { 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"},
827 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
828 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "POST",
829 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/batch",
830 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Execute multiple API calls in a single request (max 25)",
831 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "body": '{"requests": [{"method": "GET", "path": "/api/timeline", "params": {}}]}',
832 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
833 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/workspaces", "description": "List agent workspaces", "params": "status"}points for programmatic access to Fossil repositories.
834 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
835 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All endpoints live under /projects/<slug>/fossil/api/.
836 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Auth: Bearer token (APIToken or PersonalAccessToken) or session cookie.
837 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All responses are JSON. A"""JSON API endpoints for programmatic access to Fossil repositories.
838 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
839 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All endpoints live under /projects/<slug>/fossil/api/.
840 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Auth: Bearer token (APIToken or PersonalAccessToken) or session cookie.
841 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All responses a{ess required"}, ion
842 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
843 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for protection in BranchProtection.objects.filter(repository=repo, deleted_at__isnull=True):
844 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if protection.matches_branch(target_branch):
845 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if protection.restrict_push:
846 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse(
847 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"error": f"Branch '{target_branch}' is protected: only admins can merge to it"},
848 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ status=403,
849 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
850 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if protection.require_status_checks:
851 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.ci import StatusCheck
852 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
853 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for context in protection.get_required_contexts_list():
854 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ latest = StatusCheck.objects.filter(repository=repo, context=context).order_by("-created_at").first()
855 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not latest or latest.state != "success":
856 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse(
857 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ points for programmatic access to Fossil repositories.
858 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
859 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All endpoints live under /projects/<slug>/fossil/api/.
860 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Auth: Bearer token (APIToken or PersonalAccessToken) or session cookie.
861 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All responses are JSON. A"""JSON API endpoints for programmatic access to Fossil repositories.
862 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
863 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All endpoints live under /projects/<slug>/fossil/api/.
864 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Auth: Bearer token (APIToken or PersonalAccessToken) or session cookie.
865 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All responses a{ess required"}, ody)
866 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ except (json.JSONDecodeError, ValueError):
867 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "Invalid JSON body"}, status=400)
868 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
869 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ agent_id = (data.get("agent_id") or "").strip()
870 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not agent_id:
871 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "agent_id is required"}, stm.ticket_uuid,
872 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "a01,
873 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
874 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
875 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
876 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @csrf_exempt
877 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def api_ticket_release(request, slug, ticket_uuid):
878 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Release a ticke },
879 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ status=201,
880 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
881 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
882 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
883 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @csrf_exempt
884 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def api_ticket_release(request, slug, ticket_uuid):
885 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Release a ticket claim.
886 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
887 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ POST /projects/<slug>/fossil/api/tickets/<uuid>/release
888 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"agent_id": "claude-abc"}
889 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
890 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Soft-deletes the claim record so the unique constraint slot is freed.
891 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """
892 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if request.method != "POST":
893 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "POST required"}, status=405)
894 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
895 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project, repo = _get_repo(slug)
896 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ user, token, err = _check_api_auth(request, project, repo, required_scope="write")
897 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err is not None:
898 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return err
899 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
900 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if token is None and (user is None or not can_write_project(user, project)):
901 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "Write access required"}, status=403)
902 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
903 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ try:
904 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ data = json.loads(request.body) if request.body else {}
905 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ except (json.JSONDecodeError, ValueError):
906 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ data = {}
907 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
908 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ agent_id = (data.get("agent_id") or "").strip()
909 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not agent_id:
910 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "agent_id is required"}, status=400)
911 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
912 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Closee bug by ...",
913 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ up
914 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ lt.returncode != 0:
915 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Clean up on failure
916 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ subprocess.run([cli.binary, "close", = (data.get("agent_id") or "").strip()
917 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not agent_id:
918 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "agent_id is required"}, stm.ticket_uuid,
919 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "a01,
920 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
921 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
922 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
923 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @csrf_exempt
924 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def api_ticket_release(request, slug, ticket_uuid):
925 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Release a ticke },
926 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ status=201,
927 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
928 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
929 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
930 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @csrf_exempt
931 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def api_ticket_release(request, slug, ticket_uuid):
932 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Release a ticket claim.
933 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
934 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ POST /projects/<slug>/fossil/api/tickets/<uuid>/release
935 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"agent_id": "claude-abc"}
936 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
937 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Soft-deletes the claim record so the unique constraint slot is freed.
938 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """
939 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if request.method != "POST":
940 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "POST required"}, status=405)
941 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
942 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project, repo = _get_repo(slug)
943 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ user, token, err = _check_api_auth(request, project, repo, required_scope="write")
944 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err is not None:
945 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return err
946 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
947 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if token is None and (user is None or not can_write_project(user, project)):
948 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "Write access required"}, status=403)
949 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
950 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ try:
951 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ data = json.loads(request.bodypoints for programmatic access to Fossil repositories.
952 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
953 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All endpoints live under /projects/<slug>/fossil/api/.
954 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Auth: Bearer token (APIToken or PersonalAccessToken) or session cookie.
955 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All responses are JSON. A"""JSON API endpoints for programmatic access to Fossil repositories.
956 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
957 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All endpoints live under /projects/<slug>/fossil/api/.
958 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Auth: Bearer token (APIToken or PersonalAccessToken) or session cookie.
959 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All responses are JSON. All read endpoints check can_read_project.
960 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """
961 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
962 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import json
963 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import logging
964 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import math
965 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import re
966 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import shutil
967 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import subprocess
968 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import tempfile
969 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import time
970 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
971 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.db import transaction
972 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.http import JsonResponse, StreamingHttpResponse
973 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.shortcuts import get_object_or_404
974 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.test import RequestFactory
975 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.utils import timezone
976 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.views.decorators.csrf import csrf_exempt
977 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.views.decorators.http import require_GET
978 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
979 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.api_auth import authenticate_request
980 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.models import FossilRepository
981 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.reader import FossilReader
982 { 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
983 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from projects.models import Project
984 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
985 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ logger = logging.getLogger(__name__)
986 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
987 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
988 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _get_repo(slug):
989 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Look up project and repository by slug, or return 404 JSON."""
990 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project = get_object_or_404(Project, slug=slug, deleted_at__isnull=True)
991 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ repo = get_object_or_404(FossilRepository, project=project, deleted_at__isnull=True)
992 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return project, repo
993 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
994 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
995 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _check_api_auth(request, project, repo, required_scope="read"):
996 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Authenticate request and check access.
997 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
998 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Args:
999 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ required_scope: "read" or "write" — enforced on both API tokens and PAT scopes.
1000 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1001 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Returns (user, token, error_response). If erropoints for programmatic access to Fossil repositories.
1002 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1003 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All endpoints live under /projects/<slug>/fossil/api/.
1004 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Auth: Bearer token (APIToken or PersonalAccessToken) or session cookie.
1005 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All responses are JSON. A"""JSON API endpoints for programmatic access to Fossil repositories.
1006 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1007 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All endpoints live under /projects/<slug>/fossil/api/.
1008 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Auth: Bearer tokf not agent_id:
1009 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "agent_id is required"}, status=400)
1010 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1011 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.agent_claims import TicketClaim
1012 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1013 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ claim = TicketClaim.objects.filter(repository=repo, ticket_uuid=ticket_uuid).first()
1014 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if claim is None:
1015 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "No active claim for this ticket"}, status=404)
1016 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1017 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if claim.agent_id != agent_id:
1018 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "Only the claiming agent can submit work for this ticket"}, status=403)
1019 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1020 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if claim.status != "claimed":
1021 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": f"Claim is already {claim.status}"}, status=409)
1022 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1023 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ summary = (data.get("summary") or "").strip()
1024 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ files_changed = data.get("files_changed") or []
1025 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1026 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ claim.status = "submitted"
1027 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ claim.summary = summary
1028 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ claim.files_changed = files_changed
1029 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ claim.save(update_fields=["status", "summary", "files_changed", "updated_at", "version"])
1030 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1031 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Optionally add a comment to the Fossil ticket via CLI
1032 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if summary:
1033 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.cli import FossilCLI
1034 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1035 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ cli = FossilCLI()
1036 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ points for programmatic access to Fossil repositories.
1037 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1038 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All endpoints live under /projects/<slug>/fossil/api/.
1039 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Auth: Bearer token (APIToken or PersonalAccessToken) or session cookie.
1040 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All responses are JSON. All read endp"""JSON AP "branch": entry.branch,
1041 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "timestamp": _isoformat(entry.timestamp),
1042 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
1043 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ": _isoformat(entry.timestamp),
1044 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
1045 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
1046 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
1047 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ last_checkin_count = current_count
1048 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ except Exception:
1049 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ pass
1050 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1051 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Check for new claims
1052 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ new_claims = TicketClaim.all_objects.filter(repository=repo, pk__gt=last_claim_id).order_by("pk")
1053 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for claim in new_claims:
1054 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ pass
1055 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1056 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Yield events
1057 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for event in events:
1058 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ yield f"event: {event['type']}\ndata: {json.dumps(event['data'])}\n\n"
1059 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1060 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Heartbeat every ~15 seconds (3 iterations * 5s sleep)
1061 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ heartbeat_counter += 1
1062 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not events and heartbeat_counter >= 3:
1063 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ yield ": heartbeat\n\n"
1064 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ heartbeat_counter = 0
1065 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1066 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ time.sleep(5)
1067 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1068 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ response = StreamingHttpResponse(event_stream(), content_type="text/event-stream")
1069 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ response["Cache-Control"] = "no-cache"
1070 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ response["X-Accel-Buffering"] = "no"
1071 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return response
1072 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1073 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1074 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # --- Code Review API ---
1075 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1076 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1077 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @csrf_exempt
1078 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def api_review_create(request, slug):
1079 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Submit code changes for review.
1080 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1081 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ points for programmatic access to Fossil repositories.
1082 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1083 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All endpoints live under /projects/<slug>/fossil/api/.
1084 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Auth: Bearer token (APIToken or PersonalAccessToken) or session cookie.
1085 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All responses are JSON. A"""JSON API endpoints for programmatic access to Fossil repositories.
1086 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1087 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All endpoints live under /projects/<slug>/fossil/api/.
1088 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Auth: Bearer toky\\n+++ b/src/auth.py\\ "comment": entry.comment,
1089 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "branch": entry.branch,
1090 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "timestamp": _isoformat(entry.timestamp),
1091 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
1092 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
1093 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
1094 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ last_checkin_count = current_count
1095 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ except Exception:
1096 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ pass
1097 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1098 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Check for new claims
1099 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ new_claims = TicketClaim.all_objects.filter(repository=repo, pk__gt=last_claim_id).order_by("pk")
1100 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for claim in new_claims:
1101 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ pass
1102 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1103 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Yield events
1104 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for event in events:
1105 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ yield f"event: {event['type']}\ndata: {json.dumps(event['data'])}\n\n"
1106 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1107 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Heartbeat every ~15 seconds (3 iterations * 5s sleep)
1108 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ heartbeat_counter += 1
1109 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not events and heartbeat_counter >= 3:
1110 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ yield ": heartbeat\n\n"
1111 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ heartbeat_counter = 0
1112 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1113 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ time.sleep(5)
1114 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1115 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ response = StreamingHttpResponse(event_stream(), content_type="text/event-stream")
1116 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ response["Cache-Control"] = "no-cache"
1117 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ response["X-Accel-Buffering"] = "no"
1118 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return response
1119 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1120 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1121 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # --- Code Review API ---
1122 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1123 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1124 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @csrf_exempt
1125 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def api_review_create(request, slug):
1126 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Submit code changes for review.
1127 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1128 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ POST /projects/<slug>/fossil/api/reviews/create
1129 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
1130 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "title": "Fix null pointer in auth module",
1131 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "The auth check was failing when ...",
1132 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "diff": "--- a/src/auth.py\\n+++ b/src/auth.py\\n...",
1133 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "files_changed": ["src/auth.py", "tests/test_auth.py"],
1134 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "agent_id": "claude-abc",
1135 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "workspace": "agent-fix-123",
1136 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "ticket_uuid": "abc123..."
1137 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
1138 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """
1139 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if request.method != "POST":
1140 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "POST required"}, status=405)
1141 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1142 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project, repo = _get_repo(slug)
1143 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ user, token, err = _check_api_auth(request, project, repo, required_scope="write")
1144 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err is not None:
1145 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return err
1146 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1147 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if token is None and (user is None or not can_write_project(user, project)):
1148 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "Write access required"}, status=403)
1149 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1150 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ try:
1151 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ data = json.loads(request.body)
1152 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ except (json.JSONDecodeError, ValueError):
1153 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "Invalid JSON body"}, status=400)
1154 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1155 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ title = (data.get("title") or "").strip()
1156 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not title:
1157 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "Review title is required"}, status=400)
1158 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1159 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ diff = (data.get("diff") or "").strip()
1160 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not diff:
1161 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "Diff is required"}, status=400)
1162 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1163 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Resolve optional workspace reference
1164 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ workspace_name = (data.get("workspace") or "").strip()
1165 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ workspace_obj = None
1166 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if workspace_name:
1167 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.workspaces import AgentWorkspace
1168 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1169 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ workspace_obj = AgentWorkspace.objects.filter(repository=repo, name=workspace_name).first()
1170 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1171 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # If linking to a ticket, verify the caller owns the claim
1172 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ticket_uuid = (data.get("ticket_uuid") or "").strip()
1173 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ review_agent_id = (data.get("agent_id") or "").strip()
1174 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if ticket_uuid:
1175 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.agent_claims import TicketClaim
1176 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1177 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ claim = TicketClaim.objects.filter(repository=repo, ticket_uuid=ticket_uuid).first()
1178 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if claim is not None and review_agent_id and claim.agent_id != review_agent_id:
1179 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "Cannot create a review for a ticket claimed by another agent"}, status=403)
1180 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1181 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.code_reviews import CodeReview
1182 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1183 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ review = CodeReview.objects.create(
1184 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ repository=repo,
1185 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ workspace=workspace_obj,
1186 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ title=title,
1187 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ description=data.get("description", ""),
1188 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ diff=diff,
1189 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ files_changed=data.get("files_changed", []),
1190 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ agent_id=review_agent_id,
1191 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ticket_uuid=ticket_uuid,
1192 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ created_by=user,
1193 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
1194 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1195 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse(
1196 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
1197 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "id": review.pk,
1198 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "title": review.title,
1199 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": review.description,
1200 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "status": review.status,
1201 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "agent_id": review.agent_id,
1202 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "files_changed": review.files_changed,
1203 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "created_at": _isoformat(review.created_at),
1204 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
1205 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ status=201,
1206 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
1207 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1208 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1209 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @csrf_exempt
1210 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def api_review_list(request, slug):
1211 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """List code reviews for a repository, optionally filtered by status.
1212 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1213 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ GET /projects/<slug>/fossil/api/reviews
1214 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Optional query params: status (pending, approved, changes_requested, merged)
1215 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """
1216 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if request.method != "GET":
1217 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "GET required"}, status=405)
1218 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1219 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project, repo = _get_repo(slug)
1220 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ user, token, err = _check_api_auth(request, project, repo)
1221 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err is not None:
1222 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return err
1223 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1224 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.code_reviews import CodeReview
1225 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1226 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ qs = CodeReview.objects.filter(repository=repo)
1227 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ status_filter = request.GET.get("status", "").strip()
1228 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if status_filter:
1229 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ qs = qs.filter(status=status_filter)
1230 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1231 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ page, per_page = _paginate_params(request)
1232 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ total = qs.count()
1233 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ total_pages = max(1, math.ceil(total / per_page))
1234 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ page = min(page, total_pages)
1235 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ reviews_page = qs[(page - 1) * per_page : page * per_page]
1236 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1237 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ reviews = []
1238 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for r in reviews_page:
1239 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ reviews.append(
1240 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
1241 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "id": r.pk,
1242 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "title": r.title,
1243 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "status": r.status,
1244 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "agent_id": r.agent_id,
1245 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "files_changed": r.files_changed,
1246 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "comment_count": r.comments.count(),
1247 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "created_at": _isoformat(r.created_at),
1248 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "updated_at": _isoformat(r.updated_at),
1249 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
1250 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
1251 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1252 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse(
1253 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
1254 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "reviews": reviews,
1255 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "total": total,
1256 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "page": page,
1257 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "per_page": per_page,
1258 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "total_pages": total_pages,
1259 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
1260 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
1261 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1262 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1263 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @csrf_exempt
1264 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def api_review_detail(request, slug, review_id):
1265 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Get a code review with its comments.
1266 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1267 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ GET /projects/<slug>/fossil/api/reviews/<id>
1268 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """
1269 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if request.method != "GET":
1270 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "GET required"}, status=405)
1271 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1272 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project, repo = _get_repo(slug)
1273 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ user, token, err = _check_api_auth(request, project, repo)
1274 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err is not None:
1275 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return err
1276 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1277 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.code_reviews import CodeReview
1278 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1279 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ review = CodeReview.objects.filter(repository=repo, pk=review_id).first()
1280 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if review is None:
1281 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "Review not found"}, status=404)
1282 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1283 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ comments = []
1284 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for c in review.comments.all():
1285 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ comments.append(
1286 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
1287 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "id": c.pk,
1288 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "body": c.body,
1289 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "file_path": c.file_path,
1290 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "line_number": c.line_number,
1291 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "author": c.author,
1292 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "created_at": _isoformat(c.created_at),
1293 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
1294 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
1295 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1296 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse(
1297 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
1298 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "id": review.pk,
1299 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "title": review.title,
1300 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": review.description,
1301 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "diff": review.diff,
1302 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "status": review.status,
1303 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "agent_id": review.agent_id,
1304 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "files_changed": review.files_changed,
1305 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "ticket_uuid": review.ticket_uuid,
1306 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "workspace": review.workspace.name if review.workspace else None,
1307 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "comments": comments,
1308 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "created_at": _isoformat(review.created_at),
1309 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "updated_at": _isoformat(review.updated_at),
1310 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
1311 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
1312 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1313 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1314 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @csrf_exempt
1315 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def api_review_comment(request, slug, review_id):
1316 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Add a comment to a code review.
1317 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1318 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ POST /projects/<slug>/fossil/api/reviews/<id>/comment
1319 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
1320 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "body": "This looks good but consider...",
1321 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "file_path": "src/auth.py",
1322 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "line_number": 42,
1323 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "author": "human-reviewer"
1324 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
1325 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """
1326 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if request.method != "POST":
1327 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "POST required"}, status=405)
1328 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1329 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project, repo = _get_repo(slug)
1330 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ user, token, err = _check_api_auth(request, project, repo, required_scope="write")
1331 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err is not None:
1332 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return err
1333 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1334 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.code_reviews import CodeReview, ReviewComment
1335 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1336 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ review = CodeReview.objects.filter(repository=repo, pk=review_id).first()
1337 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if review is None:
1338 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "Review not found"}, status=404)
1339 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1340 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ try:
1341 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ data = json.loads(request.body)
1342 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ except (json.JSONDecodeError, ValueError):
1343 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "Invalid JSON body"}, status=400)
1344 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1345 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ body = (data.get("body") or "").strip()
1346 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not body:
1347 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "Comment body is required"}, status=400)
1348 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1349 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Determine author from auth context, not caller-supplied data
1350 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if user:
1351 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ author = user.username
1352 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ elif token:
1353 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ author = f"token:{token.name}" if hasattr(token, "name") else "api-token"
1354 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ else:
1355 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ author = "anonymous"
1356 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1357 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ comment = ReviewComment.objects.create(
1358 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ review=review,
1359 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ body=body,
1360 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ file_path=data.get("file_path", ""),
1361 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ line_number=data.get("line_number"),
1362 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ author=author,
1363 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ created_by=user,
1364 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
1365 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1366 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponsepoints for programmatic access to Fossil repositories.
1367 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1368 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All endpoints live under /projects/<slug>/fossil/api/.
1369 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Auth: Bearer token (APIToken or PersonalAccessToken) or session cookie.
1370 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All responses are JSON. A"""JSON API endpoints for programmatic access to Fossil repositories.
1371 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1372 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All endpoints live under /projects/<slug>/fossil/api/.
1373 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Auth: Bearer tokser, project)):
1374 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "Write access required"}, status=403)
1375 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1376 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.code_reviews import CodeReview
1377 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1378 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ review = CodeReview.objects.filter(repository=repo, pk=revicannot approve their own review.
1379 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ctory.get(full_path, da
1380 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if review.status == "merged":
1381 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "Review is already merged"}, status=409)
1382 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1383 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if review.status != "approved":
1384 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"error": "Review must be approved before merging"}, status=409)
1385 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1386 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ review.status = "merged"
1387 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ review.save(update_fields=["status", "updated_at", "version"])
1388 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1389 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ approver_agent_id andet_uuid).first()
1390 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if claim and claim.status in ("claimed", "submitted"):
1391 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ claim.status = "merged"
1392 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ claim.save(update_fields=["status", "updated_at", "version"])
1393 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1394 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse({"id": review.pk, "status": review.statu return JsonResppoints for programmatic"""JSON API endauthor = (data.get("authorauthor andif not authorAuthor i=checkout_dir,
1395 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
1396 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ 0)points for programmatic access to Fossil repositories.
1397 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1398 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All endpoints live under /projects/<slug>/fossil/api/.
1399 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Auth: Bearer token (APIToken or PersonalAccessToken) or session cookie.
1400 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All responses are JSON. All read endpoints check can_read_project.
1401 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """
1402 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1403 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import json
1404 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import logging
1405 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import math
1406 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import re
1407 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import shutil
1408 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import subprocess
1409 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import tempfile
1410 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import time
1411 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1412 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.db import transaction
1413 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.http import JsonResponse, StreamingHttpResponse
1414 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.shortcuts import get_object_or_404
1415 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.test import RequestFactor"""JSON API endpoapprovlogger = logging.getLogger(__name__)
1416 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1417 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1418 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _get_repo(slug):
1419 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Look up project and repository by slug, or return 404 JSON."""
1420 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project = get_object_or_404(Project, slug=slug, deleted_at__isnull=True)
1421 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ repo = get_object_or_404(FossilRepository, project=project, deleted_at__isnull=True)
1422 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return project, repo
1423 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1424 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1425 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _check_api_auth(request, project, repo, required_scope="read"):
1426 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Authenticate request and check access.
1427 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1428 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Args:
1429 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ require"""JSON API endpoints for programmatic access to Fossil repositories.
1430 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1431 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All endpoints live under /projects/<slug>/fossil/api/.
1432 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Auth: Bearer token (APIToken or PersonalAccessToken) or session cookie.
1433 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All responses are JSON. A"""JSON API endpoints for programmatic access to Fossil repositories.
1434 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1435 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All endpoints live under /projects/<slug>/fossil/api/.
1436 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Auth: Bearer token (APIToken or PersonalAccessToken) or session cookpoints for programmatic access to Fossil repositories.
1437 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1438 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All endpoints live under /projects/<slug>/fossil/api/.
1439 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Auth: Bearer token (APIToken or PersonalAccessToken) or session cookie.
1440 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All responses are JSON. All read endpoints check can_read_project.
1441 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """
1442 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1443 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import json
1444 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import logging
1445 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import math
1446 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import re
1447 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import shutil
1448 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import subprocess
1449 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import tempfile
1450 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import time
1451 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1452 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.db import transaction
1453 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.http import JsonResponse, StreamingHttpResponse
1454 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.shortcuts import get_object_or_404
1455 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.test import RequestFactory
1456 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.utils import timezone
1457 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.views.decorators.csrf import csrf_exempt
1458 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.views.decorators.http import require_GET
1459 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1460 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.api_auth import authenticate_request
1461 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.models import FossilRepository
1462 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.reader import FossilReader
1463 { 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
1464 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from projects.models import Project
1465 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1466 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ logger = logging.getLogger(__name__)
1467 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1468 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1469 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _get_repo(slug):
1470 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Look up project and repository by slug, or return 404 JSON."""
1471 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project = get_object_or_404(Project, slug=slug, deleted_at__isnull=True)
1472 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ repo = get_object_or_404(FossilRepository, project=project, deleted_at__isnull=True)
1473 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """JSON API endpoints for programmatic access to Fossil repositories.
1474 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1475 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All endpoints live under /projects/<slug>/fossil/api/.
1476 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Auth: Bearer token (APIToken or PersonalAccessToken) or session cookie.
1477 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ All responses are JSON. All read endpoints check can_read_project.
1478 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """
1479 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1480 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import json
1481 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import logging
1482 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import math
1483 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import re
1484 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import shutil
1485 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import subprocess
1486 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import tempfile
1487 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import time
1488 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1489 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.db import transaction
1490 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.http import JsonResponse, StreamingHttpResponse
1491 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.shortcuts import get_object_or_404
1492 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.test import RequestFactory
1493 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.utils import timezone
1494 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.views.decorators.csrf import csrf_exempt
1495 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from django.views.decorators.http import require_GET
1496 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1497 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.api_auth import authenticate_request
1498 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.models import FossilRepository
1499 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from fossil.reader import FossilReader
1500 { 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
1501 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from projects.models import Project
1502 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1503 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ logger = logging.getLogger(__name__)
1504 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1505 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1506 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _get_repo(slug):
1507 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Look up project and repository by slug, or return 404 JSON."""
1508 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ project = get_object_or_404(Project, slug=slug, deleted_at__isnull=True)
1509 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ repo = get_object_or_404(FossilRepository, project=project, deleted_at__isnull=True)
1510 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return project, repo
1511 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1512 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1513 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _check_api_auth(request, project, repo, required_scope="read"):
1514 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Authenticate request and check access.
1515 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1516 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Args:
1517 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ required_scope: "read" or "write" — enforced on both API tokens and PAT scopes.
1518 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1519 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Returns (user, token, error_response). If error_response is not None,
1520 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ the caller should return it immediately.
1521 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """
1522 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ user, token, err = authenticate_request(request, repository=repo, required_scope=required_scope)
1523 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err is not None:
1524 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return None, None, err
1525 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1526 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # For project-scoped APITokens (no user), the token itself grants access
1527 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # since it's scoped to this repository and scope was already checked.
1528 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if token is not None and user is None:
1529 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return user, token, None
1530 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1531 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # For user-scoped auth (PAT or session), check project visibility
1532 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if user is not None:
1533 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if required_scope == "write" and not can_write_project(user, project):
1534 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return None, None, JsonResponse({"error": "Write access required"}, status=403)
1535 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not can_read_project(user, project):
1536 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return None, None, JsonResponse({"error": "Access denied"}, status=403)
1537 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1538 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return user, token, None
1539 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1540 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1541 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _paginate_params(request, default_per_page=25, max_per_page=100):
1542 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Extract and validate page/per_page from query params."""
1543 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ try:
1544 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ page = max(1, int(request.GET.get("page", "1")))
1545 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ except (ValueError, TypeError):
1546 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ page = 1
1547 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ try:
1548 { 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)))))
1549 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ except (ValueError, TypeError):
1550 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ per_page = default_per_page
1551 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return page, per_page
1552 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1553 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1554 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _isoformat(dt):
1555 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Safely format a datetime to ISO 8601, or None."""
1556 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if dt is None:
1557 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return None
1558 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return dt.isoformat()
1559 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1560 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1561 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # --- API Documentation ---
1562 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1563 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
1564 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @csrf_exempt
1565 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ @require_GET
1566 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def api_docs(request, slug):
1567 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Return JSON listing all available API endpoints with descriptions."""
1568 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ base = f"/projects/{slug}/fossil/api"
1569 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return JsonResponse(
1570 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
1571 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "endpoints": [
1572 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/project", "description": "Project metadata"},
1573 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
1574 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "GET",
1575 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/timeline",
1576 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Recent checkins (paginated)",
1577 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "params": "page, per_page, branch",
1578 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
1579 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
1580 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "GET",
1581 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/tickets",
1582 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Ticket list (paginated, filterable)",
1583 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "params": "page, per_page, status",
1584 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
1585 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/tickets/<uuid>", "description": "Single ticket detail with comments"},
1586 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/wiki", "description": "Wiki page list"},
1587 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/wiki/<name>", "description": "Single wiki page with content"},
1588 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/branches", "description": "Branch list"},
1589 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/tags", "description": "Tag list"},
1590 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/releases", "description": "Release list"},
1591 { 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"},
1592 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
1593 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "POST",
1594 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/batch",
1595 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Execute multiple API calls in a single request (max 25)",
1596 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "body": '{"requests": [{"method": "GET", "path": "/api/timeline", "params": {}}]}',
1597 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
1598 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/workspaces", "description": "List agent workspaces", "params": "status"},
1599 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
1600 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "POST",
1601 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/workspaces/create",
1602 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Create an isolated agent workspace",
1603 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "body": '{"name": "...", "description": "...", "agent_id": "..."}',
1604 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
1605 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {"method": "GET", "path": f"{base}/workspaces/<name>", "description": "Get workspace details"},
1606 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
1607 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "POST",
1608 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/workspaces/<name>/commit",
1609 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Commit changes in a workspace",
1610 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "body": '{"message": "...", "files": []}',
1611 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
1612 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
1613 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "POST",
1614 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/workspaces/<name>/merge",
1615 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Merge workspace branch back to trunk",
1616 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "body": '{"target_branch": "trunk"}',
1617 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
1618 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
1619 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "DELETE",
1620 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/workspaces/<name>/abandon",
1621 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Abandon and clean up a workspace",
1622 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ },
1623 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ {
1624 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "method": "POST",
1625 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "path": f"{base}/tickets/<uuid>/claim",
1626 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "description": "Claim a ticket for exclusive agent work",
1627 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!