150 lines · 4.8 KB · Rust
RawDownload
use std::convert::TryInto; use bstr::BString;use git_hash::ObjectId; use crate::{    entry,    extension::{Signature, UntrackedCache},    util::{read_u32, split_at_byte_exclusive, split_at_pos, var_int},}; pub struct OidStat {    pub stat: entry::Stat,    pub id: ObjectId,} /// A directory with information about its untracked files, and its sub-directoriespub struct Directory {    /// The directories name, or an empty string if this is the root directory.    pub name: BString,    /// Untracked files and directory names    pub untracked_entries: Vec<BString>,    /// indices for sub-directories similar to this one.    pub sub_directories: Vec<usize>,     /// The directories stat data, if available or valid // TODO: or is it the exclude file?    pub stat: Option<entry::Stat>,    /// The oid of a .gitignore file, if it exists    pub exclude_file_oid: Option<ObjectId>,    /// TODO: figure out what this really does    pub check_only: bool,} /// Only used as an indicatorpub const SIGNATURE: Signature = *b"UNTR"; #[allow(unused)]pub fn decode(data: &[u8], object_hash: git_hash::Kind) -> Option<UntrackedCache> {    if !data.last().map(|b| *b == 0).unwrap_or(false) {        return None;    }    let (identifier_len, data) = var_int(data)?;    let (identifier, data) = split_at_pos(data, identifier_len.try_into().ok()?)?;     let hash_len = object_hash.len_in_bytes();    let (info_exclude, data) = decode_oid_stat(data, hash_len)?;    let (excludes_file, data) = decode_oid_stat(data, hash_len)?;    let (dir_flags, data) = read_u32(data)?;    let (exclude_filename_per_dir, data) = split_at_byte_exclusive(data, 0)?;     let (num_directory_blocks, data) = var_int(data)?;     let mut res = UntrackedCache {        identifier: identifier.into(),        info_exclude: (!info_exclude.id.is_null()).then(|| info_exclude),        excludes_file: (!excludes_file.id.is_null()).then(|| excludes_file),        exclude_filename_per_dir: exclude_filename_per_dir.into(),        dir_flags,        directories: Vec::new(),    };    if num_directory_blocks == 0 {        return data.is_empty().then(|| res);    }     let num_directory_blocks = num_directory_blocks.try_into().ok()?;    let directories = &mut res.directories;    directories.reserve(num_directory_blocks);     let data = decode_directory_block(data, directories)?;    if directories.len() != num_directory_blocks {        return None;    }    let (valid, data) = git_bitmap::ewah::decode(data).ok()?;    let (check_only, data) = git_bitmap::ewah::decode(data).ok()?;    let (hash_valid, mut data) = git_bitmap::ewah::decode(data).ok()?;     if valid.num_bits() > num_directory_blocks        || check_only.num_bits() > num_directory_blocks        || hash_valid.num_bits() > num_directory_blocks    {        return None;    }     check_only.for_each_set_bit(|index| {        directories[index].check_only = true;        Some(())    })?;    valid.for_each_set_bit(|index| {        let (stat, rest) = crate::decode::stat(data)?;        directories[index].stat = stat.into();        data = rest;        Some(())    });    hash_valid.for_each_set_bit(|index| {        let (hash, rest) = split_at_pos(data, hash_len)?;        data = rest;        directories[index].exclude_file_oid = ObjectId::from(hash).into();        Some(())    });     // null-byte checked in the beginning    if data.len() != 1 {        return None;    }    res.into()} fn decode_directory_block<'a>(data: &'a [u8], directories: &mut Vec<Directory>) -> Option<&'a [u8]> {    let (num_untracked, data) = var_int(data)?;    let (num_dirs, data) = var_int(data)?;    let (name, mut data) = split_at_byte_exclusive(data, 0)?;    let mut untracked_entries = Vec::<BString>::with_capacity(num_untracked.try_into().ok()?);    for _ in 0..num_untracked {        let (name, rest) = split_at_byte_exclusive(data, 0)?;        data = rest;        untracked_entries.push(name.into());    }     let index = directories.len();    directories.push(Directory {        name: name.into(),        untracked_entries,        sub_directories: Vec::with_capacity(num_dirs.try_into().ok()?),        // the following are set later through their bitmaps        stat: None,        exclude_file_oid: None,        check_only: false,    });     for _ in 0..num_dirs {        let subdir_index = directories.len();        let rest = decode_directory_block(data, directories)?;        data = rest;        directories[index].sub_directories.push(subdir_index);    }     data.into()} fn decode_oid_stat(data: &[u8], hash_len: usize) -> Option<(OidStat, &[u8])> {    let (stat, data) = crate::decode::stat(data)?;    let (hash, data) = split_at_pos(data, hash_len)?;    Some((        OidStat {            stat,            id: ObjectId::from(hash),        },        data,    ))}