cli: add a command for printing the contents of a file in a revision

I'm adding this mostly because it's useful for testing. That's also
the reason it supports displaying conflicts. I didn't call it `cat`
like `hg cat` because I haven't found `hg cat` on multiple files
useful.
This commit is contained in:
Martin von Zweigbergk 2022-04-06 11:00:27 -07:00 committed by Martin von Zweigbergk
parent 243836ebf3
commit 7a013a59ae
3 changed files with 110 additions and 0 deletions

View file

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### New features
* The new `jj print` command prints the contents of a file in a revision.
## [0.4.0] - 2022-04-02
### Breaking changes

View file

@ -936,6 +936,7 @@ enum Commands {
Checkout(CheckoutArgs),
Untrack(UntrackArgs),
Files(FilesArgs),
Print(PrintArgs),
Diff(DiffArgs),
Show(ShowArgs),
Status(StatusArgs),
@ -1012,6 +1013,15 @@ struct FilesArgs {
paths: Vec<String>,
}
/// Print contents of a file in a revision
#[derive(clap::Args, Clone, Debug)]
struct PrintArgs {
/// The revision to get the file contents from
#[clap(long, short, default_value = "@")]
revision: String,
path: String,
}
#[derive(clap::Args, Clone, Debug)]
#[clap(group(ArgGroup::new("format").args(&["summary", "git", "color-words"])))]
struct DiffFormatArgs {
@ -1899,6 +1909,34 @@ fn cmd_files(ui: &mut Ui, command: &CommandHelper, args: &FilesArgs) -> Result<(
Ok(())
}
fn cmd_print(ui: &mut Ui, command: &CommandHelper, args: &PrintArgs) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?;
let commit = workspace_command.resolve_single_rev(ui, &args.revision)?;
let path = ui.parse_file_path(workspace_command.workspace_root(), &args.path)?;
let repo = workspace_command.repo();
match commit.tree().path_value(&path) {
None => {
return Err(CommandError::UserError("No such path".to_string()));
}
Some(TreeValue::Normal { id, .. }) => {
let mut contents = repo.store().read_file(&path, &id)?;
std::io::copy(&mut contents, &mut ui.stdout_formatter().as_mut())?;
}
Some(TreeValue::Conflict(id)) => {
let conflict = repo.store().read_conflict(&path, &id)?;
let mut contents = vec![];
conflicts::materialize_conflict(repo.store(), &path, &conflict, &mut contents).unwrap();
ui.stdout_formatter().write_all(&contents)?;
}
_ => {
return Err(CommandError::UserError(
"Path exists but is not a file".to_string(),
));
}
}
Ok(())
}
fn show_color_words_diff_hunks(
left: &[u8],
right: &[u8],
@ -4556,6 +4594,7 @@ where
Commands::Checkout(sub_args) => cmd_checkout(&mut ui, &command_helper, sub_args),
Commands::Untrack(sub_args) => cmd_untrack(&mut ui, &command_helper, sub_args),
Commands::Files(sub_args) => cmd_files(&mut ui, &command_helper, sub_args),
Commands::Print(sub_args) => cmd_print(&mut ui, &command_helper, sub_args),
Commands::Diff(sub_args) => cmd_diff(&mut ui, &command_helper, sub_args),
Commands::Show(sub_args) => cmd_show(&mut ui, &command_helper, sub_args),
Commands::Status(sub_args) => cmd_status(&mut ui, &command_helper, sub_args),

View file

@ -0,0 +1,67 @@
// Copyright 2022 Google LLC
//
// 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 crate::common::TestEnvironment;
pub mod common;
#[test]
fn test_print() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_success(test_env.env_root(), &["init", "repo", "--git"]);
let repo_path = test_env.env_root().join("repo");
std::fs::write(repo_path.join("file1"), "a\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["new"]);
std::fs::write(repo_path.join("file1"), "b\n").unwrap();
std::fs::create_dir(repo_path.join("dir")).unwrap();
std::fs::write(repo_path.join("dir").join("file2"), "c\n").unwrap();
let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file1", "-r", "@-"]);
insta::assert_snapshot!(stdout, @"a
");
let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file1"]);
insta::assert_snapshot!(stdout, @"b
");
let subdir_file = if cfg!(unix) {
"dir/file2"
} else {
"dir\\file2"
};
let stdout = test_env.jj_cmd_success(&repo_path, &["print", subdir_file]);
insta::assert_snapshot!(stdout, @"c
");
let stdout = test_env.jj_cmd_failure(&repo_path, &["print", "non-existent"]);
insta::assert_snapshot!(stdout, @"Error: No such path
");
let stdout = test_env.jj_cmd_failure(&repo_path, &["print", "dir"]);
insta::assert_snapshot!(stdout, @"Error: Path exists but is not a file
");
// Can print a conflict
test_env.jj_cmd_success(&repo_path, &["new"]);
std::fs::write(repo_path.join("file1"), "c\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["rebase", "-d", "@--"]);
let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file1"]);
insta::assert_snapshot!(stdout, @r###"
<<<<<<<
-------
+++++++
-b
+a
+++++++
c
>>>>>>>
"###);
}