rust/rhg/src/commands/files.rs
author Arseniy Alekseyev <aalekseyev@janestreet.com>
Tue, 16 Nov 2021 11:53:58 +0000
changeset 48409 005ae1a343f8
parent 48342 10c32e1b892a
child 48453 9b0e1f64656f
permissions -rw-r--r--
rhg: add support for narrow clones and sparse checkouts This adds a minimal support that can be implemented without parsing the narrowspec. We can parse the narrowspec and add support for more operations later. The reason we need so few code changes is as follows: Most operations need no special treatment of sparse because some of them only read dirstate (`rhg files` without `-r`), which bakes in the filtering, some of them only read store (`rhg files -r`, `rhg cat`), and some of them read no data at all (`rhg root`, `rhg debugrequirements`). `status` is the command that might care about sparse, so we just disable rhg on it. For narrow clones, `rhg files` clearly needs the narrowspec to work correctly, so we fall back. `rhg cat` seems to work consistently with `hg cat` if the file exists. If the file is hidden by narrow spec, the error message is different and confusing, so that's something that we should improve in follow-up patches. Differential Revision: https://phab.mercurial-scm.org/D11764

use crate::error::CommandError;
use crate::ui::Ui;
use crate::ui::UiError;
use crate::utils::path_utils::relativize_paths;
use clap::Arg;
use hg::errors::HgError;
use hg::operations::list_rev_tracked_files;
use hg::operations::Dirstate;
use hg::repo::Repo;
use hg::utils::hg_path::HgPath;
use std::borrow::Cow;

pub const HELP_TEXT: &str = "
List tracked files.

Returns 0 on success.
";

pub fn args() -> clap::App<'static, 'static> {
    clap::SubCommand::with_name("files")
        .arg(
            Arg::with_name("rev")
                .help("search the repository as it is in REV")
                .short("-r")
                .long("--revision")
                .value_name("REV")
                .takes_value(true),
        )
        .about(HELP_TEXT)
}

pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
    let relative = invocation.config.get(b"ui", b"relative-paths");
    if relative.is_some() {
        return Err(CommandError::unsupported(
            "non-default ui.relative-paths",
        ));
    }

    let rev = invocation.subcommand_args.value_of("rev");

    let repo = invocation.repo?;

    // It seems better if this check is removed: this would correspond to
    // automatically enabling the extension if the repo requires it.
    // However we need this check to be in sync with vanilla hg so hg tests
    // pass.
    if repo.has_sparse()
        && invocation.config.get(b"extensions", b"sparse").is_none()
    {
        return Err(CommandError::unsupported(
            "repo is using sparse, but sparse extension is not enabled",
        ));
    }

    if let Some(rev) = rev {
        if repo.has_narrow() {
            return Err(CommandError::unsupported(
                "rhg files -r <rev> is not supported in narrow clones",
            ));
        }
        let files = list_rev_tracked_files(repo, rev).map_err(|e| (e, rev))?;
        display_files(invocation.ui, repo, files.iter())
    } else {
        // The dirstate always reflects the sparse narrowspec, so if
        // we only have sparse without narrow all is fine.
        // If we have narrow, then [hg files] needs to check if
        // the store narrowspec is in sync with the one of the dirstate,
        // so we can't support that without explicit code.
        if repo.has_narrow() {
            return Err(CommandError::unsupported(
                "rhg files is not supported in narrow clones",
            ));
        }
        let distate = Dirstate::new(repo)?;
        let files = distate.tracked_files()?;
        display_files(invocation.ui, repo, files.into_iter().map(Ok))
    }
}

fn display_files<'a>(
    ui: &Ui,
    repo: &Repo,
    files: impl IntoIterator<Item = Result<&'a HgPath, HgError>>,
) -> Result<(), CommandError> {
    let mut stdout = ui.stdout_buffer();
    let mut any = false;

    relativize_paths(repo, files, |path: Cow<[u8]>| -> Result<(), UiError> {
        any = true;
        stdout.write_all(path.as_ref())?;
        stdout.write_all(b"\n")
    })?;
    stdout.flush()?;
    if any {
        Ok(())
    } else {
        Err(CommandError::Unsuccessful)
    }
}