Merge branch 'forking': repository forking, pull request merge methods and every repository setting for pull requests, commits, pushes and issues
The branch's own commit (c82bd536) says what it holds. Merged with main at aea0bcff (AI providers as a workspace setting): the only conflict was the size of the API's operation list (378, plus main's 7 AI operations and the branch's 17: 402); the OpenAPI document, the CLI's operation table and command reference regenerated unchanged. Deploy migrations first: repos 0019 and 0020, work 0041 to 0043, actions 0023; then identity, repos, work, actions, security, events, the API and the site together. Checked on the merged tree: cargo test --workspace (33 suites, all passing), wasm32 checks of repos, work, actions, api, identity, security, events, search and integrations, the site's tsc -b --force, tests (935) and production build, the contracts' typecheck, the deploy tool's tests (92) and the docs build (539 pages).
| 132 | 132 | Op::ArchiveRepo, | |
| 133 | 133 | Op::UnarchiveRepo, | |
| 134 | 134 | Op::TransferRepo, | |
| 135 | + | Op::ForkRepo, | |
| 136 | + | Op::ListForks, | |
| 137 | + | Op::SyncFork, | |
| 138 | + | Op::DetachFork, | |
| 135 | 139 | Op::DeleteRepo, | |
| 136 | 140 | Op::ListDeletedRepos, | |
| 137 | 141 | Op::RestoreRepo, | |
| ⋯ | |||
| 144 | 148 | ], | |
| 145 | 149 | ), | |
| 146 | 150 | ( | |
| 151 | + | "Commit comments", | |
| 152 | + | "Comments on a commit, as a whole or on one line of a file it changed. Anyone who can read a repository reads them, and may comment unless the repository turned commit comments off.", | |
| 153 | + | &[ | |
| 154 | + | Op::ListRepoCommitComments, | |
| 155 | + | Op::ListCommitComments, | |
| 156 | + | Op::CreateCommitComment, | |
| 157 | + | Op::UpdateCommitComment, | |
| 158 | + | Op::DeleteCommitComment, | |
| 159 | + | ], | |
| 160 | + | ), | |
| 161 | + | ( | |
| 147 | 162 | "Repository insights", | |
| 148 | 163 | "What a repository's default branch says about it, read in the background and kept by commit: the languages it is written in, who made it, and its license.", | |
| 149 | 164 | &[Op::About(AboutOp::GetLanguages), Op::About(AboutOp::ListContributors), Op::About(AboutOp::GetLicense)], | |
| ⋯ | |||
| 372 | 387 | ), | |
| 373 | 388 | ( | |
| 374 | 389 | "Pull requests", | |
| 375 | − | "A proposed change in its own fork or on a branch. Several can be made for one issue; the one merged resolves it.", | |
| 390 | + | "A proposed change in its own fork or on a branch. Several can be made for one issue; the one merged resolves it. It merges by a merge commit, a squash, a rebase or a fast-forward, now or with auto-merge once everything it needs is met.", | |
| 376 | 391 | &[ | |
| 377 | 392 | Op::ListPullRequests, | |
| 378 | 393 | Op::CreatePullRequest, | |
| ⋯ | |||
| 384 | 399 | Op::RequestReviewers, | |
| 385 | 400 | Op::RemoveRequestedReviewers, | |
| 386 | 401 | Op::ReviewPullRequest, | |
| 402 | + | Op::GetPullRequestMergeOptions, | |
| 387 | 403 | Op::MergePullRequest, | |
| 404 | + | Op::UpdatePullRequestBranch, | |
| 405 | + | Op::EnablePullRequestAutoMerge, | |
| 406 | + | Op::DisablePullRequestAutoMerge, | |
| 407 | + | Op::DeletePullRequestHeadBranch, | |
| 408 | + | Op::RestorePullRequestHeadBranch, | |
| 388 | 409 | Op::ClosePullRequest, | |
| 389 | 410 | Op::ReopenPullRequest, | |
| 390 | 411 | Op::GetMergeQueue, | |
| ⋯ | |||
| 519 | 540 | Op::Protection(ProtectionOp::SetForkPrApproval), | |
| 520 | 541 | Op::Protection(ProtectionOp::GetActionsAccess), | |
| 521 | 542 | Op::Protection(ProtectionOp::SetActionsAccess), | |
| 543 | + | Op::Protection(ProtectionOp::GetActionsPermissions), | |
| 544 | + | Op::Protection(ProtectionOp::SetActionsPermissions), | |
| 522 | 545 | Op::Protection(ProtectionOp::CreateRepositoryDispatch), | |
| 523 | 546 | Op::Protection(ProtectionOp::GetWorkspaceWorkflowPermissions), | |
| 524 | 547 | Op::Protection(ProtectionOp::SetWorkspaceWorkflowPermissions), | |
| ⋯ | |||
| 656 | 679 | Op::DeclineInvitation => "Decline a workspace invitation", | |
| 657 | 680 | Op::RevokeWorkspaceInvite => "Revoke a workspace's invite", | |
| 658 | 681 | Op::TransferRepo => "Transfer a repository", | |
| 682 | + | Op::ForkRepo => "Fork a repository", | |
| 683 | + | Op::ListForks => "List forks", | |
| 684 | + | Op::SyncFork => "Sync a fork", | |
| 685 | + | Op::DetachFork => "Detach a fork", | |
| 659 | 686 | Op::RenameRepo => "Rename a repository", | |
| 660 | 687 | Op::RenameBranch => "Rename a branch", | |
| 661 | 688 | Op::CreateBranch => "Create a branch", | |
| ⋯ | |||
| 715 | 742 | Op::ConvertPullRequestToDraft => "Convert a pull request to a draft", | |
| 716 | 743 | Op::GetPullRequestChanges => "Get a pull request's changes", | |
| 717 | 744 | Op::MergePullRequest => "Merge a pull request", | |
| 745 | + | Op::GetPullRequestMergeOptions => "Get a pull request's merge options", | |
| 746 | + | Op::UpdatePullRequestBranch => "Update a pull request's branch", | |
| 747 | + | Op::EnablePullRequestAutoMerge => "Enable auto-merge", | |
| 748 | + | Op::DisablePullRequestAutoMerge => "Disable auto-merge", | |
| 749 | + | Op::DeletePullRequestHeadBranch => "Delete a pull request's branch", | |
| 750 | + | Op::RestorePullRequestHeadBranch => "Restore a pull request's branch", | |
| 751 | + | Op::ListRepoCommitComments => "List a repository's commit comments", | |
| 752 | + | Op::ListCommitComments => "List a commit's comments", | |
| 753 | + | Op::CreateCommitComment => "Comment on a commit", | |
| 754 | + | Op::UpdateCommitComment => "Edit a commit comment", | |
| 755 | + | Op::DeleteCommitComment => "Delete a commit comment", | |
| 718 | 756 | Op::ListEvents => "List repository events", | |
| 719 | 757 | Op::ListIntegrations => "List integrations", | |
| 720 | 758 | Op::ConnectIntegration => "Connect an integration", | |
| ⋯ | |||
| 845 | 883 | /// free workspace's private storage has no room for it. | |
| 846 | 884 | fn may_need_payment(op: Op) -> Option<&'static str> { | |
| 847 | 885 | match op { | |
| 848 | − | Op::UpdateRepo | Op::SetRepoVisibility | Op::TransferRepo => Some( | |
| 886 | + | Op::UpdateRepo | Op::SetRepoVisibility | Op::TransferRepo | Op::ForkRepo => Some( | |
| 849 | 887 | "A free workspace's private storage has no room for this private repository.", | |
| 850 | 888 | ), | |
| 851 | 889 | _ => None, | |
| 13 | 13 | use g1t_contracts::identity::AgentScope; | |
| 14 | 14 | use g1t_contracts::events::Event; | |
| 15 | 15 | use g1t_contracts::identity::{CreateWorkspaceArgs, UpdateWorkspaceArgs, Workspace}; | |
| 16 | − | use g1t_contracts::repos::{CreateArgs, GetArgs, ListArgs as ListReposArgs, Repo, RepoPath}; | |
| 16 | + | use g1t_contracts::repos::{ | |
| 17 | + | CreateArgs, DetachForkArgs, ForkRepoArgs, ForksArgs, GetArgs, ListArgs as ListReposArgs, Repo, RepoPath, SyncForkArgs, | |
| 18 | + | }; | |
| 17 | 19 | use g1t_contracts::teams::{ | |
| 18 | 20 | CreateTeamArgs, DeleteTeamArgs, ListTeamsArgs, RemoveTeamMemberArgs, RemoveTeamRepoArgs, ReviewAlgorithm, | |
| 19 | 21 | ReviewAssignment, SetTeamCreationArgs, SetTeamMemberArgs, SetTeamRepoArgs, Team, TeamArgs, TeamCreation, TeamRole, | |
| ⋯ | |||
| 42 | 44 | use crate::rules::RulesOp; | |
| 43 | 45 | use crate::security::SecurityOp; | |
| 44 | 46 | use g1t_contracts::inbox::{Reason, Severity, WATCH_EVENTS, WatchLevel}; | |
| 47 | + | use g1t_contracts::repos::UpdateMethod; | |
| 48 | + | use g1t_contracts::rules::MergeMethod; | |
| 45 | 49 | use g1t_contracts::work::*; | |
| 46 | 50 | use g1t_contracts::{FailureCode, Outcome, Viewer}; | |
| 47 | 51 | use serde::Serialize; | |
| ⋯ | |||
| 142 | 146 | CreateRepo, | |
| 143 | 147 | UpdateRepo, | |
| 144 | 148 | TransferRepo, | |
| 149 | + | ForkRepo, | |
| 150 | + | ListForks, | |
| 151 | + | SyncFork, | |
| 152 | + | DetachFork, | |
| 145 | 153 | RenameRepo, | |
| 146 | 154 | RenameBranch, | |
| 147 | 155 | CreateBranch, | |
| ⋯ | |||
| 197 | 205 | ReopenPullRequest, | |
| 198 | 206 | GetPullRequestChanges, | |
| 199 | 207 | MergePullRequest, | |
| 208 | + | GetPullRequestMergeOptions, | |
| 209 | + | UpdatePullRequestBranch, | |
| 210 | + | EnablePullRequestAutoMerge, | |
| 211 | + | DisablePullRequestAutoMerge, | |
| 212 | + | DeletePullRequestHeadBranch, | |
| 213 | + | RestorePullRequestHeadBranch, | |
| 214 | + | ListRepoCommitComments, | |
| 215 | + | ListCommitComments, | |
| 216 | + | CreateCommitComment, | |
| 217 | + | UpdateCommitComment, | |
| 218 | + | DeleteCommitComment, | |
| 200 | 219 | ListEvents, | |
| 201 | 220 | ListIntegrations, | |
| 202 | 221 | ConnectIntegration, | |
| ⋯ | |||
| 482 | 501 | }) | |
| 483 | 502 | } | |
| 484 | 503 | ||
| 504 | + | fn commit_sha_schema() -> Value { | |
| 505 | + | json!({ "type": "string", "description": "The commit's hash, as list_commits gives it." }) | |
| 506 | + | } | |
| 507 | + | ||
| 508 | + | fn commit_comment_id_schema() -> Value { | |
| 509 | + | json!({ | |
| 510 | + | "type": "string", | |
| 511 | + | "description": "The commit comment's id, as list_commit_comments and list_repo_commit_comments give it.", | |
| 512 | + | }) | |
| 513 | + | } | |
| 514 | + | ||
| 485 | 515 | fn workspace_schema() -> Value { | |
| 486 | 516 | json!({ "type": "string", "description": "The workspace's slug, e.g. \"flagon-io\"." }) | |
| 487 | 517 | } | |
| ⋯ | |||
| 725 | 755 | properties | |
| 726 | 756 | } | |
| 727 | 757 | ||
| 758 | + | /// How a pull request merges. | |
| 759 | + | fn merge_method_schema(description: &str) -> Value { | |
| 760 | + | json!({ | |
| 761 | + | "type": "string", | |
| 762 | + | "enum": MergeMethod::ALL.map(MergeMethod::as_str), | |
| 763 | + | "description": description, | |
| 764 | + | }) | |
| 765 | + | } | |
| 766 | + | ||
| 767 | + | /// A merge method given by name, if one was given: `Err` names the ones | |
| 768 | + | /// there are. | |
| 769 | + | fn merge_method(input: &Value, key: &str) -> std::result::Result<Option<MergeMethod>, String> { | |
| 770 | + | match input[key].as_str().map(str::trim).filter(|given| !given.is_empty()) { | |
| 771 | + | None => Ok(None), | |
| 772 | + | Some(given) => MergeMethod::ALL | |
| 773 | + | .into_iter() | |
| 774 | + | .find(|method| method.as_str().eq_ignore_ascii_case(given)) | |
| 775 | + | .map(Some) | |
| 776 | + | .ok_or_else(|| format!("{key} must be merge, squash, rebase or fast_forward, not {given}.")), | |
| 777 | + | } | |
| 778 | + | } | |
| 779 | + | ||
| 780 | + | /// What a merge or squash commit says by default. | |
| 781 | + | const COMMIT_MESSAGE_DEFAULTS: [(&str, CommitMessageDefault); 5] = [ | |
| 782 | + | ("default", CommitMessageDefault::Default), | |
| 783 | + | ("pull_title", CommitMessageDefault::PullTitle), | |
| 784 | + | ("pull_title_and_description", CommitMessageDefault::PullTitleAndDescription), | |
| 785 | + | ("pull_title_and_commits", CommitMessageDefault::PullTitleAndCommits), | |
| 786 | + | ("custom", CommitMessageDefault::Custom), | |
| 787 | + | ]; | |
| 788 | + | ||
| 789 | + | fn commit_message_default(input: &Value, key: &str) -> std::result::Result<Option<CommitMessageDefault>, String> { | |
| 790 | + | match input[key].as_str().map(str::trim).filter(|given| !given.is_empty()) { | |
| 791 | + | None => Ok(None), | |
| 792 | + | Some(given) => COMMIT_MESSAGE_DEFAULTS | |
| 793 | + | .iter() | |
| 794 | + | .find(|(name, _)| name.eq_ignore_ascii_case(given)) | |
| 795 | + | .map(|(_, value)| Some(*value)) | |
| 796 | + | .ok_or_else(|| { | |
| 797 | + | format!("{key} must be default, pull_title, pull_title_and_description, pull_title_and_commits or custom, not {given}.") | |
| 798 | + | }), | |
| 799 | + | } | |
| 800 | + | } | |
| 801 | + | ||
| 728 | 802 | fn alert_id_schema() -> Value { | |
| 729 | 803 | json!({ | |
| 730 | 804 | "type": "string", | |
| ⋯ | |||
| 733 | 807 | } | |
| 734 | 808 | ||
| 735 | 809 | impl Op { | |
| 736 | − | pub const ALL: [Op; 385] = [ | |
| 810 | + | pub const ALL: [Op; 402] = [ | |
| 737 | 811 | Op::Whoami, | |
| 738 | 812 | Op::GetWorkspace, | |
| 739 | 813 | Op::CreateWorkspace, | |
| ⋯ | |||
| 764 | 838 | Op::CreateRepo, | |
| 765 | 839 | Op::UpdateRepo, | |
| 766 | 840 | Op::TransferRepo, | |
| 841 | + | Op::ForkRepo, | |
| 842 | + | Op::ListForks, | |
| 843 | + | Op::SyncFork, | |
| 844 | + | Op::DetachFork, | |
| 767 | 845 | Op::RenameRepo, | |
| 768 | 846 | Op::RenameBranch, | |
| 769 | 847 | Op::CreateBranch, | |
| ⋯ | |||
| 819 | 897 | Op::ReopenPullRequest, | |
| 820 | 898 | Op::GetPullRequestChanges, | |
| 821 | 899 | Op::MergePullRequest, | |
| 900 | + | Op::GetPullRequestMergeOptions, | |
| 901 | + | Op::UpdatePullRequestBranch, | |
| 902 | + | Op::EnablePullRequestAutoMerge, | |
| 903 | + | Op::DisablePullRequestAutoMerge, | |
| 904 | + | Op::DeletePullRequestHeadBranch, | |
| 905 | + | Op::RestorePullRequestHeadBranch, | |
| 906 | + | Op::ListRepoCommitComments, | |
| 907 | + | Op::ListCommitComments, | |
| 908 | + | Op::CreateCommitComment, | |
| 909 | + | Op::UpdateCommitComment, | |
| 910 | + | Op::DeleteCommitComment, | |
| 822 | 911 | Op::ListEvents, | |
| 823 | 912 | Op::ListIntegrations, | |
| 824 | 913 | Op::ConnectIntegration, | |
| ⋯ | |||
| 1060 | 1149 | Op::Protection(ProtectionOp::SetForkPrApproval), | |
| 1061 | 1150 | Op::Protection(ProtectionOp::GetActionsAccess), | |
| 1062 | 1151 | Op::Protection(ProtectionOp::SetActionsAccess), | |
| 1152 | + | Op::Protection(ProtectionOp::GetActionsPermissions), | |
| 1153 | + | Op::Protection(ProtectionOp::SetActionsPermissions), | |
| 1063 | 1154 | Op::Protection(ProtectionOp::CreateRepositoryDispatch), | |
| 1064 | 1155 | Op::Protection(ProtectionOp::GetWorkspaceWorkflowPermissions), | |
| 1065 | 1156 | Op::Protection(ProtectionOp::SetWorkspaceWorkflowPermissions), | |
| ⋯ | |||
| 1158 | 1249 | Op::CreateRepo => "create_repo", | |
| 1159 | 1250 | Op::UpdateRepo => "update_repo", | |
| 1160 | 1251 | Op::TransferRepo => "transfer_repo", | |
| 1252 | + | Op::ForkRepo => "fork_repo", | |
| 1253 | + | Op::ListForks => "list_forks", | |
| 1254 | + | Op::SyncFork => "sync_fork", | |
| 1255 | + | Op::DetachFork => "detach_fork", | |
| 1161 | 1256 | Op::RenameRepo => "rename_repo", | |
| 1162 | 1257 | Op::RenameBranch => "rename_branch", | |
| 1163 | 1258 | Op::CreateBranch => "create_branch", | |
| ⋯ | |||
| 1213 | 1308 | Op::ConvertPullRequestToDraft => "convert_pull_request_to_draft", | |
| 1214 | 1309 | Op::GetPullRequestChanges => "get_pull_request_changes", | |
| 1215 | 1310 | Op::MergePullRequest => "merge_pull_request", | |
| 1311 | + | Op::GetPullRequestMergeOptions => "get_pull_request_merge_options", | |
| 1312 | + | Op::UpdatePullRequestBranch => "update_pull_request_branch", | |
| 1313 | + | Op::EnablePullRequestAutoMerge => "enable_pull_request_auto_merge", | |
| 1314 | + | Op::DisablePullRequestAutoMerge => "disable_pull_request_auto_merge", | |
| 1315 | + | Op::DeletePullRequestHeadBranch => "delete_pull_request_head_branch", | |
| 1316 | + | Op::RestorePullRequestHeadBranch => "restore_pull_request_head_branch", | |
| 1317 | + | Op::ListRepoCommitComments => "list_repo_commit_comments", | |
| 1318 | + | Op::ListCommitComments => "list_commit_comments", | |
| 1319 | + | Op::CreateCommitComment => "create_commit_comment", | |
| 1320 | + | Op::UpdateCommitComment => "update_commit_comment", | |
| 1321 | + | Op::DeleteCommitComment => "delete_commit_comment", | |
| 1216 | 1322 | Op::ListEvents => "list_events", | |
| 1217 | 1323 | Op::ListIntegrations => "list_integrations", | |
| 1218 | 1324 | Op::ConnectIntegration => "connect_integration", | |
| ⋯ | |||
| 1416 | 1522 | Op::ListRepos => "Repositories you can see, optionally filtered by a search query.", | |
| 1417 | 1523 | Op::GetRepo => "One repository's details.", | |
| 1418 | 1524 | Op::UpdateRepo => { | |
| 1419 | − | "Change a repository's description, website, topics and default branch, whether its default branch is protected, and whether it is private. Only the fields given are changed. Its description, website and topics need the Maintain role or higher; protecting its default branch, making it public or private and changing its default branch need the Admin role (and making it public or private, the workspace's member privileges to allow it, unless you are an owner), and a free workspace takes a private repository only while its private storage has room. A protected branch refuses pushes and changes only by merging a pull request. A new default branch must already exist; open pull requests then merge into it." | |
| 1525 | + | "Change a repository's description, website, topics and default branch, whether its default branch is protected, whether it is private, whether a private repository may be forked, and the most branches and tags one push may change (max_ref_updates). Only the fields given are changed. Its description, website and topics need the Maintain role or higher; protecting its default branch, making it public or private, allowing forking, max_ref_updates and changing its default branch need the Admin role (and making it public or private, the workspace's member privileges to allow it, unless you are an owner), and a free workspace takes a private repository only while its private storage has room. A protected branch refuses pushes and changes only by merging a pull request. A new default branch must already exist; open pull requests then merge into it. A fork cannot be made public or private: it is as visible as its parent until it is detached (detach_fork)." | |
| 1420 | 1526 | } | |
| 1421 | 1527 | Op::RenameRepo => { | |
| 1422 | 1528 | "Give a repository a new name in its workspace. Needs the Admin role. Everything stays with it: git data, issues, pull requests, workflow runs, deployments, secrets and webhooks. Its old address keeps working: web pages, git remotes and API calls redirect to the new one until a repository is made at the old address. The new name must be free in the workspace, including names held by recently deleted repositories." | |
| ⋯ | |||
| 1454 | 1560 | Op::TransferRepo => { | |
| 1455 | 1561 | "Move a repository to another workspace, keeping its name. You must own both workspaces, and the destination must not already have a repository of that name; a free destination takes a private repository only if its private storage has room. Everything moves with it: git data, issues, pull requests, comments, labels, workflow runs, deployments, its project, and its own secrets, variables and webhooks. Its old address keeps working: web pages, git remotes and API calls redirect to the new one until a repository is made at the old address. Usage from now on is charged to the new workspace." | |
| 1456 | 1562 | } | |
| 1563 | + | Op::ForkRepo => { | |
| 1564 | + | "Fork a repository: copy it into a workspace you can create repositories in, as a new repository that remembers the one it came from (its parent). Anyone who can read a public repository may fork it; a private one only when its admins allow forking (allow_forking on update_repo). A fork is exactly as visible as its parent: a public repository's fork is public, a private one's private. Only its code and description come with it: the default branch, or every branch and tag with default_branch_only false. Its issues, pull requests, settings, rulesets, secrets, variables, webhooks, environments and deployments stay behind; issues are off in the fork, and Actions is off until an admin of the fork turns it on. Its full size counts toward the destination workspace's storage, and a free workspace takes a private fork only while its private storage has room. workspace is the destination's slug: your personal workspace when left out. name is the parent's when left out. Returns the fork, with parent naming the repository it came from." | |
| 1565 | + | } | |
| 1566 | + | Op::ListForks => { | |
| 1567 | + | "A repository's forks that you can see, newest first, each with parent naming the repository. limit is at most 100, 30 when left out. Forks of its forks are listed on them." | |
| 1568 | + | } | |
| 1569 | + | Op::SyncFork => { | |
| 1570 | + | "Bring a fork's branch up to date with its parent: branch (the fork's default branch when left out) takes in the parent's branch of the same name, or the parent's default branch for the fork's default branch. Needs the Write role on the fork and read access to its parent. A branch that is only behind moves forward (fast_forwarded); one with commits of its own gets a merge commit when the two sides' changes do not clash, edits to the same file merging line by line (merged); when both changed the same lines (or one deleted a file the other changed, or both changed a binary file) nothing is changed (conflicted, with the files in conflicts, at most 20): merge the parent's branch in a clone and push, or open a pull request. Returns status, branch, head (the branch's commit afterwards), behind and ahead (the commits each side had that the other lacked)." | |
| 1571 | + | } | |
| 1572 | + | Op::DetachFork => { | |
| 1573 | + | "Detach a fork from its parent: it becomes an ordinary repository with no parent, and can then be made public or private. Needs the Admin role on the fork. Its code, settings, and the pull requests it already opened into the parent stay as they are; it can no longer be synced or open new pull requests into the parent. It cannot be attached again." | |
| 1574 | + | } | |
| 1457 | 1575 | Op::GetRepoSettings => { | |
| 1458 | − | "How a repository handles pull requests: whether they merge by themselves, and its default branch's protection as the rules of its rulesets stack there: the checks that must pass (required_checks), the approvals a merge needs, whether its code owners must approve (`require_code_owner_review`), whether required checks can be bypassed, whether a pull request must be up to date, and the merge queue. The same rules hold for every pull request, whoever opened it. list_repo_rulesets and get_branch_rules show every rule." | |
| 1576 | + | "How a repository handles pull requests, commits and issues: the merge methods it allows and the one offered first, what merge and squash commits say by default (merge_commit_message, squash_commit_message, and the templates for custom), whether pull requests may turn on auto-merge, whether their branches are deleted once merged, whether to suggest updating branches, whether web commits are signed off, whether commit comments are allowed, whether merged pull requests close the issues they name, and whether issues can be opened (issues_enabled); also whether they merge by themselves, and its default branch's protection as the rules of its rulesets stack there: the checks that must pass (required_checks), the approvals a merge needs, whether its code owners must approve (`require_code_owner_review`), whether required checks can be bypassed, whether a pull request must be up to date, and the merge queue. The same rules hold for every pull request, whoever opened it. list_repo_rulesets and get_branch_rules show every rule." | |
| 1459 | 1577 | } | |
| 1460 | 1578 | Op::UpdateRepoSettings => { | |
| 1461 | − | "Change how a repository handles pull requests. Only the fields given are changed; required_checks replaces the whole list. The branch protection fields (required_checks, require_up_to_date, required_approvals, count_agent_approvals, allow_ignoring_checks, merge_queue, require_code_owner_review) are written to the repository's \"Default branch protection\" ruleset, made when it has none; rules only rulesets have stay as they are. A required check is named as list_check_names gives it: a workflow's name, such as CI, or another status's context, such as g1t / deploy. Needs the Maintain role or higher, and the Admin role to change a branch protection field." | |
| 1579 | + | "Change how a repository handles pull requests, commits and issues. Only the fields given are changed; required_checks replaces the whole list. At least one merge method must stay allowed, and default_merge_method must be one of them. A custom commit message (merge_commit_message or squash_commit_message custom) uses its template, whose {title}, {number}, {body}, {branch}, {head_repo}, {base}, {author}, {commits}, {issues} and {co_authors} are filled in; pull_title_and_commits is for squash commits only. The branch protection fields (required_checks, require_up_to_date, required_approvals, count_agent_approvals, allow_ignoring_checks, merge_queue, require_code_owner_review) are written to the repository's \"Default branch protection\" ruleset, made when it has none; rules only rulesets have stay as they are. A required check is named as list_check_names gives it: a workflow's name, such as CI, or another status's context, such as g1t / deploy. Needs the Maintain role or higher, and the Admin role to change a branch protection field." | |
| 1462 | 1580 | } | |
| 1463 | 1581 | Op::ListCheckNames => { | |
| 1464 | 1582 | "The check names reported on a repository's commits in the last 30 days, most recent first, with the events each was reported for: the names update_repo_settings takes in required_checks. A workflow's runs report a check named after the workflow; a check required on the default branch must be reported on a pull request's head (pull_request events) and, with the merge queue on, on its queued state (merge_group events)." | |
| ⋯ | |||
| 1575 | 1693 | "What a pull request changes: the files it touches and their line-by-line diff against the commit it started from. Use it to review a pull request or to compare several made for the same issue. A large one lists every file (up to 300; truncated says when there were more) but has the lines of only the first, about 20,000 lines read: the files after that come with omitted true and no lines or counts. Ask again with paths (up to 50 at a time) to have those files' lines." | |
| 1576 | 1694 | } | |
| 1577 | 1695 | Op::MergePullRequest => { | |
| 1578 | − | "Land a pull request on its base, the branch it merges into (the default branch unless it names another). Merging needs the Write role or higher, and only once it is marked ready and it meets every rule that holds for its base (see rules and required_checks on get_pull_request: approvals, checks, deployments, merge windows and the rest, from the repository's and its workspace's rulesets); the refusal names the first rule not met. With ignore_checks, someone who may merge can bypass required checks where the rule allows it; with bypass_rules, someone a ruleset lists as a bypass actor merges past its rules, and it is recorded. Merging into the default branch resolves the issue it was made for: the issue closes recording this pull request, and the other pull requests still in progress for that issue close as superseded; merging into another branch leaves the issue open. Where the repository has a merge queue, a pull request into the default branch joins the queue instead of landing at once. If its base has moved since the pull request was opened, it is brought up to date first and lands when that is done; a repository that requires pull requests into its default branch to be up to date refuses instead, so pull the base into its fork or branch, push, and merge again. Check status in the result to see whether it has landed." | |
| 1696 | + | "Land a pull request on its base, the branch it merges into (the default branch unless it names another). merge_method says how: merge (a merge commit joins its branch to the base, its commits included), squash (one new commit holds everything it changed), rebase (each of its commits is made again on top of the base, in order) or fast_forward (the base moves to its head, exactly the commit that was checked; only when its branch contains the base's head). Left out, the repository's default_merge_method. A method the repository does not allow (allow_merge_commit, allow_squash_merge, allow_rebase_merge, allow_fast_forward on update_repo_settings), or that a rule for the base does not (allowed_merge_methods, and required linear history, which refuses merge), is refused. commit_title and commit_message are a merge or squash commit's message: its first line and the rest; left out, the repository's merge_commit_message or squash_commit_message decides. With sha, the merge is refused (409) if the pull request's head is no longer that commit. delete_branch deletes its branch once it has merged (left out, the repository's delete_branch_on_merge); restore_pull_request_head_branch brings it back. Merging needs the Write role or higher, and only once it is marked ready and it meets every rule that holds for its base (see rules and required_checks on get_pull_request: approvals, checks, deployments, merge windows and the rest, from the repository's and its workspace's rulesets); the refusal names the first rule not met. With ignore_checks, someone who may merge can bypass required checks where the rule allows it; with bypass_rules, someone a ruleset lists as a bypass actor merges past its rules, and it is recorded. Merging into the default branch closes the issues in its closes: the issue it was made for and those its title and description name after a closing keyword (Fixes #12), where the repository closes issues (auto_close_issues); the other pull requests still in progress for its issue close as superseded. Merging into another branch closes nothing. Where the repository has a merge queue, a pull request into the default branch joins the queue instead of landing at once. If its base has moved since the pull request was opened, it is brought up to date first and lands when that is done; a repository that requires pull requests into its default branch to be up to date refuses instead: update_pull_request_branch, then merge again. Check status in the result to see whether it has landed, and merge_method and merge_commit for how." | |
| 1697 | + | } | |
| 1698 | + | Op::GetPullRequestMergeOptions => { | |
| 1699 | + | "How a pull request may merge now, as the merge box offers it to you: the methods it may merge by (the repository's, as the rules for its base allow them) and the one offered first (default_method); each method's default commit title and message, and why one cannot be used now (unavailable); whether it is behind its base, can fast-forward as it is, should be offered an update (suggest_update) or must be up to date (require_up_to_date); whether it goes through the merge queue; whether you may turn on auto-merge; whether deleting its branch starts ticked and is possible; and whether commits made merging it are signed off." | |
| 1700 | + | } | |
| 1701 | + | Op::UpdatePullRequestBranch => { | |
| 1702 | + | "Bring a pull request's branch up to date with its base: update_method merge (the default) makes a merge commit on its branch that takes in the base's new commits; rebase makes each of its commits again on top of the base's head and moves its branch there, rewriting its history. Pushed as you, so you must be able to push to its branch: the Write role for a branch of the repository, its owner for a working copy, and push access to the fork for a branch of a fork. With expected_head_sha, refused (409) if its head is no longer that commit. Edits to the same file merge line by line; a rebase drops merge commits and commits the base already has, keeps authors, dates and messages, and moves its branch only if its head is still where it was. Returns outcome: updated (with commit, its head now, and previous), up_to_date, or needs_agent when both sides changed the same lines (with reason, detail and the paths): nothing was pushed, so merge or rebase in a clone and push." | |
| 1703 | + | } | |
| 1704 | + | Op::EnablePullRequestAutoMerge => { | |
| 1705 | + | "Turn on auto-merge for a pull request: it merges on its own, as you, once its required checks, reviews and rules allow, with merge_method (the repository's default when left out) and, for merge and squash, commit_title and commit_message. Needs the Write role, since it merges as you, and a repository that allows auto-merge (allow_auto_merge). A method the repository or a rule for its base does not allow is refused. A pull request that may merge already merges at once instead. Auto-merge is turned off when it merges or closes, when someone turns it off, and when someone other than whoever turned it on pushes to it. Publishes pull.auto_merge_enabled. Returns the pull request, with auto_merge saying how and who asked." | |
| 1706 | + | } | |
| 1707 | + | Op::DisablePullRequestAutoMerge => { | |
| 1708 | + | "Turn off auto-merge for a pull request: it waits for someone to merge it. Needs the Write role, or to be its author. Publishes pull.auto_merge_disabled. Returns the pull request, with auto_merge null." | |
| 1709 | + | } | |
| 1710 | + | Op::DeletePullRequestHeadBranch => { | |
| 1711 | + | "Delete a merged or closed pull request's branch: a branch of the repository, or of a fork you can push to. Needs the Write role (push access to the fork, for a fork's branch). Refused while the pull request is open, for the default branch, for a branch an open pull request still uses, and where rules protect it. head_branch_deleted is then true, and restore_pull_request_head_branch makes it again at its head. Publishes pull.head_branch_deleted. Returns the pull request." | |
| 1712 | + | } | |
| 1713 | + | Op::RestorePullRequestHeadBranch => { | |
| 1714 | + | "Make a pull request's deleted branch again, at the pull request's head commit (head_commit). Needs the Write role (push access to the fork, for a fork's branch). Refused when a branch of that name exists again. Publishes pull.head_branch_restored. Returns the pull request." | |
| 1715 | + | } | |
| 1716 | + | Op::ListRepoCommitComments => { | |
| 1717 | + | "A repository's commit comments, newest first, 30 a page (page from 1). Each names its commit; one on a line of a file has path and line. Anyone who can read the repository can read them." | |
| 1718 | + | } | |
| 1719 | + | Op::ListCommitComments => { | |
| 1720 | + | "A commit's comments, oldest first: on the whole commit, or on one line of a file it changed (path and line). sha is the commit's hash. Anyone who can read the repository can read them." | |
| 1721 | + | } | |
| 1722 | + | Op::CreateCommitComment => { | |
| 1723 | + | "Comment on a commit, as a whole or, with path and line, on one line of a file the commit changed (a line of the file as the commit left it). Anyone who can read the repository may, unless it has turned commit comments off (allow_commit_comments on update_repo_settings). The commit's author and anyone mentioned are notified. Publishes commit_comment.created." | |
| 1724 | + | } | |
| 1725 | + | Op::UpdateCommitComment => { | |
| 1726 | + | "Change a commit comment's text, named by comment_id. Its author only. Publishes commit_comment.edited." | |
| 1579 | 1727 | } | |
| 1728 | + | Op::DeleteCommitComment => { | |
| 1729 | + | "Delete a commit comment, named by comment_id. Its author may, and so may anyone with the Maintain role or higher. This cannot be undone. Publishes commit_comment.deleted." | |
| 1730 | + | } | |
| 1580 | 1731 | Op::ListEvents => { | |
| 1581 | 1732 | "The timeline of a repository: pushes, issues, pull requests, comments, sessions, claims and integration results, newest first; page back with before. To follow it, give after (an event id): the events written after that one come oldest first, and the last one's id is the next call's after. With after and wait (seconds, at most 25), a call that finds nothing holds on until a matching event is written or the wait ends, then returns what it has, perhaps nothing. Narrow it with types, numbers (issues and pull requests, matching events about them and comments on them), since and limit (50 by default, at most 200)." | |
| 1582 | 1733 | } | |
| ⋯ | |||
| 2107 | 2258 | }), | |
| 2108 | 2259 | &["repo", "to"], | |
| 2109 | 2260 | ), | |
| 2261 | + | Op::ForkRepo => object( | |
| 2262 | + | json!({ | |
| 2263 | + | "repo": repo_schema(), | |
| 2264 | + | "workspace": { | |
| 2265 | + | "type": "string", | |
| 2266 | + | "description": "The slug of the workspace to fork it into, e.g. \"flagon-io\". Your personal workspace when left out.", | |
| 2267 | + | }, | |
| 2268 | + | "name": { "type": "string", "description": "The fork's name; the repository's own when left out." }, | |
| 2269 | + | "description": { "type": "string", "description": "The fork's description; the repository's own when left out, none when empty." }, | |
| 2270 | + | "default_branch_only": { | |
| 2271 | + | "type": "boolean", | |
| 2272 | + | "description": "Copy only the default branch (the default). false copies every branch and tag.", | |
| 2273 | + | }, | |
| 2274 | + | }), | |
| 2275 | + | &["repo"], | |
| 2276 | + | ), | |
| 2277 | + | Op::ListForks => object( | |
| 2278 | + | json!({ | |
| 2279 | + | "repo": repo_schema(), | |
| 2280 | + | "limit": { "type": "integer", "minimum": 1, "maximum": 100, "description": "At most this many; 30 when left out." }, | |
| 2281 | + | }), | |
| 2282 | + | &["repo"], | |
| 2283 | + | ), | |
| 2284 | + | Op::SyncFork => object( | |
| 2285 | + | json!({ | |
| 2286 | + | "repo": repo_schema(), | |
| 2287 | + | "branch": { "type": "string", "description": "The fork's branch to sync; its default branch when left out." }, | |
| 2288 | + | }), | |
| 2289 | + | &["repo"], | |
| 2290 | + | ), | |
| 2291 | + | Op::DetachFork => repo_only(), | |
| 2110 | 2292 | Op::GetRepo | Op::ListLabels | Op::AddDefaultLabels => repo_only(), | |
| 2111 | 2293 | Op::CreateLabel => object( | |
| 2112 | 2294 | json!({ | |
| ⋯ | |||
| 2194 | 2376 | "type": "string", | |
| 2195 | 2377 | "description": "Make this existing branch the default: the one clones check out and pull requests merge into.", | |
| 2196 | 2378 | }, | |
| 2379 | + | "allow_forking": { | |
| 2380 | + | "type": "boolean", | |
| 2381 | + | "description": "Let people who can read this private repository fork it; their forks stay private. Public repositories can always be forked.", | |
| 2382 | + | }, | |
| 2383 | + | "max_ref_updates": { | |
| 2384 | + | "type": "integer", | |
| 2385 | + | "minimum": 0, | |
| 2386 | + | "maximum": 1000, | |
| 2387 | + | "description": "The most branches and tags one push may change, 1 to 1,000; a push that would change more is declined whole. 0 for no limit.", | |
| 2388 | + | }, | |
| 2197 | 2389 | }), | |
| 2198 | 2390 | &["repo"], | |
| 2199 | 2391 | ), | |
| ⋯ | |||
| 2323 | 2515 | "type": "boolean", | |
| 2324 | 2516 | "description": "Refuse to merge until the code owners of every file a pull request changes, as the CODEOWNERS file of the branch it merges into names them, have approved it, as many as each section asks. Only people's approvals count, and g1t's only where the file names @g1t.", | |
| 2325 | 2517 | }, | |
| 2518 | + | "allow_merge_commit": { | |
| 2519 | + | "type": "boolean", | |
| 2520 | + | "description": "Let pull requests merge with a merge commit (merge). At least one method stays allowed.", | |
| 2521 | + | }, | |
| 2522 | + | "allow_squash_merge": { | |
| 2523 | + | "type": "boolean", | |
| 2524 | + | "description": "Let pull requests merge as one squashed commit (squash).", | |
| 2525 | + | }, | |
| 2526 | + | "allow_rebase_merge": { | |
| 2527 | + | "type": "boolean", | |
| 2528 | + | "description": "Let pull requests merge by making each of their commits again on the base (rebase).", | |
| 2529 | + | }, | |
| 2530 | + | "allow_fast_forward": { | |
| 2531 | + | "type": "boolean", | |
| 2532 | + | "description": "Let pull requests merge by moving the base to their head, with no new commit (fast_forward). Off unless turned on.", | |
| 2533 | + | }, | |
| 2534 | + | "default_merge_method": merge_method_schema("The method offered first, and used when a merge names none. Must be allowed."), | |
| 2535 | + | "merge_commit_message": { | |
| 2536 | + | "type": "string", | |
| 2537 | + | "enum": ["default", "pull_title", "pull_title_and_description", "custom"], | |
| 2538 | + | "description": "What a merge commit says unless its merger writes it: default (\"Merge pull request #12 from owner/branch\" and its title), pull_title (its title, with its number), pull_title_and_description (and its description below), or custom (merge_commit_template).", | |
| 2539 | + | }, | |
| 2540 | + | "squash_commit_message": { | |
| 2541 | + | "type": "string", | |
| 2542 | + | "enum": ["default", "pull_title", "pull_title_and_description", "pull_title_and_commits", "custom"], | |
| 2543 | + | "description": "What a squash commit says unless its merger writes it: default (its title for one commit, else its title and the commits' messages), pull_title, pull_title_and_description, pull_title_and_commits (its title, and each commit's message below), or custom (squash_commit_template).", | |
| 2544 | + | }, | |
| 2545 | + | "merge_commit_template": { | |
| 2546 | + | "type": "string", | |
| 2547 | + | "description": "With merge_commit_message custom: the message, its first line the title. {title}, {number}, {body}, {branch}, {head_repo}, {base}, {author}, {commits}, {issues} and {co_authors} are filled in. An empty string clears it.", | |
| 2548 | + | }, | |
| 2549 | + | "squash_commit_template": { | |
| 2550 | + | "type": "string", | |
| 2551 | + | "description": "With squash_commit_message custom: the message, with the same placeholders as merge_commit_template. An empty string clears it.", | |
| 2552 | + | }, | |
| 2553 | + | "suggest_updating_branches": { | |
| 2554 | + | "type": "boolean", | |
| 2555 | + | "description": "Offer to update a pull request's branch whenever its base has new commits, not only when it must be up to date to merge.", | |
| 2556 | + | }, | |
| 2557 | + | "allow_auto_merge": { | |
| 2558 | + | "type": "boolean", | |
| 2559 | + | "description": "Let people turn on auto-merge for a pull request, so it merges on its own once everything it needs is met.", | |
| 2560 | + | }, | |
| 2561 | + | "delete_branch_on_merge": { | |
| 2562 | + | "type": "boolean", | |
| 2563 | + | "description": "Delete a pull request's branch once it merges. It can be restored from the pull request.", | |
| 2564 | + | }, | |
| 2565 | + | "require_web_commit_sign_off": { | |
| 2566 | + | "type": "boolean", | |
| 2567 | + | "description": "Commits made on g1t (web edits, merge and squash commits) carry a Signed-off-by: trailer for whoever made them.", | |
| 2568 | + | }, | |
| 2569 | + | "allow_commit_comments": { | |
| 2570 | + | "type": "boolean", | |
| 2571 | + | "description": "Let anyone who can read the repository comment on its commits. Existing comments stay readable either way.", | |
| 2572 | + | }, | |
| 2573 | + | "auto_close_issues": { | |
| 2574 | + | "type": "boolean", | |
| 2575 | + | "description": "Close the issues a pull request names (its issue, and Fixes #12 in its title or description) when it merges into the default branch.", | |
| 2576 | + | }, | |
| 2577 | + | "has_issues": { | |
| 2578 | + | "type": "boolean", | |
| 2579 | + | "description": "Whether issues can be opened in it. null goes back to the default: on for a repository, off for a fork. Existing issues stay readable either way.", | |
| 2580 | + | }, | |
| 2326 | 2581 | }), | |
| 2327 | 2582 | &["repo"], | |
| 2328 | 2583 | ), | |
| ⋯ | |||
| 2515 | 2770 | }, | |
| 2516 | 2771 | "branch": { | |
| 2517 | 2772 | "type": "string", | |
| 2518 | − | "description": "A branch already pushed to the repository that holds the change. Leave out to get a fork.", | |
| 2773 | + | "description": "A branch already pushed to the repository (or to head_repo) that holds the change. Leave out to get a fork.", | |
| 2519 | 2774 | }, | |
| 2775 | + | "head_repo": { | |
| 2776 | + | "type": "string", | |
| 2777 | + | "description": "A fork of the repository, \"owner/name\", whose branch holds the change: to propose a change from your fork. Needs branch.", | |
| 2778 | + | }, | |
| 2520 | 2779 | "body": { | |
| 2521 | 2780 | "type": "string", | |
| 2522 | 2781 | "description": "Markdown: what changed and why. Mainly for pull requests from a branch.", | |
| ⋯ | |||
| 2602 | 2861 | "type": "boolean", | |
| 2603 | 2862 | "description": "Merge although rules are not met, where a ruleset lists you as one who may bypass it. Recorded as a bypass in its evaluations.", | |
| 2604 | 2863 | }, | |
| 2864 | + | "merge_method": merge_method_schema("How to merge: merge, squash, rebase or fast_forward. The repository's default_merge_method when left out."), | |
| 2865 | + | "commit_title": { | |
| 2866 | + | "type": "string", | |
| 2867 | + | "description": "For merge and squash: the commit message's first line. The repository's default when left out.", | |
| 2868 | + | }, | |
| 2869 | + | "commit_message": { | |
| 2870 | + | "type": "string", | |
| 2871 | + | "description": "For merge and squash: the rest of the commit message. The repository's default when left out.", | |
| 2872 | + | }, | |
| 2873 | + | "sha": { | |
| 2874 | + | "type": "string", | |
| 2875 | + | "description": "The head commit you reviewed: refused (409) if the pull request's head has moved since.", | |
| 2876 | + | }, | |
| 2877 | + | "delete_branch": { | |
| 2878 | + | "type": "boolean", | |
| 2879 | + | "description": "Delete its branch once merged. The repository's delete_branch_on_merge when left out.", | |
| 2880 | + | }, | |
| 2605 | 2881 | })), | |
| 2606 | 2882 | &["repo", "number"], | |
| 2607 | 2883 | ), | |
| 2884 | + | Op::UpdatePullRequestBranch => object( | |
| 2885 | + | numbered(json!({ | |
| 2886 | + | "expected_head_sha": { | |
| 2887 | + | "type": "string", | |
| 2888 | + | "description": "The head commit you saw: refused (409) if the pull request's head has moved since.", | |
| 2889 | + | }, | |
| 2890 | + | "update_method": { | |
| 2891 | + | "type": "string", | |
| 2892 | + | "enum": ["merge", "rebase"], | |
| 2893 | + | "description": "merge (the default): a merge commit on its branch takes in the base's new commits. rebase: its commits are made again on the base's head, rewriting its branch.", | |
| 2894 | + | }, | |
| 2895 | + | })), | |
| 2896 | + | &["repo", "number"], | |
| 2897 | + | ), | |
| 2898 | + | Op::EnablePullRequestAutoMerge => object( | |
| 2899 | + | numbered(json!({ | |
| 2900 | + | "merge_method": merge_method_schema("How it will merge: merge, squash, rebase or fast_forward. The repository's default_merge_method when left out."), | |
| 2901 | + | "commit_title": { | |
| 2902 | + | "type": "string", | |
| 2903 | + | "description": "For merge and squash: the commit message's first line. The repository's default when left out.", | |
| 2904 | + | }, | |
| 2905 | + | "commit_message": { | |
| 2906 | + | "type": "string", | |
| 2907 | + | "description": "For merge and squash: the rest of the commit message. The repository's default when left out.", | |
| 2908 | + | }, | |
| 2909 | + | })), | |
| 2910 | + | &["repo", "number"], | |
| 2911 | + | ), | |
| 2912 | + | Op::GetPullRequestMergeOptions | |
| 2913 | + | | Op::DisablePullRequestAutoMerge | |
| 2914 | + | | Op::DeletePullRequestHeadBranch | |
| 2915 | + | | Op::RestorePullRequestHeadBranch => { | |
| 2916 | + | just_numbered() | |
| 2917 | + | } | |
| 2918 | + | Op::ListRepoCommitComments => object( | |
| 2919 | + | json!({ | |
| 2920 | + | "repo": repo_schema(), | |
| 2921 | + | "page": { "type": "integer", "minimum": 1, "description": "From 1; 30 comments a page." }, | |
| 2922 | + | }), | |
| 2923 | + | &["repo"], | |
| 2924 | + | ), | |
| 2925 | + | Op::ListCommitComments => object( | |
| 2926 | + | json!({ "repo": repo_schema(), "sha": commit_sha_schema() }), | |
| 2927 | + | &["repo", "sha"], | |
| 2928 | + | ), | |
| 2929 | + | Op::CreateCommitComment => object( | |
| 2930 | + | json!({ | |
| 2931 | + | "repo": repo_schema(), | |
| 2932 | + | "sha": commit_sha_schema(), | |
| 2933 | + | "body": { "type": "string", "description": "Markdown." }, | |
| 2934 | + | "path": { | |
| 2935 | + | "type": "string", | |
| 2936 | + | "description": "A file the commit changed, from the repository's root, to comment on one of its lines.", | |
| 2937 | + | }, | |
| 2938 | + | "line": { | |
| 2939 | + | "type": "integer", | |
| 2940 | + | "minimum": 1, | |
| 2941 | + | "description": "With path: the line, as the commit left the file, counted from 1.", | |
| 2942 | + | }, | |
| 2943 | + | }), | |
| 2944 | + | &["repo", "sha", "body"], | |
| 2945 | + | ), | |
| 2946 | + | Op::UpdateCommitComment => object( | |
| 2947 | + | json!({ | |
| 2948 | + | "repo": repo_schema(), | |
| 2949 | + | "comment_id": commit_comment_id_schema(), | |
| 2950 | + | "body": { "type": "string", "description": "The new text, in Markdown." }, | |
| 2951 | + | }), | |
| 2952 | + | &["repo", "comment_id", "body"], | |
| 2953 | + | ), | |
| 2954 | + | Op::DeleteCommitComment => object( | |
| 2955 | + | json!({ "repo": repo_schema(), "comment_id": commit_comment_id_schema() }), | |
| 2956 | + | &["repo", "comment_id"], | |
| 2957 | + | ), | |
| 2608 | 2958 | Op::ListEvents => object( | |
| 2609 | 2959 | json!({ | |
| 2610 | 2960 | "repo": repo_schema(), | |
| ⋯ | |||
| 3380 | 3730 | Op::ListRepos | |
| 3381 | 3731 | | Op::Search | |
| 3382 | 3732 | | Op::GetRepo | |
| 3733 | + | | Op::ListForks | |
| 3383 | 3734 | | Op::ListIssues | |
| 3384 | 3735 | | Op::GetIssue | |
| 3385 | 3736 | | Op::ListLabels | |
| ⋯ | |||
| 3392 | 3743 | | Op::ListDecisions | |
| 3393 | 3744 | | Op::Why | |
| 3394 | 3745 | | Op::GetPullRequestChanges | |
| 3746 | + | | Op::GetPullRequestMergeOptions | |
| 3747 | + | | Op::ListRepoCommitComments | |
| 3748 | + | | Op::ListCommitComments | |
| 3395 | 3749 | | Op::ListEvents | |
| 3396 | 3750 | | Op::GetRepoSettings | |
| 3397 | 3751 | | Op::ListCheckNames | |
| ⋯ | |||
| 3412 | 3766 | | ProtectionOp::GetWorkflowPermissions | |
| 3413 | 3767 | | ProtectionOp::GetForkPrApproval | |
| 3414 | 3768 | | ProtectionOp::GetActionsAccess | |
| 3769 | + | | ProtectionOp::GetActionsPermissions | |
| 3415 | 3770 | ) | |
| 3416 | 3771 | ) | |
| 3417 | 3772 | } | |
| ⋯ | |||
| 3706 | 4061 | keep_issue_open: input["keep_issue_open"].as_bool() == Some(true), | |
| 3707 | 4062 | ignore_checks: input["ignore_checks"].as_bool() == Some(true), | |
| 3708 | 4063 | bypass_rules: input["bypass_rules"].as_bool() == Some(true), | |
| 4064 | + | merge_method: None, | |
| 4065 | + | commit_title: None, | |
| 4066 | + | commit_message: None, | |
| 4067 | + | expected_head: None, | |
| 4068 | + | delete_branch: None, | |
| 4069 | + | update_method: None, | |
| 3709 | 4070 | }; | |
| 3710 | 4071 | let Services { | |
| 3711 | 4072 | identity, | |
| ⋯ | |||
| 4098 | 4459 | ) | |
| 4099 | 4460 | .await | |
| 4100 | 4461 | } | |
| 4462 | + | Op::ForkRepo => { | |
| 4463 | + | let owner = actor(); | |
| 4464 | + | // Your personal workspace (your username's), or the only | |
| 4465 | + | // one you belong to, when none is named. | |
| 4466 | + | let namespace = optional_text(input, "workspace").map(|slug| slug.to_lowercase()).unwrap_or_else(|| { | |
| 4467 | + | if owner.workspaces.iter().any(|w| w.slug == owner.username) { | |
| 4468 | + | owner.username.clone() | |
| 4469 | + | } else { | |
| 4470 | + | match owner.workspaces.as_slice() { | |
| 4471 | + | [only] => only.slug.clone(), | |
| 4472 | + | _ => String::new(), | |
| 4473 | + | } | |
| 4474 | + | } | |
| 4475 | + | }); | |
| 4476 | + | pass( | |
| 4477 | + | repos, | |
| 4478 | + | "fork", | |
| 4479 | + | &ForkRepoArgs { | |
| 4480 | + | actor: owner, | |
| 4481 | + | path: repo, | |
| 4482 | + | namespace, | |
| 4483 | + | name: optional_text(input, "name"), | |
| 4484 | + | description: input["description"].as_str().map(str::to_owned), | |
| 4485 | + | default_branch_only: input["default_branch_only"].as_bool() != Some(false), | |
| 4486 | + | surface: Some(services.audit.surface), | |
| 4487 | + | }, | |
| 4488 | + | ) | |
| 4489 | + | .await | |
| 4490 | + | } | |
| 4491 | + | Op::ListForks => { | |
| 4492 | + | pass( | |
| 4493 | + | repos, | |
| 4494 | + | "forks", | |
| 4495 | + | &ForksArgs { | |
| 4496 | + | viewer: viewer.clone(), | |
| 4497 | + | path: repo, | |
| 4498 | + | limit: integer(input, "limit"), | |
| 4499 | + | }, | |
| 4500 | + | ) | |
| 4501 | + | .await | |
| 4502 | + | } | |
| 4503 | + | Op::SyncFork => { | |
| 4504 | + | pass( | |
| 4505 | + | repos, | |
| 4506 | + | "sync_fork", | |
| 4507 | + | &SyncForkArgs { | |
| 4508 | + | actor: actor(), | |
| 4509 | + | path: repo, | |
| 4510 | + | branch: optional_text(input, "branch"), | |
| 4511 | + | surface: Some(services.audit.surface), | |
| 4512 | + | }, | |
| 4513 | + | ) | |
| 4514 | + | .await | |
| 4515 | + | } | |
| 4516 | + | Op::DetachFork => { | |
| 4517 | + | pass( | |
| 4518 | + | repos, | |
| 4519 | + | "detach_fork", | |
| 4520 | + | &DetachForkArgs { | |
| 4521 | + | actor: actor(), | |
| 4522 | + | path: repo, | |
| 4523 | + | surface: Some(services.audit.surface), | |
| 4524 | + | }, | |
| 4525 | + | ) | |
| 4526 | + | .await | |
| 4527 | + | } | |
| 4101 | 4528 | Op::ListRepos => { | |
| 4102 | 4529 | let found: Vec<Repo> = g1t_kit::call( | |
| 4103 | 4530 | repos, | |
| ⋯ | |||
| 4124 | 4551 | .await | |
| 4125 | 4552 | } | |
| 4126 | 4553 | Op::UpdateRepo => { | |
| 4554 | + | let max_ref_updates = integer(input, "max_ref_updates"); | |
| 4555 | + | if input.get("max_ref_updates").is_some_and(|given| !given.is_null()) && max_ref_updates.is_none() { | |
| 4556 | + | return failed(FailureCode::Invalid, "max_ref_updates must be a whole number, 0 for no limit."); | |
| 4557 | + | } | |
| 4127 | 4558 | let updated = pass( | |
| 4128 | 4559 | repos, | |
| 4129 | 4560 | "update", | |
| ⋯ | |||
| 4135 | 4566 | "protected": input["protected"].as_bool(), | |
| 4136 | 4567 | "topics": strings(input, "topics"), | |
| 4137 | 4568 | "website": input["website"].as_str(), | |
| 4569 | + | "allowForking": input["allow_forking"].as_bool(), | |
| 4570 | + | "maxRefUpdates": max_ref_updates, | |
| 4138 | 4571 | "surface": services.audit.surface, | |
| 4139 | 4572 | }), | |
| 4140 | 4573 | ) | |
| ⋯ | |||
| 4328 | 4761 | Outcome::Fail(failure) => return Ok(Outcome::Fail(failure)), | |
| 4329 | 4762 | }; | |
| 4330 | 4763 | let flag = |key: &str, now: bool| input[key].as_bool().unwrap_or(now); | |
| 4764 | + | let default_merge_method = match merge_method(input, "default_merge_method") { | |
| 4765 | + | Ok(method) => method.unwrap_or(current.default_merge_method), | |
| 4766 | + | Err(message) => return failed(FailureCode::Invalid, &message), | |
| 4767 | + | }; | |
| 4768 | + | let merge_commit_message = match commit_message_default(input, "merge_commit_message") { | |
| 4769 | + | Ok(Some(CommitMessageDefault::PullTitleAndCommits)) => { | |
| 4770 | + | return failed(FailureCode::Invalid, "pull_title_and_commits is for squash commits: use it in squash_commit_message."); | |
| 4771 | + | } | |
| 4772 | + | Ok(given) => given.unwrap_or(current.merge_commit_message), | |
| 4773 | + | Err(message) => return failed(FailureCode::Invalid, &message), | |
| 4774 | + | }; | |
| 4775 | + | let squash_commit_message = match commit_message_default(input, "squash_commit_message") { | |
| 4776 | + | Ok(given) => given.unwrap_or(current.squash_commit_message), | |
| 4777 | + | Err(message) => return failed(FailureCode::Invalid, &message), | |
| 4778 | + | }; | |
| 4779 | + | // A template given empty clears it; one left out stays. | |
| 4780 | + | let template = |key: &str, now: &Option<String>| match input.get(key) { | |
| 4781 | + | None => now.clone(), | |
| 4782 | + | Some(given) => given.as_str().map(str::to_owned).filter(|text| !text.trim().is_empty()), | |
| 4783 | + | }; | |
| 4784 | + | let has_issues = match input.get("has_issues") { | |
| 4785 | + | None => current.has_issues, | |
| 4786 | + | Some(Value::Null) => None, | |
| 4787 | + | Some(given) => match given.as_bool() { | |
| 4788 | + | Some(on) => Some(on), | |
| 4789 | + | None => return failed(FailureCode::Invalid, "has_issues must be true, false or null."), | |
| 4790 | + | }, | |
| 4791 | + | }; | |
| 4331 | 4792 | let settings = RepoSettings { | |
| 4793 | + | allow_merge_commit: flag("allow_merge_commit", current.allow_merge_commit), | |
| 4794 | + | allow_squash_merge: flag("allow_squash_merge", current.allow_squash_merge), | |
| 4795 | + | allow_rebase_merge: flag("allow_rebase_merge", current.allow_rebase_merge), | |
| 4796 | + | allow_fast_forward: flag("allow_fast_forward", current.allow_fast_forward), | |
| 4797 | + | default_merge_method, | |
| 4798 | + | merge_commit_message, | |
| 4799 | + | squash_commit_message, | |
| 4800 | + | merge_commit_template: template("merge_commit_template", ¤t.merge_commit_template), | |
| 4801 | + | squash_commit_template: template("squash_commit_template", ¤t.squash_commit_template), | |
| 4802 | + | suggest_updating_branches: flag("suggest_updating_branches", current.suggest_updating_branches), | |
| 4803 | + | allow_auto_merge: flag("allow_auto_merge", current.allow_auto_merge), | |
| 4804 | + | delete_branch_on_merge: flag("delete_branch_on_merge", current.delete_branch_on_merge), | |
| 4805 | + | require_web_commit_sign_off: flag("require_web_commit_sign_off", current.require_web_commit_sign_off), | |
| 4806 | + | allow_commit_comments: flag("allow_commit_comments", current.allow_commit_comments), | |
| 4807 | + | auto_close_issues: flag("auto_close_issues", current.auto_close_issues), | |
| 4808 | + | has_issues, | |
| 4332 | 4809 | auto_merge: flag("auto_merge", current.auto_merge), | |
| 4333 | 4810 | required_checks: strings(input, "required_checks").unwrap_or(current.required_checks.clone()), | |
| 4334 | 4811 | require_up_to_date: flag("require_up_to_date", current.require_up_to_date), | |
| ⋯ | |||
| 4643 | 5120 | Op::GetPullRequest => pass(work, "get_pull", &view()).await, | |
| 4644 | 5121 | Op::CreatePullRequest => { | |
| 4645 | 5122 | let user = actor(); | |
| 5123 | + | // A fork named but not as `owner/name` is refused, never | |
| 5124 | + | // read as the repository's own branch of that name. | |
| 5125 | + | let head_repo = match optional_text(input, "head_repo") { | |
| 5126 | + | Some(_) => match repo_path(&json!({ "repo": input["head_repo"] })) { | |
| 5127 | + | Some(path) => Some(path), | |
| 5128 | + | None => return failed(FailureCode::Invalid, "Give head_repo as owner/name."), | |
| 5129 | + | }, | |
| 5130 | + | None => None, | |
| 5131 | + | }; | |
| 4646 | 5132 | let opened: Outcome<Pull> = call( | |
| 4647 | 5133 | work, | |
| 4648 | 5134 | "open_pull", | |
| ⋯ | |||
| 4653 | 5139 | title: text(input, "title"), | |
| 4654 | 5140 | body: text(input, "body"), | |
| 4655 | 5141 | branch: optional_text(input, "branch"), | |
| 5142 | + | head_repo, | |
| 4656 | 5143 | // Unnamed, the change is its author's, unless an agent's token opened it. | |
| 4657 | 5144 | agent: optional_text(input, "agent") | |
| 4658 | 5145 | .unwrap_or_else(|| if g1t_contracts::rules::is_agent(&user) { "agent".into() } else { user.username.clone() }), | |
| ⋯ | |||
| 4667 | 5154 | Outcome::Fail(failure) => return Ok(Outcome::Fail(failure)), | |
| 4668 | 5155 | }; | |
| 4669 | 5156 | // Where to push. A pull request from a branch has no fork: | |
| 4670 | − | // push to that branch of the repository. | |
| 4671 | − | let source = pull.fork.as_ref().unwrap_or(&repo); | |
| 5157 | + | // push to that branch of the repository, or of the fork | |
| 5158 | + | // it was opened from. | |
| 5159 | + | let source = pull.fork.as_ref().or(pull.head_repo.as_ref()).unwrap_or(&repo); | |
| 4672 | 5160 | let remote = services.addresses.git_remote(&source.namespace, &source.name); | |
| 4673 | 5161 | ok(&json!({ | |
| 4674 | 5162 | "pull": pull, | |
| ⋯ | |||
| 4750 | 5238 | let method = if self == Op::EditComment { "edit_comment" } else { "delete_comment" }; | |
| 4751 | 5239 | pass(work, method, &asked).await | |
| 4752 | 5240 | } | |
| 4753 | − | Op::MergePullRequest => pass(work, "merge_pull", &pull_action()).await, | |
| 5241 | + | Op::MergePullRequest => { | |
| 5242 | + | let method = match merge_method(input, "merge_method") { | |
| 5243 | + | Ok(method) => method, | |
| 5244 | + | Err(message) => return failed(FailureCode::Invalid, &message), | |
| 5245 | + | }; | |
| 5246 | + | let asked = PullActionArgs { | |
| 5247 | + | merge_method: method, | |
| 5248 | + | commit_title: optional_text(input, "commit_title"), | |
| 5249 | + | commit_message: input["commit_message"].as_str().map(str::to_owned), | |
| 5250 | + | expected_head: optional_text(input, "sha"), | |
| 5251 | + | delete_branch: input["delete_branch"].as_bool(), | |
| 5252 | + | ..pull_action() | |
| 5253 | + | }; | |
| 5254 | + | pass(work, "merge_pull", &asked).await | |
| 5255 | + | } | |
| 5256 | + | Op::GetPullRequestMergeOptions => pass(work, "merge_choices", &view()).await, | |
| 5257 | + | Op::UpdatePullRequestBranch => { | |
| 5258 | + | let update_method = match input["update_method"].as_str().map(str::trim).filter(|given| !given.is_empty()) { | |
| 5259 | + | None => None, | |
| 5260 | + | Some(given) if given.eq_ignore_ascii_case("merge") => Some(UpdateMethod::Merge), | |
| 5261 | + | Some(given) if given.eq_ignore_ascii_case("rebase") => Some(UpdateMethod::Rebase), | |
| 5262 | + | Some(given) => return failed(FailureCode::Invalid, &format!("update_method must be merge or rebase, not {given}.")), | |
| 5263 | + | }; | |
| 5264 | + | let asked = PullActionArgs { | |
| 5265 | + | expected_head: optional_text(input, "expected_head_sha"), | |
| 5266 | + | update_method, | |
| 5267 | + | ..pull_action() | |
| 5268 | + | }; | |
| 5269 | + | pass(work, "catch_up_pull", &asked).await | |
| 5270 | + | } | |
| 5271 | + | Op::EnablePullRequestAutoMerge | Op::DisablePullRequestAutoMerge => { | |
| 5272 | + | let enabled = self == Op::EnablePullRequestAutoMerge; | |
| 5273 | + | let method = match merge_method(input, "merge_method") { | |
| 5274 | + | Ok(method) => method.filter(|_| enabled), | |
| 5275 | + | Err(message) if enabled => return failed(FailureCode::Invalid, &message), | |
| 5276 | + | Err(_) => None, | |
| 5277 | + | }; | |
| 5278 | + | pass( | |
| 5279 | + | work, | |
| 5280 | + | "set_auto_merge", | |
| 5281 | + | &SetAutoMergeArgs { | |
| 5282 | + | actor: actor(), | |
| 5283 | + | repo, | |
| 5284 | + | number, | |
| 5285 | + | enabled, | |
| 5286 | + | merge_method: method, | |
| 5287 | + | commit_title: optional_text(input, "commit_title").filter(|_| enabled), | |
| 5288 | + | commit_message: input["commit_message"].as_str().map(str::to_owned).filter(|_| enabled), | |
| 5289 | + | }, | |
| 5290 | + | ) | |
| 5291 | + | .await | |
| 5292 | + | } | |
| 5293 | + | Op::DeletePullRequestHeadBranch => pass(work, "delete_head_branch", &pull_action()).await, | |
| 5294 | + | Op::RestorePullRequestHeadBranch => pass(work, "restore_head_branch", &pull_action()).await, | |
| 5295 | + | Op::ListRepoCommitComments | Op::ListCommitComments => { | |
| 5296 | + | let commit = if self == Op::ListCommitComments { | |
| 5297 | + | match optional_text(input, "sha") { | |
| 5298 | + | Some(sha) => Some(sha), | |
| 5299 | + | None => return failed(FailureCode::Invalid, "Give sha: the commit's hash."), | |
| 5300 | + | } | |
| 5301 | + | } else { | |
| 5302 | + | None | |
| 5303 | + | }; | |
| 5304 | + | pass( | |
| 5305 | + | work, | |
| 5306 | + | "commit_comments", | |
| 5307 | + | &CommitCommentsArgs { | |
| 5308 | + | viewer: viewer.clone(), | |
| 5309 | + | repo, | |
| 5310 | + | commit, | |
| 5311 | + | page: integer(input, "page").filter(|page| *page > 0), | |
| 5312 | + | }, | |
| 5313 | + | ) | |
| 5314 | + | .await | |
| 5315 | + | } | |
| 5316 | + | Op::CreateCommitComment => { | |
| 5317 | + | let body = text(input, "body"); | |
| 5318 | + | if body.trim().is_empty() { | |
| 5319 | + | return failed(FailureCode::Invalid, "Give body: the comment, in Markdown."); | |
| 5320 | + | } | |
| 5321 | + | let Some(commit) = optional_text(input, "sha") else { | |
| 5322 | + | return failed(FailureCode::Invalid, "Give sha: the commit's hash."); | |
| 5323 | + | }; | |
| 5324 | + | let path = optional_text(input, "path"); | |
| 5325 | + | let line = integer(input, "line").filter(|line| *line > 0); | |
| 5326 | + | if input.get("line").is_some_and(|given| !given.is_null()) && line.is_none() { | |
| 5327 | + | return failed(FailureCode::Invalid, "line is counted from 1."); | |
| 5328 | + | } | |
| 5329 | + | if line.is_some() && path.is_none() { | |
| 5330 | + | return failed(FailureCode::Invalid, "Give path with line: the file the line is in."); | |
| 5331 | + | } | |
| 5332 | + | pass( | |
| 5333 | + | work, | |
| 5334 | + | "add_commit_comment", | |
| 5335 | + | &AddCommitCommentArgs { actor: actor(), repo, commit, body, path, line }, | |
| 5336 | + | ) | |
| 5337 | + | .await | |
| 5338 | + | } | |
| 5339 | + | Op::UpdateCommitComment | Op::DeleteCommitComment => { | |
| 5340 | + | let editing = self == Op::UpdateCommitComment; | |
| 5341 | + | let body = text(input, "body"); | |
| 5342 | + | if editing && body.trim().is_empty() { | |
| 5343 | + | return failed(FailureCode::Invalid, "Give body: the new text, in Markdown."); | |
| 5344 | + | } | |
| 5345 | + | let asked = CommitCommentArgs { actor: actor(), repo, id: text(input, "comment_id"), body }; | |
| 5346 | + | let method = if editing { "edit_commit_comment" } else { "delete_commit_comment" }; | |
| 5347 | + | pass(work, method, &asked).await | |
| 5348 | + | } | |
| 4754 | 5349 | Op::GetPullRequestChanges => { | |
| 4755 | 5350 | let found: Outcome<PullDetail> = call(work, "get_pull", &view()).await?; | |
| 4756 | 5351 | match found { | |
| 26 | 26 | SetForkPrApproval, | |
| 27 | 27 | GetActionsAccess, | |
| 28 | 28 | SetActionsAccess, | |
| 29 | + | GetActionsPermissions, | |
| 30 | + | SetActionsPermissions, | |
| 29 | 31 | CreateRepositoryDispatch, | |
| 30 | 32 | GetWorkspaceWorkflowPermissions, | |
| 31 | 33 | SetWorkspaceWorkflowPermissions, | |
| ⋯ | |||
| 39 | 41 | /// Every one: `Op::ALL` lists each as `Op::Protection(…)`, which a test | |
| 40 | 42 | /// checks against this. | |
| 41 | 43 | #[cfg(test)] | |
| 42 | − | pub const ALL: [ProtectionOp; 18] = [ | |
| 44 | + | pub const ALL: [ProtectionOp; 20] = [ | |
| 43 | 45 | ProtectionOp::UpdateEnvironment, | |
| 44 | 46 | ProtectionOp::DeleteEnvironment, | |
| 45 | 47 | ProtectionOp::GetPendingDeployments, | |
| ⋯ | |||
| 51 | 53 | ProtectionOp::SetForkPrApproval, | |
| 52 | 54 | ProtectionOp::GetActionsAccess, | |
| 53 | 55 | ProtectionOp::SetActionsAccess, | |
| 56 | + | ProtectionOp::GetActionsPermissions, | |
| 57 | + | ProtectionOp::SetActionsPermissions, | |
| 54 | 58 | ProtectionOp::CreateRepositoryDispatch, | |
| 55 | 59 | ProtectionOp::GetWorkspaceWorkflowPermissions, | |
| 56 | 60 | ProtectionOp::SetWorkspaceWorkflowPermissions, | |
| ⋯ | |||
| 73 | 77 | ProtectionOp::SetForkPrApproval => "set_fork_pr_approval", | |
| 74 | 78 | ProtectionOp::GetActionsAccess => "get_actions_access", | |
| 75 | 79 | ProtectionOp::SetActionsAccess => "set_actions_access", | |
| 80 | + | ProtectionOp::GetActionsPermissions => "get_actions_permissions", | |
| 81 | + | ProtectionOp::SetActionsPermissions => "set_actions_permissions", | |
| 76 | 82 | ProtectionOp::CreateRepositoryDispatch => "create_repository_dispatch", | |
| 77 | 83 | ProtectionOp::GetWorkspaceWorkflowPermissions => "get_workspace_workflow_permissions", | |
| 78 | 84 | ProtectionOp::SetWorkspaceWorkflowPermissions => "set_workspace_workflow_permissions", | |
| ⋯ | |||
| 113 | 119 | ProtectionOp::SetForkPrApproval => "Set the approval policy for outside pull requests", | |
| 114 | 120 | ProtectionOp::GetActionsAccess => "Get who may use a repository's actions and workflows", | |
| 115 | 121 | ProtectionOp::SetActionsAccess => "Set who may use a repository's actions and workflows", | |
| 122 | + | ProtectionOp::GetActionsPermissions => "Get whether a repository's workflows run", | |
| 123 | + | ProtectionOp::SetActionsPermissions => "Turn a repository's workflows on or off", | |
| 116 | 124 | ProtectionOp::CreateRepositoryDispatch => "Create a repository dispatch event", | |
| 117 | 125 | ProtectionOp::GetWorkspaceWorkflowPermissions => "Get a workspace's default workflow permissions", | |
| 118 | 126 | ProtectionOp::SetWorkspaceWorkflowPermissions => "Set a workspace's default workflow permissions", | |
| ⋯ | |||
| 136 | 144 | ProtectionOp::SetForkPrApproval => "Set approval_policy: first_time_contributors, outside_contributors or all_external_contributors. Needs the Admin role.", | |
| 137 | 145 | ProtectionOp::GetActionsAccess => "Which other repositories' workflows may use this private repository's actions (uses: owner/repo@ref) and reusable workflows (jobs.<id>.uses: owner/repo/.g1t/workflows/build.yml@ref): access_level is none (the default: only this repository) or organization (private repositories in the same workspace). A public repository's actions and workflows are anyone's, whatever this says, and a public repository's workflows never use a private one's. Needs the Read role.", | |
| 138 | 146 | ProtectionOp::SetActionsAccess => "Set access_level: none, or organization to let the workspace's other private repositories use this repository's actions and reusable workflows (user is read as organization). Needs the Admin role.", | |
| 147 | + | ProtectionOp::GetActionsPermissions => "Whether a repository's workflows run at all: enabled is on for a repository and off for a fork until someone chooses (enabled_chosen says whether anyone has), so a fork never runs the workflows it copied until they are turned on. While it is off, no push, pull request, schedule, dispatch, repository_dispatch, merge queue or re-run starts a run; its workflows stay listed. allowed_actions is always all. Needs the Read role.", | |
| 148 | + | ProtectionOp::SetActionsPermissions => "Turn a repository's workflows on or off (enabled). Off, nothing starts a run, and a dispatch or re-run is refused with \"Actions is turned off for owner/name\"; runs already going finish. Turning it on runs nothing by itself: the next event does. Needs the Admin role; a workflow job's own token cannot.", | |
| 139 | 149 | ProtectionOp::GetWorkspaceWorkflowPermissions => "A workspace's policy for its repositories' job tokens: default_workflow_permissions (read, the default, or write) is what a repository made from now on gets until it chooses; max_workflow_permissions (write, the default, or read) is the most any repository's default may be, so read holds every repository to read-only; can_approve_pull_request_reviews (off by default) lets its repositories allow jobs to open and approve pull requests. Members only.", | |
| 140 | 150 | ProtectionOp::SetWorkspaceWorkflowPermissions => "Change a workspace's default_workflow_permissions, max_workflow_permissions and can_approve_pull_request_reviews; fields left out stay as they are. A maximum of read makes the default read too. Owners only.", | |
| 141 | 151 | ProtectionOp::ListWorkspaceManagedWorkflows => "The workflows a workspace's owners added to its repositories, g1t's required workflows: each runs in every repository it targets as that repository's own workflow (its token, its secrets, its checks), under the path managed/<name>.yml, from a copy of its source file taken when it was saved. Each has name, path, workflow_name (its `name:`), source (owner/repo/path@ref), source_sha (the commit it was copied at), source_private, mode, repositories, visibility, content (the copy that runs) and when and by whom it was last saved. Owners only.", | |
| ⋯ | |||
| 154 | 164 | | ProtectionOp::GetWorkflowPermissions | |
| 155 | 165 | | ProtectionOp::GetForkPrApproval | |
| 156 | 166 | | ProtectionOp::GetActionsAccess | |
| 167 | + | | ProtectionOp::GetActionsPermissions | |
| 157 | 168 | | ProtectionOp::GetWorkspaceWorkflowPermissions | |
| 158 | 169 | | ProtectionOp::ListWorkspaceManagedWorkflows | |
| 159 | 170 | ) | |
| ⋯ | |||
| 217 | 228 | }), | |
| 218 | 229 | &["repo", "id", "state"], | |
| 219 | 230 | ), | |
| 220 | − | ProtectionOp::GetWorkflowPermissions | ProtectionOp::GetForkPrApproval | ProtectionOp::GetActionsAccess => (json!({ "repo": repo }), &["repo"]), | |
| 231 | + | ProtectionOp::GetWorkflowPermissions | |
| 232 | + | | ProtectionOp::GetForkPrApproval | |
| 233 | + | | ProtectionOp::GetActionsAccess | |
| 234 | + | | ProtectionOp::GetActionsPermissions => (json!({ "repo": repo }), &["repo"]), | |
| 235 | + | ProtectionOp::SetActionsPermissions => ( | |
| 236 | + | json!({ | |
| 237 | + | "repo": repo, | |
| 238 | + | "enabled": { "type": "boolean", "description": "Whether its workflows run." }, | |
| 239 | + | }), | |
| 240 | + | &["repo", "enabled"], | |
| 241 | + | ), | |
| 221 | 242 | ProtectionOp::SetActionsAccess => ( | |
| 222 | 243 | json!({ | |
| 223 | 244 | "repo": repo, | |
| ⋯ | |||
| 714 | 735 | }; | |
| 715 | 736 | map(settings, |s| json!({ "access_level": s["accessLevel"] })) | |
| 716 | 737 | } | |
| 738 | + | ProtectionOp::GetActionsPermissions | ProtectionOp::SetActionsPermissions => { | |
| 739 | + | let settings: Outcome<Value> = if op == ProtectionOp::GetActionsPermissions { | |
| 740 | + | g1t_kit::call(actions, "actions_settings", &json!({ "viewer": viewer, "repo": repo })).await? | |
| 741 | + | } else { | |
| 742 | + | let Some(enabled) = flag(input, "enabled") else { | |
| 743 | + | return Ok(Outcome::fail(FailureCode::Invalid, "enabled is true or false.")); | |
| 744 | + | }; | |
| 745 | + | g1t_kit::call(actions, "set_actions_settings", &json!({ "actor": actor(), "repo": repo, "enabled": enabled })).await? | |
| 746 | + | }; | |
| 747 | + | map(settings, |s| { | |
| 748 | + | json!({ | |
| 749 | + | "enabled": s.get("enabled").cloned().unwrap_or(Value::Bool(true)), | |
| 750 | + | "enabled_chosen": s.get("enabledChosen").cloned().unwrap_or(Value::Bool(false)), | |
| 751 | + | "allowed_actions": "all", | |
| 752 | + | }) | |
| 753 | + | }) | |
| 754 | + | } | |
| 717 | 755 | ProtectionOp::CreateRepositoryDispatch => { | |
| 718 | 756 | let started: Outcome<u32> = g1t_kit::call( | |
| 719 | 757 | actions, | |
| 707 | 707 | "website": "https://hello.g1t.page", | |
| 708 | 708 | "archived_at": null | |
| 709 | 709 | }, | |
| 710 | − | "notes": "Only the fields given change. `website` gets `https://` when no scheme is given; an empty string clears it. `default_branch` must name a branch that exists (`422` otherwise); it is changed after the other fields. `description`, `website`, `topics` and `protected` need the Maintain role or higher, and `private` and `default_branch` the Admin role (`403` otherwise, saying which role). Changing `private` here needs no confirmation; `POST /repos/{owner}/{name}/visibility` asks for one. Making it private is refused with `402` when a free workspace's private storage has no room for it. See [Managing a repository](/guides/managing-repositories/) and [Access and roles](/guides/access-and-roles/)." | |
| 710 | + | "notes": "Only the fields given change. `website` gets `https://` when no scheme is given; an empty string clears it. `default_branch` must name a branch that exists (`422` otherwise); it is changed after the other fields. `description`, `website`, `topics` and `protected` need the Maintain role or higher, and `private`, `allow_forking`, `max_ref_updates` and `default_branch` the Admin role (`403` otherwise, saying which role). Changing `private` here needs no confirmation; `POST /repos/{owner}/{name}/visibility` asks for one. Making it private is refused with `402` when a free workspace's private storage has no room for it. A fork cannot change `private` (`422`): detach it first. See [Managing a repository](/guides/managing-repositories/) and [Access and roles](/guides/access-and-roles/)." | |
| 711 | 711 | }, | |
| 712 | 712 | "transfer_repo": { | |
| 713 | 713 | "request": { | |
| ⋯ | |||
| 729 | 729 | }, | |
| 730 | 730 | "notes": "The repository at its new address. Refused with `409` when the destination already has a repository of that name, `403` unless you own both workspaces, and `402` when a free destination has no room for a private repository. The old address redirects. See [Transferring a repository](/guides/transferring-repositories/)." | |
| 731 | 731 | }, | |
| 732 | + | "fork_repo": { | |
| 733 | + | "request": { | |
| 734 | + | "workspace": "ada", | |
| 735 | + | "default_branch_only": true | |
| 736 | + | }, | |
| 737 | + | "response": { | |
| 738 | + | "id": "rep_01m4b7k2v9c3tq8x5n6d0fr1hz", | |
| 739 | + | "namespace": "ada", | |
| 740 | + | "name": "hello", | |
| 741 | + | "description": "A tiny service that says hello.", | |
| 742 | + | "is_private": false, | |
| 743 | + | "owner_id": "usr_01kq3r8w2m6vd5e9t0hy7jx4cb", | |
| 744 | + | "default_branch": "main", | |
| 745 | + | "fork_of": null, | |
| 746 | + | "parent_id": "rep_01m3m5q6p0e2qaw6mmjahk0qrr", | |
| 747 | + | "parent": { | |
| 748 | + | "namespace": "flagon-io", | |
| 749 | + | "name": "hello" | |
| 750 | + | }, | |
| 751 | + | "allow_forking": false, | |
| 752 | + | "protected": false, | |
| 753 | + | "created_at": "2026-10-14T09:30:12.418Z", | |
| 754 | + | "website": null, | |
| 755 | + | "archived_at": null | |
| 756 | + | }, | |
| 757 | + | "notes": "The new fork, with `parent` naming the repository it came from. Refused with `403` when the repository is private and its admins have not allowed forking, or when you cannot create repositories in the workspace; `409` when the workspace already has a repository of that name (give another `name`); `402` when a free workspace's private storage has no room for a private fork. Its code and description are copied; nothing else is. See [Forking a repository](/guides/forking-repositories/) and [Forks](/concepts/forks/)." | |
| 758 | + | }, | |
| 759 | + | "list_forks": { | |
| 760 | + | "query": { | |
| 761 | + | "limit": "30" | |
| 762 | + | }, | |
| 763 | + | "response": [ | |
| 764 | + | { | |
| 765 | + | "id": "rep_01m4b7k2v9c3tq8x5n6d0fr1hz", | |
| 766 | + | "namespace": "ada", | |
| 767 | + | "name": "hello", | |
| 768 | + | "description": "A tiny service that says hello.", | |
| 769 | + | "is_private": false, | |
| 770 | + | "owner_id": "usr_01kq3r8w2m6vd5e9t0hy7jx4cb", | |
| 771 | + | "default_branch": "main", | |
| 772 | + | "fork_of": null, | |
| 773 | + | "parent_id": "rep_01m3m5q6p0e2qaw6mmjahk0qrr", | |
| 774 | + | "parent": { | |
| 775 | + | "namespace": "flagon-io", | |
| 776 | + | "name": "hello" | |
| 777 | + | }, | |
| 778 | + | "allow_forking": false, | |
| 779 | + | "protected": false, | |
| 780 | + | "created_at": "2026-10-14T09:30:12.418Z", | |
| 781 | + | "website": null, | |
| 782 | + | "archived_at": null | |
| 783 | + | } | |
| 784 | + | ], | |
| 785 | + | "notes": "Only the forks you can read, newest first: a private repository's forks are listed for people who can read them. Forks of a fork are listed on that fork. See [Forks](/concepts/forks/)." | |
| 786 | + | }, | |
| 787 | + | "sync_fork": { | |
| 788 | + | "request": { | |
| 789 | + | "branch": "main" | |
| 790 | + | }, | |
| 791 | + | "response": { | |
| 792 | + | "status": "fast_forwarded", | |
| 793 | + | "branch": "main", | |
| 794 | + | "head": "9c4e1f0b7a2d3e5f6a8b9c0d1e2f3a4b5c6d7e8f", | |
| 795 | + | "behind": 3, | |
| 796 | + | "ahead": 0, | |
| 797 | + | "conflicts": [] | |
| 798 | + | }, | |
| 799 | + | "notes": "`status` is `up_to_date`, `fast_forwarded`, `merged` (a merge commit joins the fork's own commits and the parent's) or `conflicted`: both sides changed the files in `conflicts`, and nothing was changed. Merge the parent's branch in a clone and push. Refused with `422` when the repository is not a fork, `403` without the Write role on it, and `404` when its parent is gone or you can no longer read it. See [Sync a fork](/guides/forking-repositories/#sync-a-fork)." | |
| 800 | + | }, | |
| 801 | + | "detach_fork": { | |
| 802 | + | "response": { | |
| 803 | + | "id": "rep_01m4b7k2v9c3tq8x5n6d0fr1hz", | |
| 804 | + | "namespace": "ada", | |
| 805 | + | "name": "hello", | |
| 806 | + | "description": "A tiny service that says hello.", | |
| 807 | + | "is_private": false, | |
| 808 | + | "owner_id": "usr_01kq3r8w2m6vd5e9t0hy7jx4cb", | |
| 809 | + | "default_branch": "main", | |
| 810 | + | "fork_of": null, | |
| 811 | + | "parent_id": null, | |
| 812 | + | "parent": null, | |
| 813 | + | "allow_forking": false, | |
| 814 | + | "protected": false, | |
| 815 | + | "created_at": "2026-10-14T09:30:12.418Z", | |
| 816 | + | "website": null, | |
| 817 | + | "archived_at": null | |
| 818 | + | }, | |
| 819 | + | "notes": "The repository, now with no parent. Needs the Admin role (`403` otherwise); `422` when it is not a fork. It cannot be attached again. See [Detach a fork](/guides/forking-repositories/#detach-a-fork)." | |
| 820 | + | }, | |
| 732 | 821 | "rename_repo": { | |
| 733 | 822 | "request": { | |
| 734 | 823 | "name": "greeter" | |
| ⋯ | |||
| 946 | 1035 | "allow_ignoring_checks": true, | |
| 947 | 1036 | "merge_queue": false, | |
| 948 | 1037 | "require_code_owner_review": false, | |
| 1038 | + | "has_issues": null, | |
| 1039 | + | "issues_enabled": true, | |
| 1040 | + | "allow_merge_commit": true, | |
| 1041 | + | "allow_squash_merge": true, | |
| 1042 | + | "allow_rebase_merge": true, | |
| 1043 | + | "allow_fast_forward": false, | |
| 1044 | + | "default_merge_method": "merge", | |
| 1045 | + | "merge_commit_message": "default", | |
| 1046 | + | "squash_commit_message": "default", | |
| 1047 | + | "merge_commit_template": null, | |
| 1048 | + | "squash_commit_template": null, | |
| 1049 | + | "suggest_updating_branches": true, | |
| 1050 | + | "allow_auto_merge": true, | |
| 1051 | + | "delete_branch_on_merge": false, | |
| 1052 | + | "require_web_commit_sign_off": false, | |
| 1053 | + | "allow_commit_comments": true, | |
| 1054 | + | "auto_close_issues": true, | |
| 949 | 1055 | "updated_by": null, | |
| 950 | 1056 | "updated_at": null | |
| 951 | 1057 | } | |
| ⋯ | |||
| 959 | 1065 | "required_approvals": 1, | |
| 960 | 1066 | "count_agent_approvals": false, | |
| 961 | 1067 | "merge_queue": true, | |
| 962 | − | "require_code_owner_review": true | |
| 1068 | + | "require_code_owner_review": true, | |
| 1069 | + | "allow_merge_commit": false, | |
| 1070 | + | "default_merge_method": "squash", | |
| 1071 | + | "squash_commit_message": "pull_title_and_description", | |
| 1072 | + | "delete_branch_on_merge": true | |
| 963 | 1073 | }, | |
| 964 | 1074 | "response": { | |
| 965 | 1075 | "auto_merge": false, | |
| ⋯ | |||
| 973 | 1083 | "allow_ignoring_checks": true, | |
| 974 | 1084 | "merge_queue": true, | |
| 975 | 1085 | "require_code_owner_review": true, | |
| 1086 | + | "has_issues": null, | |
| 1087 | + | "issues_enabled": true, | |
| 1088 | + | "allow_merge_commit": false, | |
| 1089 | + | "allow_squash_merge": true, | |
| 1090 | + | "allow_rebase_merge": true, | |
| 1091 | + | "allow_fast_forward": false, | |
| 1092 | + | "default_merge_method": "squash", | |
| 1093 | + | "merge_commit_message": "default", | |
| 1094 | + | "squash_commit_message": "pull_title_and_description", | |
| 1095 | + | "merge_commit_template": null, | |
| 1096 | + | "squash_commit_template": null, | |
| 1097 | + | "suggest_updating_branches": true, | |
| 1098 | + | "allow_auto_merge": true, | |
| 1099 | + | "delete_branch_on_merge": true, | |
| 1100 | + | "require_web_commit_sign_off": false, | |
| 1101 | + | "allow_commit_comments": true, | |
| 1102 | + | "auto_close_issues": true, | |
| 976 | 1103 | "updated_by": "syntaqx", | |
| 977 | 1104 | "updated_at": "2026-10-04T16:20:37.508Z" | |
| 978 | 1105 | }, | |
| 979 | − | "notes": "`required_checks` replaces the whole list: at most 20 names, each a workflow's name or another status's context, as [`list_check_names`](/reference/api/repositories/list-check-names/) gives them. A pull request merges only once each passes on its head; one nothing has reported holds it too. With the merge queue on, each must also pass on the queued state, so the workflows behind them need `merge_group` in their `on:`. `required_approvals` is at most 6. See [what a repository can ask for](/guides/working-with-g1t/#what-a-repository-can-ask-for). With `require_code_owner_review`, a pull request into the default branch merges only once the code owners of every file it changes have approved it, as many as each section of its CODEOWNERS file asks: see [Code owners](/guides/codeowners/)." | |
| 1106 | + | "notes": "`required_checks` replaces the whole list: at most 20 names, each a workflow's name or another status's context, as [`list_check_names`](/reference/api/repositories/list-check-names/) gives them. A pull request merges only once each passes on its head; one nothing has reported holds it too. With the merge queue on, each must also pass on the queued state, so the workflows behind them need `merge_group` in their `on:`. `required_approvals` is at most 6. See [what a repository can ask for](/guides/working-with-g1t/#what-a-repository-can-ask-for). With `require_code_owner_review`, a pull request into the default branch merges only once the code owners of every file it changes have approved it, as many as each section of its CODEOWNERS file asks: see [Code owners](/guides/codeowners/). At least one of `allow_merge_commit`, `allow_squash_merge`, `allow_rebase_merge` and `allow_fast_forward` stays on, and `default_merge_method` must be one of them (`422` otherwise). `merge_commit_template` and `squash_commit_template` are used with `custom`: see [Commit messages](/guides/pull-requests/#commit-messages) for their placeholders. `has_issues` `null` goes back to the default: on for a repository, off for a fork. See [Repository settings](/guides/managing-repositories/#settings)." | |
| 980 | 1107 | }, | |
| 981 | 1108 | "list_check_names": { | |
| 982 | 1109 | "response": [ | |
| ⋯ | |||
| 2050 | 2177 | }, | |
| 2051 | 2178 | "notes": "Takes them off who is asked; anyone not asked is ignored. Reviews they already gave stay, as do the people a team's review assignment picked: remove them by username." | |
| 2052 | 2179 | }, | |
| 2180 | + | "get_pull_request_merge_options": { | |
| 2181 | + | "response": { | |
| 2182 | + | "methods": [ | |
| 2183 | + | "merge", | |
| 2184 | + | "squash", | |
| 2185 | + | "rebase" | |
| 2186 | + | ], | |
| 2187 | + | "default_method": "squash", | |
| 2188 | + | "options": [ | |
| 2189 | + | { | |
| 2190 | + | "method": "merge", | |
| 2191 | + | "commit_title": "Merge pull request #14 from flagon-io/greet-caller", | |
| 2192 | + | "commit_message": "Greeting should name the caller", | |
| 2193 | + | "unavailable": null | |
| 2194 | + | }, | |
| 2195 | + | { | |
| 2196 | + | "method": "squash", | |
| 2197 | + | "commit_title": "Greeting should name the caller (#14)", | |
| 2198 | + | "commit_message": "* Read the name from the first argument\n* Fall back to \"world\"", | |
| 2199 | + | "unavailable": null | |
| 2200 | + | }, | |
| 2201 | + | { | |
| 2202 | + | "method": "rebase", | |
| 2203 | + | "commit_title": null, | |
| 2204 | + | "commit_message": null, | |
| 2205 | + | "unavailable": null | |
| 2206 | + | } | |
| 2207 | + | ], | |
| 2208 | + | "behind": false, | |
| 2209 | + | "can_fast_forward": false, | |
| 2210 | + | "suggest_update": false, | |
| 2211 | + | "require_up_to_date": false, | |
| 2212 | + | "merge_queue": false, | |
| 2213 | + | "auto_merge_allowed": true, | |
| 2214 | + | "delete_branch_default": true, | |
| 2215 | + | "can_delete_branch": true, | |
| 2216 | + | "sign_off": false | |
| 2217 | + | }, | |
| 2218 | + | "notes": "`methods` are the repository's allowed methods, narrowed by the rules for the branch it merges into: required linear history leaves out `merge`. `fast_forward` lists `unavailable` until the branch contains its base's head. `merge` and `squash` carry the commit message a merge would use if you wrote none. See [Merging a pull request](/guides/pull-requests/#merging-a-pull-request)." | |
| 2219 | + | }, | |
| 2053 | 2220 | "merge_pull_request": { | |
| 2054 | 2221 | "request": { | |
| 2055 | − | "keep_issue_open": false | |
| 2222 | + | "merge_method": "squash", | |
| 2223 | + | "commit_title": "Greeting names the caller (#14)", | |
| 2224 | + | "sha": "9f2c4e1a7b3d5f60812a4c6e8b0d2f4a6c8e0b13", | |
| 2225 | + | "delete_branch": true | |
| 2056 | 2226 | }, | |
| 2057 | 2227 | "response": { | |
| 2058 | 2228 | "id": "pr_01m43smh3vexsr5pmp60qwv0vs", | |
| ⋯ | |||
| 2074 | 2244 | "merge_base": "3a7e9c1b5d2f4a6c8e0b2d4f6a8c0e2b4d6f8a1c", | |
| 2075 | 2245 | "merged_by": "syntaqx", | |
| 2076 | 2246 | "merged_at": "2026-10-01T18:52:17.093Z", | |
| 2247 | + | "merge_method": "squash", | |
| 2248 | + | "merge_commit": "c41d7e2a9b0f3e5d7c9a1b3d5f7e9c0a2b4d6f81", | |
| 2249 | + | "closes": [ | |
| 2250 | + | 12 | |
| 2251 | + | ], | |
| 2077 | 2252 | "superseded_by": null, | |
| 2078 | 2253 | "check_status": "passed", | |
| 2079 | 2254 | "files": [ | |
| ⋯ | |||
| 2102 | 2277 | "created_at": "2026-10-01T18:20:02.117Z", | |
| 2103 | 2278 | "updated_at": "2026-10-01T18:52:17.093Z" | |
| 2104 | 2279 | }, | |
| 2105 | − | "notes": "A draft, or one whose checks have not passed, answers `409`. A pull request still `open` in the response has not landed yet: `landing` is true on it while it is brought up to date, and [get the merge queue](/reference/api/pull-requests/get-merge-queue/) shows it waiting in the [merge queue](/guides/merge-queue/). Merging records the pull request in the issue's `resolved_by` and sets `superseded_by` on the pull requests it closes." | |
| 2280 | + | "notes": "A draft, or one whose checks have not passed, answers `409`. A pull request still `open` in the response has not landed yet: `landing` is true on it while it is brought up to date, and [get the merge queue](/reference/api/pull-requests/get-merge-queue/) shows it waiting in the [merge queue](/guides/merge-queue/). Merging records the pull request in the issue's `resolved_by`, closes the issues in `closes` (into the default branch, where `auto_close_issues` is on) and sets `superseded_by` on the pull requests it closes. A `merge_method` the repository or a rule for the base does not allow answers `422`, and a head that moved since `sha`, `409`. `merge_commit` is the commit the base is at afterwards: the merge or squash commit, the last rebased one, or the head for a fast-forward. See [Merging a pull request](/guides/pull-requests/#merging-a-pull-request)." | |
| 2281 | + | }, | |
| 2282 | + | "update_pull_request_branch": { | |
| 2283 | + | "request": { | |
| 2284 | + | "expected_head_sha": "9f2c4e1a7b3d5f60812a4c6e8b0d2f4a6c8e0b13", | |
| 2285 | + | "update_method": "merge" | |
| 2286 | + | }, | |
| 2287 | + | "response": { | |
| 2288 | + | "outcome": "updated", | |
| 2289 | + | "commit": "5b8d0f2a4c6e8a0c2e4a6c8e0a2c4e6a8c0e2a4c", | |
| 2290 | + | "previous": "9f2c4e1a7b3d5f60812a4c6e8b0d2f4a6c8e0b13" | |
| 2291 | + | }, | |
| 2292 | + | "notes": "`outcome` is `updated` (its branch moved from `previous` to `commit`), `up_to_date` (it already holds its base's head; `commit` only) or `needs_agent` (nothing was pushed: `reason` is `overlap`, `conflicting` or `unsupported`, with `detail` and the `paths` concerned). `update_method` `rebase` rewrites its branch, so pull it again before pushing more. A head that moved since `expected_head_sha` answers `409`. See [Keeping a pull request up to date](/guides/pull-requests/#keeping-it-up-to-date)." | |
| 2293 | + | }, | |
| 2294 | + | "enable_pull_request_auto_merge": { | |
| 2295 | + | "request": { | |
| 2296 | + | "merge_method": "squash", | |
| 2297 | + | "commit_title": "Greeting names the caller (#14)" | |
| 2298 | + | }, | |
| 2299 | + | "response": { | |
| 2300 | + | "id": "pr_01m43smh3vexsr5pmp60qwv0vs", | |
| 2301 | + | "repo_id": "rep_01m3m5q6p0e2qaw6mmjahk0qrr", | |
| 2302 | + | "number": 14, | |
| 2303 | + | "issue": 12, | |
| 2304 | + | "title": "Greeting should name the caller", | |
| 2305 | + | "body": "Reads a name from the first argument. `hello ana` prints \"Hello, ana!\".", | |
| 2306 | + | "agent": "claude-code", | |
| 2307 | + | "runtime": "external", | |
| 2308 | + | "status": "open", | |
| 2309 | + | "fork": { | |
| 2310 | + | "namespace": "pulls", | |
| 2311 | + | "name": "pr_01m43smh3vexsr5pmp60qwv0vs" | |
| 2312 | + | }, | |
| 2313 | + | "fork_repo_id": "rep_01m43smh5xd7aw2kq9tv0b4c8e", | |
| 2314 | + | "branch": null, | |
| 2315 | + | "head_commit": "9f2c4e1a7b3d5f60812a4c6e8b0d2f4a6c8e0b13", | |
| 2316 | + | "merge_base": "3a7e9c1b5d2f4a6c8e0b2d4f6a8c0e2b4d6f8a1c", | |
| 2317 | + | "merged_by": null, | |
| 2318 | + | "merged_at": null, | |
| 2319 | + | "superseded_by": null, | |
| 2320 | + | "check_status": "running", | |
| 2321 | + | "files": [ | |
| 2322 | + | { | |
| 2323 | + | "path": "src/main.rs", | |
| 2324 | + | "additions": 6, | |
| 2325 | + | "deletions": 2 | |
| 2326 | + | } | |
| 2327 | + | ], | |
| 2328 | + | "assignees": [], | |
| 2329 | + | "reviewers": [ | |
| 2330 | + | "ana" | |
| 2331 | + | ], | |
| 2332 | + | "labels": [], | |
| 2333 | + | "milestone": null, | |
| 2334 | + | "base": "main", | |
| 2335 | + | "author": { | |
| 2336 | + | "id": "usr_01kkntcg1eeb98j62xjm7eh09p", | |
| 2337 | + | "username": "syntaqx", | |
| 2338 | + | "display_username": "syntaqx", | |
| 2339 | + | "kind": "user", | |
| 2340 | + | "verified": false, | |
| 2341 | + | "workspaces": [] | |
| 2342 | + | }, | |
| 2343 | + | "requested_by": null, | |
| 2344 | + | "created_at": "2026-10-01T18:20:02.117Z", | |
| 2345 | + | "updated_at": "2026-10-01T18:44:09.310Z", | |
| 2346 | + | "auto_merge": { | |
| 2347 | + | "method": "squash", | |
| 2348 | + | "commit_title": "Greeting names the caller (#14)", | |
| 2349 | + | "commit_message": null, | |
| 2350 | + | "enabled_by": "syntaqx", | |
| 2351 | + | "enabled_at": "2026-10-01T18:44:09.310Z" | |
| 2352 | + | }, | |
| 2353 | + | "closes": [ | |
| 2354 | + | 12 | |
| 2355 | + | ] | |
| 2356 | + | }, | |
| 2357 | + | "notes": "A repository with `allow_auto_merge` off answers `422`, as does a method it or a rule for the base does not allow. A pull request that may already merge merges at once: check `status`. `auto_merge` is cleared when it merges or closes, when it is turned off, and when someone other than `enabled_by` pushes to it. See [Auto-merge](/guides/pull-requests/#auto-merge)." | |
| 2358 | + | }, | |
| 2359 | + | "disable_pull_request_auto_merge": { | |
| 2360 | + | "response": { | |
| 2361 | + | "id": "pr_01m43smh3vexsr5pmp60qwv0vs", | |
| 2362 | + | "repo_id": "rep_01m3m5q6p0e2qaw6mmjahk0qrr", | |
| 2363 | + | "number": 14, | |
| 2364 | + | "issue": 12, | |
| 2365 | + | "title": "Greeting should name the caller", | |
| 2366 | + | "body": "Reads a name from the first argument. `hello ana` prints \"Hello, ana!\".", | |
| 2367 | + | "agent": "claude-code", | |
| 2368 | + | "runtime": "external", | |
| 2369 | + | "status": "open", | |
| 2370 | + | "fork": { | |
| 2371 | + | "namespace": "pulls", | |
| 2372 | + | "name": "pr_01m43smh3vexsr5pmp60qwv0vs" | |
| 2373 | + | }, | |
| 2374 | + | "fork_repo_id": "rep_01m43smh5xd7aw2kq9tv0b4c8e", | |
| 2375 | + | "branch": null, | |
| 2376 | + | "head_commit": "9f2c4e1a7b3d5f60812a4c6e8b0d2f4a6c8e0b13", | |
| 2377 | + | "merge_base": "3a7e9c1b5d2f4a6c8e0b2d4f6a8c0e2b4d6f8a1c", | |
| 2378 | + | "merged_by": null, | |
| 2379 | + | "merged_at": null, | |
| 2380 | + | "superseded_by": null, | |
| 2381 | + | "check_status": "running", | |
| 2382 | + | "files": [ | |
| 2383 | + | { | |
| 2384 | + | "path": "src/main.rs", | |
| 2385 | + | "additions": 6, | |
| 2386 | + | "deletions": 2 | |
| 2387 | + | } | |
| 2388 | + | ], | |
| 2389 | + | "assignees": [], | |
| 2390 | + | "reviewers": [ | |
| 2391 | + | "ana" | |
| 2392 | + | ], | |
| 2393 | + | "labels": [], | |
| 2394 | + | "milestone": null, | |
| 2395 | + | "base": "main", | |
| 2396 | + | "author": { | |
| 2397 | + | "id": "usr_01kkntcg1eeb98j62xjm7eh09p", | |
| 2398 | + | "username": "syntaqx", | |
| 2399 | + | "display_username": "syntaqx", | |
| 2400 | + | "kind": "user", | |
| 2401 | + | "verified": false, | |
| 2402 | + | "workspaces": [] | |
| 2403 | + | }, | |
| 2404 | + | "requested_by": null, | |
| 2405 | + | "created_at": "2026-10-01T18:20:02.117Z", | |
| 2406 | + | "updated_at": "2026-10-01T18:44:09.310Z", | |
| 2407 | + | "auto_merge": null, | |
| 2408 | + | "closes": [ | |
| 2409 | + | 12 | |
| 2410 | + | ] | |
| 2411 | + | } | |
| 2412 | + | }, | |
| 2413 | + | "delete_pull_request_head_branch": { | |
| 2414 | + | "response": { | |
| 2415 | + | "id": "pr_01m43smh3vexsr5pmp60qwv0vs", | |
| 2416 | + | "repo_id": "rep_01m3m5q6p0e2qaw6mmjahk0qrr", | |
| 2417 | + | "number": 14, | |
| 2418 | + | "issue": 12, | |
| 2419 | + | "title": "Greeting should name the caller", | |
| 2420 | + | "body": "Reads a name from the first argument. `hello ana` prints \"Hello, ana!\".", | |
| 2421 | + | "agent": "claude-code", | |
| 2422 | + | "runtime": "external", | |
| 2423 | + | "status": "merged", | |
| 2424 | + | "fork": null, | |
| 2425 | + | "fork_repo_id": null, | |
| 2426 | + | "branch": "greet-caller", | |
| 2427 | + | "head_commit": "9f2c4e1a7b3d5f60812a4c6e8b0d2f4a6c8e0b13", | |
| 2428 | + | "merge_base": "3a7e9c1b5d2f4a6c8e0b2d4f6a8c0e2b4d6f8a1c", | |
| 2429 | + | "merged_by": "syntaqx", | |
| 2430 | + | "merged_at": "2026-10-01T18:52:17.093Z", | |
| 2431 | + | "superseded_by": null, | |
| 2432 | + | "check_status": "passed", | |
| 2433 | + | "files": [ | |
| 2434 | + | { | |
| 2435 | + | "path": "src/main.rs", | |
| 2436 | + | "additions": 6, | |
| 2437 | + | "deletions": 2 | |
| 2438 | + | } | |
| 2439 | + | ], | |
| 2440 | + | "assignees": [], | |
| 2441 | + | "reviewers": [ | |
| 2442 | + | "ana" | |
| 2443 | + | ], | |
| 2444 | + | "labels": [], | |
| 2445 | + | "milestone": null, | |
| 2446 | + | "base": "main", | |
| 2447 | + | "author": { | |
| 2448 | + | "id": "usr_01kkntcg1eeb98j62xjm7eh09p", | |
| 2449 | + | "username": "syntaqx", | |
| 2450 | + | "display_username": "syntaqx", | |
| 2451 | + | "kind": "user", | |
| 2452 | + | "verified": false, | |
| 2453 | + | "workspaces": [] | |
| 2454 | + | }, | |
| 2455 | + | "requested_by": null, | |
| 2456 | + | "created_at": "2026-10-01T18:20:02.117Z", | |
| 2457 | + | "updated_at": "2026-10-01T18:53:02.415Z", | |
| 2458 | + | "merge_method": "squash", | |
| 2459 | + | "merge_commit": "c41d7e2a9b0f3e5d7c9a1b3d5f7e9c0a2b4d6f81", | |
| 2460 | + | "closes": [ | |
| 2461 | + | 12 | |
| 2462 | + | ], | |
| 2463 | + | "head_branch_deleted": true | |
| 2464 | + | }, | |
| 2465 | + | "notes": "An open pull request answers `409`: close or merge it first. `head_branch_deleted` is then `true`; [restore it](/reference/api/pull-requests/restore-pull-request-head-branch/) at `head_commit`." | |
| 2466 | + | }, | |
| 2467 | + | "restore_pull_request_head_branch": { | |
| 2468 | + | "response": { | |
| 2469 | + | "id": "pr_01m43smh3vexsr5pmp60qwv0vs", | |
| 2470 | + | "repo_id": "rep_01m3m5q6p0e2qaw6mmjahk0qrr", | |
| 2471 | + | "number": 14, | |
| 2472 | + | "issue": 12, | |
| 2473 | + | "title": "Greeting should name the caller", | |
| 2474 | + | "body": "Reads a name from the first argument. `hello ana` prints \"Hello, ana!\".", | |
| 2475 | + | "agent": "claude-code", | |
| 2476 | + | "runtime": "external", | |
| 2477 | + | "status": "merged", | |
| 2478 | + | "fork": null, | |
| 2479 | + | "fork_repo_id": null, | |
| 2480 | + | "branch": "greet-caller", | |
| 2481 | + | "head_commit": "9f2c4e1a7b3d5f60812a4c6e8b0d2f4a6c8e0b13", | |
| 2482 | + | "merge_base": "3a7e9c1b5d2f4a6c8e0b2d4f6a8c0e2b4d6f8a1c", | |
| 2483 | + | "merged_by": "syntaqx", | |
| 2484 | + | "merged_at": "2026-10-01T18:52:17.093Z", | |
| 2485 | + | "superseded_by": null, | |
| 2486 | + | "check_status": "passed", | |
| 2487 | + | "files": [ | |
| 2488 | + | { | |
| 2489 | + | "path": "src/main.rs", | |
| 2490 | + | "additions": 6, | |
| 2491 | + | "deletions": 2 | |
| 2492 | + | } | |
| 2493 | + | ], | |
| 2494 | + | "assignees": [], | |
| 2495 | + | "reviewers": [ | |
| 2496 | + | "ana" | |
| 2497 | + | ], | |
| 2498 | + | "labels": [], | |
| 2499 | + | "milestone": null, | |
| 2500 | + | "base": "main", | |
| 2501 | + | "author": { | |
| 2502 | + | "id": "usr_01kkntcg1eeb98j62xjm7eh09p", | |
| 2503 | + | "username": "syntaqx", | |
| 2504 | + | "display_username": "syntaqx", | |
| 2505 | + | "kind": "user", | |
| 2506 | + | "verified": false, | |
| 2507 | + | "workspaces": [] | |
| 2508 | + | }, | |
| 2509 | + | "requested_by": null, | |
| 2510 | + | "created_at": "2026-10-01T18:20:02.117Z", | |
| 2511 | + | "updated_at": "2026-10-02T09:10:44.902Z", | |
| 2512 | + | "merge_method": "squash", | |
| 2513 | + | "merge_commit": "c41d7e2a9b0f3e5d7c9a1b3d5f7e9c0a2b4d6f81", | |
| 2514 | + | "closes": [ | |
| 2515 | + | 12 | |
| 2516 | + | ], | |
| 2517 | + | "head_branch_deleted": false | |
| 2518 | + | }, | |
| 2519 | + | "notes": "The branch is made again at `head_commit`. A branch of that name that exists again answers `409`." | |
| 2520 | + | }, | |
| 2521 | + | "list_repo_commit_comments": { | |
| 2522 | + | "query": { | |
| 2523 | + | "page": 1 | |
| 2524 | + | }, | |
| 2525 | + | "response": [ | |
| 2526 | + | { | |
| 2527 | + | "id": "ccm_01m4a2s1b5c7e9g3k6m0p4s8v2", | |
| 2528 | + | "repo_id": "rep_01m3m5q6p0e2qaw6mmjahk0qrr", | |
| 2529 | + | "commit": "9f2c4e1a7b3d5f60812a4c6e8b0d2f4a6c8e0b13", | |
| 2530 | + | "path": null, | |
| 2531 | + | "line": null, | |
| 2532 | + | "author": "syntaqx", | |
| 2533 | + | "body": "Released as v0.4.0.", | |
| 2534 | + | "created_at": "2026-10-02T08:30:57.664Z", | |
| 2535 | + | "updated_at": null | |
| 2536 | + | }, | |
| 2537 | + | { | |
| 2538 | + | "id": "ccm_01m4a2r7k3d9f1h5j8n2q6t0w4", | |
| 2539 | + | "repo_id": "rep_01m3m5q6p0e2qaw6mmjahk0qrr", | |
| 2540 | + | "commit": "9f2c4e1a7b3d5f60812a4c6e8b0d2f4a6c8e0b13", | |
| 2541 | + | "path": "src/main.rs", | |
| 2542 | + | "line": 12, | |
| 2543 | + | "author": "ana", | |
| 2544 | + | "body": "Should an empty name fall back to \"world\"?", | |
| 2545 | + | "created_at": "2026-10-01T19:02:11.208Z", | |
| 2546 | + | "updated_at": null | |
| 2547 | + | } | |
| 2548 | + | ], | |
| 2549 | + | "notes": "Newest first, 30 a page. A comment with `path` and `line` is on that line of the file as its `commit` left it; one without is on the whole commit." | |
| 2550 | + | }, | |
| 2551 | + | "list_commit_comments": { | |
| 2552 | + | "params": { | |
| 2553 | + | "owner": "flagon-io", | |
| 2554 | + | "name": "hello", | |
| 2555 | + | "sha": "9f2c4e1a7b3d5f60812a4c6e8b0d2f4a6c8e0b13" | |
| 2556 | + | }, | |
| 2557 | + | "response": [ | |
| 2558 | + | { | |
| 2559 | + | "id": "ccm_01m4a2r7k3d9f1h5j8n2q6t0w4", | |
| 2560 | + | "repo_id": "rep_01m3m5q6p0e2qaw6mmjahk0qrr", | |
| 2561 | + | "commit": "9f2c4e1a7b3d5f60812a4c6e8b0d2f4a6c8e0b13", | |
| 2562 | + | "path": "src/main.rs", | |
| 2563 | + | "line": 12, | |
| 2564 | + | "author": "ana", | |
| 2565 | + | "body": "Should an empty name fall back to \"world\"?", | |
| 2566 | + | "created_at": "2026-10-01T19:02:11.208Z", | |
| 2567 | + | "updated_at": null | |
| 2568 | + | } | |
| 2569 | + | ], | |
| 2570 | + | "notes": "Oldest first." | |
| 2106 | 2571 | }, | |
| 2572 | + | "create_commit_comment": { | |
| 2573 | + | "params": { | |
| 2574 | + | "owner": "flagon-io", | |
| 2575 | + | "name": "hello", | |
| 2576 | + | "sha": "9f2c4e1a7b3d5f60812a4c6e8b0d2f4a6c8e0b13" | |
| 2577 | + | }, | |
| 2578 | + | "request": { | |
| 2579 | + | "body": "Should an empty name fall back to \"world\"?", | |
| 2580 | + | "path": "src/main.rs", | |
| 2581 | + | "line": 12 | |
| 2582 | + | }, | |
| 2583 | + | "response": { | |
| 2584 | + | "id": "ccm_01m4a2r7k3d9f1h5j8n2q6t0w4", | |
| 2585 | + | "repo_id": "rep_01m3m5q6p0e2qaw6mmjahk0qrr", | |
| 2586 | + | "commit": "9f2c4e1a7b3d5f60812a4c6e8b0d2f4a6c8e0b13", | |
| 2587 | + | "path": "src/main.rs", | |
| 2588 | + | "line": 12, | |
| 2589 | + | "author": "ana", | |
| 2590 | + | "body": "Should an empty name fall back to \"world\"?", | |
| 2591 | + | "created_at": "2026-10-01T19:02:11.208Z", | |
| 2592 | + | "updated_at": null | |
| 2593 | + | }, | |
| 2594 | + | "notes": "`line` needs `path`, a file the commit changed, and counts lines of that file as the commit left it. A repository with `allow_commit_comments` off answers `403`; a commit it does not have, `404`. See [Commit comments](/guides/pull-requests/#commit-comments)." | |
| 2595 | + | }, | |
| 2596 | + | "update_commit_comment": { | |
| 2597 | + | "params": { | |
| 2598 | + | "owner": "flagon-io", | |
| 2599 | + | "name": "hello", | |
| 2600 | + | "comment_id": "ccm_01m4a2r7k3d9f1h5j8n2q6t0w4" | |
| 2601 | + | }, | |
| 2602 | + | "request": { | |
| 2603 | + | "body": "Should an empty name fall back to \"world\"? The README says it does." | |
| 2604 | + | }, | |
| 2605 | + | "response": { | |
| 2606 | + | "id": "ccm_01m4a2r7k3d9f1h5j8n2q6t0w4", | |
| 2607 | + | "repo_id": "rep_01m3m5q6p0e2qaw6mmjahk0qrr", | |
| 2608 | + | "commit": "9f2c4e1a7b3d5f60812a4c6e8b0d2f4a6c8e0b13", | |
| 2609 | + | "path": "src/main.rs", | |
| 2610 | + | "line": 12, | |
| 2611 | + | "author": "ana", | |
| 2612 | + | "body": "Should an empty name fall back to \"world\"? The README says it does.", | |
| 2613 | + | "created_at": "2026-10-01T19:02:11.208Z", | |
| 2614 | + | "updated_at": "2026-10-01T19:05:40.117Z" | |
| 2615 | + | }, | |
| 2616 | + | "notes": "Its author only." | |
| 2617 | + | }, | |
| 2618 | + | "delete_commit_comment": { | |
| 2619 | + | "params": { | |
| 2620 | + | "owner": "flagon-io", | |
| 2621 | + | "name": "hello", | |
| 2622 | + | "comment_id": "ccm_01m4a2r7k3d9f1h5j8n2q6t0w4" | |
| 2623 | + | }, | |
| 2624 | + | "notes": "Answers `204` with no body. Its author, or someone with the Maintain role or higher." | |
| 2625 | + | }, | |
| 2107 | 2626 | "close_pull_request": { | |
| 2108 | 2627 | "response": { | |
| 2109 | 2628 | "id": "pr_01m43smh3vexsr5pmp60qwv0vs", | |
| ⋯ | |||
| 11411 | 11930 | "access_level": "none" | |
| 11412 | 11931 | } | |
| 11413 | 11932 | }, | |
| 11933 | + | "get_actions_permissions": { | |
| 11934 | + | "params": { | |
| 11935 | + | "owner": "ada", | |
| 11936 | + | "name": "hello" | |
| 11937 | + | }, | |
| 11938 | + | "response": { | |
| 11939 | + | "enabled": false, | |
| 11940 | + | "enabled_chosen": false, | |
| 11941 | + | "allowed_actions": "all" | |
| 11942 | + | }, | |
| 11943 | + | "notes": "A fork's workflows are off until someone turns them on: here `ada/hello` is a fork nobody has chosen for yet. See [Forks](/concepts/forks/)." | |
| 11944 | + | }, | |
| 11945 | + | "set_actions_permissions": { | |
| 11946 | + | "params": { | |
| 11947 | + | "owner": "ada", | |
| 11948 | + | "name": "hello" | |
| 11949 | + | }, | |
| 11950 | + | "request": { | |
| 11951 | + | "enabled": true | |
| 11952 | + | }, | |
| 11953 | + | "response": { | |
| 11954 | + | "enabled": true, | |
| 11955 | + | "enabled_chosen": true, | |
| 11956 | + | "allowed_actions": "all" | |
| 11957 | + | }, | |
| 11958 | + | "notes": "Needs the Admin role (`403` otherwise); a workflow job's own token is refused. Turning workflows on starts nothing by itself. See [Turn workflows off](/guides/actions/#turn-workflows-on-or-off)." | |
| 11959 | + | }, | |
| 11414 | 11960 | "set_actions_access": { | |
| 11415 | 11961 | "params": { | |
| 11416 | 11962 | "owner": "flagon-io", | |
| 154 | 154 | } | |
| 155 | 155 | match op { | |
| 156 | 156 | Op::GetWorkspace | Op::CreateWorkspace | Op::UpdateWorkspace => through::<g1t_contracts::identity::Workspace>(op, sent), | |
| 157 | − | Op::ListRepos => through::<Vec<repos::Repo>>(op, sent), | |
| 157 | + | Op::ListRepos | Op::ListForks => through::<Vec<repos::Repo>>(op, sent), | |
| 158 | + | Op::SyncFork => through::<repos::SyncedFork>(op, sent), | |
| 158 | 159 | Op::Search => through::<search::SearchResults>(op, sent), | |
| 159 | 160 | Op::GetRepo | |
| 160 | 161 | | Op::CreateRepo | |
| 161 | 162 | | Op::UpdateRepo | |
| 162 | 163 | | Op::TransferRepo | |
| 164 | + | | Op::ForkRepo | |
| 165 | + | | Op::DetachFork | |
| 163 | 166 | | Op::RenameRepo | |
| 164 | 167 | | Op::RenameBranch | |
| 165 | 168 | | Op::ArchiveRepo | |
| ⋯ | |||
| 193 | 196 | | Op::ReopenPullRequest | |
| 194 | 197 | | Op::ConvertPullRequestToDraft | |
| 195 | 198 | | Op::MergePullRequest | |
| 199 | + | | Op::EnablePullRequestAutoMerge | |
| 200 | + | | Op::DisablePullRequestAutoMerge | |
| 201 | + | | Op::DeletePullRequestHeadBranch | |
| 202 | + | | Op::RestorePullRequestHeadBranch | |
| 196 | 203 | | Op::RequestReviewers | |
| 197 | 204 | | Op::RemoveRequestedReviewers => { | |
| 198 | 205 | through::<work::Pull>(op, sent) | |
| 199 | 206 | } | |
| 207 | + | Op::UpdatePullRequestBranch => through::<repos::PullBranchUpdate>(op, sent), | |
| 208 | + | Op::GetPullRequestMergeOptions => through::<work::MergeChoices>(op, sent), | |
| 209 | + | Op::ListRepoCommitComments | Op::ListCommitComments => through::<Vec<work::CommitComment>>(op, sent), | |
| 210 | + | Op::CreateCommitComment | Op::UpdateCommitComment => through::<work::CommitComment>(op, sent), | |
| 200 | 211 | Op::ListWorkflows => through::<Vec<actions::Workflow>>(op, sent), | |
| 201 | 212 | Op::GetWorkflow => through::<actions::WorkflowDetail>(op, sent), | |
| 202 | 213 | Op::ListWorkflowRuns => through::<Vec<actions::WorkflowRun>>(op, sent), | |
| 364 | 364 | route("GET", "/repos/:owner/:name/zipball/:ref", Op::Archives(ArchiveOp::DownloadZipball), &[]), | |
| 365 | 365 | route("PATCH", "/repos/:owner/:name", Op::UpdateRepo, &[]), | |
| 366 | 366 | route("POST", "/repos/:owner/:name/transfer", Op::TransferRepo, &[]), | |
| 367 | + | route("POST", "/repos/:owner/:name/forks", Op::ForkRepo, &[]), | |
| 368 | + | route("GET", "/repos/:owner/:name/forks", Op::ListForks, &[("limit", "limit")]), | |
| 369 | + | route("POST", "/repos/:owner/:name/merge-upstream", Op::SyncFork, &[]), | |
| 370 | + | route("POST", "/repos/:owner/:name/detach", Op::DetachFork, &[]), | |
| 367 | 371 | route("DELETE", "/repos/:owner/:name", Op::DeleteRepo, &[]), | |
| 368 | 372 | route( | |
| 369 | 373 | "GET", | |
| ⋯ | |||
| 498 | 502 | ), | |
| 499 | 503 | route("GET", "/repos/:owner/:name/actions/permissions/access", Op::Protection(ProtectionOp::GetActionsAccess), &[]), | |
| 500 | 504 | route("PUT", "/repos/:owner/:name/actions/permissions/access", Op::Protection(ProtectionOp::SetActionsAccess), &[]), | |
| 505 | + | route("GET", "/repos/:owner/:name/actions/permissions", Op::Protection(ProtectionOp::GetActionsPermissions), &[]), | |
| 506 | + | route("PUT", "/repos/:owner/:name/actions/permissions", Op::Protection(ProtectionOp::SetActionsPermissions), &[]), | |
| 501 | 507 | route("POST", "/repos/:owner/:name/dispatches", Op::Protection(ProtectionOp::CreateRepositoryDispatch), &[]), | |
| 502 | 508 | route( | |
| 503 | 509 | "GET", | |
| ⋯ | |||
| 1121 | 1127 | Op::MergePullRequest, | |
| 1122 | 1128 | &[], | |
| 1123 | 1129 | ), | |
| 1130 | + | route("GET", "/repos/:owner/:name/pulls/:number/merge-options", Op::GetPullRequestMergeOptions, &[]), | |
| 1131 | + | route("PUT", "/repos/:owner/:name/pulls/:number/update-branch", Op::UpdatePullRequestBranch, &[]), | |
| 1132 | + | route("PUT", "/repos/:owner/:name/pulls/:number/auto-merge", Op::EnablePullRequestAutoMerge, &[]), | |
| 1133 | + | route("DELETE", "/repos/:owner/:name/pulls/:number/auto-merge", Op::DisablePullRequestAutoMerge, &[]), | |
| 1134 | + | route("DELETE", "/repos/:owner/:name/pulls/:number/head-branch", Op::DeletePullRequestHeadBranch, &[]), | |
| 1135 | + | route("POST", "/repos/:owner/:name/pulls/:number/head-branch/restore", Op::RestorePullRequestHeadBranch, &[]), | |
| 1136 | + | // Commit comments: GitHub's addresses. | |
| 1137 | + | route("GET", "/repos/:owner/:name/comments", Op::ListRepoCommitComments, &[("page", "page")]), | |
| 1138 | + | route("PATCH", "/repos/:owner/:name/comments/:comment_id", Op::UpdateCommitComment, &[]), | |
| 1139 | + | route("DELETE", "/repos/:owner/:name/comments/:comment_id", Op::DeleteCommitComment, &[]), | |
| 1140 | + | route("GET", "/repos/:owner/:name/commits/:sha/comments", Op::ListCommitComments, &[]), | |
| 1141 | + | route("POST", "/repos/:owner/:name/commits/:sha/comments", Op::CreateCommitComment, &[]), | |
| 1124 | 1142 | // Artifacts mode's docs, slides, designs and dashboards. Search comes | |
| 1125 | 1143 | // before an artifact by id, which it would otherwise match. | |
| 1126 | 1144 | route("GET", "/workspaces/:workspace/artifacts", Op::Folios(FoliosOp::List), &[("tab", "tab"), ("kind", "kind"), ("space", "space"), ("project", "project"), ("q", "q"), ("state", "state"), ("cursor", "cursor"), ("limit", "limit")]), | |
| ⋯ | |||
| 1148 | 1166 | pub fn no_content(&self) -> bool { | |
| 1149 | 1167 | matches!( | |
| 1150 | 1168 | self.op, | |
| 1151 | − | Op::DeleteComment | Op::Caches(CachesOp::DeleteCache) | |
| 1169 | + | Op::DeleteComment | Op::DeleteCommitComment | Op::Caches(CachesOp::DeleteCache) | |
| 1152 | 1170 | ) | |
| 1153 | 1171 | } | |
| 1154 | 1172 | ||
| ⋯ | |||
| 1510 | 1528 | } | |
| 1511 | 1529 | ||
| 1512 | 1530 | #[test] | |
| 1531 | + | fn merging_settings_and_commit_comments_have_their_addresses() { | |
| 1532 | + | let op = |method: &str, path: &str| resolve(method, path, &[], Value::Null).unwrap().0.op; | |
| 1533 | + | assert_eq!(op("GET", "/repos/acme/web/pulls/9/merge-options"), Op::GetPullRequestMergeOptions); | |
| 1534 | + | assert_eq!(op("PUT", "/repos/acme/web/pulls/9/update-branch"), Op::UpdatePullRequestBranch); | |
| 1535 | + | assert_eq!(op("PUT", "/repos/acme/web/pulls/9/auto-merge"), Op::EnablePullRequestAutoMerge); | |
| 1536 | + | assert_eq!(op("DELETE", "/repos/acme/web/pulls/9/auto-merge"), Op::DisablePullRequestAutoMerge); | |
| 1537 | + | assert_eq!(op("DELETE", "/repos/acme/web/pulls/9/head-branch"), Op::DeletePullRequestHeadBranch); | |
| 1538 | + | assert_eq!(op("POST", "/repos/acme/web/pulls/9/head-branch/restore"), Op::RestorePullRequestHeadBranch); | |
| 1539 | + | let (route, input) = resolve("GET", "/repos/acme/web/comments", &[("page".to_owned(), "2".to_owned())], Value::Null).unwrap(); | |
| 1540 | + | assert_eq!(route.op, Op::ListRepoCommitComments); | |
| 1541 | + | assert_eq!(input, json!({ "page": "2", "repo": "acme/web" })); | |
| 1542 | + | let (route, input) = | |
| 1543 | + | resolve("POST", "/repos/acme/web/commits/9f2c4e1/comments", &[], json!({ "body": "Why?", "path": "a.rs", "line": 3 })).unwrap(); | |
| 1544 | + | assert_eq!(route.op, Op::CreateCommitComment); | |
| 1545 | + | assert_eq!(input, json!({ "body": "Why?", "path": "a.rs", "line": 3, "sha": "9f2c4e1", "repo": "acme/web" })); | |
| 1546 | + | assert_eq!(op("GET", "/repos/acme/web/commits/9f2c4e1/comments"), Op::ListCommitComments); | |
| 1547 | + | // Still a commit's statuses, beside its comments. | |
| 1548 | + | assert_eq!(op("GET", "/repos/acme/web/commits/main/statuses"), Op::Checks(ChecksOp::ListCommitStatuses)); | |
| 1549 | + | let (route, input) = resolve("PATCH", "/repos/acme/web/comments/cc_1", &[], json!({ "body": "Better" })).unwrap(); | |
| 1550 | + | assert_eq!(route.op, Op::UpdateCommitComment); | |
| 1551 | + | assert_eq!(input, json!({ "body": "Better", "comment_id": "cc_1", "repo": "acme/web" })); | |
| 1552 | + | let (route, _) = resolve("DELETE", "/repos/acme/web/comments/cc_1", &[], Value::Null).unwrap(); | |
| 1553 | + | assert!(route.op == Op::DeleteCommitComment && route.no_content()); | |
| 1554 | + | // An issue's comments keep theirs. | |
| 1555 | + | assert_eq!(op("PATCH", "/repos/acme/web/issues/comments/cmt_1"), Op::EditComment); | |
| 1556 | + | } | |
| 1557 | + | ||
| 1558 | + | #[test] | |
| 1513 | 1559 | fn pull_requests_reopen_and_turn_draft_and_comments_are_named_by_id() { | |
| 1514 | 1560 | let op = |method: &str, path: &str| resolve(method, path, &[], Value::Null).unwrap().0.op; | |
| 1515 | 1561 | assert_eq!(op("POST", "/repos/acme/web/pulls/9/reopen"), Op::ReopenPullRequest); | |
| 70 | 70 | Tool { | |
| 71 | 71 | name: "repository", | |
| 72 | 72 | title: "Repositories", | |
| 73 | − | description: "Repositories: find, read and create them, change their settings and rulesets (what may happen to branches and tags, and what a pull request needs to merge), check their CODEOWNERS file, manage their labels and milestones, see and dismiss their security alerts (secrets and vulnerable dependencies), read what their default branch says (languages, contributors, license), star them and publish releases. Name one as \"owner/name\". Deleting, transferring and changing visibility need `confirm`.", | |
| 73 | + | description: "Repositories: find, read and create them, change their settings and rulesets (what may happen to branches and tags, and what a pull request needs to merge), check their CODEOWNERS file, manage their labels and milestones, see and dismiss their security alerts (secrets and vulnerable dependencies), read what their default branch says (languages, contributors, license), star them, publish releases and comment on their commits. Name one as \"owner/name\". Deleting, transferring and changing visibility need `confirm`.", | |
| 74 | 74 | default_action: None, | |
| 75 | 75 | actions: &[ | |
| 76 | 76 | a("list", Op::ListRepos, "Repositories you can see"), | |
| 77 | 77 | a("get", Op::GetRepo, "One repository"), | |
| 78 | 78 | a("commits", Op::ListCommits, "A branch's commits with their messages (why each was made); with path, the commit that last changed it"), | |
| 79 | + | a("commit_comments", Op::ListCommitComments, "A commit's comments, on it or on lines of its files"), | |
| 80 | + | a("list_commit_comments", Op::ListRepoCommitComments, "All its commit comments, newest first"), | |
| 81 | + | a("comment_on_commit", Op::CreateCommitComment, "Comment on a commit; path and line for one line of a file it changed"), | |
| 82 | + | a("edit_commit_comment", Op::UpdateCommitComment, "Change your commit comment's text"), | |
| 83 | + | a("delete_commit_comment", Op::DeleteCommitComment, "Delete a commit comment: your own, or any with the Maintain role"), | |
| 79 | 84 | a("contents", Op::GetContents, "A file's text, or a directory's entries and README, at a branch, tag or commit"), | |
| 80 | 85 | a("tarball", Op::Archives(ArchiveOp::DownloadTarball), "A 10-minute link to a branch, tag or commit as a .tar.gz"), | |
| 81 | 86 | a("zipball", Op::Archives(ArchiveOp::DownloadZipball), "A 10-minute link to a branch, tag or commit as a .zip"), | |
| 82 | 87 | a("create", Op::CreateRepo, "Create one, empty or copied from a public git URL"), | |
| 83 | − | a("update", Op::UpdateRepo, "Change description, website, topics, default branch, protection"), | |
| 88 | + | a("update", Op::UpdateRepo, "Change description, website, topics, default branch, protection, forking"), | |
| 84 | 89 | a("get_settings", Op::GetRepoSettings, "How pull requests merge, and the default branch's protection as its rules stack"), | |
| 85 | 90 | a("update_settings", Op::UpdateRepoSettings, "Change how pull requests merge and the default branch protection ruleset"), | |
| 86 | 91 | a("check_names", Op::ListCheckNames, "Check names reported lately, to require in a ruleset"), | |
| ⋯ | |||
| 123 | 128 | a("delete_branch", Op::DeleteBranch, "Delete a branch; make it again at its commit to restore it"), | |
| 124 | 129 | a("rename", Op::RenameRepo, "Rename it; old addresses redirect"), | |
| 125 | 130 | a("transfer", Op::TransferRepo, "Move it to another workspace you own"), | |
| 131 | + | a("fork", Op::ForkRepo, "Fork it into a workspace: its code and description, linked to it"), | |
| 132 | + | a("forks", Op::ListForks, "Its forks you can see"), | |
| 133 | + | a("sync_fork", Op::SyncFork, "Bring a fork's branch up to date with its parent"), | |
| 134 | + | a("detach_fork", Op::DetachFork, "Make a fork an ordinary repository, no longer linked to its parent"), | |
| 126 | 135 | a("archive", Op::ArchiveRepo, "Make it read-only"), | |
| 127 | 136 | a("unarchive", Op::UnarchiveRepo, "Make it writable again"), | |
| 128 | 137 | a("set_visibility", Op::SetRepoVisibility, "Make it public or private"), | |
| ⋯ | |||
| 166 | 175 | Tool { | |
| 167 | 176 | name: "pull_request", | |
| 168 | 177 | title: "Pull requests", | |
| 169 | − | description: "Pull requests: start a change for an issue, record your session, mark it ready, ask people and teams to review, review and merge. Read `overlaps` and `behind` on `get` before going far, and `code_owners` for whose approval it needs.", | |
| 178 | + | description: "Pull requests: start a change for an issue, record your session, mark it ready, ask people and teams to review, review, update its branch, and merge (by merge, squash, rebase or fast_forward) now or with auto-merge. Read `overlaps` and `behind` on `get` before going far, and `code_owners` for whose approval it needs.", | |
| 170 | 179 | default_action: None, | |
| 171 | 180 | actions: &[ | |
| 172 | 181 | a("list", Op::ListPullRequests, "Pull requests on a repository, newest first"), | |
| ⋯ | |||
| 185 | 194 | a("review", Op::ReviewPullRequest, "Approve or request changes"), | |
| 186 | 195 | a("close", Op::ClosePullRequest, "Close without merging"), | |
| 187 | 196 | a("reopen", Op::ReopenPullRequest, "Reopen a closed one"), | |
| 188 | − | a("merge", Op::MergePullRequest, "Land it, or join the merge queue"), | |
| 197 | + | a("merge", Op::MergePullRequest, "Land it (merge, squash, rebase or fast_forward), or join the merge queue"), | |
| 198 | + | a("merge_options", Op::GetPullRequestMergeOptions, "How it may merge now: methods, default messages, whether it is behind"), | |
| 199 | + | a("update_branch", Op::UpdatePullRequestBranch, "Bring its branch up to date with its base, by merge or rebase"), | |
| 200 | + | a("enable_auto_merge", Op::EnablePullRequestAutoMerge, "Merge it on its own once its checks, reviews and rules allow"), | |
| 201 | + | a("disable_auto_merge", Op::DisablePullRequestAutoMerge, "Turn its auto-merge off"), | |
| 202 | + | a("delete_branch", Op::DeletePullRequestHeadBranch, "Delete a merged or closed one's branch; restorable"), | |
| 203 | + | a("restore_branch", Op::RestorePullRequestHeadBranch, "Make its deleted branch again at its head"), | |
| 189 | 204 | a("merge_queue", Op::GetMergeQueue, "The repository's merge queue"), | |
| 190 | 205 | ], | |
| 191 | 206 | }, | |
| ⋯ | |||
| 261 | 276 | a("set_approval_policy", Op::Protection(ProtectionOp::SetForkPrApproval), "Set which pull requests' runs wait for approval"), | |
| 262 | 277 | a("get_access", Op::Protection(ProtectionOp::GetActionsAccess), "Which repositories may use this one's actions and workflows"), | |
| 263 | 278 | a("set_access", Op::Protection(ProtectionOp::SetActionsAccess), "Let the workspace's private repositories use them, or not"), | |
| 279 | + | a("get_enabled", Op::Protection(ProtectionOp::GetActionsPermissions), "Whether its workflows run at all; off for a fork until turned on"), | |
| 280 | + | a("set_enabled", Op::Protection(ProtectionOp::SetActionsPermissions), "Turn its workflows on or off"), | |
| 264 | 281 | a("repository_dispatch", Op::Protection(ProtectionOp::CreateRepositoryDispatch), "Start repository_dispatch workflows with an event"), | |
| 265 | 282 | a("get_workspace_permissions", Op::Protection(ProtectionOp::GetWorkspaceWorkflowPermissions), "A workspace's default and maximum token permissions"), | |
| 266 | 283 | a("set_workspace_permissions", Op::Protection(ProtectionOp::SetWorkspaceWorkflowPermissions), "Set them, and whether jobs may open pull requests"), | |
| ⋯ | |||
| 634 | 651 | | Op::DeleteTeam | |
| 635 | 652 | | Op::RemoveTeamRepo | |
| 636 | 653 | | Op::MergePullRequest | |
| 654 | + | | Op::DeleteCommitComment | |
| 637 | 655 | | Op::RemoveRunner | |
| 638 | 656 | | Op::DeleteRunnerGroup | |
| 639 | 657 | | Op::UpdateRunnerSettings | |
| 182 | 182 | { label: 'The merge queue', slug: 'guides/merge-queue' }, | |
| 183 | 183 | { label: 'Rules', slug: 'guides/rules' }, | |
| 184 | 184 | { label: 'CODEOWNERS', slug: 'guides/codeowners' }, | |
| 185 | − | { label: 'Forks and branches', slug: 'concepts/forks' }, | |
| 185 | + | { label: 'Forks and working copies', slug: 'concepts/forks' }, | |
| 186 | 186 | ], | |
| 187 | 187 | }, | |
| 188 | 188 | { | |
| ⋯ | |||
| 197 | 197 | { label: 'People and teams', slug: 'guides/people-and-teams' }, | |
| 198 | 198 | { label: 'Managing a repository', slug: 'guides/managing-repositories' }, | |
| 199 | 199 | { label: 'Releases', slug: 'guides/releases' }, | |
| 200 | + | { label: 'Forking a repository', slug: 'guides/forking-repositories' }, | |
| 200 | 201 | { label: 'Transferring a repository', slug: 'guides/transferring-repositories' }, | |
| 201 | 202 | { label: 'Audit log', slug: 'guides/audit-log' }, | |
| 202 | 203 | { label: 'Usage and billing', slug: 'guides/usage-and-billing' }, | |
| 130 | 130 | ||
| 131 | 131 | ## Pull requests and forks | |
| 132 | 132 | ||
| 133 | − | ### Forks are only for pull requests | |
| 133 | + | ### A fork's storage counts in full | |
| 134 | 134 | ||
| 135 | − | You can't fork a repository into your own workspace. On g1t, a fork is a | |
| 136 | − | pull request's own working copy, made when the pull request is opened. | |
| 135 | + | A fork counts toward its workspace's private repository storage at its | |
| 136 | + | whole size, even where it is the same as the repository it came from. | |
| 137 | 137 | ||
| 138 | − | - **Why.** We built forks to isolate each pull request's work, so many | |
| 139 | − | people and agents can work at once. | |
| 140 | − | See [Forks and branches](/concepts/forks/). | |
| 141 | − | - **Instead.** To contribute, open a pull request: it gets its own fork. To | |
| 142 | − | start a copy of your own, clone the repository and push it to a new one | |
| 143 | − | in your workspace; pushing creates it. | |
| 144 | − | - **Status.** Not scheduled. | |
| 138 | + | - **Why.** Cloudflare has not documented whether a fork shares stored | |
| 139 | + | objects with its source or copies them, so g1t never assumes a fork is | |
| 140 | + | cheaper than a copy. | |
| 141 | + | - **Instead.** Fork with **Copy the default branch only** (the default) | |
| 142 | + | unless you need every branch and tag, and delete forks you no longer use. | |
| 143 | + | - **Status.** Depends on Cloudflare. See | |
| 144 | + | [What a fork costs](/concepts/forks/#what-a-fork-costs). | |
| 145 | 145 | ||
| 146 | − | ### Pull request forks are removed a day after they close | |
| 146 | + | ### Pull requests' working copies are removed a day after they close | |
| 147 | 147 | ||
| 148 | − | A day after a pull request merges or closes, its fork's git data is | |
| 149 | − | removed. The pull request's changes stay readable: its head is kept in the | |
| 150 | − | repository as `refs/pull/<pull request id>/head`. Pushing to the fork, or | |
| 151 | − | reopening the pull request, makes the fork again from there. | |
| 148 | + | A day after a pull request merges or closes, its working copy's git data | |
| 149 | + | is removed. The pull request's changes stay readable: its head is kept in the | |
| 150 | + | repository as `refs/pull/<pull request id>/head`. Pushing to the working | |
| 151 | + | copy, or reopening the pull request, makes it again from there. | |
| 152 | 152 | ||
| 153 | 153 | - **Why.** Cloudflare has not documented whether a fork shares stored | |
| 154 | − | objects with its source or copies them, so forks are not kept longer | |
| 155 | − | than they are useful. | |
| 154 | + | objects with its source or copies them, so working copies are not | |
| 155 | + | kept longer than they are useful. | |
| 156 | 156 | - **Instead.** Nothing you need to do. To keep working on a closed pull | |
| 157 | 157 | request's change, fetch `refs/pull/<pull request id>/head` and push it | |
| 158 | 158 | to a branch. | |
| 159 | − | - **Status.** Clear fork storage rules depend on Cloudflare. | |
| 159 | + | - **Status.** Clear storage rules for copies depend on Cloudflare. | |
| 160 | 160 | ||
| 161 | 161 | ### Some dependency update options are not applied yet | |
| 162 | 162 | ||
| ⋯ | |||
| 197 | 197 | See [Conflicts](/guides/pull-requests/#conflicts). | |
| 198 | 198 | - **Status.** Planned. | |
| 199 | 199 | ||
| 200 | + | ### Large merges, squashes and rebases | |
| 201 | + | ||
| 202 | + | g1t merges, squashes, rebases and updates a pull request's branch by itself | |
| 203 | + | up to 1,000 changed files, 100 files merged line by line (each up to 1 MiB) | |
| 204 | + | and 250 commits in a rebase. Beyond them it refuses, and nothing changes. | |
| 205 | + | ||
| 206 | + | - **Instead.** Squash or merge instead of rebasing, or merge the base into | |
| 207 | + | the branch in a clone and push. See | |
| 208 | + | [Merge methods](/guides/pull-requests/#merge-methods). | |
| 209 | + | - **Status.** Not scheduled. | |
| 210 | + | ||
| 200 | 211 | ## Actions and runners | |
| 201 | 212 | ||
| 202 | 213 | ### Docker shares the job's network | |
| 143 | 143 | ## Pull request forks | |
| 144 | 144 | ||
| 145 | 145 | A pull request made for an issue gets a fork of its own | |
| 146 | − | ([Forks and branches](/concepts/forks/)). `fork_for_pull` makes it in the | |
| 146 | + | ([Forks and working copies](/concepts/forks/)). `fork_for_pull` makes it in the | |
| 147 | 147 | same namespace as its source, as `pulls--<pull request id>`, with | |
| 148 | 148 | Artifacts' own fork of the default branch, and it is served at | |
| 149 | 149 | `g1t.sh/pulls/<id>.git`. Anyone who can read the source can read the fork. | |
| ⋯ | |||
| 163 | 163 | `repos` also builds the test merge for every pull request, | |
| 164 | 164 | `refs/pull/<n>/merge`, without a sandbox, using a line-level merge on | |
| 165 | 165 | files up to 1 MiB (`test_merge.rs`). When a pull request is behind and the | |
| 166 | − | two sides changed different files, **Catch up with main** is done the same | |
| 167 | − | way (`catch_up.rs`). Testing several pull requests together is the merge | |
| 166 | + | two sides' changes do not clash, updating its branch is done the same way | |
| 167 | + | (`catch_up.rs`), by a merge or a rebase; so are merge, squash and rebase | |
| 168 | + | merges, up to 1,000 changed files, 100 files merged line by line and 250 | |
| 169 | + | commits in a rebase. Testing several pull requests together is the merge | |
| 168 | 170 | queue's job ([Work and the merge queue](/architecture/work/#the-merge-queue)). | |
| 169 | 171 | ||
| 170 | 172 | ## Backups | |
Comments
Sign in to comment.