1use super::{GitCall, MODULE_NAME};
3use crate::runtime::StoreData;
4use crate::runtime::fuel;
5use crate::runtime::host::{
6 abi_arg, read_string_arg, register_host_value_async, write_submilli_string_struct,
7 write_submilli_uint8array_struct,
8};
9use crate::runtime::intrinsic_types::{IntrinsicTypes, build_intrinsic_types};
10use crate::stdlib::session::value;
11use serde::de::DeserializeOwned;
12use serde_json::Value;
13use wasmtime::{Caller, FuncType, HeapType, Linker, RefType, Result, Val, ValType, bail};
14
15pub fn install(linker: &mut Linker<StoreData>) -> Result<()> {
16 let engine = linker.engine().clone();
17 let intr = build_intrinsic_types(&engine)?;
18 install_repository_reads(linker, &engine, &intr)?;
19 install_repository_writes(linker, &engine, &intr)
20}
21
22fn install_repository_reads(
23 linker: &mut Linker<StoreData>,
24 engine: &wasmtime::Engine,
25 intr: &IntrinsicTypes,
26) -> Result<()> {
27 let argument = ValType::Ref(RefType::new(
28 true,
29 HeapType::ConcreteStruct(intr.object.clone()),
30 ));
31 let receiver = ValType::Ref(RefType::new(
32 false,
33 HeapType::ConcreteStruct(intr.object.clone()),
34 ));
35 let object = ValType::Ref(RefType::new(
36 false,
37 HeapType::ConcreteStruct(intr.object_shape.clone()),
38 ));
39 let bytes = ValType::Ref(RefType::new(
40 false,
41 HeapType::ConcreteStruct(intr.uint8_array.clone()),
42 ));
43 let array = ValType::Ref(RefType::new(
44 false,
45 HeapType::ConcreteStruct(intr.array.clone()),
46 ));
47 let repository = crate::mangle::package_symbol(MODULE_NAME, "Repository");
48 register_host_value_async(
49 linker,
50 MODULE_NAME,
51 crate::mangle::extend(&repository, "status"),
52 FuncType::new(engine, [receiver.clone()], [object.clone()]),
53 |caller, params| {
54 Box::pin(async move {
55 let call = GitCall::new(caller, "status")?;
56 let arguments = ();
57 let path = repository_path(caller, abi_arg(params, 0)?)?;
58 let result = call.execute(caller, path, arguments, super::status).await?;
59 encode_json(caller, &result, call.max_bytes)
60 })
61 },
62 )?;
63 register_host_value_async(
64 linker,
65 MODULE_NAME,
66 crate::mangle::extend(&repository, "log"),
67 FuncType::new(
68 engine,
69 [receiver.clone(), argument.clone()],
70 [object.clone()],
71 ),
72 |caller, params| {
73 Box::pin(async move {
74 prepare_history_cache(caller)?;
75 let call = GitCall::new(caller, "log")?;
76 let arguments = read_argument(
77 caller,
78 abi_arg(params, 1)?,
79 Some(serde_json::json!({})),
80 &call,
81 )
82 .await?;
83 let path = repository_path(caller, abi_arg(params, 0)?)?;
84 let result = call.execute(caller, path, arguments, super::log).await?;
85 encode_json(caller, &result, call.max_bytes)
86 })
87 },
88 )?;
89 register_host_value_async(
90 linker,
91 MODULE_NAME,
92 crate::mangle::extend(&repository, "diff"),
93 FuncType::new(
94 engine,
95 [receiver.clone(), argument.clone()],
96 [object.clone()],
97 ),
98 |caller, params| {
99 Box::pin(async move {
100 let call = GitCall::new(caller, "diff")?;
101 let arguments = read_argument(
102 caller,
103 abi_arg(params, 1)?,
104 Some(serde_json::json!({})),
105 &call,
106 )
107 .await?;
108 let path = repository_path(caller, abi_arg(params, 0)?)?;
109 let result = call.execute(caller, path, arguments, super::diff).await?;
110 encode_json(caller, &result, call.max_bytes)
111 })
112 },
113 )?;
114 register_host_value_async(
115 linker,
116 MODULE_NAME,
117 crate::mangle::extend(&repository, "show"),
118 FuncType::new(
119 engine,
120 [receiver.clone(), argument.clone(), argument.clone()],
121 [bytes.clone()],
122 ),
123 |caller, params| {
124 Box::pin(async move {
125 let call = GitCall::new(caller, "show")?;
126 let arguments = super::ShowArguments {
127 revision: read_argument(caller, abi_arg(params, 1)?, None, &call).await?,
128 path: read_argument(caller, abi_arg(params, 2)?, None, &call).await?,
129 };
130 let path = repository_path(caller, abi_arg(params, 0)?)?;
131 let result = call.execute(caller, path, arguments, super::show).await?;
132 Ok(Val::AnyRef(Some(
133 write_submilli_uint8array_struct(caller, &result.0)?.to_anyref(),
134 )))
135 })
136 },
137 )?;
138 register_host_value_async(
139 linker,
140 MODULE_NAME,
141 crate::mangle::extend(&repository, "branches"),
142 FuncType::new(engine, [receiver.clone()], [array.clone()]),
143 |caller, params| {
144 Box::pin(async move {
145 let call = GitCall::new(caller, "branches")?;
146 let arguments = ();
147 let path = repository_path(caller, abi_arg(params, 0)?)?;
148 let result = call
149 .execute(caller, path, arguments, super::branches)
150 .await?;
151 encode_json(caller, &result, call.max_bytes)
152 })
153 },
154 )?;
155 register_host_value_async(
156 linker,
157 MODULE_NAME,
158 crate::mangle::extend(&repository, "remotes"),
159 FuncType::new(engine, [receiver.clone()], [array.clone()]),
160 |caller, params| {
161 Box::pin(async move {
162 let call = GitCall::new(caller, "remotes")?;
163 let arguments = ();
164 let path = repository_path(caller, abi_arg(params, 0)?)?;
165 let result = call
166 .execute(caller, path, arguments, super::remotes)
167 .await?;
168 encode_json(caller, &result, call.max_bytes)
169 })
170 },
171 )?;
172 Ok(())
173}
174
175fn install_repository_writes(
176 linker: &mut Linker<StoreData>,
177 engine: &wasmtime::Engine,
178 intr: &IntrinsicTypes,
179) -> Result<()> {
180 let argument = ValType::Ref(RefType::new(
181 true,
182 HeapType::ConcreteStruct(intr.object.clone()),
183 ));
184 let receiver = ValType::Ref(RefType::new(
185 false,
186 HeapType::ConcreteStruct(intr.object.clone()),
187 ));
188 let object = ValType::Ref(RefType::new(
189 false,
190 HeapType::ConcreteStruct(intr.object_shape.clone()),
191 ));
192 let completion = argument.clone();
194 let repository = crate::mangle::package_symbol(MODULE_NAME, "Repository");
195 register_host_value_async(
196 linker,
197 MODULE_NAME,
198 crate::mangle::extend(&repository, "add"),
199 FuncType::new(
200 engine,
201 [receiver.clone(), argument.clone()],
202 [completion.clone()],
203 ),
204 |caller, params| {
205 Box::pin(async move {
206 invalidate_history_cache(caller)?;
207 let call = GitCall::new(caller, "add")?;
208 let arguments = read_argument(caller, abi_arg(params, 1)?, None, &call).await?;
209 let path = repository_path(caller, abi_arg(params, 0)?)?;
210 call.execute(caller, path, arguments, super::add).await?;
211 crate::runtime::prelude::undefined::value(caller)
212 })
213 },
214 )?;
215 register_host_value_async(
216 linker,
217 MODULE_NAME,
218 crate::mangle::extend(&repository, "commit"),
219 FuncType::new(
220 engine,
221 [receiver.clone(), argument.clone()],
222 [ValType::Ref(RefType::new(
223 false,
224 intr.string.clone().into(),
225 ))],
226 ),
227 |caller, params| {
228 Box::pin(async move {
229 invalidate_history_cache(caller)?;
230 let call = GitCall::new(caller, "commit")?;
231 let arguments = read_argument(caller, abi_arg(params, 1)?, None, &call).await?;
232 let path = repository_path(caller, abi_arg(params, 0)?)?;
233 let result = call.execute(caller, path, arguments, super::commit).await?;
234 Ok(Val::AnyRef(Some(
235 write_submilli_string_struct(caller, &result)?.to_anyref(),
236 )))
237 })
238 },
239 )?;
240 register_host_value_async(
241 linker,
242 MODULE_NAME,
243 crate::mangle::extend(&repository, "createBranch"),
244 FuncType::new(
245 engine,
246 [receiver.clone(), argument.clone(), argument.clone()],
247 [completion.clone()],
248 ),
249 |caller, params| {
250 Box::pin(async move {
251 invalidate_history_cache(caller)?;
252 let call = GitCall::new(caller, "createBranch")?;
253 let arguments = super::BranchArguments {
254 name: read_argument(caller, abi_arg(params, 1)?, None, &call).await?,
255 start: read_argument(
256 caller,
257 abi_arg(params, 2)?,
258 Some(String::from("HEAD")),
259 &call,
260 )
261 .await?,
262 };
263 let path = repository_path(caller, abi_arg(params, 0)?)?;
264 call.execute(caller, path, arguments, super::create_branch)
265 .await?;
266 crate::runtime::prelude::undefined::value(caller)
267 })
268 },
269 )?;
270 register_host_value_async(
271 linker,
272 MODULE_NAME,
273 crate::mangle::extend(&repository, "switchBranch"),
274 FuncType::new(
275 engine,
276 [receiver.clone(), argument.clone()],
277 [completion.clone()],
278 ),
279 |caller, params| {
280 Box::pin(async move {
281 invalidate_history_cache(caller)?;
282 let call = GitCall::new(caller, "switchBranch")?;
283 let arguments = read_argument(caller, abi_arg(params, 1)?, None, &call).await?;
284 let path = repository_path(caller, abi_arg(params, 0)?)?;
285 call.execute(caller, path, arguments, super::switch_branch)
286 .await?;
287 crate::runtime::prelude::undefined::value(caller)
288 })
289 },
290 )?;
291 register_host_value_async(
292 linker,
293 MODULE_NAME,
294 crate::mangle::extend(&repository, "addRemote"),
295 FuncType::new(
296 engine,
297 [receiver.clone(), argument.clone(), argument.clone()],
298 [completion.clone()],
299 ),
300 |caller, params| {
301 Box::pin(async move {
302 let call = GitCall::new(caller, "addRemote")?;
303 let arguments = super::RemoteArguments {
304 name: read_argument(caller, abi_arg(params, 1)?, None, &call).await?,
305 url: read_argument(caller, abi_arg(params, 2)?, None, &call).await?,
306 };
307 let path = repository_path(caller, abi_arg(params, 0)?)?;
308 call.execute(caller, path, arguments, super::add_remote)
309 .await?;
310 crate::runtime::prelude::undefined::value(caller)
311 })
312 },
313 )?;
314 register_host_value_async(
315 linker,
316 MODULE_NAME,
317 crate::mangle::extend(&repository, "setRemoteUrl"),
318 FuncType::new(
319 engine,
320 [receiver.clone(), argument.clone(), argument.clone()],
321 [completion.clone()],
322 ),
323 |caller, params| {
324 Box::pin(async move {
325 let call = GitCall::new(caller, "setRemoteUrl")?;
326 let arguments = super::RemoteArguments {
327 name: read_argument(caller, abi_arg(params, 1)?, None, &call).await?,
328 url: read_argument(caller, abi_arg(params, 2)?, None, &call).await?,
329 };
330 let path = repository_path(caller, abi_arg(params, 0)?)?;
331 call.execute(caller, path, arguments, super::set_remote_url)
332 .await?;
333 crate::runtime::prelude::undefined::value(caller)
334 })
335 },
336 )?;
337 register_host_value_async(
338 linker,
339 MODULE_NAME,
340 crate::mangle::extend(&repository, "fetch"),
341 FuncType::new(
342 engine,
343 [receiver.clone(), argument.clone(), argument.clone()],
344 [object.clone()],
345 ),
346 |caller, params| {
347 Box::pin(async move {
348 invalidate_history_cache(caller)?;
349 let call = GitCall::new(caller, "fetch")?;
350 let arguments = super::FetchArguments {
351 remote: read_argument(
352 caller,
353 abi_arg(params, 1)?,
354 Some(String::from("origin")),
355 &call,
356 )
357 .await?,
358 branch: read_argument(caller, abi_arg(params, 2)?, Some(String::new()), &call)
359 .await?,
360 };
361 let path = repository_path(caller, abi_arg(params, 0)?)?;
362 let result = call.execute(caller, path, arguments, super::fetch).await?;
363 encode_json(caller, &result, call.max_bytes)
364 })
365 },
366 )?;
367 register_host_value_async(
368 linker,
369 MODULE_NAME,
370 crate::mangle::extend(&repository, "pull"),
371 FuncType::new(
372 engine,
373 [receiver.clone(), argument.clone(), argument.clone()],
374 [object.clone()],
375 ),
376 |caller, params| {
377 Box::pin(async move {
378 invalidate_history_cache(caller)?;
379 let call = GitCall::new(caller, "pull")?;
380 let arguments = super::FetchArguments {
381 remote: read_argument(
382 caller,
383 abi_arg(params, 1)?,
384 Some(String::from("origin")),
385 &call,
386 )
387 .await?,
388 branch: read_argument(caller, abi_arg(params, 2)?, Some(String::new()), &call)
389 .await?,
390 };
391 let path = repository_path(caller, abi_arg(params, 0)?)?;
392 let result = call.execute(caller, path, arguments, super::pull).await?;
393 encode_json(caller, &result, call.max_bytes)
394 })
395 },
396 )?;
397 Ok(())
398}
399
400pub(super) async fn open_repository(
401 caller: &mut Caller<'_, StoreData>,
402 params: &[Val],
403) -> Result<Val> {
404 let call = GitCall::new(caller, "open")?;
405 let path: String = read_argument(caller, abi_arg(params, 0)?, None, &call).await?;
406 let path = normalize_repository_path(caller, &path)?;
407 call.execute(caller, path.clone(), (), super::open).await?;
408 Ok(Val::AnyRef(Some(
409 write_submilli_string_struct(caller, &path)?.to_anyref(),
410 )))
411}
412
413pub(super) async fn init_repository(
414 caller: &mut Caller<'_, StoreData>,
415 params: &[Val],
416) -> Result<Val> {
417 invalidate_history_cache(caller)?;
418 let call = GitCall::new(caller, "init")?;
419 let path: String = read_argument(caller, abi_arg(params, 0)?, None, &call).await?;
420 let options = read_argument(
421 caller,
422 abi_arg(params, 1)?,
423 Some(serde_json::json!({})),
424 &call,
425 )
426 .await?;
427 let path = normalize_repository_path(caller, &path)?;
428 call.execute(caller, path.clone(), options, super::init)
429 .await?;
430 Ok(Val::AnyRef(Some(
431 write_submilli_string_struct(caller, &path)?.to_anyref(),
432 )))
433}
434
435pub(super) async fn clone_repository(
436 caller: &mut Caller<'_, StoreData>,
437 params: &[Val],
438) -> Result<Val> {
439 invalidate_history_cache(caller)?;
440 let call = GitCall::new(caller, "clone")?;
441 let url = read_argument(caller, abi_arg(params, 0)?, None, &call).await?;
442 let path: String = read_argument(caller, abi_arg(params, 1)?, None, &call).await?;
443 let options = read_argument(
444 caller,
445 abi_arg(params, 2)?,
446 Some(serde_json::json!({})),
447 &call,
448 )
449 .await?;
450 let path = normalize_repository_path(caller, &path)?;
451 call.execute(
452 caller,
453 path.clone(),
454 super::CloneArguments { url, options },
455 super::clone_repository,
456 )
457 .await?;
458 Ok(Val::AnyRef(Some(
459 write_submilli_string_struct(caller, &path)?.to_anyref(),
460 )))
461}
462
463async fn read_argument<T: DeserializeOwned>(
464 caller: &mut Caller<'_, StoreData>,
465 argument: &Val,
466 default: Option<T>,
467 call: &GitCall,
468) -> Result<T> {
469 if crate::runtime::prelude::undefined::is_undefined(caller, argument)? {
470 return default.ok_or_else(|| {
471 wasmtime::Error::msg(format!(
472 "git: undefined is not accepted for this argument to {}",
473 call.operation
474 ))
475 });
476 }
477 let units = super::before_deadline(call.deadline, value::serialize(caller, argument)).await??;
480 if units.len() as u64 * 2 > call.max_bytes {
481 bail!("git: argument size limit exceeded");
482 }
483 fuel::charge(&mut *caller, fuel::PARSE, units.len() as u64)?;
484 let text = String::from_utf16(&units)
485 .map_err(|_| wasmtime::Error::msg("git: arguments must be valid Unicode"))?;
486 Ok(serde_json::from_str(&text)?)
487}
488
489fn repository_path(caller: &mut Caller<'_, StoreData>, receiver: &Val) -> Result<String> {
490 let field = super::class::path(caller, receiver)?;
491 let path = read_string_arg(caller, &field, "git repository")?;
492 normalize_repository_path(caller, &path)
493}
494
495fn normalize_repository_path(caller: &Caller<'_, StoreData>, path: &str) -> Result<String> {
496 let path = crate::runtime::fs::guest_normalize(caller.data().vfs.cwd(), path)?;
497 if path
498 .split('/')
499 .any(|part| part.eq_ignore_ascii_case(".git") || super::stage::is_reserved_stage_name(part))
500 {
501 bail!("git: repository path targets protected metadata");
502 }
503 Ok(path)
504}
505
506fn prepare_history_cache(caller: &mut Caller<'_, StoreData>) -> Result<()> {
507 if caller.data().git.is_none() {
508 bail!("submilli:git is disabled; configure the blueprint git block");
509 }
510 if caller.data().git_history.is_none() {
511 let cache = super::log_cache::Cache::new(&caller.data().tenant_limits)?;
512 caller.data_mut().git_history = Some(std::sync::Arc::new(cache));
513 }
514 Ok(())
515}
516
517fn invalidate_history_cache(caller: &Caller<'_, StoreData>) -> Result<()> {
518 if let Some(cache) = &caller.data().git_history {
519 cache.invalidate()?;
520 }
521 Ok(())
522}
523
524fn encode_json(caller: &mut Caller<'_, StoreData>, result: &Value, max_bytes: u64) -> Result<Val> {
525 let text = serde_json::to_string(result)?;
526 if text.len() as u64 > max_bytes {
527 bail!("git: result limit exceeded");
528 }
529 fuel::charge(&mut *caller, fuel::PARSE, 2 * text.len() as u64)?;
530 value::deserialize(caller, &text.encode_utf16().collect::<Vec<_>>())
531}