feat: git retime 改为在 --start-date/--end-date 区间内均匀分布提交

- 新增 --end-date:提交按比例铺满 [start-date, end-date],第 i 个提交
  落在第 i×天数÷提交数 天,提交远少于天数时也不会堆在第一天
- 移除 --per-day / --day-gap:原先的“每天一个、日期顺延”语义不直观
- 只给 --start-date 时集中在该日;--end-date 需配合 --start-date 且不早于它
- 同一天的多条提交仍按顺序落在 21:00-23:00 之间
- 文档与预览文案同步为区间语义
This commit is contained in:
2026-11-01 10:11:39 +08:00
parent 1ae9488d8a
commit 84aa91ba7f
7 changed files with 157 additions and 130 deletions
+16 -31
View File
@@ -29,8 +29,7 @@ type gitRetimeOptions struct {
// Redating: when StartDate is set, the selected commits are re-dated in
// order starting there instead of keeping their original days.
StartDate time.Time
DayGap int
PerDay int
EndDate time.Time // defaults to StartDate when only the start is given
}
// runGitRetime rewrites the timestamps of commits in [From, To] so they land
@@ -118,13 +117,12 @@ func writeRetimePreview(req BuiltinRequest, plan retimePlan, opts gitRetimeOptio
fmt.Fprintf(w, "目标: %s\n", target)
dateRule := "保留原日期"
if !opts.StartDate.IsZero() {
dateRule = "从 " + opts.StartDate.Format("2006-01-02") + " 起重排"
if opts.PerDay > 0 {
dateRule += fmt.Sprintf(",每天 %d 个", opts.PerDay)
}
if opts.DayGap > 1 {
dateRule += fmt.Sprintf(",间隔 %d 天", opts.DayGap)
end := opts.EndDate
if end.IsZero() {
end = opts.StartDate
}
dateRule = fmt.Sprintf("在 %s 至 %s 之间均匀分布",
opts.StartDate.Format("2006-01-02"), end.Format("2006-01-02"))
}
fmt.Fprintf(w, "窗口: %s-%s(%s)\n\n",
clockOf(opts.Window), clockOf(opts.WindowEnd), dateRule)
@@ -218,8 +216,7 @@ func collectRetimePlan(ctx context.Context, runner gitRunner, opts gitRetimeOpti
FirstGapMax: 20 * time.Minute,
Rand: newRetimeRand(opts.Seed),
StartDate: opts.StartDate,
DayGap: opts.DayGap,
PerDay: opts.PerDay,
EndDate: opts.EndDate,
}
_ = opts.MatchEmote
return buildPlan(selected, plannerOpts, branch, remote), nil
@@ -388,26 +385,14 @@ func parseGitRetimeArgs(args []string, now time.Time) (gitRetimeOptions, bool, e
if opts.StartDate, err = parseDate(v); err != nil {
return opts, false, err
}
case "--day-gap":
case "--end-date":
v, err := takeValue()
if err != nil {
return opts, false, err
}
n, err := strconv.Atoi(v)
if err != nil || n < 1 {
return opts, false, fmt.Errorf("--day-gap 需要大于 0 的整数(天数),收到 %q", v)
}
opts.DayGap = n
case "--per-day":
v, err := takeValue()
if err != nil {
if opts.EndDate, err = parseDate(v); err != nil {
return opts, false, err
}
n, err := strconv.Atoi(v)
if err != nil || n < 1 {
return opts, false, fmt.Errorf("--per-day 需要大于 0 的整数(每天提交数),收到 %q", v)
}
opts.PerDay = n
default:
return opts, false, fmt.Errorf("未知选项 %q", arg)
}
@@ -437,16 +422,16 @@ func parseGitRetimeArgs(args []string, now time.Time) (gitRetimeOptions, bool, e
if opts.MaxGap <= opts.MinGap {
return opts, false, fmt.Errorf("间隔上限必须大于下限")
}
// --day-gap / --per-day only make sense together with --start-date.
// --end-date only makes sense together with --start-date, and must not
// end before it starts.
if opts.StartDate.IsZero() {
if opts.DayGap > 0 || opts.PerDay > 0 {
return opts, false, fmt.Errorf("--day-gap 与 --per-day 需要配合 --start-date 使用" +
if !opts.EndDate.IsZero() {
return opts, false, fmt.Errorf("--end-date 需要配合 --start-date 使用" +
"(不给 --start-date 时保留每个提交的原日期)")
}
opts.DayGap = 1
} else if opts.DayGap < 1 {
// Re-dating defaults to consecutive days.
opts.DayGap = 1
} else if !opts.EndDate.IsZero() && opts.EndDate.Before(opts.StartDate) {
return opts, false, fmt.Errorf("--end-date %s 早于 --start-date %s",
opts.EndDate.Format("2006-01-02"), opts.StartDate.Format("2006-01-02"))
}
return opts, confirmed, nil
}