feat: git retime 支持重排提交日期,新增 install.sh

- 新增 --start-date:按原顺序从指定日期起重排提交日期,默认每天一个
- 新增 --per-day 控制每天提交数、--day-gap 控制日期间隔;两者需配合
  --start-date 使用,否则报错提示
- 不给 --start-date 时保持原行为(保留原日期,只改时分秒)
- 预览在重排模式下显示完整的新日期时间,并说明日期规则
- install.sh:补 shebang 与可执行权限,构建后安装到 /usr/local/bin
- 文档:README.git.md 增加「重排日期」小节与选项表
This commit is contained in:
2026-11-01 10:05:19 +08:00
parent f3b90d205c
commit 1ae9488d8a
10 changed files with 291 additions and 18 deletions
+41 -7
View File
@@ -32,6 +32,13 @@ type retimeOptions struct {
// spacing found on the remote branch. A zero (or out-of-range) entry falls
// back to a random gap.
Gaps []time.Duration
// StartDate, when set, re-dates the commits instead of keeping their
// original days: the selected commits are laid out in order starting at
// StartDate, stepping DayGap days per day and PerDay commits per day.
StartDate time.Time
DayGap int // days between consecutive commit days, at least 1
PerDay int // commits placed on one day, 0 means "unlimited" (one day only)
}
// defaultRetimeOptions matches the command's documented behaviour: commits land
@@ -59,33 +66,60 @@ func planRetime(commits []gitCommit, opts retimeOptions) []retime {
window := opts.WindowEnd - opts.WindowStart
out := make([]retime, 0, len(commits))
// When a start date is given, days are assigned rather than inherited.
redate := !opts.StartDate.IsZero()
if redate && opts.DayGap < 1 {
opts.DayGap = 1
}
// Group by calendar day, preserving order.
var dayCommits []gitCommit
currentDay := time.Time{}
var day time.Time
flush := func() {
if len(dayCommits) == 0 {
return
}
out = append(out, planDay(dayCommits, opts, window)...)
out = append(out, planDay(day, dayCommits, opts, window)...)
dayCommits = nil
}
for _, c := range commits {
day := c.Date()
if !currentDay.IsZero() && !day.Equal(currentDay) {
if redate {
// Start a new day whenever the current one is full.
if len(dayCommits) == 0 {
if !day.IsZero() {
day = day.AddDate(0, 0, opts.DayGap)
} else {
day = midnightOf(opts.StartDate)
}
}
if opts.PerDay > 0 && len(dayCommits) >= opts.PerDay {
flush()
day = day.AddDate(0, 0, opts.DayGap)
}
dayCommits = append(dayCommits, c)
continue
}
original := c.Date()
if !day.IsZero() && !original.Equal(day) {
flush()
}
currentDay = day
day = original
dayCommits = append(dayCommits, c)
}
flush()
return out
}
// midnightOf returns the start of the calendar day of t, in t's location.
func midnightOf(t time.Time) time.Time {
return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
}
// planDay spreads one day's commits across the window.
func planDay(commits []gitCommit, opts retimeOptions, window time.Duration) []retime {
func planDay(day time.Time, commits []gitCommit, opts retimeOptions, window time.Duration) []retime {
out := make([]retime, 0, len(commits))
day := commits[0].Date()
offsets, squeezed := spreadWithin(day, len(commits), opts, window)