use git::bstr::BString;use git_repository as git; use crate::OutputFormat; pub struct Options { pub format: OutputFormat, pub dry_run: bool, pub remote: Option<String>, /// If non-empty, override all ref-specs otherwise configured in the remote pub ref_specs: Vec<BString>, pub handshake_info: bool,} pub const PROGRESS_RANGE: std::ops::RangeInclusive<u8> = 1..=3; pub(crate) mod function { use anyhow::bail; use git_repository as git; use git_repository::{prelude::ObjectIdExt, refspec::match_group::validate::Fix, remote::fetch::Status}; use super::Options; use crate::OutputFormat; pub fn fetch<P>( repo: git::Repository, progress: P, mut out: impl std::io::Write, err: impl std::io::Write, Options { format, dry_run, remote, handshake_info, ref_specs, }: Options, ) -> anyhow::Result<()> where P: git::Progress, P::SubProgress: 'static, { if format != OutputFormat::Human { bail!("JSON output isn't yet supported for fetching."); } let mut remote = crate::repository::remote::by_name_or_url(&repo, remote.as_deref())?; if !ref_specs.is_empty() { remote.replace_refspecs(ref_specs.iter(), git::remote::Direction::Fetch)?; } let res: git::remote::fetch::Outcome = remote .connect(git::remote::Direction::Fetch, progress)? .prepare_fetch(Default::default())? .with_dry_run(dry_run) .receive(&git::interrupt::IS_INTERRUPTED)?; if handshake_info { writeln!(out, "Handshake Information")?; writeln!(out, "\t{:?}", res.ref_map.handshake)?; } let ref_specs = remote.refspecs(git::remote::Direction::Fetch); match res.status { Status::NoChange => { let show_unmapped = false; crate::repository::remote::refs::print_refmap( &repo, ref_specs, res.ref_map, show_unmapped, &mut out, err, ) } Status::DryRun { update_refs } => print_updates(&repo, update_refs, ref_specs, res.ref_map, &mut out, err), Status::Change { update_refs, write_pack_bundle, } => { print_updates(&repo, update_refs, ref_specs, res.ref_map, &mut out, err)?; if let Some(data_path) = write_pack_bundle.data_path { writeln!(out, "pack file: \"{}\"", data_path.display()).ok(); } if let Some(index_path) = write_pack_bundle.index_path { writeln!(out, "index file: \"{}\"", index_path.display()).ok(); } Ok(()) } }?; if dry_run { writeln!(out, "DRY-RUN: No ref was updated and no pack was received.").ok(); } Ok(()) } pub(crate) fn print_updates( repo: &git::Repository, update_refs: git::remote::fetch::refs::update::Outcome, refspecs: &[git::refspec::RefSpec], mut map: git::remote::fetch::RefMap, mut out: impl std::io::Write, mut err: impl std::io::Write, ) -> anyhow::Result<()> { let mut last_spec_index = usize::MAX; let mut updates = update_refs .iter_mapping_updates(&map.mappings, refspecs) .filter_map(|(update, mapping, spec, edit)| spec.map(|spec| (update, mapping, spec, edit))) .collect::<Vec<_>>(); updates.sort_by_key(|t| t.2); for (update, mapping, spec, edit) in updates { if mapping.spec_index != last_spec_index { last_spec_index = mapping.spec_index; spec.to_ref().write_to(&mut out)?; writeln!(out)?; } write!(out, "\t")?; match &mapping.remote { git::remote::fetch::Source::ObjectId(id) => { write!(out, "{}", id.attach(repo).shorten_or_id())?; } git::remote::fetch::Source::Ref(r) => { crate::repository::remote::refs::print_ref(&mut out, r)?; } }; match edit { Some(edit) => { writeln!(out, " -> {} [{}]", edit.name, update.mode) } None => writeln!(out, " (fetch only)"), }?; } if !map.fixes.is_empty() { writeln!( err, "The following destination refs were removed as they didn't start with 'ref/'" )?; map.fixes.sort_by(|l, r| match (l, r) { ( Fix::MappingWithPartialDestinationRemoved { spec: l, .. }, Fix::MappingWithPartialDestinationRemoved { spec: r, .. }, ) => l.cmp(r), }); let mut prev_spec = None; for fix in &map.fixes { match fix { Fix::MappingWithPartialDestinationRemoved { name, spec } => { if prev_spec.map_or(true, |prev_spec| prev_spec != spec) { prev_spec = spec.into(); spec.to_ref().write_to(&mut err)?; writeln!(err)?; } writeln!(err, "\t{name}")?; } } } } if map.remote_refs.len() - map.mappings.len() != 0 { writeln!( err, "server sent {} tips, {} were filtered due to {} refspec(s).", map.remote_refs.len(), map.remote_refs.len() - map.mappings.len(), refspecs.len() )?; } Ok(()) }}
use git::bstr::BString;
use git_repository as git;
use crate::OutputFormat;
pub struct Options {
pub format: OutputFormat,
pub dry_run: bool,
pub remote: Option<String>,
/// If non-empty, override all ref-specs otherwise configured in the remote
pub ref_specs: Vec<BString>,
pub handshake_info: bool,
}
pub const PROGRESS_RANGE: std::ops::RangeInclusive<u8> = 1..=3;
pub(crate) mod function {
use anyhow::bail;
use git_repository as git;
use git_repository::{prelude::ObjectIdExt, refspec::match_group::validate::Fix, remote::fetch::Status};
use super::Options;
use crate::OutputFormat;
pub fn fetch<P>(
repo: git::Repository,
progress: P,
mut out: impl std::io::Write,
err: impl std::io::Write,
Options {
format,
dry_run,
remote,
handshake_info,
ref_specs,
}: Options,
) -> anyhow::Result<()>
where
P: git::Progress,
P::SubProgress: 'static,
{
if format != OutputFormat::Human {
bail!("JSON output isn't yet supported for fetching.");
}
let mut remote = crate::repository::remote::by_name_or_url(&repo, remote.as_deref())?;
if !ref_specs.is_empty() {
remote.replace_refspecs(ref_specs.iter(), git::remote::Direction::Fetch)?;
}
let res: git::remote::fetch::Outcome = remote
.connect(git::remote::Direction::Fetch, progress)?
.prepare_fetch(Default::default())?
.with_dry_run(dry_run)
.receive(&git::interrupt::IS_INTERRUPTED)?;
if handshake_info {
writeln!(out, "Handshake Information")?;
writeln!(out, "\t{:?}", res.ref_map.handshake)?;
}
let ref_specs = remote.refspecs(git::remote::Direction::Fetch);
match res.status {
Status::NoChange => {
let show_unmapped = false;
crate::repository::remote::refs::print_refmap(
&repo,
ref_specs,
res.ref_map,
show_unmapped,
&mut out,
err,
)
}
Status::DryRun { update_refs } => print_updates(&repo, update_refs, ref_specs, res.ref_map, &mut out, err),
Status::Change {
update_refs,
write_pack_bundle,
} => {
print_updates(&repo, update_refs, ref_specs, res.ref_map, &mut out, err)?;
if let Some(data_path) = write_pack_bundle.data_path {
writeln!(out, "pack file: \"{}\"", data_path.display()).ok();
}
if let Some(index_path) = write_pack_bundle.index_path {
writeln!(out, "index file: \"{}\"", index_path.display()).ok();
}
Ok(())
}
}?;
if dry_run {
writeln!(out, "DRY-RUN: No ref was updated and no pack was received.").ok();
}
Ok(())
}
pub(crate) fn print_updates(
repo: &git::Repository,
update_refs: git::remote::fetch::refs::update::Outcome,
refspecs: &[git::refspec::RefSpec],
mut map: git::remote::fetch::RefMap,
mut out: impl std::io::Write,
mut err: impl std::io::Write,
) -> anyhow::Result<()> {
let mut last_spec_index = usize::MAX;
let mut updates = update_refs
.iter_mapping_updates(&map.mappings, refspecs)
.filter_map(|(update, mapping, spec, edit)| spec.map(|spec| (update, mapping, spec, edit)))
.collect::<Vec<_>>();
updates.sort_by_key(|t| t.2);
for (update, mapping, spec, edit) in updates {
if mapping.spec_index != last_spec_index {
last_spec_index = mapping.spec_index;
spec.to_ref().write_to(&mut out)?;
writeln!(out)?;
}
write!(out, "\t")?;
match &mapping.remote {
git::remote::fetch::Source::ObjectId(id) => {
write!(out, "{}", id.attach(repo).shorten_or_id())?;
}
git::remote::fetch::Source::Ref(r) => {
crate::repository::remote::refs::print_ref(&mut out, r)?;
}
};
match edit {
Some(edit) => {
writeln!(out, " -> {} [{}]", edit.name, update.mode)
}
None => writeln!(out, " (fetch only)"),
}?;
}
if !map.fixes.is_empty() {
writeln!(
err,
"The following destination refs were removed as they didn't start with 'ref/'"
)?;
map.fixes.sort_by(|l, r| match (l, r) {
(
Fix::MappingWithPartialDestinationRemoved { spec: l, .. },
Fix::MappingWithPartialDestinationRemoved { spec: r, .. },
) => l.cmp(r),
});
let mut prev_spec = None;
for fix in &map.fixes {
match fix {
Fix::MappingWithPartialDestinationRemoved { name, spec } => {
if prev_spec.map_or(true, |prev_spec| prev_spec != spec) {
prev_spec = spec.into();
spec.to_ref().write_to(&mut err)?;
writeln!(err)?;
}
writeln!(err, "\t{name}")?;
}
}
}
}
if map.remote_refs.len() - map.mappings.len() != 0 {
writeln!(
err,
"server sent {} tips, {} were filtered due to {} refspec(s).",
map.remote_refs.len(),
map.remote_refs.len() - map.mappings.len(),
refspecs.len()
)?;
}
Ok(())
}
}