1use assert_cmd::cargo::CommandCargoExt;
2use predicates::boolean::PredicateBooleanExt;
3use predicates::{prelude::predicate, str::PredicateStrExt};
4use serial_test::serial;
6use std::process::Command;
7use std::str::from_utf8;
12 pub use std::fs::File;
13 pub use std::io::{self, Write};
14 pub use std::os::unix::io::FromRawFd;
15 pub use std::path::PathBuf;
16 pub use std::process::Stdio;
18 pub use std::time::Duration;
20 pub use assert_cmd::assert::OutputAssertExt;
21 pub use nix::pty::{openpty, OpenptyResult};
22 pub use wait_timeout::ChildExt;
24 pub const SAFE_CHILD_PROCESS_CREATION_TIME: Duration = Duration::from_millis(100);
25 pub const CHILD_WAIT_TIMEOUT: Duration = Duration::from_secs(15);
31use utils::mocked_pagers;
33const EXAMPLES_DIR: &str = "tests/examples";
35fn bat_raw_command_with_config() -> Command {
36 let mut cmd = Command::cargo_bin("bat").unwrap();
37 cmd.current_dir("tests/examples");
38 cmd.env_remove("BAT_CACHE_PATH");
39 cmd.env_remove("BAT_CONFIG_DIR");
40 cmd.env_remove("BAT_CONFIG_PATH");
41 cmd.env_remove("BAT_OPTS");
42 cmd.env_remove("BAT_PAGER");
43 cmd.env_remove("BAT_STYLE");
44 cmd.env_remove("BAT_TABS");
45 cmd.env_remove("BAT_THEME");
46 cmd.env_remove("COLORTERM");
47 cmd.env_remove("NO_COLOR");
48 cmd.env_remove("PAGER");
52fn bat_raw_command() -> Command {
53 let mut cmd = bat_raw_command_with_config();
54 cmd.arg("--no-config");
58fn bat_with_config() -> assert_cmd::Command {
59 assert_cmd::Command::from_std(bat_raw_command_with_config())
62fn bat() -> assert_cmd::Command {
63 assert_cmd::Command::from_std(bat_raw_command())
72 .stdout("hello world\n")
79 .write_stdin("foo\nbar\n")
82 .stdout("foo\nbar\n");
92 .stdout("hello world\nhello world\n");
96fn concatenate_stdin() {
101 .write_stdin("stdin\n")
104 .stdout("hello world\nstdin\nhello world\n");
108fn concatenate_empty_first() {
114 .stdout("hello world\n");
118fn concatenate_empty_last() {
124 .stdout("hello world\n");
128fn concatenate_empty_both() {
138fn concatenate_empty_between() {
145 .stdout("hello world\nhello world\n");
149fn concatenate_empty_first_and_last() {
156 .stdout("hello world\n");
160fn concatenate_single_line() {
162 .arg("single-line.txt")
163 .arg("single-line.txt")
166 .stdout("Single LineSingle Line");
170fn concatenate_single_line_empty() {
172 .arg("single-line.txt")
174 .arg("single-line.txt")
177 .stdout("Single LineSingle Line");
183 .arg("multiline.txt")
184 .arg("--style=numbers")
185 .arg("--decorations=always")
188 .stdout(" 1 line 1\n 2 line 2\n 3 line 3\n 4 line 4\n");
194 .arg("multiline.txt")
195 .arg("--line-range=2:3")
198 .stdout("line 2\nline 3\n");
202fn line_range_first_two() {
204 .arg("multiline.txt")
205 .arg("--line-range=:2")
208 .stdout("line 1\nline 2\n");
212fn line_range_last_3() {
214 .arg("multiline.txt")
215 .arg("--line-range=2:")
218 .stdout("line 2\nline 3\nline 4\n");
222fn line_range_multiple() {
224 .arg("multiline.txt")
225 .arg("--line-range=1:2")
226 .arg("--line-range=4:4")
229 .stdout("line 1\nline 2\nline 4\n");
233fn setup_temp_file(content: &[u8]) -> io::Result<(PathBuf, tempfile::TempDir)> {
234 let dir = tempfile::tempdir().expect("Couldn't create tempdir");
235 let path = dir.path().join("temp_file");
236 File::create(&path)?.write_all(content)?;
242fn basic_io_cycle() -> io::Result<()> {
243 let (filename, dir) = setup_temp_file(b"I am not empty")?;
245 let file_out = Stdio::from(File::create(&filename)?);
246 let res = bat_raw_command()
258fn first_file_cyclic_is_ok() -> io::Result<()> {
259 let (filename, dir) = setup_temp_file(b"I am not empty")?;
261 let file_out = Stdio::from(File::create(&filename)?);
262 let res = bat_raw_command()
274fn empty_file_cycle_is_ok() -> io::Result<()> {
275 let (filename, dir) = setup_temp_file(b"I am not empty")?;
277 let file_out = Stdio::from(File::create(&filename)?);
278 let res = bat_raw_command()
290fn stdin_to_stdout_cycle() -> io::Result<()> {
291 let (filename, dir) = setup_temp_file(b"I am not empty")?;
292 let file_in = Stdio::from(File::open(&filename)?);
293 let file_out = Stdio::from(File::create(&filename)?);
294 let res = bat_raw_command()
307fn no_args_doesnt_break() {
308 // To simulate bat getting started from the shell, a process is created with stdin and stdout
309 // as the slave end of a pseudo terminal. Although both point to the same "file", bat should
310 // not exit, because in this case it is safe to read and write to the same fd, which is why
312 let OpenptyResult { master, slave } = openpty(None, None).expect("Couldn't open pty.");
313 let mut master = unsafe { File::from_raw_fd(master) };
314 let stdin = unsafe { Stdio::from_raw_fd(slave) };
315 let stdout = unsafe { Stdio::from_raw_fd(slave) };
317 let mut child = bat_raw_command()
321 .expect("Failed to start.");
323 // Some time for the child process to start and to make sure, that we can poll the exit status.
324 // Although this waiting period is not necessary, it is best to keep it in and be absolutely
325 // sure, that the try_wait does not error later.
326 thread::sleep(SAFE_CHILD_PROCESS_CREATION_TIME);
328 // The child process should be running and waiting for input,
329 // therefore no exit status should be available.
330 let exit_status = child
332 .expect("Error polling exit status, this should never happen.");
333 assert!(exit_status.is_none());
335 // Write Ctrl-D (end of transmission) to the pty.
338 .expect("Couldn't write EOT character to master end.");
340 let exit_status = child
341 .wait_timeout(CHILD_WAIT_TIMEOUT)
342 .expect("Error polling exit status, this should never happen.")
343 .expect("Exit status not set, but the child should have exited already.");
345 assert!(exit_status.success());
353 .arg("--style=numbers")
354 .arg("--decorations=always")
372fn tabs_passthrough_wrapped() {
376 .arg("--style=plain")
377 .arg("--decorations=always")
399 .arg("--style=plain")
400 .arg("--decorations=always")
422 .arg("--style=plain")
423 .arg("--decorations=always")
441fn tabs_passthrough() {
445 .arg("--style=plain")
446 .arg("--decorations=always")
468 .arg("--style=plain")
469 .arg("--decorations=always")
491 .arg("--style=plain")
492 .arg("--decorations=always")
510fn fail_non_existing() {
511 bat().arg("non-existing-file").assert().failure();
516 bat().arg("sub_directory").assert().failure();
520fn do_not_exit_directory() {
522 .arg("sub_directory")
525 .stdout("hello world\n")
532 .env("PAGER", "echo pager-output")
533 .arg("--paging=always")
537 .stdout(predicate::eq("pager-output\n").normalize());
541fn pager_overwrite() {
543 .env("PAGER", "echo other-pager")
544 .env("BAT_PAGER", "echo pager-output")
545 .arg("--paging=always")
549 .stdout(predicate::eq("pager-output\n").normalize());
555 .env("PAGER", "echo other-pager")
556 .env("BAT_PAGER", "")
557 .arg("--paging=always")
561 .stdout(predicate::eq("hello world\n").normalize());
565fn env_var_pager_value_bat() {
568 .arg("--paging=always")
572 .stdout(predicate::eq("hello world\n").normalize());
576fn env_var_bat_pager_value_bat() {
578 .env("BAT_PAGER", "bat")
579 .arg("--paging=always")
583 .stderr(predicate::str::contains("bat as a pager is disallowed"));
587fn pager_value_bat() {
590 .arg("--paging=always")
594 .stderr(predicate::str::contains("bat as a pager is disallowed"));
597/// We shall use less instead of most if PAGER is used since PAGER
598/// is a generic env var
600#[serial] // Because of PATH
601fn pager_most_from_pager_env_var() {
602 mocked_pagers::with_mocked_versions_of_more_and_most_in_path(|| {
603 // If the output is not "I am most" then we know 'most' is not used
605 .env("PAGER", mocked_pagers::from("most"))
606 .arg("--paging=always")
610 .stdout(predicate::eq("hello world\n").normalize());
614/// If the bat-specific BAT_PAGER is used, obey the wish of the user
617#[serial] // Because of PATH
618fn pager_most_from_bat_pager_env_var() {
619 mocked_pagers::with_mocked_versions_of_more_and_most_in_path(|| {
621 .env("BAT_PAGER", mocked_pagers::from("most"))
622 .arg("--paging=always")
626 .stdout(predicate::str::contains("I am most"));
630/// Same reasoning with --pager as with BAT_PAGER
632#[serial] // Because of PATH
633fn pager_most_from_pager_arg() {
634 mocked_pagers::with_mocked_versions_of_more_and_most_in_path(|| {
636 .arg("--paging=always")
637 .arg(format!("--pager={}", mocked_pagers::from("most")))
641 .stdout(predicate::str::contains("I am most"));
645/// Make sure the logic for 'most' applies even if an argument is passed
647#[serial] // Because of PATH
648fn pager_most_with_arg() {
649 mocked_pagers::with_mocked_versions_of_more_and_most_in_path(|| {
651 .env("PAGER", format!("{} -w", mocked_pagers::from("most")))
652 .arg("--paging=always")
656 .stdout(predicate::eq("hello world\n").normalize());
660/// Sanity check that 'more' is treated like 'most'
662#[serial] // Because of PATH
664 mocked_pagers::with_mocked_versions_of_more_and_most_in_path(|| {
666 .env("PAGER", mocked_pagers::from("more"))
667 .arg("--paging=always")
671 .stdout(predicate::eq("hello world\n").normalize());
676fn alias_pager_disable() {
678 .env("PAGER", "echo other-pager")
683 .stdout(predicate::eq("hello world\n").normalize());
687fn alias_pager_disable_long_overrides_short() {
689 .env("PAGER", "echo pager-output")
691 .arg("--paging=always")
695 .stdout(predicate::eq("pager-output\n").normalize());
699fn pager_failed_to_parse() {
701 .env("BAT_PAGER", "mismatched-quotes 'a")
702 .arg("--paging=always")
706 .stderr(predicate::str::contains("Could not parse pager command"));
710fn diagnostic_sanity_check() {
715 .stdout(predicate::str::contains("BAT_PAGER="))
720fn config_location_test() {
722 .env("BAT_CONFIG_PATH", "bat.conf")
723 .arg("--config-file")
726 .stdout("bat.conf\n");
729 .env("BAT_CONFIG_PATH", "not-existing.conf")
730 .arg("--config-file")
733 .stdout("not-existing.conf\n");
737fn config_location_when_generating() {
738 let tmp_dir = tempdir().expect("can create temporary directory");
739 let tmp_config_path = tmp_dir.path().join("should-be-created.conf");
741 // Create the file with bat
743 .env("BAT_CONFIG_PATH", tmp_config_path.to_str().unwrap())
744 .arg("--generate-config-file")
748 predicate::str::is_match("Success! Config file written to .*should-be-created.conf\n")
752 // Now we expect the file to exist. If it exists, we assume contents are correct
753 assert!(tmp_config_path.exists());
757fn config_location_from_bat_config_dir_variable() {
759 .env("BAT_CONFIG_DIR", "conf/")
760 .arg("--config-file")
763 .stdout(predicate::str::is_match("conf/config\n").unwrap());
767fn config_read_arguments_from_file() {
769 .env("BAT_CONFIG_PATH", "bat.conf")
773 .stdout(predicate::eq("dummy-pager-from-config\n").normalize());
778 // The output will be converted to UTF-8 with a leading UTF-8 BOM
781 .arg("--decorations=always")
782 .arg("test_UTF-16LE.txt")
785 .stdout(std::str::from_utf8(b"\xEF\xBB\xBFhello world\n").unwrap());
789fn can_print_file_named_cache() {
799fn can_print_file_named_cache_with_additional_argument() {
805 .stdout("test\nhello world\n")
810fn can_print_file_starting_with_cache() {
820fn does_not_print_unwanted_file_named_cache() {
821 bat_with_config().arg("cach").assert().failure();
825fn accepts_no_custom_assets_arg() {
826 // Just make sure --no-custom-assets is considered a valid arg
827 // Don't bother to actually verify that it works
829 .arg("--no-custom-assets")
838 .arg("unicode-wrap.txt")
839 .arg("--style=numbers,snip")
840 .arg("--decorations=always")
841 .arg("--terminal-width=40")
845 " 1 ビタミンA ビタミンD ビタミンE ビ
851 3 고양이 고양이 고양이 고양이 고양이
861 5 1 บวก 2 บวก 3 บวก 4 บวก 5 บวก 6 บวก
862 7 บวก 8 บวก 9 บวก 10 บวก 11 บวก 12
863 บวก 13 บวก 14 บวก 15 บวก 16 บวก 17
866 7 Бельгия Болгария Чехия Дания Герман
867 ия Эстония Ирландия Греция Испания
868 Франция Хорватия Италия Кипр Латвия
869 Литва Люксембург Венгрия Мальта Ни
870 дерланды Австрия Польша Португалия
871 Румыния Словения Словакия Финляндия
872 Швеция Великобритания
880 .arg("multiline.txt")
881 .arg("--style=numbers,snip")
882 .arg("--decorations=always")
883 .arg("--line-range=1:2")
884 .arg("--line-range=4:")
885 .arg("--terminal-width=80")
891 ...─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ 8< ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
898fn empty_file_leads_to_empty_output_with_grid_enabled() {
902 .arg("--decorations=always")
903 .arg("--terminal-width=80")
910fn empty_file_leads_to_empty_output_with_rule_enabled() {
914 .arg("--decorations=always")
915 .arg("--terminal-width=80")
925 .arg("--decorations=always")
926 .arg("--style=header")
928 .arg("--file-name=foo")
931 .stdout("File: foo\n")
936fn header_full_basic() {
939 .arg("--decorations=always")
940 .arg("--style=header-filename,header-filesize")
942 .arg("--file-name=foo")
945 .stdout("File: foo\nSize: 12 B\n")
953 .arg("--decorations=always")
954 .arg("--style=header")
956 .arg("--file-name=foo")
959 .stdout("File: foo <BINARY>\n")
964fn header_full_binary() {
967 .arg("--decorations=always")
968 .arg("--style=header-filename,header-filesize")
970 .arg("--file-name=foo")
973 .stdout("File: foo <BINARY>\nSize: 4 B\n")
980 .arg("--decorations=always")
981 .arg("--style=header")
984 .write_stdin("stdin\n")
985 .arg("--file-name=foo")
988 .stdout("File: foo\n")
993fn filename_stdin_binary() {
994 let vec = vec![0; 1];
996 .arg("--decorations=always")
997 .arg("--style=header")
999 .arg("--file-name=foo")
1002 .stdout("File: foo <BINARY>\n")
1007fn filename_multiple_ok() {
1009 .arg("--decorations=always")
1010 .arg("--style=header")
1013 .arg("--file-name=foo")
1014 .arg("single-line.txt")
1015 .arg("--file-name=bar")
1018 .stdout("File: foo\n\nFile: bar\n")
1023fn filename_multiple_err() {
1025 .arg("--decorations=always")
1026 .arg("--style=header")
1029 .arg("--file-name=foo")
1030 .arg("single-line.txt")
1036fn header_padding() {
1038 .arg("--decorations=always")
1039 .arg("--style=header")
1041 .arg("single-line.txt")
1043 .stdout("File: test.txt\nhello world\n\nFile: single-line.txt\nSingle Line\n")
1048fn header_full_padding() {
1050 .arg("--decorations=always")
1051 .arg("--style=header-filename,header-filesize")
1053 .arg("single-line.txt")
1055 .stdout("File: test.txt\nSize: 12 B\nhello world\n\nFile: single-line.txt\nSize: 11 B\nSingle Line\n")
1060fn header_padding_rule() {
1062 .arg("--decorations=always")
1063 .arg("--style=header,rule")
1064 .arg("--terminal-width=80")
1066 .arg("single-line.txt")
1071────────────────────────────────────────────────────────────────────────────────
1072File: single-line.txt
1080fn header_full_padding_rule() {
1082 .arg("--decorations=always")
1083 .arg("--style=header-filename,header-filesize,rule")
1084 .arg("--terminal-width=80")
1086 .arg("single-line.txt")
1092────────────────────────────────────────────────────────────────────────────────
1093File: single-line.txt
1102fn grid_overrides_rule() {
1104 .arg("--decorations=always")
1105 .arg("--style=grid,rule")
1106 .arg("--terminal-width=80")
1108 .arg("single-line.txt")
1112────────────────────────────────────────────────────────────────────────────────
1114────────────────────────────────────────────────────────────────────────────────
1115────────────────────────────────────────────────────────────────────────────────
1117────────────────────────────────────────────────────────────────────────────────
1121 "\x1b[33m[bat warning]\x1b[0m: Style 'rule' is a subset of style 'grid', 'rule' will not be visible.\n",
1125#[cfg(target_os = "linux")]
1127fn file_with_invalid_utf8_filename() {
1128 use std::ffi::OsStr;
1131 use std::os::unix::ffi::OsStrExt;
1133 let tmp_dir = tempdir().expect("can create temporary directory");
1134 let file_path = tmp_dir
1136 .join(OsStr::from_bytes(b"test-invalid-utf8-\xC3(.rs"));
1138 let mut file = File::create(&file_path).expect("can create temporary file");
1139 writeln!(file, "dummy content").expect("can write to file");
1143 .arg(file_path.as_os_str())
1146 .stdout("dummy content\n");
1150fn do_not_panic_regression_tests() {
1159 .arg("--color=always")
1160 .arg(&format!("regression_tests/{}", filename))
1167fn do_not_detect_different_syntax_for_stdin_and_files() {
1168 let file = "regression_tests/issue_985.js";
1170 let cmd_for_file = bat()
1171 .arg("--color=always")
1172 .arg("--map-syntax=*.js:Markdown")
1173 .arg(&format!("--file-name={}", file))
1174 .arg("--style=plain")
1179 let cmd_for_stdin = bat()
1180 .arg("--color=always")
1181 .arg("--map-syntax=*.js:Markdown")
1182 .arg("--style=plain")
1183 .arg(&format!("--file-name={}", file))
1184 .pipe_stdin(Path::new(EXAMPLES_DIR).join(file))
1190 from_utf8(&cmd_for_file.get_output().stdout).expect("output is valid utf-8"),
1191 from_utf8(&cmd_for_stdin.get_output().stdout).expect("output is valid utf-8")
1196fn no_first_line_fallback_when_mapping_to_invalid_syntax() {
1197 let file = "regression_tests/first_line_fallback.invalid-syntax";
1200 .arg("--color=always")
1201 .arg("--map-syntax=*.invalid-syntax:InvalidSyntax")
1202 .arg(&format!("--file-name={}", file))
1203 .arg("--style=plain")
1207 .stderr(predicate::str::contains("unknown syntax: 'InvalidSyntax'"));
1214 .arg("nonprintable.txt")
1216 .stdout("hello·world␊\n├──┤␍␀␇␈␛")
1224 .arg("--color=never")
1225 .arg("--decorations=never")
1226 .arg("single-line.txt")
1229 .stdout("Single Line");
1233fn no_paging_short_arg() {
1236 .arg("--color=never")
1237 .arg("--decorations=never")
1238 .arg("single-line.txt")
1241 .stdout("Single Line");
1248 .arg("--color=never")
1249 .arg("--decorations=never")
1250 .arg("single-line.txt")
1253 .stdout("Single Line");
1257fn plain_mode_does_not_add_nonexisting_newline() {
1259 .arg("--paging=never")
1260 .arg("--color=never")
1261 .arg("--decorations=always")
1262 .arg("--style=plain")
1263 .arg("single-line.txt")
1266 .stdout("Single Line");
1269// Regression test for https://github.com/sharkdp/bat/issues/299
1271fn grid_for_file_without_newline() {
1273 .arg("--paging=never")
1274 .arg("--color=never")
1275 .arg("--terminal-width=80")
1276 .arg("--wrap=never")
1277 .arg("--decorations=always")
1278 .arg("--style=full")
1279 .arg("single-line.txt")
1284───────┬────────────────────────────────────────────────────────────────────────
1285 │ File: single-line.txt
1287───────┼────────────────────────────────────────────────────────────────────────
1289───────┴────────────────────────────────────────────────────────────────────────
1295// For ANSI theme, use underscore as a highlighter
1297fn ansi_highlight_underline() {
1299 .arg("--paging=never")
1300 .arg("--color=never")
1301 .arg("--terminal-width=80")
1302 .arg("--wrap=never")
1303 .arg("--decorations=always")
1304 .arg("--theme=ansi")
1305 .arg("--style=plain")
1306 .arg("--highlight-line=1")
1307 .write_stdin("Ansi Underscore Test\nAnother Line")
1310 .stdout("\x1B[4mAnsi Underscore Test\n\x1B[24mAnother Line")
1314// Ensure that ANSI passthrough is emitted properly for both wrapping and non-wrapping printer.
1316fn ansi_passthrough_emit() {
1317 for wrapping in &["never", "character"] {
1319 .arg("--paging=never")
1320 .arg("--color=never")
1321 .arg("--terminal-width=80")
1322 .arg(format!("--wrap={}", wrapping))
1323 .arg("--decorations=always")
1324 .arg("--style=plain")
1325 .write_stdin("\x1B[33mColor\nColor \x1B[m\nPlain\n")
1328 .stdout("\x1B[33m\x1B[33mColor\n\x1B[33mColor \x1B[m\nPlain\n")
1334fn ignored_suffix_arg() {
1341 .stdout("\u{1b}[38;5;231m{\u{1b}[0m\u{1b}[38;5;208m\"\u{1b}[0m\u{1b}[38;5;208mtest\u{1b}[0m\u{1b}[38;5;208m\"\u{1b}[0m\u{1b}[38;5;231m:\u{1b}[0m\u{1b}[38;5;231m \u{1b}[0m\u{1b}[38;5;186m\"\u{1b}[0m\u{1b}[38;5;186mvalue\u{1b}[0m\u{1b}[38;5;186m\"\u{1b}[0m\u{1b}[38;5;231m}\u{1b}[0m")
1347 .arg("--ignored-suffix=.suffix")
1348 .arg("test.json.suffix")
1351 .stdout("\u{1b}[38;5;231m{\u{1b}[0m\u{1b}[38;5;208m\"\u{1b}[0m\u{1b}[38;5;208mtest\u{1b}[0m\u{1b}[38;5;208m\"\u{1b}[0m\u{1b}[38;5;231m:\u{1b}[0m\u{1b}[38;5;231m \u{1b}[0m\u{1b}[38;5;186m\"\u{1b}[0m\u{1b}[38;5;186mvalue\u{1b}[0m\u{1b}[38;5;186m\"\u{1b}[0m\u{1b}[38;5;231m}\u{1b}[0m")
1357 .arg("test.json.suffix")
1360 .stdout("\u{1b}[38;5;231m{\"test\": \"value\"}\u{1b}[0m")
1365fn all_global_git_config_locations_syntax_mapping_work() {
1366 let fake_home = Path::new(EXAMPLES_DIR).join("git").canonicalize().unwrap();
1367 let expected = "\u{1b}[38;5;231m[\u{1b}[0m\u{1b}[38;5;149muser\u{1b}[0m\u{1b}[38;5;231m]\u{1b}[0m
1368\u{1b}[38;5;231m \u{1b}[0m\u{1b}[38;5;231memail\u{1b}[0m\u{1b}[38;5;231m \u{1b}[0m\u{1b}[38;5;203m=\u{1b}[0m\u{1b}[38;5;231m \u{1b}[0m\u{1b}[38;5;186mfoo@bar.net\u{1b}[0m
1369\u{1b}[38;5;231m \u{1b}[0m\u{1b}[38;5;231mname\u{1b}[0m\u{1b}[38;5;231m \u{1b}[0m\u{1b}[38;5;203m=\u{1b}[0m\u{1b}[38;5;231m \u{1b}[0m\u{1b}[38;5;186mfoobar\u{1b}[0m
1373 .env("XDG_CONFIG_HOME", fake_home.join(".config").as_os_str())
1376 .arg("git/.config/git/config")
1383 .env("HOME", fake_home.as_os_str())
1386 .arg("git/.config/git/config")
1393 .env("HOME", fake_home.as_os_str())
1396 .arg("git/.gitconfig")
1404fn acknowledgements() {
1406 .arg("--acknowledgements")
1410 // Just some sanity checking that avoids names of persons, except our own Keith Hall :)
1411 predicate::str::contains(
1412 "Copyright (c) 2018-2021 bat-developers (https://github.com/sharkdp/bat).",
1414 .and(predicate::str::contains(
1415 "Copyright (c) 2012-2020 The Sublime CMake authors",
1417 .and(predicate::str::contains(
1418 "Copyright 2014-2015 SaltStack Team",
1420 .and(predicate::str::contains(
1421 "Copyright (c) 2013-present Dracula Theme",
1423 .and(predicate::str::contains(
1424 "## syntaxes/01_Packages/Rust/LICENSE.txt",
1426 .and(predicate::str::contains(
1427 "## syntaxes/02_Extra/http-request-response/LICENSE",
1429 .and(predicate::str::contains(
1430 "## themes/dracula-sublime/LICENSE",
1432 .and(predicate::str::contains("Copyright (c) 2017 b123400"))
1433 .and(predicate::str::contains("Copyright (c) 2021 Keith Hall"))
1434 .and(predicate::str::contains("Copyright 2014 Clams")),
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list