Conversation
Co-Authored-By: Nate Smith <vilmibm@neongrid.space>
Co-Authored-By: Nate Smith <vilmibm@neongrid.space>
Co-Authored-By: Nate Smith <vilmibm@neongrid.space>
Co-Authored-By: Nate Smith <vilmibm@neongrid.space>
Co-Authored-By: Nate Smith <vilmibm@neongrid.space>
Co-Authored-By: Nate Smith <vilmibm@neongrid.space>
|
@probablycorey wouldn't this conflict with the git repo branch settings where the branch gets deleted when the PR is merged ? |
command/pr.go
Outdated
| return "", err | ||
| } | ||
|
|
||
| err = git.CheckoutBranch(repo.DefaultBranchRef.Name) |
There was a problem hiding this comment.
This might be edge case-y, but there might not be a local branch equivalent of origin/<default-branch>. In that case, this command would fail. We could maybe fall back to any other branch, as long as we can switch away from the current one so we can delete it.
There was a problem hiding this comment.
This seems pretty edge case-y, but maybe this is something we could fix in a future PR. I'm also not sure how i'd fix it and wondering if failing is better than switching to a random branch because that would be pretty unexpected.
| if call >= len(cs.Stubs) { | ||
| panic(fmt.Sprintf("more execs than stubs. most recent call: %v", cmd)) | ||
| } | ||
| // fmt.Printf("Called stub for `%v`\n", cmd) // Helpful for debugging |
There was a problem hiding this comment.
This makes me thing that should expand our cmd stubber to be able to assert that a specific command was invoked
There was a problem hiding this comment.
I think that would still run into similar problems. When things don't match up correctly you still want to print out the commands that are called. I think there is a better solution to what we're doing with command stubbing, but I've not been able to think of it yet!
There was a problem hiding this comment.
Something that I only caught in the re-review: I understood that in the design spec, the prompt to delete a branch also suggests that the branch in the remote repository will be deleted. In this implementation, only the local branch is deleted; the remote branch stays. Is this feature something that is planned for this PR or as a follow-up?
In my view, deleting the remote branch would be a bigger priority than deleting the local one, since the former matches the behavior of the web UI flow with Merge Button followed by "delete branch".
I just noticed the "and on GitHub" language too! I think this PR is getting big enough as it is. A follow-up PR to add remote branch deletion makes sense to me. |
command/root.go
Outdated
| return baseRepo, nil | ||
| } | ||
|
|
||
| func convertRepoInterfaceToRepository(ctx context.Context, repo ghrepo.Interface) (*api.Repository, error) { |
There was a problem hiding this comment.
I've added this function so we can get an *api.Repository object from a ghrepo.Interface.
There was a problem hiding this comment.
Is it worth doing a type check in this to see if a given interface-satisfying value is already an api.Repository? (genuine question, I'm not sure)
@vilmibm I feel like that would make the code more complicated. I do think this is annoying though and if you'd be interested it might be worth doing a spike on trying to return api.Repository in more places instead of the ghrepo interface. |
command/pr.go
Outdated
| return err | ||
| } | ||
|
|
||
| err = git.CheckoutBranch(repo.DefaultBranchRef.Name) |
There was a problem hiding this comment.
You can technically get the default branch from git by doing
git symbolic-ref refs/remotes/origin/HEAD
however, in my experience this value will usually be out of date after the remote repository changes its default branch. So I think it's best to continue to get this information from the server. 👍
command/pr.go
Outdated
| return err | ||
| } | ||
|
|
||
| err = git.CheckoutBranch(repo.DefaultBranchRef.Name) |
There was a problem hiding this comment.
Technically, we only need to switch to a different branch if we're doing to delete a branch that happens to be the current branch. If the pr.HeadRefName branch to delete is not the current one, I would say that we should ideally avoid the git checkout step.
There was a problem hiding this comment.
That makes sense. I added the behavior you recommended and a test.
| prCmd.AddCommand(prReopenCmd) | ||
| prCmd.AddCommand(prMergeCmd) | ||
| prMergeCmd.Flags().BoolP("merge", "m", true, "Merge the commits with the base branch") | ||
| prMergeCmd.Flags().BoolP("delete-branch", "d", true, "Delete the local branch after merge") |
There was a problem hiding this comment.
For scripting use, I would prefer that this flag would be false by default.
The deletion of the local branch is potentially destructive right now and, in my mind, prone to edge-cases:
- assumes that a local branch corresponding to a PR always exists;
- assumes that the local branch name is always the same as PR
head; - assumes that the branch to be deleted does not have unpushed commits.
If I write a script that programatically merges multiple PRs:
for pr in 12 13 14; do gh pr merge --merge $pr; doneI would get 3 git errors about not being able to delete the local branch if I've never actually checked out those PRs locally before. I could avoid it by doing:
gh pr merge --merge $pr --delete-branch=falsebut I feel that it's best if this flag was opt-in instead of opt-out by default.
There was a problem hiding this comment.
My only concern here is that the default behavior for deletion would be different between interactive and non-interactive. But if other people don't think this is a problem then I'm cool with the change.
command/root.go
Outdated
| } | ||
|
|
||
| func convertRepoInterfaceToRepository(ctx context.Context, repo ghrepo.Interface) (*api.Repository, error) { | ||
| apiClient, err := apiClientForContext(ctx) |
There was a problem hiding this comment.
We should try to pass an API client from the outside to avoid instantiating multiple API clients during the lifetime of the same command #943
command/root.go
Outdated
| return nil, err | ||
| } | ||
|
|
||
| repoContext, err := context.ResolveRemotesToRepos(remotes, apiClient, "") |
There was a problem hiding this comment.
I think this method of resolving is too complex for this use case and that resolving a "ghrepo" to a *Repository object could be done simpler. Additionally, it doesn't look like the repo argument to the parent function is respected in the lookup.
We have a function that can resolve a ghrepo to a Repository: api.GitHubRepo(). Currently, the payload returned does not include the information about the default branch, but you could expand the field list requested in that query to include defaultBranchRef { name }.
There was a problem hiding this comment.
One more optimization that we could easily do: since the repo ghrepo.Interface object might already be an *api.Repository (and often it will be), we could detect and return that and avoid making an API request:
if r, ok := repo.(*api.Repository); ok && r.DefaultBranchRef.Name != "" {
return r, nil
}
return api.GitHubRepo(repo)There was a problem hiding this comment.
I got rid of the convertRepoInterfaceToRepository method and replaced it with api.GitHubRepo. I'm against the type checking idea though. It feels like extra code without much benefit. But I'm interested in exploring how to ensure we always get an api.Repository (or why we sometimes only need a ghrepo interface)
|
@ampinsk I've updated the text to show this now Instead of saying merge it uses the same language as it had in the choices: "Create a merge commit" I've also got better warnings for commits. |
mislav
left a comment
There was a problem hiding this comment.
If someone used gh pr merge --repo OWNER/REPO to merge PRs in a repository different than the current one, should we still offer to delete the local branch? If there happens to be one locally, I don't think we should ever delete it because it likely belongs to another (unrelated) repo.
| for _, s := range stubs { | ||
| http.StubResponse(s.ResponseCode, s.ResponseBody) | ||
| } | ||
| http.StubRepoResponse("OWNER", "REPO") |
There was a problem hiding this comment.
I noticed that this test already has http.StubRepoResponse("OWNER", "REPO") above. Is this extra stub necessary?
There was a problem hiding this comment.
Unfortunately yes, because we have to convert the ghrepo interface into an api.Repository interface which does another lookup.
After some internal discussion we're moving to this idea...
|
|
I think all the Local branches are never deleted when the repo flag is set |
| state | ||
| url | ||
| headRefName | ||
| mergeable |
There was a problem hiding this comment.
Is it necessary to request this field if we're never going to consume it when listing pull requests? This is a potentially expensive field as GitHub starts a background job to check mergeability.
| err := fmt.Errorf("%s Pull request #%d has conflicts and isn't mergeable ", utils.Red("!"), pr.Number) | ||
| return err | ||
| } else if pr.Mergeable == "UNKNOWN" { | ||
| err := fmt.Errorf("%s Pull request #%d can't be merged right now; try again in a few seconds", utils.Red("!"), pr.Number) |
There was a problem hiding this comment.
"UNKNOWN" usually means that mergeability is currently still being calculated. Instead of presenting this as an error, we could be optimistic and assume that the PR is OK to merge and allow the API merge operation to proceed—it has a chance of succeeding, and if it fails the API error message should let us know why.
| } else if pr.Mergeable == "UNKNOWN" { | ||
| err := fmt.Errorf("%s Pull request #%d can't be merged right now; try again in a few seconds", utils.Red("!"), pr.Number) | ||
| return err | ||
| } else if pr.State == "MERGED" { |
There was a problem hiding this comment.
I think we should check this state before any checks around the .Mergeable field. If the PR is already merged, its .Mergeable field might report "UNKOWN" or "CONFLICTING", in which case the current logic will trigger one of the error messages above, which would be misleading to the user.
| fmt.Fprintf(colorableOut(cmd), "%s %s pull request #%d\n", utils.Magenta("✔"), action, pr.Number) | ||
|
|
||
| if deleteBranch && !cmd.Flags().Changed("repo") { | ||
| repo, err := api.GitHubRepo(apiClient, baseRepo) |
There was a problem hiding this comment.
The repo value looks like it's only used inside a conditional, so we can initialize it inside that same conditional as well (skipping an API request if the condition is false).
|
Tried this in gh version 0.9.0: $ gh pr merge -dm 14
✔ Merged pull request #14
✔ Deleted branch docsIt did merge the pull request. It did not actually delete the |

This PR adds two features:
gh mergewith no merge method flags (--merge | --rebase | --squash) enters you into interactive mode.Here is how it looks:


Closes #373