├── .dockerignore ├── .env.example ├── data └── permissions │ ├── bors.try.json.example │ └── bors.review.json.example ├── src ├── tests │ ├── utils │ │ ├── mod.rs │ │ ├── io.rs │ │ ├── webhook.rs │ │ └── sync.rs │ └── mock │ │ ├── permissions.rs │ │ ├── oauth.rs │ │ ├── app.rs │ │ ├── workflow.rs │ │ └── comment.rs ├── utils │ ├── mod.rs │ ├── logging.rs │ ├── text.rs │ └── sort_queue.rs ├── github │ ├── error.rs │ ├── labels.rs │ ├── oauth.rs │ └── mod.rs ├── lib.rs ├── bors │ ├── handlers │ │ ├── ping.rs │ │ ├── help.rs │ │ ├── retry.rs │ │ └── info.rs │ ├── context.rs │ ├── labels.rs │ ├── command │ │ └── mod.rs │ ├── event.rs │ └── build.rs ├── templates.rs └── permissions.rs ├── .gitignore ├── tests └── data │ ├── migrations │ ├── 20250331142635_add_pr_status_index.sql │ ├── 20250624212942_add_workflow_cascade_delete.sql │ ├── 20250624153028_rename_build_id_to_try_build_id.sql │ ├── 20250227095730_add_priority_to_pr.sql │ ├── 20251215115722_add_kind_to_build.sql │ ├── 20250307150834_add_rollup_to_pr.sql │ ├── 20250318120405_add_pr_status.sql │ ├── 20250304150656_add_delegated_to_pr.sql │ ├── 20250313013946_add_base_branch_to_pr.sql │ ├── 20240626072340_add_approved_by_to_pr.sql │ ├── 20250311143621_add_approved_sha_to_pr.sql │ ├── 20250315140840_add_merge_state_to_pr.sql │ ├── 20240518024921_create_pr.sql │ ├── 20250324151905_rename_delegated_to_delegated_permission.sql │ ├── 20250624151719_add_auto_build_to_pr.sql │ ├── 20250702100259_add_check_run_id_to_build.sql │ ├── 20250605133222_add_author_to_pr.sql │ ├── 20250607174500_add_assignees_to_pr.sql │ ├── 20250605092733_add_title_to_pr.sql │ ├── 20250729051122_create_comment.sql │ ├── 20250304172534_add_repository_model.sql │ ├── 20240517094752_create_build.sql │ └── 20240518024946_create_workflow.sql │ └── webhook │ ├── security-advisory-published.json │ ├── installation-unsuspend.json │ └── installation-suspend.json ├── migrations ├── 20250304172534_add_repository_model.down.sql ├── 20250605092733_add_title_to_pr.down.sql ├── 20250307150834_add_rollup_to_pr.down.sql ├── 20250307150834_add_rollup_to_pr.up.sql ├── 20250318120405_add_pr_status.down.sql ├── 20250331142635_add_pr_status_index.down.sql ├── 20250605133222_add_author_to_pr.down.sql ├── 20250729051122_create_comment.down.sql ├── 20251215115722_add_kind_to_build.down.sql ├── 20240518024946_create_workflow.down.sql ├── 20250227095730_add_priority_to_pr.down.sql ├── 20250227095730_add_priority_to_pr.up.sql ├── 20250304150656_add_delegated_to_pr.down.sql ├── 20250607174500_add_assignees_to_pr.down.sql ├── 20240626072340_add_approved_by_to_pr.down.sql ├── 20250313013946_add_base_branch_to_pr.down.sql ├── 20250702100259_add_check_run_id_to_build.down.sql ├── 20250702100259_add_check_run_id_to_build.up.sql ├── 20240517094752_create_build.down.sql ├── 20240518024921_create_pr.down.sql ├── 20240626072340_add_approved_by_to_pr.up.sql ├── 20250311143621_add_approved_sha_to_pr.down.sql ├── 20250311143621_add_approved_sha_to_pr.up.sql ├── 20250315140840_add_merge_state_to_pr.down.sql ├── 20250605092733_add_title_to_pr.up.sql ├── 20250624151719_add_auto_build_to_pr.down.sql ├── 20250605133222_add_author_to_pr.up.sql ├── 20250304150656_add_delegated_to_pr.up.sql ├── 20250318120405_add_pr_status.up.sql ├── 20250331142635_add_pr_status_index.up.sql ├── 20250607174500_add_assignees_to_pr.up.sql ├── 20250624151719_add_auto_build_to_pr.up.sql ├── 20250315140840_add_merge_state_to_pr.up.sql ├── 20250313013946_add_base_branch_to_pr.up.sql ├── 20250624212942_add_workflow_cascade_delete.down.sql ├── 20250624212942_add_workflow_cascade_delete.up.sql ├── 20251215115722_add_kind_to_build.up.sql ├── 20250624153028_rename_build_id_to_try_build_id.down.sql ├── 20250624153028_rename_build_id_to_try_build_id.up.sql ├── 20250304172534_add_repository_model.up.sql ├── 20250729051122_create_comment.up.sql ├── 20250324151905_rename_delegated_to_delegated_permission.up.sql ├── 20250324151905_rename_delegated_to_delegated_permission.down.sql ├── 20240517094752_create_build.up.sql ├── 20240518024921_create_pr.up.sql └── 20240518024946_create_workflow.up.sql ├── docker-compose.yaml ├── .sqlx ├── query-31a9c72676df4d3c7f13b7a0b92dcb7e473fa2086d7e4b84a9dd4aacac2fde28.json ├── query-aa9f3483df0d2ab722603300368e19a9ae88785203c49bc4622a704d9bef220d.json ├── query-712d1d8da977a960ecfe0da7cecfe0ba1211800398b110f9cf2328e84360f29a.json ├── query-bf55fcca8efe00b9cf906c31b3266f17bd2abf63dcee374959bd8fb978890656.json ├── query-33cf1b803a0b658f197ab26d87e2c6c4fe42128cb481a234372626f35392e809.json ├── query-667daf4042d6129c923d1f8f6b1b19d263ad3a242cbf60841d1284da702b78ff.json ├── query-6d15016184e78bd7c8b9e6448bb5dbb8e95162d7d0432cb07ce9da170c1941d7.json ├── query-a7498731ebc2ae95a48c52046eb9377656acd7bb2907000abcbee1c7ce90ad43.json ├── query-3ae0ba7ce98f0a68b47df52a9181134df3ac7818af0c085ac432eb52874a62a2.json ├── query-e766e5d3d5c9323532585bd1c770e6d19f443219fdf2b4debce24b22bf91868a.json ├── query-493281673a29a83ddf6d37bdddfb099ae6455cdbd346e4e97eaffcb3b5e7a874.json ├── query-a23e71d4cf48f7138fc72a7d502006c1b6b73d1567a16bddde9b4007beff2500.json ├── query-bf382d117c8d4509dd1da794203a6e8e5fe74e1905fecfa75707b532340e9f5e.json ├── query-0f0acf10f909aa60beaedde032c1770566948a06c2b2e1f07806e948d83803e1.json ├── query-6bb7558f15546f6b11396411a4b0461b25d20350e99cd55e8f0bc2bca2177134.json ├── query-d41b24391f18cdbdf9edac46544a5275ba46b057d2f64233f5dff829d6601b90.json ├── query-4d41f7edf9b67d7f682fd662cd6588b6a2f927c5bf08a132a45b2488a73716d9.json ├── query-2eaf959d9345ee1d1baf5453b693f0914014029977243d15ef233e8fba49f2d5.json ├── query-da72c377ada32448d3f8f75227ead7dedf0851d40f36c59fd4052fa95f370e2e.json ├── query-8b1b5771a0ae3683308438798c4885b1e593b38f6b16617bc9d52dda33115076.json ├── query-6a911b59abd89bdb10e283902ea3a08c483e5a6238344c7ac969a0602674e888.json ├── query-90c5f33613d8562ad5e1be99c6574d268ada15d677f4f8b15435d9605aad04f8.json ├── query-cc7019b45037e155c4ce8f0bad9df0dcbe339fb59f93aec8bb6d34719ee042d3.json ├── query-4e0aaa8eea9ccadab0fc5514558a7a72f05ef3ba1644f43ae5e5d54bd64ca533.json ├── query-070fd5babee0d508900290c872bd9247855b9712d6633c1dc3683751941749b1.json ├── query-c34bcda0288120bab553f7ca8bf0f84c02a320d4663394ed4f4ca04d73faf718.json ├── query-41b428f19147925d886548386f70b9f5aaaa3f01446ec534242d42433dba62ae.json ├── query-0abf331d49412456f637308ffd57af315bfdfe13182eb0564e02ae24a1780662.json ├── query-0a04cfb0a86e79776e834b665aca52fec0229462cb548fd37a153b9470e54e95.json ├── query-3107d965f4602e40a60f856dda74ed7cc57f40071bad913011db294e90d6859b.json ├── query-62763d313a8278147d0f6fea3dd3d945e77bbdcf50e3fcfba029f24ce7481618.json ├── query-a146064228559bca97e795319590ddeb83e3a97562c6330fe72375bfc3cadfd3.json ├── query-483efa3070e78c109d493533ca114433d1228427b35742760521487a9bc0be51.json ├── query-f697693a6cbccdf9b2557842a1ff87959d6e85012dba5e9d7159541ef317b691.json ├── query-5c3c81357e7329a9db4b2bf05c2615433b4a61c43f26f2ae39e68d02d609bf90.json └── query-367bf2de86a8a92b56b2be296cef119a7835ab5fd09326265a4a1403d0d6ad19.json ├── templates ├── not_found.html ├── help.html └── base.html ├── .github ├── renovate.json5 └── workflows │ ├── deploy-production.yml │ ├── deploy-staging.yml │ └── test.yml ├── Dockerfile ├── LICENSE-MIT ├── rust-bors.example.toml ├── Cargo.toml ├── scripts └── seed.py └── README.md /.dockerignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | DATABASE_URL=postgres://bors:bors@localhost:5432/bors 2 | -------------------------------------------------------------------------------- /data/permissions/bors.try.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "github_ids": [] 3 | } 4 | -------------------------------------------------------------------------------- /data/permissions/bors.review.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "github_ids": [] 3 | } 4 | -------------------------------------------------------------------------------- /src/tests/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod io; 2 | pub mod sync; 3 | pub mod webhook; 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .env 3 | .DS_Store 4 | __pycache__/ 5 | *.review.json 6 | *.try.json 7 | -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod logging; 2 | pub mod sort_queue; 3 | pub mod text; 4 | pub mod timing; 5 | -------------------------------------------------------------------------------- /tests/data/migrations/20250331142635_add_pr_status_index.sql: -------------------------------------------------------------------------------- 1 | -- Empty to satisfy migration tests 2 | -------------------------------------------------------------------------------- /tests/data/migrations/20250624212942_add_workflow_cascade_delete.sql: -------------------------------------------------------------------------------- 1 | -- Empty to satisfy migration tests 2 | -------------------------------------------------------------------------------- /migrations/20250304172534_add_repository_model.down.sql: -------------------------------------------------------------------------------- 1 | -- Add down migration script here 2 | DROP TABLE IF EXISTS repository; -------------------------------------------------------------------------------- /migrations/20250605092733_add_title_to_pr.down.sql: -------------------------------------------------------------------------------- 1 | -- Add down migration script here 2 | ALTER TABLE pull_request DROP COLUMN title; -------------------------------------------------------------------------------- /migrations/20250307150834_add_rollup_to_pr.down.sql: -------------------------------------------------------------------------------- 1 | -- Add up migration script here 2 | ALTER TABLE pull_request DROP COLUMN rollup; 3 | -------------------------------------------------------------------------------- /migrations/20250307150834_add_rollup_to_pr.up.sql: -------------------------------------------------------------------------------- 1 | -- Add up migration script here 2 | ALTER TABLE pull_request ADD COLUMN rollup TEXT; 3 | -------------------------------------------------------------------------------- /migrations/20250318120405_add_pr_status.down.sql: -------------------------------------------------------------------------------- 1 | -- Add down migration script here 2 | ALTER TABLE pull_request DROP COLUMN status; 3 | -------------------------------------------------------------------------------- /migrations/20250331142635_add_pr_status_index.down.sql: -------------------------------------------------------------------------------- 1 | -- Add down migration script here 2 | DROP INDEX IF EXISTS pull_request_status_idx; -------------------------------------------------------------------------------- /migrations/20250605133222_add_author_to_pr.down.sql: -------------------------------------------------------------------------------- 1 | -- Add down migration script here 2 | ALTER TABLE pull_request DROP COLUMN author; 3 | -------------------------------------------------------------------------------- /migrations/20250729051122_create_comment.down.sql: -------------------------------------------------------------------------------- 1 | DROP INDEX IF EXISTS comment_repo_pr_tag_idx; 2 | 3 | DROP TABLE IF EXISTS comment; 4 | -------------------------------------------------------------------------------- /migrations/20251215115722_add_kind_to_build.down.sql: -------------------------------------------------------------------------------- 1 | -- Add down migration script here 2 | ALTER TABLE build 3 | DROP COLUMN kind; 4 | -------------------------------------------------------------------------------- /migrations/20240518024946_create_workflow.down.sql: -------------------------------------------------------------------------------- 1 | DROP INDEX IF EXISTS workflow_build_id_url_idx; 2 | 3 | DROP TABLE IF EXISTS workflow; 4 | -------------------------------------------------------------------------------- /migrations/20250227095730_add_priority_to_pr.down.sql: -------------------------------------------------------------------------------- 1 | -- Add down migration script here 2 | ALTER TABLE pull_request DROP COLUMN priority; 3 | -------------------------------------------------------------------------------- /migrations/20250227095730_add_priority_to_pr.up.sql: -------------------------------------------------------------------------------- 1 | -- Add up migration script here 2 | ALTER TABLE pull_request ADD COLUMN priority INT; 3 | -------------------------------------------------------------------------------- /migrations/20250304150656_add_delegated_to_pr.down.sql: -------------------------------------------------------------------------------- 1 | -- Add down migration script here 2 | ALTER TABLE pull_request DROP COLUMN delegated; -------------------------------------------------------------------------------- /migrations/20250607174500_add_assignees_to_pr.down.sql: -------------------------------------------------------------------------------- 1 | -- Add down migration script here 2 | ALTER TABLE pull_request DROP COLUMN assignees; -------------------------------------------------------------------------------- /migrations/20240626072340_add_approved_by_to_pr.down.sql: -------------------------------------------------------------------------------- 1 | -- Add down migration script here 2 | ALTER TABLE pull_request DROP COLUMN approved_by; 3 | -------------------------------------------------------------------------------- /migrations/20250313013946_add_base_branch_to_pr.down.sql: -------------------------------------------------------------------------------- 1 | -- Add down migration script here 2 | ALTER TABLE pull_request DROP COLUMN base_branch; 3 | -------------------------------------------------------------------------------- /migrations/20250702100259_add_check_run_id_to_build.down.sql: -------------------------------------------------------------------------------- 1 | -- Add down migration script here 2 | ALTER TABLE build DROP COLUMN check_run_id; 3 | -------------------------------------------------------------------------------- /migrations/20250702100259_add_check_run_id_to_build.up.sql: -------------------------------------------------------------------------------- 1 | -- Add up migration script here 2 | ALTER TABLE build ADD COLUMN check_run_id BIGINT; 3 | -------------------------------------------------------------------------------- /migrations/20240517094752_create_build.down.sql: -------------------------------------------------------------------------------- 1 | DROP INDEX IF EXISTS build_repository_branch_commit_sha_idx; 2 | 3 | DROP TABLE IF EXISTS build; 4 | -------------------------------------------------------------------------------- /migrations/20240518024921_create_pr.down.sql: -------------------------------------------------------------------------------- 1 | DROP INDEX IF EXISTS pull_request_repository_number_idx; 2 | 3 | DROP TABLE IF EXISTS pull_request; 4 | -------------------------------------------------------------------------------- /migrations/20240626072340_add_approved_by_to_pr.up.sql: -------------------------------------------------------------------------------- 1 | -- Add up migration script here 2 | ALTER TABLE pull_request ADD COLUMN approved_by TEXT; 3 | 4 | -------------------------------------------------------------------------------- /migrations/20250311143621_add_approved_sha_to_pr.down.sql: -------------------------------------------------------------------------------- 1 | -- Add down migration script here 2 | ALTER TABLE pull_request DROP COLUMN approved_sha; 3 | -------------------------------------------------------------------------------- /migrations/20250311143621_add_approved_sha_to_pr.up.sql: -------------------------------------------------------------------------------- 1 | -- Add up migration script here 2 | ALTER TABLE pull_request ADD COLUMN approved_sha TEXT; 3 | -------------------------------------------------------------------------------- /migrations/20250315140840_add_merge_state_to_pr.down.sql: -------------------------------------------------------------------------------- 1 | -- Add down migration script here 2 | ALTER TABLE pull_request DROP COLUMN mergeable_state; 3 | -------------------------------------------------------------------------------- /migrations/20250605092733_add_title_to_pr.up.sql: -------------------------------------------------------------------------------- 1 | -- Add up migration script here 2 | ALTER TABLE pull_request ADD COLUMN title TEXT NOT NULL DEFAULT ''; -------------------------------------------------------------------------------- /migrations/20250624151719_add_auto_build_to_pr.down.sql: -------------------------------------------------------------------------------- 1 | -- Add down migration script here 2 | ALTER TABLE pull_request DROP COLUMN auto_build_id; 3 | -------------------------------------------------------------------------------- /migrations/20250605133222_add_author_to_pr.up.sql: -------------------------------------------------------------------------------- 1 | -- Add up migration script here 2 | ALTER TABLE pull_request ADD COLUMN author TEXT NOT NULL DEFAULT ''; 3 | -------------------------------------------------------------------------------- /migrations/20250304150656_add_delegated_to_pr.up.sql: -------------------------------------------------------------------------------- 1 | -- Add up migration script here 2 | ALTER TABLE pull_request ADD COLUMN delegated BOOLEAN NOT NULL DEFAULT FALSE; -------------------------------------------------------------------------------- /migrations/20250318120405_add_pr_status.up.sql: -------------------------------------------------------------------------------- 1 | -- Add up migration script here 2 | ALTER TABLE pull_request 3 | ADD COLUMN status TEXT NOT NULL DEFAULT 'open'; 4 | -------------------------------------------------------------------------------- /migrations/20250331142635_add_pr_status_index.up.sql: -------------------------------------------------------------------------------- 1 | -- Add up migration script here 2 | CREATE INDEX IF NOT EXISTS pull_request_status_idx ON pull_request (status); -------------------------------------------------------------------------------- /migrations/20250607174500_add_assignees_to_pr.up.sql: -------------------------------------------------------------------------------- 1 | -- Add up migration script here 2 | ALTER TABLE pull_request ADD COLUMN assignees TEXT NOT NULL DEFAULT ''; 3 | -------------------------------------------------------------------------------- /migrations/20250624151719_add_auto_build_to_pr.up.sql: -------------------------------------------------------------------------------- 1 | -- Add up migration script here 2 | ALTER TABLE pull_request ADD COLUMN auto_build_id INTEGER REFERENCES build(id); 3 | -------------------------------------------------------------------------------- /migrations/20250315140840_add_merge_state_to_pr.up.sql: -------------------------------------------------------------------------------- 1 | -- Add up migration script here 2 | ALTER TABLE pull_request ADD COLUMN mergeable_state TEXT NOT NULL DEFAULT 'unknown'; -------------------------------------------------------------------------------- /migrations/20250313013946_add_base_branch_to_pr.up.sql: -------------------------------------------------------------------------------- 1 | -- Add up migration script here 2 | ALTER TABLE pull_request 3 | ADD COLUMN base_branch TEXT NOT NULL DEFAULT 'master'; 4 | -------------------------------------------------------------------------------- /tests/data/migrations/20250624153028_rename_build_id_to_try_build_id.sql: -------------------------------------------------------------------------------- 1 | -- The remaining should be renamed build_id -> try_build_id 2 | 3 | UPDATE pull_request 4 | SET 5 | try_build_id = 1 6 | WHERE 7 | id = 1; 8 | -------------------------------------------------------------------------------- /tests/data/migrations/20250227095730_add_priority_to_pr.sql: -------------------------------------------------------------------------------- 1 | UPDATE pull_request 2 | SET 3 | priority = 1 4 | WHERE 5 | id = 1; 6 | 7 | UPDATE pull_request 8 | SET 9 | priority = 2 10 | WHERE 11 | id = 2; 12 | -------------------------------------------------------------------------------- /tests/data/migrations/20251215115722_add_kind_to_build.sql: -------------------------------------------------------------------------------- 1 | -- Empty to satisfy migration tests 2 | -- We don't need to add any new data here, as the kind column has been auto-filled 3 | -- from the branch column by this migration. 4 | -------------------------------------------------------------------------------- /tests/data/migrations/20250307150834_add_rollup_to_pr.sql: -------------------------------------------------------------------------------- 1 | UPDATE pull_request 2 | SET 3 | rollup = 'always' 4 | WHERE 5 | id = 1; 6 | 7 | UPDATE pull_request 8 | SET 9 | rollup = 'never' 10 | WHERE 11 | id = 2; 12 | -------------------------------------------------------------------------------- /tests/data/migrations/20250318120405_add_pr_status.sql: -------------------------------------------------------------------------------- 1 | UPDATE pull_request 2 | SET 3 | status = 'merged' 4 | WHERE 5 | id = 1; 6 | 7 | UPDATE pull_request 8 | SET 9 | status = 'closed' 10 | WHERE 11 | id = 2; 12 | -------------------------------------------------------------------------------- /tests/data/migrations/20250304150656_add_delegated_to_pr.sql: -------------------------------------------------------------------------------- 1 | UPDATE pull_request 2 | SET 3 | delegated = TRUE 4 | WHERE 5 | id = 1; 6 | 7 | UPDATE pull_request 8 | SET 9 | delegated = FALSE 10 | WHERE 11 | id = 2; 12 | -------------------------------------------------------------------------------- /migrations/20250624212942_add_workflow_cascade_delete.down.sql: -------------------------------------------------------------------------------- 1 | -- Add down migration script here 2 | ALTER TABLE workflow DROP CONSTRAINT fk_build_id; 3 | ALTER TABLE workflow ADD CONSTRAINT fk_build_id FOREIGN KEY (build_id) REFERENCES build(id); 4 | -------------------------------------------------------------------------------- /tests/data/migrations/20250313013946_add_base_branch_to_pr.sql: -------------------------------------------------------------------------------- 1 | UPDATE pull_request 2 | SET 3 | base_branch = 'main' 4 | WHERE 5 | id = 1; 6 | 7 | UPDATE pull_request 8 | SET 9 | base_branch = 'beta' 10 | WHERE 11 | id = 2; 12 | -------------------------------------------------------------------------------- /tests/data/migrations/20240626072340_add_approved_by_to_pr.sql: -------------------------------------------------------------------------------- 1 | UPDATE pull_request 2 | SET 3 | approved_by = 'kobzol' 4 | WHERE 5 | id = 1; 6 | 7 | UPDATE pull_request 8 | SET 9 | approved_by = 'sakib25800' 10 | WHERE 11 | id = 2; 12 | -------------------------------------------------------------------------------- /migrations/20250624212942_add_workflow_cascade_delete.up.sql: -------------------------------------------------------------------------------- 1 | -- Add up migration script here 2 | ALTER TABLE workflow DROP CONSTRAINT fk_build_id; 3 | ALTER TABLE workflow ADD CONSTRAINT fk_build_id FOREIGN KEY (build_id) REFERENCES build(id) ON DELETE CASCADE; 4 | -------------------------------------------------------------------------------- /tests/data/migrations/20250311143621_add_approved_sha_to_pr.sql: -------------------------------------------------------------------------------- 1 | UPDATE pull_request 2 | SET 3 | approved_sha = 'abc123def456' 4 | WHERE 5 | id = 1; 6 | 7 | UPDATE pull_request 8 | SET 9 | approved_sha = 'fed987cba654' 10 | WHERE 11 | id = 2; 12 | -------------------------------------------------------------------------------- /tests/data/migrations/20250315140840_add_merge_state_to_pr.sql: -------------------------------------------------------------------------------- 1 | UPDATE pull_request 2 | SET 3 | mergeable_state = 'mergeable' 4 | WHERE 5 | id = 1; 6 | 7 | UPDATE pull_request 8 | SET 9 | mergeable_state = 'has_conflicts' 10 | WHERE 11 | id = 2; 12 | -------------------------------------------------------------------------------- /tests/data/migrations/20240518024921_create_pr.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO 2 | pull_request (repository, number, build_id) 3 | VALUES 4 | ('rust-lang/bors', 269, 1), 5 | ('rust-lang/cargo', 14718, 2), 6 | ('rust-lang/rust', 136864, 3), 7 | ('rust-lang/clippy', 10521, NULL); 8 | -------------------------------------------------------------------------------- /src/tests/utils/io.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | const ROOT_DIR: &str = env!("CARGO_MANIFEST_DIR"); 4 | 5 | pub fn load_test_file(path: &str) -> String { 6 | let path = Path::new(ROOT_DIR).join("tests").join("data").join(path); 7 | std::fs::read_to_string(path).unwrap() 8 | } 9 | -------------------------------------------------------------------------------- /tests/data/migrations/20250324151905_rename_delegated_to_delegated_permission.sql: -------------------------------------------------------------------------------- 1 | UPDATE pull_request 2 | SET 3 | delegated_permission = 'review' 4 | WHERE 5 | id = 1; 6 | 7 | UPDATE pull_request 8 | SET 9 | delegated_permission = NULL 10 | WHERE 11 | id = 2; 12 | -------------------------------------------------------------------------------- /migrations/20251215115722_add_kind_to_build.up.sql: -------------------------------------------------------------------------------- 1 | -- Add up migration script here 2 | ALTER TABLE build 3 | ADD COLUMN kind TEXT NOT NULL DEFAULT 'try'; 4 | 5 | UPDATE build 6 | SET kind = 7 | CASE 8 | WHEN build.branch LIKE '%auto' THEN 'auto' 9 | ELSE 'try' 10 | END; 11 | -------------------------------------------------------------------------------- /tests/data/migrations/20250624151719_add_auto_build_to_pr.sql: -------------------------------------------------------------------------------- 1 | UPDATE pull_request 2 | SET 3 | auto_build_id = 2 4 | WHERE 5 | id = 1; 6 | 7 | UPDATE pull_request 8 | SET 9 | auto_build_id = 3 10 | WHERE 11 | id = 2; 12 | 13 | UPDATE pull_request 14 | SET 15 | auto_build_id = 1 16 | WHERE 17 | id = 3; 18 | -------------------------------------------------------------------------------- /migrations/20250624153028_rename_build_id_to_try_build_id.down.sql: -------------------------------------------------------------------------------- 1 | -- Add down migration script here 2 | ALTER TABLE pull_request DROP CONSTRAINT fk_try_build_id; 3 | 4 | ALTER TABLE pull_request RENAME COLUMN try_build_id TO build_id; 5 | 6 | ALTER TABLE pull_request ADD CONSTRAINT fk_build_id FOREIGN KEY (build_id) REFERENCES build(id); 7 | -------------------------------------------------------------------------------- /migrations/20250624153028_rename_build_id_to_try_build_id.up.sql: -------------------------------------------------------------------------------- 1 | -- Add up migration script here 2 | ALTER TABLE pull_request DROP CONSTRAINT fk_build_id; 3 | 4 | ALTER TABLE pull_request RENAME COLUMN build_id TO try_build_id; 5 | 6 | ALTER TABLE pull_request ADD CONSTRAINT fk_try_build_id FOREIGN KEY (try_build_id) REFERENCES build(id); 7 | -------------------------------------------------------------------------------- /tests/data/migrations/20250702100259_add_check_run_id_to_build.sql: -------------------------------------------------------------------------------- 1 | UPDATE build 2 | SET 3 | check_run_id = 1234567890 4 | WHERE 5 | id = 1; 6 | 7 | UPDATE build 8 | SET 9 | check_run_id = 2345678901 10 | WHERE 11 | id = 2; 12 | 13 | UPDATE build 14 | SET 15 | check_run_id = 3456789012 16 | WHERE 17 | id = 3; 18 | -------------------------------------------------------------------------------- /migrations/20250304172534_add_repository_model.up.sql: -------------------------------------------------------------------------------- 1 | -- Add up migration script here 2 | CREATE TABLE IF NOT EXISTS repository 3 | ( 4 | id SERIAL PRIMARY KEY, 5 | name TEXT NOT NULL UNIQUE, 6 | tree_state INT NULL, 7 | treeclosed_src TEXT, 8 | created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() 9 | ); 10 | -------------------------------------------------------------------------------- /src/utils/logging.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Error; 2 | use tracing::span::Span; 3 | 4 | #[allow(unused)] 5 | pub trait LogError { 6 | fn log_error(&self, error: Error); 7 | } 8 | 9 | impl LogError for Span { 10 | fn log_error(&self, error: Error) { 11 | self.in_scope(|| { 12 | tracing::error!("Error: {error:?}"); 13 | }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | services: 3 | db: 4 | image: postgres:16.9 5 | environment: 6 | POSTGRES_USER: bors 7 | POSTGRES_PASSWORD: bors 8 | POSTGRES_DB: bors 9 | ports: 10 | - "5432:5432" 11 | healthcheck: 12 | test: ["CMD-SHELL", "pg_isready"] 13 | interval: 10s 14 | timeout: 5s 15 | retries: 5 16 | -------------------------------------------------------------------------------- /migrations/20250729051122_create_comment.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS comment ( 2 | id SERIAL PRIMARY KEY, 3 | repository TEXT NOT NULL, 4 | pr_number BIGINT NOT NULL, 5 | tag TEXT NOT NULL, 6 | node_id TEXT NOT NULL, 7 | created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP 8 | ); 9 | 10 | CREATE INDEX comment_repo_pr_tag_idx ON comment (repository, pr_number, tag); 11 | -------------------------------------------------------------------------------- /migrations/20250324151905_rename_delegated_to_delegated_permission.up.sql: -------------------------------------------------------------------------------- 1 | -- Add up migration script here 2 | ALTER TABLE pull_request 3 | ADD COLUMN delegated_permission TEXT; 4 | 5 | UPDATE pull_request 6 | SET 7 | delegated_permission = CASE 8 | WHEN delegated = TRUE THEN 'review' 9 | ELSE NULL 10 | END; 11 | 12 | ALTER TABLE pull_request 13 | DROP COLUMN delegated; 14 | -------------------------------------------------------------------------------- /migrations/20250324151905_rename_delegated_to_delegated_permission.down.sql: -------------------------------------------------------------------------------- 1 | -- Add down migration script here 2 | ALTER TABLE pull_request 3 | ADD COLUMN delegated BOOLEAN; 4 | 5 | UPDATE pull_request 6 | SET 7 | delegated = CASE 8 | WHEN delegated_permission = 'review' THEN TRUE 9 | ELSE FALSE 10 | END; 11 | 12 | ALTER TABLE pull_request 13 | DROP COLUMN delegated_permission; 14 | -------------------------------------------------------------------------------- /.sqlx/query-31a9c72676df4d3c7f13b7a0b92dcb7e473fa2086d7e4b84a9dd4aacac2fde28.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "DELETE FROM comment WHERE id = $1", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int4" 9 | ] 10 | }, 11 | "nullable": [] 12 | }, 13 | "hash": "31a9c72676df4d3c7f13b7a0b92dcb7e473fa2086d7e4b84a9dd4aacac2fde28" 14 | } 15 | -------------------------------------------------------------------------------- /tests/data/migrations/20250605133222_add_author_to_pr.sql: -------------------------------------------------------------------------------- 1 | UPDATE pull_request 2 | SET 3 | author = 'kobzol' 4 | WHERE 5 | id = 1; 6 | 7 | UPDATE pull_request 8 | SET 9 | author = 'ehuss' 10 | WHERE 11 | id = 2; 12 | 13 | UPDATE pull_request 14 | SET 15 | author = 'matthiaskrgr' 16 | WHERE 17 | id = 3; 18 | 19 | UPDATE pull_request 20 | SET 21 | author = 'flip1995' 22 | WHERE 23 | id = 4; -------------------------------------------------------------------------------- /.sqlx/query-aa9f3483df0d2ab722603300368e19a9ae88785203c49bc4622a704d9bef220d.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "UPDATE pull_request SET auto_build_id = NULL WHERE id = $1", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int4" 9 | ] 10 | }, 11 | "nullable": [] 12 | }, 13 | "hash": "aa9f3483df0d2ab722603300368e19a9ae88785203c49bc4622a704d9bef220d" 14 | } 15 | -------------------------------------------------------------------------------- /.sqlx/query-712d1d8da977a960ecfe0da7cecfe0ba1211800398b110f9cf2328e84360f29a.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "UPDATE build SET status = $1 WHERE id = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Text", 9 | "Int4" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "712d1d8da977a960ecfe0da7cecfe0ba1211800398b110f9cf2328e84360f29a" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-bf55fcca8efe00b9cf906c31b3266f17bd2abf63dcee374959bd8fb978890656.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "UPDATE pull_request SET delegated_permission = NULL WHERE id = $1", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int4" 9 | ] 10 | }, 11 | "nullable": [] 12 | }, 13 | "hash": "bf55fcca8efe00b9cf906c31b3266f17bd2abf63dcee374959bd8fb978890656" 14 | } 15 | -------------------------------------------------------------------------------- /tests/data/migrations/20250607174500_add_assignees_to_pr.sql: -------------------------------------------------------------------------------- 1 | UPDATE pull_request 2 | SET 3 | assignees = 'kobzol,ehuss' 4 | WHERE 5 | id = 1; 6 | 7 | UPDATE pull_request 8 | SET 9 | assignees = 'sakib25800' 10 | WHERE 11 | id = 2; 12 | 13 | UPDATE pull_request 14 | SET 15 | assignees = 'matthiaskrgr,flip1995,lnicola' 16 | WHERE 17 | id = 3; 18 | 19 | UPDATE pull_request 20 | SET 21 | assignees = '' 22 | WHERE 23 | id = 4; -------------------------------------------------------------------------------- /.sqlx/query-33cf1b803a0b658f197ab26d87e2c6c4fe42128cb481a234372626f35392e809.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "UPDATE build SET check_run_id = $1 WHERE id = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int8", 9 | "Int4" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "33cf1b803a0b658f197ab26d87e2c6c4fe42128cb481a234372626f35392e809" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-667daf4042d6129c923d1f8f6b1b19d263ad3a242cbf60841d1284da702b78ff.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "UPDATE pull_request SET priority = $1 WHERE id = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int4", 9 | "Int4" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "667daf4042d6129c923d1f8f6b1b19d263ad3a242cbf60841d1284da702b78ff" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-6d15016184e78bd7c8b9e6448bb5dbb8e95162d7d0432cb07ce9da170c1941d7.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "UPDATE workflow SET status = $1 WHERE run_id = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Text", 9 | "Int8" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "6d15016184e78bd7c8b9e6448bb5dbb8e95162d7d0432cb07ce9da170c1941d7" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-a7498731ebc2ae95a48c52046eb9377656acd7bb2907000abcbee1c7ce90ad43.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "UPDATE pull_request SET rollup = $1 WHERE id = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Text", 9 | "Int4" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "a7498731ebc2ae95a48c52046eb9377656acd7bb2907000abcbee1c7ce90ad43" 15 | } 16 | -------------------------------------------------------------------------------- /migrations/20240517094752_create_build.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS build ( 2 | id SERIAL PRIMARY KEY, 3 | repository TEXT NOT NULL, 4 | branch TEXT NOT NULL, 5 | commit_sha TEXT NOT NULL, 6 | status TEXT NOT NULL, 7 | parent TEXT NOT NULL, 8 | created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP 9 | ); 10 | 11 | CREATE UNIQUE INDEX IF NOT EXISTS build_repository_branch_commit_sha_idx ON build (repository, branch, commit_sha); 12 | -------------------------------------------------------------------------------- /.sqlx/query-3ae0ba7ce98f0a68b47df52a9181134df3ac7818af0c085ac432eb52874a62a2.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "UPDATE pull_request SET auto_build_id = $1 WHERE id = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int4", 9 | "Int4" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "3ae0ba7ce98f0a68b47df52a9181134df3ac7818af0c085ac432eb52874a62a2" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-e766e5d3d5c9323532585bd1c770e6d19f443219fdf2b4debce24b22bf91868a.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "UPDATE pull_request SET try_build_id = $1 WHERE id = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int4", 9 | "Int4" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "e766e5d3d5c9323532585bd1c770e6d19f443219fdf2b4debce24b22bf91868a" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-493281673a29a83ddf6d37bdddfb099ae6455cdbd346e4e97eaffcb3b5e7a874.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "UPDATE pull_request SET delegated_permission = $1 WHERE id = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Text", 9 | "Int4" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "493281673a29a83ddf6d37bdddfb099ae6455cdbd346e4e97eaffcb3b5e7a874" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-a23e71d4cf48f7138fc72a7d502006c1b6b73d1567a16bddde9b4007beff2500.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "UPDATE pull_request SET assignees = $1 WHERE repository = $2 AND number = $3", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Text", 9 | "Text", 10 | "Int8" 11 | ] 12 | }, 13 | "nullable": [] 14 | }, 15 | "hash": "a23e71d4cf48f7138fc72a7d502006c1b6b73d1567a16bddde9b4007beff2500" 16 | } 17 | -------------------------------------------------------------------------------- /.sqlx/query-bf382d117c8d4509dd1da794203a6e8e5fe74e1905fecfa75707b532340e9f5e.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "UPDATE pull_request SET status = $3 WHERE repository = $1 AND number = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Text", 9 | "Int8", 10 | "Text" 11 | ] 12 | }, 13 | "nullable": [] 14 | }, 15 | "hash": "bf382d117c8d4509dd1da794203a6e8e5fe74e1905fecfa75707b532340e9f5e" 16 | } 17 | -------------------------------------------------------------------------------- /migrations/20240518024921_create_pr.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS pull_request ( 2 | id SERIAL PRIMARY KEY, 3 | repository TEXT NOT NULL, 4 | number BIGINT NOT NULL, 5 | build_id INT, 6 | created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, 7 | CONSTRAINT fk_build_id FOREIGN KEY (build_id) REFERENCES build(id) 8 | ); 9 | 10 | -- create index for repository and number 11 | CREATE UNIQUE INDEX IF NOT EXISTS pull_request_repository_number_idx ON pull_request (repository, number); 12 | -------------------------------------------------------------------------------- /.sqlx/query-0f0acf10f909aa60beaedde032c1770566948a06c2b2e1f07806e948d83803e1.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "UPDATE pull_request SET mergeable_state = $3 WHERE repository = $1 AND number = $2", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Text", 9 | "Int8", 10 | "Text" 11 | ] 12 | }, 13 | "nullable": [] 14 | }, 15 | "hash": "0f0acf10f909aa60beaedde032c1770566948a06c2b2e1f07806e948d83803e1" 16 | } 17 | -------------------------------------------------------------------------------- /tests/data/migrations/20250605092733_add_title_to_pr.sql: -------------------------------------------------------------------------------- 1 | UPDATE pull_request 2 | SET 3 | title = 'Fix critical bug in merge queue' 4 | WHERE 5 | id = 1; 6 | 7 | UPDATE pull_request 8 | SET 9 | title = 'Add support for new CI platform' 10 | WHERE 11 | id = 2; 12 | 13 | UPDATE pull_request 14 | SET 15 | title = 'Update documentation for rollup feature' 16 | WHERE 17 | id = 3; 18 | 19 | UPDATE pull_request 20 | SET 21 | title = 'Refactor database operations' 22 | WHERE 23 | id = 4; -------------------------------------------------------------------------------- /templates/not_found.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Page not found{% endblock %} 4 | 5 | {% block head %} 6 | 15 | {% endblock %} 16 | 17 | {% block body %} 18 |
19 |

