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
use std::convert::TryInto;

use git_actor as actor;
use git_hash::ObjectId;
use git_lock as lock;
use git_ref::{
    transaction::{Change, LogChange, PreviousValue, RefEdit, RefLog},
    FullName, PartialNameRef, Target,
};

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

const DEFAULT_LOCK_MODE: git_lock::acquire::Fail = git_lock::acquire::Fail::Immediately;

/// Obtain and alter references comfortably
pub trait ReferenceAccessExt: easy::Access + Sized {
    /// 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).
    fn tag_reference(
        &self,
        name: impl AsRef<str>,
        target: impl Into<ObjectId>,
        constraint: PreviousValue,
    ) -> Result<Reference<'_, Self>, 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,
            },
            DEFAULT_LOCK_MODE,
            None,
        )?;
        assert_eq!(edits.len(), 1, "reference splits should ever happen");
        let edit = edits.pop().expect("exactly one item");
        Ok(easy::Reference {
            inner: git_ref::Reference {
                name: edit.name,
                target: id.into(),
                peeled: None,
            },
            access: 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`.
    fn namespace(&self) -> Option<&git_ref::Namespace> {
        self.state().refs.namespace.as_ref()
    }

    /// Remove the currently set reference namespace and return it, affecting only this `Easy`.
    fn clear_namespace(&mut self) -> Option<git_ref::Namespace> {
        self.state_mut().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`.
    fn set_namespace<'a, Name, E>(
        &mut self,
        namespace: Name,
    ) -> Result<Option<git_ref::Namespace>, git_validate::refname::Error>
    where
        Name: TryInto<PartialNameRef<'a>, Error = E>,
        git_validate::refname::Error: From<E>,
    {
        let namespace = git_ref::namespace::expand(namespace)?;
        Ok(self.state_mut().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.
    fn reference<Name, E>(
        &self,
        name: Name,
        target: impl Into<ObjectId>,
        constraint: PreviousValue,
        log_message: impl Into<BString>,
    ) -> Result<Reference<'_, Self>, reference::edit::Error>
    where
        Name: TryInto<FullName, Error = E>,
        reference::edit::Error: From<E>,
    {
        let name = name.try_into()?;
        let id = target.into();
        let mut edits = self.edit_reference(
            RefEdit {
                change: Change::Update {
                    log: LogChange {
                        mode: RefLog::AndReference,
                        force_create_reflog: false,
                        message: log_message.into(),
                    },
                    expected: constraint,
                    new: Target::Peeled(id),
                },
                name,
                deref: false,
            },
            DEFAULT_LOCK_MODE,
            None,
        )?;
        assert_eq!(
            edits.len(),
            1,
            "only one reference can be created, splits aren't possible"
        );

        Ok(git_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`, handle locks via `lock_mode` 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.
    fn edit_reference(
        &self,
        edit: RefEdit,
        lock_mode: lock::acquire::Fail,
        log_committer: Option<&actor::Signature>,
    ) -> Result<Vec<RefEdit>, reference::edit::Error> {
        self.edit_references(Some(edit), lock_mode, log_committer)
    }

    /// Edit one or more references as described by their `edits`, with `lock_mode` deciding on how to handle competing
    /// transactions. `log_committer` is the name appearing in reference logs.
    ///
    /// 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.
    fn edit_references(
        &self,
        edits: impl IntoIterator<Item = RefEdit>,
        lock_mode: lock::acquire::Fail,
        log_committer: Option<&actor::Signature>,
    ) -> Result<Vec<RefEdit>, reference::edit::Error> {
        let committer_storage;
        let committer = match log_committer {
            Some(c) => c,
            None => {
                committer_storage = self.committer()?;
                &committer_storage
            }
        };
        self.state()
            .refs
            .transaction()
            .prepare(edits, lock_mode)?
            .commit(committer)
            .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`][easy::Head].
    fn head(&self) -> Result<easy::Head<'_, Self>, reference::find::existing::Error> {
        let head = self.find_reference("HEAD")?;
        Ok(match head.inner.target {
            Target::Symbolic(branch) => match self.find_reference(branch.to_partial()) {
                Ok(r) => easy::head::Kind::Symbolic(r.detach()),
                Err(reference::find::existing::Error::NotFound) => easy::head::Kind::Unborn(branch),
                Err(err) => return Err(err),
            },
            Target::Peeled(target) => easy::head::Kind::Detached {
                target,
                peeled: head.inner.peeled,
            },
        }
        .attach(self))
    }

    /// 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(…)`][ReferenceAccessExt::try_find_reference()] if the reference might not exist
    /// without that being considered an error.
    fn find_reference<'a, Name, E>(&self, name: Name) -> Result<Reference<'_, Self>, reference::find::existing::Error>
    where
        Name: TryInto<PartialNameRef<'a>, Error = E>,
        git_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][easy::reference::iter::Platform::all()] or [prefixed][easy::reference::iter::Platform::prefixed()]
    /// references.
    fn references(&self) -> Result<easy::reference::iter::Platform<'_, Self>, easy::reference::iter::Error> {
        Ok(easy::reference::iter::Platform {
            platform: self.state().refs.iter()?,
            access: 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()`][ReferenceAccessExt::find_reference()].
    fn try_find_reference<'a, Name, E>(&self, name: Name) -> Result<Option<Reference<'_, Self>>, reference::find::Error>
    where
        Name: TryInto<PartialNameRef<'a>, Error = E>,
        git_ref::file::find::Error: From<E>,
    {
        let state = self.state();
        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()),
        }
    }
}

impl<A> ReferenceAccessExt for A where A: easy::Access + Sized {}