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:
@@ -22,10 +22,15 @@ kopsc git retime 2026-10-01 2026-10-15 --interval 10-25 # 间隔改为 10-2
|
|||||||
kopsc git retime 2026-10-01 2026-10-15 --window 20:00-22:30 # 换一个时间段
|
kopsc git retime 2026-10-01 2026-10-15 --window 20:00-22:30 # 换一个时间段
|
||||||
kopsc git retime 2026-10-01 2026-10-15 --seed 42 # 固定随机种子,结果可复现
|
kopsc git retime 2026-10-01 2026-10-15 --seed 42 # 固定随机种子,结果可复现
|
||||||
kopsc git retime 2026-10-01 2026-10-15 --no-push --yes # 只改本地,不推送
|
kopsc git retime 2026-10-01 2026-10-15 --no-push --yes # 只改本地,不推送
|
||||||
|
|
||||||
|
# 连日期一起重排:从 2026-11-01 起,每天 2 个提交,日期间隔 3 天
|
||||||
|
kopsc git retime 2026-10-01 2026-10-15 --start-date 2026-11-01 --per-day 2 --day-gap 3
|
||||||
```
|
```
|
||||||
|
|
||||||
### 时间怎么分配
|
### 时间怎么分配
|
||||||
|
|
||||||
|
默认**保留每个提交的原日期**,只改时分秒;加 `--start-date` 才会重排日期(见下)。
|
||||||
|
|
||||||
- 每个提交落在**自己原来的日期**上,日期不变。
|
- 每个提交落在**自己原来的日期**上,日期不变。
|
||||||
- 同一天内的提交按原顺序**严格递增**。
|
- 同一天内的提交按原顺序**严格递增**。
|
||||||
- 相邻提交间隔是 **5–40 分钟**的随机值,**秒位也随机**(如 `21:07:43` → `21:32:11`),
|
- 相邻提交间隔是 **5–40 分钟**的随机值,**秒位也随机**(如 `21:07:43` → `21:32:11`),
|
||||||
@@ -34,6 +39,30 @@ kopsc git retime 2026-10-01 2026-10-15 --no-push --yes # 只改本地,
|
|||||||
- 如果某天提交太多、21:00–23:00 放不下所有间隔,会**自动压紧为窗口内均匀分布**,
|
- 如果某天提交太多、21:00–23:00 放不下所有间隔,会**自动压紧为窗口内均匀分布**,
|
||||||
预览表中该行标注「⚠ 已压缩间隔」。
|
预览表中该行标注「⚠ 已压缩间隔」。
|
||||||
|
|
||||||
|
### 重排日期(--start-date)
|
||||||
|
|
||||||
|
加 `--start-date <日期>` 后,范围内的提交按原顺序**重新分配日期**,从该日开始:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kopsc git retime 2026-10-01 2026-10-15 --start-date 2026-11-01 # 每天一个,连续日期
|
||||||
|
kopsc git retime 2026-10-01 2026-10-15 --start-date 2026-11-01 --per-day 3 # 每天最多 3 个
|
||||||
|
kopsc git retime 2026-10-01 2026-10-15 --start-date 2026-11-01 --day-gap 2 # 日期间隔 2 天
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
窗口: 21:00-23:00(从 2026-11-15 起重排,每天 1 个,间隔 2 天)
|
||||||
|
|
||||||
|
提交 原时间 新时间 主题
|
||||||
|
f89cdd9 2026-10-01 01:00:00 2026-11-15 21:09:21 c1
|
||||||
|
e06e019 2026-10-01 02:00:00 2026-11-17 21:03:30 c2
|
||||||
|
99f29b6 2026-10-01 03:00:00 2026-11-19 21:03:20 c3
|
||||||
|
```
|
||||||
|
|
||||||
|
- 不给 `--per-day` 时**每天一个**提交;给了则一天最多放这么多个,放满换到下一个日期。
|
||||||
|
- `--day-gap` 是相邻两个提交日期间隔的天数,默认 1(连续日期)。
|
||||||
|
- 重排后每条提交仍落在该日的 21:00–23:00 内,同日多条严格递增。
|
||||||
|
- `--per-day` / `--day-gap` 必须配合 `--start-date`,否则报错提示。
|
||||||
|
|
||||||
### 预览示例
|
### 预览示例
|
||||||
|
|
||||||
```text
|
```text
|
||||||
@@ -77,6 +106,9 @@ git reset --hard refs/kopsc-backup/main-20261101-093000
|
|||||||
| `--seed N` | 随机种子,同一批提交用同一种子得到相同结果 |
|
| `--seed N` | 随机种子,同一批提交用同一种子得到相同结果 |
|
||||||
| `--remote <名称>` | 强推到哪个远程,默认取唯一远程 |
|
| `--remote <名称>` | 强推到哪个远程,默认取唯一远程 |
|
||||||
| `--branch <名称>` | 改写的分支,默认当前分支 |
|
| `--branch <名称>` | 改写的分支,默认当前分支 |
|
||||||
|
| `--start-date <YYYY-MM-DD>` | 重排日期:从该日起按顺序重新分配提交日期(默认保留原日期) |
|
||||||
|
| `--per-day N` | 重排时每天最多放 N 个提交,默认 1;需配合 `--start-date` |
|
||||||
|
| `--day-gap N` | 重排时相邻提交日期间隔 N 天,默认 1;需配合 `--start-date` |
|
||||||
| `--no-push` | 只改写本地,不推送 |
|
| `--no-push` | 只改写本地,不推送 |
|
||||||
| `-y`, `--yes` | 确认执行(没有它只预览) |
|
| `-y`, `--yes` | 确认执行(没有它只预览) |
|
||||||
|
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ kopsc java run --jdk 17 --print-flags app.jar # 只看最终 java 命令行
|
|||||||
|
|
||||||
kopsc git retime 2026-10-01 2026-10-15 # 预览:把该范围提交时间改到 21:00-23:00
|
kopsc git retime 2026-10-01 2026-10-15 # 预览:把该范围提交时间改到 21:00-23:00
|
||||||
kopsc git retime 2026-10-01 2026-10-15 --yes # 确认后改写并强推(会改写历史!)
|
kopsc git retime 2026-10-01 2026-10-15 --yes # 确认后改写并强推(会改写历史!)
|
||||||
|
kopsc git retime 2026-10-01 2026-10-15 --start-date 2026-11-01 --per-day 2 # 连日期一起重排
|
||||||
|
|
||||||
kopsc list # 全部命令及当前平台可用性
|
kopsc list # 全部命令及当前平台可用性
|
||||||
kopsc explain docker.clearsafe # 匹配过程、全部候选及各自被选中/拒绝的原因
|
kopsc explain docker.clearsafe # 匹配过程、全部候选及各自被选中/拒绝的原因
|
||||||
|
|||||||
Executable
+11
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# 构建 kopsc 并安装到 /usr/local/bin,供本机直接使用。
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
go build -o bin/kopsc ./cmd/kopsc
|
||||||
|
mv ./bin/kopsc /usr/local/bin
|
||||||
|
chmod +x /usr/local/bin/kopsc
|
||||||
|
|
||||||
|
echo "已安装: /usr/local/bin/kopsc"
|
||||||
@@ -32,6 +32,13 @@ type retimeOptions struct {
|
|||||||
// spacing found on the remote branch. A zero (or out-of-range) entry falls
|
// spacing found on the remote branch. A zero (or out-of-range) entry falls
|
||||||
// back to a random gap.
|
// back to a random gap.
|
||||||
Gaps []time.Duration
|
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
|
// 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
|
window := opts.WindowEnd - opts.WindowStart
|
||||||
out := make([]retime, 0, len(commits))
|
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.
|
// Group by calendar day, preserving order.
|
||||||
var dayCommits []gitCommit
|
var dayCommits []gitCommit
|
||||||
currentDay := time.Time{}
|
var day time.Time
|
||||||
flush := func() {
|
flush := func() {
|
||||||
if len(dayCommits) == 0 {
|
if len(dayCommits) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
out = append(out, planDay(dayCommits, opts, window)...)
|
out = append(out, planDay(day, dayCommits, opts, window)...)
|
||||||
dayCommits = nil
|
dayCommits = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range commits {
|
for _, c := range commits {
|
||||||
day := c.Date()
|
if redate {
|
||||||
if !currentDay.IsZero() && !day.Equal(currentDay) {
|
// 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()
|
flush()
|
||||||
}
|
}
|
||||||
currentDay = day
|
day = original
|
||||||
dayCommits = append(dayCommits, c)
|
dayCommits = append(dayCommits, c)
|
||||||
}
|
}
|
||||||
flush()
|
flush()
|
||||||
return out
|
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.
|
// 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))
|
out := make([]retime, 0, len(commits))
|
||||||
day := commits[0].Date()
|
|
||||||
|
|
||||||
offsets, squeezed := spreadWithin(day, len(commits), opts, window)
|
offsets, squeezed := spreadWithin(day, len(commits), opts, window)
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,12 @@ type gitRetimeOptions struct {
|
|||||||
Branch string
|
Branch string
|
||||||
NoPush bool
|
NoPush bool
|
||||||
MatchEmote bool // --interval match-remote
|
MatchEmote bool // --interval match-remote
|
||||||
|
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// runGitRetime rewrites the timestamps of commits in [From, To] so they land
|
// runGitRetime rewrites the timestamps of commits in [From, To] so they land
|
||||||
@@ -110,8 +116,18 @@ func writeRetimePreview(req BuiltinRequest, plan retimePlan, opts gitRetimeOptio
|
|||||||
fmt.Fprintf(w, "改写: %d 个提交(分支 %s,远程 %s)\n", len(plan.Commits), plan.Branch, plan.Remote)
|
fmt.Fprintf(w, "改写: %d 个提交(分支 %s,远程 %s)\n", len(plan.Commits), plan.Branch, plan.Remote)
|
||||||
fmt.Fprintf(w, "强推: %s\n", pushSummary(plan, opts))
|
fmt.Fprintf(w, "强推: %s\n", pushSummary(plan, opts))
|
||||||
fmt.Fprintf(w, "目标: %s\n", target)
|
fmt.Fprintf(w, "目标: %s\n", target)
|
||||||
fmt.Fprintf(w, "窗口: %s-%s(保留原日期)\n\n",
|
dateRule := "保留原日期"
|
||||||
clockOf(opts.Window), clockOf(opts.WindowEnd))
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "窗口: %s-%s(%s)\n\n",
|
||||||
|
clockOf(opts.Window), clockOf(opts.WindowEnd), dateRule)
|
||||||
|
|
||||||
rows := [][]string{{"提交", "原时间", "新时间", "主题"}}
|
rows := [][]string{{"提交", "原时间", "新时间", "主题"}}
|
||||||
for _, item := range plan.Commits {
|
for _, item := range plan.Commits {
|
||||||
@@ -122,13 +138,21 @@ func writeRetimePreview(req BuiltinRequest, plan retimePlan, opts gitRetimeOptio
|
|||||||
rows = append(rows, []string{
|
rows = append(rows, []string{
|
||||||
item.Short,
|
item.Short,
|
||||||
item.OldAuthor.Format("2006-01-02 15:04:05"),
|
item.OldAuthor.Format("2006-01-02 15:04:05"),
|
||||||
item.NewTime.Format("15:04:05") + mark,
|
newTimeCell(item, opts) + mark,
|
||||||
item.Subject,
|
item.Subject,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
writeDisplayColumns(w, rows)
|
writeDisplayColumns(w, rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newTimeCell shows the new time, with its date when dates are reassigned.
|
||||||
|
func newTimeCell(item retime, opts gitRetimeOptions) string {
|
||||||
|
if opts.StartDate.IsZero() {
|
||||||
|
return item.NewTime.Format("15:04:05")
|
||||||
|
}
|
||||||
|
return item.NewTime.Format("2006-01-02 15:04:05")
|
||||||
|
}
|
||||||
|
|
||||||
// pushSummary states whether the branch will be force pushed.
|
// pushSummary states whether the branch will be force pushed.
|
||||||
func pushSummary(plan retimePlan, opts gitRetimeOptions) string {
|
func pushSummary(plan retimePlan, opts gitRetimeOptions) string {
|
||||||
if opts.NoPush {
|
if opts.NoPush {
|
||||||
@@ -193,6 +217,9 @@ func collectRetimePlan(ctx context.Context, runner gitRunner, opts gitRetimeOpti
|
|||||||
MaxGap: opts.MaxGap,
|
MaxGap: opts.MaxGap,
|
||||||
FirstGapMax: 20 * time.Minute,
|
FirstGapMax: 20 * time.Minute,
|
||||||
Rand: newRetimeRand(opts.Seed),
|
Rand: newRetimeRand(opts.Seed),
|
||||||
|
StartDate: opts.StartDate,
|
||||||
|
DayGap: opts.DayGap,
|
||||||
|
PerDay: opts.PerDay,
|
||||||
}
|
}
|
||||||
_ = opts.MatchEmote
|
_ = opts.MatchEmote
|
||||||
return buildPlan(selected, plannerOpts, branch, remote), nil
|
return buildPlan(selected, plannerOpts, branch, remote), nil
|
||||||
@@ -353,6 +380,34 @@ func parseGitRetimeArgs(args []string, now time.Time) (gitRetimeOptions, bool, e
|
|||||||
opts.Branch = v
|
opts.Branch = v
|
||||||
case "--no-push":
|
case "--no-push":
|
||||||
opts.NoPush = true
|
opts.NoPush = true
|
||||||
|
case "--start-date":
|
||||||
|
v, err := takeValue()
|
||||||
|
if err != nil {
|
||||||
|
return opts, false, err
|
||||||
|
}
|
||||||
|
if opts.StartDate, err = parseDate(v); err != nil {
|
||||||
|
return opts, false, err
|
||||||
|
}
|
||||||
|
case "--day-gap":
|
||||||
|
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 {
|
||||||
|
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:
|
default:
|
||||||
return opts, false, fmt.Errorf("未知选项 %q", arg)
|
return opts, false, fmt.Errorf("未知选项 %q", arg)
|
||||||
}
|
}
|
||||||
@@ -382,6 +437,17 @@ func parseGitRetimeArgs(args []string, now time.Time) (gitRetimeOptions, bool, e
|
|||||||
if opts.MaxGap <= opts.MinGap {
|
if opts.MaxGap <= opts.MinGap {
|
||||||
return opts, false, fmt.Errorf("间隔上限必须大于下限")
|
return opts, false, fmt.Errorf("间隔上限必须大于下限")
|
||||||
}
|
}
|
||||||
|
// --day-gap / --per-day only make sense together with --start-date.
|
||||||
|
if opts.StartDate.IsZero() {
|
||||||
|
if opts.DayGap > 0 || opts.PerDay > 0 {
|
||||||
|
return opts, false, fmt.Errorf("--day-gap 与 --per-day 需要配合 --start-date 使用" +
|
||||||
|
"(不给 --start-date 时保留每个提交的原日期)")
|
||||||
|
}
|
||||||
|
opts.DayGap = 1
|
||||||
|
} else if opts.DayGap < 1 {
|
||||||
|
// Re-dating defaults to consecutive days.
|
||||||
|
opts.DayGap = 1
|
||||||
|
}
|
||||||
return opts, confirmed, nil
|
return opts, confirmed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -423,3 +423,47 @@ func fixtureResp(args ...string) gitResponse {
|
|||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseGitRetimeArgsRedate(t *testing.T) {
|
||||||
|
opts, _, err := parseGitRetimeArgs([]string{
|
||||||
|
"2026-10-01", "2026-10-15",
|
||||||
|
"--start-date", "2026-11-01",
|
||||||
|
"--per-day", "2",
|
||||||
|
"--day-gap", "3",
|
||||||
|
}, time.Now())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse: %v", err)
|
||||||
|
}
|
||||||
|
if opts.StartDate.Format("2006-01-02") != "2026-11-01" || opts.PerDay != 2 || opts.DayGap != 3 {
|
||||||
|
t.Fatalf("opts = %+v", opts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseGitRetimeArgsRedateValidation(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
args []string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{[]string{"2026-10-01", "2026-10-02", "--per-day", "2"}, "需要配合 --start-date"},
|
||||||
|
{[]string{"2026-10-01", "2026-10-02", "--day-gap", "2"}, "需要配合 --start-date"},
|
||||||
|
{[]string{"2026-10-01", "2026-10-02", "--start-date", "2026/11/01"}, "YYYY-MM-DD"},
|
||||||
|
{[]string{"2026-10-01", "2026-10-02", "--start-date", "2026-11-01", "--per-day", "0"}, "大于 0"},
|
||||||
|
{[]string{"2026-10-01", "2026-10-02", "--start-date", "2026-11-01", "--day-gap", "x"}, "大于 0"},
|
||||||
|
}
|
||||||
|
for _, tc := range tests {
|
||||||
|
_, _, err := parseGitRetimeArgs(tc.args, time.Now())
|
||||||
|
if err == nil || !strings.Contains(err.Error(), tc.want) {
|
||||||
|
t.Errorf("args %v: err = %v, want %q", tc.args, err, tc.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseGitRetimeArgsDefaultDayGap(t *testing.T) {
|
||||||
|
opts, _, err := parseGitRetimeArgs([]string{"2026-10-01", "2026-10-02", "--start-date", "2026-11-01"}, time.Now())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse: %v", err)
|
||||||
|
}
|
||||||
|
if opts.DayGap != 1 {
|
||||||
|
t.Errorf("DayGap = %d, want the default 1", opts.DayGap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -327,3 +327,87 @@ func TestBuildPlanListsEachSqueezedDayOnce(t *testing.T) {
|
|||||||
t.Errorf("squeezed day = %q", got)
|
t.Errorf("squeezed day = %q", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPlanRetimeRedateStartingFromDay(t *testing.T) {
|
||||||
|
// Three commits originally on 2026-10-01, re-dated from 2026-11-01 with one
|
||||||
|
// commit per day.
|
||||||
|
date := day(2026, 10, 1)
|
||||||
|
commits := []gitCommit{
|
||||||
|
commitAt("c1", date, 9, 0, 0),
|
||||||
|
commitAt("c2", date, 10, 0, 0),
|
||||||
|
commitAt("c3", date, 11, 0, 0),
|
||||||
|
}
|
||||||
|
opts := defaultRetimeOptions(3)
|
||||||
|
opts.StartDate = day(2026, 11, 1)
|
||||||
|
opts.PerDay = 1
|
||||||
|
opts.DayGap = 1
|
||||||
|
|
||||||
|
plan := planRetime(commits, opts)
|
||||||
|
if len(plan) != 3 {
|
||||||
|
t.Fatalf("plan has %d entries", len(plan))
|
||||||
|
}
|
||||||
|
for i, want := range []string{"2026-11-01", "2026-11-02", "2026-11-03"} {
|
||||||
|
got := plan[i].NewTime.In(cst).Format("2006-01-02")
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("commit %d re-dated to %s, want %s", i+1, got, want)
|
||||||
|
}
|
||||||
|
if h := plan[i].NewTime.In(cst).Hour(); h < 21 || h > 22 {
|
||||||
|
t.Errorf("commit %d at hour %d, want 21-22", i+1, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPlanRetimeRedateHonoursDayGap(t *testing.T) {
|
||||||
|
date := day(2026, 10, 1)
|
||||||
|
commits := []gitCommit{
|
||||||
|
commitAt("c1", date, 9, 0, 0),
|
||||||
|
commitAt("c2", date, 10, 0, 0),
|
||||||
|
}
|
||||||
|
opts := defaultRetimeOptions(4)
|
||||||
|
opts.StartDate = day(2026, 11, 1)
|
||||||
|
opts.PerDay = 1
|
||||||
|
opts.DayGap = 3
|
||||||
|
|
||||||
|
plan := planRetime(commits, opts)
|
||||||
|
if got := plan[1].NewTime.In(cst).Format("2006-01-02"); got != "2026-11-04" {
|
||||||
|
t.Fatalf("second commit on %s, want 2026-11-04 (gap 3 days)", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPlanRetimeRedatePacksSeveralPerDay(t *testing.T) {
|
||||||
|
date := day(2026, 10, 1)
|
||||||
|
var commits []gitCommit
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
commits = append(commits, commitAt("c", date, 9, i, 0))
|
||||||
|
}
|
||||||
|
opts := defaultRetimeOptions(5)
|
||||||
|
opts.StartDate = day(2026, 11, 1)
|
||||||
|
opts.PerDay = 2
|
||||||
|
opts.DayGap = 1
|
||||||
|
|
||||||
|
plan := planRetime(commits, opts)
|
||||||
|
firstDay := plan[0].NewTime.In(cst).Format("2006-01-02")
|
||||||
|
secondOfFirstDay := plan[1].NewTime.In(cst).Format("2006-01-02")
|
||||||
|
thirdDay := plan[2].NewTime.In(cst).Format("2006-01-02")
|
||||||
|
if firstDay != "2026-11-01" || secondOfFirstDay != "2026-11-01" {
|
||||||
|
t.Fatalf("first two commits should share 2026-11-01, got %s and %s", firstDay, secondOfFirstDay)
|
||||||
|
}
|
||||||
|
if thirdDay != "2026-11-02" {
|
||||||
|
t.Fatalf("third commit on %s, want 2026-11-02", thirdDay)
|
||||||
|
}
|
||||||
|
// Within a day the times stay ordered.
|
||||||
|
if !plan[1].NewTime.After(plan[0].NewTime) {
|
||||||
|
t.Fatalf("same-day commits are not ordered: %v, %v", plan[0].NewTime, plan[1].NewTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPlanRetimeWithoutStartDateKeepsOriginalDays(t *testing.T) {
|
||||||
|
commits := []gitCommit{
|
||||||
|
commitAt("c1", day(2026, 10, 1), 9, 0, 0),
|
||||||
|
commitAt("c2", day(2026, 10, 5), 9, 0, 0),
|
||||||
|
}
|
||||||
|
plan := planRetime(commits, defaultRetimeOptions(1))
|
||||||
|
if a, b := plan[0].NewTime.In(cst).Format("2006-01-02"), plan[1].NewTime.In(cst).Format("2006-01-02"); a != "2026-10-01" || b != "2026-10-05" {
|
||||||
|
t.Fatalf("original days changed: %s, %s", a, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -487,7 +487,8 @@ func Builtin() *Registry {
|
|||||||
Summary: "把指定日期范围的提交时间改到 21:00-23:00 并强推",
|
Summary: "把指定日期范围的提交时间改到 21:00-23:00 并强推",
|
||||||
Action: "读取本地分支在给定日期范围内(含两端)的提交,保留各自的原日期," +
|
Action: "读取本地分支在给定日期范围内(含两端)的提交,保留各自的原日期," +
|
||||||
"把时分秒重排到 21:00-23:00 之间:同一日期内相邻提交严格递增,间隔取 5-40 分钟的随机值(秒位随机)," +
|
"把时分秒重排到 21:00-23:00 之间:同一日期内相邻提交严格递增,间隔取 5-40 分钟的随机值(秒位随机)," +
|
||||||
"也可用 --interval match-remote 参照远程分支的间隔。随后重建这段历史并以 --force-with-lease 强推。" +
|
"也可用 --interval match-remote 参照远程分支的间隔;加 --start-date 时改为按顺序重排日期" +
|
||||||
|
"(--per-day 控制每天几个、--day-gap 控制日期间隔)。随后重建这段历史并以 --force-with-lease 强推。" +
|
||||||
"改动前自动创建 refs/kopsc-backup/<分支>-<时间戳> 备份引用,工作区有未提交改动时拒绝执行;" +
|
"改动前自动创建 refs/kopsc-backup/<分支>-<时间戳> 备份引用,工作区有未提交改动时拒绝执行;" +
|
||||||
"默认只打印预览表并以退出码 2 结束,必须显式加 --yes 才会改写并推送",
|
"默认只打印预览表并以退出码 2 结束,必须显式加 --yes 才会改写并推送",
|
||||||
Usage: "<开始日期> <结束日期> [选项]",
|
Usage: "<开始日期> <结束日期> [选项]",
|
||||||
|
|||||||
Reference in New Issue
Block a user