218 lines · 7.3 KB · Rust
RawDownload
// Copyright 2024 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. //! Code for working with copies and renames. use std::collections::HashMap;use std::pin::Pin;use std::task::Context;use std::task::Poll;use std::task::ready; use futures::Stream; use crate::backend::BackendResult;use crate::backend::CopyRecord;use crate::merge::Diff;use crate::merge::MergedTreeValue;use crate::merged_tree::MergedTree;use crate::merged_tree::TreeDiffStream;use crate::repo_path::RepoPath;use crate::repo_path::RepoPathBuf; /// A collection of CopyRecords.#[derive(Default, Debug)]pub struct CopyRecords {    records: Vec<CopyRecord>,    // Maps from `source` or `target` to the index of the entry in `records`.    // Conflicts are excluded by keeping an out of range value.    sources: HashMap<RepoPathBuf, usize>,    targets: HashMap<RepoPathBuf, usize>,} impl CopyRecords {    /// Adds information about `CopyRecord`s to `self`. A target with multiple    /// conflicts is discarded and treated as not having an origin.    pub fn add_records(        &mut self,        copy_records: impl IntoIterator<Item = BackendResult<CopyRecord>>,    ) -> BackendResult<()> {        for record in copy_records {            let r = record?;            self.sources                .entry(r.source.clone())                // TODO: handle conflicts instead of ignoring both sides.                .and_modify(|value| *value = usize::MAX)                .or_insert(self.records.len());            self.targets                .entry(r.target.clone())                // TODO: handle conflicts instead of ignoring both sides.                .and_modify(|value| *value = usize::MAX)                .or_insert(self.records.len());            self.records.push(r);        }        Ok(())    }     /// Returns true if there are copy records associated with a source path.    pub fn has_source(&self, source: &RepoPath) -> bool {        self.sources.contains_key(source)    }     /// Gets any copy record associated with a source path.    pub fn for_source(&self, source: &RepoPath) -> Option<&CopyRecord> {        self.sources.get(source).and_then(|&i| self.records.get(i))    }     /// Returns true if there are copy records associated with a target path.    pub fn has_target(&self, target: &RepoPath) -> bool {        self.targets.contains_key(target)    }     /// Gets any copy record associated with a target path.    pub fn for_target(&self, target: &RepoPath) -> Option<&CopyRecord> {        self.targets.get(target).and_then(|&i| self.records.get(i))    }     /// Gets all copy records.    pub fn iter(&self) -> impl Iterator<Item = &CopyRecord> {        self.records.iter()    }} /// Whether or not the source path was deleted.#[derive(Clone, Copy, Debug, Eq, PartialEq)]pub enum CopyOperation {    /// The source path was not deleted.    Copy,    /// The source path was renamed to the destination.    Rename,} /// A `TreeDiffEntry` with copy information.pub struct CopiesTreeDiffEntry {    /// The path.    pub path: CopiesTreeDiffEntryPath,    /// The resolved tree values if available.    pub values: BackendResult<Diff<MergedTreeValue>>,} /// Path and copy information of `CopiesTreeDiffEntry`.#[derive(Clone, Debug, Eq, PartialEq)]pub struct CopiesTreeDiffEntryPath {    /// The source path and copy information if this is a copy or rename.    pub source: Option<(RepoPathBuf, CopyOperation)>,    /// The target path.    pub target: RepoPathBuf,} impl CopiesTreeDiffEntryPath {    /// The source path.    pub fn source(&self) -> &RepoPath {        self.source.as_ref().map_or(&self.target, |(path, _)| path)    }     /// The target path.    pub fn target(&self) -> &RepoPath {        &self.target    }     /// Whether this entry was copied or renamed from the source. Returns `None`    /// if the path is unchanged.    pub fn copy_operation(&self) -> Option<CopyOperation> {        self.source.as_ref().map(|(_, op)| *op)    }} /// Wraps a `TreeDiffStream`, adding support for copies and renames.pub struct CopiesTreeDiffStream<'a> {    inner: TreeDiffStream<'a>,    source_tree: MergedTree,    target_tree: MergedTree,    copy_records: &'a CopyRecords,} impl<'a> CopiesTreeDiffStream<'a> {    /// Create a new diff stream with copy information.    pub fn new(        inner: TreeDiffStream<'a>,        source_tree: MergedTree,        target_tree: MergedTree,        copy_records: &'a CopyRecords,    ) -> Self {        Self {            inner,            source_tree,            target_tree,            copy_records,        }    }     fn resolve_copy_source(        &self,        source: &RepoPath,        values: BackendResult<Diff<MergedTreeValue>>,    ) -> BackendResult<(CopyOperation, Diff<MergedTreeValue>)> {        let target_value = values?.after;        let source_value = self.source_tree.path_value(source)?;        // If the source path is deleted in the target tree, it's a rename.        let source_value_at_target = self.target_tree.path_value(source)?;        let copy_op = if source_value_at_target.is_absent() || source_value_at_target.is_tree() {            CopyOperation::Rename        } else {            CopyOperation::Copy        };        Ok((copy_op, Diff::new(source_value, target_value)))    }} impl Stream for CopiesTreeDiffStream<'_> {    type Item = CopiesTreeDiffEntry;     fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {        while let Some(diff_entry) = ready!(self.inner.as_mut().poll_next(cx)) {            let Some(CopyRecord { source, .. }) = self.copy_records.for_target(&diff_entry.path)            else {                let target_deleted =                    matches!(&diff_entry.values, Ok(diff) if diff.after.is_absent());                if target_deleted && self.copy_records.has_source(&diff_entry.path) {                    // Skip the "delete" entry when there is a rename.                    continue;                }                return Poll::Ready(Some(CopiesTreeDiffEntry {                    path: CopiesTreeDiffEntryPath {                        source: None,                        target: diff_entry.path,                    },                    values: diff_entry.values,                }));            };             let (copy_op, values) = match self.resolve_copy_source(source, diff_entry.values) {                Ok((copy_op, values)) => (copy_op, Ok(values)),                // Fall back to "copy" (= path still exists) if unknown.                Err(err) => (CopyOperation::Copy, Err(err)),            };            return Poll::Ready(Some(CopiesTreeDiffEntry {                path: CopiesTreeDiffEntryPath {                    source: Some((source.clone(), copy_op)),                    target: diff_entry.path,                },                values,            }));        }         Poll::Ready(None)    }}