view rust/hg-core/src/operations/cat.rs @ 46154:ecbb2fc9418c

copies-rust: rename Oracle.is_ancestor to Oracle.is_overwrite The core information that we want here is about "does information from revision X overwrite information in Y". To do so, we check is X is an ancestors of Y, but this is an implementation details, they could be other ways. We update the naming to clarify this (and align more with wording used in upcoming changesets. For people curious about other ways: for example we could record the overwrite graph as it happens and reuse that to check if X overwrite Y, without having to do potential expensive `is_ancestor` call on the revision graph. Differential Revision: https://phab.mercurial-scm.org/D9496
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Wed, 02 Dec 2020 16:11:35 +0100
parents dca9cb99971c
children 8a4914397d02
line wrap: on
line source

// list_tracked_files.rs
//
// Copyright 2020 Antoine Cezar <antoine.cezar@octobus.net>
//
// This software may be used and distributed according to the terms of the
// GNU General Public License version 2 or any later version.

use std::convert::From;
use std::path::{Path, PathBuf};

use crate::revlog::changelog::Changelog;
use crate::revlog::manifest::Manifest;
use crate::revlog::path_encode::path_encode;
use crate::revlog::revlog::Revlog;
use crate::revlog::revlog::RevlogError;
use crate::revlog::Node;
use crate::revlog::NodePrefix;
use crate::revlog::Revision;
use crate::utils::files::get_path_from_bytes;
use crate::utils::hg_path::{HgPath, HgPathBuf};

const METADATA_DELIMITER: [u8; 2] = [b'\x01', b'\n'];

/// Kind of error encountered by `CatRev`
#[derive(Debug)]
pub enum CatRevErrorKind {
    /// Error when reading a `revlog` file.
    IoError(std::io::Error),
    /// The revision has not been found.
    InvalidRevision,
    /// Found more than one revision whose ID match the requested prefix
    AmbiguousPrefix,
    /// A `revlog` file is corrupted.
    CorruptedRevlog,
    /// The `revlog` format version is not supported.
    UnsuportedRevlogVersion(u16),
    /// The `revlog` data format is not supported.
    UnknowRevlogDataFormat(u8),
}

/// A `CatRev` error
#[derive(Debug)]
pub struct CatRevError {
    /// Kind of error encountered by `CatRev`
    pub kind: CatRevErrorKind,
}

impl From<CatRevErrorKind> for CatRevError {
    fn from(kind: CatRevErrorKind) -> Self {
        CatRevError { kind }
    }
}

impl From<RevlogError> for CatRevError {
    fn from(err: RevlogError) -> Self {
        match err {
            RevlogError::IoError(err) => CatRevErrorKind::IoError(err),
            RevlogError::UnsuportedVersion(version) => {
                CatRevErrorKind::UnsuportedRevlogVersion(version)
            }
            RevlogError::InvalidRevision => CatRevErrorKind::InvalidRevision,
            RevlogError::AmbiguousPrefix => CatRevErrorKind::AmbiguousPrefix,
            RevlogError::Corrupted => CatRevErrorKind::CorruptedRevlog,
            RevlogError::UnknowDataFormat(format) => {
                CatRevErrorKind::UnknowRevlogDataFormat(format)
            }
        }
        .into()
    }
}

/// List files under Mercurial control at a given revision.
///
/// * `root`: Repository root
/// * `rev`: The revision to cat the files from.
/// * `files`: The files to output.
pub fn cat(
    root: &Path,
    rev: &str,
    files: &[HgPathBuf],
) -> Result<Vec<u8>, CatRevError> {
    let changelog = Changelog::open(&root)?;
    let manifest = Manifest::open(&root)?;

    let changelog_entry = match rev.parse::<Revision>() {
        Ok(rev) => changelog.get_rev(rev)?,
        _ => {
            let changelog_node = NodePrefix::from_hex(&rev)
                .map_err(|_| CatRevErrorKind::InvalidRevision)?;
            changelog.get_node(changelog_node.borrow())?
        }
    };
    let manifest_node = Node::from_hex(&changelog_entry.manifest_node()?)
        .map_err(|_| CatRevErrorKind::CorruptedRevlog)?;

    let manifest_entry = manifest.get_node((&manifest_node).into())?;
    let mut bytes = vec![];

    for (manifest_file, node_bytes) in manifest_entry.files_with_nodes() {
        for cat_file in files.iter() {
            if cat_file.as_bytes() == manifest_file.as_bytes() {
                let index_path = store_path(root, manifest_file, b".i");
                let data_path = store_path(root, manifest_file, b".d");

                let file_log = Revlog::open(&index_path, Some(&data_path))?;
                let file_node = Node::from_hex(node_bytes)
                    .map_err(|_| CatRevErrorKind::CorruptedRevlog)?;
                let file_rev = file_log.get_node_rev((&file_node).into())?;
                let data = file_log.get_rev_data(file_rev)?;
                if data.starts_with(&METADATA_DELIMITER) {
                    let end_delimiter_position = data
                        [METADATA_DELIMITER.len()..]
                        .windows(METADATA_DELIMITER.len())
                        .position(|bytes| bytes == METADATA_DELIMITER);
                    if let Some(position) = end_delimiter_position {
                        let offset = METADATA_DELIMITER.len() * 2;
                        bytes.extend(data[position + offset..].iter());
                    }
                } else {
                    bytes.extend(data);
                }
            }
        }
    }

    Ok(bytes)
}

fn store_path(root: &Path, hg_path: &HgPath, suffix: &[u8]) -> PathBuf {
    let encoded_bytes =
        path_encode(&[b"data/", hg_path.as_bytes(), suffix].concat());
    [
        root,
        &Path::new(".hg/store/"),
        get_path_from_bytes(&encoded_bytes),
    ]
    .iter()
    .collect()
}