Metadata-Version: 2.4
Name: gerrit-vote
Version: 0.0.4
Summary: Cast label votes (Code-Review by default) on Gerrit changes via REST — voting only, never submits.
Project-URL: Homepage, https://github.com/Junbo-Zheng/gerrit-vote
Project-URL: Repository, https://github.com/Junbo-Zheng/gerrit-vote
Project-URL: Issues, https://github.com/Junbo-Zheng/gerrit-vote/issues
Author: Junbo Zheng
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: cli,code-review,gerrit,vote
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Version Control
Requires-Python: >=3.10
Requires-Dist: questionary>=2.0
Requires-Dist: tomli>=2.0; python_version < '3.11'
Provides-Extra: dev
Requires-Dist: black>=24; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Description-Content-Type: text/markdown

<!-- SPDX-License-Identifier: Apache-2.0 -->
# gerrit-vote

[![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
[![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](pyproject.toml)
[![CI](https://github.com/Junbo-Zheng/gerrit-vote/actions/workflows/ci.yml/badge.svg)](https://github.com/Junbo-Zheng/gerrit-vote/actions/workflows/ci.yml)

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.

> [!IMPORTANT]
> `gerrit-vote` only casts label votes. It **never submits, merges, or abandons**
> a change.

## Features

- **Clipboard-first.** A bare `gerrit-vote` reads 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-stdin` to read piped text.
- **Confirm before voting.** The changes are listed, then an arrow-key menu lets
  you pick the value (`+2` default, `+1`, `0`, `-1`, `-2`, or quit). `-y` skips
  it; `--dry-run` previews without voting.
- **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:NAME` URL or `--topic NAME` to vote on every
  open change in a Gerrit topic; the topic is expanded to its changes before the
  confirmation prompt. Add `--related` to also pull in each change's dependency
  chain.
- **Multi-host batches.** Each change resolves its own host's credentials, so a
  mix of Gerrit instances votes each on the right one.
- **Skips closed changes.** Merged or abandoned changes are detected and skipped,
  never re-voted.
- **Permission fallback.** If `+2` is not in your permitted range but `+1` is, it
  downgrades automatically (disable with `--no-fallback`).
- **Readable output.** Colored results on a TTY (honoring `NO_COLOR`) plus a
  summary line every run.

## Installation

```bash
pip install gerrit-vote
```

Requires Python 3.10 or newer.

> [!TIP]
> Installing as a standalone CLI? `pipx install gerrit-vote` keeps 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:

0. **Command-line overrides** `-u/--user` and `-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.

1. **Environment variables** (single host):

   ```bash
   export GERRIT_URL=https://gerrit.example.com
   export GERRIT_USER=your-username
   export GERRIT_HTTP_PASSWORD=your-http-password
   ```

2. **Config file** `~/.config/gerrit-vote/config.toml` — required for multiple
   hosts. Copy [`config.toml.example`](config.toml.example) and fill it in:

   ```toml
   default_host = "gerrit.example.com"

   [hosts."gerrit.example.com"]
   url = "https://gerrit.example.com"
   user = "your-username"
   password = "your-http-password"
   ```

   ```bash
   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-vote` prints the
> exact environment-variable and `config.toml` snippet you need — including the
> correct hostname — so you are never left guessing.

## Usage

The easiest flow — copy the links, then:

```bash
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:

```bash
# 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:

```bash
# 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
```

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`):

```bash
gerrit-vote https://gerrit.example.com/c/proj/+/12345 -u ci-bot -p "$GERRIT_PW"
```

Preview, or pipe text in:

```bash
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 |
| `--extract` | Only print the change links found; do not vote |
| `--dry-run` | Resolve credentials and read changes, but cast nothing |
| `-y`, `--yes` | Skip the confirmation menu and vote immediately |
| `--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 |
| `--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 |

### 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:

```console
$ 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`:

```console
$ gerrit-vote --label Verified https://gerrit.example.com/c/12345 1
```

## Library use

The voting and extraction logic is importable:

```python
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:

```python
from gerrit_vote import 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)
```

## Development

Run straight from the source tree — no build, no install:

```bash
./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:

```bash
pip install ".[dev]" --no-deps   # pytest + black; the package itself need not be installed
```

CI runs on every push and pull request: a fast job lints with `black` 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.
