Skip to main content

git_policy_cli/
lfs_bounded_push.rs

1//! What:
2//!  Run one bounded `git push` of the auto-push, preceded by the Git LFS upload when the repository has one.
3//! Why:
4//!  The auto-push and its reconcile pushes publish refs, so they upload the LFS objects the pushed commits point to
5//!  exactly as a typed `git push` does. The wrapper is the parent of both children and captures their output, so it
6//!  owns the explanation: a missing `git-lfs` is refused before anything starts, a failed upload ends the attempt with
7//!  the full explanation after the program's own output, and the push is not started. The push carries no hook, and no
8//!  process other than Git and `git-lfs` is started for this, none of them the wrapper.
9//!
10//! In TS you'd write (pseudocode):
11//! ```ts
12//! const failed = plan && await uploadBounded(plan); if (failed) return failed; return await runBoundedPush(pushCommand(git, args));
13//! ```
14
15/// The attempt record, the coordinator's Git and its error type.
16use super::auto_push_single_flight::{PushAttempt, PushGit, SingleFlightError, git_command, run_query};
17/// The deadline, the grace, the low-speed abort and the push itself.
18use super::bounded_push::{STOP_GRACE, apply_low_speed_defaults, push_deadline, run_bounded_push, strip_one_line_break};
19/// The check that the program can start.
20use super::lfs_push_precheck::lfs_refusal;
21/// What to upload and the bounded run of it.
22use super::lfs_upload::run_upload_bounded;
23use super::lfs_upload_plan::plan_upload;
24/// Owned OS strings for Git arguments.
25use std::ffi::OsString;
26
27/// Exit status of a push the wrapper refused before anything started.
28const REFUSED_EXIT_CODE: i64 = 1;
29
30/// What:
31///  Run `git <arguments>` (a `push`) as a bounded child, after the upload when the repository has LFS keys.
32/// Why:
33///   `arguments[0]` is `push`; what follows is its option region, which the pre-check and the plan read.
34///
35/// In TS you'd write (pseudocode):
36/// ```ts
37/// async function runLfsAwarePush(git: PushGit, args: string[]): Promise<PushAttempt>;
38/// ```
39pub fn run_push(git: &PushGit, arguments: &[OsString]) -> Result<PushAttempt, SingleFlightError> {
40    let region: &[OsString] = arguments.get(1..).unwrap_or_default();
41    if git.lfs {
42        if let Some(refusal) = lfs_refusal(&[], region, git.environment.as_slice()) {
43            return Ok(PushAttempt {
44                exit_code: REFUSED_EXIT_CODE,
45                output: strip_one_line_break(refusal.as_str()).to_owned(),
46            });
47        }
48        let mut query = |query_arguments: &[&str]| -> Option<String> {
49            let owned: Vec<OsString> = query_arguments.iter().map(OsString::from).collect();
50            let output = run_query(git, owned.as_slice()).ok()?;
51            if output.code != Some(0) {
52                return None;
53            }
54            return Some(String::from_utf8_lossy(output.stdout.as_slice()).trim().to_owned());
55        };
56        if let Some(plan) = plan_upload(&[], region, git.environment.as_slice(), git.cwd.as_path(), &mut query)
57            && let Some(failed) = run_upload_bounded(git, &plan)?
58        {
59            return Ok(failed);
60        }
61    }
62    let mut command = git_command(git, arguments);
63    apply_low_speed_defaults(&mut command, git.environment.as_slice());
64    return run_bounded_push(command, push_deadline(git.environment.as_slice()), STOP_GRACE, git.environment.as_slice());
65}