diff options
author | Dan Davison <dandavison7@gmail.com> | 2021-08-29 20:25:28 -0400 |
---|---|---|
committer | Dan Davison <dandavison7@gmail.com> | 2021-08-29 22:51:06 -0400 |
commit | 8db8e908432539543341109f94a19dc2c6f4af7f (patch) | |
tree | fa8a283f1b908ff80ae39b892842c63dc85caede /src/subcommands/diff.rs | |
parent | 97203ce2338a54b62080116f17b9b928279ae8c8 (diff) |
Refactor: subcommands module
Diffstat (limited to 'src/subcommands/diff.rs')
-rw-r--r-- | src/subcommands/diff.rs | 130 |
1 files changed, 130 insertions, 0 deletions
diff --git a/src/subcommands/diff.rs b/src/subcommands/diff.rs new file mode 100644 index 00000000..21c7fb07 --- /dev/null +++ b/src/subcommands/diff.rs @@ -0,0 +1,130 @@ +use std::io::{ErrorKind, Write}; +use std::path::PathBuf; +use std::process; + +use bytelines::ByteLinesReader; + +use crate::config::{self, delta_unreachable}; +use crate::delta; + +/// Run `git diff` on the files provided on the command line and display the output. +pub fn diff( + minus_file: Option<&PathBuf>, + plus_file: Option<&PathBuf>, + config: &config::Config, + writer: &mut dyn Write, +) -> i32 { + use std::io::BufReader; + if minus_file.is_none() || plus_file.is_none() { + eprintln!( + "\ +The main way to use delta is to configure it as the pager for git: \ +see https://github.com/dandavison/delta#configuration. \ +You can also use delta to diff two files: `delta file_A file_B`." + ); + return config.error_exit_code; + } + let minus_file = minus_file.unwrap(); + let plus_file = plus_file.unwrap(); + + let diff_command = "git"; + let diff_command_path = match grep_cli::resolve_binary(PathBuf::from(diff_command)) { + Ok(path) => path, + Err(err) => { + eprintln!("Failed to resolve command '{}': {}", diff_command, err); + return config.error_exit_code; + } + }; + + let diff_process = process::Command::new(diff_command_path) + .args(&["diff", "--no-index"]) + .args(&[minus_file, plus_file]) + .stdout(process::Stdio::piped()) + .spawn(); + + if let Err(err) = diff_process { + eprintln!("Failed to execute the command '{}': {}", diff_command, err); + return config.error_exit_code; + } + let mut diff_process = diff_process.unwrap(); + + if let Err(error) = delta::delta( + BufReader::new(diff_process.stdout.take().unwrap()).byte_lines(), + writer, + config, + ) { + match error.kind() { + ErrorKind::BrokenPipe => return 0, + _ => { + eprintln!("{}", error); + return config.error_exit_code; + } + } + }; + + // Return the exit code from the `git diff` processl, so that the exit code + // contract of `delta file_A file_B` is the same as that of `diff file_A + // file_B` (i.e. 0 if same, 1 if different, 2 if error). + diff_process + .wait() + .unwrap_or_else(|_| { + delta_unreachable(&format!("'{}' process not running.", diff_command)); + }) + .code() + .unwrap_or_else(|| { + eprintln!("'{}' process terminated without exit status.", diff_command); + config.error_exit_code + }) +} + +#[cfg(test)] +mod main_tests { + use std::io::{Cursor, Read, Seek, SeekFrom}; + use std::path::PathBuf; + + use super::diff; + use crate::tests::integration_test_utils; + + #[test] + #[ignore] // https://github.com/dandavison/delta/pull/546 + fn test_diff_same_empty_file() { + _do_diff_test("/dev/null", "/dev/null", false); + } + + #[test] + #[cfg_attr(target_os = "windows", ignore)] + fn test_diff_same_non_empty_file() { + _do_diff_test("/etc/passwd", "/etc/passwd", false); + } + + #[test] + #[cfg_attr(target_os = "windows", ignore)] + fn test_diff_empty_vs_non_empty_file() { + _do_diff_test("/dev/null", "/etc/passwd", true); + } + + #[test] + #[cfg_attr(target_os = "windows", ignore)] + fn test_diff_two_non_empty_files() { + _do_diff_test("/etc/group", "/etc/passwd", true); + } + + fn _do_diff_test(file_a: &str, file_b: &str, expect_diff: bool) { + let config = integration_test_utils::make_config_from_args(&[]); + let mut writer = Cursor::new(vec![]); + let exit_code = diff( + Some(&PathBuf::from(file_a)), + Some(&PathBuf::from(file_b)), + &config, + &mut writer, + ); + assert_eq!(exit_code, if expect_diff { 1 } else { 0 }); + } + + fn _read_to_string(cursor: &mut Cursor<Vec<u8>>) -> String { + let mut s = String::new(); + cursor.seek(SeekFrom::Start(0)).unwrap(); + cursor.read_to_string(&mut s).unwrap(); + s + } +} |