forked from mirrors/jj
Allow \0 escape for nulls
This allows safely getting e.g. multiple descriptions, and knowing where the boundaries are
This commit is contained in:
parent
fedd289a13
commit
2177dc0657
6 changed files with 74 additions and 9 deletions
|
@ -20,6 +20,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
* `jj op log` now supports `--no-graph`.
|
||||
|
||||
* Templates now support an additional escape: `\0`. This will output a literal
|
||||
null byte. This may be useful for e.g.
|
||||
`jj log -T 'description ++ "\0"' --no-graph` to output descriptions only, but
|
||||
be able to tell where the boundaries are
|
||||
|
||||
### Fixed bugs
|
||||
|
||||
## [0.9.0] - 2023-09-06
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
whitespace = _{ " " | "\t" | "\r" | "\n" | "\x0c" }
|
||||
|
||||
escape = @{ "\\" ~ ("t" | "r" | "n" | "\"" | "\\") }
|
||||
escape = @{ "\\" ~ ("t" | "r" | "n" | "0" | "\"" | "\\") }
|
||||
literal_char = @{ !("\"" | "\\") ~ ANY }
|
||||
raw_literal = @{ literal_char+ }
|
||||
literal = { "\"" ~ (raw_literal | escape)* ~ "\"" }
|
||||
|
|
|
@ -256,12 +256,13 @@ fn parse_string_literal(pair: Pair<Rule>) -> String {
|
|||
Rule::raw_literal => {
|
||||
result.push_str(part.as_str());
|
||||
}
|
||||
Rule::escape => match part.as_str().as_bytes()[1] as char {
|
||||
'"' => result.push('"'),
|
||||
'\\' => result.push('\\'),
|
||||
't' => result.push('\t'),
|
||||
'r' => result.push('\r'),
|
||||
'n' => result.push('\n'),
|
||||
Rule::escape => match &part.as_str()[1..] {
|
||||
"\"" => result.push('"'),
|
||||
"\\" => result.push('\\'),
|
||||
"t" => result.push('\t'),
|
||||
"r" => result.push('\r'),
|
||||
"n" => result.push('\n'),
|
||||
"0" => result.push('\0'),
|
||||
char => panic!("invalid escape: \\{char:?}"),
|
||||
},
|
||||
_ => panic!("unexpected part of string: {part:?}"),
|
||||
|
@ -963,8 +964,8 @@ mod tests {
|
|||
fn test_string_literal() {
|
||||
// "\<char>" escapes
|
||||
assert_eq!(
|
||||
parse_into_kind(r#" "\t\r\n\"\\" "#),
|
||||
Ok(ExpressionKind::String("\t\r\n\"\\".to_owned())),
|
||||
parse_into_kind(r#" "\t\r\n\"\\\0" "#),
|
||||
Ok(ExpressionKind::String("\t\r\n\"\\\0".to_owned())),
|
||||
);
|
||||
|
||||
// Invalid "\<char>" escape
|
||||
|
|
|
@ -314,6 +314,42 @@ fn test_log_with_or_without_diff() {
|
|||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_log_null_terminate_multiline_descriptions() {
|
||||
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");
|
||||
|
||||
test_env.jj_cmd_success(
|
||||
&repo_path,
|
||||
&["commit", "-m", "commit 1 line 1", "-m", "commit 1 line 2"],
|
||||
);
|
||||
test_env.jj_cmd_success(
|
||||
&repo_path,
|
||||
&["commit", "-m", "commit 2 line 1", "-m", "commit 2 line 2"],
|
||||
);
|
||||
test_env.jj_cmd_success(
|
||||
&repo_path,
|
||||
&["describe", "-m", "commit 3 line 1", "-m", "commit 3 line 2"],
|
||||
);
|
||||
|
||||
let stdout = test_env.jj_cmd_success(
|
||||
&repo_path,
|
||||
&[
|
||||
"log",
|
||||
"-r",
|
||||
"~root()",
|
||||
"-T",
|
||||
r#"description ++ "\0""#,
|
||||
"--no-graph",
|
||||
],
|
||||
);
|
||||
insta::assert_debug_snapshot!(
|
||||
stdout,
|
||||
@r###""commit 3 line 1\n\ncommit 3 line 2\n\0commit 2 line 1\n\ncommit 2 line 2\n\0commit 1 line 1\n\ncommit 1 line 2\n\0""###
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_log_shortest_accessors() {
|
||||
let test_env = TestEnvironment::default();
|
||||
|
|
|
@ -146,6 +146,27 @@ fn test_op_log_no_graph() {
|
|||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_op_log_no_graph_null_terminated() {
|
||||
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");
|
||||
test_env.jj_cmd_success(&repo_path, &["commit", "-m", "message1"]);
|
||||
test_env.jj_cmd_success(&repo_path, &["commit", "-m", "message2"]);
|
||||
|
||||
let stdout = test_env.jj_cmd_success(
|
||||
&repo_path,
|
||||
&[
|
||||
"op",
|
||||
"log",
|
||||
"--no-graph",
|
||||
"--template",
|
||||
r#"id.short(4) ++ "\0""#,
|
||||
],
|
||||
);
|
||||
insta::assert_debug_snapshot!(stdout, @r###""c8b0\07277\019b8\0f1c4\0""###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_op_log_template() {
|
||||
let test_env = TestEnvironment::default();
|
||||
|
|
|
@ -279,6 +279,8 @@ fn test_templater_list_method() {
|
|||
|
||||
insta::assert_snapshot!(render(r#""".lines().join("|")"#), @"");
|
||||
insta::assert_snapshot!(render(r#""a\nb\nc".lines().join("|")"#), @"a|b|c");
|
||||
// Null separator
|
||||
insta::assert_snapshot!(render(r#""a\nb\nc".lines().join("\0")"#), @"a\0b\0c");
|
||||
// Keyword as separator
|
||||
insta::assert_snapshot!(render(r#""a\nb\nc".lines().join(commit_id.short(2))"#), @"a00b00c");
|
||||
|
||||
|
|
Loading…
Reference in a new issue