Skip to content

Commit

Permalink
feat: gix clone and gix fetch with controls for shallow repositor…
Browse files Browse the repository at this point in the history
…ies.

TBD: more elaborate documentation
  • Loading branch information
Byron committed Mar 11, 2023
1 parent 93d412c commit 9723e1a
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 5 deletions.
7 changes: 5 additions & 2 deletions gitoxide-core/src/repository/clone.rs
Expand Up @@ -5,6 +5,7 @@ pub struct Options {
pub bare: bool,
pub handshake_info: bool,
pub no_tags: bool,
pub shallow: gix::remote::fetch::Shallow,
}

pub const PROGRESS_RANGE: std::ops::RangeInclusive<u8> = 1..=3;
Expand All @@ -30,6 +31,7 @@ pub(crate) mod function {
handshake_info,
bare,
no_tags,
shallow,
}: Options,
) -> anyhow::Result<()>
where
Expand Down Expand Up @@ -66,8 +68,9 @@ pub(crate) mod function {
if no_tags {
prepare = prepare.configure_remote(|r| Ok(r.with_fetch_tags(gix::remote::fetch::Tags::None)));
}
let (mut checkout, fetch_outcome) =
prepare.fetch_then_checkout(&mut progress, &gix::interrupt::IS_INTERRUPTED)?;
let (mut checkout, fetch_outcome) = prepare
.with_shallow(shallow)
.fetch_then_checkout(&mut progress, &gix::interrupt::IS_INTERRUPTED)?;

let (repo, outcome) = if bare {
(checkout.persist(), None)
Expand Down
3 changes: 3 additions & 0 deletions gitoxide-core/src/repository/fetch.rs
Expand Up @@ -8,6 +8,7 @@ pub struct Options {
pub remote: Option<String>,
/// If non-empty, override all ref-specs otherwise configured in the remote
pub ref_specs: Vec<BString>,
pub shallow: gix::remote::fetch::Shallow,
pub handshake_info: bool,
}

Expand All @@ -30,6 +31,7 @@ pub(crate) mod function {
dry_run,
remote,
handshake_info,
shallow,
ref_specs,
}: Options,
) -> anyhow::Result<()>
Expand All @@ -50,6 +52,7 @@ pub(crate) mod function {
.connect(gix::remote::Direction::Fetch, progress)?
.prepare_fetch(Default::default())?
.with_dry_run(dry_run)
.with_shallow(shallow)
.receive(&gix::interrupt::IS_INTERRUPTED)?;

if handshake_info {
Expand Down
4 changes: 4 additions & 0 deletions src/plumbing/main.rs
Expand Up @@ -130,13 +130,15 @@ pub fn main() -> Result<()> {
bare,
no_tags,
remote,
shallow,
directory,
}) => {
let opts = core::repository::clone::Options {
format,
bare,
handshake_info,
no_tags,
shallow: shallow.into(),
};
prepare_and_run(
"clone",
Expand All @@ -152,13 +154,15 @@ pub fn main() -> Result<()> {
dry_run,
handshake_info,
remote,
shallow,
ref_spec,
}) => {
let opts = core::repository::fetch::Options {
format,
dry_run,
remote,
handshake_info,
shallow: shallow.into(),
ref_specs: ref_spec,
};
prepare_and_run(
Expand Down
87 changes: 87 additions & 0 deletions src/plumbing/options/mod.rs
Expand Up @@ -135,6 +135,9 @@ pub mod config {

#[cfg(feature = "gitoxide-core-blocking-client")]
pub mod fetch {
use gix::remote::fetch::Shallow;
use std::num::NonZeroU32;

#[derive(Debug, clap::Parser)]
pub struct Platform {
/// Don't change the local repository, but otherwise try to be as accurate as possible.
Expand All @@ -145,6 +148,9 @@ pub mod fetch {
#[clap(long, short = 'H')]
pub handshake_info: bool,

#[clap(flatten)]
pub shallow: ShallowOptions,

/// The name of the remote to connect to, or the url of the remote to connect to directly.
///
/// If unset, the current branch will determine the remote.
Expand All @@ -155,10 +161,56 @@ pub mod fetch {
#[clap(value_parser = crate::shared::AsBString)]
pub ref_spec: Vec<gix::bstr::BString>,
}

#[derive(Debug, clap::Parser)]
pub struct ShallowOptions {
/// Fetch with the history truncated to the given number of commits as seen from the remote.
#[clap(long, conflicts_with_all = ["shallow_since", "shallow_exclude", "deepen", "unshallow"])]
pub depth: Option<NonZeroU32>,

/// Extend the current shallow boundary by the given amount of commits, with 0 meaning no change.
#[clap(long, value_name = "DEPTH", conflicts_with_all = ["depth", "shallow_since", "shallow_exclude", "unshallow"])]
pub deepen: Option<u32>,

/// Cutoff all history past the given date. Can be combined with shallow-exclude.
#[clap(long, value_parser = crate::shared::AsTime, value_name = "DATE", conflicts_with_all = ["depth", "deepen", "unshallow"])]
pub shallow_since: Option<gix::date::Time>,

/// Cutoff all history past the tag-name or ref-name. Can be combined with shallow-since.
#[clap(long, value_parser = crate::shared::AsPartialRefName, value_name = "REF_NAME", conflicts_with_all = ["depth", "deepen", "unshallow"])]
pub shallow_exclude: Vec<gix::refs::PartialName>,

/// Remove the shallow boundary and fetch the entire history available on the remote.
#[clap(long, conflicts_with_all = ["shallow_since", "shallow_exclude", "depth", "deepen", "unshallow"])]
pub unshallow: bool,
}

impl From<ShallowOptions> for Shallow {
fn from(opts: ShallowOptions) -> Self {
if let Some(depth) = opts.depth {
Shallow::DepthAtRemote(depth)
} else if !opts.shallow_exclude.is_empty() {
Shallow::Exclude {
remote_refs: opts.shallow_exclude,
since_cutoff: opts.shallow_since,
}
} else if let Some(cutoff) = opts.shallow_since {
Shallow::Since { cutoff }
} else if let Some(depth) = opts.deepen {
Shallow::Deepen(depth)
} else if opts.unshallow {
Shallow::undo()
} else {
Shallow::default()
}
}
}
}

#[cfg(feature = "gitoxide-core-blocking-client")]
pub mod clone {
use gix::remote::fetch::Shallow;
use std::num::NonZeroU32;
use std::{ffi::OsString, path::PathBuf};

#[derive(Debug, clap::Parser)]
Expand All @@ -175,12 +227,47 @@ pub mod clone {
#[clap(long)]
pub no_tags: bool,

#[clap(flatten)]
pub shallow: ShallowOptions,

/// The url of the remote to connect to, like `https://github.com/byron/gitoxide`.
pub remote: OsString,

/// The directory to initialize with the new repository and to which all data should be written.
pub directory: Option<PathBuf>,
}

#[derive(Debug, clap::Parser)]
pub struct ShallowOptions {
/// Create a shallow clone with the history truncated to the given number of commits.
#[clap(long, conflicts_with_all = ["shallow_since", "shallow_exclude"])]
pub depth: Option<NonZeroU32>,

/// Cutoff all history past the given date. Can be combined with shallow-exclude.
#[clap(long, value_parser = crate::shared::AsTime, value_name = "DATE")]
pub shallow_since: Option<gix::date::Time>,

/// Cutoff all history past the tag-name or ref-name. Can be combined with shallow-since.
#[clap(long, value_parser = crate::shared::AsPartialRefName, value_name = "REF_NAME")]
pub shallow_exclude: Vec<gix::refs::PartialName>,
}

impl From<ShallowOptions> for Shallow {
fn from(opts: ShallowOptions) -> Self {
if let Some(depth) = opts.depth {
Shallow::DepthAtRemote(depth)
} else if !opts.shallow_exclude.is_empty() {
Shallow::Exclude {
remote_refs: opts.shallow_exclude,
since_cutoff: opts.shallow_since,
}
} else if let Some(cutoff) = opts.shallow_since {
Shallow::Since { cutoff }
} else {
Shallow::default()
}
}
}
}

#[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))]
Expand Down
8 changes: 7 additions & 1 deletion src/plumbing/progress.rs
Expand Up @@ -214,8 +214,14 @@ static GIT_CONFIG: &[Record] = &[
},
Record {
config: "clone.rejectShallow",
usage: Planned {
note: Some("probably trivial to implement once there is protocol support for shallow clones"),
},
},
Record {
config: "receive.shallowUpdate",
usage: NotPlanned {
reason: "it's not a use-case we consider important now, but once that changes it can be implemented",
reason: "it looks like a server-only setting that allows boundaries to change if refs are pushed that are outside of the boundary.",
},
},
Record {
Expand Down
30 changes: 28 additions & 2 deletions src/shared.rs
Expand Up @@ -292,7 +292,7 @@ mod clap {
}
}

use clap::builder::{OsStringValueParser, TypedValueParser};
use clap::builder::{OsStringValueParser, StringValueParser, TypedValueParser};

#[derive(Clone)]
pub struct AsPathSpec;
Expand All @@ -306,5 +306,31 @@ mod clap {
.parse_ref(cmd, arg, value)
}
}

#[derive(Clone)]
pub struct AsTime;

impl TypedValueParser for AsTime {
type Value = gix::date::Time;

fn parse_ref(&self, cmd: &Command, arg: Option<&Arg>, value: &OsStr) -> Result<Self::Value, Error> {
StringValueParser::new()
.try_map(|arg| gix::date::parse(&arg, Some(std::time::SystemTime::now())))
.parse_ref(cmd, arg, value)
}
}

#[derive(Clone)]
pub struct AsPartialRefName;

impl TypedValueParser for AsPartialRefName {
type Value = gix::refs::PartialName;

fn parse_ref(&self, cmd: &Command, arg: Option<&Arg>, value: &OsStr) -> Result<Self::Value, Error> {
AsBString
.try_map(gix::refs::PartialName::try_from)
.parse_ref(cmd, arg, value)
}
}
}
pub use self::clap::{AsBString, AsHashKind, AsOutputFormat, AsPathSpec};
pub use self::clap::{AsBString, AsHashKind, AsOutputFormat, AsPartialRefName, AsPathSpec, AsTime};

0 comments on commit 9723e1a

Please sign in to comment.