summaryrefslogtreecommitdiffstats
path: root/vendor/gix/src/repository/reference.rs
blob: b977c6ea87224730951cd57f897e329847e3050d (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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
use std::convert::TryInto;

use gix_hash::ObjectId;
use gix_macros::momo;
use gix_ref::{
    transaction::{Change, LogChange, PreviousValue, RefEdit, RefLog},
    FullName, PartialNameRef, Target,
};

use crate::{bstr::BString, ext::ReferenceExt, reference, Reference};

/// Obtain and alter references comfortably
impl crate::Repository {
    /// Create a lightweight tag with given `name` (and without `refs/tags/` prefix) pointing to the given `target`, and return it as reference.
    ///
    /// It will be created with `constraint` which is most commonly to [only create it][PreviousValue::MustNotExist]
    /// or to [force overwriting a possibly existing tag](PreviousValue::Any).
    #[momo]
    pub fn tag_reference(
        &self,
        name: impl AsRef<str>,
        target: impl Into<ObjectId>,
        constraint: PreviousValue,
    ) -> Result<Reference<'_>, reference::edit::Error> {
        let id = target.into();
        let mut edits = self.edit_reference(RefEdit {
            change: Change::Update {
                log: Default::default(),
                expected: constraint,
                new: Target::Peeled(id),
            },
            name: format!("refs/tags/{}", name.as_ref()).try_into()?,
            deref: false,
        })?;
        assert_eq!(edits.len(), 1, "reference splits should ever happen");
        let edit = edits.pop().expect("exactly one item");
        Ok(Reference {
            inner: gix_ref::Reference {
                name: edit.name,
                target: id.into(),
                peeled: None,
            },
            repo: self,
        })
    }

    /// Returns the currently set namespace for references, or `None` if it is not set.
    ///
    /// Namespaces allow to partition references, and is configured per `Easy`.
    pub fn namespace(&self) -> Option<&gix_ref::Namespace> {
        self.refs.namespace.as_ref()
    }

    /// Remove the currently set reference namespace and return it, affecting only this `Easy`.
    pub fn clear_namespace(&mut self) -> Option<gix_ref::Namespace> {
        self.refs.namespace.take()
    }

    /// Set the reference namespace to the given value, like `"foo"` or `"foo/bar"`.
    ///
    /// Note that this value is shared across all `Easy…` instances as the value is stored in the shared `Repository`.
    pub fn set_namespace<'a, Name, E>(
        &mut self,
        namespace: Name,
    ) -> Result<Option<gix_ref::Namespace>, gix_validate::reference::name::Error>
    where
        Name: TryInto<&'a PartialNameRef, Error = E>,
        gix_validate::reference::name::Error: From<E>,
    {
        let namespace = gix_ref::namespace::expand(namespace)?;
        Ok(self.refs.namespace.replace(namespace))
    }

    // TODO: more tests or usage
    /// Create a new reference with `name`, like `refs/heads/branch`, pointing to `target`, adhering to `constraint`
    /// during creation and writing `log_message` into the reflog. Note that a ref-log will be written even if `log_message` is empty.
    ///
    /// The newly created Reference is returned.
    pub fn reference<Name, E>(
        &self,
        name: Name,
        target: impl Into<ObjectId>,
        constraint: PreviousValue,
        log_message: impl Into<BString>,
    ) -> Result<Reference<'_>, reference::edit::Error>
    where
        Name: TryInto<FullName, Error = E>,
        gix_validate::reference::name::Error: From<E>,
    {
        self.reference_inner(
            name.try_into().map_err(gix_validate::reference::name::Error::from)?,
            target.into(),
            constraint,
            log_message.into(),
        )
    }

    fn reference_inner(
        &self,
        name: FullName,
        id: ObjectId,
        constraint: PreviousValue,
        log_message: BString,
    ) -> Result<Reference<'_>, reference::edit::Error> {
        let mut edits = self.edit_reference(RefEdit {
            change: Change::Update {
                log: LogChange {
                    mode: RefLog::AndReference,
                    force_create_reflog: false,
                    message: log_message,
                },
                expected: constraint,
                new: Target::Peeled(id),
            },
            name,
            deref: false,
        })?;
        assert_eq!(
            edits.len(),
            1,
            "only one reference can be created, splits aren't possible"
        );

        Ok(gix_ref::Reference {
            name: edits.pop().expect("exactly one edit").name,
            target: Target::Peeled(id),
            peeled: None,
        }
        .attach(self))
    }

    /// Edit a single reference as described in `edit`, and write reference logs as `log_committer`.
    ///
    /// One or more `RefEdit`s  are returned - symbolic reference splits can cause more edits to be performed. All edits have the previous
    /// reference values set to the ones encountered at rest after acquiring the respective reference's lock.
    pub fn edit_reference(&self, edit: RefEdit) -> Result<Vec<RefEdit>, reference::edit::Error> {
        self.edit_references(Some(edit))
    }

    /// Edit one or more references as described by their `edits`.
    /// Note that one can set the committer name for use in the ref-log by temporarily
    /// [overriding the git-config][crate::Repository::config_snapshot_mut()].
    ///
    /// Returns all reference edits, which might be more than where provided due the splitting of symbolic references, and
    /// whose previous (_old_) values are the ones seen on in storage after the reference was locked.
    pub fn edit_references(
        &self,
        edits: impl IntoIterator<Item = RefEdit>,
    ) -> Result<Vec<RefEdit>, reference::edit::Error> {
        let (file_lock_fail, packed_refs_lock_fail) = self.config.lock_timeout()?;
        self.refs
            .transaction()
            .prepare(edits, file_lock_fail, packed_refs_lock_fail)?
            .commit(self.committer().transpose()?)
            .map_err(Into::into)
    }

    /// Return the repository head, an abstraction to help dealing with the `HEAD` reference.
    ///
    /// The `HEAD` reference can be in various states, for more information, the documentation of [`Head`][crate::Head].
    pub fn head(&self) -> Result<crate::Head<'_>, reference::find::existing::Error> {
        let head = self.find_reference("HEAD")?;
        Ok(match head.inner.target {
            Target::Symbolic(branch) => match self.find_reference(&branch) {
                Ok(r) => crate::head::Kind::Symbolic(r.detach()),
                Err(reference::find::existing::Error::NotFound) => crate::head::Kind::Unborn(branch),
                Err(err) => return Err(err),
            },
            Target::Peeled(target) => crate::head::Kind::Detached {
                target,
                peeled: head.inner.peeled,
            },
        }
        .attach(self))
    }

    /// Resolve the `HEAD` reference, follow and peel its target and obtain its object id,
    /// following symbolic references and tags until a commit is found.
    ///
    /// Note that this may fail for various reasons, most notably because the repository
    /// is freshly initialized and doesn't have any commits yet.
    ///
    /// Also note that the returned id is likely to point to a commit, but could also
    /// point to a tree or blob. It won't, however, point to a tag as these are always peeled.
    pub fn head_id(&self) -> Result<crate::Id<'_>, reference::head_id::Error> {
        Ok(self.head()?.into_peeled_id()?)
    }

    /// Return the name to the symbolic reference `HEAD` points to, or `None` if the head is detached.
    ///
    /// The difference to [`head_ref()`][Self::head_ref()] is that the latter requires the reference to exist,
    /// whereas here we merely return a the name of the possibly unborn reference.
    pub fn head_name(&self) -> Result<Option<FullName>, reference::find::existing::Error> {
        Ok(self.head()?.referent_name().map(std::borrow::ToOwned::to_owned))
    }

    /// Return the reference that `HEAD` points to, or `None` if the head is detached or unborn.
    pub fn head_ref(&self) -> Result<Option<Reference<'_>>, reference::find::existing::Error> {
        Ok(self.head()?.try_into_referent())
    }

    /// Return the commit object the `HEAD` reference currently points to after peeling it fully,
    /// following symbolic references and tags until a commit is found.
    ///
    /// Note that this may fail for various reasons, most notably because the repository
    /// is freshly initialized and doesn't have any commits yet. It could also fail if the
    /// head does not point to a commit.
    pub fn head_commit(&self) -> Result<crate::Commit<'_>, reference::head_commit::Error> {
        Ok(self.head()?.peel_to_commit_in_place()?)
    }

    /// Return the tree id the `HEAD` reference currently points to after peeling it fully,
    /// following symbolic references and tags until a commit is found.
    ///
    /// Note that this may fail for various reasons, most notably because the repository
    /// is freshly initialized and doesn't have any commits yet. It could also fail if the
    /// head does not point to a commit.
    pub fn head_tree_id(&self) -> Result<crate::Id<'_>, reference::head_tree_id::Error> {
        Ok(self.head_commit()?.tree_id()?)
    }

    /// Find the reference with the given partial or full `name`, like `main`, `HEAD`, `heads/branch` or `origin/other`,
    /// or return an error if it wasn't found.
    ///
    /// Consider [`try_find_reference(…)`][crate::Repository::try_find_reference()] if the reference might not exist
    /// without that being considered an error.
    pub fn find_reference<'a, Name, E>(&self, name: Name) -> Result<Reference<'_>, reference::find::existing::Error>
    where
        Name: TryInto<&'a PartialNameRef, Error = E>,
        gix_ref::file::find::Error: From<E>,
    {
        self.try_find_reference(name)?
            .ok_or(reference::find::existing::Error::NotFound)
    }

    /// Return a platform for iterating references.
    ///
    /// Common kinds of iteration are [all][crate::reference::iter::Platform::all()] or [prefixed][crate::reference::iter::Platform::prefixed()]
    /// references.
    pub fn references(&self) -> Result<reference::iter::Platform<'_>, reference::iter::Error> {
        Ok(reference::iter::Platform {
            platform: self.refs.iter()?,
            repo: self,
        })
    }

    /// Try to find the reference named `name`, like `main`, `heads/branch`, `HEAD` or `origin/other`, and return it.
    ///
    /// Otherwise return `None` if the reference wasn't found.
    /// If the reference is expected to exist, use [`find_reference()`][crate::Repository::find_reference()].
    pub fn try_find_reference<'a, Name, E>(&self, name: Name) -> Result<Option<Reference<'_>>, reference::find::Error>
    where
        Name: TryInto<&'a PartialNameRef, Error = E>,
        gix_ref::file::find::Error: From<E>,
    {
        let state = self;
        match state.refs.try_find(name) {
            Ok(r) => match r {
                Some(r) => Ok(Some(Reference::from_ref(r, self))),
                None => Ok(None),
            },
            Err(err) => Err(err.into()),
        }
    }
}