Add a --missing-as-empty flag to allow non-existent paths

By default, difftastic now errors if paths don't exist.
html_output
Wilfred Hughes 2022-02-03 21:26:51 +07:00
parent 635ef12f84
commit 9e48b64d2c
4 changed files with 40 additions and 9 deletions

@ -21,6 +21,14 @@ constants.
If given binary files, difftastic will now report if the file contents If given binary files, difftastic will now report if the file contents
are identical. are identical.
### Command Line Interface
Difftastic will now error if either argument does not exist, unless
`--missing-as-empty` (new argument) is passed. This is a better
default, but requires Mercurial uses to [specify this
flag](https://difftastic.wilfred.me.uk/mercurial.html) in their
configuration.
## 0.18.1 (released 30 January 2022) ## 0.18.1 (released 30 January 2022)
Fixed a compilation issue on Rust 1.54 (0.18 only built on newer Fixed a compilation issue on Rust 1.54 (0.18 only built on newer

@ -19,6 +19,7 @@ following to your `.hgrc` to run difftastic with `hg dft`.
``` ```
[extdiff] [extdiff]
cmd.dft = difft cmd.dft = difft
opts.dft = --missing-as-empty
``` ```
## hg log -p ## hg log -p

@ -2,7 +2,11 @@
use std::{fs, io::ErrorKind::*, path::Path}; use std::{fs, io::ErrorKind::*, path::Path};
pub fn read_files_or_die(lhs_path: &Path, rhs_path: &Path) -> (Vec<u8>, Vec<u8>) { pub fn read_files_or_die(
lhs_path: &Path,
rhs_path: &Path,
missing_as_empty: bool,
) -> (Vec<u8>, Vec<u8>) {
let lhs_res = fs::read(lhs_path); let lhs_res = fs::read(lhs_path);
let rhs_res = fs::read(rhs_path); let rhs_res = fs::read(rhs_path);
@ -12,8 +16,8 @@ pub fn read_files_or_die(lhs_path: &Path, rhs_path: &Path) -> (Vec<u8>, Vec<u8>)
// Proceed if we've been given two paths and only one // Proceed if we've been given two paths and only one
// exists. This is important for mercurial diffs when a file // exists. This is important for mercurial diffs when a file
// has been removed. // has been removed.
(Ok(lhs_src), Err(e)) if e.kind() == NotFound => (lhs_src, vec![]), (Ok(lhs_src), Err(e)) if missing_as_empty && e.kind() == NotFound => (lhs_src, vec![]),
(Err(e), Ok(rhs_src)) if e.kind() == NotFound => (vec![], rhs_src), (Err(e), Ok(rhs_src)) if missing_as_empty && e.kind() == NotFound => (vec![], rhs_src),
(lhs_res, rhs_res) => { (lhs_res, rhs_res) => {
// Something else went wrong. Print both errors // Something else went wrong. Print both errors
// encountered. // encountered.

@ -95,6 +95,7 @@ enum ColorOutput {
enum Mode { enum Mode {
Diff { Diff {
print_unchanged: bool, print_unchanged: bool,
missing_as_empty: bool,
background_color: BackgroundColor, background_color: BackgroundColor,
color_output: ColorOutput, color_output: ColorOutput,
display_width: usize, display_width: usize,
@ -170,6 +171,10 @@ fn app() -> clap::App<'static> {
Arg::new("skip-unchanged").long("skip-unchanged") Arg::new("skip-unchanged").long("skip-unchanged")
.help("Don't display anything if a file is unchanged.") .help("Don't display anything if a file is unchanged.")
) )
.arg(
Arg::new("missing-as-empty").long("missing-as-empty")
.help("Treat paths that don't exist as equivalent to an empty file.")
)
.arg( .arg(
Arg::new("paths") Arg::new("paths")
.value_name("PATHS") .value_name("PATHS")
@ -274,9 +279,11 @@ fn parse_args() -> Mode {
}; };
let print_unchanged = !matches.is_present("skip-unchanged"); let print_unchanged = !matches.is_present("skip-unchanged");
let missing_as_empty = matches.is_present("missing-as-empty");
Mode::Diff { Mode::Diff {
print_unchanged, print_unchanged,
missing_as_empty,
background_color, background_color,
color_output, color_output,
display_width, display_width,
@ -340,6 +347,7 @@ fn main() {
} }
Mode::Diff { Mode::Diff {
print_unchanged, print_unchanged,
missing_as_empty,
background_color, background_color,
color_output, color_output,
display_width, display_width,
@ -354,7 +362,7 @@ fn main() {
let rhs_path = Path::new(&rhs_path); let rhs_path = Path::new(&rhs_path);
if lhs_path.is_dir() && rhs_path.is_dir() { if lhs_path.is_dir() && rhs_path.is_dir() {
for diff_result in diff_directories(lhs_path, rhs_path) { for diff_result in diff_directories(lhs_path, rhs_path, missing_as_empty) {
print_diff_result( print_diff_result(
display_width, display_width,
background_color, background_color,
@ -363,7 +371,7 @@ fn main() {
); );
} }
} else { } else {
let diff_result = diff_file(&display_path, lhs_path, rhs_path); let diff_result = diff_file(&display_path, lhs_path, rhs_path, missing_as_empty);
print_diff_result( print_diff_result(
display_width, display_width,
background_color, background_color,
@ -376,8 +384,13 @@ fn main() {
} }
/// Print a diff between two files. /// Print a diff between two files.
fn diff_file(display_path: &str, lhs_path: &Path, rhs_path: &Path) -> DiffResult { fn diff_file(
let (lhs_bytes, rhs_bytes) = read_files_or_die(lhs_path, rhs_path); display_path: &str,
lhs_path: &Path,
rhs_path: &Path,
missing_as_empty: bool,
) -> DiffResult {
let (lhs_bytes, rhs_bytes) = read_files_or_die(lhs_path, rhs_path, missing_as_empty);
diff_file_content(display_path, &lhs_bytes, &rhs_bytes) diff_file_content(display_path, &lhs_bytes, &rhs_bytes)
} }
@ -473,7 +486,7 @@ fn diff_file_content(display_path: &str, lhs_bytes: &[u8], rhs_bytes: &[u8]) ->
/// ///
/// When more than one file is modified, the hg extdiff extension passes directory /// When more than one file is modified, the hg extdiff extension passes directory
/// paths with the all the modified files. /// paths with the all the modified files.
fn diff_directories(lhs_dir: &Path, rhs_dir: &Path) -> Vec<DiffResult> { fn diff_directories(lhs_dir: &Path, rhs_dir: &Path, missing_as_empty: bool) -> Vec<DiffResult> {
let mut res = vec![]; let mut res = vec![];
for entry in WalkDir::new(lhs_dir).into_iter().filter_map(Result::ok) { for entry in WalkDir::new(lhs_dir).into_iter().filter_map(Result::ok) {
let lhs_path = entry.path(); let lhs_path = entry.path();
@ -486,7 +499,12 @@ fn diff_directories(lhs_dir: &Path, rhs_dir: &Path) -> Vec<DiffResult> {
let rel_path = lhs_path.strip_prefix(lhs_dir).unwrap(); let rel_path = lhs_path.strip_prefix(lhs_dir).unwrap();
let rhs_path = Path::new(rhs_dir).join(rel_path); let rhs_path = Path::new(rhs_dir).join(rel_path);
res.push(diff_file(&rel_path.to_string_lossy(), lhs_path, &rhs_path)); res.push(diff_file(
&rel_path.to_string_lossy(),
lhs_path,
&rhs_path,
missing_as_empty,
));
} }
res res
} }