Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle git blame output #705

Merged
merged 5 commits into from
Aug 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ name = "delta"
path = "src/main.rs"

[dependencies]
chrono = "0.4.19"
chrono-humanize = "0.2.1"
ansi_colours = "1.0.4"
ansi_term = "0.12.1"
atty = "0.2.14"
Expand Down
122 changes: 122 additions & 0 deletions src/blame.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
use chrono::{DateTime, FixedOffset};
use lazy_static::lazy_static;
use regex::Regex;

use crate::config;
use crate::delta;
use crate::format;

#[derive(Debug)]
pub struct BlameLine<'a> {
pub commit: &'a str,
pub author: &'a str,
pub time: DateTime<FixedOffset>,
pub line_number: usize,
pub code: &'a str,
}

// E.g.
//ea82f2d0 (Dan Davison 2021-08-22 18:20:19 -0700 120) let mut handled_line = self.handle_commit_meta_header_line()?

lazy_static! {
static ref BLAME_LINE_REGEX: Regex = Regex::new(
r"(?x)
^
(
[0-9a-f]{8} # commit hash
)
[\ ]
\( # open (
(
[^\ ].*[^\ ] # author name
)
[\ ]+
( # timestamp
[0-9]{4}-[0-9]{2}-[0-9]{2}\ [0-9]{2}:[0-9]{2}:[0-9]{2}\ [-+][0-9]{4}
)
[\ ]+
(
[0-9]+ # line number
)
\) # close )
(
.* # code, with leading space
)
$
"
)
.unwrap();
}

pub fn parse_git_blame_line<'a>(line: &'a str, timestamp_format: &str) -> Option<BlameLine<'a>> {
if let Some(caps) = BLAME_LINE_REGEX.captures(line) {
let commit = caps.get(1).unwrap().as_str();
let author = caps.get(2).unwrap().as_str();
let timestamp = caps.get(3).unwrap().as_str();
if let Ok(time) = DateTime::parse_from_str(timestamp, timestamp_format) {
let line_number_str = caps.get(4).unwrap().as_str();
if let Ok(line_number) = line_number_str.parse::<usize>() {
let code = caps.get(5).unwrap().as_str();
Some(BlameLine {
commit,
author,
time,
line_number,
code,
})
} else {
None
}
} else {
None
}
} else {
None
}
}

lazy_static! {
pub static ref BLAME_PLACEHOLDER_REGEX: Regex =
format::make_placeholder_regex(&["timestamp", "author", "commit"]);
}

pub fn format_blame_metadata(
format_data: &[format::FormatStringPlaceholderData],
blame: &BlameLine,
config: &config::Config,
) -> String {
let mut s = String::new();
let mut suffix = "";
for placeholder in format_data {
s.push_str(placeholder.prefix);

let alignment_spec = placeholder.alignment_spec.unwrap_or("<");
let width = placeholder.width.unwrap_or(15);

let pad = |s| format::pad(s, width, alignment_spec);
match placeholder.placeholder {
Some("timestamp") => s.push_str(&pad(
&chrono_humanize::HumanTime::from(blame.time).to_string()
)),
Some("author") => s.push_str(&pad(blame.author)),
Some("commit") => s.push_str(&pad(&delta::format_raw_line(blame.commit, config))),
None => {}
Some(_) => unreachable!(),
}
suffix = placeholder.suffix;
}
s.push_str(suffix);
s
}

#[test]
fn test_blame_line_regex() {
for line in &[
"ea82f2d0 (Dan Davison 2021-08-22 18:20:19 -0700 120) let mut handled_line = self.handle_commit_meta_header_line()?",
"b2257cfa (Dan Davison 2020-07-18 15:34:43 -0400 1) use std::borrow::Cow;"
] {
let caps = BLAME_LINE_REGEX.captures(line);
assert!(caps.is_some());
assert!(parse_git_blame_line(line, "%Y-%m-%d %H:%M:%S %z").is_some());
}
}
27 changes: 27 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,33 @@ pub struct Opt {
/// (underline), 'ol' (overline), or the combination 'ul ol'.
pub hunk_header_decoration_style: String,

/// Format string for git blame commit metadata. Available placeholders are
/// "{timestamp}", "{author}", and "{commit}".
#[structopt(
long = "blame-format",
default_value = "{timestamp:<15} {author:<15} {commit:<8} │ "
)]
pub blame_format: String,

/// Background colors used for git blame lines (space-separated string).
/// Lines added by the same commit are painted with the same color; colors
/// are recycled as needed.
#[structopt(long = "blame-palette")]
pub blame_palette: Option<String>,

/// Format of `git blame` timestamp in raw git output received by delta.
#[structopt(
long = "blame-timestamp-format",
default_value = "%Y-%m-%d %H:%M:%S %z"
)]
pub blame_timestamp_format: String,

/// Default language used for syntax highlighting when this cannot be
/// inferred from a filename. It will typically make sense to set this in
/// per-repository git config ().git/config)
#[structopt(long = "default-language")]
pub default_language: Option<String>,

/// The regular expression used to decide what a word is for the within-line highlight
/// algorithm. For less fine-grained matching than the default try --word-diff-regex="\S+"
/// --max-line-distance=1.0 (this is more similar to `git --word-diff`).
Expand Down
12 changes: 12 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,15 @@ use crate::style::{self, Style};
pub struct Config {
pub available_terminal_width: usize,
pub background_color_extends_to_terminal_width: bool,
pub blame_format: String,
pub blame_palette: Option<Vec<String>>,
pub blame_timestamp_format: String,
pub commit_style: Style,
pub color_only: bool,
pub commit_regex: Regex,
pub cwd_relative_to_repo_root: Option<String>,
pub decorations_width: cli::Width,
pub default_language: Option<String>,
pub diff_stat_align_width: usize,
pub error_exit_code: i32,
pub file_added_label: String,
Expand Down Expand Up @@ -201,11 +205,19 @@ impl From<cli::Opt> for Config {
background_color_extends_to_terminal_width: opt
.computed
.background_color_extends_to_terminal_width,
blame_format: opt.blame_format,
blame_palette: opt.blame_palette.map(|s| {
s.split_whitespace()
.map(|s| s.to_owned())
.collect::<Vec<String>>()
}),
blame_timestamp_format: opt.blame_timestamp_format,
commit_style,
color_only: opt.color_only,
commit_regex,
cwd_relative_to_repo_root: std::env::var("GIT_PREFIX").ok(),
decorations_width: opt.computed.decorations_width,
default_language: opt.default_language,
diff_stat_align_width: opt.diff_stat_align_width,
error_exit_code: 2, // Use 2 for error because diff uses 0 and 1 for non-error.
file_added_label,
Expand Down
Loading