Skip to content

Repository files navigation

github2gerrit

Mirror GitHub pull requests into Gerrit changes.

github2gerrit serves projects where Gerrit is the source of truth and GitHub hosts a read-only mirror. Automation tools such as Dependabot raise pull requests against the GitHub mirror; this action translates those pull requests into Gerrit changes, keeping the two systems in sync across the entire lifecycle: creation, updates/rebases, metadata edits, closure, and cleanup.

The implementation is a Python CLI tool (github2gerrit, published to PyPI) wrapped in a GitHub composite action, plus a reusable workflow for straightforward deployment in consuming repositories.

Goals and purpose

  • Let Gerrit-based projects receive dependency updates (Dependabot) and other automated changes raised against a GitHub mirror.
  • Keep GitHub PRs and Gerrit changes synchronized in both directions: PR updates become new patchsets, merged/abandoned changes close their source PRs, and closed PRs abandon their Gerrit changes.
  • Avoid duplicate Gerrit changes through Change-Id reuse and reconciliation when automation rebases or re-raises PRs.

How it works

flowchart TD
A[PR event on GitHub mirror] --> B[github2gerrit action]
B --> C{Operation mode}
C -->|opened| D[Create Gerrit change]
C -->|synchronize| E[New patchset on existing change]
C -->|edited| F[Sync PR metadata to Gerrit]
C -->|closed| G[Abandon Gerrit change]
D --> H[Comment Gerrit URL on PR]
E --> H
Loading

In more detail, a run:

  1. Reads PR context and inputs; detects the operation mode (CREATE, UPDATE, EDIT, CLOSE) from the triggering event.
  2. Reads .gitreview for the Gerrit host, port, and project (or uses explicit GERRIT_SERVER / GERRIT_PROJECT inputs).
  3. Sets up git and SSH for Gerrit, derives missing credentials from the organization name where possible.
  4. Prepares commits: squashed single commit (default), one-by-one cherry-picks (SUBMIT_SINGLE_COMMITS), or PR title/body as the commit message (USE_PR_AS_COMMIT). Reuses existing Change-Id trailers on updates so pushes create new patchsets, not new changes.
  5. Pushes to refs/for/<branch> with a Gerrit topic derived from the project and PR number (prefix configurable via G2G_TOPIC_PREFIX, default GH), queries Gerrit for the resulting URL/number/SHA, and cross-links: a back-reference comment in Gerrit and the change URL(s) on the PR.

See docs/features.md for detailed feature documentation: PR update handling, comment commands, duplicate detection, reconciliation, cleanup, commit normalization, configuration precedence, and credential derivation.

Quick start

