-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
fix: issue-5850 - Settle override conflicts between edges and propagate changes #7025
base: latest
Are you sure you want to change the base?
Changes from 19 commits
865070c
cd20196
0cad4e0
6b276f3
fff3072
e97176e
19155ed
dedbccf
4a83786
7ee2f2e
0917421
6d0ad42
67d7d5c
643cbea
c5f6e4a
6c9bbf9
63760d9
6111630
b42d4c8
7d97726
35e5790
35ce2f7
5630954
f859e92
29f0750
17ec457
4cde831
701b125
b119f67
3136b64
80126c7
c4b5338
9db1232
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -342,7 +342,28 @@ class Node { | |
} | ||
|
||
get overridden () { | ||
return !!(this.overrides && this.overrides.value && this.overrides.name === this.name) | ||
if (!this.overrides) { | ||
return false | ||
} | ||
if (!this.overrides.value) { | ||
return false | ||
} | ||
if (this.overrides.name !== this.name) { | ||
return false | ||
} | ||
|
||
// The overrides rule is for a package with this name, but some override rules only apply to specific | ||
// versions. To make sure this package was actually overridden, we check whether any edge going in | ||
// had the rule applied to it, in which case its overrides set is different than its source node. | ||
for (const edge of this.edgesIn) { | ||
if (this.overrides.isEqual(edge.overrides)) { | ||
if (!edge.overrides.isEqual(edge.from.overrides)) { | ||
return true | ||
} | ||
} | ||
} | ||
|
||
return false | ||
} | ||
|
||
get package () { | ||
|
@@ -820,9 +841,6 @@ class Node { | |
target.root = root | ||
} | ||
|
||
if (!this.overrides && this.parent && this.parent.overrides) { | ||
this.overrides = this.parent.overrides.getNodeRule(this) | ||
} | ||
// tree should always be valid upon root setter completion. | ||
treeCheck(this) | ||
if (this !== root) { | ||
|
@@ -1004,10 +1022,21 @@ class Node { | |
return false | ||
} | ||
|
||
// XXX need to check for two root nodes? | ||
if (node.overrides !== this.overrides) { | ||
return false | ||
// If this node has no dependencies, then it's irrelevant to check the override | ||
// rules of the replacement node. | ||
if (this.edgesOut.size) { | ||
// XXX need to check for two root nodes? | ||
if (node.overrides) { | ||
if (!node.overrides.isEqual(this.overrides)) { | ||
return false | ||
} | ||
} else { | ||
if (this.overrides) { | ||
return false | ||
} | ||
} | ||
} | ||
|
||
ignorePeers = new Set(ignorePeers) | ||
|
||
// gather up all the deps of this node and that are only depended | ||
|
@@ -1247,10 +1276,6 @@ class Node { | |
this[_changePath](newPath) | ||
} | ||
|
||
if (parent.overrides) { | ||
this.overrides = parent.overrides.getNodeRule(this) | ||
} | ||
|
||
// clobbers anything at that path, resets all appropriate references | ||
this.root = parent.root | ||
} | ||
|
@@ -1344,9 +1369,100 @@ class Node { | |
this.edgesOut.set(edge.name, edge) | ||
} | ||
|
||
addEdgeIn (edge) { | ||
recalculateOutEdgesOverrides () { | ||
// For each edge out propogate the new overrides through. | ||
for (const [, edge] of this.edgesOut) { | ||
This comment was marked as resolved.
Sorry, something went wrong. |
||
edge.reload(true) | ||
if (edge.to) { | ||
edge.to.updateOverridesEdgeInAdded(edge.overrides) | ||
} | ||
} | ||
} | ||
|
||
findSpecificOverrideSet (first, second) { | ||
This comment was marked as resolved.
Sorry, something went wrong. |
||
for (let overrideSet = second; overrideSet; overrideSet = overrideSet.parent) { | ||
if (overrideSet.isEqual(first)) { | ||
return second | ||
} | ||
} | ||
for (let overrideSet = first; overrideSet; overrideSet = overrideSet.parent) { | ||
if (overrideSet.isEqual(second)) { | ||
return first | ||
} | ||
} | ||
console.log('Conflicting override sets') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @wraithgar This case means that the same node has two edges with completely different override sets pointing to it. This log is useful for debugging, not sure if just to remove the line or there's some other convention used in the arborist. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
} | ||
|
||
updateOverridesEdgeInRemoved (otherOverrideSet) { | ||
// If this edge's overrides isn't equal to this node's overrides, then removing it won't change newOverrideSet later. | ||
if (!this.overrides || !this.overrides.isEqual(otherOverrideSet)) { | ||
return false | ||
} | ||
let newOverrideSet | ||
for (const edge of this.edgesIn) { | ||
if (newOverrideSet && edge.overrides) { | ||
newOverrideSet = this.findSpecificOverrideSet(edge.overrides, newOverrideSet) | ||
} else { | ||
newOverrideSet = edge.overrides | ||
} | ||
} | ||
if (this.overrides.isEqual(newOverrideSet)) { | ||
return false | ||
} | ||
this.overrides = newOverrideSet | ||
if (this.overrides) { | ||
// Optimization: if there's any override set at all, then no non-extraneous node has an empty override set. So if we temporarily have no | ||
// override set (for example, we removed all the edges in), there's no use updating all the edges out right now. Let's just wait until | ||
// we have an actual override set later. | ||
this.recalculateOutEdgesOverrides() | ||
} | ||
return true | ||
} | ||
|
||
// This logic isn't perfect either. When we have two edges in that have different override sets, then we have to decide which set is correct. | ||
// This function assumes the more specific override set is applicable, so if we have dependencies A->B->C and A->C | ||
// and an override set that specifies what happens for C under A->B, this will work even if the new A->C edge comes along and tries to change | ||
// the override set. | ||
// The strictly correct logic is not to allow two edges with different overrides to point to the same node, because even if this node can satisfy | ||
// both, one of its dependencies might need to be different depending on the edge leading to it. | ||
// However, this might cause a lot of duplication, because the conflict in the dependencies might never actually happen. | ||
updateOverridesEdgeInAdded (otherOverrideSet) { | ||
if (!otherOverrideSet) { | ||
// Assuming there are any overrides at all, the overrides field is never undefined for any node at the end state of the tree. | ||
// So if the new edge's overrides is undefined it will be updated later. So we can wait with updating the node's overrides field. | ||
return false | ||
} | ||
if (!this.overrides) { | ||
this.overrides = otherOverrideSet | ||
this.recalculateOutEdgesOverrides() | ||
return true | ||
} | ||
if (this.overrides.isEqual(otherOverrideSet)) { | ||
return false | ||
} | ||
const newOverrideSet = this.findSpecificOverrideSet(this.overrides, otherOverrideSet) | ||
if (newOverrideSet) { | ||
if (!this.overrides.isEqual(newOverrideSet)) { | ||
this.overrides = newOverrideSet | ||
this.recalculateOutEdgesOverrides() | ||
return true | ||
} | ||
return false | ||
} | ||
// This is an error condition. We can only get here if the new override set is in conflict with the existing. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @wraithgar This case means that the same node has two edges with completely different override sets pointing to it, which means that this node can't really decide which one is correct.
Currently I chose (1), but maybe it should at least show some kind of warning. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A hard-won lesson we learned with peer dependencies is that
So, unless there's a really elegant way to do This also leaves the door open later to implementing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hey @wraithgar, I've encountered a problem with throwing an exception here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Until clarity on the issue you can always There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, I checked the behavior of the algorithms here. @jdalton @wraithgar |
||
} | ||
|
||
deleteEdgeIn (edge) { | ||
this.edgesIn.delete(edge) | ||
if (edge.overrides) { | ||
this.overrides = edge.overrides | ||
this.updateOverridesEdgeInRemoved(edge.overrides) | ||
} | ||
} | ||
|
||
addEdgeIn (edge) { | ||
// We need to handle the case where the new edge in has an overrides field which is different from the current value. | ||
if (!this.overrides || !this.overrides.isEqual(edge.overrides)) { | ||
this.updateOverridesEdgeInAdded(edge.overrides) | ||
} | ||
|
||
this.edgesIn.add(edge) | ||
|
This comment was marked as resolved.
Sorry, something went wrong.
This comment was marked as resolved.
Sorry, something went wrong.