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,167 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
#
|
|
3
|
+
# git review save — pause the review on the current branch and put it aside.
|
|
4
|
+
#
|
|
5
|
+
# A review in progress lives on a review/<branch> branch. Saving it converts that
|
|
6
|
+
# branch into review-saved/<branch> and returns you to where you started, so you
|
|
7
|
+
# can pick up something else and resume later with git review continue.
|
|
8
|
+
#
|
|
9
|
+
# The saved review carries everything needed to resume exactly where you left off:
|
|
10
|
+
# whole mode the staged PR diff and your uncommitted edits, captured as a
|
|
11
|
+
# commit whose diff against the tip is just your edits.
|
|
12
|
+
# step mode the step you are on, the edits on it, and every edit you have
|
|
13
|
+
# banked on other commits — the banked-edit refs are moved out of
|
|
14
|
+
# refs/review-edits/ (which git review clean prunes) into
|
|
15
|
+
# refs/review-saved-edits/ so a git review clean never touches them.
|
|
16
|
+
#
|
|
17
|
+
set -eu
|
|
18
|
+
|
|
19
|
+
prog="git review save"
|
|
20
|
+
|
|
21
|
+
usage() {
|
|
22
|
+
cat <<EOF
|
|
23
|
+
usage: $prog
|
|
24
|
+
|
|
25
|
+
Pause the review on the current review/* branch: stash it as review-saved/<branch>
|
|
26
|
+
and return to the branch you started from. Resume it later with git review continue.
|
|
27
|
+
EOF
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
die() {
|
|
31
|
+
echo "error: $1" >&2
|
|
32
|
+
exit 1
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
case "${1:-}" in
|
|
36
|
+
-h | --h)
|
|
37
|
+
usage
|
|
38
|
+
exit 0
|
|
39
|
+
;;
|
|
40
|
+
"") ;;
|
|
41
|
+
*)
|
|
42
|
+
echo "error: unexpected argument $1" >&2
|
|
43
|
+
usage >&2
|
|
44
|
+
exit 1
|
|
45
|
+
;;
|
|
46
|
+
esac
|
|
47
|
+
|
|
48
|
+
cur="$(git symbolic-ref --quiet --short HEAD || true)"
|
|
49
|
+
case "$cur" in
|
|
50
|
+
review/*) ;;
|
|
51
|
+
*)
|
|
52
|
+
echo "error: not on a review/* branch (HEAD is ${cur:-detached})" >&2
|
|
53
|
+
exit 1
|
|
54
|
+
;;
|
|
55
|
+
esac
|
|
56
|
+
|
|
57
|
+
src="$(git config "branch.$cur.reviewsource" || true)"
|
|
58
|
+
tip="$(git config "branch.$cur.reviewtip" || true)"
|
|
59
|
+
if [ -z "$src" ] || [ -z "$tip" ]; then
|
|
60
|
+
die "missing review metadata; was $cur created with git review start?"
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
saved="review-saved/$src"
|
|
64
|
+
git rev-parse --verify --quiet "refs/heads/$saved" >/dev/null &&
|
|
65
|
+
die "$saved already exists; resume it with git review continue or discard it with git review forget --saved $src"
|
|
66
|
+
|
|
67
|
+
mode="$(git config "branch.$cur.reviewmode" || echo whole)"
|
|
68
|
+
|
|
69
|
+
# Where to return to once the review is put aside — the branch you started from,
|
|
70
|
+
# falling back to the base branch, exactly like git review abort.
|
|
71
|
+
ret="$(git config "branch.$cur.reviewreturn" || true)"
|
|
72
|
+
base="$(git config "branch.$cur.reviewbase" || true)"
|
|
73
|
+
target=""
|
|
74
|
+
if [ -n "$ret" ] && git rev-parse --verify --quiet "refs/heads/$ret" >/dev/null; then
|
|
75
|
+
target="$ret"
|
|
76
|
+
elif [ -n "$base" ] && git rev-parse --verify --quiet "refs/heads/$base" >/dev/null; then
|
|
77
|
+
target="$base"
|
|
78
|
+
fi
|
|
79
|
+
[ -n "$target" ] || die "could not determine a branch to return to; switch away manually first"
|
|
80
|
+
|
|
81
|
+
# copy_meta <key>: copy branch.<cur>.<key> to branch.<saved>.<key> when set.
|
|
82
|
+
# The explicit if (rather than a && one-liner) keeps the function's exit status 0
|
|
83
|
+
# when the key is absent, so it does not trip set -e for the optional keys.
|
|
84
|
+
copy_meta() {
|
|
85
|
+
v="$(git config "branch.$cur.$1" || true)"
|
|
86
|
+
if [ -n "$v" ]; then
|
|
87
|
+
git config "branch.$saved.$1" "$v"
|
|
88
|
+
fi
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if [ "$mode" = "step" ]; then
|
|
92
|
+
start="$(git config "branch.$cur.reviewstart" || true)"
|
|
93
|
+
count="$(git config "branch.$cur.reviewcount" || true)"
|
|
94
|
+
step="$(git config "branch.$cur.reviewstep" || true)"
|
|
95
|
+
# A key deleted by a hand-edit (while reviewmode stays "step") would otherwise
|
|
96
|
+
# let set -e kill the bare read silently mid-script; read with || true and report
|
|
97
|
+
# it. Unlike status/preview there is no later numeric guard, so cover step too.
|
|
98
|
+
if [ -z "$start" ] || [ -z "$count" ] || [ -z "$step" ]; then
|
|
99
|
+
die "missing review metadata; was $cur created with git review start?"
|
|
100
|
+
fi
|
|
101
|
+
commits="$(git rev-list --reverse --first-parent --no-merges "$start..$tip")"
|
|
102
|
+
|
|
103
|
+
# Bank the edits on the commit we are sitting on, the same way review-next does
|
|
104
|
+
# before it moves, so the current step's work joins the other banked steps.
|
|
105
|
+
cstep="$(printf '%s\n' "$commits" | sed -n "${step}p")"
|
|
106
|
+
git add -A
|
|
107
|
+
tree="$(git write-tree)"
|
|
108
|
+
if [ "$tree" != "$(git rev-parse "$cstep^{tree}")" ]; then
|
|
109
|
+
edit="$(git commit-tree "$tree" -p "$cstep" -m "review edits step $step")"
|
|
110
|
+
git update-ref "refs/review-edits/$src/$step" "$edit"
|
|
111
|
+
else
|
|
112
|
+
# Reverting the step back to a clean tree must clear any edits we banked
|
|
113
|
+
# earlier, or they resurrect when the saved review is restored.
|
|
114
|
+
git update-ref -d "refs/review-edits/$src/$step" 2>/dev/null || true
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
# Move the banked edits aside so git review clean (which prunes refs/review-edits/)
|
|
118
|
+
# leaves the saved review intact, and so they travel with it.
|
|
119
|
+
git for-each-ref --format='%(refname)' "refs/review-edits/$src/" | while read -r ref; do
|
|
120
|
+
n="${ref##*/}"
|
|
121
|
+
git update-ref "refs/review-saved-edits/$src/$n" "$(git rev-parse "$ref")"
|
|
122
|
+
git update-ref -d "$ref"
|
|
123
|
+
done
|
|
124
|
+
|
|
125
|
+
# The visible branch is just an anchor + metadata holder; point it at the tip.
|
|
126
|
+
git update-ref "refs/heads/$saved" "$tip"
|
|
127
|
+
git config "branch.$saved.reviewmode" step
|
|
128
|
+
copy_meta reviewstart
|
|
129
|
+
copy_meta reviewcount
|
|
130
|
+
copy_meta reviewstep
|
|
131
|
+
note="on step $step/$count"
|
|
132
|
+
else
|
|
133
|
+
# whole mode: HEAD is the review's lower bound, the index holds the staged PR
|
|
134
|
+
# diff and the working tree holds that plus your edits. Capture the working tree
|
|
135
|
+
# as a commit parented on the tip, so its diff against the tip is exactly your
|
|
136
|
+
# edits — review-continue replays that to restore the editable state.
|
|
137
|
+
lower="$(git rev-parse HEAD)"
|
|
138
|
+
git add -A
|
|
139
|
+
tree="$(git write-tree)"
|
|
140
|
+
# Parent the saved commit on both the tip and the lower bound. The tip parent
|
|
141
|
+
# makes "git diff tip..saved" exactly your edits; the lower parent keeps the
|
|
142
|
+
# lower bound reachable even when it is a synthetic merge-tree commit (the
|
|
143
|
+
# folded-base case in git review start), so a gc between save and continue cannot
|
|
144
|
+
# prune it out from under "git reset --soft $lower".
|
|
145
|
+
commit="$(git commit-tree "$tree" -p "$tip" -p "$lower" -m "git review save: $src")"
|
|
146
|
+
git update-ref "refs/heads/$saved" "$commit"
|
|
147
|
+
git config "branch.$saved.reviewmode" whole
|
|
148
|
+
git config "branch.$saved.reviewsavedlower" "$lower"
|
|
149
|
+
note="whole-PR review"
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
# Metadata common to both modes.
|
|
153
|
+
copy_meta reviewsource
|
|
154
|
+
copy_meta reviewbase
|
|
155
|
+
copy_meta reviewtip
|
|
156
|
+
copy_meta reviewprevreviewed
|
|
157
|
+
copy_meta reviewlocal
|
|
158
|
+
git config "branch.$saved.reviewreturn" "$target"
|
|
159
|
+
|
|
160
|
+
# Leave the review branch: drop its working-tree changes (captured above), return
|
|
161
|
+
# to the starting branch and delete review/<src>. Its config goes with it; the
|
|
162
|
+
# saved copy already holds everything.
|
|
163
|
+
git switch -q --discard-changes "$target"
|
|
164
|
+
git branch -D "$cur" >/dev/null
|
|
165
|
+
|
|
166
|
+
echo "saved review of $src ($note) as $saved; returned to $target"
|
|
167
|
+
echo "resume it with git review continue"
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
#
|
|
3
|
+
# git review start — stage a pull request diff on a review/<branch> branch.
|
|
4
|
+
#
|
|
5
|
+
# Two independent axes:
|
|
6
|
+
# range — where the review starts: the merge-base with the base branch
|
|
7
|
+
# (default), your last review (--delta), or an explicit commit
|
|
8
|
+
# (--from <commit>).
|
|
9
|
+
# layout — how you see it: the whole range as one staged diff (default), or
|
|
10
|
+
# one commit at a time (--step, advance with git review next).
|
|
11
|
+
#
|
|
12
|
+
set -eu
|
|
13
|
+
|
|
14
|
+
prog="git review start"
|
|
15
|
+
|
|
16
|
+
usage() {
|
|
17
|
+
cat <<EOF
|
|
18
|
+
usage: $prog [<branch>] [<base> | --base <base> | --delta | --from <commit>] [--step] [--local]
|
|
19
|
+
|
|
20
|
+
Stage a pull request diff on a review/<branch> branch for inline review.
|
|
21
|
+
With no <branch>, review the branch you currently have checked out.
|
|
22
|
+
|
|
23
|
+
<branch> branch to review; omit it to review the current branch
|
|
24
|
+
<base> commit-ish to diff against — a branch, tag or commit
|
|
25
|
+
(required for a full review; set a default with:
|
|
26
|
+
git config reviewworkflow.base <branch>)
|
|
27
|
+
--base <base> the base to diff against; use it to pass a base while
|
|
28
|
+
letting <branch> default to the current branch
|
|
29
|
+
--delta review only the commits added since your last review
|
|
30
|
+
--from <commit> review only the commits after <commit>
|
|
31
|
+
--step review one commit at a time; advance with git review next
|
|
32
|
+
--local review your local branches directly, without fetching
|
|
33
|
+
EOF
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
die() {
|
|
37
|
+
echo "error: $1" >&2
|
|
38
|
+
exit 1
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
src=""
|
|
42
|
+
base=""
|
|
43
|
+
base_explicit=0
|
|
44
|
+
from=""
|
|
45
|
+
delta=0
|
|
46
|
+
step=0
|
|
47
|
+
local_mode=0
|
|
48
|
+
while [ $# -gt 0 ]; do
|
|
49
|
+
case "$1" in
|
|
50
|
+
-h | --h)
|
|
51
|
+
usage
|
|
52
|
+
exit 0
|
|
53
|
+
;;
|
|
54
|
+
-d | --delta) delta=1 ;;
|
|
55
|
+
--step) step=1 ;;
|
|
56
|
+
--local) local_mode=1 ;;
|
|
57
|
+
--base)
|
|
58
|
+
shift
|
|
59
|
+
[ $# -gt 0 ] || die "--base requires a base"
|
|
60
|
+
[ -n "$1" ] || die "--base requires a base"
|
|
61
|
+
[ "$base_explicit" -eq 0 ] || die "base given more than once"
|
|
62
|
+
base="$1"
|
|
63
|
+
base_explicit=1
|
|
64
|
+
;;
|
|
65
|
+
--base=*)
|
|
66
|
+
[ "$base_explicit" -eq 0 ] || die "base given more than once"
|
|
67
|
+
base="${1#--base=}"
|
|
68
|
+
[ -n "$base" ] || die "--base requires a base"
|
|
69
|
+
base_explicit=1
|
|
70
|
+
;;
|
|
71
|
+
--from)
|
|
72
|
+
shift
|
|
73
|
+
[ $# -gt 0 ] || die "--from requires a commit"
|
|
74
|
+
from="$1"
|
|
75
|
+
[ -n "$from" ] || die "--from requires a commit"
|
|
76
|
+
;;
|
|
77
|
+
--from=*)
|
|
78
|
+
from="${1#--from=}"
|
|
79
|
+
[ -n "$from" ] || die "--from requires a commit"
|
|
80
|
+
;;
|
|
81
|
+
--)
|
|
82
|
+
# End-of-options, the git convention: everything after is positional,
|
|
83
|
+
# so a branch whose name starts with "-" (or collides with a flag) can
|
|
84
|
+
# still be reviewed, e.g. git review start -- --weird develop.
|
|
85
|
+
shift
|
|
86
|
+
while [ $# -gt 0 ]; do
|
|
87
|
+
if [ -z "$src" ]; then
|
|
88
|
+
src="$1"
|
|
89
|
+
elif [ -z "$base" ]; then
|
|
90
|
+
base="$1"
|
|
91
|
+
base_explicit=1
|
|
92
|
+
else
|
|
93
|
+
die "unexpected argument $1"
|
|
94
|
+
fi
|
|
95
|
+
shift
|
|
96
|
+
done
|
|
97
|
+
break
|
|
98
|
+
;;
|
|
99
|
+
-*)
|
|
100
|
+
echo "error: unknown option $1" >&2
|
|
101
|
+
usage >&2
|
|
102
|
+
exit 1
|
|
103
|
+
;;
|
|
104
|
+
*)
|
|
105
|
+
if [ -z "$src" ]; then
|
|
106
|
+
src="$1"
|
|
107
|
+
elif [ -z "$base" ]; then
|
|
108
|
+
base="$1"
|
|
109
|
+
base_explicit=1
|
|
110
|
+
else
|
|
111
|
+
die "unexpected argument $1"
|
|
112
|
+
fi
|
|
113
|
+
;;
|
|
114
|
+
esac
|
|
115
|
+
shift
|
|
116
|
+
done
|
|
117
|
+
|
|
118
|
+
if [ "$delta" -eq 1 ] && [ -n "$from" ]; then
|
|
119
|
+
die "use only one of --delta and --from"
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
if [ "$base_explicit" -eq 1 ] && { [ "$delta" -eq 1 ] || [ -n "$from" ]; }; then
|
|
123
|
+
die "base is ignored with --delta/--from; pass only one"
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
git rev-parse --git-dir >/dev/null 2>&1 || die "not a git repository"
|
|
127
|
+
|
|
128
|
+
# Shared helpers, sourced from the libexec directory the dispatcher points us at.
|
|
129
|
+
# shellcheck source=../git-review-lib.sh
|
|
130
|
+
. "${GIT_REVIEW_LIBEXEC:?}/git-review-lib.sh"
|
|
131
|
+
|
|
132
|
+
# With no <branch>, review the branch you currently have checked out. This is
|
|
133
|
+
# git's own default — push, status, log and rebase all act on the current branch
|
|
134
|
+
# when you name nothing. It only resolves the name; the mode (remote vs --local)
|
|
135
|
+
# is still chosen by flags. The check against the review/*, review-saved/* and
|
|
136
|
+
# review-fixes/* namespaces avoids the nonsense of reviewing a review branch.
|
|
137
|
+
if [ -z "$src" ]; then
|
|
138
|
+
cur="$(git symbolic-ref --quiet --short HEAD || true)"
|
|
139
|
+
[ -n "$cur" ] || die "no branch given and HEAD is detached; name a branch to review"
|
|
140
|
+
case "$cur" in
|
|
141
|
+
review/* | review-saved/* | review-fixes/*) die "refusing to review a review branch (HEAD is $cur); name a branch to review" ;;
|
|
142
|
+
esac
|
|
143
|
+
src="$cur"
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
# Remote to review from; defaults to origin, override with reviewworkflow.remote.
|
|
147
|
+
remote="$(git config reviewworkflow.remote || echo origin)"
|
|
148
|
+
|
|
149
|
+
# Remember where to return to when the review is aborted.
|
|
150
|
+
returnbranch="$(git symbolic-ref --quiet --short HEAD || true)"
|
|
151
|
+
|
|
152
|
+
if [ -z "$base" ]; then
|
|
153
|
+
base="$(git config reviewworkflow.base || true)"
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
if ! git diff --quiet || ! git diff --cached --quiet; then
|
|
157
|
+
die "you have local changes; commit or stash them first"
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
# --local reviews your local branches directly; the default reviews the remote's.
|
|
161
|
+
# The two are kept apart everywhere: which ref the tip comes from, how it is named
|
|
162
|
+
# in messages, and the delta marker — local markers live in their own config
|
|
163
|
+
# section (reviewworkflowlocal.*) so a local and a remote review of the same branch
|
|
164
|
+
# name never overwrite each other's progress. A separate section, rather than a
|
|
165
|
+
# reviewworkflow.<src>.local.reviewed key, is deliberate: the latter would be
|
|
166
|
+
# ambiguous for a branch literally named "<src>.local".
|
|
167
|
+
if [ "$local_mode" -eq 1 ]; then
|
|
168
|
+
srcref="refs/heads/$src"
|
|
169
|
+
srclabel="$src"
|
|
170
|
+
markerkey="reviewworkflowlocal.$src.reviewed"
|
|
171
|
+
else
|
|
172
|
+
git fetch --quiet "$remote" || die "could not update from $remote"
|
|
173
|
+
srcref="refs/remotes/$remote/$src"
|
|
174
|
+
srclabel="$remote/$src"
|
|
175
|
+
markerkey="reviewworkflow.$src.reviewed"
|
|
176
|
+
fi
|
|
177
|
+
|
|
178
|
+
# Resolve the base into a ref and a human label. A base is normally a branch, so
|
|
179
|
+
# try the branch namespace first — the remote's copy, or the local branch with
|
|
180
|
+
# --local — to keep the familiar origin/<base> labels and the merged-base folding
|
|
181
|
+
# below. Anything that is not a branch there still works as a read-only lower
|
|
182
|
+
# bound via any commit-ish: a tag, a SHA, or an origin/<x> spelled out in full.
|
|
183
|
+
# baseref stays empty when the base does not resolve at all; the full-review path
|
|
184
|
+
# treats that as fatal ("not found"), while the delta/from folding treats an
|
|
185
|
+
# unresolvable base as a no-op, exactly as before.
|
|
186
|
+
baseref=""
|
|
187
|
+
baselabel="$base"
|
|
188
|
+
if [ -n "$base" ]; then
|
|
189
|
+
if [ "$local_mode" -eq 1 ]; then
|
|
190
|
+
if git rev-parse --verify --quiet "refs/heads/$base^{commit}" >/dev/null; then
|
|
191
|
+
baseref="refs/heads/$base"
|
|
192
|
+
fi
|
|
193
|
+
else
|
|
194
|
+
if git rev-parse --verify --quiet "refs/remotes/$remote/$base^{commit}" >/dev/null; then
|
|
195
|
+
baseref="refs/remotes/$remote/$base"
|
|
196
|
+
baselabel="$remote/$base"
|
|
197
|
+
fi
|
|
198
|
+
fi
|
|
199
|
+
if [ -z "$baseref" ]; then
|
|
200
|
+
bcommit="$(git rev-parse --verify --quiet "$base^{commit}" || true)"
|
|
201
|
+
if [ -n "$bcommit" ]; then
|
|
202
|
+
baseref="$bcommit"
|
|
203
|
+
fi
|
|
204
|
+
fi
|
|
205
|
+
fi
|
|
206
|
+
|
|
207
|
+
git rev-parse --verify --quiet "$srcref" >/dev/null ||
|
|
208
|
+
die "$srclabel not found"
|
|
209
|
+
|
|
210
|
+
rb="review/$src"
|
|
211
|
+
git rev-parse --verify --quiet "refs/heads/$rb" >/dev/null &&
|
|
212
|
+
die "$rb already exists; run git review clean $src first"
|
|
213
|
+
git rev-parse --verify --quiet "refs/heads/review-saved/$src" >/dev/null &&
|
|
214
|
+
die "you have a saved review of $src; resume it with git review continue $src or discard it with git review forget --saved $src"
|
|
215
|
+
|
|
216
|
+
tip="$(git rev-parse "$srcref")"
|
|
217
|
+
prev="$(git config "$markerkey" || true)"
|
|
218
|
+
|
|
219
|
+
# A remote review targets the remote's copy of the branch, never your local one,
|
|
220
|
+
# which git review start never updates. If a local branch of the same name exists
|
|
221
|
+
# and points elsewhere, you are reviewing a different snapshot than the one you
|
|
222
|
+
# have checked out — and a later git review finish --onto-source, which writes to
|
|
223
|
+
# the local branch, will refuse unless it sits at the reviewed tip. Warn rather than
|
|
224
|
+
# silently review the wrong thing; --local reviews what you have checked out.
|
|
225
|
+
if [ "$local_mode" -eq 0 ]; then
|
|
226
|
+
localtip="$(git rev-parse --verify --quiet "refs/heads/$src" || true)"
|
|
227
|
+
if [ -n "$localtip" ] && [ "$localtip" != "$tip" ]; then
|
|
228
|
+
echo "note: reviewing $srclabel ($(git rev-parse --short "$tip")), which differs from your local $src ($(git rev-parse --short "$localtip")); use --local to review what you have checked out" >&2
|
|
229
|
+
fi
|
|
230
|
+
fi
|
|
231
|
+
|
|
232
|
+
# Resolve the lower bound of the review range (exclusive).
|
|
233
|
+
if [ "$delta" -eq 1 ]; then
|
|
234
|
+
[ -n "$prev" ] || die "no previous review of $src recorded; run a full review first"
|
|
235
|
+
[ "$prev" != "$tip" ] || die "no new commits since your last review of $src"
|
|
236
|
+
git merge-base --is-ancestor "$prev" "$tip" ||
|
|
237
|
+
die "$src was force-pushed since your last review; run a full review instead"
|
|
238
|
+
start="$prev"
|
|
239
|
+
range="since last review ($(git rev-parse --short "$prev"))"
|
|
240
|
+
elif [ -n "$from" ]; then
|
|
241
|
+
start="$(git rev-parse --verify --quiet "$from^{commit}")" || die "unknown commit: $from"
|
|
242
|
+
git merge-base --is-ancestor "$start" "$tip" ||
|
|
243
|
+
die "$from is not an ancestor of $srclabel"
|
|
244
|
+
[ "$start" != "$tip" ] || die "no commits to review after $from"
|
|
245
|
+
range="since $(git rev-parse --short "$start")"
|
|
246
|
+
else
|
|
247
|
+
[ -n "$base" ] ||
|
|
248
|
+
die "no base branch set; pass one as an argument or with --base, or run: git config reviewworkflow.base <branch>"
|
|
249
|
+
[ -n "$baseref" ] ||
|
|
250
|
+
die "$baselabel not found"
|
|
251
|
+
start="$(git merge-base "$baseref" "$srcref")" ||
|
|
252
|
+
die "could not compute merge-base with $baselabel"
|
|
253
|
+
# Nothing to review when the source is already at the base (e.g. running on the
|
|
254
|
+
# base branch itself, or a branch fully merged into it). Mirror the explicit
|
|
255
|
+
# guards on --delta/--from instead of creating an empty review/ branch.
|
|
256
|
+
[ "$start" != "$tip" ] ||
|
|
257
|
+
die "no commits to review; $srclabel is already at $baselabel"
|
|
258
|
+
range="vs $baselabel"
|
|
259
|
+
if [ -n "$prev" ] && [ "$prev" != "$tip" ] && git merge-base --is-ancestor "$prev" "$tip"; then
|
|
260
|
+
n="$(git rev-list --count "$prev..$tip")"
|
|
261
|
+
echo "note: previously reviewed at $(git rev-parse --short "$prev"); $n new commit(s) since (use --delta to review only those)" >&2
|
|
262
|
+
fi
|
|
263
|
+
fi
|
|
264
|
+
|
|
265
|
+
if [ "$step" -eq 1 ]; then
|
|
266
|
+
# --first-parent --no-merges: walk only the branch's own commits, skipping
|
|
267
|
+
# merge commits (e.g. merges of the base branch) so changes that came from
|
|
268
|
+
# the base are never shown as a step.
|
|
269
|
+
commits="$(git rev-list --reverse --first-parent --no-merges "$start..$tip")"
|
|
270
|
+
[ -n "$commits" ] || die "no commits to review in range"
|
|
271
|
+
count="$(printf '%s\n' "$commits" | wc -l | tr -d ' ')"
|
|
272
|
+
first="$(printf '%s\n' "$commits" | sed -n '1p')"
|
|
273
|
+
|
|
274
|
+
git switch -q -c "$rb" "$first" || die "could not create $rb"
|
|
275
|
+
git reset --soft HEAD^
|
|
276
|
+
git config "branch.$rb.reviewsource" "$src"
|
|
277
|
+
git config "branch.$rb.reviewbase" "$base"
|
|
278
|
+
git config "branch.$rb.reviewtip" "$tip"
|
|
279
|
+
git config "branch.$rb.reviewstart" "$start"
|
|
280
|
+
git config "branch.$rb.reviewmode" "step"
|
|
281
|
+
git config "branch.$rb.reviewcount" "$count"
|
|
282
|
+
git config "branch.$rb.reviewstep" "1"
|
|
283
|
+
if [ -n "$prev" ]; then
|
|
284
|
+
git config "branch.$rb.reviewprevreviewed" "$prev"
|
|
285
|
+
fi
|
|
286
|
+
if [ "$local_mode" -eq 1 ]; then
|
|
287
|
+
git config "branch.$rb.reviewlocal" "1"
|
|
288
|
+
fi
|
|
289
|
+
git config "$markerkey" "$tip"
|
|
290
|
+
if [ -n "$returnbranch" ]; then
|
|
291
|
+
git config "branch.$rb.reviewreturn" "$returnbranch"
|
|
292
|
+
fi
|
|
293
|
+
|
|
294
|
+
echo "$rb ready ($range) — walking $count commit(s) one at a time"
|
|
295
|
+
show_commit "$first" 1 "$count"
|
|
296
|
+
exit 0
|
|
297
|
+
fi
|
|
298
|
+
|
|
299
|
+
# Lower bound for the staged diff. Normally it is the range start, but if the
|
|
300
|
+
# base branch has been merged into the PR, fold that already-merged base content
|
|
301
|
+
# into the lower bound so changes that came from the base do not appear as part
|
|
302
|
+
# of the review. This is a no-op when the base was not merged in (it never
|
|
303
|
+
# invents deletions), and degrades gracefully on git without merge-tree.
|
|
304
|
+
lower="$start"
|
|
305
|
+
if [ -n "$baseref" ]; then
|
|
306
|
+
mb="$(git merge-base "$baseref" "$tip" 2>/dev/null || true)"
|
|
307
|
+
if [ -n "$mb" ] && ! git merge-base --is-ancestor "$mb" "$start"; then
|
|
308
|
+
if ltree="$(git merge-tree --write-tree "$start" "$mb" 2>/dev/null)"; then
|
|
309
|
+
lower="$(git commit-tree "$ltree" -p "$start" -m 'review lower bound')"
|
|
310
|
+
else
|
|
311
|
+
echo "note: could not exclude merged base content from the review diff" >&2
|
|
312
|
+
fi
|
|
313
|
+
fi
|
|
314
|
+
fi
|
|
315
|
+
|
|
316
|
+
git switch -q -c "$rb" "$tip" || die "could not create $rb"
|
|
317
|
+
git reset -q --soft "$lower"
|
|
318
|
+
|
|
319
|
+
git config "branch.$rb.reviewsource" "$src"
|
|
320
|
+
git config "branch.$rb.reviewbase" "$base"
|
|
321
|
+
git config "branch.$rb.reviewtip" "$tip"
|
|
322
|
+
if [ -n "$prev" ]; then
|
|
323
|
+
git config "branch.$rb.reviewprevreviewed" "$prev"
|
|
324
|
+
fi
|
|
325
|
+
if [ "$local_mode" -eq 1 ]; then
|
|
326
|
+
git config "branch.$rb.reviewlocal" "1"
|
|
327
|
+
fi
|
|
328
|
+
git config "$markerkey" "$tip"
|
|
329
|
+
if [ -n "$returnbranch" ]; then
|
|
330
|
+
git config "branch.$rb.reviewreturn" "$returnbranch"
|
|
331
|
+
fi
|
|
332
|
+
|
|
333
|
+
echo "$rb ready ($range) — the staged diff is the PR; edit, then run git review finish"
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
#
|
|
3
|
+
# git review status — show the state of the review on the current branch.
|
|
4
|
+
#
|
|
5
|
+
set -eu
|
|
6
|
+
|
|
7
|
+
prog="git review status"
|
|
8
|
+
|
|
9
|
+
usage() {
|
|
10
|
+
cat <<EOF
|
|
11
|
+
usage: $prog
|
|
12
|
+
|
|
13
|
+
Show the state of the review on the current review/* branch: source PR, mode,
|
|
14
|
+
and (in commit-by-commit mode) which commit you are on and which steps have
|
|
15
|
+
banked edits.
|
|
16
|
+
EOF
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
case "${1:-}" in
|
|
20
|
+
-h | --h)
|
|
21
|
+
usage
|
|
22
|
+
exit 0
|
|
23
|
+
;;
|
|
24
|
+
"") ;;
|
|
25
|
+
*)
|
|
26
|
+
echo "error: unexpected argument $1" >&2
|
|
27
|
+
usage >&2
|
|
28
|
+
exit 1
|
|
29
|
+
;;
|
|
30
|
+
esac
|
|
31
|
+
|
|
32
|
+
cur="$(git symbolic-ref --quiet --short HEAD || true)"
|
|
33
|
+
case "$cur" in
|
|
34
|
+
review/*) ;;
|
|
35
|
+
*)
|
|
36
|
+
echo "not on a review/* branch (HEAD is ${cur:-detached})" >&2
|
|
37
|
+
exit 1
|
|
38
|
+
;;
|
|
39
|
+
esac
|
|
40
|
+
|
|
41
|
+
src="$(git config "branch.$cur.reviewsource" || true)"
|
|
42
|
+
tip="$(git config "branch.$cur.reviewtip" || true)"
|
|
43
|
+
mode="$(git config "branch.$cur.reviewmode" || echo whole)"
|
|
44
|
+
if [ -z "$src" ] || [ -z "$tip" ]; then
|
|
45
|
+
echo "error: missing review metadata; was $cur created with git review start?" >&2
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
printf 'review of %s (tip %s)\n' "$src" "$(git rev-parse --short "$tip")"
|
|
50
|
+
printf ' branch %s\n' "$cur"
|
|
51
|
+
|
|
52
|
+
if [ "$mode" = "step" ]; then
|
|
53
|
+
count="$(git config "branch.$cur.reviewcount" || true)"
|
|
54
|
+
step="$(git config "branch.$cur.reviewstep" || true)"
|
|
55
|
+
start="$(git config "branch.$cur.reviewstart" || true)"
|
|
56
|
+
# A key deleted by a hand-edit (while reviewmode stays "step") would otherwise
|
|
57
|
+
# let set -e kill the bare read silently mid-script; read with || true and report
|
|
58
|
+
# it. reviewstep falls through to the numeric guard below for a precise message.
|
|
59
|
+
if [ -z "$count" ] || [ -z "$start" ]; then
|
|
60
|
+
echo "error: missing review metadata; was $cur created with git review start?" >&2
|
|
61
|
+
exit 1
|
|
62
|
+
fi
|
|
63
|
+
commits="$(git rev-list --reverse --first-parent --no-merges "$start..$tip")"
|
|
64
|
+
# Guard against a step that maps to no commit (corrupt config, hand-edited
|
|
65
|
+
# metadata): otherwise cnow ends up empty and git rev-parse '' crashes while
|
|
66
|
+
# the printf around it still "succeeds", printing a corrupt status line.
|
|
67
|
+
total="$(printf '%s\n' "$commits" | grep -c .)"
|
|
68
|
+
case "$step" in
|
|
69
|
+
'' | *[!0-9]*)
|
|
70
|
+
echo "error: corrupt review metadata: reviewstep is '$step', not a positive integer" >&2
|
|
71
|
+
exit 1
|
|
72
|
+
;;
|
|
73
|
+
esac
|
|
74
|
+
if [ "$step" -lt 1 ] || [ "$step" -gt "$total" ]; then
|
|
75
|
+
echo "error: review step $step out of range (1..$total) — corrupt metadata?" >&2
|
|
76
|
+
exit 1
|
|
77
|
+
fi
|
|
78
|
+
cnow="$(printf '%s\n' "$commits" | sed -n "${step}p")"
|
|
79
|
+
printf ' mode step [%s/%s] on %s %s\n' "$step" "$count" \
|
|
80
|
+
"$(git rev-parse --short "$cnow")" "$(git log -1 --format='%s' "$cnow")"
|
|
81
|
+
|
|
82
|
+
banked=""
|
|
83
|
+
i=1
|
|
84
|
+
while [ "$i" -le "$count" ]; do
|
|
85
|
+
if git rev-parse --verify --quiet "refs/review-edits/$src/$i" >/dev/null; then
|
|
86
|
+
banked="$banked $i"
|
|
87
|
+
fi
|
|
88
|
+
i=$((i + 1))
|
|
89
|
+
done
|
|
90
|
+
printf ' banked %s\n' "${banked:- none}"
|
|
91
|
+
|
|
92
|
+
hint="git review next"
|
|
93
|
+
[ "$step" -gt 1 ] && hint="$hint / git review prev"
|
|
94
|
+
printf ' next edit, then %s (git review finish when done)\n' "$hint"
|
|
95
|
+
else
|
|
96
|
+
base="$(git config "branch.$cur.reviewbase" || true)"
|
|
97
|
+
printf ' mode whole%s\n' "${base:+ (base $base)}"
|
|
98
|
+
printf ' next edit, then git review finish\n'
|
|
99
|
+
fi
|