Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh

Commit

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).

syntaqxcommitted Parentsaea0bcfb4a3d89Browse files
141 files+1864−101125 files not counted0/141 viewed
+40−2
132132 Op::ArchiveRepo,
133133 Op::UnarchiveRepo,
134134 Op::TransferRepo,
135+ Op::ForkRepo,
136+ Op::ListForks,
137+ Op::SyncFork,
138+ Op::DetachFork,
135139 Op::DeleteRepo,
136140 Op::ListDeletedRepos,
137141 Op::RestoreRepo,
144148 ],
145149 ),
146150 (
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+ (
147162 "Repository insights",
148163 "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.",
149164 &[Op::About(AboutOp::GetLanguages), Op::About(AboutOp::ListContributors), Op::About(AboutOp::GetLicense)],
372387 ),
373388 (
374389 "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.",
376391 &[
377392 Op::ListPullRequests,
378393 Op::CreatePullRequest,
384399 Op::RequestReviewers,
385400 Op::RemoveRequestedReviewers,
386401 Op::ReviewPullRequest,
402+ Op::GetPullRequestMergeOptions,
387403 Op::MergePullRequest,
404+ Op::UpdatePullRequestBranch,
405+ Op::EnablePullRequestAutoMerge,
406+ Op::DisablePullRequestAutoMerge,
407+ Op::DeletePullRequestHeadBranch,
408+ Op::RestorePullRequestHeadBranch,
388409 Op::ClosePullRequest,
389410 Op::ReopenPullRequest,
390411 Op::GetMergeQueue,
519540 Op::Protection(ProtectionOp::SetForkPrApproval),
520541 Op::Protection(ProtectionOp::GetActionsAccess),
521542 Op::Protection(ProtectionOp::SetActionsAccess),
543+ Op::Protection(ProtectionOp::GetActionsPermissions),
544+ Op::Protection(ProtectionOp::SetActionsPermissions),
522545 Op::Protection(ProtectionOp::CreateRepositoryDispatch),
523546 Op::Protection(ProtectionOp::GetWorkspaceWorkflowPermissions),
524547 Op::Protection(ProtectionOp::SetWorkspaceWorkflowPermissions),
656679 Op::DeclineInvitation => "Decline a workspace invitation",
657680 Op::RevokeWorkspaceInvite => "Revoke a workspace's invite",
658681 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",
659686 Op::RenameRepo => "Rename a repository",
660687 Op::RenameBranch => "Rename a branch",
661688 Op::CreateBranch => "Create a branch",
715742 Op::ConvertPullRequestToDraft => "Convert a pull request to a draft",
716743 Op::GetPullRequestChanges => "Get a pull request's changes",
717744 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",
718756 Op::ListEvents => "List repository events",
719757 Op::ListIntegrations => "List integrations",
720758 Op::ConnectIntegration => "Connect an integration",
845883 /// free workspace's private storage has no room for it.
846884 fn may_need_payment(op: Op) -> Option<&'static str> {
847885 match op {
848− Op::UpdateRepo | Op::SetRepoVisibility | Op::TransferRepo => Some(
886+ Op::UpdateRepo | Op::SetRepoVisibility | Op::TransferRepo | Op::ForkRepo => Some(
849887 "A free workspace's private storage has no room for this private repository.",
850888 ),
851889 _ => None,
+605−10
1313 use g1t_contracts::identity::AgentScope;
1414 use g1t_contracts::events::Event;
1515 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+};
1719 use g1t_contracts::teams::{
1820 CreateTeamArgs, DeleteTeamArgs, ListTeamsArgs, RemoveTeamMemberArgs, RemoveTeamRepoArgs, ReviewAlgorithm,
1921 ReviewAssignment, SetTeamCreationArgs, SetTeamMemberArgs, SetTeamRepoArgs, Team, TeamArgs, TeamCreation, TeamRole,
4244 use crate::rules::RulesOp;
4345 use crate::security::SecurityOp;
4446 use g1t_contracts::inbox::{Reason, Severity, WATCH_EVENTS, WatchLevel};
47+use g1t_contracts::repos::UpdateMethod;
48+use g1t_contracts::rules::MergeMethod;
4549 use g1t_contracts::work::*;
4650 use g1t_contracts::{FailureCode, Outcome, Viewer};
4751 use serde::Serialize;
142146 CreateRepo,
143147 UpdateRepo,
144148 TransferRepo,
149+ ForkRepo,
150+ ListForks,
151+ SyncFork,
152+ DetachFork,
145153 RenameRepo,
146154 RenameBranch,
147155 CreateBranch,
197205 ReopenPullRequest,
198206 GetPullRequestChanges,
199207 MergePullRequest,
208+ GetPullRequestMergeOptions,
209+ UpdatePullRequestBranch,
210+ EnablePullRequestAutoMerge,
211+ DisablePullRequestAutoMerge,
212+ DeletePullRequestHeadBranch,
213+ RestorePullRequestHeadBranch,
214+ ListRepoCommitComments,
215+ ListCommitComments,
216+ CreateCommitComment,
217+ UpdateCommitComment,
218+ DeleteCommitComment,
200219 ListEvents,
201220 ListIntegrations,
202221 ConnectIntegration,
482501 })
483502 }
484503
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+
485515 fn workspace_schema() -> Value {
486516 json!({ "type": "string", "description": "The workspace's slug, e.g. \"flagon-io\"." })
487517 }
725755 properties
726756 }
727757
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+
728802 fn alert_id_schema() -> Value {
729803 json!({
730804 "type": "string",
733807 }
734808
735809 impl Op {
736− pub const ALL: [Op; 385] = [
810+ pub const ALL: [Op; 402] = [
737811 Op::Whoami,
738812 Op::GetWorkspace,
739813 Op::CreateWorkspace,
764838 Op::CreateRepo,
765839 Op::UpdateRepo,
766840 Op::TransferRepo,
841+ Op::ForkRepo,
842+ Op::ListForks,
843+ Op::SyncFork,
844+ Op::DetachFork,
767845 Op::RenameRepo,
768846 Op::RenameBranch,
769847 Op::CreateBranch,
819897 Op::ReopenPullRequest,
820898 Op::GetPullRequestChanges,
821899 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,
822911 Op::ListEvents,
823912 Op::ListIntegrations,
824913 Op::ConnectIntegration,
10601149 Op::Protection(ProtectionOp::SetForkPrApproval),
10611150 Op::Protection(ProtectionOp::GetActionsAccess),
10621151 Op::Protection(ProtectionOp::SetActionsAccess),
1152+ Op::Protection(ProtectionOp::GetActionsPermissions),
1153+ Op::Protection(ProtectionOp::SetActionsPermissions),
10631154 Op::Protection(ProtectionOp::CreateRepositoryDispatch),
10641155 Op::Protection(ProtectionOp::GetWorkspaceWorkflowPermissions),
10651156 Op::Protection(ProtectionOp::SetWorkspaceWorkflowPermissions),
11581249 Op::CreateRepo => "create_repo",
11591250 Op::UpdateRepo => "update_repo",
11601251 Op::TransferRepo => "transfer_repo",
1252+ Op::ForkRepo => "fork_repo",
1253+ Op::ListForks => "list_forks",
1254+ Op::SyncFork => "sync_fork",
1255+ Op::DetachFork => "detach_fork",
11611256 Op::RenameRepo => "rename_repo",
11621257 Op::RenameBranch => "rename_branch",
11631258 Op::CreateBranch => "create_branch",
12131308 Op::ConvertPullRequestToDraft => "convert_pull_request_to_draft",
12141309 Op::GetPullRequestChanges => "get_pull_request_changes",
12151310 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",
12161322 Op::ListEvents => "list_events",
12171323 Op::ListIntegrations => "list_integrations",
12181324 Op::ConnectIntegration => "connect_integration",
14161522 Op::ListRepos => "Repositories you can see, optionally filtered by a search query.",
14171523 Op::GetRepo => "One repository's details.",
14181524 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)."
14201526 }
14211527 Op::RenameRepo => {
14221528 "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."
14541560 Op::TransferRepo => {
14551561 "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."
14561562 }
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+ }
14571575 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."
14591577 }
14601578 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."
14621580 }
14631581 Op::ListCheckNames => {
14641582 "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)."
15751693 "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."
15761694 }
15771695 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."
15791727 }
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+ }
15801731 Op::ListEvents => {
15811732 "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)."
15821733 }
21072258 }),
21082259 &["repo", "to"],
21092260 ),
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(),
21102292 Op::GetRepo | Op::ListLabels | Op::AddDefaultLabels => repo_only(),
21112293 Op::CreateLabel => object(
21122294 json!({
21942376 "type": "string",
21952377 "description": "Make this existing branch the default: the one clones check out and pull requests merge into.",
21962378 },
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+ },
21972389 }),
21982390 &["repo"],
21992391 ),
23232515 "type": "boolean",
23242516 "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.",
23252517 },
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+ },
23262581 }),
23272582 &["repo"],
23282583 ),
25152770 },
25162771 "branch": {
25172772 "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.",
25192774 },
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+ },
25202779 "body": {
25212780 "type": "string",
25222781 "description": "Markdown: what changed and why. Mainly for pull requests from a branch.",
26022861 "type": "boolean",
26032862 "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.",
26042863 },
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+ },
26052881 })),
26062882 &["repo", "number"],
26072883 ),
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+ ),
26082958 Op::ListEvents => object(
26092959 json!({
26102960 "repo": repo_schema(),
33803730 Op::ListRepos
33813731 | Op::Search
33823732 | Op::GetRepo
3733+ | Op::ListForks
33833734 | Op::ListIssues
33843735 | Op::GetIssue
33853736 | Op::ListLabels
33923743 | Op::ListDecisions
33933744 | Op::Why
33943745 | Op::GetPullRequestChanges
3746+ | Op::GetPullRequestMergeOptions
3747+ | Op::ListRepoCommitComments
3748+ | Op::ListCommitComments
33953749 | Op::ListEvents
33963750 | Op::GetRepoSettings
33973751 | Op::ListCheckNames
34123766 | ProtectionOp::GetWorkflowPermissions
34133767 | ProtectionOp::GetForkPrApproval
34143768 | ProtectionOp::GetActionsAccess
3769+ | ProtectionOp::GetActionsPermissions
34153770 )
34163771 )
34173772 }
37064061 keep_issue_open: input["keep_issue_open"].as_bool() == Some(true),
37074062 ignore_checks: input["ignore_checks"].as_bool() == Some(true),
37084063 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,
37094070 };
37104071 let Services {
37114072 identity,
40984459 )
40994460 .await
41004461 }
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+ }
41014528 Op::ListRepos => {
41024529 let found: Vec<Repo> = g1t_kit::call(
41034530 repos,
41244551 .await
41254552 }
41264553 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+ }
41274558 let updated = pass(
41284559 repos,
41294560 "update",
41354566 "protected": input["protected"].as_bool(),
41364567 "topics": strings(input, "topics"),
41374568 "website": input["website"].as_str(),
4569+ "allowForking": input["allow_forking"].as_bool(),
4570+ "maxRefUpdates": max_ref_updates,
41384571 "surface": services.audit.surface,
41394572 }),
41404573 )
43284761 Outcome::Fail(failure) => return Ok(Outcome::Fail(failure)),
43294762 };
43304763 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+ };
43314792 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", &current.merge_commit_template),
4801+ squash_commit_template: template("squash_commit_template", &current.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,
43324809 auto_merge: flag("auto_merge", current.auto_merge),
43334810 required_checks: strings(input, "required_checks").unwrap_or(current.required_checks.clone()),
43344811 require_up_to_date: flag("require_up_to_date", current.require_up_to_date),
46435120 Op::GetPullRequest => pass(work, "get_pull", &view()).await,
46445121 Op::CreatePullRequest => {
46455122 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+ };
46465132 let opened: Outcome<Pull> = call(
46475133 work,
46485134 "open_pull",
46535139 title: text(input, "title"),
46545140 body: text(input, "body"),
46555141 branch: optional_text(input, "branch"),
5142+ head_repo,
46565143 // Unnamed, the change is its author's, unless an agent's token opened it.
46575144 agent: optional_text(input, "agent")
46585145 .unwrap_or_else(|| if g1t_contracts::rules::is_agent(&user) { "agent".into() } else { user.username.clone() }),
46675154 Outcome::Fail(failure) => return Ok(Outcome::Fail(failure)),
46685155 };
46695156 // 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);
46725160 let remote = services.addresses.git_remote(&source.namespace, &source.name);
46735161 ok(&json!({
46745162 "pull": pull,
47505238 let method = if self == Op::EditComment { "edit_comment" } else { "delete_comment" };
47515239 pass(work, method, &asked).await
47525240 }
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+ }
47545349 Op::GetPullRequestChanges => {
47555350 let found: Outcome<PullDetail> = call(work, "get_pull", &view()).await?;
47565351 match found {
+40−2
2626 SetForkPrApproval,
2727 GetActionsAccess,
2828 SetActionsAccess,
29+ GetActionsPermissions,
30+ SetActionsPermissions,
2931 CreateRepositoryDispatch,
3032 GetWorkspaceWorkflowPermissions,
3133 SetWorkspaceWorkflowPermissions,
3941 /// Every one: `Op::ALL` lists each as `Op::Protection(…)`, which a test
4042 /// checks against this.
4143 #[cfg(test)]
42− pub const ALL: [ProtectionOp; 18] = [
44+ pub const ALL: [ProtectionOp; 20] = [
4345 ProtectionOp::UpdateEnvironment,
4446 ProtectionOp::DeleteEnvironment,
4547 ProtectionOp::GetPendingDeployments,
5153 ProtectionOp::SetForkPrApproval,
5254 ProtectionOp::GetActionsAccess,
5355 ProtectionOp::SetActionsAccess,
56+ ProtectionOp::GetActionsPermissions,
57+ ProtectionOp::SetActionsPermissions,
5458 ProtectionOp::CreateRepositoryDispatch,
5559 ProtectionOp::GetWorkspaceWorkflowPermissions,
5660 ProtectionOp::SetWorkspaceWorkflowPermissions,
7377 ProtectionOp::SetForkPrApproval => "set_fork_pr_approval",
7478 ProtectionOp::GetActionsAccess => "get_actions_access",
7579 ProtectionOp::SetActionsAccess => "set_actions_access",
80+ ProtectionOp::GetActionsPermissions => "get_actions_permissions",
81+ ProtectionOp::SetActionsPermissions => "set_actions_permissions",
7682 ProtectionOp::CreateRepositoryDispatch => "create_repository_dispatch",
7783 ProtectionOp::GetWorkspaceWorkflowPermissions => "get_workspace_workflow_permissions",
7884 ProtectionOp::SetWorkspaceWorkflowPermissions => "set_workspace_workflow_permissions",
113119 ProtectionOp::SetForkPrApproval => "Set the approval policy for outside pull requests",
114120 ProtectionOp::GetActionsAccess => "Get who may use a repository's actions and workflows",
115121 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",
116124 ProtectionOp::CreateRepositoryDispatch => "Create a repository dispatch event",
117125 ProtectionOp::GetWorkspaceWorkflowPermissions => "Get a workspace's default workflow permissions",
118126 ProtectionOp::SetWorkspaceWorkflowPermissions => "Set a workspace's default workflow permissions",
136144 ProtectionOp::SetForkPrApproval => "Set approval_policy: first_time_contributors, outside_contributors or all_external_contributors. Needs the Admin role.",
137145 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.",
138146 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.",
139149 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.",
140150 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.",
141151 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.",
154164 | ProtectionOp::GetWorkflowPermissions
155165 | ProtectionOp::GetForkPrApproval
156166 | ProtectionOp::GetActionsAccess
167+ | ProtectionOp::GetActionsPermissions
157168 | ProtectionOp::GetWorkspaceWorkflowPermissions
158169 | ProtectionOp::ListWorkspaceManagedWorkflows
159170 )
217228 }),
218229 &["repo", "id", "state"],
219230 ),
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+ ),
221242 ProtectionOp::SetActionsAccess => (
222243 json!({
223244 "repo": repo,
714735 };
715736 map(settings, |s| json!({ "access_level": s["accessLevel"] }))
716737 }
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+ }
717755 ProtectionOp::CreateRepositoryDispatch => {
718756 let started: Outcome<u32> = g1t_kit::call(
719757 actions,
+551−5
707707 "website": "https://hello.g1t.page",
708708 "archived_at": null
709709 },
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/)."
711711 },
712712 "transfer_repo": {
713713 "request": {
729729 },
730730 "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/)."
731731 },
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+ },
732821 "rename_repo": {
733822 "request": {
734823 "name": "greeter"
9461035 "allow_ignoring_checks": true,
9471036 "merge_queue": false,
9481037 "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,
9491055 "updated_by": null,
9501056 "updated_at": null
9511057 }
9591065 "required_approvals": 1,
9601066 "count_agent_approvals": false,
9611067 "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
9631073 },
9641074 "response": {
9651075 "auto_merge": false,
9731083 "allow_ignoring_checks": true,
9741084 "merge_queue": true,
9751085 "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,
9761103 "updated_by": "syntaqx",
9771104 "updated_at": "2026-10-04T16:20:37.508Z"
9781105 },
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)."
9801107 },
9811108 "list_check_names": {
9821109 "response": [
20502177 },
20512178 "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."
20522179 },
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+ },
20532220 "merge_pull_request": {
20542221 "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
20562226 },
20572227 "response": {
20582228 "id": "pr_01m43smh3vexsr5pmp60qwv0vs",
20742244 "merge_base": "3a7e9c1b5d2f4a6c8e0b2d4f6a8c0e2b4d6f8a1c",
20752245 "merged_by": "syntaqx",
20762246 "merged_at": "2026-10-01T18:52:17.093Z",
2247+ "merge_method": "squash",
2248+ "merge_commit": "c41d7e2a9b0f3e5d7c9a1b3d5f7e9c0a2b4d6f81",
2249+ "closes": [
2250+ 12
2251+ ],
20772252 "superseded_by": null,
20782253 "check_status": "passed",
20792254 "files": [
21022277 "created_at": "2026-10-01T18:20:02.117Z",
21032278 "updated_at": "2026-10-01T18:52:17.093Z"
21042279 },
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."
21062571 },
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+ },
21072626 "close_pull_request": {
21082627 "response": {
21092628 "id": "pr_01m43smh3vexsr5pmp60qwv0vs",
1141111930 "access_level": "none"
1141211931 }
1141311932 },
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+ },
1141411960 "set_actions_access": {
1141511961 "params": {
1141611962 "owner": "flagon-io",
+12−1
154154 }
155155 match op {
156156 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),
158159 Op::Search => through::<search::SearchResults>(op, sent),
159160 Op::GetRepo
160161 | Op::CreateRepo
161162 | Op::UpdateRepo
162163 | Op::TransferRepo
164+ | Op::ForkRepo
165+ | Op::DetachFork
163166 | Op::RenameRepo
164167 | Op::RenameBranch
165168 | Op::ArchiveRepo
193196 | Op::ReopenPullRequest
194197 | Op::ConvertPullRequestToDraft
195198 | Op::MergePullRequest
199+ | Op::EnablePullRequestAutoMerge
200+ | Op::DisablePullRequestAutoMerge
201+ | Op::DeletePullRequestHeadBranch
202+ | Op::RestorePullRequestHeadBranch
196203 | Op::RequestReviewers
197204 | Op::RemoveRequestedReviewers => {
198205 through::<work::Pull>(op, sent)
199206 }
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),
200211 Op::ListWorkflows => through::<Vec<actions::Workflow>>(op, sent),
201212 Op::GetWorkflow => through::<actions::WorkflowDetail>(op, sent),
202213 Op::ListWorkflowRuns => through::<Vec<actions::WorkflowRun>>(op, sent),
+47−1
364364 route("GET", "/repos/:owner/:name/zipball/:ref", Op::Archives(ArchiveOp::DownloadZipball), &[]),
365365 route("PATCH", "/repos/:owner/:name", Op::UpdateRepo, &[]),
366366 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, &[]),
367371 route("DELETE", "/repos/:owner/:name", Op::DeleteRepo, &[]),
368372 route(
369373 "GET",
498502 ),
499503 route("GET", "/repos/:owner/:name/actions/permissions/access", Op::Protection(ProtectionOp::GetActionsAccess), &[]),
500504 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), &[]),
501507 route("POST", "/repos/:owner/:name/dispatches", Op::Protection(ProtectionOp::CreateRepositoryDispatch), &[]),
502508 route(
503509 "GET",
11211127 Op::MergePullRequest,
11221128 &[],
11231129 ),
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, &[]),
11241142 // Artifacts mode's docs, slides, designs and dashboards. Search comes
11251143 // before an artifact by id, which it would otherwise match.
11261144 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")]),
11481166 pub fn no_content(&self) -> bool {
11491167 matches!(
11501168 self.op,
1151− Op::DeleteComment | Op::Caches(CachesOp::DeleteCache)
1169+ Op::DeleteComment | Op::DeleteCommitComment | Op::Caches(CachesOp::DeleteCache)
11521170 )
11531171 }
11541172
15101528 }
15111529
15121530 #[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]
15131559 fn pull_requests_reopen_and_turn_draft_and_comments_are_named_by_id() {
15141560 let op = |method: &str, path: &str| resolve(method, path, &[], Value::Null).unwrap().0.op;
15151561 assert_eq!(op("POST", "/repos/acme/web/pulls/9/reopen"), Op::ReopenPullRequest);
+22−4
7070 Tool {
7171 name: "repository",
7272 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`.",
7474 default_action: None,
7575 actions: &[
7676 a("list", Op::ListRepos, "Repositories you can see"),
7777 a("get", Op::GetRepo, "One repository"),
7878 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"),
7984 a("contents", Op::GetContents, "A file's text, or a directory's entries and README, at a branch, tag or commit"),
8085 a("tarball", Op::Archives(ArchiveOp::DownloadTarball), "A 10-minute link to a branch, tag or commit as a .tar.gz"),
8186 a("zipball", Op::Archives(ArchiveOp::DownloadZipball), "A 10-minute link to a branch, tag or commit as a .zip"),
8287 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"),
8489 a("get_settings", Op::GetRepoSettings, "How pull requests merge, and the default branch's protection as its rules stack"),
8590 a("update_settings", Op::UpdateRepoSettings, "Change how pull requests merge and the default branch protection ruleset"),
8691 a("check_names", Op::ListCheckNames, "Check names reported lately, to require in a ruleset"),
123128 a("delete_branch", Op::DeleteBranch, "Delete a branch; make it again at its commit to restore it"),
124129 a("rename", Op::RenameRepo, "Rename it; old addresses redirect"),
125130 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"),
126135 a("archive", Op::ArchiveRepo, "Make it read-only"),
127136 a("unarchive", Op::UnarchiveRepo, "Make it writable again"),
128137 a("set_visibility", Op::SetRepoVisibility, "Make it public or private"),
166175 Tool {
167176 name: "pull_request",
168177 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.",
170179 default_action: None,
171180 actions: &[
172181 a("list", Op::ListPullRequests, "Pull requests on a repository, newest first"),
185194 a("review", Op::ReviewPullRequest, "Approve or request changes"),
186195 a("close", Op::ClosePullRequest, "Close without merging"),
187196 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"),
189204 a("merge_queue", Op::GetMergeQueue, "The repository's merge queue"),
190205 ],
191206 },
261276 a("set_approval_policy", Op::Protection(ProtectionOp::SetForkPrApproval), "Set which pull requests' runs wait for approval"),
262277 a("get_access", Op::Protection(ProtectionOp::GetActionsAccess), "Which repositories may use this one's actions and workflows"),
263278 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"),
264281 a("repository_dispatch", Op::Protection(ProtectionOp::CreateRepositoryDispatch), "Start repository_dispatch workflows with an event"),
265282 a("get_workspace_permissions", Op::Protection(ProtectionOp::GetWorkspaceWorkflowPermissions), "A workspace's default and maximum token permissions"),
266283 a("set_workspace_permissions", Op::Protection(ProtectionOp::SetWorkspaceWorkflowPermissions), "Set them, and whether jobs may open pull requests"),
634651 | Op::DeleteTeam
635652 | Op::RemoveTeamRepo
636653 | Op::MergePullRequest
654+ | Op::DeleteCommitComment
637655 | Op::RemoveRunner
638656 | Op::DeleteRunnerGroup
639657 | Op::UpdateRunnerSettings
+2−1
182182 { label: 'The merge queue', slug: 'guides/merge-queue' },
183183 { label: 'Rules', slug: 'guides/rules' },
184184 { label: 'CODEOWNERS', slug: 'guides/codeowners' },
185− { label: 'Forks and branches', slug: 'concepts/forks' },
185+ { label: 'Forks and working copies', slug: 'concepts/forks' },
186186 ],
187187 },
188188 {
197197 { label: 'People and teams', slug: 'guides/people-and-teams' },
198198 { label: 'Managing a repository', slug: 'guides/managing-repositories' },
199199 { label: 'Releases', slug: 'guides/releases' },
200+ { label: 'Forking a repository', slug: 'guides/forking-repositories' },
200201 { label: 'Transferring a repository', slug: 'guides/transferring-repositories' },
201202 { label: 'Audit log', slug: 'guides/audit-log' },
202203 { label: 'Usage and billing', slug: 'guides/usage-and-billing' },
+29−18
130130
131131 ## Pull requests and forks
132132
133−### Forks are only for pull requests
133+### A fork's storage counts in full
134134
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.
137137
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).
145145
146−### Pull request forks are removed a day after they close
146+### Pull requests' working copies are removed a day after they close
147147
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.
152152
153153 - **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.
156156 - **Instead.** Nothing you need to do. To keep working on a closed pull
157157 request's change, fetch `refs/pull/<pull request id>/head` and push it
158158 to a branch.
159−- **Status.** Clear fork storage rules depend on Cloudflare.
159+- **Status.** Clear storage rules for copies depend on Cloudflare.
160160
161161 ### Some dependency update options are not applied yet
162162
197197 See [Conflicts](/guides/pull-requests/#conflicts).
198198 - **Status.** Planned.
199199
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+
200211 ## Actions and runners
201212
202213 ### Docker shares the job's network
+5−3
143143 ## Pull request forks
144144
145145 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
147147 same namespace as its source, as `pulls--<pull request id>`, with
148148 Artifacts' own fork of the default branch, and it is served at
149149 `g1t.sh/pulls/<id>.git`. Anyone who can read the source can read the fork.
163163 `repos` also builds the test merge for every pull request,
164164 `refs/pull/<n>/merge`, without a sandbox, using a line-level merge on
165165 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
168170 queue's job ([Work and the merge queue](/architecture/work/#the-merge-queue)).
169171
170172 ## Backups
+160−45
Loading the diff…
+7−6
Loading the diff…
+37−0
Loading the diff…
+2−2
Loading the diff…
+231−0
Loading the diff…
+74−1
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…

Comments

Sign in to comment.