]>
Commit | Line | Data |
---|---|---|
1 | use std::convert::TryInto; | |
2 | ||
3 | use gix_hash::ObjectId; | |
4 | use gix_macros::momo; | |
5 | use gix_ref::{ | |
6 | transaction::{Change, LogChange, PreviousValue, RefEdit, RefLog}, | |
7 | FullName, PartialNameRef, Target, | |
8 | }; | |
9 | ||
10 | use crate::{bstr::BString, ext::ReferenceExt, reference, Reference}; | |
11 | ||
12 | /// Obtain and alter references comfortably | |
13 | impl crate::Repository { | |
14 | /// Create a lightweight tag with given `name` (and without `refs/tags/` prefix) pointing to the given `target`, and return it as reference. | |
15 | /// | |
16 | /// It will be created with `constraint` which is most commonly to [only create it][PreviousValue::MustNotExist] | |
17 | /// or to [force overwriting a possibly existing tag](PreviousValue::Any). | |
18 | #[momo] | |
19 | pub fn tag_reference( | |
20 | &self, | |
21 | name: impl AsRef<str>, | |
22 | target: impl Into<ObjectId>, | |
23 | constraint: PreviousValue, | |
24 | ) -> Result<Reference<'_>, reference::edit::Error> { | |
25 | let id = target.into(); | |
26 | let mut edits = self.edit_reference(RefEdit { | |
27 | change: Change::Update { | |
28 | log: Default::default(), | |
29 | expected: constraint, | |
30 | new: Target::Peeled(id), | |
31 | }, | |
32 | name: format!("refs/tags/{}", name.as_ref()).try_into()?, | |
33 | deref: false, | |
34 | })?; | |
35 | assert_eq!(edits.len(), 1, "reference splits should ever happen"); | |
36 | let edit = edits.pop().expect("exactly one item"); | |
37 | Ok(Reference { | |
38 | inner: gix_ref::Reference { | |
39 | name: edit.name, | |
40 | target: id.into(), | |
41 | peeled: None, | |
42 | }, | |
43 | repo: self, | |
44 | }) | |
45 | } | |
46 | ||
47 | /// Returns the currently set namespace for references, or `None` if it is not set. | |
48 | /// | |
49 | /// Namespaces allow to partition references, and is configured per `Easy`. | |
50 | pub fn namespace(&self) -> Option<&gix_ref::Namespace> { | |
51 | self.refs.namespace.as_ref() | |
52 | } | |
53 | ||
54 | /// Remove the currently set reference namespace and return it, affecting only this `Easy`. | |
55 | pub fn clear_namespace(&mut self) -> Option<gix_ref::Namespace> { | |
56 | self.refs.namespace.take() | |
57 | } | |
58 | ||
59 | /// Set the reference namespace to the given value, like `"foo"` or `"foo/bar"`. | |
60 | /// | |
61 | /// Note that this value is shared across all `Easy…` instances as the value is stored in the shared `Repository`. | |
62 | pub fn set_namespace<'a, Name, E>( | |
63 | &mut self, | |
64 | namespace: Name, | |
65 | ) -> Result<Option<gix_ref::Namespace>, gix_validate::reference::name::Error> | |
66 | where | |
67 | Name: TryInto<&'a PartialNameRef, Error = E>, | |
68 | gix_validate::reference::name::Error: From<E>, | |
69 | { | |
70 | let namespace = gix_ref::namespace::expand(namespace)?; | |
71 | Ok(self.refs.namespace.replace(namespace)) | |
72 | } | |
73 | ||
74 | // TODO: more tests or usage | |
75 | /// Create a new reference with `name`, like `refs/heads/branch`, pointing to `target`, adhering to `constraint` | |
76 | /// during creation and writing `log_message` into the reflog. Note that a ref-log will be written even if `log_message` is empty. | |
77 | /// | |
78 | /// The newly created Reference is returned. | |
79 | pub fn reference<Name, E>( | |
80 | &self, | |
81 | name: Name, | |
82 | target: impl Into<ObjectId>, | |
83 | constraint: PreviousValue, | |
84 | log_message: impl Into<BString>, | |
85 | ) -> Result<Reference<'_>, reference::edit::Error> | |
86 | where | |
87 | Name: TryInto<FullName, Error = E>, | |
88 | gix_validate::reference::name::Error: From<E>, | |
89 | { | |
90 | self.reference_inner( | |
91 | name.try_into().map_err(gix_validate::reference::name::Error::from)?, | |
92 | target.into(), | |
93 | constraint, | |
94 | log_message.into(), | |
95 | ) | |
96 | } | |
97 | ||
98 | fn reference_inner( | |
99 | &self, | |
100 | name: FullName, | |
101 | id: ObjectId, | |
102 | constraint: PreviousValue, | |
103 | log_message: BString, | |
104 | ) -> Result<Reference<'_>, reference::edit::Error> { | |
105 | let mut edits = self.edit_reference(RefEdit { | |
106 | change: Change::Update { | |
107 | log: LogChange { | |
108 | mode: RefLog::AndReference, | |
109 | force_create_reflog: false, | |
110 | message: log_message, | |
111 | }, | |
112 | expected: constraint, | |
113 | new: Target::Peeled(id), | |
114 | }, | |
115 | name, | |
116 | deref: false, | |
117 | })?; | |
118 | assert_eq!( | |
119 | edits.len(), | |
120 | 1, | |
121 | "only one reference can be created, splits aren't possible" | |
122 | ); | |
123 | ||
124 | Ok(gix_ref::Reference { | |
125 | name: edits.pop().expect("exactly one edit").name, | |
126 | target: Target::Peeled(id), | |
127 | peeled: None, | |
128 | } | |
129 | .attach(self)) | |
130 | } | |
131 | ||
132 | /// Edit a single reference as described in `edit`, and write reference logs as `log_committer`. | |
133 | /// | |
134 | /// One or more `RefEdit`s are returned - symbolic reference splits can cause more edits to be performed. All edits have the previous | |
135 | /// reference values set to the ones encountered at rest after acquiring the respective reference's lock. | |
136 | pub fn edit_reference(&self, edit: RefEdit) -> Result<Vec<RefEdit>, reference::edit::Error> { | |
137 | self.edit_references(Some(edit)) | |
138 | } | |
139 | ||
140 | /// Edit one or more references as described by their `edits`. | |
141 | /// Note that one can set the committer name for use in the ref-log by temporarily | |
142 | /// [overriding the git-config][crate::Repository::config_snapshot_mut()]. | |
143 | /// | |
144 | /// Returns all reference edits, which might be more than where provided due the splitting of symbolic references, and | |
145 | /// whose previous (_old_) values are the ones seen on in storage after the reference was locked. | |
146 | pub fn edit_references( | |
147 | &self, | |
148 | edits: impl IntoIterator<Item = RefEdit>, | |
149 | ) -> Result<Vec<RefEdit>, reference::edit::Error> { | |
150 | let (file_lock_fail, packed_refs_lock_fail) = self.config.lock_timeout()?; | |
151 | self.refs | |
152 | .transaction() | |
153 | .prepare(edits, file_lock_fail, packed_refs_lock_fail)? | |
154 | .commit(self.committer().transpose()?) | |
155 | .map_err(Into::into) | |
156 | } | |
157 | ||
158 | /// Return the repository head, an abstraction to help dealing with the `HEAD` reference. | |
159 | /// | |
160 | /// The `HEAD` reference can be in various states, for more information, the documentation of [`Head`][crate::Head]. | |
161 | pub fn head(&self) -> Result<crate::Head<'_>, reference::find::existing::Error> { | |
162 | let head = self.find_reference("HEAD")?; | |
163 | Ok(match head.inner.target { | |
164 | Target::Symbolic(branch) => match self.find_reference(&branch) { | |
165 | Ok(r) => crate::head::Kind::Symbolic(r.detach()), | |
166 | Err(reference::find::existing::Error::NotFound) => crate::head::Kind::Unborn(branch), | |
167 | Err(err) => return Err(err), | |
168 | }, | |
169 | Target::Peeled(target) => crate::head::Kind::Detached { | |
170 | target, | |
171 | peeled: head.inner.peeled, | |
172 | }, | |
173 | } | |
174 | .attach(self)) | |
175 | } | |
176 | ||
177 | /// Resolve the `HEAD` reference, follow and peel its target and obtain its object id. | |
178 | /// | |
179 | /// Note that this may fail for various reasons, most notably because the repository | |
180 | /// is freshly initialized and doesn't have any commits yet. | |
181 | /// | |
182 | /// Also note that the returned id is likely to point to a commit, but could also | |
183 | /// point to a tree or blob. It won't, however, point to a tag as these are always peeled. | |
184 | pub fn head_id(&self) -> Result<crate::Id<'_>, reference::head_id::Error> { | |
185 | let mut head = self.head()?; | |
186 | head.peel_to_id_in_place() | |
187 | .ok_or_else(|| reference::head_id::Error::Unborn { | |
188 | name: head.referent_name().expect("unborn").to_owned(), | |
189 | })? | |
190 | .map_err(Into::into) | |
191 | } | |
192 | ||
193 | /// Return the name to the symbolic reference `HEAD` points to, or `None` if the head is detached. | |
194 | /// | |
195 | /// The difference to [`head_ref()`][Self::head_ref()] is that the latter requires the reference to exist, | |
196 | /// whereas here we merely return a the name of the possibly unborn reference. | |
197 | pub fn head_name(&self) -> Result<Option<FullName>, reference::find::existing::Error> { | |
198 | Ok(self.head()?.referent_name().map(std::borrow::ToOwned::to_owned)) | |
199 | } | |
200 | ||
201 | /// Return the reference that `HEAD` points to, or `None` if the head is detached or unborn. | |
202 | pub fn head_ref(&self) -> Result<Option<Reference<'_>>, reference::find::existing::Error> { | |
203 | Ok(self.head()?.try_into_referent()) | |
204 | } | |
205 | ||
206 | /// Return the commit object the `HEAD` reference currently points to after peeling it fully. | |
207 | /// | |
208 | /// Note that this may fail for various reasons, most notably because the repository | |
209 | /// is freshly initialized and doesn't have any commits yet. It could also fail if the | |
210 | /// head does not point to a commit. | |
211 | pub fn head_commit(&self) -> Result<crate::Commit<'_>, reference::head_commit::Error> { | |
212 | Ok(self.head()?.peel_to_commit_in_place()?) | |
213 | } | |
214 | ||
215 | /// Find the reference with the given partial or full `name`, like `main`, `HEAD`, `heads/branch` or `origin/other`, | |
216 | /// or return an error if it wasn't found. | |
217 | /// | |
218 | /// Consider [`try_find_reference(…)`][crate::Repository::try_find_reference()] if the reference might not exist | |
219 | /// without that being considered an error. | |
220 | pub fn find_reference<'a, Name, E>(&self, name: Name) -> Result<Reference<'_>, reference::find::existing::Error> | |
221 | where | |
222 | Name: TryInto<&'a PartialNameRef, Error = E>, | |
223 | gix_ref::file::find::Error: From<E>, | |
224 | { | |
225 | self.try_find_reference(name)? | |
226 | .ok_or(reference::find::existing::Error::NotFound) | |
227 | } | |
228 | ||
229 | /// Return a platform for iterating references. | |
230 | /// | |
231 | /// Common kinds of iteration are [all][crate::reference::iter::Platform::all()] or [prefixed][crate::reference::iter::Platform::prefixed()] | |
232 | /// references. | |
233 | pub fn references(&self) -> Result<reference::iter::Platform<'_>, reference::iter::Error> { | |
234 | Ok(reference::iter::Platform { | |
235 | platform: self.refs.iter()?, | |
236 | repo: self, | |
237 | }) | |
238 | } | |
239 | ||
240 | /// Try to find the reference named `name`, like `main`, `heads/branch`, `HEAD` or `origin/other`, and return it. | |
241 | /// | |
242 | /// Otherwise return `None` if the reference wasn't found. | |
243 | /// If the reference is expected to exist, use [`find_reference()`][crate::Repository::find_reference()]. | |
244 | pub fn try_find_reference<'a, Name, E>(&self, name: Name) -> Result<Option<Reference<'_>>, reference::find::Error> | |
245 | where | |
246 | Name: TryInto<&'a PartialNameRef, Error = E>, | |
247 | gix_ref::file::find::Error: From<E>, | |
248 | { | |
249 | let state = self; | |
250 | match state.refs.try_find(name) { | |
251 | Ok(r) => match r { | |
252 | Some(r) => Ok(Some(Reference::from_ref(r, self))), | |
253 | None => Ok(None), | |
254 | }, | |
255 | Err(err) => Err(err.into()), | |
256 | } | |
257 | } | |
258 | } |