Skip to content

Bug 2048352 - Migrate code-review bot to push to Github #3490

Draft
nahadji wants to merge 8 commits into
mozilla:masterfrom
nahadji:bug-2048352-git-push
Draft

Bug 2048352 - Migrate code-review bot to push to Github #3490
nahadji wants to merge 8 commits into
mozilla:masterfrom
nahadji:bug-2048352-git-push

Conversation

@nahadji

@nahadji nahadji commented Jul 9, 2026

Copy link
Copy Markdown

Adds a Git push path to the code-review bot so the analysis workflow can push
patch stacks to a GitHub repository instead of hg.mozilla.org/try
(Bug 2048352).

The change is additive: the Mercurial path is untouched, and the Git backend is
selected per repository via a new optional repo_type field in the
repositories configuration (defaults to hg, so existing configurations keep
working unchanged).

What's included

  • GitRepository: applies a Phabricator patch stack as Git commits (preserving
    authorship), writes the same try_task_config.json, and force-pushes to a
    configured branch on the remote. No Lando git2hg lookup (the base is
    already a Git hash); missing bases fall back to the default revision and are
    reported to Phabricator like the Mercurial path does.
  • GitWorker: mirrors MercurialWorker, minus the treestatus wait (no "try"
    tree to gate on). Eligible push errors are retried with exponential backoff.
  • Backend selection in start_analysis based on repo_type.
  • Failure publication: fail:git results are reported to Phabricator and Lando.
  • Auth: pushes are authenticated over HTTPS with a short-lived GitHub App
    installation token (generated per run with simple-github, restricted to the
    try repository, never written to disk). The bot reads GITHUB_APP_ID and
    GITHUB_APP_PRIVKEY from the runtime configuration secret.

Verified

  • Unit tests (tests/test_git.py, extended tests/test_phabricator_analysis.py);
    self-contained (local git repos, no Mercurial environment needed).
  • The push pipeline was exercised for real against
    mozilla-releng/staging-firefox with a write deploy key (patch stack applied,
    try_task_config.json committed, branch pushed and verified, then deleted).

nahadji added 8 commits July 9, 2026 16:31
Mirror the Mercurial Repository interface in a new GitRepository class so the
analysis workflow can apply a Phabricator patch stack and push it to a Git
remote (e.g. a GitHub "try" repository) instead of hg.mozilla.org/try.

This is additive: the Mercurial path is unchanged. The repository class to use
will be selected per-repository in a later change.

Differences from the Mercurial implementation:
- the base revision is already a Git hash, so there is no Lando git2hg lookup;
- authentication uses an SSH deploy key via GIT_SSH_COMMAND.

Add tests (test_git.py) covering patch application, try_task_config.json,
push to a local bare repo, base-revision resolution, clean, and the
diff-header normalisation. They are self-contained (local git only) and need
no Mercurial environment.
Add a GitWorker mirroring MercurialWorker for Git: clean the clone, apply the
Phabricator patch stack, write try_task_config.json, push to the remote try
repository and return a Treeherder link. Unlike the Mercurial worker there is
no treestatus wait (Git has no "try" tree); eligible push errors are retried
with exponential backoff.

Also make clean() equivalent to the Mercurial one. apply_build now checks out
the base with a detached HEAD so the bot's patch and try_task_config commits
stay throwaway drafts, and clean() refreshes from the remote (when configured)
and resets back to the base. This prevents a reused cached clone from
accumulating commits across builds.

Add tests for the worker success path, skippable patches, general failures,
retry-without-treestatus, and that a reused clone drops the previous build's
commits.
Add an optional repo_type field to RepositoryConf (default "hg" so existing
repository secrets keep working) and branch start_analysis on it: a repository
with repo_type "git" builds a GitRepository + GitWorker using the Git cache,
while everything else keeps the Mercurial Repository + MercurialWorker.

Add tests: repo_type defaults to hg and accepts git, and start_analysis picks
the right backend for each repo type.
The publication layer only knew the fail:mercurial mode, so a fail:git result
from the GitWorker fell through to "Unsupported publication": the Phabricator
build was never marked as failed and stayed in a running state forever, and
Lando was never notified.

