+103
-36
crates/knot/src/model/repository.rs
+103
-36
crates/knot/src/model/repository.rs
···
7
7
public::xrpc::XrpcError,
8
8
types::sh::tangled::repo::{blob, branches, diff, log, tags},
9
9
};
10
+
use axum::{
11
+
http::{HeaderMap, HeaderValue},
12
+
response::IntoResponse,
13
+
};
10
14
use gix::{ThreadSafeRepository, bstr::ByteSlice as _};
11
15
use lexicon::sh::tangled::repo::{get_default_branch, refs, tree};
16
+
use serde::Serialize;
12
17
use std::cmp::Reverse;
13
18
use tokio_rayon::AsyncThreadPool as _;
14
19
···
40
35
fn blob(
41
36
&self,
42
37
params: blob::Input,
43
-
) -> impl Future<Output = Result<blob::Output, XrpcError>> + Send;
38
+
) -> impl Future<Output = Result<DirectResponse<blob::Output>, XrpcError>> + Send;
44
39
45
40
fn branches(
46
41
&self,
···
55
50
fn diff(
56
51
&self,
57
52
params: diff::Input,
58
-
) -> impl Future<Output = Result<diff::Output, XrpcError>> + Send;
53
+
) -> impl Future<Output = Result<XrpcResponse<diff::Output>, XrpcError>> + Send;
59
54
60
55
fn log(
61
56
&self,
···
70
65
fn tree(
71
66
&self,
72
67
params: tree::Input,
73
-
) -> impl Future<Output = Result<tree::Output, XrpcError>> + Send;
68
+
) -> impl Future<Output = Result<XrpcResponse<tree::Output>, XrpcError>> + Send;
69
+
}
70
+
71
+
pub struct XrpcResponse<T> {
72
+
pub value: T,
73
+
pub immutable: bool,
74
+
}
75
+
76
+
impl<T> IntoResponse for XrpcResponse<T>
77
+
where
78
+
T: Serialize,
79
+
{
80
+
fn into_response(self) -> axum::response::Response {
81
+
use axum::{http::header::CACHE_CONTROL, response::Json};
82
+
83
+
let mut headers = HeaderMap::new();
84
+
if self.immutable {
85
+
headers.insert(
86
+
CACHE_CONTROL,
87
+
HeaderValue::from_static("public, immutable, s-maxage=604800"),
88
+
);
89
+
}
90
+
91
+
(headers, Json(self.value)).into_response()
92
+
}
93
+
}
94
+
95
+
pub struct DirectResponse<T> {
96
+
pub value: T,
97
+
pub immutable: bool,
98
+
}
99
+
100
+
impl<T> IntoResponse for DirectResponse<T>
101
+
where
102
+
T: IntoResponse,
103
+
{
104
+
fn into_response(self) -> axum::response::Response {
105
+
use axum::http::header::CACHE_CONTROL;
106
+
107
+
let mut headers = HeaderMap::new();
108
+
if self.immutable {
109
+
headers.insert(
110
+
CACHE_CONTROL,
111
+
HeaderValue::from_static("public, immutable, s-maxage=604800"),
112
+
);
113
+
}
114
+
115
+
(headers, self.value).into_response()
116
+
}
74
117
}
75
118
76
119
pub struct GitOxideRepository<'a> {
···
133
80
}
134
81
135
82
impl TangledRepository for GitOxideRepository<'_> {
136
-
async fn blob(&self, params: blob::Input) -> Result<blob::Output, XrpcError> {
83
+
async fn blob(&self, params: blob::Input) -> Result<DirectResponse<blob::Output>, XrpcError> {
137
84
let repo = self.repo.to_thread_local();
138
85
self.state
139
86
.pool()
140
87
.spawn_fifo_async(move || {
141
-
let tip = resolve_rev(&repo, params.rev.as_deref())?;
88
+
let (tip, immutable) = resolve_rev(&repo, params.rev.as_deref())?;
142
89
let entry = tip
143
90
.tree()?
144
91
.lookup_entry_by_path(¶ms.path)?
···
151
98
let mut blob = entry.object()?.into_blob();
152
99
let data = blob.take_data();
153
100
if params.raw {
154
-
return Ok(blob::Output::Raw(data));
101
+
return Ok(DirectResponse {
102
+
value: blob::Output::Raw(data),
103
+
immutable,
104
+
});
155
105
}
156
106
157
107
let size = data.len();
···
167
111
),
168
112
};
169
113
170
-
Ok(blob::Output::Json(Box::new(
171
-
lexicon::sh::tangled::repo::blob::Output {
114
+
Ok(DirectResponse {
115
+
value: blob::Output::Json(Box::new(lexicon::sh::tangled::repo::blob::Output {
172
116
rev: params.rev.as_deref().unwrap_or_default().to_owned(),
173
117
path: params.path.to_owned(),
174
118
content,
···
177
121
is_binary,
178
122
mime_type: "".to_string(),
179
123
last_commit: None,
180
-
},
181
-
)))
124
+
})),
125
+
immutable,
126
+
})
182
127
})
183
128
.await
184
129
}
···
268
211
.await
269
212
}
270
213
271
-
async fn diff(&self, params: diff::Input) -> Result<diff::Output, XrpcError> {
214
+
async fn diff(&self, params: diff::Input) -> Result<XrpcResponse<diff::Output>, XrpcError> {
272
215
let repo = self.repo.to_thread_local();
273
216
self.state
274
217
.pool()
275
218
.spawn_fifo_async(move || {
276
-
let this_commit = resolve_rev(&repo, Some(¶ms.rev))?;
219
+
let (this_commit, immutable) = resolve_rev(&repo, Some(¶ms.rev))?;
277
220
let diff = super::nicediff::unified_diff_from_parent(this_commit).unwrap();
278
221
let response = diff::Output {
279
222
rev: params.rev,
280
223
diff,
281
224
};
282
-
Ok(response)
225
+
Ok(XrpcResponse {
226
+
value: response,
227
+
immutable,
228
+
})
283
229
})
284
230
.await
285
231
}
···
307
247
}
308
248
};
309
249
310
-
let tip = resolve_rev(&repo, params.rev.as_deref())?;
250
+
let (tip, _) = resolve_rev(&repo, params.rev.as_deref())?;
311
251
312
252
let mut commits = Vec::new();
313
253
for commit in repo
···
371
311
.await
372
312
}
373
313
374
-
async fn tree(&self, params: tree::Input) -> Result<tree::Output, XrpcError> {
314
+
async fn tree(&self, params: tree::Input) -> Result<XrpcResponse<tree::Output>, XrpcError> {
375
315
let repo = self.repo.to_thread_local();
376
316
self.state
377
317
.pool()
378
318
.spawn_fifo_async(move || {
379
-
let tip = resolve_rev(&repo, params.rev.as_deref())?;
319
+
let (tip, immutable) = resolve_rev(&repo, params.rev.as_deref())?;
380
320
let dotdot = params.path.clone().and_then(|mut path| {
381
321
path.pop();
382
322
match path.as_os_str().is_empty() {
···
393
333
.ok_or(PathNotFound(subpath.to_string_lossy()))?;
394
334
395
335
if !entry.mode().is_tree() {
396
-
return Ok(tree::Output {
397
-
files: vec![],
398
-
dotdot,
399
-
parent: params.path.clone(),
400
-
rev: params.rev.as_deref().unwrap_or_default().to_string(),
401
-
readme: None,
336
+
return Ok(XrpcResponse {
337
+
value: tree::Output {
338
+
files: vec![],
339
+
dotdot,
340
+
parent: params.path.clone(),
341
+
rev: params.rev.as_deref().unwrap_or_default().to_string(),
342
+
readme: None,
343
+
},
344
+
immutable,
402
345
});
403
346
}
404
347
···
442
379
})
443
380
.collect();
444
381
445
-
Ok(tree::Output {
446
-
files,
447
-
dotdot,
448
-
parent,
449
-
rev: params.rev.as_deref().unwrap_or_default().to_string(),
450
-
readme,
382
+
Ok(XrpcResponse {
383
+
value: tree::Output {
384
+
files,
385
+
dotdot,
386
+
parent,
387
+
rev: params.rev.as_deref().unwrap_or_default().to_string(),
388
+
readme,
389
+
},
390
+
immutable,
451
391
})
452
392
})
453
393
.await
454
394
}
455
395
}
456
396
397
+
/// Resolve a revspec into a [`gix::Commit`] and an immutable flag.
398
+
///
399
+
/// A revspec is considered immutable if, and only if, it is an object ID.
457
400
fn resolve_rev<'repo>(
458
401
repo: &'repo gix::Repository,
459
402
rev: Option<&str>,
460
-
) -> Result<gix::Commit<'repo>, XrpcError> {
403
+
) -> Result<(gix::Commit<'repo>, bool), XrpcError> {
461
404
use std::str::FromStr as _;
462
405
463
-
let revision = if let Some(refspec) = rev {
406
+
Ok(if let Some(refspec) = rev {
464
407
match gix::ObjectId::from_str(refspec) {
465
-
Ok(id) => repo.find_commit(id).map_err(RefNotFound)?,
408
+
Ok(id) => (repo.find_commit(id).map_err(RefNotFound)?, true),
466
409
Err(_) => {
467
410
// Assume the revspec is a branch or tag.
468
411
let mut reference = repo.find_reference(refspec).map_err(RefNotFound)?;
469
-
reference.peel_to_commit().map_err(RefNotFound)?
412
+
(reference.peel_to_commit().map_err(RefNotFound)?, false)
470
413
}
471
414
}
472
415
} else {
473
-
repo.head_commit().map_err(RefNotFound)?
474
-
};
475
-
476
-
Ok(revision)
416
+
(repo.head_commit().map_err(RefNotFound)?, false)
417
+
})
477
418
}
+2
-2
crates/knot/src/public/xrpc/sh/tangled/repo.rs
+2
-2
crates/knot/src/public/xrpc/sh/tangled/repo.rs
···
47
47
xrpc_query!("sh.tangled.repo.blob", direct blob);
48
48
xrpc_query!("sh.tangled.repo.branches", branches);
49
49
xrpc_query!("sh.tangled.repo.getDefaultBranch", get_default_branch);
50
-
xrpc_query!("sh.tangled.repo.diff", diff);
50
+
xrpc_query!("sh.tangled.repo.diff", direct diff);
51
51
xrpc_query!("sh.tangled.repo.log", log);
52
52
xrpc_query!("sh.tangled.repo.tags", tags);
53
-
xrpc_query!("sh.tangled.repo.tree", tree);
53
+
xrpc_query!("sh.tangled.repo.tree", direct tree);
54
54
55
55
#[xrpc::query("sh.tangled.repo.languages")]
56
56
#[tracing::instrument(skip(_knot))]