Feedback & approvals
Review feedback in GitStack lives on threads attached to a single review. You read the
threads with gs review comments, start one with gs review comment, and answer with
gs review reply or gs review resolve as you push fixes. Approvals ride the same
surface: gs review approve marks a review good to land, and a later patchset can clear
that approval. Every command here takes a review ref like r1 (see
reference syntax) and infers the repo from your git origin unless
you pass --repo.
gs review comments#
List the comment threads on a review.
gs review comments [r<n>] [--unresolved] [--patchset N] [--filter human|agent|all]
| Flag | Description |
|---|---|
--unresolved |
Show only threads that are still open. |
--patchset N |
View threads against patchset N. 0, the default, means the latest. |
--filter <who> |
Limit to human, agent, or all threads. Default is all. |
--repo <name> |
Repo name. Default is inferred from origin. |
--unresolved--patchset N0, the default, means the latest.--filter <who>human, agent, or all threads. Default is all.--repo <name>The review ref is optional; with no ref the command reads the review at HEAD. Each row
shows the thread id, its state (open or resolved), the author, and the comment body.
Replies print under their thread with an /reply suffix on the id.
gs review comments r1 --unresolved
Thread ids are ULIDs. They print as the shortest prefix (at least four characters) that is
unique among that review's threads, so you copy the short prefix straight into reply,
resolve, or --resolves.
r1 reads like a stack position, but review ids are global. If r1 is not on your
current branch, gs review comments prints a warning naming the branch and stack it
found, then still shows the threads. The mutating commands below refuse instead of
warning.
gs review comment#
Open a new comment thread on a review, optionally anchored to a file and line.
gs review comment [r<n>] -m "..." [--file F --line N[:M]]
| Flag | Description |
|---|---|
--message, -m |
Comment body. Opens $EDITOR when omitted. |
--file <path> |
Anchor the thread to a file, relative to the repo root. |
--line <N or N:M> |
Anchor to a single line or a range. Requires --file. |
--commit <sha> |
Commit the anchor refers to. Default is HEAD. |
--patchset N |
Patchset to attach to. Default is the latest. |
--repo <name> |
Repo name. Default is inferred from origin. |
--message, -m$EDITOR when omitted.--file <path>--line <N or N:M>--file.--commit <sha>--patchset N--repo <name>Leave off --file for a review-level thread. Add --file and --line to pin the thread
to a specific spot in the diff.
gs review comment r2 --file internal/billing/proration.go --line 42 \
-m "guard against a zero denominator here"
A line anchor only makes sense against a file, so --line without --file is an error.
Ranges use N:M, for example --line 42:57.
gs review reply#
Reply to an existing thread.
gs review reply <r<n>> <thread> -m "..."
| Flag | Description |
|---|---|
--message, -m |
Reply body. Opens $EDITOR when omitted. |
--repo <name> |
Repo name. Default is inferred from origin. |
--message, -m$EDITOR when omitted.--repo <name>Both the review ref and the thread prefix are required. The thread prefix is the short id
from gs review comments.
gs review reply r2 01H8 -m "fixed in patchset 2"
gs review resolve#
Mark a thread resolved.
gs review resolve <r<n>> <thread>
| Flag | Description |
|---|---|
--repo <name> |
Repo name. Default is inferred from origin. |
--repo <name>gs review resolve r2 01H8
A reply posted on an already-resolved thread reopens it. If you resolve a thread and then
add a closing reply, the thread comes back as open. Reply first and resolve last, or let
--resolves (below) resolve the thread at push time so you never touch it again by hand.
gs commit --resolves / gs amend --resolves#
--resolves records which threads a fix closes so the resolution rides along with your next
push. It lives on both gs commit and gs amend, takes a
thread-id prefix, and repeats.
gs amend --no-edit --resolves 01H8
gs commit -m "billing: add the missing guard" --resolves 01J3 --resolves 01H8
| Flag | Description |
|---|---|
--resolves <prefix> |
Thread id or prefix to resolve on the next push. Repeatable. |
--resolves <prefix>Each prefix expands against the threads on the review at HEAD, so run it from the review the
thread belongs to. The ids accumulate across every commit and amend (including across a
rebase conflict you resume with --continue) and drain on the next gs push. The push
prints one outcome line per id, resolved thread <id> on success and
warning: thread <id> was not resolved: <reason> when the server rejects it, so trust those
lines rather than re-checking the thread list. Pushes made on your behalf by gs sync or
autostack print the same lines.
Because the server resolves the thread as part of the push, there is no separate
gs review resolve call to sequence against a reply. This is the safe way to close
threads you also replied to.
gs review approve#
Approve a review, signalling it is good to land.
gs review approve [r<n>]
| Flag | Description |
|---|---|
--patchset N |
Patchset to approve. Default is the latest. |
--repo <name> |
Repo name. Default is inferred from origin. |
--patchset N--repo <name>The review ref is optional; with no ref the command approves the review at HEAD.
gs review approve r1
Approvals clear automatically when a new patchset changes the review's content. A
fast_forward that appends commits (extending the review) and a force_push that rewrites
them (revising it) both drop the approval, so the author cannot slip new code past a stale
LGTM. A rebase, where the diff is unchanged and only the commits underneath were rewritten,
keeps the approval. gs review comments and gs review show label each patchset with its
kind, so you can see which change reset an approval. See reviews for the
patchset model.
How many approvals a review needs is up to the repo. By default a review needs no LGTM: a
writer can land their own review, and a repo owner can land anyone's. A repo can require
LGTMs, require every requested reviewer, or hold owners to the same rule in the
[approvals] table of .gitstack/policy.toml.
Only LGTMs from people who can currently write to the repo count.
gs review unapprove#
Withdraw an approval you previously gave.
gs review unapprove [r<n>]
| Flag | Description |
|---|---|
--patchset N |
Patchset to unapprove. Default is the latest. |
--repo <name> |
Repo name. Default is inferred from origin. |
--patchset N--repo <name>gs review unapprove r1
gs review reviewers#
List the reviewers on a review, or add and remove them.
gs review reviewers <r<n>> [--add <user>] [--remove <user>]
| Flag | Description |
|---|---|
--add <user> |
Reviewer to add, by handle (or ULID). Repeatable. |
--remove <user> |
Reviewer to remove, by handle or ULID. Repeatable. |
--repo <name> |
Repo name. Default is inferred from origin. |
--add <user>--remove <user>--repo <name>The bare form lists the current reviewers. Pass --add or --remove (each repeatable, and
you can mix both in one call) to change the set. Name reviewers by handle; a ULID works too.
--remove drops the reviewer whichever spelling you or they were added with.
Requested reviewers put the review in their queue. They gate landing only when the repo's
landing policy sets require_requested_reviewers = true: then every requested reviewer who
can approve (a writer or owner other than the author) must LGTM before the review lands, and
gs submit names who it is waiting on. A requested reviewer who can only read the repo is
skipped, so they never block a land.
gs review reviewers r1
gs review reviewers r1 --add dana --remove sam
For the wider review lifecycle (create, split, land) see reviews and landing and cleanup. New to the flow, start with the getting started guide.