379 lines · 13.4 KB · Rust
RawDownload
/// Print the effective URL or URLs of the selected remote.////// Without an explicit remote, selection follows the fetch or push configuration for the current branch according to `direction`.pub fn url(    repo: gix::Repository,    name: Option<&str>,    direction: gix::remote::Direction,    all: bool,    mut out: impl std::io::Write,) -> anyhow::Result<()> {    let remote = match (name, direction) {        (Some(name), _) => repo.find_fetch_remote(Some(name.into()))?,        (None, gix::remote::Direction::Fetch) => repo.find_fetch_remote(None)?,        (None, gix::remote::Direction::Push) => repo            .head()?            .into_remote(gix::remote::Direction::Push)            .or_else(|| repo.find_default_remote(gix::remote::Direction::Push))            .transpose()?            .ok_or_else(|| anyhow::anyhow!("Could not determine a remote for pushing"))?,    };    if all {        let mut urls = remote.urls(direction).peekable();        if urls.peek().is_none() {            anyhow::bail!("The remote has no {} URL", direction.as_str());        }        for url in urls {            out.write_all(&url.to_bstring())?;            out.write_all(b"\n")?;        }    } else {        let url = remote            .url(direction)            .ok_or_else(|| anyhow::anyhow!("The remote has no {} URL", direction.as_str()))?;        out.write_all(&url.to_bstring())?;        out.write_all(b"\n")?;    }    Ok(())} #[cfg(any(feature = "blocking-client", feature = "async-client"))]mod refs_impl {    use anyhow::bail;    use gix::{        protocol::handshake,        refspec::{RefSpec, match_group::validate::Fix},        remote::fetch::refmap::Source,    };     use super::by_name_or_url;    use crate::OutputFormat;     pub mod refs {        use gix::bstr::BString;         use crate::OutputFormat;         pub const PROGRESS_RANGE: std::ops::RangeInclusive<u8> = 1..=2;         pub enum Kind {            Remote,            Tracking {                ref_specs: Vec<BString>,                show_unmapped_remote_refs: bool,            },        }         pub struct Options {            pub format: OutputFormat,            pub name_or_url: Option<String>,            pub handshake_info: bool,        }         pub(crate) use super::{print, print_ref};    }     #[gix::protocol::bisync::bisync]    pub async fn refs_fn(        repo: gix::Repository,        kind: refs::Kind,        mut progress: impl gix::Progress,        mut out: impl std::io::Write,        err: impl std::io::Write,        refs::Options {            format,            name_or_url,            handshake_info,        }: refs::Options,    ) -> anyhow::Result<()> {        use anyhow::Context;        let mut remote = by_name_or_url(&repo, name_or_url.as_deref())?;        let show_unmapped = if let refs::Kind::Tracking {            ref_specs,            show_unmapped_remote_refs,        } = &kind        {            if format != OutputFormat::Human {                bail!("JSON output isn't yet supported for listing ref-mappings.");            }            if !ref_specs.is_empty() {                remote.replace_refspecs(ref_specs.iter(), gix::remote::Direction::Fetch)?;                remote = remote.with_fetch_tags(gix::remote::fetch::Tags::None);            }            *show_unmapped_remote_refs        } else {            false        };        progress.info(format!(            "Connecting to {:?}",            remote                .url(gix::remote::Direction::Fetch)                .context("Remote didn't have a URL to connect to")?                .to_bstring()        ));        let (map, handshake) = remote            .connect(gix::remote::Direction::Fetch)            .await?            .ref_map(                &mut progress,                gix::remote::ref_map::Options {                    prefix_from_spec_as_filter_on_remote: !matches!(kind, refs::Kind::Remote),                    ..Default::default()                },            )            .await?;         if handshake_info {            writeln!(out, "Handshake Information")?;            writeln!(out, "\t{handshake:?}")?;        }        match kind {            refs::Kind::Tracking { .. } => print_refmap(                &repo,                remote.refspecs(gix::remote::Direction::Fetch),                map,                show_unmapped,                out,                err,            ),            refs::Kind::Remote => {                match format {                    OutputFormat::Human => drop(print(out, &map.remote_refs)),                    #[cfg(feature = "serde")]                    OutputFormat::Json => serde_json::to_writer_pretty(                        out,                        &map.remote_refs.into_iter().map(JsonRef::from).collect::<Vec<_>>(),                    )?,                }                Ok(())            }        }    }     pub(crate) fn print_refmap(        repo: &gix::Repository,        refspecs: &[RefSpec],        mut map: gix::remote::fetch::RefMap,        show_unmapped_remotes: bool,        mut out: impl std::io::Write,        mut err: impl std::io::Write,    ) -> anyhow::Result<()> {        let mut last_spec_index = gix::remote::fetch::refmap::SpecIndex::ExplicitInRemote(usize::MAX);        map.mappings.sort_by_key(|m| m.spec_index);        for mapping in &map.mappings {            if mapping.spec_index != last_spec_index {                last_spec_index = mapping.spec_index;                let spec = mapping                    .spec_index                    .get(refspecs, &map.extra_refspecs)                    .expect("refspecs here are the ones used for mapping");                spec.to_ref().write_to(&mut out)?;                let is_implicit = mapping.spec_index.implicit_index().is_some();                if is_implicit {                    write!(&mut out, " (implicit")?;                    if spec.to_ref()                        == gix::remote::fetch::Tags::Included                            .to_refspec()                            .expect("always yields refspec")                    {                        write!(&mut out, ", due to auto-tag")?;                    }                    write!(&mut out, ")")?;                }                writeln!(out)?;            }             write!(out, "\t")?;            let target_id = match &mapping.remote {                gix::remote::fetch::refmap::Source::ObjectId(id) => {                    write!(out, "{id}")?;                    id                }                gix::remote::fetch::refmap::Source::Ref(r) => print_ref(&mut out, r)?,            };            match &mapping.local {                Some(local) => {                    write!(out, " -> {local} ")?;                    match repo.try_find_reference(local)? {                        Some(tracking) => {                            let msg = match tracking.try_id() {                                Some(id) => {                                    if id.as_ref() == target_id {                                        "[up-to-date]"                                    } else {                                        "[changed]"                                    }                                }                                None => "[skipped]",                            };                            writeln!(out, "{msg}")                        }                        None => writeln!(out, "[new]"),                    }                }                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.is_some_and(|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()            )?;            if show_unmapped_remotes {                writeln!(&mut out, "\nFiltered: ")?;                for remote_ref in map.remote_refs.iter().filter(|r| {                    !map.mappings.iter().any(|m| match &m.remote {                        Source::Ref(other) => other == *r,                        Source::ObjectId(_) => false,                    })                }) {                    print_ref(&mut out, remote_ref)?;                    writeln!(&mut out)?;                }            }        }        if refspecs.is_empty() {            bail!(                "Without refspecs there is nothing to show here. Add refspecs as arguments or configure them in .git/config."            )        }        Ok(())    }     #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]    pub enum JsonRef {        Peeled {            path: String,            tag: String,            object: String,        },        Direct {            path: String,            object: String,        },        Unborn {            path: String,            target: String,        },        Symbolic {            path: String,            #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]            tag: Option<String>,            target: String,            object: String,        },    }     impl From<handshake::Ref> for JsonRef {        fn from(value: handshake::Ref) -> Self {            match value {                handshake::Ref::Unborn { full_ref_name, target } => JsonRef::Unborn {                    path: full_ref_name.to_string(),                    target: target.to_string(),                },                handshake::Ref::Direct {                    full_ref_name: path,                    object,                } => JsonRef::Direct {                    path: path.to_string(),                    object: object.to_string(),                },                handshake::Ref::Symbolic {                    full_ref_name: path,                    tag,                    target,                    object,                } => JsonRef::Symbolic {                    path: path.to_string(),                    tag: tag.map(|t| t.to_string()),                    target: target.to_string(),                    object: object.to_string(),                },                handshake::Ref::Peeled {                    full_ref_name: path,                    tag,                    object,                } => JsonRef::Peeled {                    path: path.to_string(),                    tag: tag.to_string(),                    object: object.to_string(),                },            }        }    }     pub(crate) fn print_ref(mut out: impl std::io::Write, r: &handshake::Ref) -> std::io::Result<&gix::hash::oid> {        match r {            handshake::Ref::Direct {                full_ref_name: path,                object,            } => write!(&mut out, "{object} {path}").map(|_| object.as_ref()),            handshake::Ref::Peeled {                full_ref_name: path,                tag,                object,            } => write!(&mut out, "{tag} {path} object:{object}").map(|_| tag.as_ref()),            handshake::Ref::Symbolic {                full_ref_name: path,                tag,                target,                object,            } => match tag {                Some(tag) => {                    write!(&mut out, "{tag} {path} symref-target:{target} peeled:{object}").map(|_| tag.as_ref())                }                None => write!(&mut out, "{object} {path} symref-target:{target}").map(|_| object.as_ref()),            },            handshake::Ref::Unborn { full_ref_name, target } => {                static NULL: gix::hash::ObjectId = gix::hash::ObjectId::null(gix::hash::Kind::Sha1);                write!(&mut out, "unborn {full_ref_name} symref-target:{target}").map(|_| NULL.as_ref())            }        }    }     pub(crate) fn print(mut out: impl std::io::Write, refs: &[handshake::Ref]) -> std::io::Result<()> {        for r in refs {            print_ref(&mut out, r)?;            writeln!(out)?;        }        Ok(())    }}#[cfg(any(feature = "blocking-client", feature = "async-client"))]pub use refs_impl::{JsonRef, refs, refs_fn as refs}; #[cfg(any(feature = "blocking-client", feature = "async-client"))]pub(crate) fn by_name_or_url<'repo>(    repo: &'repo gix::Repository,    name_or_url: Option<&str>,) -> anyhow::Result<gix::Remote<'repo>> {    repo.find_fetch_remote(name_or_url.map(Into::into)).map_err(Into::into)}