diff options
Diffstat (limited to 'vendor/gix/src/head')
-rw-r--r-- | vendor/gix/src/head/log.rs | 35 | ||||
-rw-r--r-- | vendor/gix/src/head/mod.rs | 122 | ||||
-rw-r--r-- | vendor/gix/src/head/peel.rs | 119 |
3 files changed, 276 insertions, 0 deletions
diff --git a/vendor/gix/src/head/log.rs b/vendor/gix/src/head/log.rs new file mode 100644 index 000000000..6aa7ed1d3 --- /dev/null +++ b/vendor/gix/src/head/log.rs @@ -0,0 +1,35 @@ +use std::convert::TryInto; + +use gix_hash::ObjectId; + +use crate::{ + bstr::{BString, ByteSlice}, + Head, +}; + +impl<'repo> Head<'repo> { + /// Return a platform for obtaining iterators on the reference log associated with the `HEAD` reference. + pub fn log_iter(&self) -> gix_ref::file::log::iter::Platform<'static, 'repo> { + gix_ref::file::log::iter::Platform { + store: &self.repo.refs, + name: "HEAD".try_into().expect("HEAD is always valid"), + buf: Vec::new(), + } + } + + /// Return a list of all branch names that were previously checked out with the first-ever checked out branch + /// being the first entry of the list, and the most recent is the last, along with the commit they were pointing to + /// at the time. + pub fn prior_checked_out_branches(&self) -> std::io::Result<Option<Vec<(BString, ObjectId)>>> { + Ok(self.log_iter().all()?.map(|log| { + log.filter_map(Result::ok) + .filter_map(|line| { + line.message + .strip_prefix(b"checkout: moving from ") + .and_then(|from_to| from_to.find(" to ").map(|pos| &from_to[..pos])) + .map(|from_branch| (from_branch.as_bstr().to_owned(), line.previous_oid())) + }) + .collect() + })) + } +} diff --git a/vendor/gix/src/head/mod.rs b/vendor/gix/src/head/mod.rs new file mode 100644 index 000000000..094e78a86 --- /dev/null +++ b/vendor/gix/src/head/mod.rs @@ -0,0 +1,122 @@ +//! +use std::convert::TryInto; + +use gix_hash::ObjectId; +use gix_ref::FullNameRef; + +use crate::{ + ext::{ObjectIdExt, ReferenceExt}, + Head, +}; + +/// Represents the kind of `HEAD` reference. +#[derive(Clone)] +pub enum Kind { + /// The existing reference the symbolic HEAD points to. + /// + /// This is the common case. + Symbolic(gix_ref::Reference), + /// The yet-to-be-created reference the symbolic HEAD refers to. + /// + /// This is the case in a newly initialized repository. + Unborn(gix_ref::FullName), + /// The head points to an object directly, not to a symbolic reference. + /// + /// This state is less common and can occur when checking out commits directly. + Detached { + /// The object to which the head points to + target: ObjectId, + /// Possibly the final destination of `target` after following the object chain from tag objects to commits. + peeled: Option<ObjectId>, + }, +} + +impl Kind { + /// Attach this instance to a `repo` to produce a [`Head`]. + pub fn attach(self, repo: &crate::Repository) -> Head<'_> { + Head { kind: self, repo } + } +} + +/// Access +impl<'repo> Head<'repo> { + /// Returns the name of this references, always `HEAD`. + pub fn name(&self) -> &'static FullNameRef { + // TODO: use a statically checked version of this when available. + "HEAD".try_into().expect("HEAD is valid") + } + + /// Returns the full reference name of this head if it is not detached, or `None` otherwise. + pub fn referent_name(&self) -> Option<&FullNameRef> { + Some(match &self.kind { + Kind::Symbolic(r) => r.name.as_ref(), + Kind::Unborn(name) => name.as_ref(), + Kind::Detached { .. } => return None, + }) + } + + /// Returns true if this instance is detached, and points to an object directly. + pub fn is_detached(&self) -> bool { + matches!(self.kind, Kind::Detached { .. }) + } + + /// Returns true if this instance is not yet born, hence it points to a ref that doesn't exist yet. + /// + /// This is the case in a newly initialized repository. + pub fn is_unborn(&self) -> bool { + matches!(self.kind, Kind::Unborn(_)) + } + + // TODO: tests + /// Returns the id the head points to, which isn't possible on unborn heads. + pub fn id(&self) -> Option<crate::Id<'repo>> { + match &self.kind { + Kind::Symbolic(r) => r.target.try_id().map(|oid| oid.to_owned().attach(self.repo)), + Kind::Detached { peeled, target } => { + (*peeled).unwrap_or_else(|| target.to_owned()).attach(self.repo).into() + } + Kind::Unborn(_) => None, + } + } + + /// Try to transform this instance into the symbolic reference that it points to, or return `None` if head is detached or unborn. + pub fn try_into_referent(self) -> Option<crate::Reference<'repo>> { + match self.kind { + Kind::Symbolic(r) => r.attach(self.repo).into(), + _ => None, + } + } +} + +mod remote { + use super::Head; + use crate::{remote, Remote}; + + /// Remote + impl<'repo> Head<'repo> { + /// Return the remote with which the currently checked our reference can be handled as configured by `branch.<name>.remote|pushRemote` + /// or fall back to the non-branch specific remote configuration. `None` is returned if the head is detached or unborn, so there is + /// no branch specific remote. + /// + /// This is equivalent to calling [`Reference::remote(…)`][crate::Reference::remote()] and + /// [`Repository::remote_default_name()`][crate::Repository::remote_default_name()] in order. + /// + /// Combine it with [`find_default_remote()`][crate::Repository::find_default_remote()] as fallback to handle detached heads, + /// i.e. obtain a remote even in case of detached heads. + pub fn into_remote( + self, + direction: remote::Direction, + ) -> Option<Result<Remote<'repo>, remote::find::existing::Error>> { + let repo = self.repo; + self.try_into_referent()? + .remote(direction) + .or_else(|| repo.find_default_remote(direction)) + } + } +} + +/// +pub mod log; + +/// +pub mod peel; diff --git a/vendor/gix/src/head/peel.rs b/vendor/gix/src/head/peel.rs new file mode 100644 index 000000000..65a876bc4 --- /dev/null +++ b/vendor/gix/src/head/peel.rs @@ -0,0 +1,119 @@ +use crate::{ + ext::{ObjectIdExt, ReferenceExt}, + Head, +}; + +mod error { + use crate::{object, reference}; + + /// The error returned by [Head::peel_to_id_in_place()][super::Head::peel_to_id_in_place()] and [Head::into_fully_peeled_id()][super::Head::into_fully_peeled_id()]. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + FindExistingObject(#[from] object::find::existing::Error), + #[error(transparent)] + PeelReference(#[from] reference::peel::Error), + } +} + +pub use error::Error; + +use crate::head::Kind; + +/// +pub mod to_commit { + use crate::object; + + /// The error returned by [Head::peel_to_commit_in_place()][super::Head::peel_to_commit_in_place()]. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + Peel(#[from] super::Error), + #[error("Branch '{name}' does not have any commits")] + Unborn { name: gix_ref::FullName }, + #[error(transparent)] + ObjectKind(#[from] object::try_into::Error), + } +} + +impl<'repo> Head<'repo> { + // TODO: tests + /// Peel this instance to make obtaining its final target id possible, while returning an error on unborn heads. + pub fn peeled(mut self) -> Result<Self, Error> { + self.peel_to_id_in_place().transpose()?; + Ok(self) + } + + // TODO: tests + /// Follow the symbolic reference of this head until its target object and peel it by following tag objects until there is no + /// more object to follow, and return that object id. + /// + /// Returns `None` if the head is unborn. + pub fn peel_to_id_in_place(&mut self) -> Option<Result<crate::Id<'repo>, Error>> { + Some(match &mut self.kind { + Kind::Unborn(_name) => return None, + Kind::Detached { + peeled: Some(peeled), .. + } => Ok((*peeled).attach(self.repo)), + Kind::Detached { peeled: None, target } => { + match target + .attach(self.repo) + .object() + .map_err(Into::into) + .and_then(|obj| obj.peel_tags_to_end().map_err(Into::into)) + .map(|peeled| peeled.id) + { + Ok(peeled) => { + self.kind = Kind::Detached { + peeled: Some(peeled), + target: *target, + }; + Ok(peeled.attach(self.repo)) + } + Err(err) => Err(err), + } + } + Kind::Symbolic(r) => { + let mut nr = r.clone().attach(self.repo); + let peeled = nr.peel_to_id_in_place().map_err(Into::into); + *r = nr.detach(); + peeled + } + }) + } + + // TODO: tests + // TODO: something similar in `crate::Reference` + /// Follow the symbolic reference of this head until its target object and peel it by following tag objects until there is no + /// more object to follow, transform the id into a commit if possible and return that. + /// + /// Returns an error if the head is unborn or if it doesn't point to a commit. + pub fn peel_to_commit_in_place(&mut self) -> Result<crate::Commit<'repo>, to_commit::Error> { + let id = self.peel_to_id_in_place().ok_or_else(|| to_commit::Error::Unborn { + name: self.referent_name().expect("unborn").to_owned(), + })??; + id.object() + .map_err(|err| to_commit::Error::Peel(Error::FindExistingObject(err))) + .and_then(|object| object.try_into_commit().map_err(Into::into)) + } + + /// Consume this instance and transform it into the final object that it points to, or `None` if the `HEAD` + /// reference is yet to be born. + pub fn into_fully_peeled_id(self) -> Option<Result<crate::Id<'repo>, Error>> { + Some(match self.kind { + Kind::Unborn(_name) => return None, + Kind::Detached { + peeled: Some(peeled), .. + } => Ok(peeled.attach(self.repo)), + Kind::Detached { peeled: None, target } => target + .attach(self.repo) + .object() + .map_err(Into::into) + .and_then(|obj| obj.peel_tags_to_end().map_err(Into::into)) + .map(|obj| obj.id.attach(self.repo)), + Kind::Symbolic(r) => r.attach(self.repo).peel_to_id_in_place().map_err(Into::into), + }) + } +} |