Repository: cli/cli

Issue:
Set default repo when creating fork during pr creation
### Describe the bug

When creating a fork during PR creation the default repository should be set

### Affected version

2.49.2

### Steps to reproduce the behavior

1. `gh repo clone ...` a 3rd party repository you do not have forked
2. `gh pr create`

### Expected vs actual behavior

After this the PR is created, but if I want to send another PR, I get a complaint that the default repository needs to be set manually using `set-default` first.
If so, it would be nice if not an error but directly a selection would be shown.
But actually after creating that fork, I'd expect the upstream to automatically be set as default repository.


Hints from issue comments:
@BagToad: 👋 Hey @Vampire, thanks for writing this up.

> Affected version
> 2.49.2

This `gh` version is fairly old, would you please try with the latest and let us know if the same behavior persists? ❤ @Vampire: I updated right before writing this, but it was too late already :-D@Vampire: Nope, still the same:
```bash
$ gh repo clone https://github.com/cli/cli github-cli
Cloning into 'github-cli'...
remote: Enumerating objects: 63105, done.
remote: Counting objects: 100% (645/645), done.
remote: Compressing objects: 100% (216/216), done.
remote: Total 63105 (delta 576), reused 429 (delta 429), pack-reused 62460 (from 2)
Receiving objects: 100% (63105/63105), 42.43 MiB | 11.16 MiB/s, done.
Resolving deltas: 100% (43487/43487), done.
$ cd github-cli/
$ git remote -v
origin  https://github.com/cli/cli.git (fetch)
origin  https://github.com/cli/cli.git (push)
$ git sw -c dummy
Switched to a new branch 'dummy'
$ gh pr create
? Where should we push the 'dummy' branch? Create a fork of cli/cli

Creating pull request for Vampire:dummy into trunk in cli/cli

? Title (required) dummy

? Title (required) dummy
? Choose a template Open a blank pull request
? Body <Received>
? What's next? Continue in browser
Changed cli/cli remote to "upstream"
Added Vampire/cli as remote "origin"
remote:
remote:
branch 'dummy' set up to track 'origin/dummy'.
To github.com:Vampire/cli.git
 * [new branch]        HEAD -> dummy
Opening https://github.com/cli/cli/compare/trunk...Vampire:dummy in your browser.
$ gh pr create
X No default remote repository has been set for this directory.

please run `gh repo set-default` to select a default remote repository.
```@Vampire: Actually, it could also be considered to make this setting on `gh repo fork` as the 98.7 % case should be for sending PRs upstream.
Or it could ask.@BagToad: Sorry for the delays on this @Vampire - digging into this today 🙇 @BagToad: Okay! So, I do not see any indication in the code that setting the default repo during `pr create` is the intended behavior, so I don't think this is quite a bug. 

I'm in favour of accepting this enhancement, however - I think it makes sense to set the default repo automatically when we create a fork. This is what `gh repo fork` does already, so I think we should align with that behavior rather than prompting.

Relevant `gh repo fork` code:

https://github.com/cli/cli/blob/db9dbfa4cf421f79094fe128a4d8aed20628be48/pkg/cmd/repo/fork/fork.go#L372-L384

This is where the fork happens in `pr create`: 

https://github.com/cli/cli/blob/db9dbfa4cf421f79094fe128a4d8aed20628be48/pkg/cmd/pr/create/create.go#L942-L950

I suspect that this change would happen somewhere near this code.

-----

## Acceptance Criteria

**Given** I have cloned a repo owned by an actor other than myself
**When** I run `gh pr create` and select `Create a fork of <repo>`
**Then** the **_upstream repository_** is set as the default repository, and a useful message is printed to stderr, aligning with the behavior of `gh repo fork`@daviddl9: hi @BagToad, I can take up this enhancement task!@BagToad: Hey @daviddl9, that is fine you can take this and I'll assign it to you 😁 

FYI you were accidentally blocked from the `cli` org because of your [testing pull requests](https://github.com/search?q=repo%3Acli%2Fcli+involves%3Adaviddl9&type=pullrequests). This was not intentional and is only because the `cli` organization gets _so much spam_ and so we block anything that looks like spam 😅 

Please keep the optics of spam in mind as you work on this issue, see https://github.com/cli/cli/pull/10409#issuecomment-2646298157.

Feel free to use one of my testing repos (https://github.com/BagToad/testing-public-1) for forking and pull request testing 😁 
@andyfeller: @BagToad @Vampire : Wanted to bubble up a nuanced question that @daviddl9 and I were discussing in https://github.com/cli/cli/pull/10458#issuecomment-2671764329:

**If an error arises from setting the upstream branch as the default happens during `gh pr create` before pushing the branch up, is this 1) a hard error that gives up before pushing the branch or 2) a soft error that we warn the user about but still continue with the rest of the command?**

The PR as merged treats it as a hard error.

Appreciate additional thoughts!@BagToad: @andyfeller @daviddl9 Thanks for the interesting discussion!

