Skip to content

Commit 2fc9dbe

Browse files
CopilotByron
andcommitted
fix: empty blob hashes are now automatically considered present.
Co-authored-by: Byron <[email protected]>
1 parent 42f8db5 commit 2fc9dbe

File tree

2 files changed

+132
-1
lines changed

2 files changed

+132
-1
lines changed

gix/src/repository/object.rs

Lines changed: 29 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))
@@ -95,6 +103,12 @@ impl crate::Repository {
95103
size: 0,
96104
});
97105
}
106+
if id == ObjectId::empty_blob(self.object_hash()) {
107+
return Ok(gix_odb::find::Header::Loose {
108+
kind: gix_object::Kind::Blob,
109+
size: 0,
110+
});
111+
}
98112
self.objects.header(id)
99113
}
100114

@@ -109,7 +123,7 @@ impl crate::Repository {
109123
#[doc(alias = "exists", alias = "git2")]
110124
pub fn has_object(&self, id: impl AsRef<gix_hash::oid>) -> bool {
111125
let id = id.as_ref();
112-
if id.to_owned().is_empty_tree() {
126+
if id.to_owned().is_empty_tree() || id.to_owned().is_empty_blob() {
113127
true
114128
} else {
115129
self.objects.exists(id)
@@ -130,6 +144,12 @@ impl crate::Repository {
130144
size: 0,
131145
}));
132146
}
147+
if id == ObjectId::empty_blob(self.object_hash()) {
148+
return Ok(Some(gix_odb::find::Header::Loose {
149+
kind: gix_object::Kind::Blob,
150+
size: 0,
151+
}));
152+
}
133153
self.objects.try_header(&id).map_err(Into::into)
134154
}
135155

@@ -144,6 +164,14 @@ impl crate::Repository {
144164
repo: self,
145165
}));
146166
}
167+
if id == ObjectId::empty_blob(self.object_hash()) {
168+
return Ok(Some(Object {
169+
id,
170+
kind: gix_object::Kind::Blob,
171+
data: Vec::new(),
172+
repo: self,
173+
}));
174+
}
147175

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

gix/tests/gix/repository/object.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,109 @@ mod find {
507507
);
508508
Ok(())
509509
}
510+
511+
#[test]
512+
fn empty_blob_can_always_be_found() -> crate::Result {
513+
let repo = basic_repo()?;
514+
let empty_blob = gix::hash::ObjectId::empty_blob(repo.object_hash());
515+
assert_eq!(repo.find_object(empty_blob)?.into_blob().data.len(), 0);
516+
assert!(repo.has_object(empty_blob));
517+
assert_eq!(
518+
repo.find_header(empty_blob)?,
519+
gix_odb::find::Header::Loose {
520+
kind: gix_object::Kind::Blob,
521+
size: 0,
522+
},
523+
"empty blob is considered a loose object"
524+
);
525+
assert_eq!(
526+
repo.try_find_object(empty_blob)?
527+
.expect("present")
528+
.into_blob()
529+
.data
530+
.len(),
531+
0
532+
);
533+
assert_eq!(
534+
repo.try_find_header(empty_blob)?,
535+
Some(gix_odb::find::Header::Loose {
536+
kind: gix_object::Kind::Blob,
537+
size: 0,
538+
}),
539+
"empty blob is considered a loose object"
540+
);
541+
542+
// Note: Unlike empty tree, empty blobs might actually exist in the repository.
543+
// The key point is that has_object() should always return true for empty blobs,
544+
// regardless of whether they are physically stored or not.
545+
Ok(())
546+
}
547+
}
548+
549+
#[test]
550+
fn empty_blob_is_always_considered_present() -> crate::Result {
551+
use gix_object::Find;
552+
553+
// Test with an empty in-memory repository to ensure empty blob is considered present
554+
// even when it doesn't physically exist
555+
let repo = empty_bare_in_memory_repo()?;
556+
let empty_blob = gix::hash::ObjectId::empty_blob(repo.object_hash());
557+
558+
// The key behavior being tested: has_object should return true for empty blob
559+
assert!(repo.has_object(empty_blob), "empty blob should always be considered present");
560+
561+
// Verify that the lower-level object database doesn't have it
562+
let mut buf = Vec::new();
563+
let lower_level_result = repo.objects.try_find(&empty_blob, &mut buf)?;
564+
565+
// Empty blob might or might not exist at the lower level - that's implementation dependent
566+
// But has_object should always return true regardless
567+
match lower_level_result {
568+
Some(_) => {
569+
// If it exists at the lower level, that's fine
570+
}
571+
None => {
572+
// If it doesn't exist at the lower level, has_object should still return true
573+
// thanks to our special handling
574+
}
575+
}
576+
577+
Ok(())
578+
}
579+
580+
#[test]
581+
fn empty_blob_edge_cases() -> crate::Result {
582+
let repo = empty_bare_in_memory_repo()?;
583+
let empty_blob_id = gix::hash::ObjectId::empty_blob(repo.object_hash());
584+
585+
// Test all the related methods for empty blobs
586+
assert!(repo.has_object(&empty_blob_id), "has_object should return true");
587+
588+
// Test find_header
589+
let header = repo.find_header(empty_blob_id)?;
590+
assert_eq!(header.kind(), gix_object::Kind::Blob);
591+
assert_eq!(header.size(), 0);
592+
593+
// Test try_find_header
594+
let header = repo.try_find_header(empty_blob_id)?.expect("should find header");
595+
assert_eq!(header.kind(), gix_object::Kind::Blob);
596+
assert_eq!(header.size(), 0);
597+
598+
// Test find_object
599+
let obj = repo.find_object(empty_blob_id)?;
600+
assert_eq!(obj.kind, gix_object::Kind::Blob);
601+
assert_eq!(obj.data.len(), 0);
602+
603+
// Test try_find_object
604+
let obj = repo.try_find_object(empty_blob_id)?.expect("should find object");
605+
assert_eq!(obj.kind, gix_object::Kind::Blob);
606+
assert_eq!(obj.data.len(), 0);
607+
608+
// Test that we can get a blob from the object
609+
let blob = obj.into_blob();
610+
assert_eq!(blob.data.len(), 0);
611+
612+
Ok(())
510613
}
511614

512615
mod tag {

0 commit comments

Comments
 (0)