Handle fail:git in publish_analysis_phabricator with a failure message
mirroring the Mercurial one, and publish the existing patch-failure warning to
Lando for both VCS modes.
… Git path

GitRepository.get_base_identifier eagerly fell back to the default revision
when the base was missing locally, so apply_build could never set
missing_base_revision on the build and the "your patch has been rebased"
warning was never published to Phabricator. Return the base revision as-is,
mirroring the Mercurial implementation, and let apply_build record the
fallback.

clean() silently kept the previous build's commits when default_revision was
left to its bare HEAD default without an origin remote to resolve against.
Resolve the pristine base explicitly (remote-tracking branch first) and raise
a clear error when none can be determined, and check it out detached instead
of moving a local branch.
…ecret

The deploy key used to push to Git try repositories lives in its own
Taskcluster secret (field ssh_privkey), separate from the bot's runtime
configuration secret whose tokens are shared with other environments.

Add an optional --git-ssh-key-secret argument (GIT_SSH_KEY_SECRET in the
environment) naming that secret. When set, the bot fetches it and Git
repositories push with this key, falling back to the global ssh_key
otherwise. The secret name stays configuration so the key's final location
can be changed without a code change.
…fective

test_phabricator_analysis.py imported code_review_bot.workflow at module
level, which binds taskcluster.utils.stringDate inside the workflow module at
collection time, before the autouse mock_taskcluster_date fixture can patch
it. The frozen date then never applied to Workflow.index, breaking the date
assertions of test_index.py when the whole suite runs. Import the module
inside the test that needs it, like the other tests do.
…token

Replace the SSH deploy key authentication with short-lived GitHub App
installation tokens, as suggested by the maintainers. The bot reads
GITHUB_APP_ID and GITHUB_APP_PRIVKEY from the runtime configuration secret,
generates a token restricted to the try repository with simple-github (cached
for the run, installation tokens are valid one hour), and pushes over HTTPS
with the token injected at push time. The token is never written to disk nor
to the git configuration, and read operations keep using plain urls as the
repositories are public.

This removes the ssh key temporary file handling and the dedicated
--git-ssh-key-secret argument.

@La0 La0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a lot of code duplication for the new GitWorker & GitRepository classes, you should create a base class that shares as most code as possible between both git & hg implementations, then inherit from these base classes into specific implementations.

The ideal way to do that is to make a first PR which splits the existing Worker & Repository into a base class + specific mercurial implementation, then create another PR on top that implements the git part (what you did here).

Things like the configuration in __init__, patch normalization, the whole workflow to apply a build (albeit specific git/mercurial applications which can be further extracted), try commit build, skippable commit detection, retry detection, the overall worker run can be shared across classes.

You should end up with something like that:

flowchart TD

  base_worker[BaseWorker] --> hg_worker[MercurialWorker]

  base_worker[BaseWorker] --> git_worker[GitWorker]


  base_repo[BaseRepository] --> hg_repo[MercurialRepository]

  base_repo[BaseRepository] --> git_repo[GitRepository]
Loading

taskcluster.secrets["ssh_key"],
args.mercurial_repository,
args.github_repository,
github_app_id=taskcluster.secrets["GITHUB_APP_ID"],

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nicer to create a dedicated GITHUB maaping in the conf with the following parts:

  • app_id
  • app_privkey

This would allow adding more configuration later on (as the needs for Github will certainly grow in that code base).

Also you need to document these new options there : https://github.com/mozilla/code-review/blob/master/docs/configuration.md

"RepositoryConf",
"name, try_name, url, try_url, decision_env_prefix, ssh_user",
"name, try_name, url, try_url, decision_env_prefix, ssh_user, repo_type",
# repo_type is optional and defaults to Mercurial so existing repository

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

)
worker = GitWorker()
else:
repository = Repository(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should rename Repository to MercurialRepository to avoid any confusion

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants