ace-git 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.ace-defaults/git/config.yml +83 -0
- data/.ace-defaults/nav/protocols/guide-sources/ace-git.yml +10 -0
- data/.ace-defaults/nav/protocols/tmpl-sources/ace-git.yml +19 -0
- data/.ace-defaults/nav/protocols/wfi-sources/ace-git.yml +19 -0
- data/CHANGELOG.md +762 -0
- data/LICENSE +21 -0
- data/README.md +48 -0
- data/Rakefile +14 -0
- data/docs/demo/ace-git-getting-started.gif +0 -0
- data/docs/demo/ace-git-getting-started.tape.yml +18 -0
- data/docs/demo/fixtures/README.md +3 -0
- data/docs/demo/fixtures/sample.txt +1 -0
- data/docs/getting-started.md +87 -0
- data/docs/handbook.md +50 -0
- data/docs/usage.md +259 -0
- data/exe/ace-git +37 -0
- data/handbook/guides/version-control/ruby.md +41 -0
- data/handbook/guides/version-control/rust.md +49 -0
- data/handbook/guides/version-control/typescript.md +47 -0
- data/handbook/guides/version-control-system-git.g.md +829 -0
- data/handbook/skills/as-git-rebase/SKILL.md +43 -0
- data/handbook/skills/as-git-reorganize-commits/SKILL.md +41 -0
- data/handbook/skills/as-github-pr-create/SKILL.md +60 -0
- data/handbook/skills/as-github-pr-update/SKILL.md +41 -0
- data/handbook/skills/as-github-release-publish/SKILL.md +58 -0
- data/handbook/templates/commit/squash.template.md +59 -0
- data/handbook/templates/pr/bugfix.template.md +103 -0
- data/handbook/templates/pr/default.template.md +40 -0
- data/handbook/templates/pr/feature.template.md +41 -0
- data/handbook/workflow-instructions/git/rebase.wf.md +402 -0
- data/handbook/workflow-instructions/git/reorganize-commits.wf.md +158 -0
- data/handbook/workflow-instructions/github/pr/create.wf.md +282 -0
- data/handbook/workflow-instructions/github/pr/update.wf.md +199 -0
- data/handbook/workflow-instructions/github/release-publish.wf.md +162 -0
- data/lib/ace/git/atoms/command_executor.rb +253 -0
- data/lib/ace/git/atoms/date_resolver.rb +129 -0
- data/lib/ace/git/atoms/diff_numstat_parser.rb +82 -0
- data/lib/ace/git/atoms/diff_parser.rb +110 -0
- data/lib/ace/git/atoms/file_grouper.rb +152 -0
- data/lib/ace/git/atoms/git_scope_filter.rb +86 -0
- data/lib/ace/git/atoms/git_status_fetcher.rb +29 -0
- data/lib/ace/git/atoms/grouped_stats_formatter.rb +233 -0
- data/lib/ace/git/atoms/lock_error_detector.rb +79 -0
- data/lib/ace/git/atoms/pattern_filter.rb +156 -0
- data/lib/ace/git/atoms/pr_identifier_parser.rb +88 -0
- data/lib/ace/git/atoms/repository_checker.rb +97 -0
- data/lib/ace/git/atoms/repository_state_detector.rb +92 -0
- data/lib/ace/git/atoms/stale_lock_cleaner.rb +247 -0
- data/lib/ace/git/atoms/status_formatter.rb +180 -0
- data/lib/ace/git/atoms/task_pattern_extractor.rb +57 -0
- data/lib/ace/git/atoms/time_formatter.rb +84 -0
- data/lib/ace/git/cli/commands/branch.rb +62 -0
- data/lib/ace/git/cli/commands/diff.rb +252 -0
- data/lib/ace/git/cli/commands/pr.rb +119 -0
- data/lib/ace/git/cli/commands/status.rb +84 -0
- data/lib/ace/git/cli.rb +87 -0
- data/lib/ace/git/models/diff_config.rb +185 -0
- data/lib/ace/git/models/diff_result.rb +94 -0
- data/lib/ace/git/models/repo_status.rb +202 -0
- data/lib/ace/git/molecules/branch_reader.rb +92 -0
- data/lib/ace/git/molecules/config_loader.rb +108 -0
- data/lib/ace/git/molecules/diff_filter.rb +102 -0
- data/lib/ace/git/molecules/diff_generator.rb +160 -0
- data/lib/ace/git/molecules/git_status_fetcher.rb +32 -0
- data/lib/ace/git/molecules/pr_metadata_fetcher.rb +286 -0
- data/lib/ace/git/molecules/recent_commits_fetcher.rb +53 -0
- data/lib/ace/git/organisms/diff_orchestrator.rb +178 -0
- data/lib/ace/git/organisms/repo_status_loader.rb +264 -0
- data/lib/ace/git/version.rb +7 -0
- data/lib/ace/git.rb +230 -0
- metadata +201 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
---
|
|
2
|
+
doc-type: workflow
|
|
3
|
+
title: Rebase Workflow
|
|
4
|
+
purpose: Safe rebase workflow with state capture, explicit conflict triage, and verification
|
|
5
|
+
ace-docs:
|
|
6
|
+
last-updated: 2026-03-12
|
|
7
|
+
last-checked: 2026-03-21
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Rebase Workflow
|
|
11
|
+
|
|
12
|
+
## Purpose
|
|
13
|
+
|
|
14
|
+
Rebase feature branches against a target branch with automatic state capture for recovery and verification. The workflow attempts simple rebase first, keeps localized conflicts on the normal rebase path, and escalates to cherry-pick only when per-commit replay is the safer tool.
|
|
15
|
+
|
|
16
|
+
## Requirements
|
|
17
|
+
|
|
18
|
+
- **Bash 4+**: Required for array syntax (macOS: install via Homebrew)
|
|
19
|
+
- **ace-b36ts**: For generating compact session IDs
|
|
20
|
+
|
|
21
|
+
## Strategies
|
|
22
|
+
|
|
23
|
+
| Strategy | Trigger | Description |
|
|
24
|
+
|----------|---------|-------------|
|
|
25
|
+
| **simple** (DEFAULT) | No conflicts | Standard `git rebase` - preserves exact commit history |
|
|
26
|
+
| **continue-first** | Small or localized conflict set | Resolve the conflict and continue the existing rebase |
|
|
27
|
+
| **cherry-pick** (ESCALATION) | Repeated conflicts, large conflict set, or explicit request | Per-commit replay from cached commit list |
|
|
28
|
+
|
|
29
|
+
> For interactive history editing, see [Appendix: Alternative Strategies](#appendix-alternative-strategies).
|
|
30
|
+
|
|
31
|
+
## Variables & Helpers
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Variables
|
|
35
|
+
# $target_branch: Branch to rebase against (default: auto-detect or origin/main)
|
|
36
|
+
# $no_verify: Skip post-rebase verification (default: false)
|
|
37
|
+
|
|
38
|
+
# Helper: Auto-detect target branch from task spec or default to origin/main
|
|
39
|
+
get_target_branch() {
|
|
40
|
+
local task_file
|
|
41
|
+
task_file=$(ls _current/*.s.md 2>/dev/null | head -1)
|
|
42
|
+
if [ -n "$task_file" ] && [ -f "$task_file" ] && [ -r "$task_file" ]; then
|
|
43
|
+
ruby -ryaml -e '
|
|
44
|
+
c = File.read(ARGV[0])
|
|
45
|
+
if c.start_with?("---")
|
|
46
|
+
yaml_content = c.split("---", 3)[1]
|
|
47
|
+
if yaml_content
|
|
48
|
+
data = YAML.safe_load(yaml_content, permitted_classes: [Symbol])
|
|
49
|
+
puts data.dig("worktree", "target_branch") || "origin/main"
|
|
50
|
+
else
|
|
51
|
+
puts "origin/main"
|
|
52
|
+
end
|
|
53
|
+
else
|
|
54
|
+
puts "origin/main"
|
|
55
|
+
end
|
|
56
|
+
' "$task_file" 2>/dev/null || echo "origin/main"
|
|
57
|
+
else
|
|
58
|
+
echo "origin/main"
|
|
59
|
+
fi
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Helper: Recover session variables (for multi-shell workflows)
|
|
63
|
+
recover_session() {
|
|
64
|
+
if [ -n "$cache_dir" ]; then return; fi
|
|
65
|
+
sessions=($(ls -td .ace-local/git/*-rebase 2>/dev/null))
|
|
66
|
+
if [ ${#sessions[@]} -eq 0 ]; then
|
|
67
|
+
echo "ERROR: No rebase sessions found"; exit 1
|
|
68
|
+
elif [ ${#sessions[@]} -eq 1 ]; then
|
|
69
|
+
cache_dir="${sessions[0]}"
|
|
70
|
+
else
|
|
71
|
+
echo "Multiple sessions found:"
|
|
72
|
+
for i in "${!sessions[@]}"; do
|
|
73
|
+
echo " [$i] $(basename ${sessions[$i]} | sed 's/-rebase$//')"
|
|
74
|
+
done
|
|
75
|
+
read -p "Select (Enter for most recent): " choice
|
|
76
|
+
if [ -z "$choice" ]; then
|
|
77
|
+
cache_dir="${sessions[0]}"
|
|
78
|
+
elif echo "$choice" | grep -qE '^[0-9]+$' && [ "$choice" -lt ${#sessions[@]} ]; then
|
|
79
|
+
cache_dir="${sessions[$choice]}"
|
|
80
|
+
else
|
|
81
|
+
echo "ERROR: Invalid selection"; exit 1
|
|
82
|
+
fi
|
|
83
|
+
fi
|
|
84
|
+
session_id=$(basename "$cache_dir" | sed 's/-rebase$//')
|
|
85
|
+
target_branch=$(grep "target_branch:" "$cache_dir/metadata.yml" | cut -d' ' -f2)
|
|
86
|
+
export cache_dir session_id target_branch
|
|
87
|
+
echo "Recovered session: $session_id"
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Session Continuity:** If continuing in a new shell, call `recover_session` before any phase. Variables `cache_dir`, `session_id`, and `target_branch` are exported for subprocess visibility.
|
|
92
|
+
|
|
93
|
+
**Conflict Policy:** The first conflict stays on the normal rebase path unless the conflict set is clearly large or the operator explicitly wants per-commit replay. Cherry-pick fallback is the escalation path, not the default response to every conflict.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Phase 1: State Capture
|
|
98
|
+
|
|
99
|
+
### 1.1 Initialize Session
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# Set target branch
|
|
103
|
+
target_branch="${target_branch:-$(get_target_branch)}"
|
|
104
|
+
|
|
105
|
+
# Generate session ID and cache directory
|
|
106
|
+
session_id=$(ace-b36ts encode now)
|
|
107
|
+
cache_dir=".ace-local/git/${session_id}-rebase"
|
|
108
|
+
mkdir -p "$cache_dir"
|
|
109
|
+
export cache_dir session_id target_branch
|
|
110
|
+
|
|
111
|
+
echo "Session: $session_id | Target: $target_branch | Cache: $cache_dir"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 1.2 Capture Pre-Rebase State
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
git fetch origin
|
|
118
|
+
merge_base=$(git merge-base HEAD "$target_branch")
|
|
119
|
+
|
|
120
|
+
# Metadata
|
|
121
|
+
cat > "$cache_dir/metadata.yml" << EOF
|
|
122
|
+
session_id: $session_id
|
|
123
|
+
target_branch: $target_branch
|
|
124
|
+
source_branch: $(git branch --show-current)
|
|
125
|
+
source_head: $(git rev-parse HEAD)
|
|
126
|
+
merge_base: $merge_base
|
|
127
|
+
started_at: $(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
128
|
+
EOF
|
|
129
|
+
|
|
130
|
+
# Commit list (oldest first for cherry-pick replay)
|
|
131
|
+
git log --reverse --oneline "$merge_base"..HEAD > "$cache_dir/commits.txt"
|
|
132
|
+
: > "$cache_dir/applied-shas.txt"
|
|
133
|
+
|
|
134
|
+
# Diffs for recovery and verification
|
|
135
|
+
git diff "$merge_base"..HEAD > "$cache_dir/pre-rebase.diff"
|
|
136
|
+
git diff --stat "$merge_base"..HEAD > "$cache_dir/pre-rebase.stats"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 1.3 Validate State
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
# Check working directory is clean
|
|
143
|
+
if ! git diff --quiet || ! git diff --cached --quiet; then
|
|
144
|
+
echo "ERROR: Working directory not clean. Commit or stash changes first."
|
|
145
|
+
exit 1
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
# Warn about untracked files
|
|
149
|
+
untracked=$(git status --porcelain=v1 | grep '^[??]' | wc -l | tr -d ' ')
|
|
150
|
+
if [ "$untracked" -gt 0 ]; then
|
|
151
|
+
echo "WARNING: $untracked untracked file(s) may be deleted during rebase cleanup."
|
|
152
|
+
git status --porcelain=v1 | grep '^[??]'
|
|
153
|
+
read -p "Press Enter to continue (or Ctrl+C to abort): "
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
# Check commits exist
|
|
157
|
+
commit_count=$(wc -l < "$cache_dir/commits.txt" | tr -d ' ')
|
|
158
|
+
if [ "$commit_count" -eq 0 ]; then
|
|
159
|
+
echo "Already up to date with $target_branch"; exit 0
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
echo "Commits to rebase: $commit_count"
|
|
163
|
+
cat "$cache_dir/commits.txt"
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Phase 2: Simple Rebase
|
|
169
|
+
|
|
170
|
+
### 2.1 Attempt Rebase
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
echo "Attempting simple rebase onto $target_branch..."
|
|
174
|
+
git rebase "$target_branch"
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### 2.2 Triage First Conflict
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
# If conflicts detected, inspect them before changing strategies
|
|
181
|
+
if [ -d ".git/rebase-merge" ] || [ -d ".git/rebase-apply" ]; then
|
|
182
|
+
conflicted_files=($(git diff --name-only --diff-filter=U))
|
|
183
|
+
conflict_count=${#conflicted_files[@]}
|
|
184
|
+
current_commit=$(git rev-parse --short REBASE_HEAD 2>/dev/null || echo "unknown")
|
|
185
|
+
|
|
186
|
+
echo "Conflict detected in rebase commit: $current_commit"
|
|
187
|
+
printf ' %s\n' "${conflicted_files[@]}"
|
|
188
|
+
|
|
189
|
+
if [ "$conflict_count" -le 2 ]; then
|
|
190
|
+
echo "Strategy: continue-first"
|
|
191
|
+
echo "Resolve conflicts, then run: git add <files> && git rebase --continue"
|
|
192
|
+
echo "Escalate only if the next rebase stop is also conflicted or resolution coordination becomes complex."
|
|
193
|
+
echo "CHANGELOG.md note: prefer git checkout --theirs CHANGELOG.md, then re-apply your entries on top before git add."
|
|
194
|
+
else
|
|
195
|
+
echo "Strategy: cherry-pick fallback (conflict set is large)"
|
|
196
|
+
echo "Abort the rebase and proceed to Phase 3."
|
|
197
|
+
fi
|
|
198
|
+
else
|
|
199
|
+
echo "Simple rebase complete - proceed to Phase 4"
|
|
200
|
+
fi
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### 2.3 Escalate to Cherry-Pick Only When Needed
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
# Use this only after triage says the conflict set is large, repeated, or explicitly requires per-commit replay
|
|
207
|
+
git rebase --abort || rm -rf .git/rebase-merge .git/rebase-apply
|
|
208
|
+
git reset --hard HEAD
|
|
209
|
+
echo "WARNING: About to delete untracked files."
|
|
210
|
+
read -p "Press Enter to continue with git clean -fd (or Ctrl+C): "
|
|
211
|
+
git clean -fd
|
|
212
|
+
# Proceed to Phase 3
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Phase 3: Cherry-Pick Fallback
|
|
218
|
+
|
|
219
|
+
Use this path only after the first-conflict triage says normal rebase is no longer the right tool.
|
|
220
|
+
|
|
221
|
+
### 3.1 Setup Work Branch
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
recover_session # Ensure session variables are set
|
|
225
|
+
|
|
226
|
+
original_branch=$(git branch --show-current)
|
|
227
|
+
git branch -m "$original_branch" "${original_branch}-backup-${session_id}"
|
|
228
|
+
git checkout --no-track -B "${original_branch}-rebase-${session_id}" "$target_branch"
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### 3.2 Apply Commits
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
while IFS=' ' read -r sha msg; do
|
|
235
|
+
# Skip commits already recorded in this replay session
|
|
236
|
+
if grep -qxF "$sha" "$cache_dir/applied-shas.txt"; then
|
|
237
|
+
echo "Skipping (already applied): $sha"
|
|
238
|
+
continue
|
|
239
|
+
fi
|
|
240
|
+
|
|
241
|
+
echo "Applying: $sha $msg"
|
|
242
|
+
if ! git cherry-pick "$sha"; then
|
|
243
|
+
echo ""
|
|
244
|
+
echo "CONFLICT in: $sha - $msg"
|
|
245
|
+
echo ""
|
|
246
|
+
echo "To resolve:"
|
|
247
|
+
echo " 1. Fix conflicts, then: git add <files> && git cherry-pick --continue"
|
|
248
|
+
echo " 2. Record the replayed SHA after continue: echo $sha >> $cache_dir/applied-shas.txt"
|
|
249
|
+
echo " 3. Re-run Phase 3.2 (already-applied SHAs will be skipped)"
|
|
250
|
+
echo ""
|
|
251
|
+
echo "To skip: git cherry-pick --skip"
|
|
252
|
+
echo "To abort: git cherry-pick --abort && git checkout ${original_branch}-backup-${session_id}"
|
|
253
|
+
exit 1
|
|
254
|
+
fi
|
|
255
|
+
|
|
256
|
+
echo "$sha" >> "$cache_dir/applied-shas.txt"
|
|
257
|
+
done < "$cache_dir/commits.txt"
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### 3.3 Finalize
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
git branch -m "${original_branch}-rebase-${session_id}" "$original_branch"
|
|
264
|
+
# Restore tracking — git branch -m drops upstream config
|
|
265
|
+
git branch --set-upstream-to="origin/${original_branch}" "$original_branch"
|
|
266
|
+
echo "Cherry-pick complete. Backup at: ${original_branch}-backup-${session_id}"
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Phase 4: Verification
|
|
272
|
+
|
|
273
|
+
### 4.1 Generate Post-Rebase Stats
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
recover_session # Ensure session variables are set
|
|
277
|
+
|
|
278
|
+
if [ "$no_verify" = "true" ]; then
|
|
279
|
+
echo "Verification skipped (--no-verify)"
|
|
280
|
+
else
|
|
281
|
+
new_merge_base=$(git merge-base HEAD "$target_branch")
|
|
282
|
+
git diff --stat "$new_merge_base"..HEAD > "$cache_dir/post-rebase.stats"
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### 4.2 Compare & Report
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
echo "=== Verification Report ==="
|
|
289
|
+
echo "Pre: $(tail -1 "$cache_dir/pre-rebase.stats")"
|
|
290
|
+
echo "Post: $(tail -1 "$cache_dir/post-rebase.stats")"
|
|
291
|
+
|
|
292
|
+
# Compare commit counts (primary check)
|
|
293
|
+
pre_commits=$(wc -l < "$cache_dir/commits.txt" | tr -d ' ')
|
|
294
|
+
post_commits=$(git log --oneline "$new_merge_base"..HEAD | wc -l | tr -d ' ')
|
|
295
|
+
|
|
296
|
+
if [ "$pre_commits" = "$post_commits" ]; then
|
|
297
|
+
echo "Commits: MATCH ($pre_commits)"
|
|
298
|
+
else
|
|
299
|
+
echo "Commits: MISMATCH (pre: $pre_commits, post: $post_commits)"
|
|
300
|
+
fi
|
|
301
|
+
|
|
302
|
+
# Compare file counts (secondary check)
|
|
303
|
+
pre_files=$(grep -cE "^diff --git" "$cache_dir/pre-rebase.diff" 2>/dev/null || echo 0)
|
|
304
|
+
post_files=$(git diff --name-only "$new_merge_base"..HEAD | wc -l | tr -d ' ')
|
|
305
|
+
echo "Files: pre=$pre_files, post=$post_files"
|
|
306
|
+
fi
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## Phase 5: Push Changes
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
# Run tests before pushing
|
|
315
|
+
if ! ace-test; then
|
|
316
|
+
echo "ERROR: Tests failed. Fix before pushing."
|
|
317
|
+
exit 1
|
|
318
|
+
fi
|
|
319
|
+
|
|
320
|
+
# Force push and set upstream tracking (-u ensures branch tracks remote after rename)
|
|
321
|
+
git push --force-with-lease -u origin "$(git branch --show-current)"
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## Recovery
|
|
327
|
+
|
|
328
|
+
### Using Cached Data
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
# Find session
|
|
332
|
+
ls -td .ace-local/git/*-rebase/ | head -5
|
|
333
|
+
|
|
334
|
+
# Set variables
|
|
335
|
+
cache_dir=$(ls -td .ace-local/git/*-rebase 2>/dev/null | head -1)
|
|
336
|
+
cat "$cache_dir/metadata.yml"
|
|
337
|
+
|
|
338
|
+
# Reset to original HEAD
|
|
339
|
+
original_head=$(grep "source_head:" "$cache_dir/metadata.yml" | cut -d' ' -f2)
|
|
340
|
+
git reset --hard "$original_head"
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Cache Cleanup
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
# Remove specific session
|
|
347
|
+
rm -rf "$cache_dir"
|
|
348
|
+
|
|
349
|
+
# Clean sessions older than 7 days
|
|
350
|
+
find .ace-local/git -name "*-rebase" -type d -mtime +7 -exec rm -rf {} +
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
## Edge Cases
|
|
356
|
+
|
|
357
|
+
| Case | Detection | Handling |
|
|
358
|
+
|------|-----------|----------|
|
|
359
|
+
| Already up to date | `commit_count -eq 0` | Exit cleanly in Phase 1.3 |
|
|
360
|
+
| Single commit | `commit_count -eq 1` | Simple rebase (no cherry-pick benefit) |
|
|
361
|
+
| Rebase in progress | `.git/rebase-merge` exists | Prompt: `--continue` or `--abort` |
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## Appendix: Alternative Strategies
|
|
366
|
+
|
|
367
|
+
### Manual Conflict Resolution
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
git rebase "$target_branch"
|
|
371
|
+
# On conflict: resolve files, git add, git rebase --continue
|
|
372
|
+
# Escalate to Phase 3 only if conflicts keep repeating or require per-commit replay
|
|
373
|
+
# CHANGELOG: git checkout --theirs CHANGELOG.md, add your entries on top
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Interactive Rebase
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
git rebase -i "$target_branch"
|
|
380
|
+
# pick/squash/reword/edit/drop commits in editor
|
|
381
|
+
# git rebase --continue after each stop
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## Success Criteria
|
|
387
|
+
|
|
388
|
+
- State captured to `.ace-local/git/{id}-rebase/`
|
|
389
|
+
- Branch rebased on target (simple, continue-first, or cherry-pick)
|
|
390
|
+
- Stats verified (or --no-verify)
|
|
391
|
+
- Tests pass
|
|
392
|
+
- Pushed with --force-with-lease
|
|
393
|
+
|
|
394
|
+
## Response Template
|
|
395
|
+
|
|
396
|
+
```
|
|
397
|
+
Session: {id} | Strategy: simple|continue-first|cherry-pick
|
|
398
|
+
Target: {branch} | Commits: {count}
|
|
399
|
+
Verification: {MATCH|MISMATCH} | Tests: {Pass|Fail}
|
|
400
|
+
Reason: {no-conflicts|localized-conflict|repeated-conflicts|large-conflict-set|user-request}
|
|
401
|
+
Status: {Complete|Needs attention}
|
|
402
|
+
```
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
---
|
|
2
|
+
doc-type: workflow
|
|
3
|
+
title: Reorganize Commits Workflow
|
|
4
|
+
purpose: simplified commit reorganization workflow
|
|
5
|
+
ace-docs:
|
|
6
|
+
last-updated: 2026-02-22
|
|
7
|
+
last-checked: 2026-03-21
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Reorganize Commits Workflow
|
|
11
|
+
|
|
12
|
+
Reorganize multiple commits into clean, logical commits.
|
|
13
|
+
|
|
14
|
+
## Key Principle
|
|
15
|
+
|
|
16
|
+
**Reorganize = Reorder, NOT Squash**
|
|
17
|
+
|
|
18
|
+
The goal is to reorder commits into logical groups while preserving per-scope granularity.
|
|
19
|
+
`ace-git-commit` creates one commit per configuration scope by design - this is expected behavior, not a problem to fix.
|
|
20
|
+
|
|
21
|
+
| Term | Meaning |
|
|
22
|
+
|------|---------|
|
|
23
|
+
| Reorganize | Reorder commits into logical groups |
|
|
24
|
+
| Squash | Combine multiple commits into one (NOT the goal here) |
|
|
25
|
+
|
|
26
|
+
## Scope Determination
|
|
27
|
+
|
|
28
|
+
**If user provides explicit scope** (commit list, range, or PR reference):
|
|
29
|
+
- Use the user-provided scope directly
|
|
30
|
+
- Trust the user's intent - they know what they want to reorganize
|
|
31
|
+
- Do NOT second-guess with embedded status
|
|
32
|
+
|
|
33
|
+
**If no explicit scope provided**:
|
|
34
|
+
- Use embedded repository status as the default
|
|
35
|
+
- "ahead N" means N unpushed commits (reasonable default)
|
|
36
|
+
- If user seems to expect more commits, ASK before proceeding
|
|
37
|
+
|
|
38
|
+
**Mismatch Warning**: If embedded status shows fewer commits than user seems to expect (e.g., user provides a commit list longer than "ahead N"), STOP and clarify:
|
|
39
|
+
- Did user want to reorganize all PR commits (use merge-base)?
|
|
40
|
+
- Or just the unpushed ones (use embedded status)?
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Steps
|
|
45
|
+
|
|
46
|
+
### 1. Identify Base
|
|
47
|
+
|
|
48
|
+
Determine base commit using **Scope Determination** above.
|
|
49
|
+
|
|
50
|
+
**Common patterns**:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# From user-provided commit range (user said "reorganize abc123..HEAD")
|
|
54
|
+
base=abc123
|
|
55
|
+
|
|
56
|
+
# From merge-base for full PR (user wants all PR commits)
|
|
57
|
+
base=$(git merge-base HEAD origin/main)
|
|
58
|
+
|
|
59
|
+
# From embedded status (default: unpushed commits only)
|
|
60
|
+
# If status shows "ahead 5", use HEAD~5
|
|
61
|
+
base=HEAD~5
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
> ⚠️ **When in doubt**: If user provides explicit commits or range, use that. Otherwise use embedded status but verify it matches user's expectations.
|
|
65
|
+
|
|
66
|
+
### 2. Identify Commit Intentions
|
|
67
|
+
|
|
68
|
+
Read all commit messages (only the messages) to understand what changes will be reorganized:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
git log $base..HEAD --format="%s"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Based on the commit messages, define the logical intention(s) that will be used in step 4. This helps `ace-git-commit` group changes correctly.
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
### 3. Reset
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
git reset --soft $base
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 4. Create Logical Commits
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
ace-git-commit -i "brief intention"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**IMPORTANT:** Do NOT specify file paths. Let the tool group by scope automatically.
|
|
90
|
+
|
|
91
|
+
`ace-git-commit` handles grouping and messages automatically.
|
|
92
|
+
|
|
93
|
+
**Expected Output**: Multiple commits, one per scope:
|
|
94
|
+
```
|
|
95
|
+
[1/5] Committing ace-foo changes...
|
|
96
|
+
abc1234 feat(ace-foo): Implement feature X
|
|
97
|
+
[2/5] Committing ace-bar changes...
|
|
98
|
+
def5678 test(ace-bar): Update tests for feature X
|
|
99
|
+
...
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
This is correct behavior - do NOT try to combine these into fewer commits.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Recovery
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
git reflog
|
|
110
|
+
git reset --hard HEAD@{n}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Manual Override (almost never needed)
|
|
116
|
+
|
|
117
|
+
**Before using manual override, verify**:
|
|
118
|
+
- Did you try adjusting the intention? (usually fixes grouping issues)
|
|
119
|
+
- Are you trying to squash commits? (that's not the goal - stop)
|
|
120
|
+
- Is the auto-grouping actually wrong, or just different from expectation?
|
|
121
|
+
|
|
122
|
+
Only use if `ace-git-commit` groups files incorrectly AND adjusting the intention doesn't help:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
git reset --soft $base && git reset HEAD
|
|
126
|
+
ace-git-commit <paths> -i "group 1"
|
|
127
|
+
ace-git-commit <paths> -i "group 2"
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Examples
|
|
133
|
+
|
|
134
|
+
### User Provides Explicit Scope
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
User: "Reorganize these commits: abc, def, ghi, jkl..."
|
|
138
|
+
→ Use merge-base to include all listed commits
|
|
139
|
+
→ Do NOT use "ahead 5" from embedded status
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### No Explicit Scope (Use Default)
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
User: "Reorganize commits"
|
|
146
|
+
Embedded status: "ahead 5"
|
|
147
|
+
→ Use HEAD~5 (unpushed commits)
|
|
148
|
+
→ If user expected more, they would have specified
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Mismatch - ASK Before Proceeding
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
User: "Here are the 12 commits to reorganize: ..."
|
|
155
|
+
Embedded status: "ahead 5"
|
|
156
|
+
→ User provided 12, status shows 5
|
|
157
|
+
→ ASK: "Do you want all 12 commits (from main) or just the 5 unpushed?"
|
|
158
|
+
```
|