This commit is contained in:
Thomas Otto 2024-10-03 15:54:22 +02:00 committed by GitHub
commit 53d1cba8b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 57 additions and 15 deletions

View File

@ -79,9 +79,12 @@ pub struct Opt {
#[arg( #[arg(
long = "blame-timestamp-format", long = "blame-timestamp-format",
default_value = "%Y-%m-%d %H:%M:%S %z", default_value = "%Y-%m-%d %H:%M:%S %z",
value_name = "FMT" value_name = "FMT_IN"
)] )]
/// Format of `git blame` timestamp in raw git output received by delta. /// Expected formatting of the timestamp in raw `git blame` output read by delta.
///
/// Must follow the format given to the git `--date=format:...` option, i.e. the
/// `strftime` conventions. Spelled out days or months are not supported.
pub blame_timestamp_format: String, pub blame_timestamp_format: String,
#[arg(long = "blame-timestamp-output-format", value_name = "FMT")] #[arg(long = "blame-timestamp-output-format", value_name = "FMT")]

View File

@ -187,11 +187,30 @@ impl<'a> StateMachine<'a> {
} }
} }
#[derive(Debug)]
// The parsed time stamp, or if that fails the raw input string.
pub(crate) enum BlameTime<'a> {
Time(DateTime<FixedOffset>),
RawString(&'a str),
}
impl<'a> BlameTime<'a> {
fn format(&self, format_str: &Option<String>) -> String {
match self {
Self::Time(datetime) => match format_str {
Some(time_format) => datetime.format(time_format).to_string(),
None => chrono_humanize::HumanTime::from(*datetime).to_string(),
},
Self::RawString(s) => s.to_string(),
}
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct BlameLine<'a> { pub struct BlameLine<'a> {
pub commit: &'a str, pub commit: &'a str,
pub author: &'a str, pub author: &'a str,
pub time: DateTime<FixedOffset>, pub time: BlameTime<'a>,
pub line_number: usize, pub line_number: usize,
pub code: &'a str, pub code: &'a str,
} }
@ -210,11 +229,11 @@ lazy_static! {
[\ ] [\ ]
\( # open ( which the previous file name may not contain in case a name does (which is more likely) \( # open ( which the previous file name may not contain in case a name does (which is more likely)
( (
[^\ ].*[^\ ] # author name [^\ ].*?[^\ ] # author name (non-greedy via '?' so the following timestamp is matched)
) )
[\ ]+ [\ ]+
( # timestamp ( # timestamp, a general regex to capture input which will be parsed by strftime
[0-9]{4}-[0-9]{2}-[0-9]{2}\ [0-9]{2}:[0-9]{2}:[0-9]{2}\ [-+][0-9]{4} (?:[0-9]+[\ :\+-T]+)*[0-9]+ # numbers and possible separatores, then a final number
) )
[\ ]+ [\ ]+
( (
@ -237,7 +256,10 @@ pub fn parse_git_blame_line<'a>(line: &'a str, timestamp_format: &str) -> Option
let author = caps.get(2).unwrap().as_str(); let author = caps.get(2).unwrap().as_str();
let timestamp = caps.get(3).unwrap().as_str(); let timestamp = caps.get(3).unwrap().as_str();
let time = DateTime::parse_from_str(timestamp, timestamp_format).ok()?; let time = match DateTime::parse_from_str(timestamp, timestamp_format) {
Ok(datetime) => BlameTime::Time(datetime),
Err(_) => BlameTime::RawString(timestamp),
};
let line_number = caps.get(4).unwrap().as_str().parse::<usize>().ok()?; let line_number = caps.get(4).unwrap().as_str().parse::<usize>().ok()?;
@ -272,12 +294,9 @@ pub fn format_blame_metadata(
let width = placeholder.width.unwrap_or(15); let width = placeholder.width.unwrap_or(15);
let field = match placeholder.placeholder { let field = match placeholder.placeholder {
Some(Placeholder::Str("timestamp")) => { Some(Placeholder::Str("timestamp")) => Some(Cow::from(
Some(Cow::from(match &config.blame_timestamp_output_format { blame.time.format(&config.blame_timestamp_output_format),
Some(time_format) => blame.time.format(time_format).to_string(), )),
None => chrono_humanize::HumanTime::from(blame.time).to_string(),
}))
}
Some(Placeholder::Str("author")) => Some(Cow::from(blame.author)), Some(Placeholder::Str("author")) => Some(Cow::from(blame.author)),
Some(Placeholder::Str("commit")) => Some(delta::format_raw_line(blame.commit, config)), Some(Placeholder::Str("commit")) => Some(delta::format_raw_line(blame.commit, config)),
None => None, None => None,
@ -405,6 +424,9 @@ mod tests {
assert!(caps.is_some()); assert!(caps.is_some());
assert!(parse_git_blame_line(line, "%Y-%m-%d %H:%M:%S %z").is_some()); assert!(parse_git_blame_line(line, "%Y-%m-%d %H:%M:%S %z").is_some());
} }
let strange_date = "1234 (Author Name-Here 19-01-0500 18-08+2022T41 1) --date=format:'%d-%m%z %M-%H+%YT%S'";
assert!(parse_git_blame_line(strange_date, "%d-%m%z %M-%H+%YT%S").is_some());
} }
#[test] #[test]
@ -456,6 +478,23 @@ mod tests {
); );
} }
#[test]
fn test_blame_timestamp_regex() {
// Not supported: rfc2822, the weekday start (plus possible localisation) can't be separated from the author
for timestamp_fmt in &[
"1234 (Author Name-Here 2021-08-22 18:20:19 -0700 1) default blame (iso)",
"1234 (Author Name-Here 2022-01-19T23:49:50+01:00 1) iso strict",
"1234 (Author Name-Here 2022-01-19 1) short",
"1234 (Author Name-Here 123456789 1) unix",
"1234 (Author Name-Here 123456789 +0100 1) raw",
"1234 (Author Name-Here 19-01-0500 18-08+2022T41 1) --date=format:'%d-%m%z %M-%H+%YT%S'"
] {
let caps = BLAME_LINE_REGEX.captures(timestamp_fmt);
assert!(caps.is_some());
assert!(caps.unwrap().get(3).is_some())
}
}
#[test] #[test]
fn test_color_assignment() { fn test_color_assignment() {
let mut writer = Cursor::new(vec![0; 512]); let mut writer = Cursor::new(vec![0; 512]);
@ -551,7 +590,7 @@ mod tests {
BlameLine { BlameLine {
commit: "", commit: "",
author: "", author: "",
time, time: BlameTime::Time(time),
line_number: 0, line_number: 0,
code: "", code: "",
} }
@ -568,7 +607,7 @@ mod tests {
BlameLine { BlameLine {
commit: "", commit: "",
author, author,
time: chrono::DateTime::default(), time: BlameTime::Time(chrono::DateTime::default()),
line_number: 0, line_number: 0,
code: "", code: "",
} }