I don't have strong opinions myself, but I loosely prefer this being an error and not a warning, like you have currently.

I prefer consistency between `gh pr create` and `gh repo fork`. I see `gh repo fork` loosely as a model of what should happen while forking. It looks like this is considered an error in `gh repo fork`, so I'd prefer to keep it consistent across all operations that fork (unless, of course, an exception is more valuable than the consistency 😁) -

https://github.com/cli/cli/blob/537a22228cd6b42b740d7f1c09f47c45bb1dab30/pkg/cmd/repo/fork/fork.go#L372-L374

I recognize that `gh pr create` and `gh repo fork` have different goals, so it might make sense to avoid failing `gh pr create` completely because a failure doesn't hinder the PR creation goal. However, I personally do not find that compelling enough for me to prefer a warning over a failure, softly violating the consistency between the two commands. @Vampire: Maybe the main question is, when can it fail for what reason?
And can the reason be mitigated to then successfully send the PR which would anyway work if this error would be a warning.
I don't have right now too strong feelings either way either.
Having it as warning because it just is a secondary convenience operation sounds like a good argument to me.
But consistency also is a good argument.
So I'm actually not really able to stir in one direction.@Vampire: Another thought.
Maybe both arguments can be satisfied.
Also for `gh repo fork` setting the default repo would just be a secondary convenience operation, so make it a warning for both cases and you have the consistency as well as it only being a warning.@Vampire: Uhm, and actually while thinking about it.
Does `gh repo fork` actually set the default repo?
Up in the comment https://github.com/cli/cli/issues/10277#issuecomment-2609407497 I suggested to consider adding this for `gh repo fork` too, so I guess I was under the impression that `gh repo fork` does not set the default repo.

