How to Use the LLVM Pull Request Automator

This script is designed to automate the process of creating and landing a stack of pull requests from a local commit branch to the main branch of LLVM‘s GitHub repository. While it’s possible to use this for normal workflows, its main purpose is to give contributors a practical alternative to pushing directly to LLVM's main branch. See the discussion at https://discourse.llvm.org/t/rfc-require-pull-requests-for-all-llvm-project-commits/88164 for more context.

Prerequisites

Before running the script, ensure you have the following set up:

  1. git command line tool: The script relies on git for all local repository operations.

  2. GitHub Token: You must have a GitHub Personal Access Token with repo scope. This token must be set as an environment variable:

    export LLVM_GITHUB_TOKEN="your_github_token_here"
    

    or to avoid having your token in your shell history something like:

    export LLVM_GITHUB_TOKEN="$(gh auth token)"
    
  3. Git Remotes: Your local repository should be configured with remotes for the upstream LLVM repository and your personal fork.

    • The script defaults to using upstream (e.g., https://github.com/llvm/llvm-project.git) for the main LLVM repository and origin for your personal fork.
    • If your remotes are named differently, you can override these defaults using the --upstream-remote and --remote flags.

Basic Usage

To run the script, navigate to your local llvm-project repository, check out the branch containing your stack of commits, and run:

python3 git-llvm-push

Assuming your remotes are configured with upstream pointing to https://github.com/llvm/llvm-project.git and origin pointing to your personal fork:

This will:

  1. Fetch the latest changes from upstream/main.
  2. Rebase your current branch on top of upstream/main.
  3. For each commit in your branch (from oldest to newest): a. Push the commit to a temporary branch on your fork (origin). b. Create a pull request targeting the upstream repository's main branch. c. Attempt to merge the pull request. d. If the merge is successful, it will rebase the local branch again and proceed to the next commit.

If any rebase or merge fails, the script will abort and clean up after itself, leaving your repository in the last good state. This means any commits that were successfully merged before the failure will remain merged, but temporary branches and other transient state will be removed.

Cleanup Steps

Regardless of success or failure, the script performs the following cleanup steps to ensure your local repository is left in a consistent state:

  1. Checkout Original Branch: The script will check out the branch you were on when you started the script.
  2. Delete Temporary Remote Branches: Any temporary branches created on your fork (e.g., users/johndoe/my-feature-1) will be deleted from the remote. This prevents clutter in your fork.

Examples

Dry Run (Safe Mode)

To see what actions the script would perform without actually creating branches, pushing code, or opening pull requests, use the --dry-run flag.

python3 git-llvm-push --dry-run

Creating Draft Pull Requests

If you want to create pull requests but not have them ready for review immediately, use the --draft flag.

python3 git-llvm-push --draft

Enabling Auto-Merge

You can use the --auto-merge flag to create a pull request and enable the “auto-merge” feature on GitHub, rather than having the script try to merge it directly. This is only supported for a single commit, as the script would need to block until your first PR landed to move onto the next, or otherwise be too complex for a simple script.

python3 git-llvm-push --auto-merge

Creating PRs Without Merging

If you only want to create the pull requests and then merge them manually later, use the --no-merge flag. Currently, this is only supported for single-commit branches.

python3 git-llvm-push --no-merge

Working with alternate remotes

If you have cloned the main llvm/llvm-project.git repository directly, your origin remote will point to upstream. If your personal fork is tracked under a different remote name (e.g., my-fork), you will need to specify both the --upstream-remote and --remote flags:

python3 git-llvm-push --upstream-remote origin --remote my-fork

Alternate usage via git llvm-push

If the script is available on your PATH, you can use it as a git subcommand, similar to git clang-format.

git llvm-push [FLAGS] ...

Auto-Detection Features

To simplify usage, the script attempts to auto-detect certain values if they are not explicitly provided via command-line arguments:

  • GitHub Login (--login): If the --login flag is omitted, the script will attempt to fetch your GitHub username using the provided LLVM_GITHUB_TOKEN by making an API call to GitHub.
  • Temporary Branch Prefix (--prefix): If the --prefix flag is omitted, temporary branches created on your fork will be prefixed with users/<your_github_login>/. For example, if your login is johndoe, branches will be named like users/johndoe/my-feature-1.

Command-Line Options

FlagDescriptionDefault
--baseThe base branch to target with the pull requests.main
--remoteThe remote for your personal fork to push temporary branches to.origin
--upstream-remoteThe remote for the upstream repository to create pull requests against.upstream
--loginYour GitHub username. If not provided, it will be queried from the token.(auto-detected)
--prefixThe prefix for temporary branches created on your fork.users/<username>/
--draftCreate pull requests as drafts.(not set)
--no-mergeCreate pull requests but do not attempt to merge them. (Single commit only)(not set)
--auto-mergeEnable auto-merge on created pull requests instead of merging directly. (Single commit only)(not set)
--dry-runPrint the commands that would be run without executing them.(not set)
-v, --verbosePrint all commands being run and other verbose output.(not set)
-q, --quietPrint only essential output and errors, suppressing progress messages.(not set)