mirror of
https://github.com/martinvonz/jj.git
synced 2024-12-25 05:29:39 +00:00
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:
parent
243836ebf3
commit
7a013a59ae
3 changed files with 110 additions and 0 deletions
|
@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### New features
|
||||||
|
|
||||||
|
* The new `jj print` command prints the contents of a file in a revision.
|
||||||
|
|
||||||
## [0.4.0] - 2022-04-02
|
## [0.4.0] - 2022-04-02
|
||||||
|
|
||||||
### Breaking changes
|
### Breaking changes
|
||||||
|
|
|
@ -936,6 +936,7 @@ enum Commands {
|
||||||
Checkout(CheckoutArgs),
|
Checkout(CheckoutArgs),
|
||||||
Untrack(UntrackArgs),
|
Untrack(UntrackArgs),
|
||||||
Files(FilesArgs),
|
Files(FilesArgs),
|
||||||
|
Print(PrintArgs),
|
||||||
Diff(DiffArgs),
|
Diff(DiffArgs),
|
||||||
Show(ShowArgs),
|
Show(ShowArgs),
|
||||||
Status(StatusArgs),
|
Status(StatusArgs),
|
||||||
|
@ -1012,6 +1013,15 @@ struct FilesArgs {
|
||||||
paths: Vec<String>,
|
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)]
|
#[derive(clap::Args, Clone, Debug)]
|
||||||
#[clap(group(ArgGroup::new("format").args(&["summary", "git", "color-words"])))]
|
#[clap(group(ArgGroup::new("format").args(&["summary", "git", "color-words"])))]
|
||||||
struct DiffFormatArgs {
|
struct DiffFormatArgs {
|
||||||
|
@ -1899,6 +1909,34 @@ fn cmd_files(ui: &mut Ui, command: &CommandHelper, args: &FilesArgs) -> Result<(
|
||||||
Ok(())
|
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(
|
fn show_color_words_diff_hunks(
|
||||||
left: &[u8],
|
left: &[u8],
|
||||||
right: &[u8],
|
right: &[u8],
|
||||||
|
@ -4556,6 +4594,7 @@ where
|
||||||
Commands::Checkout(sub_args) => cmd_checkout(&mut ui, &command_helper, sub_args),
|
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::Untrack(sub_args) => cmd_untrack(&mut ui, &command_helper, sub_args),
|
||||||
Commands::Files(sub_args) => cmd_files(&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::Diff(sub_args) => cmd_diff(&mut ui, &command_helper, sub_args),
|
||||||
Commands::Show(sub_args) => cmd_show(&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),
|
Commands::Status(sub_args) => cmd_status(&mut ui, &command_helper, sub_args),
|
||||||
|
|
67
tests/test_print_command.rs
Normal file
67
tests/test_print_command.rs
Normal 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
|
||||||
|
>>>>>>>
|
||||||
|
"###);
|
||||||
|
}
|
Loading…
Reference in a new issue