Skip to content
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
2 changes: 2 additions & 0 deletions docs/manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,12 @@ Formatting strings use a subset of the placeholders available in `git log --form
| %ae | author email |
| %ad | author date |
| %as | author date in short format `YYYY-MM-DD` |
| %ar | author date, relative format (e.g., "21 hours ago") |
| %cn | committer name |
| %ce | committer email |
| %cd | committer date |
| %cs | committer date in short format `YYYY-MM-DD` |
| %cr | committer date, relative format (e.g., "4 days ago") |

If you add a '+' (plus sign) after % of a placeholder, a line-feed is inserted immediately before the expansion if and only if the placeholder expands to a non-empty string.

Expand Down
66 changes: 58 additions & 8 deletions src/print/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,14 @@ const AUTHOR: usize = 7;
const AUTHOR_EMAIL: usize = 8;
const AUTHOR_DATE: usize = 9;
const AUTHOR_DATE_SHORT: usize = 10;
const COMMITTER: usize = 11;
const COMMITTER_EMAIL: usize = 12;
const COMMITTER_DATE: usize = 13;
const COMMITTER_DATE_SHORT: usize = 14;
const BODY: usize = 15;
const BODY_RAW: usize = 16;
const AUTHOR_DATE_RELATIVE: usize = 11;
const COMMITTER: usize = 12;
const COMMITTER_EMAIL: usize = 13;
const COMMITTER_DATE: usize = 14;
const COMMITTER_DATE_SHORT: usize = 15;
const COMMITTER_DATE_RELATIVE: usize = 16;
const BODY: usize = 17;
const BODY_RAW: usize = 18;

const MODE_SPACE: usize = 1;
const MODE_PLUS: usize = 2;
Expand All @@ -57,8 +59,8 @@ const MODE_MINUS: usize = 3;
lazy_static! {
pub static ref PLACEHOLDERS: Vec<[String; 4]> = {
let base = vec![
"n", "H", "h", "P", "p", "d", "s", "an", "ae", "ad", "as", "cn", "ce", "cd", "cs", "b",
"B",
"n", "H", "h", "P", "p", "d", "s", "an", "ae", "ad", "as", "ar", "cn", "ce", "cd",
"cs", "cr", "b", "B",
];
base.iter()
.map(|b| {
Expand Down Expand Up @@ -260,6 +262,14 @@ pub fn format_commit(
}
write!(out, "{}", format_date(commit.author().when(), "%F"))
}
AUTHOR_DATE_RELATIVE => {
match mode {
MODE_SPACE => write!(out, " ").unwrap(),
MODE_PLUS => add_line(&mut lines, &mut out, wrapping),
_ => {}
}
write!(out, "{}", format_relative_time(commit.author().when()))
}
COMMITTER => {
match mode {
MODE_SPACE => write!(out, " ").unwrap(),
Expand Down Expand Up @@ -296,6 +306,14 @@ pub fn format_commit(
}
write!(out, "{}", format_date(commit.committer().when(), "%F"))
}
COMMITTER_DATE_RELATIVE => {
match mode {
MODE_SPACE => write!(out, " ").unwrap(),
MODE_PLUS => add_line(&mut lines, &mut out, wrapping),
_ => {}
}
write!(out, "{}", format_relative_time(commit.committer().when()))
}
BODY => {
let message = commit
.message()
Expand Down Expand Up @@ -517,6 +535,38 @@ pub fn format_date(time: Time, format: &str) -> String {
format!("{}", date.format(format))
}

/// Format a time as a relative time string (e.g., "21 hours ago", "4 days ago")
pub fn format_relative_time(time: Time) -> String {
let commit_time =
Local::from_offset(&FixedOffset::east(time.offset_minutes())).timestamp(time.seconds(), 0);
let now = Local::now();
let duration = now.signed_duration_since(commit_time);

let seconds = duration.num_seconds();
let minutes = duration.num_minutes();
let hours = duration.num_hours();
let days = duration.num_hours() / 24;
let weeks = days / 7;
let months = days / 30;
let years = days / 365;

if seconds < 60 {
format!("{} seconds ago", seconds)
} else if minutes < 60 {
format!("{} minutes ago", minutes)
} else if hours < 24 {
format!("{} hours ago", hours)
} else if days < 7 {
format!("{} days ago", days)
} else if weeks < 4 {
format!("{} weeks ago", weeks)
} else if months < 12 {
format!("{} months ago", months)
} else {
format!("{} years ago", years)
}
}

fn append_wrapped(vec: &mut Vec<String>, str: String, wrapping: &Option<Options>) {
if str.is_empty() {
vec.push(str);
Expand Down
Loading