cli: add ui.color = "debug"

When using `ui.color = "debug"`, changes in the output style
additionally include delimiters << and >>, as well as all active labels
at this point separated by ::. The output is otherwise unformatted and
the delimiters and labels inherit the style of the content they apply
to.
This commit is contained in:
tinger 2024-05-06 18:23:36 +02:00 committed by tingerrr
parent de4ea5bc5a
commit d0a29a831d
12 changed files with 194 additions and 39 deletions

View file

@ -27,6 +27,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Conflict markers now include an explanation of what each part of the conflict
represents.
* `ui.color = "debug"` prints active labels alongside the regular colored output.
### Fixed bugs
## [0.17.1] - 2024-05-07

View file

@ -2411,7 +2411,7 @@ pub struct GlobalArgs {
#[derive(clap::Args, Clone, Debug)]
pub struct EarlyArgs {
/// When to colorize output (always, never, auto)
/// When to colorize output (always, never, debug, auto)
#[arg(long, value_name = "WHEN", global = true)]
pub color: Option<ColorChoice>,
/// Silence non-primary command output

View file

@ -77,6 +77,7 @@
"enum": [
"always",
"never",
"debug",
"auto"
],
"default": "auto"

View file

@ -132,18 +132,19 @@ pub struct FormatterFactory {
enum FormatterFactoryKind {
PlainText,
Sanitized,
Color { rules: Arc<Rules> },
Color { rules: Arc<Rules>, debug: bool },
}
impl FormatterFactory {
pub fn prepare(
config: &config::Config,
debug: bool,
color: bool,
sanitized: bool,
) -> Result<Self, config::ConfigError> {
let kind = if color {
let rules = Arc::new(rules_from_config(config)?);
FormatterFactoryKind::Color { rules }
FormatterFactoryKind::Color { rules, debug }
} else if sanitized {
FormatterFactoryKind::Sanitized
} else {
@ -159,8 +160,8 @@ impl FormatterFactory {
match &self.kind {
FormatterFactoryKind::PlainText => Box::new(PlainTextFormatter::new(output)),
FormatterFactoryKind::Sanitized => Box::new(SanitizingFormatter::new(output)),
FormatterFactoryKind::Color { rules } => {
Box::new(ColorFormatter::new(output, rules.clone()))
FormatterFactoryKind::Color { rules, debug } => {
Box::new(ColorFormatter::new(output, rules.clone(), *debug))
}
}
}
@ -255,6 +256,7 @@ impl Style {
#[derive(Clone, Debug)]
pub struct ColorFormatter<W: Write> {
output: W,
debug: bool,
rules: Arc<Rules>,
/// The stack of currently applied labels. These determine the desired
/// style.
@ -265,19 +267,24 @@ pub struct ColorFormatter<W: Write> {
}
impl<W: Write> ColorFormatter<W> {
pub fn new(output: W, rules: Arc<Rules>) -> ColorFormatter<W> {
pub fn new(output: W, rules: Arc<Rules>, debug: bool) -> ColorFormatter<W> {
ColorFormatter {
output,
rules,
debug,
labels: vec![],
cached_styles: HashMap::new(),
current_style: Style::default(),
}
}
pub fn for_config(output: W, config: &config::Config) -> Result<Self, config::ConfigError> {
pub fn for_config(
output: W,
config: &config::Config,
debug: bool,
) -> Result<Self, config::ConfigError> {
let rules = rules_from_config(config)?;
Ok(Self::new(output, Arc::new(rules)))
Ok(Self::new(output, Arc::new(rules), debug))
}
fn requested_style(&mut self) -> Style {
@ -473,19 +480,43 @@ impl<W: Write> Write for ColorFormatter<W> {
* Some tools (like `less -R`) get confused and lose coloring of lines
after a newline.
*/
for line in data.split_inclusive(|b| *b == b'\n') {
if line.ends_with(b"\n") {
self.write_new_style()?;
write_sanitized(&mut self.output, &line[..line.len() - 1])?;
write_line_exclusive(
&mut self.output,
&line[..line.len() - 1],
&self.labels,
self.debug,
)?;
let labels = mem::take(&mut self.labels);
self.write_new_style()?;
self.output.write_all(b"\n")?;
self.labels = labels;
} else {
self.write_new_style()?;
write_sanitized(&mut self.output, line)?;
write_line_exclusive(&mut self.output, line, &self.labels, self.debug)?;
}
}
fn write_line_exclusive<W: Write>(
output: &mut W,
buf: &[u8],
labels: &[String],
debug: bool,
) -> io::Result<()> {
if debug {
write!(output, "<<{}::", labels.join(" "))?;
write_sanitized(output, buf)?;
write!(output, ">>")?;
} else {
write_sanitized(output, buf)?;
}
Ok(())
}
Ok(data.len())
}
@ -699,7 +730,8 @@ mod tests {
}
let mut output: Vec<u8> = vec![];
let mut formatter =
ColorFormatter::for_config(&mut output, &config_builder.build().unwrap()).unwrap();
ColorFormatter::for_config(&mut output, &config_builder.build().unwrap(), false)
.unwrap();
for color in colors {
formatter.push_label(&color.replace(' ', "-")).unwrap();
write!(formatter, " {color} ").unwrap();
@ -744,7 +776,8 @@ mod tests {
}
let mut output: Vec<u8> = vec![];
let mut formatter =
ColorFormatter::for_config(&mut output, &config_builder.build().unwrap()).unwrap();
ColorFormatter::for_config(&mut output, &config_builder.build().unwrap(), false)
.unwrap();
for [label, _] in labels_and_colors {
formatter.push_label(&label.replace(' ', "-")).unwrap();
write!(formatter, " {label} ").unwrap();
@ -769,7 +802,7 @@ mod tests {
"#,
);
let mut output: Vec<u8> = vec![];
let mut formatter = ColorFormatter::for_config(&mut output, &config).unwrap();
let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
write!(formatter, " before ").unwrap();
formatter.push_label("inside").unwrap();
write!(formatter, " inside ").unwrap();
@ -793,7 +826,7 @@ mod tests {
"#,
);
let mut output: Vec<u8> = vec![];
let mut formatter = ColorFormatter::for_config(&mut output, &config).unwrap();
let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
formatter.push_label("red_fg").unwrap();
write!(formatter, " fg only ").unwrap();
formatter.pop_label().unwrap();
@ -841,7 +874,7 @@ mod tests {
"#,
);
let mut output: Vec<u8> = vec![];
let mut formatter = ColorFormatter::for_config(&mut output, &config).unwrap();
let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
formatter.push_label("not_bold").unwrap();
write!(formatter, " not bold ").unwrap();
formatter.push_label("bold_font").unwrap();
@ -863,7 +896,7 @@ mod tests {
"#,
);
let mut output: Vec<u8> = vec![];
let mut formatter = ColorFormatter::for_config(&mut output, &config).unwrap();
let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
write!(formatter, "before").unwrap();
formatter.push_label("red").unwrap();
write!(formatter, "first").unwrap();
@ -885,7 +918,7 @@ mod tests {
"#,
);
let mut output: Vec<u8> = vec![];
let mut formatter = ColorFormatter::for_config(&mut output, &config).unwrap();
let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
formatter.push_label("red").unwrap();
write!(formatter, "\x1b[1mnot actually bold\x1b[0m").unwrap();
formatter.pop_label().unwrap();
@ -906,7 +939,7 @@ mod tests {
"#,
);
let mut output: Vec<u8> = vec![];
let mut formatter = ColorFormatter::for_config(&mut output, &config).unwrap();
let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
write!(formatter, " before outer ").unwrap();
formatter.push_label("outer").unwrap();
write!(formatter, " before inner ").unwrap();
@ -930,7 +963,7 @@ mod tests {
"#,
);
let mut output: Vec<u8> = vec![];
let mut formatter = ColorFormatter::for_config(&mut output, &config).unwrap();
let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
formatter.push_label("outer").unwrap();
write!(formatter, " not colored ").unwrap();
formatter.push_label("inner").unwrap();
@ -953,7 +986,7 @@ mod tests {
"#,
);
let mut output: Vec<u8> = vec![];
let err = ColorFormatter::for_config(&mut output, &config)
let err = ColorFormatter::for_config(&mut output, &config, false)
.unwrap_err()
.to_string();
insta::assert_snapshot!(err,
@ -970,7 +1003,7 @@ mod tests {
"##,
);
let mut output: Vec<u8> = vec![];
let err = ColorFormatter::for_config(&mut output, &config)
let err = ColorFormatter::for_config(&mut output, &config, false)
.unwrap_err()
.to_string();
insta::assert_snapshot!(err,
@ -989,7 +1022,7 @@ mod tests {
"#,
);
let mut output: Vec<u8> = vec![];
let mut formatter = ColorFormatter::for_config(&mut output, &config).unwrap();
let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
formatter.push_label("outer").unwrap();
write!(formatter, "Blue on yellow, ").unwrap();
formatter.push_label("default_fg").unwrap();
@ -1018,7 +1051,7 @@ mod tests {
"#,
);
let mut output: Vec<u8> = vec![];
let mut formatter = ColorFormatter::for_config(&mut output, &config).unwrap();
let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
formatter.push_label("outer1").unwrap();
formatter.push_label("inner2").unwrap();
write!(formatter, " hello ").unwrap();
@ -1038,7 +1071,7 @@ mod tests {
"#,
);
let mut output: Vec<u8> = vec![];
let mut formatter = ColorFormatter::for_config(&mut output, &config).unwrap();
let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
formatter.push_label("outer").unwrap();
formatter.push_label("inner").unwrap();
write!(formatter, " hello ").unwrap();
@ -1060,7 +1093,7 @@ mod tests {
"#,
);
let mut output: Vec<u8> = vec![];
let mut formatter = ColorFormatter::for_config(&mut output, &config).unwrap();
let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
formatter.push_label("a").unwrap();
write!(formatter, " a1 ").unwrap();
formatter.push_label("b").unwrap();
@ -1087,7 +1120,7 @@ mod tests {
"#,
);
let mut output: Vec<u8> = vec![];
let mut formatter = ColorFormatter::for_config(&mut output, &config).unwrap();
let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
formatter.push_label("outer").unwrap();
formatter.push_label("inner").unwrap();
write!(formatter, " inside ").unwrap();
@ -1095,6 +1128,26 @@ mod tests {
insta::assert_snapshot!(String::from_utf8(output).unwrap(), @" inside ");
}
#[test]
fn test_color_formatter_debug() {
// Behaves like the color formatter, but surrounds each write with <<...>>,
// adding the active labels before the actual content separated by a ::.
let config = config_from_string(
r#"
colors.outer = "green"
"#,
);
let mut output: Vec<u8> = vec![];
let mut formatter = ColorFormatter::for_config(&mut output, &config, true).unwrap();
formatter.push_label("outer").unwrap();
formatter.push_label("inner").unwrap();
write!(formatter, " inside ").unwrap();
formatter.pop_label().unwrap();
formatter.pop_label().unwrap();
drop(formatter);
insta::assert_snapshot!(String::from_utf8(output).unwrap(), @"<<outer inner:: inside >>");
}
#[test]
fn test_heading_labeled_writer() {
let config = config_from_string(
@ -1105,7 +1158,7 @@ mod tests {
);
let mut output: Vec<u8> = vec![];
let mut formatter: Box<dyn Formatter> =
Box::new(ColorFormatter::for_config(&mut output, &config).unwrap());
Box::new(ColorFormatter::for_config(&mut output, &config, false).unwrap());
HeadingLabeledWriter::new(formatter.as_mut(), "inner", "Should be noop: ");
let mut writer = HeadingLabeledWriter::new(formatter.as_mut(), "inner", "Heading: ");
write!(writer, "Message").unwrap();
@ -1146,7 +1199,7 @@ mod tests {
// Replayed output should be labeled.
let config = config_from_string(r#" colors.inner = "red" "#);
let mut output: Vec<u8> = vec![];
let mut formatter = ColorFormatter::for_config(&mut output, &config).unwrap();
let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
recorder.replay(&mut formatter).unwrap();
drop(formatter);
insta::assert_snapshot!(
@ -1155,7 +1208,7 @@ mod tests {
// Replayed output should be split at push/pop_label() call.
let mut output: Vec<u8> = vec![];
let mut formatter = ColorFormatter::for_config(&mut output, &config).unwrap();
let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
recorder
.replay_with(&mut formatter, |formatter, range| {
let data = &recorder.data()[range];

View file

@ -1292,7 +1292,8 @@ mod tests {
fn render_ok(&self, template: &str) -> String {
let template = self.parse(template).unwrap();
let mut output = Vec::new();
let mut formatter = ColorFormatter::new(&mut output, self.color_rules.clone().into());
let mut formatter =
ColorFormatter::new(&mut output, self.color_rules.clone().into(), false);
template.format(&(), &mut formatter).unwrap();
drop(formatter);
String::from_utf8(output).unwrap()

View file

@ -275,7 +275,7 @@ mod tests {
.build()
.unwrap();
let mut output = Vec::new();
let mut formatter = ColorFormatter::for_config(&mut output, &config).unwrap();
let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap();
write(&mut formatter).unwrap();
drop(formatter);
String::from_utf8(output).unwrap()

View file

@ -179,6 +179,7 @@ fn progress_indicator_setting(config: &config::Config) -> bool {
pub enum ColorChoice {
Always,
Never,
Debug,
#[default]
Auto,
}
@ -190,6 +191,7 @@ impl FromStr for ColorChoice {
match s {
"always" => Ok(ColorChoice::Always),
"never" => Ok(ColorChoice::Never),
"debug" => Ok(ColorChoice::Debug),
"auto" => Ok(ColorChoice::Auto),
_ => Err("must be one of always, never, or auto"),
}
@ -201,6 +203,7 @@ impl fmt::Display for ColorChoice {
let s = match self {
ColorChoice::Always => "always",
ColorChoice::Never => "never",
ColorChoice::Debug => "debug",
ColorChoice::Auto => "auto",
};
write!(f, "{s}")
@ -215,10 +218,15 @@ fn color_setting(config: &config::Config) -> ColorChoice {
.unwrap_or_default()
}
fn debug_color(choice: ColorChoice) -> bool {
matches!(choice, ColorChoice::Debug)
}
fn use_color(choice: ColorChoice) -> bool {
match choice {
ColorChoice::Always => true,
ColorChoice::Never => false,
ColorChoice::Debug => true,
ColorChoice::Auto => io::stdout().is_terminal(),
}
}
@ -249,12 +257,14 @@ fn pager_setting(config: &config::Config) -> Result<CommandNameAndArgs, CommandE
impl Ui {
pub fn with_config(config: &config::Config) -> Result<Ui, CommandError> {
let color = use_color(color_setting(config));
let color = color_setting(config);
let debug = debug_color(color);
let color = use_color(color);
let quiet = be_quiet(config);
// Sanitize ANSI escape codes if we're printing to a terminal. Doesn't affect
// ANSI escape codes that originate from the formatter itself.
let sanitize = io::stdout().is_terminal();
let formatter_factory = FormatterFactory::prepare(config, color, sanitize)?;
let formatter_factory = FormatterFactory::prepare(config, debug, color, sanitize)?;
let progress_indicator = progress_indicator_setting(config);
Ok(Ui {
color,
@ -268,13 +278,15 @@ impl Ui {
}
pub fn reset(&mut self, config: &config::Config) -> Result<(), CommandError> {
self.color = use_color(color_setting(config));
let color = color_setting(config);
let debug = debug_color(color);
self.color = use_color(color);
self.quiet = be_quiet(config);
self.paginate = pagination_setting(config)?;
self.pager_cmd = pager_setting(config)?;
self.progress_indicator = progress_indicator_setting(config);
let sanitize = io::stdout().is_terminal();
self.formatter_factory = FormatterFactory::prepare(config, self.color, sanitize)?;
self.formatter_factory = FormatterFactory::prepare(config, debug, self.color, sanitize)?;
Ok(())
}

View file

@ -164,7 +164,7 @@ To get started, see the tutorial at https://github.com/martinvonz/jj/blob/main/d
Possible values: `true`, `false`
* `--color <WHEN>` — When to colorize output (always, never, auto)
* `--color <WHEN>` — When to colorize output (always, never, debug, auto)
* `--quiet` — Silence non-primary command output
Possible values: `true`, `false`

View file

@ -360,6 +360,75 @@ fn test_log_builtin_templates_colored() {
"###);
}
#[test]
fn test_log_builtin_templates_colored_debug() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
let repo_path = test_env.env_root().join("repo");
let render =
|template| test_env.jj_cmd_success(&repo_path, &["--color=debug", "log", "-T", template]);
test_env.jj_cmd_ok(
&repo_path,
&[
"--config-toml=user.email=''",
"--config-toml=user.name=''",
"new",
],
);
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "my-branch"]);
insta::assert_snapshot!(render(r#"builtin_log_oneline"#), @r###"
<<node::@>> <<log working_copy change_id shortest prefix::r>><<log working_copy change_id shortest rest::lvkpnrz>><<log working_copy:: >><<log working_copy email placeholder::(no email set)>><<log working_copy:: >><<log working_copy committer timestamp local format::2001-02-03 08:05:08>><<log working_copy:: >><<log working_copy branches name::my-branch>><<log working_copy:: >><<log working_copy commit_id shortest prefix::d>><<log working_copy commit_id shortest rest::c315397>><<log working_copy:: >><<log working_copy empty::(empty)>><<log working_copy:: >><<log working_copy empty description placeholder::(no description set)>><<log working_copy::>>
<<node::>> <<log change_id shortest prefix::q>><<log change_id shortest rest::pvuntsm>><<log:: >><<log author username::test.user>><<log:: >><<log committer timestamp local format::2001-02-03 08:05:07>><<log:: >><<log commit_id shortest prefix::2>><<log commit_id shortest rest::30dd059>><<log:: >><<log empty::(empty)>><<log:: >><<log empty description placeholder::(no description set)>><<log::>>
<<node::>> <<log change_id shortest prefix::z>><<log change_id shortest rest::zzzzzzz>><<log:: >><<log root::root()>><<log:: >><<log commit_id shortest prefix::0>><<log commit_id shortest rest::0000000>><<log::>>
"###);
insta::assert_snapshot!(render(r#"builtin_log_compact"#), @r###"
<<node::@>> <<log working_copy change_id shortest prefix::r>><<log working_copy change_id shortest rest::lvkpnrz>><<log working_copy:: >><<log working_copy email placeholder::(no email set)>><<log working_copy:: >><<log working_copy committer timestamp local format::2001-02-03 08:05:08>><<log working_copy:: >><<log working_copy branches name::my-branch>><<log working_copy:: >><<log working_copy commit_id shortest prefix::d>><<log working_copy commit_id shortest rest::c315397>><<log working_copy::>>
<<log working_copy empty::(empty)>><<log working_copy:: >><<log working_copy empty description placeholder::(no description set)>><<log working_copy::>>
<<node::>> <<log change_id shortest prefix::q>><<log change_id shortest rest::pvuntsm>><<log:: >><<log author email::test.user@example.com>><<log:: >><<log committer timestamp local format::2001-02-03 08:05:07>><<log:: >><<log commit_id shortest prefix::2>><<log commit_id shortest rest::30dd059>><<log::>>
<<log empty::(empty)>><<log:: >><<log empty description placeholder::(no description set)>><<log::>>
<<node::>> <<log change_id shortest prefix::z>><<log change_id shortest rest::zzzzzzz>><<log:: >><<log root::root()>><<log:: >><<log commit_id shortest prefix::0>><<log commit_id shortest rest::0000000>><<log::>>
"###);
insta::assert_snapshot!(render(r#"builtin_log_comfortable"#), @r###"
<<node::@>> <<log working_copy change_id shortest prefix::r>><<log working_copy change_id shortest rest::lvkpnrz>><<log working_copy:: >><<log working_copy email placeholder::(no email set)>><<log working_copy:: >><<log working_copy committer timestamp local format::2001-02-03 08:05:08>><<log working_copy:: >><<log working_copy branches name::my-branch>><<log working_copy:: >><<log working_copy commit_id shortest prefix::d>><<log working_copy commit_id shortest rest::c315397>><<log working_copy::>>
<<log working_copy empty::(empty)>><<log working_copy:: >><<log working_copy empty description placeholder::(no description set)>><<log working_copy::>>
<<log::>>
<<node::>> <<log change_id shortest prefix::q>><<log change_id shortest rest::pvuntsm>><<log:: >><<log author email::test.user@example.com>><<log:: >><<log committer timestamp local format::2001-02-03 08:05:07>><<log:: >><<log commit_id shortest prefix::2>><<log commit_id shortest rest::30dd059>><<log::>>
<<log empty::(empty)>><<log:: >><<log empty description placeholder::(no description set)>><<log::>>
<<log::>>
<<node::>> <<log change_id shortest prefix::z>><<log change_id shortest rest::zzzzzzz>><<log:: >><<log root::root()>><<log:: >><<log commit_id shortest prefix::0>><<log commit_id shortest rest::0000000>><<log::>>
<<log::>>
"###);
insta::assert_snapshot!(render(r#"builtin_log_detailed"#), @r###"
<<node::@>> <<log::Commit ID: >><<log commit_id::dc31539712c7294d1d712cec63cef4504b94ca74>><<log::>>
<<log::Change ID: >><<log change_id::rlvkpnrzqnoowoytxnquwvuryrwnrmlp>><<log::>>
<<log::Branches: >><<log local_branches name::my-branch>><<log::>>
<<log::Author: >><<log name placeholder::(no name set)>><<log:: <>><<log email placeholder::(no email set)>><<log::>>><<log:: (>><<log author timestamp local format::2001-02-03 08:05:08>><<log::)>><<log::>>
<<log::Committer: >><<log name placeholder::(no name set)>><<log:: <>><<log email placeholder::(no email set)>><<log::>>><<log:: (>><<log committer timestamp local format::2001-02-03 08:05:08>><<log::)>><<log::>>
<<log::>>
<<log empty description placeholder:: >><<log empty description placeholder::(no description set)>><<log::>>
<<log::>>
<<node::>> <<log::Commit ID: >><<log commit_id::230dd059e1b059aefc0da06a2e5a7dbf22362f22>><<log::>>
<<log::Change ID: >><<log change_id::qpvuntsmwlqtpsluzzsnyyzlmlwvmlnu>><<log::>>
<<log::Author: >><<log author name::Test User>><<log:: <>><<log author email::test.user@example.com>><<log::>>><<log:: (>><<log author timestamp local format::2001-02-03 08:05:07>><<log::)>><<log::>>
<<log::Committer: >><<log committer name::Test User>><<log:: <>><<log committer email::test.user@example.com>><<log::>>><<log:: (>><<log committer timestamp local format::2001-02-03 08:05:07>><<log::)>><<log::>>
<<log::>>
<<log empty description placeholder:: >><<log empty description placeholder::(no description set)>><<log::>>
<<log::>>
<<node::>> <<log::Commit ID: >><<log commit_id::0000000000000000000000000000000000000000>><<log::>>
<<log::Change ID: >><<log change_id::zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz>><<log::>>
<<log::Author: >><<log name placeholder::(no name set)>><<log:: <>><<log email placeholder::(no email set)>><<log::>>><<log:: (>><<log author timestamp local format::1970-01-01 11:00:00>><<log::)>><<log::>>
<<log::Committer: >><<log name placeholder::(no name set)>><<log:: <>><<log email placeholder::(no email set)>><<log::>>><<log:: (>><<log committer timestamp local format::1970-01-01 11:00:00>><<log::)>><<log::>>
<<log::>>
<<log empty description placeholder:: >><<log empty description placeholder::(no description set)>><<log::>>
<<log::>>
"###);
}
#[test]
fn test_log_obslog_divergence() {
let test_env = TestEnvironment::default();

View file

@ -486,6 +486,14 @@ fn test_color_ui_messages() {
 qpvuntsm 230dd059 (empty) (no description set)
Hint: Prefix the expression with 'all:' to allow any number of revisions (i.e. 'all:..').
"###);
// debugging colors
let (stdout, _stderr) = test_env.jj_cmd_ok(&repo_path, &["st", "--color", "debug"]);
insta::assert_snapshot!(stdout, @r###"
<<::The working copy is clean>>
<<::Working copy : >><<working_copy change_id shortest prefix::m>><<working_copy change_id shortest rest::zvwutvl>><<working_copy:: >><<working_copy commit_id shortest prefix::1>><<working_copy commit_id shortest rest::67f90e7>><<working_copy:: >><<working_copy empty::(empty)>><<working_copy:: >><<working_copy empty description placeholder::(no description set)>><<::>>
<<::Parent commit: >><<change_id shortest prefix::q>><<change_id shortest rest::pvuntsm>><<:: >><<commit_id shortest prefix::2>><<commit_id shortest rest::30dd059>><<:: >><<empty::(empty)>><<:: >><<empty description placeholder::(no description set)>><<::>>
"###);
}
#[test]
@ -595,7 +603,7 @@ fn test_help() {
--ignore-immutable Allow rewriting immutable commits
--at-operation <AT_OPERATION> Operation to load the repo at [default: @] [aliases: at-op]
--debug Enable debug logging
--color <WHEN> When to colorize output (always, never, auto)
--color <WHEN> When to colorize output (always, never, debug, auto)
--quiet Silence non-primary command output
--no-pager Disable the pager
--config-toml <TOML> Additional configuration options (can be repeated)

View file

@ -1161,6 +1161,14 @@ fn test_graph_template_color() {
third line
"###);
let stdout = test_env.jj_cmd_success(&repo_path, &["--color=debug", "log", "-T", template]);
insta::assert_snapshot!(stdout, @r###"
<<node::@>> <<log working_copy description::single line>>
<<node::>> <<log description::first line>>
<<log description::second line>>
<<log description::third line>>
<<node::>>
"###);
}
#[test]

View file

@ -80,8 +80,9 @@ Don't forget to change these to your own details!
### Colorizing output
Possible values are `always`, `never` and `auto` (default: `auto`).
`auto` will use color only when writing to a terminal.
Possible values are `always`, `never`, `debug` and `auto` (default: `auto`).
`auto` will use color only when writing to a terminal. `debug` will print the
active labels alongside the regular colorized output.
This setting overrides the `NO_COLOR` environment variable (if set).