1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
use std::{
    convert::TryInto,
    fmt::{Debug, Formatter},
    path::Path,
};

use git_hash::SIZE_OF_SHA1_DIGEST as SHA1_SIZE;

use crate::file::{self, commit::Commit, File, COMMIT_DATA_ENTRY_SIZE};

/// Access
impl File {
    /// The number of base graphs that this file depends on.
    pub fn base_graph_count(&self) -> u8 {
        self.base_graph_count
    }

    /// Returns the commit data for the commit located at the given lexigraphical position.
    ///
    /// `pos` must range from 0 to self.num_commits().
    ///
    /// # Panics
    ///
    /// Panics if `pos` is out of bounds.
    pub fn commit_at(&self, pos: file::Position) -> Commit<'_> {
        Commit::new(self, pos)
    }

    /// The kind of hash used in this File.
    ///
    /// Note that it is always conforming to the hash used in the owning repository.
    pub fn hash_kind(&self) -> git_hash::Kind {
        git_hash::Kind::Sha1
    }

    /// Returns 20 bytes sha1 at the given index in our list of (sorted) sha1 hashes.
    /// The position ranges from 0 to self.num_commits()
    // copied from git-odb/src/pack/index/access.rs
    pub fn id_at(&self, pos: file::Position) -> &git_hash::oid {
        assert!(
            pos.0 < self.num_commits(),
            "expected lexigraphical position less than {}, got {}",
            self.num_commits(),
            pos.0
        );
        let pos: usize = pos
            .0
            .try_into()
            .expect("an architecture able to hold 32 bits of integer");
        let start = self.oid_lookup_offset + (pos * SHA1_SIZE);
        git_hash::oid::try_from(&self.data[start..start + SHA1_SIZE]).expect("20 bytes SHA1 to be alright")
    }

    /// Return an iterator over all object hashes stored in the base graph.
    pub fn iter_base_graph_ids(&self) -> impl Iterator<Item = &git_hash::oid> {
        let start = self.base_graphs_list_offset.unwrap_or(0);
        let base_graphs_list = &self.data[start..start + (SHA1_SIZE * usize::from(self.base_graph_count))];
        base_graphs_list
            .chunks(SHA1_SIZE)
            .map(|bytes| git_hash::oid::try_from(bytes).expect("20 bytes SHA1 to be alright"))
    }

    /// return an iterator over all commits in this file.
    pub fn iter_commits(&self) -> impl Iterator<Item = Commit<'_>> {
        (0..self.num_commits()).map(move |i| self.commit_at(file::Position(i)))
    }

    /// Return an iterator over all object hashes stored in this file.
    pub fn iter_ids(&self) -> impl Iterator<Item = &git_hash::oid> {
        (0..self.num_commits()).map(move |i| self.id_at(file::Position(i)))
    }

    /// Translate the given object hash to its position within this file, if present.
    // copied from git-odb/src/pack/index/access.rs
    pub fn lookup(&self, id: impl AsRef<git_hash::oid>) -> Option<file::Position> {
        let id = id.as_ref();
        let first_byte = usize::from(id.first_byte());
        let mut upper_bound = self.fan[first_byte];
        let mut lower_bound = if first_byte != 0 { self.fan[first_byte - 1] } else { 0 };

        // Bisect using indices
        // TODO: Performance of V2 could possibly be better if we would be able to do a binary search
        // on 20 byte chunks directly, but doing so requires transmuting and that is not safe, even though
        // it should not be if the bytes match up and the type has no destructor.
        while lower_bound < upper_bound {
            let mid = (lower_bound + upper_bound) / 2;
            let mid_sha = self.id_at(file::Position(mid));

            use std::cmp::Ordering::*;
            match id.cmp(mid_sha) {
                Less => upper_bound = mid,
                Equal => return Some(file::Position(mid)),
                Greater => lower_bound = mid + 1,
            }
        }
        None
    }

    /// Returns the number of commits in this graph file.
    ///
    /// The maximum valid `file::Position` that can be used with this file is one less than
    /// `num_commits()`.
    pub fn num_commits(&self) -> u32 {
        self.fan[255]
    }

    /// Returns the path to this file.
    pub fn path(&self) -> &Path {
        &self.path
    }
}

impl File {
    /// Returns the byte slice for the given commit in this file's Commit Data (CDAT) chunk.
    pub(crate) fn commit_data_bytes(&self, pos: file::Position) -> &[u8] {
        assert!(
            pos.0 < self.num_commits(),
            "expected lexigraphical position less than {}, got {}",
            self.num_commits(),
            pos.0
        );
        let pos: usize = pos
            .0
            .try_into()
            .expect("an architecture able to hold 32 bits of integer");
        let start = self.commit_data_offset + (pos * COMMIT_DATA_ENTRY_SIZE);
        &self.data[start..start + COMMIT_DATA_ENTRY_SIZE]
    }

    /// Returns the byte slice for this file's entire Extra Edge List (EDGE) chunk.
    pub(crate) fn extra_edges_data(&self) -> Option<&[u8]> {
        Some(&self.data[self.extra_edges_list_range.clone()?])
    }
}

impl Debug for File {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, r#"File("{:?}")"#, self.path.display())
    }
}