-
Notifications
You must be signed in to change notification settings - Fork 17
feat: add ability to detect current project #242
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
base: master
Are you sure you want to change the base?
Conversation
Respect remote order when inferring project —
|
In previous implementations, we would infer the current project by getting a list of all remote URLs and then choosing the first project that matched one of them, which isn't necessarily the correct one. In this commit, we add some logic to try to make the best choice possible, using the following logic: 1. Get a list of all remotes, with their names and URLs 2. Get a list of all projects from Semaphore 3. Make a list of all remotes where there is a project configured for that remote's URL (so all remotes wihch have an associated project in Semaphore) Once we have that, we choose one based on the following logic: 1. If there's only one, choose that 2. If the user has run `gh repo set-default` in this repository, use the repository they chose at that point. 3. If there's one named "origin" choose that one. This is git's default remote name when you clone. 4. If there's one named "upstream" choose that one. This is what the `gh` CLI will rename your "origin" repo to when you create a fork for a new PR. 5. If there's more than one remote but they all have the same URL, then just use whichever project matches that URL. 6. If we still can't decide, print an error to the user; they're probably doing something weird.
|
@hamir-suspect Alright, two updates:
One decision I'd like input on: currently, if the user has run
In other words, they're creating PRs against project B but We can flip this logic so that if they have a matching |
|
Hey @danudey totally agree with the current logic—keeping the gh repo set-default remote as the first choice. Just a small nit-pick and we can merge this |
Co-authored-by: Amir Hasanbasic <43892661+hamir-suspect@users.noreply.github.com>
| return &gitRemote, nil | ||
| } | ||
| } | ||
| return &GitRemote{}, fmt.Errorf("no remote matching %s found in remote list") |
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.
You are missing the parameter for the fmt.Errorf, I think it would be remoteNameOrUrl
| } | ||
|
|
||
| fields := strings.Fields(lines[0]) | ||
| remoteName := strings.Split(fields[0], ".")[1] |
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.
You are accessing the position 1 of the array without checking the length of the array. Please do the same thing you did with line (split+len)
| for _, line := range lines { | ||
| fields := strings.Fields(line) | ||
| keyFields := strings.Split(line, ".") | ||
| remoteName := keyFields[1] |
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.
Here we have the same potential problem as well, please add a length check.
| // If we only got one remote with a configured project, return it; | ||
| // alternately, if we got multiple, return the alphabetically first | ||
| // one. | ||
| if len(gitRemotes) == 1 { | ||
| return gitRemotes[0].Project, 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.
Can you explain to me this comment please, how is the gitRemotes sorted?
| lines := strings.Split(strings.TrimSpace(string(out)), "\n") | ||
|
|
||
| projectClient := client.NewProjectV1AlphaApi() | ||
| projects, err := projectClient.ListProjects() |
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.
You are missing an error check here
|
|
||
| type GitRemoteList []GitRemote | ||
|
|
||
| func (grl GitRemoteList) Contains(remoteNameOrUrl string) bool { |
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.
I couldn't find anywhere where this method is used, if this is a leftover please remove or tell where it is/will be used.
| Long: `Determine the Semaphore project associated with this repository. | ||
|
|
||
| Resolution order: | ||
| 1. A remote selected by 'gh repo set-default', if present. | ||
| 2. The 'origin' remote, when configured. | ||
| 3. The 'upstream' remote, when configured. | ||
| 4. Any remaining remote whose URL is shared by all candidates. | ||
| 5. An explicit error when multiple distinct URLs remain.`, |
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.
nit: this long string has whitespaces before each line which I think wasn't intentional, please correct me if I'm wrong.
| fields := strings.Fields(line) | ||
| keyFields := strings.Split(line, ".") | ||
| remoteName := keyFields[1] | ||
| url := fields[1] |
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.
To improve readability and validation we could extract this to a function and do some more validation of the expected formatting of the string that we expect.
But I'm okay with maintaining this style and just fixing the length check.
| utils.Check(err) | ||
| fmt.Println(string(jsonBody)) | ||
| } else { | ||
| fmt.Println(project.Metadata.Id, project.Metadata.Name, project.Spec.Repository.Url) |
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.
nit: we could format this in some way that is easy to split the string
| for _, line := range lines { | ||
| fields := strings.Fields(line) | ||
| keyFields := strings.Split(line, ".") | ||
| remoteName := keyFields[1] | ||
| url := fields[1] | ||
|
|
||
| for _, proj := range projects.Projects { | ||
| if proj.Spec.Repository.Url == url { | ||
| remotes = append(remotes, GitRemote{Name: remoteName, URL: url, Project: proj}) | ||
| break | ||
| } | ||
| } | ||
|
|
||
| } |
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.
This part of the code is O(n*m) because of the nested loops, you could use maps to improve the complexity to O(n+m).
I think this is not that much about performance but for readability. But I can see some cases that a few ms can compound in the long run.
|
Hey @danudey, I left some comments of things that we need to fix. We can discuss the nit picks and fixes that aren't critical in your view. Also thank you for submitting a PR, few free to tag me with any question that you have. |
The Semaphore CLI tool has the ability to detect the current project, but there are a few caveats:
originis the upstream remote; this is not a safe assumption because when creating a fork theghcli tool will rename the upstream remote toupstreamand set the new fork to beorigin.This means that if a user needs the current project ID (e.g. to trigger a pipeline via the API), they must get the list of all remote URLs themselves, get the list of all projects themselves, find a match between the two, and then call
sem get project <project name>to get the project's ID.This PR does a few things (updated from the previous implementation):
First, we add a new function
getAllGitRemotesAndProjects(), which does the following:git configSecondly, we also add a small helper function,
getGitHubBaseRemoteName(), which checks the repository's git configuration viagit configto see if the user has rungh repo set-defaultto choose a default remote.Thirdly, we refactor
InferProject()to callgetAllGitRemotesAndProjects()and then choose one of the results with the following detection logic:gh repo set-defaultand that remote is in the list, use thatoriginrepo, use that; if not but there's anupstreamrepo, use thatThe original
InferProject()simply chose theoriginremote and used that, so this new logic will cover those cases as well, and thus shouldn't cause issues with the rest of the codebase.Lastly, we add a new
sem get current_project(aliassem get cur) which simply prints out the project's metadata:Originally I was planning on highlighting the current project when a user did
sem get projectwithout an argument, but that ended up feeling messier than just having a separate command for it which a user could parse.Edit: Added a
--jsonflag tocurrent_projectto format the output as JSON (which includes far more data).Edit 2: Bumped golang version from 1.20 to 1.21 to get the
slicespackage (should go to 1.25 IMHO but this is a minimal change), and added-buildvcs=falseto work around git permissions warnings in docker container when runningmake buildEdit 3: Completely rewrote almost everything into a vastly simplified and yet more complicated implementation.