-
Notifications
You must be signed in to change notification settings - Fork 7.8k
allow users to open up the last commit of a repository while using repo override flag #4845
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…t than the one they are currently in
mislav
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for taking this on!
api/queries_repo.go
Outdated
| DefaultBranchRef struct { | ||
| Target struct { | ||
| Commit struct { | ||
| History struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of reading History off of a Commit object, you can read its oid field which will be the commit SHA.
query ($repoOwner: String!, $repoName: String!) {
repository(owner: $repoOwner, name: $repoName) {
defaultBranchRef {
name
target {
... on Commit {
oid
messageHeadline
}
}
}
}
}
api/queries_repo.go
Outdated
| if err := gql.QueryNamed(context.Background(), "LastCommit", &responseData, variables); err != nil { | ||
| return nil, err | ||
| } | ||
| return (*git.Commit)(&responseData.Repository.DefaultBranchRef.Target.Commit.History.Edges[0].Node), nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels super weird to cast at unrelated struct into a git.Commit object in Go. I would rather copy the fields manually:
return &git.Commit{
Sha: ...,
Title: ...
}, ...
api/queries_repo.go
Outdated
| }, nil | ||
| } | ||
|
|
||
| func LastCommit(client *Client, repo ghrepo.Interface) (*git.Commit, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's unfortunate that now the api package has to import (and thus depend on) the whole git package just so it can return a git.Commit type. Ideally api and git packages should be entirely separated concerns, unaware of each others' existence. I have some ideas how to iron this out, but I don't think it's time yet, and since this isn't a pressing issue, let's leave this like it is and circle back later.
pkg/cmd/browse/browse.go
Outdated
| if err != nil { | ||
| return err | ||
| } | ||
| opts.LastCommit = func() (*git.Commit, error) { return api.LastCommit(api.NewClientFromHTTP(httpClient), baseRepo) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I see what you're doing here and it's a solid start, but I wonder if we can make this even more simpler & straightforward for the runBrowse() implementation. Right now, swapping out implementations of LastCommit like this is kind of awkward. Even though we pass around and assign funcs to struct fields quite often in gh codebase, I'm thinking that it has gone too far and that we should replace this approach with interfaces instead.
Ultimately, runBrowse() doesn't need to know about the type of lookup that will be performed. The only thing it wants to know is: what is the last commit for this repository? Of course, the definition of what "this repository" is ultimately depends on the --repo flag, but runBrowse() ideally should not be aware of whether --repo was used or not.
I propose to pass a concept of a "git client" to runBrowse via its opts struct. Something like:
type BrowseOptions struct {
GitClient gitClient
}
type gitClient interface {
LastCommit() (*git.Commit, error)
}What implements the gitClient interface? Right now there are no implementations, but you could create some:
- The first and obvious implementation is the one that consults
git.LastCommit()and therefore reads from the local git repository of the current directory; - The second implementation is the one that uses
api.LastCommit(httpClient, repo)to fetch commit information from the API.
Either of these implementations could be assigned to opts.GitClient by the command's RunE block based on whether or not the --repo flag has been passed. Then, runBrowse() can consume from the git client without knowing what type of a git client that is or where it reads from.
Thank you for the quick review! Assuming I read your review suggestions correctly: Lines 102 to 104 in afaf8d9
I like the interface because this same section seems closer to plain text now: Lines 102 to 104 in c06fa43
This still needs tests. I wasn't sure if the client implementations belonged in |
That's to be expected, since declaring interfaces and whole types to satisfy them is always going to be more work than defining anonymous funcs. The size of the code is not a code smell in it of itself, especially considering that I wanted you to take this approach so that we can extend it (as a follow up) to other commands that take
Eventually I do want this to live somewhere that's easily imported by other commands, so definitely outside of |
pkg/cmd/browse/browse.go
Outdated
| commit, err := opts.LastCommit() | ||
| if err == nil { | ||
| commit, err := opts.GitClient.LastCommit() | ||
| if err == nil && commit != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- If there was an error, that error should be surfaced to the user and the command aborted. Is there a reason to intentionally silence it?
- What are the cases when
commit == nil?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, commit should never be nil if err is nil.
I only silenced the error because that was how the code was before:
I think it might be silenced because the git error wouldn't be understandable.
fatal: ambiguous argument 'HEAD': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'
/usr/bin/git: exit status 128
Edit: if the retrieval part of LastCommit fails, maybe we should return a custom message, that way we can return err?
git.LastCommit: "a previous commit could not be found for this repository"
api.LastCommit: "a previous commit for OWNER/REPO could not be found"
pkg/cmd/browse/browse_test.go
Outdated
| opts: BrowseOptions{ | ||
| CommitFlag: true, | ||
| LastCommit: git.LastCommit, | ||
| GitClient: &localGitClient{}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One nice thing about GitClient now being an interface is that you can pass any fake implementation for tests, as long as it satisfies the interface. You can make a test git client that returns a static commit information on LastCommit without having to look it up over either HTTP or by shelling out to git. Then, you don't have to stub either HTTP calls nor git calls in tests anymore 🎉
mislav
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fantastic! Thank you for your work and your patience
fixes #4715
Given the two options in the issue, I thought it would be best to allow this functionality rather than to produce an error. I tried to minimize the footprint of the solution while doing so.