@@ -48,6 +48,7 @@ use crate::{
48
48
49
49
/// Enumeration of errors that can occur in repo operations.
50
50
pub mod error {
51
+ use crate :: Oid ;
51
52
use std:: path:: PathBuf ;
52
53
use thiserror:: Error ;
53
54
@@ -56,6 +57,8 @@ pub mod error {
56
57
pub enum Repo {
57
58
#[ error( "path not found for: {0}" ) ]
58
59
PathNotFound ( PathBuf ) ,
60
+ #[ error( "blob not found for: {0}" ) ]
61
+ BlobNotFound ( Oid ) ,
59
62
}
60
63
}
61
64
@@ -301,6 +304,20 @@ impl Repository {
301
304
Ok ( Blob :: < BlobRef < ' a > > :: new ( file. id ( ) , git2_blob, last_commit) )
302
305
}
303
306
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
+
304
321
/// Returns the last commit, if exists, for a `path` in the history of
305
322
/// `rev`.
306
323
pub fn last_commit < P , C > ( & self , path : & P , rev : C ) -> Result < Option < Commit > , Error >
@@ -524,6 +541,53 @@ impl Repository {
524
541
Ok ( diff)
525
542
}
526
543
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
+
527
591
/// Returns a full reference name with namespace(s) included.
528
592
pub ( crate ) fn namespaced_refname < ' a > (
529
593
& ' a self ,
0 commit comments