657 lines · 23.5 KB · Rust
RawDownload
// Copyright 2023 The Jujutsu Authors//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License. use std::cmp::max;use std::collections::BTreeMap;use std::collections::HashMap;use std::fmt;use std::fmt::Debug;use std::io::Write as _;use std::iter;use std::ops::Bound;use std::path::Path;use std::sync::Arc; use async_trait::async_trait;use blake2::Blake2b512;use digest::Digest as _;use itertools::Itertools as _;use smallvec::SmallVec;use smallvec::smallvec;use tempfile::NamedTempFile; use super::changed_path::CompositeChangedPathIndex;use super::changed_path::collect_changed_paths;use super::composite::AsCompositeIndex;use super::composite::ChangeIdIndexImpl;use super::composite::CommitIndexSegment;use super::composite::CommitIndexSegmentId;use super::composite::CompositeCommitIndex;use super::composite::CompositeIndex;use super::composite::DynCommitIndexSegment;use super::entry::GlobalCommitPosition;use super::entry::LocalCommitPosition;use super::entry::SmallGlobalCommitPositionsVec;use super::entry::SmallLocalCommitPositionsVec;use super::readonly::COMMIT_INDEX_SEGMENT_FILE_FORMAT_VERSION;use super::readonly::DefaultReadonlyIndex;use super::readonly::FieldLengths;use super::readonly::OVERFLOW_FLAG;use super::readonly::ReadonlyCommitIndexSegment;use crate::backend::BackendResult;use crate::backend::ChangeId;use crate::backend::CommitId;use crate::commit::Commit;use crate::file_util::IoResultExt as _;use crate::file_util::PathError;use crate::file_util::persist_content_addressed_temp_file;use crate::index::ChangeIdIndex;use crate::index::Index;use crate::index::IndexError;use crate::index::IndexResult;use crate::index::MutableIndex;use crate::index::ReadonlyIndex;use crate::object_id::HexPrefix;use crate::object_id::ObjectId;use crate::object_id::PrefixResolution;use crate::repo_path::RepoPathBuf;use crate::revset::ResolvedExpression;use crate::revset::Revset;use crate::revset::RevsetEvaluationError;use crate::store::Store; #[derive(Clone, Debug)]struct MutableGraphEntry {    commit_id: CommitId,    change_id: ChangeId,    generation_number: u32,    parent_positions: SmallGlobalCommitPositionsVec,} #[derive(Clone)]pub(super) struct MutableCommitIndexSegment {    parent_file: Option<Arc<ReadonlyCommitIndexSegment>>,    num_parent_commits: u32,    field_lengths: FieldLengths,    graph: Vec<MutableGraphEntry>,    commit_lookup: BTreeMap<CommitId, LocalCommitPosition>,    change_lookup: BTreeMap<ChangeId, SmallLocalCommitPositionsVec>,} impl Debug for MutableCommitIndexSegment {    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {        f.debug_struct("MutableCommitIndexSegment")            .field("parent_file", &self.parent_file)            .finish_non_exhaustive()    }} impl MutableCommitIndexSegment {    pub(super) fn full(field_lengths: FieldLengths) -> Self {        Self {            parent_file: None,            num_parent_commits: 0,            field_lengths,            graph: vec![],            commit_lookup: BTreeMap::new(),            change_lookup: BTreeMap::new(),        }    }     pub(super) fn incremental(parent_file: Arc<ReadonlyCommitIndexSegment>) -> Self {        let num_parent_commits = parent_file.as_composite().num_commits();        let field_lengths = parent_file.field_lengths();        Self {            parent_file: Some(parent_file),            num_parent_commits,            field_lengths,            graph: vec![],            commit_lookup: BTreeMap::new(),            change_lookup: BTreeMap::new(),        }    }     pub(super) fn as_composite(&self) -> &CompositeCommitIndex {        CompositeCommitIndex::new(self)    }     pub(super) fn add_commit_data(        &mut self,        commit_id: CommitId,        change_id: ChangeId,        parent_ids: &[CommitId],    ) {        if self.as_composite().has_id(&commit_id) {            return;        }        let mut entry = MutableGraphEntry {            commit_id,            change_id,            generation_number: 0,            parent_positions: SmallVec::new(),        };        for parent_id in parent_ids {            let parent_entry = self                .as_composite()                .entry_by_id(parent_id)                .expect("parent commit is not indexed");            entry.generation_number = max(                entry.generation_number,                parent_entry.generation_number() + 1,            );            entry.parent_positions.push(parent_entry.position());        }        let local_pos = LocalCommitPosition(u32::try_from(self.graph.len()).unwrap());        self.commit_lookup            .insert(entry.commit_id.clone(), local_pos);        self.change_lookup            .entry(entry.change_id.clone())            // positions are inherently sorted            .and_modify(|positions| positions.push(local_pos))            .or_insert(smallvec![local_pos]);        self.graph.push(entry);    }     pub(super) fn add_commits_from(&mut self, other_segment: &DynCommitIndexSegment) {        let other = CompositeCommitIndex::new(other_segment);        for pos in other_segment.num_parent_commits()..other.num_commits() {            let entry = other.entry_by_pos(GlobalCommitPosition(pos));            let parent_ids = entry.parents().map(|entry| entry.commit_id()).collect_vec();            self.add_commit_data(entry.commit_id(), entry.change_id(), &parent_ids);        }    }     pub(super) fn merge_in(&mut self, other: &Arc<ReadonlyCommitIndexSegment>) {        // Collect other segments down to the common ancestor segment        let files_to_add = itertools::merge_join_by(            self.as_composite().ancestor_files_without_local(),            iter::once(other).chain(other.as_composite().ancestor_files_without_local()),            |own, other| {                let own_num_commits = own.as_composite().num_commits();                let other_num_commits = other.as_composite().num_commits();                own_num_commits.cmp(&other_num_commits).reverse()            },        )        .take_while(|own_other| {            own_other                .as_ref()                .both()                .is_none_or(|(own, other)| own.id() != other.id())        })        .filter_map(|own_other| own_other.right())        .collect_vec();         for &file in files_to_add.iter().rev() {            self.add_commits_from(file.as_ref());        }    }     fn serialize_parent_filename(&self, buf: &mut Vec<u8>) {        if let Some(parent_file) = &self.parent_file {            let hex = parent_file.id().hex();            buf.extend(u32::try_from(hex.len()).unwrap().to_le_bytes());            buf.extend_from_slice(hex.as_bytes());        } else {            buf.extend(0_u32.to_le_bytes());        }    }     fn serialize_local_entries(&self, buf: &mut Vec<u8>) {        assert_eq!(self.graph.len(), self.commit_lookup.len());        debug_assert_eq!(            self.graph.len(),            self.change_lookup.values().flatten().count()        );         let num_commits = u32::try_from(self.graph.len()).unwrap();        buf.extend(num_commits.to_le_bytes());        let num_change_ids = u32::try_from(self.change_lookup.len()).unwrap();        buf.extend(num_change_ids.to_le_bytes());        // We'll write the actual values later        let parent_overflow_offset = buf.len();        buf.extend(0_u32.to_le_bytes());        let change_overflow_offset = buf.len();        buf.extend(0_u32.to_le_bytes());         // Positions of change ids in the sorted table        let change_id_pos_map: HashMap<&ChangeId, u32> = self            .change_lookup            .keys()            .enumerate()            .map(|(i, change_id)| (change_id, u32::try_from(i).unwrap()))            .collect();         let mut parent_overflow = vec![];        for entry in &self.graph {            buf.extend(entry.generation_number.to_le_bytes());             match entry.parent_positions.as_slice() {                [] => {                    buf.extend((!0_u32).to_le_bytes());                    buf.extend((!0_u32).to_le_bytes());                }                [GlobalCommitPosition(pos1)] => {                    assert!(*pos1 < OVERFLOW_FLAG);                    buf.extend(pos1.to_le_bytes());                    buf.extend((!0_u32).to_le_bytes());                }                [GlobalCommitPosition(pos1), GlobalCommitPosition(pos2)] => {                    assert!(*pos1 < OVERFLOW_FLAG);                    assert!(*pos2 < OVERFLOW_FLAG);                    buf.extend(pos1.to_le_bytes());                    buf.extend(pos2.to_le_bytes());                }                positions => {                    let overflow_pos = u32::try_from(parent_overflow.len()).unwrap();                    let num_parents = u32::try_from(positions.len()).unwrap();                    assert!(overflow_pos < OVERFLOW_FLAG);                    assert!(num_parents < OVERFLOW_FLAG);                    buf.extend((!overflow_pos).to_le_bytes());                    buf.extend((!num_parents).to_le_bytes());                    parent_overflow.extend_from_slice(positions);                }            }             buf.extend(change_id_pos_map[&entry.change_id].to_le_bytes());             assert_eq!(                entry.commit_id.as_bytes().len(),                self.field_lengths.commit_id            );            buf.extend_from_slice(entry.commit_id.as_bytes());        }         for LocalCommitPosition(pos) in self.commit_lookup.values() {            buf.extend(pos.to_le_bytes());        }         for change_id in self.change_lookup.keys() {            assert_eq!(change_id.as_bytes().len(), self.field_lengths.change_id);            buf.extend_from_slice(change_id.as_bytes());        }         let mut change_overflow = vec![];        for positions in self.change_lookup.values() {            match positions.as_slice() {                [] => panic!("change id lookup entry must not be empty"),                // Optimize for imported commits                [LocalCommitPosition(pos1)] => {                    assert!(*pos1 < OVERFLOW_FLAG);                    buf.extend(pos1.to_le_bytes());                }                positions => {                    let overflow_pos = u32::try_from(change_overflow.len()).unwrap();                    assert!(overflow_pos < OVERFLOW_FLAG);                    buf.extend((!overflow_pos).to_le_bytes());                    change_overflow.extend_from_slice(positions);                }            }        }         let num_parent_overflow = u32::try_from(parent_overflow.len()).unwrap();        buf[parent_overflow_offset..][..4].copy_from_slice(&num_parent_overflow.to_le_bytes());        for GlobalCommitPosition(pos) in parent_overflow {            buf.extend(pos.to_le_bytes());        }         let num_change_overflow = u32::try_from(change_overflow.len()).unwrap();        buf[change_overflow_offset..][..4].copy_from_slice(&num_change_overflow.to_le_bytes());        for LocalCommitPosition(pos) in change_overflow {            buf.extend(pos.to_le_bytes());        }    }     /// If the mutable segment has more than half the commits of its parent    /// segment, return mutable segment with the commits from both. This is done    /// recursively, so the stack of index segments has O(log n) files.    pub(super) fn maybe_squash_with_ancestors(self) -> Self {        let mut num_new_commits = self.num_local_commits();        let mut files_to_squash = vec![];        let mut base_parent_file = None;        for parent_file in self.as_composite().ancestor_files_without_local() {            // TODO: We should probably also squash if the parent file has less than N            // commits, regardless of how many (few) are in `self`.            if 2 * num_new_commits < parent_file.num_local_commits() {                base_parent_file = Some(parent_file.clone());                break;            }            num_new_commits += parent_file.num_local_commits();            files_to_squash.push(parent_file.clone());        }         if files_to_squash.is_empty() {            return self;        }         let mut squashed = if let Some(parent_file) = base_parent_file {            Self::incremental(parent_file)        } else {            Self::full(self.field_lengths)        };        for parent_file in files_to_squash.iter().rev() {            squashed.add_commits_from(parent_file.as_ref());        }        squashed.add_commits_from(&self);        squashed    }     pub(super) fn save_in(        mut self,        dir: &Path,    ) -> Result<Arc<ReadonlyCommitIndexSegment>, PathError> {        if self.num_local_commits() == 0            && let Some(parent_file) = self.parent_file.take()        {            return Ok(parent_file);        }         let mut buf = Vec::new();        buf.extend(COMMIT_INDEX_SEGMENT_FILE_FORMAT_VERSION.to_le_bytes());        self.serialize_parent_filename(&mut buf);        let local_entries_offset = buf.len();        self.serialize_local_entries(&mut buf);        let mut hasher = Blake2b512::new();        hasher.update(&buf);        let index_file_id = CommitIndexSegmentId::from_bytes(&hasher.finalize());        let index_file_path = dir.join(index_file_id.hex());         let mut temp_file = NamedTempFile::new_in(dir).context(dir)?;        let file = temp_file.as_file_mut();        file.write_all(&buf).context(temp_file.path())?;        persist_content_addressed_temp_file(temp_file, &index_file_path)            .context(&index_file_path)?;         Ok(ReadonlyCommitIndexSegment::load_with_parent_file(            &mut &buf[local_entries_offset..],            index_file_id,            self.parent_file,            self.field_lengths,        )        .expect("in-memory index data should be valid and readable"))    }} impl CommitIndexSegment for MutableCommitIndexSegment {    fn num_parent_commits(&self) -> u32 {        self.num_parent_commits    }     fn num_local_commits(&self) -> u32 {        self.graph.len().try_into().unwrap()    }     fn parent_file(&self) -> Option<&Arc<ReadonlyCommitIndexSegment>> {        self.parent_file.as_ref()    }     fn commit_id_to_pos(&self, commit_id: &CommitId) -> Option<LocalCommitPosition> {        self.commit_lookup.get(commit_id).copied()    }     fn resolve_neighbor_commit_ids(        &self,        commit_id: &CommitId,    ) -> (Option<CommitId>, Option<CommitId>) {        let (prev_id, next_id) = resolve_neighbor_ids(&self.commit_lookup, commit_id);        (prev_id.cloned(), next_id.cloned())    }     fn resolve_commit_id_prefix(&self, prefix: &HexPrefix) -> PrefixResolution<CommitId> {        let min_bytes_prefix = CommitId::from_bytes(prefix.min_prefix_bytes());        resolve_id_prefix(&self.commit_lookup, prefix, &min_bytes_prefix).map(|(id, _)| id.clone())    }     fn resolve_neighbor_change_ids(        &self,        change_id: &ChangeId,    ) -> (Option<ChangeId>, Option<ChangeId>) {        let (prev_id, next_id) = resolve_neighbor_ids(&self.change_lookup, change_id);        (prev_id.cloned(), next_id.cloned())    }     fn resolve_change_id_prefix(        &self,        prefix: &HexPrefix,    ) -> PrefixResolution<(ChangeId, SmallLocalCommitPositionsVec)> {        let min_bytes_prefix = ChangeId::from_bytes(prefix.min_prefix_bytes());        resolve_id_prefix(&self.change_lookup, prefix, &min_bytes_prefix)            .map(|(id, positions)| (id.clone(), positions.clone()))    }     fn generation_number(&self, local_pos: LocalCommitPosition) -> u32 {        self.graph[local_pos.0 as usize].generation_number    }     fn commit_id(&self, local_pos: LocalCommitPosition) -> CommitId {        self.graph[local_pos.0 as usize].commit_id.clone()    }     fn change_id(&self, local_pos: LocalCommitPosition) -> ChangeId {        self.graph[local_pos.0 as usize].change_id.clone()    }     fn num_parents(&self, local_pos: LocalCommitPosition) -> u32 {        self.graph[local_pos.0 as usize]            .parent_positions            .len()            .try_into()            .unwrap()    }     fn parent_positions(&self, local_pos: LocalCommitPosition) -> SmallGlobalCommitPositionsVec {        self.graph[local_pos.0 as usize].parent_positions.clone()    }} /// In-memory mutable records for the on-disk commit index backend.pub struct DefaultMutableIndex(CompositeIndex); impl DefaultMutableIndex {    pub(super) fn full(lengths: FieldLengths) -> Self {        let commits = Box::new(MutableCommitIndexSegment::full(lengths));        // Changed-path index isn't enabled by default.        let mut changed_paths = CompositeChangedPathIndex::null();        changed_paths.make_mutable();        Self(CompositeIndex::from_mutable(commits, changed_paths))    }     pub(super) fn incremental(parent_index: &DefaultReadonlyIndex) -> Self {        let commits = Box::new(MutableCommitIndexSegment::incremental(            parent_index.readonly_commits().clone(),        ));        let mut changed_paths = parent_index.changed_paths().clone();        changed_paths.make_mutable();        Self(CompositeIndex::from_mutable(commits, changed_paths))    }     pub(super) fn into_segment(        self,    ) -> (Box<MutableCommitIndexSegment>, CompositeChangedPathIndex) {        self.0.into_mutable().expect("must have mutable")    }     fn mutable_commits(&mut self) -> &mut MutableCommitIndexSegment {        self.0.mutable_commits().expect("must have mutable")    }     /// Returns the number of all indexed commits.    pub fn num_commits(&self) -> u32 {        self.0.commits().num_commits()    }     #[tracing::instrument(skip(self))]    pub(super) async fn add_commit(&mut self, commit: &Commit) -> BackendResult<()> {        let new_commit_pos = GlobalCommitPosition(self.num_commits());        self.add_commit_data(            commit.id().clone(),            commit.change_id().clone(),            commit.parent_ids(),        );

Lines 1–500 of 657.