Skip to content

Conversation

mislav
Copy link
Contributor

@mislav mislav commented Sep 15, 2020

First, this changes how "base" repository is determined when in a git repository. This affects all gh commands that operate on the current repo (i.e. when the -R, --repo flag wasn't passed).

On first run in a git repository, if there are multiple git remotes or when we are in the context of a fork, BaseRepo() will now prompt the user which repository should be queried as base repository:

? Which should be the base repository (used for e.g. querying issues) for this directory?
> cli/cli
  parent-owner/cli
  mislav/gh-cli

In non-interactive mode, the prompt is skipped and we default to the first git remote instead.

After the base repo is resolved, the result is cached in the local repository using git config so that RepositoryNetwork API lookups can be avoided in the future.

Fixes #924, fixes #458, fixes #867, fixes #317


Second, this changes how the "head" repository is determined during gh pr create.

We no longer try to guess which is a suitable push target for the "head" branch. Instead, unless the user has already fully pushed their branch, we always present the user with a prompt:

? Where should we push the 'feature' branch?
> cli/cli
  elsewhere/cli
  Create a fork of cli/cli
  Skip pushing the branch
  Cancel

Thus, the prompt also acts as a confirmation step for pushing/forking. We never fork nor push without explicit user agreement anymore.

In non-interactive mode, we error out, instructing the user to fully push the branch to a remote before starting pr create, or to use the new --head [<owner>:]<branch> flag to explicitly set which head branch should be used when creating the PR.

Fixes #1486, fixes #1330, fixes #1098, fixes #1032, fixes #800, fixes #684
Ref. #1645


TODO:

  • reference relevant issues
  • revise the prompt copy?
  • tests
  • extra flags for non-interactive pr create

On first run in a git repository, `BaseRepo()` will now prompt the user
which repository should be queried as base repository if there are
multiple git remotes or when we are in the context of a fork.

In non-interactive mode, the prompt is skipped and we default to the
first git remote instead.

After the base repo is resolved, the result is cached in the local
repository using `git config` so that RepositoryNetwork API lookups can
be avoided in the future.
return r.baseOverride, nil
}

// if any of the remotes already has a resolution, respect that
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, it seems that there should only be one resolved remote for any repo, but do we need any safeguards/messaging in place for the case when there are more than one?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly we should sort the remotes list so that if there are multiple resolved remotes we will deterministically select the same one every time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@samcoe Good catch! The remote list is already guaranteed to be sorted: first upstream, then github, then origin, then alphabetical order. This makes me feel confident that the resolution will be deterministic.

Good question about what if multiple remotes have the resolution bit. Right now I would say that this is not possible, since the resolution bit is only written when no git remote already has a bit.

for _, repo := range r.Network.Repositories {
if repo != nil && ghrepo.IsSame(repo, r.BaseOverride) {
return repo, nil
func (r *ResolvedRemotes) BaseRepo(io *iostreams.IOStreams) (ghrepo.Interface, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are your thoughts on changing this method to take in canPrompt bool argument instead of the io *iostreams.IOStreams argument? It seems that is all we are doing with io here, and might make the method easier to test.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(fwiw, iostreams is easy to test)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@samcoe That's an excellent point! It's usually better to pass primitives (such as boolean) than complex types such as IOStreams. Here, however, passing in IOStreams semantically draws attention that there will be prompt functionality inside. Right now, the Survey library isn't configured with IOStreams, but we are hoping to one day hook it up. After that, any part of functionality that needs access to stdin/stdout/stderr should take an IOStreams so it's easier to stub out in tests.

We no longer guess the head repository using heuristics; instead, we
present the user with the choice of pushable repositories and an
additional option to create a new fork.

The new `pr create --head` flag is available for the user to specify the
head branch in `branch` or `owner:branch` format and completely skip any
forking or auto-pushing checks.
@mislav mislav marked this pull request as ready for review September 16, 2020 13:02
Copy link
Contributor

@vilmibm vilmibm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • delighted to see context code going away
  • tests look thorough, thank you
  • played with this locally and it was ✨
  • awesome work

@mislav mislav merged commit b2e36a0 into trunk Sep 16, 2020
@mislav mislav deleted the base-resolve branch September 16, 2020 16:53
@jglick
Copy link

jglick commented Sep 16, 2020

Glad to see something safer. Still would like to be able to somehow configure an automatic default. In my case, if there is a remote named fork, I always want to push to it¹, so it would be tedious to select this for every new one-line PR.

¹Using the same remote branch name as the local branch, like if I had typed

git push fork

and then copied and pasted the suggested variant with --set-upstream (which like the --head here forces you to retype the branch name).

@jasonkarns
Copy link

jasonkarns commented Sep 16, 2020

@jglick since the resolved remote is persisted in git-config, it would appear that one could probably set their preferred remote via git-config manually. This would allow you to set it for your entire system, or even a collection of repos (using git's conditional-includes).

https://github.com/cli/cli/pull/1706/files#diff-5b52564f62704260aa7d42e182fd1a5dR139

(However, this would increase the chance of the multiple-remote configuration scenario that was discussed. Unsure of the ramifications of having this configured manually above the repo-level git-config.)

I recognize this doesn't provide first-class support for configuring a default; instead leveraging the cli's own persistence to side-step the normal lookup.

@mislav
Copy link
Contributor Author

mislav commented Sep 17, 2020

Glad to see something safer. Still would like to be able to somehow configure an automatic default. In my case, if there is a remote named fork, I always want to push to it

@jglick We hear you! I've opened an issue to discuss #1718

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