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,289 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
#
|
|
3
|
+
# git review forget — discard the persistent state a review leaves behind:
|
|
4
|
+
# the --delta markers (--delta) or a review paused with git review save (--saved).
|
|
5
|
+
# Exactly one mode is required.
|
|
6
|
+
#
|
|
7
|
+
# Both kinds of state outlive the review/<branch> and review-fixes/<branch>
|
|
8
|
+
# branches on purpose — a later --delta keeps working, a paused review waits to
|
|
9
|
+
# be resumed — so git review clean deliberately leaves them alone. This command
|
|
10
|
+
# is how you throw them away. To delete the review branches themselves, use
|
|
11
|
+
# git review clean.
|
|
12
|
+
#
|
|
13
|
+
set -eu
|
|
14
|
+
|
|
15
|
+
prog="git review forget"
|
|
16
|
+
|
|
17
|
+
usage() {
|
|
18
|
+
cat <<EOF
|
|
19
|
+
usage: $prog --delta (<branch> | --all | --stale [--dry-run])
|
|
20
|
+
$prog --saved (<branch> | --all) [--dry-run]
|
|
21
|
+
|
|
22
|
+
Discard the persistent state a review leaves behind. Choose one mode:
|
|
23
|
+
|
|
24
|
+
--delta forget the recorded last-reviewed tip used by git review start --delta
|
|
25
|
+
--saved discard a review paused with git review save: delete
|
|
26
|
+
review-saved/<branch>, its banked edits and metadata, and roll back
|
|
27
|
+
the --delta marker it left
|
|
28
|
+
|
|
29
|
+
--delta targets:
|
|
30
|
+
<branch> forget the marker(s) for one source branch (remote and --local)
|
|
31
|
+
--all forget every recorded marker
|
|
32
|
+
--stale forget only markers whose branch no longer exists: remote markers
|
|
33
|
+
when <remote>/<branch> is gone (fetches and prunes first), local
|
|
34
|
+
markers when the local <branch> is gone
|
|
35
|
+
--dry-run with --stale, list what would be forgotten without forgetting it
|
|
36
|
+
|
|
37
|
+
--saved targets:
|
|
38
|
+
<branch> discard the saved review for one source branch
|
|
39
|
+
--all discard every saved review
|
|
40
|
+
--dry-run list what would be discarded without discarding it
|
|
41
|
+
EOF
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
die() {
|
|
45
|
+
echo "error: $1" >&2
|
|
46
|
+
exit 1
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
mode=""
|
|
50
|
+
arg=""
|
|
51
|
+
all=0
|
|
52
|
+
stale=0
|
|
53
|
+
dryrun=0
|
|
54
|
+
while [ $# -gt 0 ]; do
|
|
55
|
+
case "$1" in
|
|
56
|
+
-h | --h)
|
|
57
|
+
usage
|
|
58
|
+
exit 0
|
|
59
|
+
;;
|
|
60
|
+
--delta)
|
|
61
|
+
[ -z "$mode" ] || [ "$mode" = delta ] || die "use only one of --delta and --saved"
|
|
62
|
+
mode="delta"
|
|
63
|
+
;;
|
|
64
|
+
--saved)
|
|
65
|
+
[ -z "$mode" ] || [ "$mode" = saved ] || die "use only one of --delta and --saved"
|
|
66
|
+
mode="saved"
|
|
67
|
+
;;
|
|
68
|
+
--all) all=1 ;;
|
|
69
|
+
--stale) stale=1 ;;
|
|
70
|
+
--dry-run) dryrun=1 ;;
|
|
71
|
+
-*)
|
|
72
|
+
echo "error: unknown option $1" >&2
|
|
73
|
+
usage >&2
|
|
74
|
+
exit 1
|
|
75
|
+
;;
|
|
76
|
+
*)
|
|
77
|
+
if [ -z "$arg" ]; then
|
|
78
|
+
arg="$1"
|
|
79
|
+
else
|
|
80
|
+
echo "error: unexpected argument $1" >&2
|
|
81
|
+
usage >&2
|
|
82
|
+
exit 1
|
|
83
|
+
fi
|
|
84
|
+
;;
|
|
85
|
+
esac
|
|
86
|
+
shift
|
|
87
|
+
done
|
|
88
|
+
|
|
89
|
+
# The mode selects which kind of state to discard; without it the request is
|
|
90
|
+
# ambiguous (markers? a saved review?), so there is no safe default.
|
|
91
|
+
if [ -z "$mode" ]; then
|
|
92
|
+
usage >&2
|
|
93
|
+
exit 1
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# ── --saved ─────────────────────────────────────────────────────────────────────
|
|
97
|
+
if [ "$mode" = saved ]; then
|
|
98
|
+
[ "$stale" -eq 0 ] || die "--stale only applies to --delta"
|
|
99
|
+
|
|
100
|
+
if [ -n "$arg" ] && [ "$all" -eq 1 ]; then
|
|
101
|
+
die "use either <branch> or --all, not both"
|
|
102
|
+
fi
|
|
103
|
+
if [ -z "$arg" ] && [ "$all" -eq 0 ]; then
|
|
104
|
+
usage >&2
|
|
105
|
+
exit 1
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
git rev-parse --git-dir >/dev/null 2>&1 || die "not a git repository"
|
|
109
|
+
|
|
110
|
+
cur="$(git symbolic-ref --quiet --short HEAD || true)"
|
|
111
|
+
|
|
112
|
+
# forget_saved_one <src>: discard the saved review for one source branch.
|
|
113
|
+
forget_saved_one() {
|
|
114
|
+
src="$1"
|
|
115
|
+
saved="review-saved/$src"
|
|
116
|
+
if ! git rev-parse --verify --quiet "refs/heads/$saved" >/dev/null; then
|
|
117
|
+
echo "no saved review for $src" >&2
|
|
118
|
+
return
|
|
119
|
+
fi
|
|
120
|
+
if [ "$dryrun" -eq 1 ]; then
|
|
121
|
+
echo "would discard saved review of $src"
|
|
122
|
+
return
|
|
123
|
+
fi
|
|
124
|
+
if [ "$saved" = "$cur" ]; then
|
|
125
|
+
echo "skipping $src (its branch is currently checked out)" >&2
|
|
126
|
+
return
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
# Roll the --delta marker back to the last actual review before the metadata
|
|
130
|
+
# disappears with the branch. Local reviews track their marker in a separate
|
|
131
|
+
# config section, matching git review start / git review abort.
|
|
132
|
+
if [ "$(git config "branch.$saved.reviewlocal" || true)" = "1" ]; then
|
|
133
|
+
markerkey="reviewworkflowlocal.$src.reviewed"
|
|
134
|
+
else
|
|
135
|
+
markerkey="reviewworkflow.$src.reviewed"
|
|
136
|
+
fi
|
|
137
|
+
prevreviewed="$(git config "branch.$saved.reviewprevreviewed" || true)"
|
|
138
|
+
if [ -n "$prevreviewed" ]; then
|
|
139
|
+
git config "$markerkey" "$prevreviewed"
|
|
140
|
+
else
|
|
141
|
+
git config --unset "$markerkey" 2>/dev/null || true
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
git for-each-ref --format='%(refname)' "refs/review-saved-edits/$src/" | while read -r ref; do
|
|
145
|
+
git update-ref -d "$ref"
|
|
146
|
+
done
|
|
147
|
+
git branch -D "$saved" >/dev/null
|
|
148
|
+
|
|
149
|
+
echo "discarded saved review of $src"
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if [ "$all" -eq 1 ]; then
|
|
153
|
+
branches="$(git for-each-ref --format='%(refname:short)' refs/heads/review-saved/)"
|
|
154
|
+
[ -n "$branches" ] || {
|
|
155
|
+
echo "no saved reviews"
|
|
156
|
+
exit 0
|
|
157
|
+
}
|
|
158
|
+
printf '%s\n' "$branches" | while read -r b; do
|
|
159
|
+
forget_saved_one "${b#review-saved/}"
|
|
160
|
+
done
|
|
161
|
+
else
|
|
162
|
+
forget_saved_one "$arg"
|
|
163
|
+
fi
|
|
164
|
+
exit 0
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
# ── --delta ─────────────────────────────────────────────────────────────────────
|
|
168
|
+
# parse_marker <key>: split a marker key into the source branch ($msrc), a human
|
|
169
|
+
# label ($mlabel) and whether it is a local marker ($mislocal). Local markers (git
|
|
170
|
+
# review start --local) live in their own config section, reviewworkflowlocal.<src>
|
|
171
|
+
# .reviewed, while remote ones are reviewworkflow.<src>.reviewed. Disjoint sections
|
|
172
|
+
# (rather than a .local segment inside one section) keep the two apart even for a
|
|
173
|
+
# branch literally named "<src>.local".
|
|
174
|
+
parse_marker() {
|
|
175
|
+
case "$1" in
|
|
176
|
+
reviewworkflowlocal.*)
|
|
177
|
+
msrc="${1#reviewworkflowlocal.}"
|
|
178
|
+
msrc="${msrc%.reviewed}"
|
|
179
|
+
mlabel=" (local)"
|
|
180
|
+
mislocal=1
|
|
181
|
+
;;
|
|
182
|
+
*)
|
|
183
|
+
msrc="${1#reviewworkflow.}"
|
|
184
|
+
msrc="${msrc%.reviewed}"
|
|
185
|
+
mlabel=""
|
|
186
|
+
mislocal=0
|
|
187
|
+
;;
|
|
188
|
+
esac
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
# forget_delta <key> <src> <verb>: unset a marker (or, in dry-run, say it would be).
|
|
192
|
+
forget_delta() {
|
|
193
|
+
if [ "$dryrun" -eq 1 ]; then
|
|
194
|
+
echo "would forget delta marker for $2$3"
|
|
195
|
+
else
|
|
196
|
+
git config --unset "$1" || true
|
|
197
|
+
echo "forgot delta marker for $2$3"
|
|
198
|
+
fi
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
# Exactly one target selector: a branch, --all or --stale.
|
|
202
|
+
selectors=0
|
|
203
|
+
[ -n "$arg" ] && selectors=$((selectors + 1))
|
|
204
|
+
[ "$all" -eq 1 ] && selectors=$((selectors + 1))
|
|
205
|
+
[ "$stale" -eq 1 ] && selectors=$((selectors + 1))
|
|
206
|
+
if [ "$selectors" -eq 0 ]; then
|
|
207
|
+
usage >&2
|
|
208
|
+
exit 1
|
|
209
|
+
fi
|
|
210
|
+
[ "$selectors" -eq 1 ] || die "use only one of <branch>, --all and --stale"
|
|
211
|
+
|
|
212
|
+
# --dry-run is a preview of the inferred --stale set; it means nothing for the
|
|
213
|
+
# explicit targets, where you already named exactly what to forget.
|
|
214
|
+
if [ "$dryrun" -eq 1 ] && [ "$stale" -ne 1 ]; then
|
|
215
|
+
die "--dry-run only applies to --stale"
|
|
216
|
+
fi
|
|
217
|
+
|
|
218
|
+
git rev-parse --git-dir >/dev/null 2>&1 || die "not a git repository"
|
|
219
|
+
|
|
220
|
+
# Remote whose branches back the markers; defaults to origin, override with
|
|
221
|
+
# reviewworkflow.remote.
|
|
222
|
+
remote="$(git config reviewworkflow.remote || echo origin)"
|
|
223
|
+
|
|
224
|
+
# ── one branch ────────────────────────────────────────────────────────────────
|
|
225
|
+
if [ -n "$arg" ]; then
|
|
226
|
+
any=0
|
|
227
|
+
if [ -n "$(git config "reviewworkflow.$arg.reviewed" || true)" ]; then
|
|
228
|
+
forget_delta "reviewworkflow.$arg.reviewed" "$arg" ""
|
|
229
|
+
any=1
|
|
230
|
+
fi
|
|
231
|
+
if [ -n "$(git config "reviewworkflowlocal.$arg.reviewed" || true)" ]; then
|
|
232
|
+
forget_delta "reviewworkflowlocal.$arg.reviewed" "$arg" " (local)"
|
|
233
|
+
any=1
|
|
234
|
+
fi
|
|
235
|
+
[ "$any" -eq 1 ] || echo "no delta marker recorded for $arg"
|
|
236
|
+
exit 0
|
|
237
|
+
fi
|
|
238
|
+
|
|
239
|
+
# Collect every marker into a temp file so the loop runs in the current shell
|
|
240
|
+
# (a pipe into `while` would forget unsets in a subshell). Both sections are
|
|
241
|
+
# gathered: remote markers (reviewworkflow.*) and local ones (reviewworkflowlocal.*).
|
|
242
|
+
tmp="$(mktemp)"
|
|
243
|
+
trap 'rm -f "$tmp"' EXIT
|
|
244
|
+
{
|
|
245
|
+
git config --get-regexp '^reviewworkflow\..*\.reviewed$' 2>/dev/null || true
|
|
246
|
+
git config --get-regexp '^reviewworkflowlocal\..*\.reviewed$' 2>/dev/null || true
|
|
247
|
+
} >"$tmp"
|
|
248
|
+
|
|
249
|
+
if [ ! -s "$tmp" ]; then
|
|
250
|
+
echo "no delta markers recorded"
|
|
251
|
+
exit 0
|
|
252
|
+
fi
|
|
253
|
+
|
|
254
|
+
# ── --stale ───────────────────────────────────────────────────────────────────
|
|
255
|
+
if [ "$stale" -eq 1 ]; then
|
|
256
|
+
# A local marker's staleness is decided entirely by whether its local branch
|
|
257
|
+
# still exists — no remote state involved. Only fetch (to prune deleted
|
|
258
|
+
# remote-tracking refs) when at least one remote marker is present; a set of
|
|
259
|
+
# purely --local markers needs no network at all. The anchored pattern matches
|
|
260
|
+
# reviewworkflow.<src>.reviewed but not reviewworkflowlocal.<src>.reviewed.
|
|
261
|
+
if grep -q '^reviewworkflow\.' "$tmp"; then
|
|
262
|
+
git fetch --prune --quiet "$remote" || die "could not fetch from $remote"
|
|
263
|
+
fi
|
|
264
|
+
|
|
265
|
+
any=0
|
|
266
|
+
while read -r key _; do
|
|
267
|
+
[ -n "$key" ] || continue
|
|
268
|
+
parse_marker "$key"
|
|
269
|
+
if [ "$mislocal" -eq 1 ]; then
|
|
270
|
+
# A local marker is stale when its local branch is gone.
|
|
271
|
+
git rev-parse --verify --quiet "refs/heads/$msrc" >/dev/null && continue
|
|
272
|
+
forget_delta "$key" "$msrc" " (local; $msrc no longer exists)"
|
|
273
|
+
else
|
|
274
|
+
git rev-parse --verify --quiet "refs/remotes/$remote/$msrc" >/dev/null && continue
|
|
275
|
+
forget_delta "$key" "$msrc" " ($remote/$msrc no longer exists)"
|
|
276
|
+
fi
|
|
277
|
+
any=1
|
|
278
|
+
done <"$tmp"
|
|
279
|
+
|
|
280
|
+
[ "$any" -eq 1 ] || echo "no stale delta markers (every branch still exists)"
|
|
281
|
+
exit 0
|
|
282
|
+
fi
|
|
283
|
+
|
|
284
|
+
# ── --all ─────────────────────────────────────────────────────────────────────
|
|
285
|
+
while read -r key _; do
|
|
286
|
+
[ -n "$key" ] || continue
|
|
287
|
+
parse_marker "$key"
|
|
288
|
+
forget_delta "$key" "$msrc" "$mlabel"
|
|
289
|
+
done <"$tmp"
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
#
|
|
3
|
+
# git review list — list every review/* branch in progress.
|
|
4
|
+
#
|
|
5
|
+
# git review status reports only the review on the current branch; this shows all
|
|
6
|
+
# of them at once, so you can see what you have open across branches. The current
|
|
7
|
+
# branch is marked with a "*".
|
|
8
|
+
#
|
|
9
|
+
set -eu
|
|
10
|
+
|
|
11
|
+
prog="git review list"
|
|
12
|
+
|
|
13
|
+
usage() {
|
|
14
|
+
cat <<EOF
|
|
15
|
+
usage: $prog
|
|
16
|
+
|
|
17
|
+
List every review/* branch in progress, with its source PR, mode and — in
|
|
18
|
+
commit-by-commit mode — which commit you are on ([k/N]). The branch you are on
|
|
19
|
+
is marked with a "*". Reviews paused with git review save are listed too, under
|
|
20
|
+
"saved".
|
|
21
|
+
EOF
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
case "${1:-}" in
|
|
25
|
+
-h | --h)
|
|
26
|
+
usage
|
|
27
|
+
exit 0
|
|
28
|
+
;;
|
|
29
|
+
"") ;;
|
|
30
|
+
*)
|
|
31
|
+
echo "error: unexpected argument $1" >&2
|
|
32
|
+
usage >&2
|
|
33
|
+
exit 1
|
|
34
|
+
;;
|
|
35
|
+
esac
|
|
36
|
+
|
|
37
|
+
git rev-parse --git-dir >/dev/null 2>&1 || {
|
|
38
|
+
echo "error: not a git repository" >&2
|
|
39
|
+
exit 1
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
cur="$(git symbolic-ref --quiet --short HEAD || true)"
|
|
43
|
+
branches="$(git for-each-ref --format='%(refname:short)' refs/heads/review/)"
|
|
44
|
+
saved="$(git for-each-ref --format='%(refname:short)' refs/heads/review-saved/)"
|
|
45
|
+
|
|
46
|
+
if [ -z "$branches" ] && [ -z "$saved" ]; then
|
|
47
|
+
echo "no reviews in progress"
|
|
48
|
+
exit 0
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# describe <branch> <marker>: print one line for a review branch, reading its
|
|
52
|
+
# mode and position from the branch config. Used for both active and saved reviews.
|
|
53
|
+
describe() {
|
|
54
|
+
b="$1"
|
|
55
|
+
label="$2"
|
|
56
|
+
src="$(git config "branch.$b.reviewsource" || true)"
|
|
57
|
+
# A branch with no reviewsource is an orphan: either hand-made, or left by a
|
|
58
|
+
# git review save / git review start that died after creating the branch but before
|
|
59
|
+
# writing its metadata. Surface it instead of skipping silently — otherwise an
|
|
60
|
+
# orphan-only state prints nothing at all — so it can be cleaned up (e.g. with
|
|
61
|
+
# git review forget --saved).
|
|
62
|
+
if [ -z "$src" ]; then
|
|
63
|
+
printf '%s %s %s(no metadata)\n' "$label" "$b" "$prefix"
|
|
64
|
+
return
|
|
65
|
+
fi
|
|
66
|
+
tip="$(git config "branch.$b.reviewtip" || true)"
|
|
67
|
+
mode="$(git config "branch.$b.reviewmode" || echo whole)"
|
|
68
|
+
|
|
69
|
+
shorttip="$tip"
|
|
70
|
+
[ -n "$tip" ] && shorttip="$(git rev-parse --short "$tip" 2>/dev/null || echo "$tip")"
|
|
71
|
+
|
|
72
|
+
if [ "$mode" = "step" ]; then
|
|
73
|
+
count="$(git config "branch.$b.reviewcount" || echo '?')"
|
|
74
|
+
step="$(git config "branch.$b.reviewstep" || echo '?')"
|
|
75
|
+
printf '%s %s %sstep [%s/%s] tip %s\n' "$label" "$b" "$prefix" "$step" "$count" "$shorttip"
|
|
76
|
+
else
|
|
77
|
+
printf '%s %s %swhole tip %s\n' "$label" "$b" "$prefix" "$shorttip"
|
|
78
|
+
fi
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
prefix=""
|
|
82
|
+
printf '%s\n' "$branches" | while read -r b; do
|
|
83
|
+
[ -n "$b" ] || continue
|
|
84
|
+
marker=" "
|
|
85
|
+
[ "$b" = "$cur" ] && marker="*"
|
|
86
|
+
describe "$b" "$marker"
|
|
87
|
+
done
|
|
88
|
+
|
|
89
|
+
# Reviews put aside with git review save, tagged so they are not mistaken for
|
|
90
|
+
# active ones. The branch you are on is still marked with "*".
|
|
91
|
+
if [ -n "$saved" ]; then
|
|
92
|
+
prefix="saved "
|
|
93
|
+
printf '%s\n' "$saved" | while read -r b; do
|
|
94
|
+
[ -n "$b" ] || continue
|
|
95
|
+
marker=" "
|
|
96
|
+
[ "$b" = "$cur" ] && marker="*"
|
|
97
|
+
describe "$b" "$marker"
|
|
98
|
+
done
|
|
99
|
+
fi
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
#
|
|
3
|
+
# git review next — advance a commit-by-commit review (started with review start --step).
|
|
4
|
+
#
|
|
5
|
+
# Moving banks the edits you made on the current commit into a ref of its own and
|
|
6
|
+
# restores any edits you had previously banked on the commit you move to, so you
|
|
7
|
+
# can walk back and forth without losing work. git review finish replays every
|
|
8
|
+
# banked edit onto the PR tip.
|
|
9
|
+
#
|
|
10
|
+
set -eu
|
|
11
|
+
|
|
12
|
+
prog="git review next"
|
|
13
|
+
|
|
14
|
+
usage() {
|
|
15
|
+
cat <<EOF
|
|
16
|
+
usage: $prog
|
|
17
|
+
|
|
18
|
+
Advance a commit-by-commit review to the next commit. Your edits are remembered;
|
|
19
|
+
run git review prev to go back, or git review finish when you are done.
|
|
20
|
+
EOF
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
case "${1:-}" in
|
|
24
|
+
-h | --h)
|
|
25
|
+
usage
|
|
26
|
+
exit 0
|
|
27
|
+
;;
|
|
28
|
+
"") ;;
|
|
29
|
+
*)
|
|
30
|
+
echo "error: unexpected argument $1" >&2
|
|
31
|
+
usage >&2
|
|
32
|
+
exit 1
|
|
33
|
+
;;
|
|
34
|
+
esac
|
|
35
|
+
|
|
36
|
+
# Shared helpers, sourced from the libexec directory the dispatcher points us at.
|
|
37
|
+
# shellcheck source=../git-review-lib.sh
|
|
38
|
+
. "${GIT_REVIEW_LIBEXEC:?}/git-review-lib.sh"
|
|
39
|
+
|
|
40
|
+
load_step_review_meta
|
|
41
|
+
|
|
42
|
+
if [ "$step" -lt "$count" ]; then
|
|
43
|
+
goto_step "$((step + 1))"
|
|
44
|
+
else
|
|
45
|
+
echo "no more commits — run git review finish"
|
|
46
|
+
fi
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
#
|
|
3
|
+
# git review prev — step a commit-by-commit review back to the previous commit.
|
|
4
|
+
#
|
|
5
|
+
# Like git review next but backwards: it banks the current commit's edits and
|
|
6
|
+
# restores any edits you had banked on the previous commit, so you can revisit
|
|
7
|
+
# and keep editing them.
|
|
8
|
+
#
|
|
9
|
+
set -eu
|
|
10
|
+
|
|
11
|
+
prog="git review prev"
|
|
12
|
+
|
|
13
|
+
usage() {
|
|
14
|
+
cat <<EOF
|
|
15
|
+
usage: $prog
|
|
16
|
+
|
|
17
|
+
Go back to the previous commit of a commit-by-commit review. Your edits are
|
|
18
|
+
remembered in both directions.
|
|
19
|
+
EOF
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
case "${1:-}" in
|
|
23
|
+
-h | --h)
|
|
24
|
+
usage
|
|
25
|
+
exit 0
|
|
26
|
+
;;
|
|
27
|
+
"") ;;
|
|
28
|
+
*)
|
|
29
|
+
echo "error: unexpected argument $1" >&2
|
|
30
|
+
usage >&2
|
|
31
|
+
exit 1
|
|
32
|
+
;;
|
|
33
|
+
esac
|
|
34
|
+
|
|
35
|
+
# Shared helpers, sourced from the libexec directory the dispatcher points us at.
|
|
36
|
+
# shellcheck source=../git-review-lib.sh
|
|
37
|
+
. "${GIT_REVIEW_LIBEXEC:?}/git-review-lib.sh"
|
|
38
|
+
|
|
39
|
+
load_step_review_meta
|
|
40
|
+
|
|
41
|
+
if [ "$step" -gt 1 ]; then
|
|
42
|
+
goto_step "$((step - 1))"
|
|
43
|
+
else
|
|
44
|
+
echo "already at the first commit"
|
|
45
|
+
fi
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
#
|
|
3
|
+
# git review preview — show the edits you have made so far, without disturbing them.
|
|
4
|
+
#
|
|
5
|
+
# It prints the same diff that git review finish would extract — your review edits
|
|
6
|
+
# on top of the PR tip — but it never commits, never switches branch and never
|
|
7
|
+
# touches your working tree or index, so you go straight back to editing where you
|
|
8
|
+
# left off. Think of it as "what would git review finish give me right now?".
|
|
9
|
+
#
|
|
10
|
+
# Everything is computed in a throwaway index (GIT_INDEX_FILE), the same plumbing
|
|
11
|
+
# style git review save/git review finish use, so running it is a no-op on your state:
|
|
12
|
+
# whole mode capture the working tree (edits + any new files) as a tree and diff
|
|
13
|
+
# it against the tip.
|
|
14
|
+
# step mode reproduce git review finish's replay in the throwaway index — the
|
|
15
|
+
# current step's edits plus every banked edit, applied onto the tip
|
|
16
|
+
# with the same --3way merge — and diff the result against the tip.
|
|
17
|
+
# An edit that genuinely conflicts with the tip is the one case that
|
|
18
|
+
# differs from git review finish: git review finish leaves you conflict
|
|
19
|
+
# markers to resolve, whereas a read-only preview cannot, so it omits
|
|
20
|
+
# that edit and prints a note pointing you at git review finish.
|
|
21
|
+
#
|
|
22
|
+
set -eu
|
|
23
|
+
|
|
24
|
+
prog="git review preview"
|
|
25
|
+
|
|
26
|
+
usage() {
|
|
27
|
+
cat <<EOF
|
|
28
|
+
usage: $prog [--stat]
|
|
29
|
+
|
|
30
|
+
Show the review edits you have made so far on the current review/* branch — the
|
|
31
|
+
same diff git review finish would extract — without committing, switching branch
|
|
32
|
+
or changing your working tree, so you can keep editing where you left off.
|
|
33
|
+
|
|
34
|
+
--stat show a diffstat summary instead of the full diff
|
|
35
|
+
EOF
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
die() {
|
|
39
|
+
echo "error: $1" >&2
|
|
40
|
+
exit 1
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
stat=0
|
|
44
|
+
while [ $# -gt 0 ]; do
|
|
45
|
+
case "$1" in
|
|
46
|
+
-h | --h)
|
|
47
|
+
usage
|
|
48
|
+
exit 0
|
|
49
|
+
;;
|
|
50
|
+
--stat) stat=1 ;;
|
|
51
|
+
*)
|
|
52
|
+
echo "error: unexpected argument $1" >&2
|
|
53
|
+
usage >&2
|
|
54
|
+
exit 1
|
|
55
|
+
;;
|
|
56
|
+
esac
|
|
57
|
+
shift
|
|
58
|
+
done
|
|
59
|
+
|
|
60
|
+
cur="$(git symbolic-ref --quiet --short HEAD || true)"
|
|
61
|
+
case "$cur" in
|
|
62
|
+
review/*) ;;
|
|
63
|
+
*)
|
|
64
|
+
echo "not on a review/* branch (HEAD is ${cur:-detached})" >&2
|
|
65
|
+
exit 1
|
|
66
|
+
;;
|
|
67
|
+
esac
|
|
68
|
+
|
|
69
|
+
src="$(git config "branch.$cur.reviewsource" || true)"
|
|
70
|
+
tip="$(git config "branch.$cur.reviewtip" || true)"
|
|
71
|
+
mode="$(git config "branch.$cur.reviewmode" || echo whole)"
|
|
72
|
+
if [ -z "$src" ] || [ -z "$tip" ]; then
|
|
73
|
+
die "missing review metadata; was $cur created with git review start?"
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
# A git review finish left mid-conflict has conflict markers in the working tree; a
|
|
77
|
+
# preview built from it would show the markers as content, not your edits. Refuse
|
|
78
|
+
# until they are resolved, mirroring the state git review finish --resume expects.
|
|
79
|
+
if [ "$(git config "branch.$cur.reviewresume" || true)" = "conflict" ]; then
|
|
80
|
+
die "git review finish is mid-conflict; resolve the markers and run git review finish --resume first"
|
|
81
|
+
fi
|
|
82
|
+
if [ -n "$(git ls-files --unmerged)" ]; then
|
|
83
|
+
die "you have unresolved merge conflicts; resolve them first"
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
# Throwaway indexes live inside the git dir and are removed on exit, so the real
|
|
87
|
+
# index is never touched and a preview leaves no residue.
|
|
88
|
+
gitdir="$(git rev-parse --git-dir)"
|
|
89
|
+
idx="$gitdir/review-preview-index.$$"
|
|
90
|
+
idx2="$gitdir/review-preview-replay.$$"
|
|
91
|
+
# Patches go through files, never shell variables: command substitution corrupts
|
|
92
|
+
# a binary hunk (dropped NUL bytes and trailing newline). curpatch holds the
|
|
93
|
+
# current step's edits, ppatch each banked edit in turn.
|
|
94
|
+
curpatch="$gitdir/review-preview-curpatch.$$"
|
|
95
|
+
ppatch="$gitdir/review-preview-patch.$$"
|
|
96
|
+
trap 'rm -f "$idx" "$idx2" "$curpatch" "$ppatch"' EXIT
|
|
97
|
+
|
|
98
|
+
# worktree_tree: capture the current working tree — staged, unstaged and any new
|
|
99
|
+
# files — as a tree object, the same content git review finish folds in with git add -A.
|
|
100
|
+
worktree_tree() {
|
|
101
|
+
cp "$(git rev-parse --git-path index)" "$idx"
|
|
102
|
+
GIT_INDEX_FILE="$idx" git add -A
|
|
103
|
+
GIT_INDEX_FILE="$idx" git write-tree
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if [ "$mode" = "step" ]; then
|
|
107
|
+
start="$(git config "branch.$cur.reviewstart" || true)"
|
|
108
|
+
count="$(git config "branch.$cur.reviewcount" || true)"
|
|
109
|
+
step="$(git config "branch.$cur.reviewstep" || true)"
|
|
110
|
+
# A key deleted by a hand-edit (while reviewmode stays "step") would otherwise
|
|
111
|
+
# let set -e kill the bare read silently mid-script; read with || true and report
|
|
112
|
+
# it. reviewstep falls through to the numeric guard below for a precise message.
|
|
113
|
+
if [ -z "$start" ] || [ -z "$count" ]; then
|
|
114
|
+
die "missing review metadata; was $cur created with git review start?"
|
|
115
|
+
fi
|
|
116
|
+
commits="$(git rev-list --reverse --first-parent --no-merges "$start..$tip")"
|
|
117
|
+
# Guard against a step that maps to no commit (corrupt config, hand-edited
|
|
118
|
+
# metadata): otherwise cstep is empty and git diff '' aborts the preview with
|
|
119
|
+
# an opaque exit 128 instead of a clear message.
|
|
120
|
+
total="$(printf '%s\n' "$commits" | grep -c .)"
|
|
121
|
+
case "$step" in
|
|
122
|
+
'' | *[!0-9]*) die "corrupt review metadata: reviewstep is '$step', not a positive integer" ;;
|
|
123
|
+
esac
|
|
124
|
+
if [ "$step" -lt 1 ] || [ "$step" -gt "$total" ]; then
|
|
125
|
+
die "review step $step out of range (1..$total) — corrupt metadata?"
|
|
126
|
+
fi
|
|
127
|
+
cstep="$(printf '%s\n' "$commits" | sed -n "${step}p")"
|
|
128
|
+
|
|
129
|
+
# The current step's edits are not banked yet (they live in the working tree),
|
|
130
|
+
# so derive them from the working tree against the commit being reviewed.
|
|
131
|
+
git diff --binary "$cstep" "$(worktree_tree)" >"$curpatch"
|
|
132
|
+
|
|
133
|
+
# Replay onto the tip exactly like git review finish: walk the steps in order,
|
|
134
|
+
# applying the current step's edits in its slot and each other step's banked
|
|
135
|
+
# edits, into a fresh index seeded with the tip — with the same --3way merge
|
|
136
|
+
# git review finish uses, so an edit whose context the PR later shifted still lands.
|
|
137
|
+
GIT_INDEX_FILE="$idx2" git read-tree "$tip"
|
|
138
|
+
good="$(GIT_INDEX_FILE="$idx2" git write-tree)"
|
|
139
|
+
conflict=0
|
|
140
|
+
i=1
|
|
141
|
+
while [ "$i" -le "$count" ]; do
|
|
142
|
+
if [ "$i" -eq "$step" ]; then
|
|
143
|
+
pf="$curpatch"
|
|
144
|
+
else
|
|
145
|
+
ref="refs/review-edits/$src/$i"
|
|
146
|
+
if git rev-parse --verify --quiet "$ref" >/dev/null; then
|
|
147
|
+
git diff --binary "${ref}^" "$ref" >"$ppatch"
|
|
148
|
+
pf="$ppatch"
|
|
149
|
+
else
|
|
150
|
+
pf=""
|
|
151
|
+
fi
|
|
152
|
+
fi
|
|
153
|
+
if [ -n "$pf" ] && [ -s "$pf" ]; then
|
|
154
|
+
# A --3way apply that conflicts leaves unmerged entries, which write-tree
|
|
155
|
+
# refuses; so apply, then try to capture the tree. On success keep it as
|
|
156
|
+
# the new baseline; on a genuine conflict roll the index back to the last
|
|
157
|
+
# clean tree and note the edit as omitted (git review finish would leave you
|
|
158
|
+
# conflict markers to resolve — a preview cannot, so it skips it).
|
|
159
|
+
if GIT_INDEX_FILE="$idx2" git apply --cached --3way --binary - <"$pf" 2>/dev/null &&
|
|
160
|
+
t="$(GIT_INDEX_FILE="$idx2" git write-tree 2>/dev/null)"; then
|
|
161
|
+
good="$t"
|
|
162
|
+
else
|
|
163
|
+
conflict=1
|
|
164
|
+
GIT_INDEX_FILE="$idx2" git read-tree "$good"
|
|
165
|
+
fi
|
|
166
|
+
fi
|
|
167
|
+
i=$((i + 1))
|
|
168
|
+
done
|
|
169
|
+
tree="$good"
|
|
170
|
+
else
|
|
171
|
+
tree="$(worktree_tree)"
|
|
172
|
+
conflict=0
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
# A conflict note is about edits, not the diff, so it must be reported whether or
|
|
176
|
+
# not anything could be applied — otherwise an all-overlapping case would wrongly
|
|
177
|
+
# look like "no edits".
|
|
178
|
+
if [ "$conflict" -eq 1 ]; then
|
|
179
|
+
echo "note: some edits overlap the PR tip; git review finish will need a 3-way merge to apply them (they are omitted below)" >&2
|
|
180
|
+
fi
|
|
181
|
+
|
|
182
|
+
if git diff --quiet "$tip" "$tree"; then
|
|
183
|
+
if [ "$conflict" -eq 1 ]; then
|
|
184
|
+
echo "all your edits overlap the PR tip; nothing previews cleanly — run git review finish to resolve them with a 3-way merge" >&2
|
|
185
|
+
else
|
|
186
|
+
echo "no review changes yet — edit some files, then run $prog again"
|
|
187
|
+
fi
|
|
188
|
+
exit 0
|
|
189
|
+
fi
|
|
190
|
+
|
|
191
|
+
if [ "$stat" -eq 1 ]; then
|
|
192
|
+
git diff --stat "$tip" "$tree"
|
|
193
|
+
else
|
|
194
|
+
git diff "$tip" "$tree"
|
|
195
|
+
fi
|