use std::io::BufRead;
use std::path::Path;
use crate::push::{PushCommandError, PushOutcome, remote_tracking_refs, upload_in_range};
pub fn pre_push<R: BufRead>(
cwd: &Path,
remote: &str,
stdin: R,
dry_run: bool,
) -> Result<PushOutcome, PushCommandError> {
if std::env::var_os("GIT_LFS_SKIP_PUSH").is_some_and(|v| v != "0" && !v.is_empty()) {
return Ok(PushOutcome::default());
}
let mut includes: Vec<String> = Vec::new();
let mut excludes: Vec<String> = Vec::new();
let mut remote_refs: Vec<String> = Vec::new();
let mut needs_remote_tracking = false;
for line in stdin.lines() {
let line = line.map_err(PushCommandError::Io)?;
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 4 {
continue;
}
let local_sha = parts[1];
let remote_ref = parts[2];
let remote_sha = parts[3];
if is_zero_oid(local_sha) {
continue;
}
includes.push(local_sha.to_owned());
remote_refs.push(remote_ref.to_owned());
if is_zero_oid(remote_sha) {
needs_remote_tracking = true;
} else {
excludes.push(remote_sha.to_owned());
}
}
if includes.is_empty() {
return Ok(PushOutcome::default());
}
if needs_remote_tracking {
excludes.extend(remote_tracking_refs(cwd, remote)?);
}
remote_refs.sort();
remote_refs.dedup();
let refspec = if remote_refs.len() == 1 {
remote_refs.pop()
} else {
None
};
let inc: Vec<&str> = includes.iter().map(String::as_str).collect();
let exc: Vec<&str> = excludes.iter().map(String::as_str).collect();
upload_in_range(cwd, remote, &inc, &exc, refspec, dry_run)
}
fn is_zero_oid(s: &str) -> bool {
!s.is_empty() && s.bytes().all(|b| b == b'0')
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detects_zero_oids() {
assert!(is_zero_oid("0000000000000000000000000000000000000000"));
assert!(is_zero_oid(
"0000000000000000000000000000000000000000000000000000000000000000"
));
assert!(!is_zero_oid("0000000000000000000000000000000000000001"));
assert!(!is_zero_oid(""));
}
}