Page not found

20 |

Go back to the index

21 |
22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /tests/data/migrations/20250729051122_create_comment.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO 2 | comment (repository, pr_number, tag, node_id) 3 | VALUES 4 | ( 5 | 'rust-lang/bors', 6 | 123, 7 | 'TryBuildStarted', 8 | 'MDU6SXNzdWUxMDAwMDE=' 9 | ), 10 | ( 11 | 'rust-lang/clippy', 12 | 12345, 13 | 'TryBuildStarted', 14 | 'MDU6SXNzdWUxMDAwMDI=' 15 | ), 16 | ( 17 | 'rust-lang/rust', 18 | 123456, 19 | 'TryBuildStarted', 20 | 'MDU6SXNzdWUxMDAwMDM=' 21 | ); 22 | -------------------------------------------------------------------------------- /migrations/20240518024946_create_workflow.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS workflow ( 2 | id SERIAL PRIMARY KEY, 3 | build_id INT NOT NULL, 4 | name TEXT NOT NULL, 5 | run_id BIGINT NOT NULL, 6 | url TEXT NOT NULL, 7 | status TEXT NOT NULL, 8 | type TEXT NOT NULL, 9 | created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, 10 | CONSTRAINT fk_build_id FOREIGN KEY (build_id) REFERENCES build(id) 11 | ); 12 | 13 | -- create index for build url 14 | CREATE UNIQUE INDEX IF NOT EXISTS workflow_build_id_url_idx ON workflow (build_id, url); 15 | -------------------------------------------------------------------------------- /.sqlx/query-6bb7558f15546f6b11396411a4b0461b25d20350e99cd55e8f0bc2bca2177134.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\n INSERT INTO comment (repository, pr_number, tag, node_id)\n VALUES ($1, $2, $3, $4)\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Text", 9 | "Int8", 10 | "Text", 11 | "Text" 12 | ] 13 | }, 14 | "nullable": [] 15 | }, 16 | "hash": "6bb7558f15546f6b11396411a4b0461b25d20350e99cd55e8f0bc2bca2177134" 17 | } 18 | -------------------------------------------------------------------------------- /.sqlx/query-d41b24391f18cdbdf9edac46544a5275ba46b057d2f64233f5dff829d6601b90.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\n INSERT INTO repository (name, tree_state, treeclosed_src)\n VALUES ($1, $2, $3)\n ON CONFLICT (name) DO NOTHING\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Text", 9 | "Int4", 10 | "Text" 11 | ] 12 | }, 13 | "nullable": [] 14 | }, 15 | "hash": "d41b24391f18cdbdf9edac46544a5275ba46b057d2f64233f5dff829d6601b90" 16 | } 17 | -------------------------------------------------------------------------------- /.sqlx/query-4d41f7edf9b67d7f682fd662cd6588b6a2f927c5bf08a132a45b2488a73716d9.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\n UPDATE pull_request\n SET approved_by = NULL,\n approved_sha = NULL,\n auto_build_id = NULL\n WHERE id = $1", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int4" 9 | ] 10 | }, 11 | "nullable": [] 12 | }, 13 | "hash": "4d41f7edf9b67d7f682fd662cd6588b6a2f927c5bf08a132a45b2488a73716d9" 14 | } 15 | -------------------------------------------------------------------------------- /.sqlx/query-2eaf959d9345ee1d1baf5453b693f0914014029977243d15ef233e8fba49f2d5.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nSELECT url\nFROM workflow\nWHERE build_id = $1\n", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "url", 9 | "type_info": "Text" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Int4" 15 | ] 16 | }, 17 | "nullable": [ 18 | false 19 | ] 20 | }, 21 | "hash": "2eaf959d9345ee1d1baf5453b693f0914014029977243d15ef233e8fba49f2d5" 22 | } 23 | -------------------------------------------------------------------------------- /.sqlx/query-da72c377ada32448d3f8f75227ead7dedf0851d40f36c59fd4052fa95f370e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nINSERT INTO workflow (build_id, name, url, run_id, type, status)\nVALUES ($1, $2, $3, $4, $5, $6)\n", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int4", 9 | "Text", 10 | "Text", 11 | "Int8", 12 | "Text", 13 | "Text" 14 | ] 15 | }, 16 | "nullable": [] 17 | }, 18 | "hash": "da72c377ada32448d3f8f75227ead7dedf0851d40f36c59fd4052fa95f370e2e" 19 | } 20 | -------------------------------------------------------------------------------- /tests/data/migrations/20250304172534_add_repository_model.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO 2 | repository (name, tree_state, treeclosed_src) 3 | VALUES 4 | ( 5 | 'rust-lang/bors', 6 | 0, 7 | 'https://buildbot.rust-lang.org/homu/queue/rust' 8 | ), 9 | ('rust-lang/cargo', NULL, NULL), 10 | ( 11 | 'rust-lang/rust', 12 | 1, 13 | 'https://github.com/rust-lang/rust/pull/109831#issuecomment-2045783212' 14 | ), 15 | ( 16 | 'rust-lang/clippy', 17 | 2, 18 | 'https://github.com/rust-lang/clippy/pull/10521#issuecomment-2045981453' 19 | ); 20 | -------------------------------------------------------------------------------- /.sqlx/query-8b1b5771a0ae3683308438798c4885b1e593b38f6b16617bc9d52dda33115076.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nUPDATE pull_request\nSET approved_by = $1,\n approved_sha = $2,\n priority = COALESCE($3, priority),\n rollup = COALESCE($4, rollup)\nWHERE id = $5\n", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Text", 9 | "Text", 10 | "Int4", 11 | "Text", 12 | "Int4" 13 | ] 14 | }, 15 | "nullable": [] 16 | }, 17 | "hash": "8b1b5771a0ae3683308438798c4885b1e593b38f6b16617bc9d52dda33115076" 18 | } 19 | -------------------------------------------------------------------------------- /src/github/error.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Error as AnyhowError; 2 | use axum::http::StatusCode; 3 | use axum::response::{IntoResponse, Response}; 4 | 5 | pub struct AppError(pub AnyhowError); 6 | 7 | impl IntoResponse for AppError { 8 | fn into_response(self) -> Response { 9 | let msg = format!("Something went wrong: {}", self.0); 10 | tracing::error!("{msg}"); 11 | (StatusCode::INTERNAL_SERVER_ERROR, msg).into_response() 12 | } 13 | } 14 | 15 | impl From for AppError 16 | where 17 | E: Into, 18 | { 19 | fn from(err: E) -> Self { 20 | Self(err.into()) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.sqlx/query-6a911b59abd89bdb10e283902ea3a08c483e5a6238344c7ac969a0602674e888.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\n INSERT INTO repository (name, tree_state, treeclosed_src)\n VALUES ($1, $2, $3)\n ON CONFLICT (name)\n DO UPDATE SET tree_state = EXCLUDED.tree_state, treeclosed_src = EXCLUDED.treeclosed_src\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Text", 9 | "Int4", 10 | "Text" 11 | ] 12 | }, 13 | "nullable": [] 14 | }, 15 | "hash": "6a911b59abd89bdb10e283902ea3a08c483e5a6238344c7ac969a0602674e888" 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/text.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | /// Pluralizes a piece of text. 4 | pub fn pluralize(base: &str, count: usize) -> Cow<'_, str> { 5 | if count == 1 { 6 | base.into() 7 | } else { 8 | format!("{base}s").into() 9 | } 10 | } 11 | 12 | #[cfg(test)] 13 | mod tests { 14 | use super::*; 15 | 16 | #[test] 17 | fn pluralize_zero() { 18 | assert_eq!(pluralize("foo", 0), "foos"); 19 | } 20 | 21 | #[test] 22 | fn pluralize_one() { 23 | assert_eq!(pluralize("foo", 1), "foo"); 24 | } 25 | 26 | #[test] 27 | fn pluralize_two() { 28 | assert_eq!(pluralize("foo", 2), "foos"); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.sqlx/query-90c5f33613d8562ad5e1be99c6574d268ada15d677f4f8b15435d9605aad04f8.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nINSERT INTO build (repository, branch, kind, commit_sha, parent, status)\nVALUES ($1, $2, $3, $4, $5, $6)\nRETURNING id\n", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "id", 9 | "type_info": "Int4" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Text", 15 | "Text", 16 | "Text", 17 | "Text", 18 | "Text", 19 | "Text" 20 | ] 21 | }, 22 | "nullable": [ 23 | false 24 | ] 25 | }, 26 | "hash": "90c5f33613d8562ad5e1be99c6574d268ada15d677f4f8b15435d9605aad04f8" 27 | } 28 | -------------------------------------------------------------------------------- /src/github/labels.rs: -------------------------------------------------------------------------------- 1 | /// An event that may trigger some modifications of labels on a PR. 2 | #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] 3 | pub enum LabelTrigger { 4 | /// A PR was approved with r+. 5 | Approved, 6 | /// A PR was unapproved, either with r- or it was automatically unapproved for some reason. 7 | Unapproved, 8 | /// A try build has failed. 9 | TryBuildFailed, 10 | /// An auto build triggered from the merge queue has succeeded and the PR was merged. 11 | AutoBuildSucceeded, 12 | /// An auto build triggered from the merge queue has failed. 13 | AutoBuildFailed, 14 | } 15 | 16 | #[derive(Debug, Eq, PartialEq)] 17 | pub enum LabelModification { 18 | Add(String), 19 | Remove(String), 20 | } 21 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(async_fn_in_trait)] 2 | 3 | //! This is the library of the bors bot. 4 | mod bors; 5 | mod config; 6 | mod database; 7 | mod github; 8 | mod permissions; 9 | pub mod server; 10 | mod templates; 11 | mod utils; 12 | 13 | pub use self::bors::process::{BorsProcess, create_bors_process}; 14 | pub use bors::{ 15 | BorsContext, CommandParser, RepositoryStore, event::BorsGlobalEvent, event::BorsRepositoryEvent, 16 | }; 17 | pub use database::{PgDbClient, TreeState}; 18 | pub use github::{ 19 | AppError, OAuthClient, OAuthConfig, WebhookSecret, api::create_github_client, 20 | api::load_repositories, 21 | }; 22 | pub use permissions::TeamApiClient; 23 | pub use server::ServerState; 24 | pub use server::create_app; 25 | 26 | #[cfg(test)] 27 | mod tests; 28 | -------------------------------------------------------------------------------- /src/tests/utils/webhook.rs: -------------------------------------------------------------------------------- 1 | use axum::body::Body; 2 | use hmac::{Hmac, Mac}; 3 | use http::Request; 4 | use sha2::Sha256; 5 | 6 | pub const TEST_WEBHOOK_SECRET: &str = "ABCDEF"; 7 | 8 | pub fn create_webhook_request(event: &str, body: &str) -> Request { 9 | let mut mac = Hmac::::new_from_slice(TEST_WEBHOOK_SECRET.as_bytes()).unwrap(); 10 | mac.update(body.as_bytes()); 11 | let signature = hex::encode(mac.finalize().into_bytes()); 12 | 13 | let signature = format!("sha256={signature}"); 14 | 15 | Request::post("/github") 16 | .header("x-github-event", event) 17 | .header("x-hub-signature-256", signature) 18 | .header("Content-Type", "application/json") 19 | .body(Body::from(body.to_string())) 20 | .unwrap() 21 | } 22 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | $schema: "https://docs.renovatebot.com/renovate-schema.json", 3 | extends: [ 4 | "config:recommended", 5 | ":maintainLockFilesMonthly", 6 | "docker:disable", 7 | ], 8 | packageRules: [ 9 | { 10 | matchCategories: ["rust"], 11 | updateTypes: ["patch"], 12 | // Disable patch updates for single dependencies because patches 13 | // are updated periodically with lockfile maintainance. 14 | enabled: false, 15 | }, 16 | ], 17 | // Receive any update that fixes security vulnerabilities. 18 | // We need this because we disabled "patch" updates for Rust. 19 | // Note: You need to enable "Dependabot alerts" in "Code security" GitHub 20 | // Settings to receive security updates. 21 | // See https://docs.renovatebot.com/configuration-options/#vulnerabilityalerts 22 | vulnerabilityAlerts: { 23 | enabled: true, 24 | }, 25 | } 26 | -------------------------------------------------------------------------------- /tests/data/migrations/20240517094752_create_build.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO build (repository, branch, commit_sha, status, parent) 2 | VALUES ('rust-lang/bors', 3 | 'automation/bors/try', 4 | 'a7ec24743ca724dd4b164b3a76d29d0da9573617', 5 | 'pending', 6 | '8f5e9988e7aa74bffcbec51af17f541d8e7d8e3c'), 7 | ('rust-lang/cargo', 8 | 'automation/bors/try-merge', 9 | 'b3f987c12ee248ef21d37b59a40b17e93fac7c8a', 10 | 'success', 11 | 'c53f32bb8a51fa9fd49d7bd83eb4b15ccfd8a372'), 12 | ('rust-lang/rust', 13 | 'automation/bors/try', 14 | '4ee5a1bfc10bc49f30a8f527557ac4a93a2b9d66', 15 | 'failure', 16 | '9d4e0ac0fca0d0c268be3e9d24d98e3906f0e89b'), 17 | ('rust-lang/rust', 18 | 'automation/bors/auto', 19 | '3ef2a1bfc10bc49f30a8f527557ac4a93a2b9d68', 20 | 'success', 21 | '9d4e0ac0fca0d0c268be3e9d24d98e3906f0e89b'); 22 | -------------------------------------------------------------------------------- /src/bors/handlers/ping.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::bors::Comment; 4 | use crate::bors::RepositoryState; 5 | use crate::github::PullRequestNumber; 6 | 7 | pub(super) async fn command_ping( 8 | repo: Arc, 9 | pr_number: PullRequestNumber, 10 | ) -> anyhow::Result<()> { 11 | let mut msg = "Pong 🏓!".to_string(); 12 | if repo.is_paused() { 13 | msg.push_str(" (bors is paused)"); 14 | } 15 | repo.client 16 | .post_comment(pr_number, Comment::new(msg)) 17 | .await?; 18 | Ok(()) 19 | } 20 | 21 | #[cfg(test)] 22 | mod tests { 23 | use crate::tests::{BorsTester, run_test}; 24 | 25 | #[sqlx::test] 26 | async fn ping_command(pool: sqlx::PgPool) { 27 | run_test(pool, async |ctx: &mut BorsTester| { 28 | ctx.post_comment("@bors ping").await?; 29 | assert_eq!(ctx.get_next_comment_text(()).await?, "Pong 🏓!"); 30 | Ok(()) 31 | }) 32 | .await; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:1.92 AS base 2 | 3 | ENV CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse 4 | 5 | RUN cargo install cargo-chef 6 | 7 | FROM base AS planner 8 | 9 | WORKDIR /app 10 | COPY . . 11 | RUN cargo chef prepare --recipe-path recipe.json 12 | 13 | FROM base AS build 14 | 15 | WORKDIR /app 16 | COPY --from=planner /app/recipe.json recipe.json 17 | 18 | RUN cargo chef cook --release --recipe-path recipe.json 19 | 20 | COPY Cargo.toml . 21 | COPY Cargo.lock . 22 | COPY migrations migrations 23 | COPY .sqlx .sqlx 24 | COPY src src 25 | COPY templates templates 26 | 27 | RUN cargo build --release 28 | 29 | FROM ubuntu:24.04 AS runtime 30 | 31 | WORKDIR / 32 | 33 | # curl is needed for healthcheck 34 | RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y ca-certificates curl 35 | 36 | COPY --from=build /app/target/release/bors . 37 | 38 | EXPOSE 80 39 | 40 | HEALTHCHECK --timeout=10s --start-period=10s \ 41 | CMD curl -f http://localhost/health || exit 1 42 | 43 | ENTRYPOINT ["./bors"] 44 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /rust-bors.example.toml: -------------------------------------------------------------------------------- 1 | # Maximum duration of CI workflows before they are considered timed out. 2 | # (Required) 3 | timeout = 3600 4 | 5 | # Whether to enable the merge queue or not. 6 | # When enabled, approved PRs will be automatically merged. 7 | # (Optional, defaults to false) 8 | merge_queue_enabled = true 9 | 10 | # Whether merge conflicts should be reported on PRs. 11 | # (Optional, defaults to false) 12 | report_merge_conflicts = true 13 | 14 | # Labels that will block approval when present on a PR 15 | # (Optional) 16 | labels_blocking_approval = ["final-comment-period", "proposed-final-comment-period"] 17 | 18 | # Labels that should be set on a PR after an event happens. 19 | # "+