summaryrefslogtreecommitdiffstats
path: root/vendor/gix/src/head/peel.rs
blob: 88e23636fb50ca3613094d5a52dc6ca6e6772d7a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
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
    // TODO: Fix this! It's not consistently peeling tags. The whole peeling business should be reconsidered to do what people usually
    //       want which is to peel references, if present, and then peel objects with control over which object type to end at.
    //       Finding a good interface for that isn't easy as ideally, it's an iterator that shows the intermediate objects so the user
    //       can select which tag of a chain to choose.
    /// 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),
        })
    }
}