@@ -623,6 +623,24 @@ pub fn render_age(time: i64) -> String {
623623 }
624624}
625625
626+/// Format unix-seconds `time` as an absolute date `YYYY-MM-DD` (UTC), using
627+/// the civil-from-days algorithm (no external date crate).
628+pub fn render_date(time: i64) -> String {
629+ let days = time.div_euclid(86_400);
630+ // Howard Hinnant's civil_from_days
631+ let z = days + 719_468;
632+ let era = z.div_euclid(146_097);
633+ let doe = z.rem_euclid(146_097);
634+ let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
635+ let y = yoe + era * 400;
636+ let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
637+ let mp = (5 * doy + 2) / 153;
638+ let d = doy - (153 * mp + 2) / 5 + 1;
639+ let m = if mp < 10 { mp + 3 } else { mp - 9 };
640+ let y = if m <= 2 { y + 1 } else { y };
641+ format!("{:04}-{:02}-{:02}", y, m, d)
642+}
643+
626644/// the repo value in context (the loaded repo), for resolving ids to commits
627645fn top_of(interp: &Interp, _x: &Value) -> Result<Value, Crash> {
628646 let old = interp.old_repo.borrow().clone();
@@ -844,6 +862,12 @@ pub struct TreeOptions {
844862 pub icons: bool,
845863 pub color: String,
846864 pub lanes: i64,
865+ /// show the full author name column
866+ pub author: bool,
867+ /// show the absolute commit date column (YYYY-MM-DD)
868+ pub date: bool,
869+ /// show the number of files changed column
870+ pub files: bool,
847871}
848872
849873fn default_tree_options(_interp: &Interp) -> Result<TreeOptions, Crash> {
@@ -854,6 +878,9 @@ fn default_tree_options(_interp: &Interp) -> Result<TreeOptions, Crash> {
854878 icons: false,
855879 color: "auto".into(),
856880 lanes: 4,
881+ author: false,
882+ date: false,
883+ files: false,
857884 })
858885}
859886
@@ -882,6 +909,18 @@ pub fn tree_with(interp: &mut Interp, opts: &Value, repo: &Value) -> Result<Valu
882909 if lanes < 1 {
883910 return Err(Crash::new("treeWith: lanes must be at least 1"));
884911 }
912+ let author = match opts.field("author")? {
913+ Value::Bool(b) => b,
914+ _ => return Err(Crash::new("treeWith: author must be a Bool")),
915+ };
916+ let date = match opts.field("date")? {
917+ Value::Bool(b) => b,
918+ _ => return Err(Crash::new("treeWith: date must be a Bool")),
919+ };
920+ let files = match opts.field("files")? {
921+ Value::Bool(b) => b,
922+ _ => return Err(Crash::new("treeWith: files must be a Bool")),
923+ };
885924 let pal = Palette {
886925 on: color_enabled(&color),
887926 };
@@ -892,6 +931,9 @@ pub fn tree_with(interp: &mut Interp, opts: &Value, repo: &Value) -> Result<Valu
892931 icons,
893932 color,
894933 lanes,
934+ author,
935+ date,
936+ files,
895937 };
896938 let text = tree_render(interp, &o, repo, &pal, true)?;
897939 Ok(Value::text(text))
@@ -909,6 +951,7 @@ struct CommitInfo {
909951 is_child_of_ancestor: bool,
910952 meta: Option<crate::domain::MetaInfo>,
911953 size: Option<usize>, // lines added+removed against parent
954+ nfiles: usize, // number of files in this commit's snapshot
912955 detail_marks: Vec<(String, char)>,
913956 children: Vec<CommitInfo>,
914957}
@@ -961,6 +1004,7 @@ fn build_info(
9611004 .collect::<Result<_, _>>()?;
9621005 let conflict = has_conflict(commit)?;
9631006 let files = commit.field("files")?;
1007+ let nfiles = files.as_list()?.len();
9641008 let (empty, size, detail_marks) = match parent_files {
9651009 Some(pf) => {
9661010 let change = Value::record(&[("from", pf.clone()), ("to", files.clone())]);
@@ -1029,6 +1073,7 @@ fn build_info(
10291073 is_child_of_ancestor: parent_is_ancestor,
10301074 meta,
10311075 size,
1076+ nfiles,
10321077 detail_marks,
10331078 children: infos,
10341079 })
@@ -1361,6 +1406,7 @@ fn clone_info(info: &CommitInfo, children: Vec<CommitInfo>) -> CommitInfo {
13611406 is_child_of_ancestor: info.is_child_of_ancestor,
13621407 meta: info.meta.clone(),
13631408 size: info.size,
1409+ nfiles: info.nfiles,
13641410 detail_marks: info.detail_marks.clone(),
13651411 children,
13661412 }
@@ -1503,6 +1549,9 @@ struct RowText {
15031549 age: String,
15041550 initials: String,
15051551 initials_author: String,
1552+ // extra right-side columns, already joined in display order (§Step 4):
1553+ // date, files changed, full author name — only the enabled ones, plain
1554+ meta_extra: Vec<String>,
15061555}
15071556
15081557fn build_row_text(
@@ -1597,16 +1646,35 @@ fn build_row_text(
15971646 }
15981647 _ => (String::new(), String::new(), String::new()),
15991648 };
1649+ // extra columns (§Step 4): date, files changed, full author name
1650+ let mut meta_extra: Vec<String> = Vec::new();
1651+ if let Some(info) = c {
1652+ if info.id != ROOT_ID {
1653+ if opts.date {
1654+ if let Some(m) = info.meta.as_ref() {
1655+ meta_extra.push(render_date(m.time));
1656+ }
1657+ }
1658+ if opts.files {
1659+ meta_extra.push(format!("{} files", info.nfiles));
1660+ }
1661+ if opts.author {
1662+ if let Some(m) = info.meta.as_ref() {
1663+ meta_extra.push(m.author.clone());
1664+ }
1665+ }
1666+ }
1667+ }
16001668 RowText {
16011669 gutter,
16021670 id,
1603−
16041671 bar,
16051672 msg,
16061673 labels,
16071674 age,
16081675 initials: init,
16091676 initials_author: author,
1677+ meta_extra,
16101678 }
16111679}
16121680
@@ -1701,7 +1769,10 @@ fn draw_rows(
17011769 line.push_str(&" ".repeat(want));
17021770 line.push_str(&pal.green(&pad_right(&t.labels, width(&t.labels))));
17031771 }
1704− if opts.margin && !t.age.is_empty() {
1772+ // metadata block: extra columns (date / files / author) then the margin
1773+ // (age + initials), right-aligned together past the message+labels edge
1774+ let has_margin = opts.margin && !t.age.is_empty();
1775+ if !t.meta_extra.is_empty() || has_margin {
17051776 let cur = if label_col && !t.labels.is_empty() {
17061777 msg_end_max + 2 + width(&t.labels)
17071778 } else {
@@ -1709,9 +1780,15 @@ fn draw_rows(
17091780 };
17101781 let want = 2 + lab_end_max.saturating_sub(cur);
17111782 line.push_str(&" ".repeat(want));
1712− line.push_str(&pal.grey(4, &t.age));
1713− line.push_str(" ");
1714− line.push_str(&pal.author(&t.initials_author, &t.initials));
1783+ let mut meta: Vec<String> = Vec::new();
1784+ for e in &t.meta_extra {
1785+ meta.push(pal.grey(4, e));
1786+ }
1787+ if has_margin {
1788+ meta.push(pal.grey(4, &t.age));
1789+ meta.push(pal.author(&t.initials_author, &t.initials));
1790+ }
1791+ line.push_str(&meta.join(" "));
17151792 }
17161793 let _ = pl;
17171794 lines.push(line);