Prerequisites

  • A Gerrit account for the automation user, with SSH access and permission to push to refs/for/* on the target project.
  • The SSH private key stored as a repository or organization secret: GERRIT_SSH_PRIVKEY_G2G.
  • A .gitreview file in the repository (recommended). Without it, pass GERRIT_SERVER, GERRIT_SERVER_PORT, and GERRIT_PROJECT explicitly.
  • Optional repository/organization variables: GERRIT_KNOWN_HOSTS, GERRIT_SSH_USER_G2G, GERRIT_SSH_USER_G2G_EMAIL. The tool derives missing values from the organization name and populates known hosts automatically on first run.

Option A: reusable workflow (recommended)

Add a thin caller workflow to the consuming repository:

# .github/workflows/github2gerrit.yamlname: github2gerriton:
pull_request_target:
types: [opened, reopened, edited, synchronize, closed]# Re-runs when a maintainer approves a fork pull requestpull_request_review:
types: [submitted, dismissed]push:
branches: [main, master]workflow_dispatch:
inputs:
PR_NUMBER:
description: "PR number to process; 0 processes all open"required: falsedefault: "0"type: stringpermissions: {}jobs:
github2gerrit:
permissions:
contents: readpull-requests: writeissues: write# yamllint disable-line rule:line-lengthuses: lfreleng-actions/github2gerrit-action/.github/workflows/github2gerrit.yaml@mainwith:
GERRIT_KNOWN_HOSTS: ${{ vars.GERRIT_KNOWN_HOSTS }}GERRIT_SSH_USER_G2G: ${{ vars.GERRIT_SSH_USER_G2G }}GERRIT_SSH_USER_G2G_EMAIL: ${{ vars.GERRIT_SSH_USER_G2G_EMAIL }}PR_NUMBER: ${{ inputs.PR_NUMBER || '0' }}secrets:
GERRIT_SSH_PRIVKEY_G2G: ${{ secrets.GERRIT_SSH_PRIVKEY_G2G }}

The push trigger enables closing PRs whose Gerrit changes have merged; workflow_dispatch enables manual processing. Repositories using the Gerrit-side dispatch integration should also declare GERRIT_CHANGE_URL, GERRIT_EVENT_TYPE, and GERRIT_BRANCH as dispatch inputs and forward them the same way.

Pin @main to a release tag or commit SHA for production use.

Option B: composite action

Call the action directly for full control over all inputs:

name: github2gerriton:
pull_request_target:
types: [opened, reopened, edited, synchronize, closed]# Re-runs when a maintainer approves a fork pull requestpull_request_review:
types: [submitted, dismissed]workflow_dispatch:
permissions:
contents: readpull-requests: writeissues: writejobs:
submit-to-gerrit:
runs-on: ubuntu-lateststeps:
- name: Submit PR to Gerritid: g2guses: lfreleng-actions/github2gerrit-action@mainwith:
GERRIT_KNOWN_HOSTS: ${{ vars.GERRIT_KNOWN_HOSTS }}GERRIT_SSH_PRIVKEY_G2G: ${{ secrets.GERRIT_SSH_PRIVKEY_G2G }}GERRIT_SSH_USER_G2G: ${{ vars.GERRIT_SSH_USER_G2G }}GERRIT_SSH_USER_G2G_EMAIL: ${{ vars.GERRIT_SSH_USER_G2G_EMAIL }}

Option C: command-line tool

The underlying Python CLI supports local and ad-hoc use (for example, processing a single PR URL or bulk-processing a repository). This is a secondary use case; see docs/cli.md.

uvx github2gerrit https://github.com/onap/portal-ng-bff/pull/33

Action inputs

InputRequiredDefaultDescription
GERRIT_SSH_PRIVKEY_G2GYesSSH private key content used to authenticate to Gerrit
GERRIT_KNOWN_HOSTSNoKnown hosts entries for Gerrit SSH (auto-populated when empty)
GERRIT_SSH_USER_G2GNo""Gerrit SSH username; derived when not supplied
GERRIT_SSH_USER_G2G_EMAILNo""Gerrit user email address; derived when not supplied
GERRIT_SERVERNo""Gerrit server hostname; .gitreview preferred
GERRIT_SERVER_PORTNo"29418"Gerrit SSH port
GERRIT_PROJECTNo""Gerrit project name; .gitreview preferred
GERRIT_HTTP_BASE_PATHNo""HTTP base path for Gerrit REST API (e.g. /r)
GERRIT_HTTP_USERNo""Gerrit HTTP user for REST queries
GERRIT_HTTP_PASSWORDNo""Gerrit HTTP password/token for REST queries
ORGANIZATIONNorepository ownerGitHub organization/owner used for credential derivation
PR_NUMBERNo"0"PR number to process; 0 processes all open PRs (dispatch)
FETCH_DEPTHNo"10"Git history depth: PR fetch, push checkout, reconciliation
SUBMIT_SINGLE_COMMITSNo"false"Submit one commit at a time to Gerrit
USE_PR_AS_COMMITNo"false"Use PR title and body as the commit message
PRESERVE_GITHUB_PRSNo"true"Do not close GitHub PRs after pushing to Gerrit
CLOSE_MERGED_PRSNo"true"Close GitHub PRs when their Gerrit change merges
CLEANUP_ABANDONEDNo"true"Close GitHub PRs for abandoned Gerrit changes
CLEANUP_GERRITNo"true"Abandon Gerrit changes when their GitHub PR closes
CREATE_MISSINGNo"false"Create a new change when UPDATE finds no existing change
ALLOW_DUPLICATESNo"true"Allow submitting duplicate changes without error
DUPLICATE_TYPESNo"open"Comma-separated Gerrit states checked for duplicates
AUTOMATION_ONLYNo"true"Accept PRs from known automation tools only
NORMALISE_COMMITNo"false"Normalize commit messages to conventional commit format
COMMIT_RULES_JSONNo""JSON commit message validation rules (see docs)
ISSUE_IDNo""Issue ID trailer to include (e.g. ABC-123)
ISSUE_ID_LOOKUP_JSONNo"[]"JSON array mapping GitHub actors to Issue IDs
REVIEWERS_EMAILNo""Comma-separated reviewer emails
DRY_RUNNo"false"Check settings and PR metadata; do not write to Gerrit
FORCENo"false"Force PR closure regardless of Gerrit change status
G2G_USE_SSH_AGENTNo"true"Use SSH agent instead of file-based keys
G2G_NO_GERRITNo"false"Run the pipeline without contacting Gerrit (forces dry-run)
G2G_DISABLEDNo""Kill switch: skip all processing when true
ALLOW_GHE_URLSNo"false"Allow GitHub Enterprise URLs in direct URL mode
VERBOSENo"false"Verbose output (sets log level to DEBUG)
CI_TESTINGNo"false"CI testing mode; overrides .gitreview
USE_LOCAL_ACTIONNo"false"Use local repository code instead of the PyPI package

Every input maps to an environment variable of the same name (VERBOSE maps to G2G_VERBOSE), and most have matching CLI flags. Reconciliation tuning (SIMILARITY_SUBJECT, SIMILARITY_UPDATE_FACTOR, SIMILARITY_FILES, REUSE_STRATEGY) is available through environment variables and CLI flags only; set these via env: on the action step when needed. See docs/cli.md for the full option reference and docs/features.md for feature-specific settings.

Action outputs

OutputDescription
gerrit_change_request_urlGerrit change URL(s), newline-separated
gerrit_change_request_numGerrit change number(s), newline-separated
gerrit_commit_shaPatch set commit SHA(s), newline-separated

Access outputs in later steps with ${{ steps.<step-id>.outputs.<output-name> }}. The reusable workflow re-exports all three outputs to callers.

Reusable workflow interface

The reusable workflow (.github/workflows/github2gerrit.yaml) wraps the composite action for workflow_call, supporting caller triggers pull_request_target, push (close PRs for merged Gerrit changes), and workflow_dispatch (manual runs and Gerrit-event dispatches). Input defaults match the composite action defaults.

InputTypeDefaultDescription
GERRIT_KNOWN_HOSTSstring""Known hosts entries for Gerrit SSH
GERRIT_SSH_USER_G2Gstring""Gerrit SSH username
GERRIT_SSH_USER_G2G_EMAILstring""Gerrit user email address
GERRIT_SERVERstring""Gerrit server hostname
GERRIT_SERVER_PORTstring"29418"Gerrit SSH port
GERRIT_PROJECTstring""Gerrit project name
GERRIT_HTTP_BASE_PATHstring""HTTP base path for Gerrit REST
GERRIT_HTTP_USERstring""Gerrit HTTP user for REST queries
GERRIT_HTTP_PASSWORDstring""Gerrit HTTP password/token for REST queries
G2G_USE_SSH_AGENTbooleantrueUse SSH agent instead of file-based keys
ORGANIZATIONstringrepository ownerGitHub organization/owner
PR_NUMBERstring"0"PR to process on dispatch; 0 processes all
FETCH_DEPTHstring"10"Git depth: PR, push, reconciliation
SUBMIT_SINGLE_COMMITSbooleanfalseSubmit one commit at a time
USE_PR_AS_COMMITbooleanfalseUse PR title and body as the commit message
PRESERVE_GITHUB_PRSbooleantrueDo not close GitHub PRs after pushing
CLOSE_MERGED_PRSbooleantrueClose GitHub PRs when their Gerrit change merges
CLEANUP_ABANDONEDbooleantrueClose GitHub PRs for abandoned Gerrit changes
CLEANUP_GERRITbooleantrueAbandon Gerrit changes when their PR closes
CREATE_MISSINGbooleanfalseCreate a change when UPDATE finds none
AUTOMATION_ONLYbooleantrueAccept PRs from known automation tools only
NORMALISE_COMMITbooleanfalseNormalize commit messages
COMMIT_RULES_JSONstring""JSON commit message validation rules
ALLOW_DUPLICATESbooleantrueAllow submitting duplicate changes
DUPLICATE_TYPESstring"open"Gerrit states checked for duplicates
FORCEbooleanfalseForce PR closure regardless of change status
VERBOSEbooleanfalseVerbose output (DEBUG log level)
ALLOW_GHE_URLSbooleanfalseAllow GitHub Enterprise URLs
DRY_RUNbooleanfalseCheck only; do not write to Gerrit
ISSUE_IDstring""Issue ID trailer to include
ISSUE_ID_LOOKUP_JSONstring"[]"JSON array mapping GitHub actors to Issue IDs
REVIEWERS_EMAILstring""Comma-separated reviewer emails
GERRIT_CHANGE_URLstring""Gerrit change URL from a Gerrit event dispatch¹
GERRIT_EVENT_TYPEstring""Gerrit event type (e.g. change-merged
GERRIT_BRANCHstring""Target branch override (Gerrit event dispatch)¹
SecretRequiredDescription
GERRIT_SSH_PRIVKEY_G2GYesSSH private key for the Gerrit automation user
OutputDescription
gerrit_change_request_urlGerrit change URL(s), newline-separated
gerrit_change_request_numGerrit change number(s), newline-separated
gerrit_commit_shaPatch set commit SHA(s), newline-separated

¹ Gerrit → GitHub reverse flow: when Gerrit-side automation dispatches the caller workflow to report a merged or abandoned change, forward these dispatch inputs and the tool closes the source GitHub PR instead of processing pull requests.

Repository variables provide operational kill switches: set G2G_NO_GERRIT to true to skip Gerrit interaction, or G2G_DISABLED to true to exit immediately without processing. Test-only settings (CI_TESTING, USE_LOCAL_ACTION) remain composite-action-only by design.

Documentation

DocumentContents
docs/features.mdFeature reference: PR updates, comment commands, cleanup, duplicate detection, reconciliation, normalization, configuration
docs/cli.mdCLI installation, options, environment variables, exit codes, debugging
docs/COMMIT_RULES.mdCommit message validation rules and COMMIT_RULES_JSON format
docs/development.mdContributor guide: local setup, testing, composite action test suite

Security notes

  • Do not hardcode secrets or keys. Provide the private key through workflow secrets and known hosts through repository or organization variables.
  • SSH handling is non-invasive: the tool creates temporary SSH files in the workspace without modifying user SSH configuration or keys, and cleans them up after execution.
  • SSH connections use IdentitiesOnly=yes to avoid unintended key usage (e.g. signing keys requiring biometric authentication).

License

Apache License 2.0. See LICENSE.

About

Python tool for converting Github pull requests to Gerrit changes, with GitHub action wrapper

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages