Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions gitoxide-core/src/repository/clone.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::OutputFormat;
use gix::bstr::BString;

pub struct Options {
pub format: OutputFormat,
Expand All @@ -7,6 +8,7 @@ pub struct Options {
pub no_tags: bool,
pub shallow: gix::remote::fetch::Shallow,
pub ref_name: Option<gix::refs::PartialName>,
pub revision: Option<BString>,
}

pub const PROGRESS_RANGE: std::ops::RangeInclusive<u8> = 1..=3;
Expand All @@ -33,6 +35,7 @@ pub(crate) mod function {
bare,
no_tags,
ref_name,
revision,
shallow,
}: Options,
) -> anyhow::Result<()>
Expand Down Expand Up @@ -78,6 +81,7 @@ pub(crate) mod function {
let (mut checkout, fetch_outcome) = prepare
.with_shallow(shallow)
.with_ref_name(ref_name.as_ref())?
.with_revision(revision)
.fetch_then_checkout(&mut progress, &gix::interrupt::IS_INTERRUPTED)?;

let (repo, outcome) = if bare {
Expand Down
11 changes: 11 additions & 0 deletions gix/src/clone/access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,17 @@ impl PrepareFetch {
self.ref_name = name.map(TryInto::try_into).transpose()?.map(ToOwned::to_owned);
Ok(self)
}

/// Set the revision to check out after fetching.
///
/// If `None`, the `HEAD` will be used, which is the default.
pub fn with_revision<T>(mut self, revision: Option<T>) -> Self
where
T: Into<BString>,
{
self.revision = revision.map(Into::into);
self
Comment on lines +64 to +72
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with_revision() is always available, but the actual resolution logic is #[cfg(feature = "revision")] in the fetch implementation. In builds with default-features = false where revision is disabled, this setter becomes a silent no-op. Consider gating the method/field behind the same feature, or documenting/returning an error when the feature isn’t enabled to avoid surprising API behavior.

Copilot uses AI. Check for mistakes.
}
}
Comment on lines +64 to 74
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new clone_by_object_id test uses with_ref_name(Some(<hex oid>)), but the API docs for with_ref_name() currently state that passing an object-id-as-hex causes subsequent clone operations to panic. Either update that doc comment to reflect the now-supported behavior (and any remaining limitations), or adjust the implementation so the documented panic can’t occur.

Copilot uses AI. Check for mistakes.

/// Consumption
Expand Down
24 changes: 24 additions & 0 deletions gix/src/clone/fetch/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ pub enum Error {
RefMap(#[from] crate::remote::ref_map::Error),
#[error(transparent)]
ReferenceName(#[from] gix_validate::reference::name::Error),
#[cfg(feature = "revision")]
#[error(transparent)]
RevisionResolve(#[from] crate::revision::spec::parse::single::Error),
#[cfg(feature = "revision")]
#[error(transparent)]
RevisionParse(#[from] gix_error::Error),
}

/// Modification
Expand Down Expand Up @@ -300,6 +306,24 @@ impl PrepareFetch {
self.ref_name.as_ref(),
)?;

#[cfg(feature = "revision")]
if let Some(rev) = &self.revision {
let id = repo.rev_parse_single(rev.as_bstr())?;
repo.edit_reference(gix_ref::transaction::RefEdit {
Comment on lines +310 to +312
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Honor --revision during checkout when --ref is also set

When with_revision() is used together with with_ref_name() (or CLI --revision with --ref) in a non-bare clone, this block detaches HEAD to the revision, but fetch_then_checkout() still passes the original ref_name into PrepareCheckout, and main_worktree_inner() prioritizes ref_name over HEAD. The result is a worktree checked out at the ref tip while HEAD points to a different commit, producing an inconsistent clone state (typically an immediate dirty tree).

Useful? React with 👍 / 👎.

change: gix_ref::transaction::Change::Update {
log: gix_ref::transaction::LogChange {
mode: gix_ref::transaction::RefLog::AndReference,
force_create_reflog: false,
message: reflog_message.clone(),
},
expected: gix_ref::transaction::PreviousValue::Any,
new: gix_ref::Target::Object(id.detach()),
},
name: "HEAD".try_into().expect("valid"),
deref: false,
})?;
}
Comment on lines +309 to +325
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When revision is set, fetch_only() updates HEAD, but fetch_then_checkout() still passes self.ref_name into PrepareCheckout, and main_worktree() prefers ref_name over HEAD. As a result, using both ref_name and revision will ignore revision for non-bare clones. Consider making these options mutually exclusive at the API/CLI level, or ensure revision takes precedence by clearing/ignoring ref_name for checkout when revision is present.

Copilot uses AI. Check for mistakes.
Comment on lines +309 to +325
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new revision behavior in fetch_only() isn’t covered by tests (e.g. verifying that with_revision(Some("<spec>")) results in a detached HEAD at the resolved id, and that it interacts correctly with shallow clones and/or ref_name). Adding a dedicated clone test would help prevent regressions in revspec resolution and HEAD updates.

Copilot uses AI. Check for mistakes.

Ok((self.repo.take().expect("still present"), outcome))
}

Expand Down
22 changes: 18 additions & 4 deletions gix/src/clone/fetch/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,10 +212,24 @@ pub(super) fn find_custom_refname<'a>(
wanted: ref_name.clone(),
}),
1 => {
let item = filtered_items[res.mappings[0]
.item_index
.expect("we map by name only and have no object-id in refspec")];
Ok((Some(item.target), Some(item.full_ref_name)))
let mapping = &res.mappings[0];
match (mapping.item_index, &mapping.lhs) {
(Some(idx), _) => {
let item = filtered_items[idx];
Ok((Some(item.target), Some(item.full_ref_name)))
}
(None, gix_refspec::match_group::SourceRef::ObjectId(id)) => {
let target = ref_map
.mappings
.iter()
.find_map(|m| m.remote.as_id().filter(|remote_id| *remote_id == *id))
.expect("if it matched, it must be in the mappings");
Ok((Some(target), None))
Comment on lines +221 to +227
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

find_custom_refname() will panic when the user-provided refspec is an object id that is not the target of any advertised remote ref (the .expect("if it matched…") will fail). Fetch-by-oid is valid even when no ref points at that oid, so this should return the requested id without searching ref_map.mappings and without panicking (e.g., return an owned id or adjust the function to carry the oid separately from the borrowed ref-map data).

Copilot uses AI. Check for mistakes.
}
(None, gix_refspec::match_group::SourceRef::FullName(_)) => {
unreachable!("only object ids have no item index")
}
}
}
_ => Err(Error::RefNameAmbiguous {
wanted: ref_name.clone(),
Expand Down
4 changes: 4 additions & 0 deletions gix/src/clone/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ pub struct PrepareFetch {
/// The name of the reference to fetch. If `None`, the reference pointed to by `HEAD` will be checked out.
#[cfg_attr(not(feature = "blocking-network-client"), allow(dead_code))]
ref_name: Option<gix_ref::PartialName>,
/// The revision to check out after fetching. If `None`, the reference pointed to by `HEAD` or `ref_name` will be checked out.
#[cfg_attr(not(feature = "blocking-network-client"), allow(dead_code))]
revision: Option<BString>,
}

/// The error returned by [`PrepareFetch::new()`].
Expand Down Expand Up @@ -122,6 +125,7 @@ impl PrepareFetch {
configure_connection: None,
shallow: remote::fetch::Shallow::NoChange,
ref_name: None,
revision: None,
})
}
}
Expand Down
13 changes: 13 additions & 0 deletions gix/tests/gix/clone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,19 @@ mod blocking_io {
Ok(())
}

#[test]
fn clone_by_object_id() -> crate::Result {
let tmp = gix_testtools::tempfile::TempDir::new()?;
let hex_id = "2d9d136fb0765f2e24c44a0f91984318d580d03b";
let (repo, _out) = gix::prepare_clone_bare(remote::repo("base").path(), tmp.path())?
.with_ref_name(Some(hex_id))?
.fetch_only(gix::progress::Discard, &std::sync::atomic::AtomicBool::default())?;

assert!(repo.head()?.is_detached());
assert_eq!(repo.head_id()?.to_string(), hex_id);
Ok(())
}

#[test]
fn from_non_shallow_then_deepen_then_deepen_since_to_unshallow() -> crate::Result {
let tmp = gix_testtools::tempfile::TempDir::new()?;
Expand Down
2 changes: 2 additions & 0 deletions src/plumbing/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,7 @@ pub fn main() -> Result<()> {
bare,
no_tags,
ref_name,
revision,
remote,
shallow,
directory,
Expand All @@ -618,6 +619,7 @@ pub fn main() -> Result<()> {
handshake_info,
no_tags,
ref_name,
revision,
shallow: shallow.into(),
};
prepare_and_run(
Expand Down
8 changes: 8 additions & 0 deletions src/plumbing/options/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,7 @@ pub mod fetch {
pub mod clone {
use std::{ffi::OsString, num::NonZeroU32, path::PathBuf};

use gix::bstr::BString;
use gix::remote::fetch::Shallow;

#[derive(Debug, clap::Parser)]
Expand All @@ -705,6 +706,13 @@ pub mod clone {
#[clap(long = "ref", value_parser = crate::shared::AsPartialRefName, value_name = "REF_NAME")]
pub ref_name: Option<gix::refs::PartialName>,

/// The revision to check out after cloning.
///
/// This is useful if you want to clone a specific commit that is not a branch tip.
/// It will fetch the default branch and then attempt to check out this revision.
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The help text says cloning with --revision will “fetch the default branch”, but the underlying clone implementation may fetch more than the default branch (e.g. wildcard refspecs in non-shallow clones). Consider rewording to something like “perform a normal clone/fetch and then attempt to check out this revision” to match actual behavior.

Suggested change
/// It will fetch the default branch and then attempt to check out this revision.
/// It will perform a normal clone/fetch and then attempt to check out this revision.

Copilot uses AI. Check for mistakes.
#[clap(long = "revision", visible_alias = "rev", value_parser = crate::shared::AsBString, value_name = "REVISION")]
pub revision: Option<BString>,

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