Solution patch:
diff --git a/go.mod b/go.mod
index 4e88a54d5..16b95545c 100644
--- a/go.mod
+++ b/go.mod
@@ -43,7 +43,7 @@ require (
 	github.com/sigstore/protobuf-specs v0.3.3
 	github.com/sigstore/sigstore-go v0.7.0
 	github.com/spf13/cobra v1.8.1
-	github.com/spf13/pflag v1.0.5
+	github.com/spf13/pflag v1.0.6
 	github.com/stretchr/testify v1.10.0
 	github.com/zalando/go-keyring v0.2.5
 	golang.org/x/crypto v0.32.0
diff --git a/go.sum b/go.sum
index d58e8de5b..f1ee4d12e 100644
--- a/go.sum
+++ b/go.sum
@@ -433,8 +433,9 @@ github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
 github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
 github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
 github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
-github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
+github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
 github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
diff --git a/pkg/cmd/pr/create/create.go b/pkg/cmd/pr/create/create.go
index b2abe0938..0066bbc6e 100644
--- a/pkg/cmd/pr/create/create.go
+++ b/pkg/cmd/pr/create/create.go
@@ -935,6 +935,7 @@ func handlePush(opts CreateOptions, ctx CreateContext) error {
 	headRepo := ctx.HeadRepo
 	headRemote := ctx.HeadRemote
 	client := ctx.Client
+	gitClient := ctx.GitClient
 
 	var err error
 	// if a head repository could not be determined so far, automatically create
@@ -976,7 +977,8 @@ func handlePush(opts CreateOptions, ctx CreateContext) error {
 		headRepoURL := ghrepo.FormatRemoteURL(headRepo, cloneProtocol)
 		gitClient := ctx.GitClient
 		origin, _ := remotes.FindByName("origin")
-		upstream, _ := remotes.FindByName("upstream")
+		upstreamName := "upstream"
+		upstream, _ := remotes.FindByName(upstreamName)
 		remoteName := "origin"
 
 		if origin != nil {
@@ -984,7 +986,7 @@ func handlePush(opts CreateOptions, ctx CreateContext) error {
 		}
 
 		if origin != nil && upstream == nil && ghrepo.IsSame(origin, ctx.BaseRepo) {
-			renameCmd, err := gitClient.Command(context.Background(), "remote", "rename", "origin", "upstream")
+			renameCmd, err := gitClient.Command(context.Background(), "remote", "rename", "origin", upstreamName)
 			if err != nil {
 				return err
 			}
@@ -992,7 +994,7 @@ func handlePush(opts CreateOptions, ctx CreateContext) error {
 				return fmt.Errorf("error renaming origin remote: %w", err)
 			}
 			remoteName = "origin"
-			fmt.Fprintf(opts.IO.ErrOut, "Changed %s remote to %q\n", ghrepo.FullName(ctx.BaseRepo), "upstream")
+			fmt.Fprintf(opts.IO.ErrOut, "Changed %s remote to %q\n", ghrepo.FullName(ctx.BaseRepo), upstreamName)
 		}
 
 		gitRemote, err := gitClient.AddRemote(context.Background(), remoteName, headRepoURL, []string{})
@@ -1002,6 +1004,19 @@ func handlePush(opts CreateOptions, ctx CreateContext) error {
 
 		fmt.Fprintf(opts.IO.ErrOut, "Added %s as remote %q\n", ghrepo.FullName(headRepo), remoteName)
 
+		// Only mark `upstream` remote as default if `gh pr create` created the remote.
+		if didForkRepo {
+			err := gitClient.SetRemoteResolution(context.Background(), upstreamName, "base")
+			if err != nil {
+				return fmt.Errorf("error setting upstream as default: %w", err)
+			}
+
+			if opts.IO.IsStdoutTTY() {
+				cs := opts.IO.ColorScheme()
+				fmt.Fprintf(opts.IO.ErrOut, "%s Repository %s set as the default repository. To learn more about the default repository, run: gh repo set-default --help\n", cs.WarningIcon(), cs.Bold(ghrepo.FullName(headRepo)))
+			}
+		}
+
 		headRemote = &ghContext.Remote{
 			Remote: gitRemote,
 			Repo:   headRepo,
@@ -1013,7 +1028,6 @@ func handlePush(opts CreateOptions, ctx CreateContext) error {
 		pushBranch := func() error {
 			w := NewRegexpWriter(opts.IO.ErrOut, gitPushRegexp, "")
 			defer w.Flush()
-			gitClient := ctx.GitClient
 			ref := fmt.Sprintf("HEAD:refs/heads/%s", ctx.HeadBranch)
 			bo := backoff.NewConstantBackOff(2 * time.Second)
 			ctx := context.Background()



Test patch:
diff --git a/pkg/cmd/attestation/verification/sigstore_integration_test.go b/pkg/cmd/attestation/verification/sigstore_integration_test.go
index e14b472b0..c37dd30c2 100644
--- a/pkg/cmd/attestation/verification/sigstore_integration_test.go
+++ b/pkg/cmd/attestation/verification/sigstore_integration_test.go
@@ -48,20 +48,22 @@ func TestLiveSigstoreVerifier(t *testing.T) {
 	}
 
 	for _, tc := range testcases {
-		verifier := NewLiveSigstoreVerifier(SigstoreConfig{
-			Logger: io.NewTestHandler(),
+		t.Run(tc.name, func(t *testing.T) {
+			verifier := NewLiveSigstoreVerifier(SigstoreConfig{
+				Logger: io.NewTestHandler(),
+			})
+
+			results, err := verifier.Verify(tc.attestations, publicGoodPolicy(t))
+
+			if tc.expectErr {
+				require.Error(t, err)
+				require.ErrorContains(t, err, tc.errContains)
+				require.Nil(t, results)
+			} else {
+				require.NoError(t, err)
+				require.Equal(t, len(tc.attestations), len(results))
+			}
 		})
-
-		results, err := verifier.Verify(tc.attestations, publicGoodPolicy(t))
-
-		if tc.expectErr {
-			require.Error(t, err, "test case: %s", tc.name)
-			require.ErrorContains(t, err, tc.errContains, "test case: %s", tc.name)
-			require.Nil(t, results, "test case: %s", tc.name)
-		} else {
-			require.Equal(t, len(tc.attestations), len(results), "test case: %s", tc.name)
-			require.NoError(t, err, "test case: %s", tc.name)
-		}
 	}
 
 	t.Run("with 2/3 verified attestations", func(t *testing.T) {
diff --git a/pkg/cmd/pr/create/create_test.go b/pkg/cmd/pr/create/create_test.go
index 6df3f9880..4d7c294db 100644
--- a/pkg/cmd/pr/create/create_test.go
+++ b/pkg/cmd/pr/create/create_test.go
@@ -798,6 +798,7 @@ func Test_createRun(t *testing.T) {
 				cs.Register("git remote rename origin upstream", 0, "")
 				cs.Register(`git remote add origin https://github.com/monalisa/REPO.git`, 0, "")
 				cs.Register(`git push --set-upstream origin HEAD:refs/heads/feature`, 0, "")
+				cs.Register(`git config --add remote.upstream.gh-resolved base`, 0, "")
 			},
 			promptStubs: func(pm *prompter.PrompterMock) {
 				pm.SelectFunc = func(p, _ string, opts []string) (int, error) {
@@ -809,7 +810,7 @@ func Test_createRun(t *testing.T) {
 				}
 			},
 			expectedOut:    "https://github.com/OWNER/REPO/pull/12\n",
-			expectedErrOut: "\nCreating pull request for monalisa:feature into master in OWNER/REPO\n\nChanged OWNER/REPO remote to \"upstream\"\nAdded monalisa/REPO as remote \"origin\"\n",
+			expectedErrOut: "\nCreating pull request for monalisa:feature into master in OWNER/REPO\n\nChanged OWNER/REPO remote to \"upstream\"\nAdded monalisa/REPO as remote \"origin\"\n! Repository monalisa/REPO set as the default repository. To learn more about the default repository, run: gh repo set-default --help\n",
 		},
 		{
 			name: "pushed to non base repo",