Cast label votes (Code-Review by default) on Gerrit changes via REST — voting only, never submits.
Project description
gerrit-vote
Cast label votes on Gerrit changes from the command line, via the Gerrit
REST API — Code-Review by default, or any label your server exposes (Verified,
etc.). Copy a pile of change links from anywhere, run gerrit-vote, confirm, done.
Beyond voting it can also abandon, restore, and delete changes, and
list the changes you own or need to review.
[!IMPORTANT]
gerrit-votenever submits or merges a change. Voting is the default and only touches thelabelsfield. The lifecycle actions (--abandon,--restore,--delete) change a change's state and are opt-in behind their own flags;--deleteis irreversible and owner-only, and always asks first.
Features
- Clipboard-first. A bare
gerrit-votereads the system clipboard, so you can copy a batch of links and just run the command. Pass explicit URLs to bypass the clipboard, or--from-stdinto read piped text. - Confirm before voting. The changes are listed, then an arrow-key menu lets
you pick the value. The value rows come from the server's permitted range for
the change (so a
0/+1label shows only0/+1, whileCode-Reviewshows+2/+1/0/-1/-2), plus quit.-yskips it;--dry-runpreviews without voting. - Owner-only combo actions. When the first change is yours (its owner email
matches your
~/.gitconfiguser.email), the menu adds three one-shot combos above the plain rows —Code-Review +1together withPresubmit-Ready +1and/orAuto-Submit +1, cast in a single atomic review. They stay hidden on changes you don't own, and are all-or-nothing: if any label in the combo isn't permitted on a change, that change is skipped rather than partially voted. - Extracts links from messy text. Finds Gerrit change links in pasted chat logs — de-duplicated, ignoring non-Gerrit noise, tolerating trailing CJK punctuation.
- Topic batches. Pass a
/q/topic:NAMEURL or--topic NAMEto vote on every open change in a Gerrit topic; the topic is expanded to its changes before the confirmation prompt. Add--relatedto also pull in each change's dependency chain, and--related-topicsto also pull in every open change sharing a topic with any change in the set. - Clipboard auto-expands the related group. A bare
gerrit-vote(clipboard source) behaves as if both--relatedand--related-topicswere given — it votes on the pasted patch plus its dependency chain and topic siblings, so you can copy one link and vote the whole related group. Explicit URL arguments are the precise path (no auto-expand);--extractand destructive actions (--abandon/--restore/--delete) never auto-expand —--extractis a pure "parse my clipboard" preview, and--dry-runalready shows the expanded set with full detail. - Multi-host batches. Each change resolves its own host's credentials, so a mix of Gerrit instances votes each on the right one.
- Lifecycle actions. Beyond voting,
--abandon/--restore/--deleteact on the same change sources (URLs, clipboard,--all). All list the target changes and confirm via an arrow-key menu first.--abandon/--restoreare reversible and default the menu to Yes;--deleteis irreversible, owner-only (changes you don't own are skipped), and defaults to No. - List your changes.
--listprints a read-only table of your non-merged changes across every configured host — both those you own (open + abandoned) and those assigned to you for review, with review rows first. - Bulk cleanup.
--allsweeps every one of your changes across all hosts (owner:self) for a one-shot--abandon/--restore/--delete; it must be given explicitly and still runs the usual confirmation. - Skips closed changes. Merged or abandoned changes are detected and skipped,
never re-voted (voting only —
--deletecan still delete abandoned changes). - Permission fallback. If
+2is not in your permitted range but+1is, it downgrades automatically (disable with--no-fallback). - Transient-failure retry. Server
5xxand connection-level errors are retried a couple of times with a short backoff; deterministic4xx(bad auth, not permitted, no such change) fail fast without retry. - Readable output. Colored results on a TTY (honoring
NO_COLOR) plus a summary line every run.
Installation
pip install gerrit-vote
Requires Python 3.10 or newer.
[!TIP] Installing as a standalone CLI?
pipx install gerrit-votekeeps it isolated from your other Python environments.
Configuration
gerrit-vote needs a Gerrit HTTP password (not your login password) — generate
one at Gerrit → Settings → HTTP Credentials → Generate Password.
Credentials resolve in this order:
-
Command-line overrides
-u/--userand-p/--password— win over env and config for that field, and apply to every host in the batch. Handy for CI or a throwaway identity. The URL still comes from the change URL /GERRIT_URL/default_host. Beware: a password on the command line lands in shell history. -
Environment variables (single host):
export GERRIT_URL=https://gerrit.example.com export GERRIT_USER=your-username export GERRIT_HTTP_PASSWORD=your-http-password
-
Config file
~/.config/gerrit-vote/config.toml— required for multiple hosts. Copyconfig.toml.exampleand fill it in:default_host = "gerrit.example.com" [hosts."gerrit.example.com"] url = "https://gerrit.example.com" user = "your-username" password = "your-http-password"
mkdir -p ~/.config/gerrit-vote cp config.toml.example ~/.config/gerrit-vote/config.toml chmod 600 ~/.config/gerrit-vote/config.toml
[!NOTE] If credentials are missing or a host is not configured,
gerrit-voteprints the exact environment-variable andconfig.tomlsnippet you need — including the correct hostname — so you are never left guessing.
Usage
The easiest flow — copy the links, then:
gerrit-vote # read clipboard, list changes, pick +2/+1/0/-1/-2/quit
gerrit-vote 1 # same, but the menu defaults to +1
gerrit-vote -y # skip the menu, vote +2 immediately
Vote on explicit changes:
# One change (clipboard ignored when URLs are given)
gerrit-vote https://gerrit.example.com/c/proj/+/12345
# Several on ONE line (space-separated), all +1
gerrit-vote <url1> <url2> <url3> 1
# With a review message
gerrit-vote https://gerrit.example.com/c/proj/+/12345 1 --message "LGTM"
Vote on every open change in a topic:
# Paste a topic query URL straight from the browser
gerrit-vote https://gerrit.example.com/q/topic:my-feature
# Or name the topic explicitly (uses the default/single configured host)
gerrit-vote --topic my-feature
# Also include each change's dependency chain
gerrit-vote --topic my-feature --related
# Also include every open change sharing a topic with any change above
gerrit-vote --topic my-feature --related --related-topics
A bare gerrit-vote reads the clipboard and auto-expands both the dependency
chain and topic siblings — copy one patch link and vote the whole related group:
gerrit-vote # clipboard: pasted patch + its chain + its topic siblings
gerrit-vote <url> # explicit URL: votes on that one only (no auto-expand)
Abandon, restore, or delete changes (same sources as voting — URLs, clipboard,
--from-stdin):
# Abandon (reversible); lists the changes, then an arrow-key menu (defaults to Yes)
gerrit-vote --abandon https://gerrit.example.com/c/proj/+/12345
# Restore an abandoned change (the inverse of --abandon)
gerrit-vote --restore https://gerrit.example.com/c/proj/+/12345
# Delete (IRREVERSIBLE, owner-only): lists, then an arrow-key menu defaulting to No
gerrit-vote --delete https://gerrit.example.com/c/proj/+/12345
gerrit-vote --delete --force-delete <url> # skip the prompt (scripts/CI)
List your own and to-review changes, or act on all of them at once:
# Read-only table across every configured host (owned + assigned-for-review)
gerrit-vote --list
# Sweep ALL your open changes on every host and abandon them (one confirmation)
gerrit-vote --all --abandon
# Restore every one of your abandoned changes; delete every open one
gerrit-vote --all --restore
gerrit-vote --all --delete # menu: open / abandoned / both; still confirms
[!CAUTION]
--alltargets every matching change of yours across all hosts in one go. Preview first with--all --abandon --dry-run(or--delete --dry-run) before committing to a bulk action.
Override credentials on the command line (handy for CI or a throwaway identity;
the URL still comes from the change link / GERRIT_URL / default_host):
gerrit-vote https://gerrit.example.com/c/proj/+/12345 -u ci-bot -p "$GERRIT_PW"
Preview, or pipe text in:
gerrit-vote --dry-run # list what would be voted, cast nothing
gerrit-vote --extract # just print the change links found
pbpaste | gerrit-vote --from-stdin -y
[!WARNING] Pasting multiple lines directly at the shell prompt does not work — the shell runs each line as its own command. Put several URLs on one line (space-separated), or use the clipboard /
--from-stdin.
Closed changes are skipped automatically, results are colored on a TTY, and a summary line is printed every run. Exit status is non-zero if any change failed.
Options
| Option | Description |
|---|---|
[value] |
Vote value 2 1 0 -1 -2; defaults to +2 |
-c, --clipboard |
Read change links from the system clipboard |
--from-stdin |
Read change links from piped stdin |
--topic TOPIC |
Vote on every open change under a Gerrit topic (host = default/single configured host) |
--related |
Also vote on each change's dependency (relation) chain, de-duplicated |
--related-topics |
Also vote on every open change sharing a topic with any change in the set (auto-enabled on clipboard, except with --extract) |
--extract |
Only print the change links found; do not vote |
--dry-run |
Resolve credentials and read changes, but change nothing (also previews --abandon/--restore/--delete/--all) |
-y, --yes |
Skip the confirmation menu (votes immediately; also skips the abandon/restore/delete prompt) |
--abandon |
Abandon the change(s) instead of voting (reversible; MERGED skipped) |
--restore |
Restore abandoned change(s) — the inverse of --abandon (only ABANDONED changes) |
--delete |
Delete the change(s) — irreversible, owner-only; lists + confirms first. With --all, a menu first picks open / abandoned / both |
--force-delete |
Skip the interactive confirm for --delete (for scripts; -y does the same) |
--all |
Target ALL your changes across every configured host (owner:self); requires --abandon/--restore/--delete. With --delete, a menu picks which statuses to sweep |
--list |
List your non-merged changes (owned + to-review) across all hosts, then exit; changes nothing |
--label LABEL |
Label to vote on (default Code-Review; common: Code-Review, Verified) |
--list-labels CHANGE |
Print the labels you may vote on CHANGE (name + allowed values), then exit; casts nothing |
-m, --message MSG |
Review message posted alongside the vote |
-u, --user USER |
Gerrit username override (else env/config); applies to every host |
-p, --password PW |
Gerrit HTTP password override (else env/config); visible in shell history |
--no-fallback |
Disable the +2 → +1 downgrade when +2 is not permitted |
-V, --version |
Print the version and exit |
Labels
Code-Review is the default and, on most servers, the only label a human votes
by hand. But the available labels are defined per Gerrit server (a server may
also expose Verified, Owners-Override, and any number of CI/bot labels), and
which ones you may vote on a given change is decided per-change by your
project/branch permissions. Rather than hardcode a list that would be wrong on
another server, query it live:
$ gerrit-vote --list-labels https://gerrit.example.com/c/12345
Labels you may vote on this change:
Code-Review: -2, -1, 0, +1, +2
Verified: -1, 0, +1
Then pass one to --label:
$ gerrit-vote --label Verified https://gerrit.example.com/c/12345 1
Combo actions on your own changes
When a change is yours, the interactive menu offers three one-shot combos above the usual value rows, each casting several labels in a single review:
❯ 1. Code-Review +1, Presubmit-Ready +1, Auto-Submit +1
2. Code-Review +1, Presubmit-Ready +1
3. Code-Review +1, Auto-Submit +1
4. +2 approve
5. +1 looks good
...
Ownership is decided by comparing the change's owner email with your global git
identity (git config --global user.email, i.e. ~/.gitconfig), so a repo-local
override does not shadow it. Combos are hidden on changes you don't own. They are
also all-or-nothing per change: if the server doesn't permit every label in the
chosen combo on a given change (e.g. that project has no Auto-Submit), the
change is skipped rather than partially voted.
Library use
The voting and extraction logic is importable:
from gerrit_vote import extract_changes, vote
for url in extract_changes(pasted_text):
result = vote(url) # defaults to +2, never submits
if result.skipped:
print(result.change_id, "skipped:", result.skip_reason)
else:
print(result.change_id, "->", result.cast) # may be 1 after fallback
Expand a topic into its open changes, then vote on each:
from gerrit_vote import change_topic, changes_in_topic, resolve, vote
creds = resolve("gerrit.example.com") # or None for the default host
for url in changes_in_topic(creds, "my-feature"):
vote(url, creds=creds)
# change_topic(creds, url) returns a change's topic (or None), the primitive
# behind --related-topics.
Lifecycle actions and listing are importable too. abandon / restore return a
VoteResult (with .action set); delete_change takes an owner gate; and
list_my_changes returns ChangeListItems tagged is_owner:
from gerrit_vote import abandon, restore, delete_change, list_my_changes, resolve
abandon("https://gerrit.example.com/c/proj/+/12345") # reversible
restore("https://gerrit.example.com/c/proj/+/12345") # inverse of abandon
# Delete only your own change (require_owner_email gates it; None disables the gate)
delete_change("https://gerrit.example.com/c/proj/+/12345",
require_owner_email="me@example.com")
creds = resolve("gerrit.example.com")
for item in list_my_changes(creds):
tag = "mine" if item.is_owner else "REVIEW"
print(item.number, item.status, tag, item.subject)
Development
Run straight from the source tree — no build, no install:
./main.py --dry-run <url> # the CLI, imported from src/ on the fly
pytest # tests import from src/ via pythonpath, no install needed
pyproject.toml sets pythonpath = ["src"], so pytest runs against the working
tree directly and always shadows any installed copy. ./main.py is a thin shim
that does the same for the CLI. Install the dev tools once with:
pip install ".[dev]" --no-deps # pytest + ruff; the package itself need not be installed
CI runs on every push and pull request: a fast job lints and formats with ruff
and runs the suite against src/, plus a package job that builds the wheel, installs it
clean, and re-runs the suite with pythonpath cleared — so the installed package
is exercised and packaging bugs fail the build.
Releasing
Publishing is automated: cut a GitHub Release and publish.yml builds the
distribution and uploads it to PyPI via Trusted Publishing (OIDC — no API token).
The repo must be registered as a trusted publisher on the PyPI project once. Bump
__version__ in src/gerrit_vote/__init__.py before tagging — PyPI is
append-only.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file gerrit_vote-0.0.6.tar.gz.
File metadata
- Download URL: gerrit_vote-0.0.6.tar.gz
- Upload date:
- Size: 56.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5ff033ec8b5a2118d13813a357fe4b15a702211a89d2d4a3d53131cf2e6e92d3
|
|
| MD5 |
9fff393834d71ed30267cc481b9e90f7
|
|
| BLAKE2b-256 |
bcd83bd841521f4a03e98d5d08d514f1599f84e08a3f9bba3960a545e9b6d3ee
|
File details
Details for the file gerrit_vote-0.0.6-py3-none-any.whl.
File metadata
- Download URL: gerrit_vote-0.0.6-py3-none-any.whl
- Upload date:
- Size: 41.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
00588c40b49f0ad9213e5cc6eccc6c070c560895eb3a2362c5ffef85b6ab8fcc
|
|
| MD5 |
f4002777d299501ca21f9d91cba90134
|
|
| BLAKE2b-256 |
e6b236e1412b3ef49c16313f645a80532a0e567647ce5adf596a485084498da2
|