git-review-workflow 0.1.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.
- package/LICENSE +21 -0
- package/README.es.md +548 -0
- package/README.md +540 -0
- package/VERSION +1 -0
- package/bin/git-review +104 -0
- package/bin/git-review-lib.sh +121 -0
- package/bin/git-review-verbs/abort +101 -0
- package/bin/git-review-verbs/clean +123 -0
- package/bin/git-review-verbs/compare +159 -0
- package/bin/git-review-verbs/continue +178 -0
- package/bin/git-review-verbs/finish +494 -0
- package/bin/git-review-verbs/forget +289 -0
- package/bin/git-review-verbs/list +99 -0
- package/bin/git-review-verbs/next +46 -0
- package/bin/git-review-verbs/prev +45 -0
- package/bin/git-review-verbs/preview +195 -0
- package/bin/git-review-verbs/save +167 -0
- package/bin/git-review-verbs/start +333 -0
- package/bin/git-review-verbs/status +99 -0
- package/completions/git-review-workflow.bash +143 -0
- package/completions/git-review-workflow.fish +140 -0
- package/completions/git-review-workflow.zsh +153 -0
- package/package.json +39 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
#
|
|
3
|
+
# git review finish — extract your review edits from the current review/* branch.
|
|
4
|
+
#
|
|
5
|
+
# By default it creates review-fixes/<branch> on top of the PR tip with your
|
|
6
|
+
# edits staged. With --onto-source it stages your edits on the PR branch itself
|
|
7
|
+
# instead. Either way the result stays local; review and commit it yourself.
|
|
8
|
+
#
|
|
9
|
+
# --abort undoes the last finish and drops you back on review/<branch> exactly
|
|
10
|
+
# where you were editing, the same way git merge --abort backs out a merge. To
|
|
11
|
+
# make that possible, a fresh finish first records an undo point (the review
|
|
12
|
+
# branch's HEAD, index and working tree, à la ORIG_HEAD) before it mutates
|
|
13
|
+
# anything, so --abort is a verbatim restore regardless of whole/step mode.
|
|
14
|
+
#
|
|
15
|
+
set -eu
|
|
16
|
+
|
|
17
|
+
prog="git review finish"
|
|
18
|
+
|
|
19
|
+
usage() {
|
|
20
|
+
cat <<EOF
|
|
21
|
+
usage: $prog [--onto-source] [--resume | --abort [--force]]
|
|
22
|
+
|
|
23
|
+
Extract your review edits from the current review/* branch.
|
|
24
|
+
|
|
25
|
+
(default) create review-fixes/<branch> on top of the PR tip and stage your edits
|
|
26
|
+
--onto-source instead, stage your edits on the PR branch itself
|
|
27
|
+
--resume continue after resolving conflicts from a commit-by-commit replay
|
|
28
|
+
--abort undo the last finish and return to editing the review
|
|
29
|
+
--force with --abort, discard changes made to the finish branch since
|
|
30
|
+
EOF
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
onto=0
|
|
34
|
+
resume=0
|
|
35
|
+
abort=0
|
|
36
|
+
force=0
|
|
37
|
+
while [ $# -gt 0 ]; do
|
|
38
|
+
case "$1" in
|
|
39
|
+
-h | --h)
|
|
40
|
+
usage
|
|
41
|
+
exit 0
|
|
42
|
+
;;
|
|
43
|
+
--onto-source) onto=1 ;;
|
|
44
|
+
--resume) resume=1 ;;
|
|
45
|
+
--abort) abort=1 ;;
|
|
46
|
+
--force) force=1 ;;
|
|
47
|
+
*)
|
|
48
|
+
echo "error: unknown option $1" >&2
|
|
49
|
+
usage >&2
|
|
50
|
+
exit 1
|
|
51
|
+
;;
|
|
52
|
+
esac
|
|
53
|
+
shift
|
|
54
|
+
done
|
|
55
|
+
|
|
56
|
+
cur="$(git symbolic-ref --quiet --short HEAD || true)"
|
|
57
|
+
[ -n "$cur" ] || {
|
|
58
|
+
echo "error: not on a branch" >&2
|
|
59
|
+
exit 1
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# worktree_tree: the tree of the current working tree captured with `git add -A`
|
|
63
|
+
# (so brand-new untracked files count too), via a throwaway index that leaves the
|
|
64
|
+
# real index untouched. Used to snapshot state and to detect later divergence.
|
|
65
|
+
worktree_tree() {
|
|
66
|
+
_gd="$(git rev-parse --git-dir)"
|
|
67
|
+
_ti="$_gd/finish-wt-index.$$"
|
|
68
|
+
cp "$(git rev-parse --git-path index)" "$_ti"
|
|
69
|
+
GIT_INDEX_FILE="$_ti" git add -A
|
|
70
|
+
GIT_INDEX_FILE="$_ti" git write-tree
|
|
71
|
+
rm -f "$_ti"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# apply_review_patch FROM TO [git-apply-args...]
|
|
75
|
+
# Diff FROM..TO and apply it, routing the patch through a temp file rather than a
|
|
76
|
+
# shell variable. Capturing a binary patch with command substitution drops its
|
|
77
|
+
# NUL bytes and trailing newline, so git apply later rejects it ("corrupt binary
|
|
78
|
+
# patch") — which broke every review edit touching a binary file. An empty diff
|
|
79
|
+
# is a no-op success; the function returns git apply's exit status so callers can
|
|
80
|
+
# branch on conflicts.
|
|
81
|
+
apply_review_patch() {
|
|
82
|
+
_from="$1"
|
|
83
|
+
_to="$2"
|
|
84
|
+
shift 2
|
|
85
|
+
_pf="$(git rev-parse --git-dir)/finish-apply.$$"
|
|
86
|
+
git diff --binary "$_from" "$_to" >"$_pf"
|
|
87
|
+
if [ -s "$_pf" ]; then
|
|
88
|
+
if git apply "$@" <"$_pf"; then _rc=0; else _rc=$?; fi
|
|
89
|
+
else
|
|
90
|
+
_rc=0
|
|
91
|
+
fi
|
|
92
|
+
rm -f "$_pf"
|
|
93
|
+
return "$_rc"
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# record_exit: once a finish has produced its branch ($1), snapshot that branch's
|
|
97
|
+
# HEAD and working tree onto the review branch's config so a later --abort can tell
|
|
98
|
+
# whether you have changed it since — and tell you up front that abort will not
|
|
99
|
+
# silently throw that work away. Recorded only on a completed finish, never for one
|
|
100
|
+
# stopped mid-conflict.
|
|
101
|
+
record_exit() {
|
|
102
|
+
git config "branch.$cur.reviewundoouthead" "$(git rev-parse HEAD)"
|
|
103
|
+
git config "branch.$cur.reviewundoouttree" "$(worktree_tree)"
|
|
104
|
+
echo "note: commits you make on $1 are kept, and uncommitted edits too while you stay on $1 — git review finish --abort refuses to discard them without --force." >&2
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# --abort restores the undo point recorded by the finish being undone. It runs
|
|
108
|
+
# before the review/* check on purpose: after a finish you are on
|
|
109
|
+
# review-fixes/<branch> (or, with --onto-source, on the PR branch), never on
|
|
110
|
+
# review/<branch>.
|
|
111
|
+
do_abort() {
|
|
112
|
+
# Find the review branch the undo point belongs to from wherever we landed
|
|
113
|
+
# after the finish.
|
|
114
|
+
case "$cur" in
|
|
115
|
+
review-fixes/*) asrc="${cur#review-fixes/}" ;;
|
|
116
|
+
review/*) asrc="${cur#review/}" ;;
|
|
117
|
+
*) asrc="$cur" ;;
|
|
118
|
+
esac
|
|
119
|
+
|
|
120
|
+
rb="review/$asrc"
|
|
121
|
+
head="$(git config "branch.$rb.reviewundohead" || true)"
|
|
122
|
+
[ -n "$head" ] || {
|
|
123
|
+
echo "error: no finish to abort (no undo point for $rb)" >&2
|
|
124
|
+
exit 1
|
|
125
|
+
}
|
|
126
|
+
git rev-parse --verify --quiet "refs/heads/$rb" >/dev/null || {
|
|
127
|
+
echo "error: $rb is gone; cannot restore the review" >&2
|
|
128
|
+
exit 1
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# The pre-finish index and working tree live only in refs/review-undo/<src>/.
|
|
132
|
+
# If those refs were deleted by hand while the config undo record survived,
|
|
133
|
+
# there is nothing left to restore the review from. Resolve them up front, with
|
|
134
|
+
# --quiet, and bail with a clear pointer — otherwise rev-parse --verify dies
|
|
135
|
+
# below with an opaque "Needed a single revision", possibly only after
|
|
136
|
+
# reset --hard has already mutated the tree.
|
|
137
|
+
idxtree="$(git rev-parse --verify --quiet "refs/review-undo/$asrc/index^{tree}" || true)"
|
|
138
|
+
wttree="$(git rev-parse --verify --quiet "refs/review-undo/$asrc/worktree^{tree}" || true)"
|
|
139
|
+
if [ -z "$idxtree" ] || [ -z "$wttree" ]; then
|
|
140
|
+
echo "error: the undo snapshot for $rb is gone (refs/review-undo/$asrc deleted); cannot restore the review." >&2
|
|
141
|
+
echo "the leftover undo record is stale — run git review clean $asrc to clear it." >&2
|
|
142
|
+
exit 1
|
|
143
|
+
fi
|
|
144
|
+
|
|
145
|
+
kind="$(git config "branch.$rb.reviewundokind" || echo fixes)"
|
|
146
|
+
srccreated="$(git config "branch.$rb.reviewundosrccreated" || echo 0)"
|
|
147
|
+
editstep="$(git config "branch.$rb.reviewundoeditstep" || true)"
|
|
148
|
+
editold="$(git config "branch.$rb.reviewundoeditold" || true)"
|
|
149
|
+
|
|
150
|
+
# Guard: a finish leaves your edits on the branch it produced (review-fixes, or
|
|
151
|
+
# the PR branch with --onto-source). If you have since changed that branch — new
|
|
152
|
+
# commits, or an edited working tree — aborting would throw that work away. Like
|
|
153
|
+
# git branch -d, refuse and point at the work; --force tears it down anyway.
|
|
154
|
+
# Skipped when no exit state was recorded: a finish stopped mid-conflict never
|
|
155
|
+
# produced the branch, so --abort there discards the in-progress conflict
|
|
156
|
+
# resolution the way git rebase --abort does.
|
|
157
|
+
outhead="$(git config "branch.$rb.reviewundoouthead" || true)"
|
|
158
|
+
outtree="$(git config "branch.$rb.reviewundoouttree" || true)"
|
|
159
|
+
if [ "$force" -eq 0 ] && [ -n "$outhead" ]; then
|
|
160
|
+
if [ "$kind" = "onto-source" ]; then
|
|
161
|
+
outbranch="$asrc"
|
|
162
|
+
else
|
|
163
|
+
outbranch="review-fixes/$asrc"
|
|
164
|
+
fi
|
|
165
|
+
diverged=0
|
|
166
|
+
nowhead="$(git rev-parse --verify --quiet "refs/heads/$outbranch" || true)"
|
|
167
|
+
if [ -n "$nowhead" ] && [ "$nowhead" != "$outhead" ]; then
|
|
168
|
+
diverged=1
|
|
169
|
+
fi
|
|
170
|
+
# Only the working tree of the branch we are actually on is meaningful; if
|
|
171
|
+
# you switched away, fall back to the committed-divergence check above.
|
|
172
|
+
if [ "$diverged" -eq 0 ] && [ "$cur" = "$outbranch" ] && [ -n "$outtree" ] &&
|
|
173
|
+
[ "$(worktree_tree)" != "$outtree" ]; then
|
|
174
|
+
diverged=1
|
|
175
|
+
fi
|
|
176
|
+
if [ "$diverged" -eq 1 ]; then
|
|
177
|
+
echo "error: $outbranch has changes since the finish; aborting would discard them." >&2
|
|
178
|
+
echo "your work stays on $outbranch — switch there to keep it, or rerun with --force to discard." >&2
|
|
179
|
+
exit 1
|
|
180
|
+
fi
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
# Return to the review branch and restore the exact pre-finish state: HEAD,
|
|
184
|
+
# then the working tree (read-tree -m -u brings back additions, edits,
|
|
185
|
+
# deletions and the captured untracked files), then the index on top.
|
|
186
|
+
# When already on the review branch — e.g. aborting a finish that stopped
|
|
187
|
+
# mid-conflict — skip the checkout (the index may hold unmerged entries that
|
|
188
|
+
# a same-branch checkout chokes on); the reset --hard below clears them.
|
|
189
|
+
if [ "$cur" != "$rb" ]; then
|
|
190
|
+
git checkout -q -f "$rb"
|
|
191
|
+
fi
|
|
192
|
+
git reset -q --hard "$head"
|
|
193
|
+
git read-tree -m -u "$wttree"
|
|
194
|
+
git read-tree "$idxtree"
|
|
195
|
+
|
|
196
|
+
# Restore the current step's banked edit ref to what it was before finish
|
|
197
|
+
# banked it (or remove it if finish was the one that created it).
|
|
198
|
+
if [ -n "$editstep" ]; then
|
|
199
|
+
eref="refs/review-edits/$asrc/$editstep"
|
|
200
|
+
if [ "$editold" = "none" ] || [ -z "$editold" ]; then
|
|
201
|
+
git update-ref -d "$eref" 2>/dev/null || true
|
|
202
|
+
else
|
|
203
|
+
git update-ref "$eref" "$editold"
|
|
204
|
+
fi
|
|
205
|
+
fi
|
|
206
|
+
|
|
207
|
+
# Drop the branch finish created, leaving only the review/<branch> you are
|
|
208
|
+
# back on. --onto-source deletes the PR branch when finish created it; when it
|
|
209
|
+
# already existed, finish staged onto it without committing and left it at the
|
|
210
|
+
# reviewed tip ($outhead), so reset it back there — this also discards any
|
|
211
|
+
# commits made on it since, which is what reaching here (untouched, or --force)
|
|
212
|
+
# means. Skipped when no exit state was recorded (mid-conflict never touched
|
|
213
|
+
# the PR branch).
|
|
214
|
+
if [ "$kind" = "onto-source" ]; then
|
|
215
|
+
if [ "$srccreated" = "1" ] &&
|
|
216
|
+
git rev-parse --verify --quiet "refs/heads/$asrc" >/dev/null; then
|
|
217
|
+
git branch -D "$asrc" >/dev/null
|
|
218
|
+
elif [ -n "$outhead" ] &&
|
|
219
|
+
git rev-parse --verify --quiet "refs/heads/$asrc" >/dev/null; then
|
|
220
|
+
git update-ref "refs/heads/$asrc" "$outhead"
|
|
221
|
+
fi
|
|
222
|
+
elif git rev-parse --verify --quiet "refs/heads/review-fixes/$asrc" >/dev/null; then
|
|
223
|
+
git branch -D "review-fixes/$asrc" >/dev/null
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
# Clear a mid-conflict marker (finish may have been aborted part-way) and the
|
|
227
|
+
# whole undo record.
|
|
228
|
+
git config --unset "branch.$rb.reviewresume" 2>/dev/null || true
|
|
229
|
+
for k in reviewundohead reviewundokind reviewundosrccreated \
|
|
230
|
+
reviewundoeditstep reviewundoeditold reviewundoouthead reviewundoouttree; do
|
|
231
|
+
git config --unset "branch.$rb.$k" 2>/dev/null || true
|
|
232
|
+
done
|
|
233
|
+
git for-each-ref --format='%(refname)' "refs/review-undo/$asrc/" |
|
|
234
|
+
while read -r ref; do
|
|
235
|
+
git update-ref -d "$ref"
|
|
236
|
+
done
|
|
237
|
+
|
|
238
|
+
echo "aborted finish; back on $rb — keep editing, then run git review finish"
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if [ "$abort" -eq 1 ]; then
|
|
242
|
+
if [ "$resume" -eq 1 ] || [ "$onto" -eq 1 ]; then
|
|
243
|
+
echo "error: --abort takes no other options" >&2
|
|
244
|
+
exit 1
|
|
245
|
+
fi
|
|
246
|
+
do_abort
|
|
247
|
+
exit 0
|
|
248
|
+
fi
|
|
249
|
+
|
|
250
|
+
if [ "$force" -eq 1 ]; then
|
|
251
|
+
echo "error: --force only applies to --abort" >&2
|
|
252
|
+
exit 1
|
|
253
|
+
fi
|
|
254
|
+
|
|
255
|
+
case "$cur" in
|
|
256
|
+
review/*) ;;
|
|
257
|
+
*)
|
|
258
|
+
echo "error: not on a review/* branch (HEAD is $cur)" >&2
|
|
259
|
+
exit 1
|
|
260
|
+
;;
|
|
261
|
+
esac
|
|
262
|
+
|
|
263
|
+
src="$(git config "branch.$cur.reviewsource" || true)"
|
|
264
|
+
tip="$(git config "branch.$cur.reviewtip" || true)"
|
|
265
|
+
if [ -z "$src" ] || [ -z "$tip" ]; then
|
|
266
|
+
echo "error: missing review metadata; was $cur created with git review start?" >&2
|
|
267
|
+
exit 1
|
|
268
|
+
fi
|
|
269
|
+
|
|
270
|
+
# A compare review (git review compare) has no writable source branch — it only
|
|
271
|
+
# stages a diff to read — so there is nothing to extract. Refuse explicitly rather
|
|
272
|
+
# than producing an empty or meaningless review-fixes branch.
|
|
273
|
+
if [ "$(git config "branch.$cur.reviewreadonly" || true)" = "1" ]; then
|
|
274
|
+
echo "error: $cur is a read-only compare review; there is nothing to write back" >&2
|
|
275
|
+
echo "compare just stages a diff to read — run git review clean ${cur#review/} when done." >&2
|
|
276
|
+
exit 1
|
|
277
|
+
fi
|
|
278
|
+
|
|
279
|
+
# Shared helpers, sourced from the libexec directory the dispatcher points us at,
|
|
280
|
+
# so the step block below validates its metadata through load_step_review_meta —
|
|
281
|
+
# the same guard the other step commands use — instead of reading the keys raw.
|
|
282
|
+
# shellcheck source=../git-review-lib.sh
|
|
283
|
+
. "${GIT_REVIEW_LIBEXEC:?}/git-review-lib.sh"
|
|
284
|
+
|
|
285
|
+
# A step review records reviewmode=step alongside reviewstart/reviewcount/
|
|
286
|
+
# reviewstep; a whole review records none of those keys. If reviewmode was lost to
|
|
287
|
+
# a hand-edit but the step keys survive, we would fall through to the whole-mode
|
|
288
|
+
# tail below and diff the current step commit (not the tip) against the tip —
|
|
289
|
+
# silently reversing the author's later commits into the extracted fix. Report the
|
|
290
|
+
# inconsistency rather than producing a corrupt patch.
|
|
291
|
+
mode="$(git config "branch.$cur.reviewmode" || true)"
|
|
292
|
+
if [ "$mode" != "step" ]; then
|
|
293
|
+
for k in reviewstart reviewcount reviewstep; do
|
|
294
|
+
[ -z "$(git config "branch.$cur.$k" || true)" ] || {
|
|
295
|
+
echo "error: corrupt review metadata: $cur has step-review keys but reviewmode is not 'step'" >&2
|
|
296
|
+
exit 1
|
|
297
|
+
}
|
|
298
|
+
done
|
|
299
|
+
fi
|
|
300
|
+
|
|
301
|
+
# record_undo: snapshot everything finish is about to mutate so --abort can put
|
|
302
|
+
# it back verbatim — the review branch's HEAD, its index, and its working tree
|
|
303
|
+
# (captured via a throwaway index so brand-new untracked edits are included).
|
|
304
|
+
# The trees are pinned under refs/review-undo/<src>/ so gc cannot drop them.
|
|
305
|
+
record_undo() {
|
|
306
|
+
wttree="$(worktree_tree)"
|
|
307
|
+
idxtree="$(git write-tree)"
|
|
308
|
+
orig="$(git rev-parse HEAD)"
|
|
309
|
+
|
|
310
|
+
git update-ref "refs/review-undo/$src/head" "$orig"
|
|
311
|
+
git update-ref "refs/review-undo/$src/index" \
|
|
312
|
+
"$(git commit-tree "$idxtree" -m 'git review finish undo (index)')"
|
|
313
|
+
git update-ref "refs/review-undo/$src/worktree" \
|
|
314
|
+
"$(git commit-tree "$wttree" -m 'git review finish undo (worktree)')"
|
|
315
|
+
|
|
316
|
+
git config "branch.$cur.reviewundohead" "$orig"
|
|
317
|
+
if [ "$onto" -eq 1 ]; then
|
|
318
|
+
git config "branch.$cur.reviewundokind" "onto-source"
|
|
319
|
+
# Remember whether the PR branch already existed, so --abort only deletes
|
|
320
|
+
# it when finish was the one that created it.
|
|
321
|
+
if git rev-parse --verify --quiet "refs/heads/$src" >/dev/null; then
|
|
322
|
+
git config "branch.$cur.reviewundosrccreated" 0
|
|
323
|
+
else
|
|
324
|
+
git config "branch.$cur.reviewundosrccreated" 1
|
|
325
|
+
fi
|
|
326
|
+
else
|
|
327
|
+
git config "branch.$cur.reviewundokind" "fixes"
|
|
328
|
+
git config "branch.$cur.reviewundosrccreated" 0
|
|
329
|
+
fi
|
|
330
|
+
|
|
331
|
+
# In step mode finish overwrites the current step's banked edit ref; record
|
|
332
|
+
# its prior value (or "none") so --abort restores exactly that.
|
|
333
|
+
if [ "$mode" = "step" ]; then
|
|
334
|
+
ustep="$(git config "branch.$cur.reviewstep" || echo 1)"
|
|
335
|
+
git config "branch.$cur.reviewundoeditstep" "$ustep"
|
|
336
|
+
uold="$(git rev-parse --verify --quiet "refs/review-edits/$src/$ustep" || true)"
|
|
337
|
+
git config "branch.$cur.reviewundoeditold" "${uold:-none}"
|
|
338
|
+
else
|
|
339
|
+
git config --unset "branch.$cur.reviewundoeditstep" 2>/dev/null || true
|
|
340
|
+
git config --unset "branch.$cur.reviewundoeditold" 2>/dev/null || true
|
|
341
|
+
fi
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
# A finish records an undo point that lives until you --abort it (or tear the
|
|
345
|
+
# review down). If one already exists when starting a fresh finish, a previous
|
|
346
|
+
# finish on this branch has not been resolved — switching back to review/<branch>
|
|
347
|
+
# by hand and finishing again would clobber the undo point with new HEAD/index/
|
|
348
|
+
# worktree snapshots while leaving the old exit state behind, so a later --abort
|
|
349
|
+
# would restore a mix of the two and could delete the earlier finish's branch.
|
|
350
|
+
# Refuse the way git refuses a second merge or rebase while one is in progress,
|
|
351
|
+
# and point at the way out.
|
|
352
|
+
if [ "$resume" -eq 0 ] && [ -n "$(git config "branch.$cur.reviewundohead" || true)" ]; then
|
|
353
|
+
if [ "$(git config "branch.$cur.reviewresume" || true)" = "conflict" ]; then
|
|
354
|
+
echo "error: a finish on $cur stopped at a conflict and is still pending." >&2
|
|
355
|
+
echo "resolve the markers and run git review finish --resume, or git review finish --abort to back out." >&2
|
|
356
|
+
else
|
|
357
|
+
echo "error: a previous finish on $cur has not been resolved." >&2
|
|
358
|
+
echo "run git review finish --abort to undo it (or git review abort to drop the whole review) before finishing again." >&2
|
|
359
|
+
fi
|
|
360
|
+
exit 1
|
|
361
|
+
fi
|
|
362
|
+
|
|
363
|
+
# Only a fresh finish records an undo point; --resume continues the same finish
|
|
364
|
+
# and must keep the point its first invocation took.
|
|
365
|
+
if [ "$resume" -eq 0 ]; then
|
|
366
|
+
record_undo
|
|
367
|
+
fi
|
|
368
|
+
|
|
369
|
+
# In commit-by-commit mode, bank the current commit's edits, move to the tip and
|
|
370
|
+
# replay every banked edit there, so what follows sees the same state as a normal
|
|
371
|
+
# whole-PR review. With --resume we skip the replay: the working tree already
|
|
372
|
+
# holds the resolved edits.
|
|
373
|
+
if [ "$mode" = "step" ] && [ "$resume" -eq 0 ]; then
|
|
374
|
+
# Validate and load the step metadata (count, step, start, commits) through the
|
|
375
|
+
# shared helper, so a hand-deleted or corrupt key is reported the same way the
|
|
376
|
+
# other step commands report it — instead of defaulting count to 0 (which
|
|
377
|
+
# silently discards every banked edit) or letting a missing reviewstart kill us
|
|
378
|
+
# under set -e with no message.
|
|
379
|
+
load_step_review_meta
|
|
380
|
+
cstep="$(printf '%s\n' "$commits" | sed -n "${step}p")"
|
|
381
|
+
git add -A
|
|
382
|
+
tree="$(git write-tree)"
|
|
383
|
+
if [ "$tree" != "$(git rev-parse "$cstep^{tree}")" ]; then
|
|
384
|
+
edit="$(git commit-tree "$tree" -p "$cstep" -m "review edits step $step")"
|
|
385
|
+
git update-ref "refs/review-edits/$src/$step" "$edit"
|
|
386
|
+
else
|
|
387
|
+
# Reverting the step back to a clean tree must clear any edits we banked
|
|
388
|
+
# earlier, or they resurrect in the replay below.
|
|
389
|
+
git update-ref -d "refs/review-edits/$src/$step" 2>/dev/null || true
|
|
390
|
+
fi
|
|
391
|
+
git reset -q --hard "$tip"
|
|
392
|
+
conflict=0
|
|
393
|
+
i=1
|
|
394
|
+
while [ "$i" -le "$count" ]; do
|
|
395
|
+
ref="refs/review-edits/$src/$i"
|
|
396
|
+
if git rev-parse --verify --quiet "$ref" >/dev/null; then
|
|
397
|
+
# Keep going on conflict so every edit lands; conflicts are left as
|
|
398
|
+
# markers for the user to resolve. apply_review_patch routes the diff
|
|
399
|
+
# through a file so a binary banked edit is not corrupted.
|
|
400
|
+
apply_review_patch "${ref}^" "$ref" --3way ||
|
|
401
|
+
conflict=1
|
|
402
|
+
fi
|
|
403
|
+
i=$((i + 1))
|
|
404
|
+
done
|
|
405
|
+
if [ "$conflict" -eq 1 ]; then
|
|
406
|
+
git config "branch.$cur.reviewresume" conflict
|
|
407
|
+
flags=""
|
|
408
|
+
[ "$onto" -eq 1 ] && flags="$flags --onto-source"
|
|
409
|
+
echo "conflict: some banked edits overlap the PR tip." >&2
|
|
410
|
+
echo "the working tree has conflict markers — resolve them, then run:" >&2
|
|
411
|
+
echo " git review finish --resume$flags" >&2
|
|
412
|
+
echo "(git review finish --abort here discards the resolution, like git rebase --abort.)" >&2
|
|
413
|
+
exit 1
|
|
414
|
+
fi
|
|
415
|
+
elif [ "$resume" -eq 1 ]; then
|
|
416
|
+
if [ "$(git config "branch.$cur.reviewresume" || true)" != "conflict" ]; then
|
|
417
|
+
echo "error: nothing to resume" >&2
|
|
418
|
+
exit 1
|
|
419
|
+
fi
|
|
420
|
+
git config --unset "branch.$cur.reviewresume" || true
|
|
421
|
+
fi
|
|
422
|
+
|
|
423
|
+
# Fold any edits into the review branch so we can diff them back out cleanly.
|
|
424
|
+
git add -A
|
|
425
|
+
if ! git diff --cached --quiet; then
|
|
426
|
+
git commit -q -m "review session ($src)"
|
|
427
|
+
fi
|
|
428
|
+
|
|
429
|
+
if git diff --quiet "$tip" "$cur"; then
|
|
430
|
+
# No edits came out of the review. The undo point recorded above guards a
|
|
431
|
+
# finish branch that will never be produced, so roll it back — otherwise it
|
|
432
|
+
# would block a later finish on this branch (and leave nothing meaningful to
|
|
433
|
+
# --abort). Then honour --onto-source by leaving you on the PR branch, the
|
|
434
|
+
# whole point of the flag, instead of stranding you on review/<branch>.
|
|
435
|
+
git config --unset "branch.$cur.reviewresume" 2>/dev/null || true
|
|
436
|
+
for k in reviewundohead reviewundokind reviewundosrccreated \
|
|
437
|
+
reviewundoeditstep reviewundoeditold reviewundoouthead reviewundoouttree; do
|
|
438
|
+
git config --unset "branch.$cur.$k" 2>/dev/null || true
|
|
439
|
+
done
|
|
440
|
+
git for-each-ref --format='%(refname)' "refs/review-undo/$src/" |
|
|
441
|
+
while read -r ref; do
|
|
442
|
+
git update-ref -d "$ref"
|
|
443
|
+
done
|
|
444
|
+
|
|
445
|
+
if [ "$onto" -eq 1 ]; then
|
|
446
|
+
# Same guard as the edited path: never move onto a local $src that sits
|
|
447
|
+
# somewhere other than the reviewed tip.
|
|
448
|
+
if git rev-parse --verify --quiet "refs/heads/$src" >/dev/null; then
|
|
449
|
+
if [ "$(git rev-parse "$src")" != "$tip" ]; then
|
|
450
|
+
echo "error: local $src is not at the reviewed tip; move or delete it first" >&2
|
|
451
|
+
exit 1
|
|
452
|
+
fi
|
|
453
|
+
git switch -q "$src"
|
|
454
|
+
else
|
|
455
|
+
git switch -q -c "$src" "$tip"
|
|
456
|
+
fi
|
|
457
|
+
echo "no review changes to apply; on $src at the reviewed tip" >&2
|
|
458
|
+
exit 0
|
|
459
|
+
fi
|
|
460
|
+
|
|
461
|
+
echo "no review changes to apply" >&2
|
|
462
|
+
exit 0
|
|
463
|
+
fi
|
|
464
|
+
|
|
465
|
+
if [ "$onto" -eq 1 ]; then
|
|
466
|
+
if git rev-parse --verify --quiet "refs/heads/$src" >/dev/null; then
|
|
467
|
+
if [ "$(git rev-parse "$src")" != "$tip" ]; then
|
|
468
|
+
echo "error: local $src is not at the reviewed tip; move or delete it first" >&2
|
|
469
|
+
exit 1
|
|
470
|
+
fi
|
|
471
|
+
git switch -q "$src"
|
|
472
|
+
else
|
|
473
|
+
git switch -q -c "$src" "$tip"
|
|
474
|
+
fi
|
|
475
|
+
apply_review_patch "$tip" "$cur" --index --3way || {
|
|
476
|
+
echo "error: could not apply review changes onto $src" >&2
|
|
477
|
+
exit 1
|
|
478
|
+
}
|
|
479
|
+
record_exit "$src"
|
|
480
|
+
echo "$src ready with your edits staged — review and commit"
|
|
481
|
+
else
|
|
482
|
+
fb="review-fixes/$src"
|
|
483
|
+
git rev-parse --verify --quiet "refs/heads/$fb" >/dev/null && {
|
|
484
|
+
echo "error: $fb already exists; remove it first" >&2
|
|
485
|
+
exit 1
|
|
486
|
+
}
|
|
487
|
+
git switch -q -c "$fb" "$tip"
|
|
488
|
+
apply_review_patch "$tip" "$cur" --index --3way || {
|
|
489
|
+
echo "error: could not apply review changes onto $fb" >&2
|
|
490
|
+
exit 1
|
|
491
|
+
}
|
|
492
|
+
record_exit "$fb"
|
|
493
|
+
echo "$fb ready with your edits staged — review and commit"
|
|
494
|
+
fi
|