-
Notifications
You must be signed in to change notification settings - Fork 1k
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
[Updater] Implement an Operation capable of refreshing a Grouped Update PR. #7192
Conversation
# This method combines checking the job's `updating_a_pull_request` flag | ||
# with verification the dependencies involved remain the same. | ||
# | ||
# If the dependencies involved have changed, we should close the old PR | ||
# rather than supersede it as the new changes don't necessarily follow | ||
# from the previous ones; dependencies could have been removed from the | ||
# project, or pinned by other changes. | ||
def should_replace_existing_pr? | ||
return false unless job.updating_a_pull_request? | ||
|
||
# NOTE: Gradle, Maven and Nuget dependency names can be case-insensitive | ||
# and the dependency name injected from a security advisory often doesn't | ||
# match what users have specified in their manifest. | ||
updated_dependencies.map(&:name).map(&:downcase) != job.dependencies.map(&:downcase) | ||
end | ||
|
||
def matches_existing_pr? | ||
!!existing_pull_request | ||
end |
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 didn't do it as part of this PR as we don't really have sufficient test coverage to be confident in making the change, but these methods should be used in preference in our other Refresh*
jobs in future to DRY them out further.
def job_group_name | ||
job.dependency_group_to_refresh | ||
end |
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.
My thinking is that in future we might have a dependency_group_to_create
key to allow us to just build a single group if we want to change the workload profile of group/version updates.
For now it might make sense to remove this indirection but I wanted to shorten the reference in a couple of places
# | ||
module Dependabot | ||
class Updater | ||
module GroupUpdateCreation |
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 considered two alternative approaches:
- Let's make a GroupUpdateOperation base class!
- Let's create a new GroupUpdaterBuilder object we instantiate and call!
I ended up 👎🏻 on both of those as I'd rather not add inherence into the Updater given the problems we've had with branching code and I wasn't totally sold on the value of a GroupUpdaterBuilder
object as a separate component we might want to add tests on specifically.
I ended up defaulting to a coarse composition approach with a view that we can revisit this later as there's still some DRYing out and test improvement to be done on the other Operations and the right pattern is likely to become clearer with more examples.
acfc5a2
to
9faebe9
Compare
PR to make the CLI compatible: dependabot/cli#105 |
# NOTE: Gradle, Maven and Nuget dependency names can be case-insensitive | ||
# and the dependency name injected from a security advisory often doesn't | ||
# match what users have specified in their manifest. | ||
updated_dependencies.map(&:name).map(&:downcase) != job.dependencies.map(&:downcase) |
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.
job.dependencies is a list of Dependabot::Dependency objects, so I think you would also need to map across the job dependency names
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 caught me out too, dependency_snapshot.job_dependencies
are hydrated Dependabot::Dependency
objects, but job.dependencies
is just the raw array from the job definition file.
We should probably do a naming cleanup pass on this to make that more obvious as I mocked tests incorrectly with objects as I had the same read as you and then had a surprise when I tried it with the CLI.
rules: | ||
patterns: | ||
- "*" | ||
dependency-group-to-refresh: everything-everywhere-all-at-once |
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.
<3
else | ||
job.existing_pull_requests.find { |pr| Set.new(pr) == updated_dependencies_set } | ||
end |
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.
We may want to break out the "existing grouped update PR?" check into its own method that we can call in #upsert_pull_request
Users could define a group that has a single dependency and matches an existing ungrouped PR, causing dependency_change.matches_existing_pr?
to return true.
If that happens, then we would close the existing ungrouped dependency PR in favor of a grouped one (which could be okay but confusing from a user perspective?)
If we introduce a #matches_existing_grouped_pr?
method then we can make sure we only upsert grouped updates during this operation
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.
Users could define a group that has a single dependency and matches an existing ungrouped PR, causing dependency_change.matches_existing_pr? to return true.
This shouldn't happen at present as grouped_updated?
on line 73 would be true for the grouped update meaning it wouldn't check the existing_pull_requests
set for any matches, so it should keep both job types in their own lane.
We might want to consider doing a one-time check between strategies when users change their config to add a group ( i.e. close any old single PRs that match the new group ), but I think we can leave that for now and allow it to be managed manually so it's ok not to do any cross-checking and do a hard fork on grouped_updated?
being true/false for the current job.
|
||
it "closes the pull request" do | ||
expect(mock_error_handler).not_to receive(:handle_dependabot_error) | ||
expect(mock_service).to receive(:close_pull_request).with(["dummy-pkg-b"], :up_to_date) |
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 doubled checked this and dummy-pkg-b is the only dependency in the "updated" gemfiles, but when using the "original" gemfiles dummy-pkg-c also updates as a transitive dependency 👍🏾
updater/spec/dependabot/updater/operations/refresh_group_update_pull_request_spec.rb
Outdated
Show resolved
Hide resolved
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.
In general this looks great! 🌟
I did leave a few comments, but other than jobs.dependencies being a list of Dependabot::Dependency objects, I don't think there is anything blocking.
I would be okay with this merging as-is and then addressing the comments in a follow-up PR
9faebe9
to
1366d67
Compare
I have zero context on this PR as I haven't closely followed the changes you guys are working on... but I saw this comment and wanted to point out that for any breaking changes in the |
…pdates-can-refresh [Updater] Implement an Operation capable of refreshing a Grouped Update PR.
"Refreshing" a Dependabot pull request essentially has three distinct outcomes, we recompute the DependencyChange on the current head of the target branch and:
This strategy is consistent with our normal single-dependency PRs with one exception; instead of looking at all existing PRs to determine whether it is updating/replacing/superseding it only considers PRs for the group rule that job is targeting.
Example output
Significant changes
This PR introduces two new attributes to the "job definition"
existing_group_pull_requests
: this serialises any grouped PRs separately to 'normal' PRs so we don't have unexpected interactions between single- and group-updatesexisting_pull_requests
key for simplicity, but it would be a breaking change so we should do it incrementally.dependency-group-to-refresh
: the name of the dependency group to be refreshed