{"id":36751843,"url":"https://github.com/abustany/saboteur","last_synced_at":"2026-01-12T12:45:41.769Z","repository":{"id":196716754,"uuid":"696990884","full_name":"abustany/saboteur","owner":"abustany","description":"GitHub merge bot that merges branch fast-forward, using Git.","archived":false,"fork":false,"pushed_at":"2025-11-21T05:41:56.000Z","size":56,"stargazers_count":0,"open_issues_count":5,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-11-21T07:16:56.396Z","etag":null,"topics":["github","golang","merge-bot"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/abustany.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-09-26T20:34:19.000Z","updated_at":"2024-11-13T09:20:55.000Z","dependencies_parsed_at":"2023-09-27T04:22:36.776Z","dependency_job_id":"2d49da7d-f243-4187-b87a-0d338a2d28ec","html_url":"https://github.com/abustany/saboteur","commit_stats":null,"previous_names":["abustany/saboteur"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/abustany/saboteur","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abustany%2Fsaboteur","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abustany%2Fsaboteur/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abustany%2Fsaboteur/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abustany%2Fsaboteur/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/abustany","download_url":"https://codeload.github.com/abustany/saboteur/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abustany%2Fsaboteur/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28338983,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-12T12:22:26.515Z","status":"ssl_error","status_checked_at":"2026-01-12T12:22:10.856Z","response_time":98,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["github","golang","merge-bot"],"created_at":"2026-01-12T12:45:41.665Z","updated_at":"2026-01-12T12:45:41.745Z","avatar_url":"https://github.com/abustany.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Saboteur 💣\n\nGitHub merge bot that merges branch fast-forward, using Git.\n\n## Why?\n\nThere is a lot of merge bots for GitHub, but all the ones I checked merge pull\nrequests using the GitHub API. This leaves you with two options:\n\n1. Let the GitHub API merge your PRs using merge commits, not ideal if you\n   prefer linear histories\n\n2. Let the GitHub API merge your PRs using the \"squash\" or \"rebase\" methods,\n   which both modify your commits, not ideal if you like your GPG signatures to\n   be preserved\n\nSaboteur will only merge PRs that are fast forward (rebased on top of) their\nbase branch, and will do so using a regular `git push`, that preserves commit\nSHAs.\n\n## Merge rules\n\nSaboteur can select the pull requests to merge based on the following criteria:\n\n- Target branch\n- Successful checks (eg. GitHub Actions results)\n- Labels\n\n## Setup\n\nSaboteur can authenticate itself using either personal access tokens or as a\nGitHub app.\n\n### Personal access tokens\n\nCreate a [classic Personal Access\nToken](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens)\nwith the `repo` scope.\n\n**Note: fine grained access tokens are currently unsupported, since they don't\nwork well with the GraphQL API used by Saboteur**.\n\nCheck the examples in [saboteur.yml](saboteur.yml) to see how to set the token\nin the config file, either directly or via an environment variable.\n\n### GitHub apps\n\n[Register a new GitHub\napp](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/registering-a-github-app),\nsetting neither a callback URL nor a webhook URL. Set the repository\npermissions as follows:\n\n- Actions: read only\n- Checks: read only\n- Commit statuses: read only\n- Content: read and write\n- Pull requests: read only\n\nLeave organization permissions and account permissions empty.\n\nOnce the app is created, write down the application ID on top of the page. Go\nto the bottom of the page and generate a new private key for the app. Go to the\n\"Install App\" section, and install the app into your account. Once the app is\ninstalled, retrieve the installation ID from the URL: if the URL looks like\n`https://github.com/organizations/my-org/settings/installations/12345678`, the\ninstallation ID is `12345678`.\n\nCheck the example in [saboteur.yml](saboteur.yml) to see where to input those\nvalues.\n\n## Is this project ready/stable?\n\nThis project is at the MVP stage: you can call it from a cron job, and it'll\nmerge PRs as instructed. The damages it can cause should be quite limited,\nsince it only attempts fast forward pushes.\n\nPotential future improvements include:\n- listening to webhook events for faster reactions\n- figure out if cloning is really necessary?\n- sandboxing the git operations better, whether it is using libgit2's SQLite\n  ODB backend, a WASM build of libgit2…\n\n## Isn't cloning a repo for each merge expensive?\n\nAccording to my understanding of git, one should be able to do a `git push`\nwithout cloning at all (we are just asking Git to update a remote ref in a fast\nforward way). This doesn't seem to work in practice for reasons I don't fully\nunderstand, so Saboteur will instead do a shallow clone of your repo, using an\nobject filter to filter out blobs (file contents). What this means is that\ncloning is pretty fast, eg. cloning the latest master of torvalds/linux with\nthis method downloads less than 5MB. When GitHub implements support for\nadditional object filters, we can make this process even more efficient (we\ncurrently clone tree objects, which we don't need)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabustany%2Fsaboteur","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fabustany%2Fsaboteur","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabustany%2Fsaboteur/lists"}