Skip to content

Enhance gh pr create to support cross repo pull requests within the same organization #10093

Open
0 of 3 issues completed

Description

@andyfeller

Describe the feature or problem you’d like to solve

gh pr create -H user:branch is limited to creating cross repo pull requests between an upstream repository and a user-owned fork due to createPullRequest mutation limitation in the headRefName field. Instead, these types of cross repo pull requests should specify the GraphQL ID of the upstream repository using headRepositoryId field.

Ideally, the -H, --head branch flag could be used with user or organization logins.

For more information, #9364 (comment)

Proposed solution

This would support intra-organization forking needs as well as simplify acceptance tests around this scenario:

# Create the PR spanning upstream and fork repositories, gh pr create does not support headRepositoryId needed for private forks
exec gh api graphql -F repositoryId="${REPO_ID}" -F headRepositoryId="${FORK_ID}" -F query='mutation CreatePullRequest($headRepositoryId: ID!, $repositoryId: ID!) { createPullRequest(input:{ baseRefName: "main", body: "Feature Body", draft: false, headRefName: "feature-branch", headRepositoryId: $headRepositoryId, repositoryId: $repositoryId, title:"Feature Title" }){ pullRequest{ id url } } }'

Additional context

Relates #9208
Relates #575

Activity

  1. williammartin commented on Dec 17, 2024

    @williammartin
    Member

    @andyfeller from an implementation point of view, are you thinking that we could look at the <owner> portion of <owner>:<branch> and lookup the headRepositoryId if it appears to be the same owner as the repo owning the base branch?

  2. andyfeller commented on Dec 18, 2024

    @andyfeller
    ContributorAuthor

    @andyfeller from an implementation point of view, are you thinking that we could look at the <owner> portion of <owner>:<branch> and lookup the headRepositoryId if it appears to be the same owner as the repo owning the base branch?

    Good question. 🤔 Let me dump my thoughts to think through this a bit...

    Stream of thought

    When we create a cross repo pull requests within our acceptance test, it shows up as:

    Screenshot of GitHub pull request UI from across 2 repos in the same organization

    Neither the base or head demonstrate the repositories involved; simple who owns them and the branches.

    In the scenario where we're creating pull requests, it's a question of whether the pull request spans:

    1. 2 branches in the same repo
    2. 2 branches across the parent and fork repos

    If there is no way to create a pull request from completely unrelated repositories AND cross repo pull requests must traverse the parent/child relationship, then perhaps it isn't important to denote the repository in -H?

    I was primarily thinking about creating a pull requests with changes from the fork/child repo to the upstream/parent repo, however I wonder if there are use cases for the reverse of creating a pull request from the parent to the child. 🤔

    As far as resolving headRepositoryId, GraphQL exposes parent field within Repository object where we can get the node ID that should be used.

    query {
      repository(owner:"tinyfists", name:"fork"){
        name
        id
        isFork
        isInOrganization
        parent {
          name
          id
          isFork
          isInOrganization
        }
      }
    }

    results in:

    {
      "data": {
        "repository": {
          "name": "fork",
          "id": "R_kgDONfdxzw",
          "isFork": true,
          "isInOrganization": true,
          "parent": {
            "name": "upstream",
            "id": "R_kgDONfdwnQ",
            "isFork": false,
            "isInOrganization": true
          }
        }
      }
    }

    Additionally, Repository contains a forks field to identify and traverse children.

    We could use the optional <owner> as an indicator that this is a cross repo pull request, resolve the parent, maybe throw an error if the information we get doesn't match while creating the PR if they do?

  3. williammartin commented on Dec 19, 2024

    @williammartin
    Member

    I'm sorry Andy, I've read your message a few times and I'm not really sure how to reply to the stream of thought. It's not that I don't like it, I'm just not following 😅 .

    If there is no way to create a pull request from completely unrelated repositories AND cross repo pull requests must traverse the parent/child relationship, then perhaps it isn't important to denote the repository in -H?

    I don't see how one follows from the other.

    We could use the optional as an indicator that this is a cross repo pull request, resolve the parent, maybe throw an error if the information we get doesn't match while creating the PR if they do?

    It seems reasonable to me that if you provide <owner> you are indicating a cross repo PR. So my question was whether we would be able to use that to go find the head repository ID? In practice I'm not sure it's possible because an org may contain many forks, and each of them may have a branch of the right name. Maybe this is what you were implying with:

    Neither the base or head demonstrate the repositories involved; simple who owns them and the branches.

    I'm just not sure whether you had a specific implementation in mind for this. If so, can you outline what it would be.

  4. andyfeller commented on Jan 9, 2025

    @andyfeller
    ContributorAuthor

    If there is no way to create a pull request from completely unrelated repositories AND cross repo pull requests must traverse the parent/child relationship, then perhaps it isn't important to denote the repository in -H?

    I don't see how one follows from the other.

    😞 Sorry for getting lost in the train of thought, that said I think you understand what I was rambling on about. Thank you for patience and working with what was provided. 🙇

    It seems reasonable to me that if you provide <owner> you are indicating a cross repo PR. So my question was whether we would be able to use that to go find the head repository ID? In practice I'm not sure it's possible because an org may contain many forks, and each of them may have a branch of the right name. Maybe this is what you were implying with:

    Neither the base or head demonstrate the repositories involved; simple who owns them and the branches.

    I'm definitely trying to figure out whether the user invoking gh pr create needs to explicitly tell use about the upstream repo as part of -H ... or not.

    Again, gh pr create -H username:branch creates a cross-repo pull request from a user fork of its upstream repo.

    • -H username:branch is leaking a technical detail of the headRefName field in the CreatePullRequestInput used under the covers.

    • -H username:branch does not require the name of the repository because the GitHub API is figuring that out on the backend.

    In order to create cross-repo pull requests between organization repositories, gh pr create needs to provide that information in the form of headRepositoryId to CreatePullRequestInput

    • Does the user provide this? If so, is this a different form of -H, --head flag like -H org/repo:branch?

    • Does the GitHub CLI attempt to figure out the base repository based on forks info within the GitHub API, removing the need for modifying -H input format?

    At the time I reported this, I was worried about including another obscure, undocumented format for the -H, --head flag when trying to think about sourcing headRepositoryId information.

    Practical example

    Here are 2 private repositories I created within my test organization, one as a fork of the other:

    $ QUERY='
    quote> query($owner:String!, $name:String!) {
      repository(owner:$owner, name:$name){
        forks(first:10){
          totalCount
          nodes {
            name
            id
            isFork
            parent {
              name
              id
              isFork
              isInOrganization
            }
          }
        }
        name
        id
        isFork
        isInOrganization
        parent {
          name
          id
          isFork
          isInOrganization
        }
      }
    }'
    
    
    $ gh api graphql -f query="$QUERY" -F owner=tinyfists -F name=upstream
    {
      "data": {
        "repository": {
          "forks": {
            "totalCount": 0,
            "nodes": []
          },
          "name": "upstream",
          "id": "R_kgDONfdwnQ",
          "isFork": false,
          "isInOrganization": true,
          "parent": null
        }
      }
    }
    
    
    $ gh api graphql -f query="$QUERY" -F owner=tinyfists -F name=fork    
    {
      "data": {
        "repository": {
          "forks": {
            "totalCount": 0,
            "nodes": []
          },
          "name": "fork",
          "id": "R_kgDONfdxzw",
          "isFork": true,
          "isInOrganization": true,
          "parent": {
            "name": "upstream",
            "id": "R_kgDONfdwnQ",
            "isFork": false,
            "isInOrganization": true
          }
        }
      }
    }

    Since we use the local repository context or -R, --repo flag to identify the repository, I was thinking maybe that is used and we pull the parent information since there can only be 1 parent. That way, gh pr create -H ... doesn't require another format and there is less for the user to provide.

    Does that make more sense, @williammartin?

  5. williammartin commented on Jan 10, 2025

    @williammartin
    Member

    Does that make more sense, @williammartin?

    I think I understand your idea more completely, thanks for taking the time.

    Since we use the local repository context or -R, --repo flag to identify the repository, I was thinking maybe that is used and we pull the parent information since there can only be 1 parent. That way, gh pr create -H ... doesn't require another format and there is less for the user to provide.

    I'm not sure how we address the following scenario, which I didn't explain very well earlier.

    org/
      repo
      repo-fork-1
      repo-fork-2
    

    Both repo-fork-1 and repo-fork-2 have feature-branch.

    I run gh pr --repo org/repo create -H org:feature-branch.

    How do we find the right head branch to use?

    Sorry if I'm misunderstanding you. I think this is just a bit of a gnarly topic with a lot of moving parts.

  6. jacob-keller commented on Jan 15, 2025

    @jacob-keller

    My work recently started requiring that we move our forks outside of personal namespaces and put them into an organization. Unfortunately, the place where we can create forks is separate from where our repositories live, so this comes up and creates some weird failures.

    Id like to help resolve this so that we can maintain use of CLI.

    It seems to me that you could figure out the repository to use by looking at the remote info, or doing a lookup when selecting where to push.

    In my case, gh pr correctly identified where to push but then fails to create the repository because it doesnt full in the graph ql data correctly. I'll have to look more closely at this tomorrow, but it seems like we should be able to solve this without needing to have the user provide too much additional data.

  7. andyfeller commented on Jan 15, 2025

    @andyfeller
    ContributorAuthor

    Since we use the local repository context or -R, --repo flag to identify the repository, I was thinking maybe that is used and we pull the parent information since there can only be 1 parent. That way, gh pr create -H ... doesn't require another format and there is less for the user to provide.

    I'm not sure how we address the following scenario, which I didn't explain very well earlier.

    org/
      repo
      repo-fork-1
      repo-fork-2
    

    Both repo-fork-1 and repo-fork-2 have feature-branch.

    I run gh pr --repo org/repo create -H org:feature-branch.

    How do we find the right head branch to use?

    Sorry if I'm misunderstanding you. I think this is just a bit of a gnarly topic with a lot of moving parts.

    Mrmmm good question and 💯 on the gnarly aspect of it! Thanks for the example as I definitely hadn't considered this. 🤔

    Would --repo org/repo just change how we resolve headRepositoryId below?

    Previously, I was assuming someone was calling gh pr create -H org:feature-branch from a local repository cloned. In the event someone provides --repo owner/name, then gh pr create should be focused on creating the PR from the remote repo instead, but repositoryId variable below would be the same parent.

    mutation CreatePullRequest($headRepositoryId: ID!, $repositoryId: ID!) {
      createPullRequest(input:{
        baseRefName: "main",
        body: "Feature Body",
        draft: false,
        headRefName: "feature-branch",
        headRepositoryId: $headRepositoryId,
        repositoryId: $repositoryId,
        title:"Feature Title"
      }){
        pullRequest{
          id
          url
        }
      }
    }
  8. jacob-keller commented on Jan 15, 2025

    @jacob-keller

    Ultimately we need to know a few things:

    1. the repository we want to create a PR in
    2. the branch we want to merge into
    3. the repository where we want the PR to come from
    4. the branch we want to create the PR from

    We have two flags currently:

    -R to specify the target repo to create the PR in. Without the -R, this is determined automatically?

    -H to specify the branch that we want the fork to use. This defaults to the current branch, but you can also specify with "owner:branch" to try and pick up a remote branch. However, this specifically does not include the repository name as it essentially assumes the owner has a single unique fork, but with organizations that won't be the case?

    With a plain "gh pr create", the flow is something like this:

    1. ask/determine where the PR is created
    2. assume the current branch is what we want as the head
    3. ask/determine where we will push the branch
    4. push the branch to the remote
    5. create the PR over the API

    I think for plain gh pr create, at step 3, we should remember where we pushed and do a lookup to get the headRepositoryID, since we already know we're pushing to that remote.

    That would solve my issue, but does not handle the cases with the options for -H to specify the head without pushing.

    Is there any other identifying information we can use to select the proper fork when dealing with multiple forks under a given organization? If not, I think extending -H to include repository name somehow would be required.

    When making PRs from a local repository, we could use remote info to limit the list to the remotes which the user has added. If we find multiple forks, we could then put up a dialog to ask the user which one they wanted?

    For non-interactive modes we would need a flag or extension of -H, I think.

  9. jacob-keller commented on Jan 15, 2025

    @jacob-keller

    Looking at the create code in https://github.com/cli/cli/blob/trunk/pkg/cmd/pr/create/create.go#L930 we already have headRepo in the create context. I'm thinking at least for the case without -H, we can extend the PR creation to do a proper lookup of the headRepositoryId via the headRepo.

    I'm unsure how to handle the case with --head/-H.

  10. jacob-keller commented on Jan 16, 2025

    @jacob-keller

    I made an initial attempt at including the headRepositoryId when we know it, at #10253

    I submitted as a draft since I have limited go experience and am not sure how to even execute tests. I think this approach will be helpful for the most common use case where we are automatically determining the head repository, as it doesn't require user intervention. However, it doesn't solve the general case with --head, as I am unsure what the best solution is there. It is also likely that this PR doesn't quite work as-is, since I am unsure how to go about testing it.

  11. 13 remaining items

  12. HACKER007-SHIP-IT commented on Dec 31, 2025

    @HACKER007-SHIP-IT
  13. HACKER007-SHIP-IT commented on Jan 2, 2026

    @HACKER007-SHIP-IT
  14. lyonstim247-lgtm commented on Mar 23, 2026

    @lyonstim247-lgtm
  15. abdoo66378-art commented on Apr 30, 2026

    @abdoo66378-art
  16. Lok-thiek commented on Sep 20, 2026

    @Lok-thiek
  17. Lok-thiek commented on Sep 20, 2026

    @Lok-thiek
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementa request to improve CLIgh-prrelating to the gh pr commandmore-info-neededMore info needed from user/contributorneeds-triageneeds to be reviewed

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions