1#![allow(clippy::result_large_err)]
2use std::ops::DerefMut;
3
4use gix_hash::ObjectId;
5use gix_object::{Exists, Find, FindExt, Write};
6use gix_odb::{Header, HeaderExt};
7use gix_ref::{
8 transaction::{LogChange, PreviousValue, RefLog},
9 FullName,
10};
11use smallvec::SmallVec;
12
13use crate::repository::{new_commit, new_commit_as};
14use crate::{commit, ext::ObjectIdExt, object, tag, Blob, Commit, Id, Object, Reference, Tag, Tree};
15
16#[cfg(feature = "tree-editor")]
18impl crate::Repository {
19 #[doc(alias = "treebuilder", alias = "git2")]
23 pub fn edit_tree(
24 &self,
25 id: impl Into<ObjectId>,
26 ) -> Result<object::tree::Editor<'_>, crate::repository::edit_tree::Error> {
27 let tree = self.find_tree(id)?;
28 Ok(tree.edit()?)
29 }
30}
31
32impl crate::Repository {
34 pub fn find_object(&self, id: impl Into<ObjectId>) -> Result<Object<'_>, object::find::existing::Error> {
44 let id = id.into();
45 if id == ObjectId::empty_tree(self.object_hash()) {
46 return Ok(Object {
47 id,
48 kind: gix_object::Kind::Tree,
49 data: Vec::new(),
50 repo: self,
51 });
52 }
53 let mut buf = self.free_buf();
54 let kind = self.objects.find(&id, &mut buf)?.kind;
55 Ok(Object::from_data(id, kind, buf, self))
56 }
57
58 pub fn find_commit(
60 &self,
61 id: impl Into<ObjectId>,
62 ) -> Result<Commit<'_>, object::find::existing::with_conversion::Error> {
63 Ok(self.find_object(id)?.try_into_commit()?)
64 }
65
66 pub fn find_tree(
68 &self,
69 id: impl Into<ObjectId>,
70 ) -> Result<Tree<'_>, object::find::existing::with_conversion::Error> {
71 Ok(self.find_object(id)?.try_into_tree()?)
72 }
73
74 pub fn find_tag(&self, id: impl Into<ObjectId>) -> Result<Tag<'_>, object::find::existing::with_conversion::Error> {
76 Ok(self.find_object(id)?.try_into_tag()?)
77 }
78
79 pub fn find_blob(
81 &self,
82 id: impl Into<ObjectId>,
83 ) -> Result<Blob<'_>, object::find::existing::with_conversion::Error> {
84 Ok(self.find_object(id)?.try_into_blob()?)
85 }
86
87 #[doc(alias = "read_header", alias = "git2")]
93 pub fn find_header(&self, id: impl Into<ObjectId>) -> Result<gix_odb::find::Header, object::find::existing::Error> {
94 let id = id.into();
95 if id == ObjectId::empty_tree(self.object_hash()) {
96 return Ok(gix_odb::find::Header::Loose {
97 kind: gix_object::Kind::Tree,
98 size: 0,
99 });
100 }
101 self.objects.header(id)
102 }
103
104 #[doc(alias = "exists", alias = "git2")]
113 pub fn has_object(&self, id: impl AsRef<gix_hash::oid>) -> bool {
114 let id = id.as_ref();
115 if id.to_owned().is_empty_tree() {
116 true
117 } else {
118 self.objects.exists(id)
119 }
120 }
121
122 pub fn try_find_header(
126 &self,
127 id: impl Into<ObjectId>,
128 ) -> Result<Option<gix_odb::find::Header>, object::find::Error> {
129 let id = id.into();
130 if id == ObjectId::empty_tree(self.object_hash()) {
131 return Ok(Some(gix_odb::find::Header::Loose {
132 kind: gix_object::Kind::Tree,
133 size: 0,
134 }));
135 }
136 self.objects.try_header(&id).map_err(Into::into)
137 }
138
139 pub fn try_find_object(&self, id: impl Into<ObjectId>) -> Result<Option<Object<'_>>, object::find::Error> {
141 let id = id.into();
142 if id == ObjectId::empty_tree(self.object_hash()) {
143 return Ok(Some(Object {
144 id,
145 kind: gix_object::Kind::Tree,
146 data: Vec::new(),
147 repo: self,
148 }));
149 }
150
151 let mut buf = self.free_buf();
152 match self.objects.try_find(&id, &mut buf)? {
153 Some(obj) => {
154 let kind = obj.kind;
155 Ok(Some(Object::from_data(id, kind, buf, self)))
156 }
157 None => Ok(None),
158 }
159 }
160}
161
162impl crate::Repository {
164 pub fn write_object(&self, object: impl gix_object::WriteTo) -> Result<Id<'_>, object::write::Error> {
169 let mut buf = self.empty_reusable_buffer();
170 object
171 .write_to(buf.deref_mut())
172 .map_err(|err| Box::new(err) as Box<dyn std::error::Error + Send + Sync + 'static>)?;
173
174 self.write_object_inner(&buf, object.kind())
175 }
176
177 fn write_object_inner(&self, buf: &[u8], kind: gix_object::Kind) -> Result<Id<'_>, object::write::Error> {
178 let oid = gix_object::compute_hash(self.object_hash(), kind, buf)
179 .map_err(|err| Box::new(err) as Box<dyn std::error::Error + Send + Sync>)?;
180 if self.objects.exists(&oid) {
181 return Ok(oid.attach(self));
182 }
183
184 self.objects
185 .write_buf(kind, buf)
186 .map(|oid| oid.attach(self))
187 .map_err(Into::into)
188 }
189
190 pub fn write_blob(&self, bytes: impl AsRef<[u8]>) -> Result<Id<'_>, object::write::Error> {
195 let bytes = bytes.as_ref();
196 let oid = gix_object::compute_hash(self.object_hash(), gix_object::Kind::Blob, bytes)
197 .map_err(|err| Box::new(err) as Box<dyn std::error::Error + Send + Sync>)?;
198 if self.objects.exists(&oid) {
199 return Ok(oid.attach(self));
200 }
201 self.objects
202 .write_buf(gix_object::Kind::Blob, bytes)
203 .map_err(Into::into)
204 .map(|oid| oid.attach(self))
205 }
206
207 pub fn write_blob_stream(&self, mut bytes: impl std::io::Read) -> Result<Id<'_>, object::write::Error> {
214 let mut buf = self.empty_reusable_buffer();
215 std::io::copy(&mut bytes, buf.deref_mut())
216 .map_err(|err| Box::new(err) as Box<dyn std::error::Error + Send + Sync>)?;
217
218 self.write_blob_stream_inner(&buf)
219 }
220
221 fn write_blob_stream_inner(&self, buf: &[u8]) -> Result<Id<'_>, object::write::Error> {
222 let oid = gix_object::compute_hash(self.object_hash(), gix_object::Kind::Blob, buf)
223 .map_err(|err| Box::new(err) as Box<dyn std::error::Error + Send + Sync>)?;
224 if self.objects.exists(&oid) {
225 return Ok(oid.attach(self));
226 }
227
228 self.objects
229 .write_buf(gix_object::Kind::Blob, buf)
230 .map_err(Into::into)
231 .map(|oid| oid.attach(self))
232 }
233}
234
235impl crate::Repository {
237 pub fn tag(
243 &self,
244 name: impl AsRef<str>,
245 target: impl AsRef<gix_hash::oid>,
246 target_kind: gix_object::Kind,
247 tagger: Option<gix_actor::SignatureRef<'_>>,
248 message: impl AsRef<str>,
249 constraint: PreviousValue,
250 ) -> Result<Reference<'_>, tag::Error> {
251 let tag = gix_object::Tag {
252 target: target.as_ref().into(),
253 target_kind,
254 name: name.as_ref().into(),
255 tagger: tagger.map(|t| t.to_owned()).transpose()?,
256 message: message.as_ref().into(),
257 pgp_signature: None,
258 };
259 let tag_id = self.write_object(&tag)?;
260 self.tag_reference(name, tag_id, constraint).map_err(Into::into)
261 }
262
263 pub fn commit_as<'a, 'c, Name, E>(
267 &self,
268 committer: impl Into<gix_actor::SignatureRef<'c>>,
269 author: impl Into<gix_actor::SignatureRef<'a>>,
270 reference: Name,
271 message: impl AsRef<str>,
272 tree: impl Into<ObjectId>,
273 parents: impl IntoIterator<Item = impl Into<ObjectId>>,
274 ) -> Result<Id<'_>, commit::Error>
275 where
276 Name: TryInto<FullName, Error = E>,
277 commit::Error: From<E>,
278 {
279 self.commit_as_inner(
280 committer.into(),
281 author.into(),
282 reference.try_into()?,
283 message.as_ref(),
284 tree.into(),
285 parents.into_iter().map(Into::into).collect(),
286 )
287 }
288
289 fn commit_as_inner(
290 &self,
291 committer: gix_actor::SignatureRef<'_>,
292 author: gix_actor::SignatureRef<'_>,
293 reference: FullName,
294 message: &str,
295 tree: ObjectId,
296 parents: SmallVec<[ObjectId; 1]>,
297 ) -> Result<Id<'_>, commit::Error> {
298 use gix_ref::{
299 transaction::{Change, RefEdit},
300 Target,
301 };
302
303 let commit = gix_object::Commit {
306 message: message.into(),
307 tree,
308 author: author.into(),
309 committer: committer.into(),
310 encoding: None,
311 parents,
312 extra_headers: Default::default(),
313 };
314
315 let commit_id = self.write_object(&commit)?;
316 self.edit_references_as(
317 Some(RefEdit {
318 change: Change::Update {
319 log: LogChange {
320 mode: RefLog::AndReference,
321 force_create_reflog: false,
322 message: crate::reference::log::message(
323 "commit",
324 commit.message.as_ref(),
325 commit.parents.len(),
326 ),
327 },
328 expected: match commit.parents.first().map(|p| Target::Object(*p)) {
329 Some(previous) => {
330 if reference.as_bstr() == "HEAD" {
331 PreviousValue::MustExistAndMatch(previous)
332 } else {
333 PreviousValue::ExistingMustMatch(previous)
334 }
335 }
336 None => PreviousValue::MustNotExist,
337 },
338 new: Target::Object(commit_id.inner),
339 },
340 name: reference,
341 deref: true,
342 }),
343 Some(committer),
344 )?;
345 Ok(commit_id)
346 }
347
348 pub fn commit<Name, E>(
367 &self,
368 reference: Name,
369 message: impl AsRef<str>,
370 tree: impl Into<ObjectId>,
371 parents: impl IntoIterator<Item = impl Into<ObjectId>>,
372 ) -> Result<Id<'_>, commit::Error>
373 where
374 Name: TryInto<FullName, Error = E>,
375 commit::Error: From<E>,
376 {
377 let author = self.author().ok_or(commit::Error::AuthorMissing)??;
378 let committer = self.committer().ok_or(commit::Error::CommitterMissing)??;
379 self.commit_as(committer, author, reference, message, tree, parents)
380 }
381
382 pub fn new_commit(
389 &self,
390 message: impl AsRef<str>,
391 tree: impl Into<ObjectId>,
392 parents: impl IntoIterator<Item = impl Into<ObjectId>>,
393 ) -> Result<Commit<'_>, new_commit::Error> {
394 let author = self.author().ok_or(new_commit::Error::AuthorMissing)??;
395 let committer = self.committer().ok_or(new_commit::Error::CommitterMissing)??;
396 Ok(self.new_commit_as(committer, author, message, tree, parents)?)
397 }
398
399 pub fn new_commit_as<'a, 'c>(
405 &self,
406 committer: impl Into<gix_actor::SignatureRef<'c>>,
407 author: impl Into<gix_actor::SignatureRef<'a>>,
408 message: impl AsRef<str>,
409 tree: impl Into<ObjectId>,
410 parents: impl IntoIterator<Item = impl Into<ObjectId>>,
411 ) -> Result<Commit<'_>, new_commit_as::Error> {
412 let commit = gix_object::Commit {
413 message: message.as_ref().into(),
414 tree: tree.into(),
415 author: author.into().into(),
416 committer: committer.into().into(),
417 encoding: None,
418 parents: parents.into_iter().map(Into::into).collect(),
419 extra_headers: Default::default(),
420 };
421 let id = self.write_object(commit)?;
422 Ok(id.object()?.into_commit())
423 }
424
425 pub fn empty_tree(&self) -> Tree<'_> {
430 self.find_object(ObjectId::empty_tree(self.object_hash()))
431 .expect("always present")
432 .into_tree()
433 }
434
435 pub fn empty_blob(&self) -> Blob<'_> {
440 Blob {
441 id: self.object_hash().empty_blob(),
442 data: Vec::new(),
443 repo: self,
444 }
445 }
446}