Skip to content

Commit 25add2b

Browse files
committed
radicle-surf: Repository.blob_at() to retrieve a blob using its oid.
Signed-off-by: Han Xu <[email protected]>
1 parent 165f25f commit 25add2b

File tree

2 files changed

+91
-1
lines changed

2 files changed

+91
-1
lines changed

radicle-surf/src/repo.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ use crate::{
4848

4949
/// Enumeration of errors that can occur in repo operations.
5050
pub mod error {
51+
use crate::Oid;
5152
use std::path::PathBuf;
5253
use thiserror::Error;
5354

@@ -56,6 +57,8 @@ pub mod error {
5657
pub enum Repo {
5758
#[error("path not found for: {0}")]
5859
PathNotFound(PathBuf),
60+
#[error("blob not found for: {0}")]
61+
BlobNotFound(Oid),
5962
}
6063
}
6164

@@ -301,6 +304,20 @@ impl Repository {
301304
Ok(Blob::<BlobRef<'a>>::new(file.id(), git2_blob, last_commit))
302305
}
303306

307+
/// Retrieves the blob with `oid` in `commit`.
308+
pub fn blob_at<'a, C: ToCommit>(
309+
&'a self,
310+
commit: C,
311+
oid: Oid,
312+
) -> Result<Blob<BlobRef<'a>>, Error> {
313+
let commit = commit
314+
.to_commit(self)
315+
.map_err(|e| Error::ToCommit(e.into()))?;
316+
let git2_blob = self.find_blob(oid)?;
317+
let last_commit = self.find_commit_of_blob(oid, &commit)?;
318+
Ok(Blob::<BlobRef<'a>>::new(oid, git2_blob, last_commit))
319+
}
320+
304321
/// Returns the last commit, if exists, for a `path` in the history of
305322
/// `rev`.
306323
pub fn last_commit<P, C>(&self, path: &P, rev: C) -> Result<Option<Commit>, Error>
@@ -524,6 +541,53 @@ impl Repository {
524541
Ok(diff)
525542
}
526543

544+
/// Returns true if the diff between `from` and `to` creates `oid`.
545+
fn diff_commits_has_oid(
546+
&self,
547+
from: Option<&git2::Commit>,
548+
to: &git2::Commit,
549+
oid: &git2::Oid,
550+
) -> Result<bool, Error> {
551+
let diff = self.diff_commits(None, from, to)?;
552+
for delta in diff.deltas() {
553+
if &delta.new_file().id() == oid {
554+
return Ok(true);
555+
}
556+
}
557+
Ok(false)
558+
}
559+
560+
/// Returns whether `oid` was created in `commit` or not.
561+
fn is_oid_in_commit(&self, oid: Oid, commit: &git2::Commit) -> Result<bool, Error> {
562+
if commit.parent_count() == 0 {
563+
return self.diff_commits_has_oid(None, commit, oid.as_ref());
564+
}
565+
566+
for p in commit.parents() {
567+
if self.diff_commits_has_oid(Some(&p), commit, oid.as_ref())? {
568+
return Ok(true);
569+
}
570+
}
571+
572+
Ok(false)
573+
}
574+
575+
/// Returns the commit that created the blob with `oid`.
576+
///
577+
/// It is assumed that `oid` exists in `head`.
578+
fn find_commit_of_blob(&self, oid: Oid, head: &Commit) -> Result<Commit, Error> {
579+
let mut revwalk = self.revwalk()?;
580+
revwalk.push(head.id.into())?;
581+
for commit_id in revwalk {
582+
let commit_id = commit_id?;
583+
let git2_commit = self.inner.find_commit(commit_id)?;
584+
if self.is_oid_in_commit(oid, &git2_commit)? {
585+
return Ok(Commit::try_from(git2_commit)?);
586+
}
587+
}
588+
Err(Error::Repo(error::Repo::BlobNotFound(oid)))
589+
}
590+
527591
/// Returns a full reference name with namespace(s) included.
528592
pub(crate) fn namespaced_refname<'a>(
529593
&'a self,

radicle-surf/t/src/source.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::path::PathBuf;
22

33
use radicle_git_ext::ref_format::refname;
4-
use radicle_surf::{Branch, Glob, Repository};
4+
use radicle_surf::{Branch, Glob, Oid, Repository};
55
use serde_json::json;
66

77
const GIT_PLATINUM: &str = "../data/git-platinum";
@@ -175,6 +175,32 @@ fn repo_blob() {
175175
assert_eq!(json_ref, json_owned);
176176
}
177177

178+
#[test]
179+
fn repo_blob_at() {
180+
let repo = Repository::open(GIT_PLATINUM).unwrap();
181+
let oid = Oid::try_from("b84992d24be67536837f5ab45a943f1b3f501878").unwrap();
182+
183+
// Retrieve the blob using its oid.
184+
let blob = repo
185+
.blob_at("27acd68c7504755aa11023300890bb85bbd69d45", oid)
186+
.unwrap();
187+
188+
// Verify the blob oid.
189+
let blob_oid = blob.object_id();
190+
assert_eq!(blob_oid, oid);
191+
192+
// Verify the commit that created the blob.
193+
let blob_commit = blob.commit();
194+
assert_eq!(
195+
blob_commit.id.to_string(),
196+
"e24124b7538658220b5aaf3b6ef53758f0a106dc"
197+
);
198+
199+
// Verify the blob content ("memory.rs").
200+
assert!(!blob.is_binary());
201+
assert_eq!(blob.size(), 6253);
202+
}
203+
178204
#[test]
179205
fn tree_ordering() {
180206
let repo = Repository::open(GIT_PLATINUM).unwrap();

0 commit comments

Comments
 (0)