Skip to content

Commit 1a4c84d

Browse files
authored
Merge pull request #2167 from GitoxideLabs/copilot/fix-3952f55e-8faf-4737-886f-09e74cab4ca8
Make `gix::Repository::has_object()` consider empty blob ids to be always present
2 parents b4812d9 + 689d839 commit 1a4c84d

File tree

2 files changed

+103
-1
lines changed

2 files changed

+103
-1
lines changed

gix/src/repository/object.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ impl crate::Repository {
4949
repo: self,
5050
});
5151
}
52+
if id == ObjectId::empty_blob(self.object_hash()) {
53+
return Ok(Object {
54+
id,
55+
kind: gix_object::Kind::Blob,
56+
data: Vec::new(),
57+
repo: self,
58+
});
59+
}
5260
let mut buf = self.free_buf();
5361
let kind = self.objects.find(&id, &mut buf)?.kind;
5462
Ok(Object::from_data(id, kind, buf, self))
@@ -86,6 +94,8 @@ impl crate::Repository {
8694
/// Obtain information about an object without fully decoding it, or fail if the object doesn't exist.
8795
///
8896
/// Note that despite being cheaper than [`Self::find_object()`], there is still some effort traversing delta-chains.
97+
/// Also note that for empty trees and blobs, it will always report it to exist in loose objects, even if they don't
98+
/// exist or if they exist in a pack.
8999
#[doc(alias = "read_header", alias = "git2")]
90100
pub fn find_header(&self, id: impl Into<ObjectId>) -> Result<gix_odb::find::Header, object::find::existing::Error> {
91101
let id = id.into();
@@ -95,6 +105,12 @@ impl crate::Repository {
95105
size: 0,
96106
});
97107
}
108+
if id == ObjectId::empty_blob(self.object_hash()) {
109+
return Ok(gix_odb::find::Header::Loose {
110+
kind: gix_object::Kind::Blob,
111+
size: 0,
112+
});
113+
}
98114
self.objects.header(id)
99115
}
100116

@@ -109,7 +125,7 @@ impl crate::Repository {
109125
#[doc(alias = "exists", alias = "git2")]
110126
pub fn has_object(&self, id: impl AsRef<gix_hash::oid>) -> bool {
111127
let id = id.as_ref();
112-
if id.to_owned().is_empty_tree() {
128+
if id.to_owned().is_empty_tree() || id.to_owned().is_empty_blob() {
113129
true
114130
} else {
115131
self.objects.exists(id)
@@ -130,6 +146,12 @@ impl crate::Repository {
130146
size: 0,
131147
}));
132148
}
149+
if id == ObjectId::empty_blob(self.object_hash()) {
150+
return Ok(Some(gix_odb::find::Header::Loose {
151+
kind: gix_object::Kind::Blob,
152+
size: 0,
153+
}));
154+
}
133155
self.objects.try_header(&id).map_err(Into::into)
134156
}
135157

@@ -144,6 +166,14 @@ impl crate::Repository {
144166
repo: self,
145167
}));
146168
}
169+
if id == ObjectId::empty_blob(self.object_hash()) {
170+
return Ok(Some(Object {
171+
id,
172+
kind: gix_object::Kind::Blob,
173+
data: Vec::new(),
174+
repo: self,
175+
}));
176+
}
147177

148178
let mut buf = self.free_buf();
149179
match self.objects.try_find(&id, &mut buf)? {

gix/tests/gix/repository/object.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use gix_odb::Header;
2+
use gix_pack::Find;
13
use gix_testtools::tempfile;
24

35
use crate::util::named_subrepo_opts;
@@ -507,6 +509,76 @@ mod find {
507509
);
508510
Ok(())
509511
}
512+
513+
#[test]
514+
fn empty_blob_can_always_be_found() -> crate::Result {
515+
let repo = basic_repo()?;
516+
let empty_blob = gix::hash::ObjectId::empty_blob(repo.object_hash());
517+
assert_eq!(repo.find_object(empty_blob)?.into_blob().data.len(), 0);
518+
assert!(repo.has_object(empty_blob));
519+
assert_eq!(
520+
repo.find_header(empty_blob)?,
521+
gix_odb::find::Header::Loose {
522+
kind: gix_object::Kind::Blob,
523+
size: 0,
524+
},
525+
"empty blob is considered a loose object"
526+
);
527+
assert_eq!(
528+
repo.try_find_object(empty_blob)?
529+
.expect("present")
530+
.into_blob()
531+
.data
532+
.len(),
533+
0
534+
);
535+
assert_eq!(
536+
repo.try_find_header(empty_blob)?,
537+
Some(gix_odb::find::Header::Loose {
538+
kind: gix_object::Kind::Blob,
539+
size: 0,
540+
}),
541+
"empty blob is considered a loose object"
542+
);
543+
Ok(())
544+
}
545+
}
546+
547+
#[test]
548+
fn empty_objects_are_always_present_but_not_in_plumbing() -> crate::Result {
549+
let repo = empty_bare_in_memory_repo()?;
550+
let empty_blob_id = gix::hash::ObjectId::empty_blob(repo.object_hash());
551+
552+
assert!(
553+
repo.has_object(empty_blob_id),
554+
"empty object is always present even if it's not"
555+
);
556+
assert!(!repo.objects.contains(&empty_blob_id));
557+
558+
let header = repo.find_header(empty_blob_id)?;
559+
assert_eq!(header.kind(), gix_object::Kind::Blob);
560+
assert_eq!(header.size(), 0);
561+
assert_eq!(repo.objects.try_header(&empty_blob_id)?, None);
562+
563+
let header = repo.try_find_header(empty_blob_id)?.expect("should find header");
564+
assert_eq!(header.kind(), gix_object::Kind::Blob);
565+
assert_eq!(header.size(), 0);
566+
567+
let obj = repo.find_object(empty_blob_id)?;
568+
assert_eq!(obj.kind, gix_object::Kind::Blob);
569+
assert_eq!(obj.data.len(), 0);
570+
571+
let mut buf = Vec::new();
572+
assert_eq!(repo.objects.try_find(&empty_blob_id, &mut buf)?, None);
573+
574+
let obj = repo.try_find_object(empty_blob_id)?.expect("should find object");
575+
assert_eq!(obj.kind, gix_object::Kind::Blob);
576+
assert_eq!(obj.data.len(), 0);
577+
578+
let blob = obj.try_into_blob()?;
579+
assert_eq!(blob.data.len(), 0);
580+
581+
Ok(())
510582
}
511583

512584
mod tag {

0 commit comments

Comments
 (0)