|
|
|
|
@ -1,6 +1,11 @@
|
|
|
|
|
//! CLI option parsing.
|
|
|
|
|
|
|
|
|
|
use std::{env, ffi::OsStr, fmt::Display, path::Path, path::PathBuf};
|
|
|
|
|
use std::{
|
|
|
|
|
env,
|
|
|
|
|
ffi::OsStr,
|
|
|
|
|
fmt::Display,
|
|
|
|
|
path::{Path, PathBuf},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
use clap::{crate_authors, crate_description, Arg, Command};
|
|
|
|
|
use const_format::formatcp;
|
|
|
|
|
@ -312,6 +317,61 @@ pub(crate) enum FileArgument {
|
|
|
|
|
DevNull,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl FileArgument {
|
|
|
|
|
pub(crate) fn permissions(&self) -> Option<FilePermissions> {
|
|
|
|
|
match self {
|
|
|
|
|
FileArgument::NamedPath(path) => {
|
|
|
|
|
let metadata = std::fs::metadata(path).ok()?;
|
|
|
|
|
Some(metadata.permissions().into())
|
|
|
|
|
}
|
|
|
|
|
FileArgument::Stdin => None,
|
|
|
|
|
FileArgument::DevNull => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A cross-platform representation of file permissions.
|
|
|
|
|
///
|
|
|
|
|
/// We're only interested in whether two permissions are the same, and
|
|
|
|
|
/// how to display them, so internally this is just a human-friendly
|
|
|
|
|
/// string.
|
|
|
|
|
#[derive(Debug, Eq, PartialEq)]
|
|
|
|
|
pub(crate) struct FilePermissions(String);
|
|
|
|
|
|
|
|
|
|
impl Display for FilePermissions {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
|
|
|
write!(f, "{}", self.0)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<String> for FilePermissions {
|
|
|
|
|
fn from(s: String) -> Self {
|
|
|
|
|
Self(s)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<&OsStr> for FilePermissions {
|
|
|
|
|
fn from(s: &OsStr) -> Self {
|
|
|
|
|
Self(s.to_string_lossy().into_owned())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<std::fs::Permissions> for FilePermissions {
|
|
|
|
|
fn from(perms: std::fs::Permissions) -> Self {
|
|
|
|
|
if cfg!(unix) {
|
|
|
|
|
use std::os::unix::fs::PermissionsExt;
|
|
|
|
|
Self(format!("{:o}", perms.mode()))
|
|
|
|
|
} else {
|
|
|
|
|
let s = if perms.readonly() {
|
|
|
|
|
"readonly"
|
|
|
|
|
} else {
|
|
|
|
|
"read-write"
|
|
|
|
|
};
|
|
|
|
|
Self(s.to_owned())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn try_canonicalize(path: &Path) -> PathBuf {
|
|
|
|
|
path.canonicalize().unwrap_or_else(|_| path.into())
|
|
|
|
|
}
|
|
|
|
|
@ -378,6 +438,8 @@ pub(crate) enum Mode {
|
|
|
|
|
/// The path where we can read the RHS file. This is often a
|
|
|
|
|
/// temporary file generated by source control.
|
|
|
|
|
rhs_path: FileArgument,
|
|
|
|
|
lhs_permissions: Option<FilePermissions>,
|
|
|
|
|
rhs_permissions: Option<FilePermissions>,
|
|
|
|
|
/// The path that we show to the user.
|
|
|
|
|
display_path: String,
|
|
|
|
|
/// If this file has been renamed, a description of the change.
|
|
|
|
|
@ -660,23 +722,38 @@ pub(crate) fn parse_args() -> Mode {
|
|
|
|
|
info!("CLI arguments: {:?}", args);
|
|
|
|
|
|
|
|
|
|
// TODO: document these different ways of calling difftastic.
|
|
|
|
|
let (display_path, lhs_path, rhs_path, renamed) = match &args[..] {
|
|
|
|
|
let (display_path, lhs_path, rhs_path, lhs_permissions, rhs_permissions, renamed) = match &args
|
|
|
|
|
[..]
|
|
|
|
|
{
|
|
|
|
|
[lhs_path, rhs_path] => {
|
|
|
|
|
let lhs_arg = FileArgument::from_cli_argument(lhs_path);
|
|
|
|
|
let rhs_arg = FileArgument::from_cli_argument(rhs_path);
|
|
|
|
|
let display_path = build_display_path(&lhs_arg, &rhs_arg);
|
|
|
|
|
(display_path, lhs_arg, rhs_arg, None)
|
|
|
|
|
|
|
|
|
|
let lhs_permissions = lhs_arg.permissions();
|
|
|
|
|
let rhs_permissions = rhs_arg.permissions();
|
|
|
|
|
|
|
|
|
|
(
|
|
|
|
|
display_path,
|
|
|
|
|
lhs_arg,
|
|
|
|
|
rhs_arg,
|
|
|
|
|
lhs_permissions,
|
|
|
|
|
rhs_permissions,
|
|
|
|
|
None,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
[display_path, lhs_tmp_file, _lhs_hash, _lhs_mode, rhs_tmp_file, _rhs_hash, _rhs_mode] => {
|
|
|
|
|
[display_path, lhs_tmp_file, _lhs_hash, lhs_mode, rhs_tmp_file, _rhs_hash, rhs_mode] => {
|
|
|
|
|
// https://git-scm.com/docs/git#Documentation/git.txt-codeGITEXTERNALDIFFcode
|
|
|
|
|
(
|
|
|
|
|
display_path.to_string_lossy().to_string(),
|
|
|
|
|
FileArgument::from_path_argument(lhs_tmp_file),
|
|
|
|
|
FileArgument::from_path_argument(rhs_tmp_file),
|
|
|
|
|
Some((*lhs_mode).into()),
|
|
|
|
|
Some((*rhs_mode).into()),
|
|
|
|
|
None,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
[old_name, lhs_tmp_file, _lhs_hash, _lhs_mode, rhs_tmp_file, _rhs_hash, _rhs_mode, new_name, _similarity] =>
|
|
|
|
|
[old_name, lhs_tmp_file, _lhs_hash, lhs_mode, rhs_tmp_file, _rhs_hash, rhs_mode, new_name, _similarity] =>
|
|
|
|
|
{
|
|
|
|
|
// Rename file.
|
|
|
|
|
// TODO: where does git document these 9 arguments?
|
|
|
|
|
@ -689,6 +766,8 @@ pub(crate) fn parse_args() -> Mode {
|
|
|
|
|
new_name,
|
|
|
|
|
FileArgument::from_path_argument(lhs_tmp_file),
|
|
|
|
|
FileArgument::from_path_argument(rhs_tmp_file),
|
|
|
|
|
Some((*lhs_mode).into()),
|
|
|
|
|
Some((*rhs_mode).into()),
|
|
|
|
|
Some(renamed),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
@ -749,6 +828,8 @@ pub(crate) fn parse_args() -> Mode {
|
|
|
|
|
language_overrides,
|
|
|
|
|
lhs_path,
|
|
|
|
|
rhs_path,
|
|
|
|
|
lhs_permissions,
|
|
|
|
|
rhs_permissions,
|
|
|
|
|
display_path,
|
|
|
|
|
renamed,
|
|
|
|
|
}
|
|
|
|
|
|