Skip to content

Commit f680afc

Browse files
authored
Merge pull request #5817 from opsmill/pog-merge-changelog-conflict-attributes-IFC-1256
Add support in DiffChangelogCollector for attribute value conflicts
2 parents 0a93e0b + 878d03a commit f680afc

File tree

3 files changed

+94
-5
lines changed

3 files changed

+94
-5
lines changed

backend/infrahub/core/changelog/diff.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from infrahub.core.constants import DiffAction, RelationshipCardinality
99
from infrahub.core.constants.database import DatabaseEdgeType
10+
from infrahub.core.diff.model.path import ConflictSelection
1011

1112
from .models import (
1213
AttributeChangelog,
@@ -21,6 +22,7 @@
2122
from infrahub.core.diff.model.path import (
2223
EnrichedDiffAttribute,
2324
EnrichedDiffNode,
25+
EnrichedDiffProperty,
2426
EnrichedDiffRelationship,
2527
EnrichedDiffRoot,
2628
)
@@ -86,8 +88,9 @@ def _process_node_attribute(
8688
match attr_property.property_type:
8789
case DatabaseEdgeType.HAS_VALUE:
8890
# TODO deserialize correct value type from string
89-
changelog_attribute.value = attr_property.new_value
90-
changelog_attribute.value_previous = attr_property.previous_value
91+
if _keep_branch_update(diff_property=attr_property):
92+
changelog_attribute.value = attr_property.new_value
93+
changelog_attribute.value_previous = attr_property.previous_value
9194
case DatabaseEdgeType.IS_PROTECTED:
9295
changelog_attribute.add_property(
9396
name="is_protected",
@@ -113,7 +116,7 @@ def _process_node_attribute(
113116
value_previous=attr_property.previous_value,
114117
)
115118

116-
node.attributes[attribute.name] = changelog_attribute
119+
node.add_attribute(attribute=changelog_attribute)
117120

118121
def _process_node_relationship(self, node: NodeChangelog, relationship: EnrichedDiffRelationship) -> None:
119122
match relationship.cardinality:
@@ -229,4 +232,10 @@ def collect_changelogs(self) -> Sequence[tuple[DiffAction, NodeChangelog]]:
229232
for node in self._diff.nodes
230233
if node.action != DiffAction.UNCHANGED
231234
]
232-
return changelogs
235+
return [(action, node_changelog) for action, node_changelog in changelogs if node_changelog.has_changes]
236+
237+
238+
def _keep_branch_update(diff_property: EnrichedDiffProperty) -> bool:
239+
if diff_property.conflict and diff_property.conflict.selected_branch == ConflictSelection.BASE_BRANCH:
240+
return False
241+
return True

backend/infrahub/core/changelog/models.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,8 @@ def delete_relationship(self, relationship: Relationship) -> None:
304304
)
305305

306306
def add_attribute(self, attribute: AttributeChangelog) -> None:
307-
self.attributes[attribute.name] = attribute
307+
if attribute.has_updates:
308+
self.attributes[attribute.name] = attribute
308309

309310
def add_relationship(
310311
self, relationship: RelationshipCardinalityOneChangelog | RelationshipCardinalityManyChangelog

backend/tests/unit/core/changelog/test_diff.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
1+
from typing import Literal
2+
from unittest.mock import AsyncMock
3+
4+
import pytest
5+
16
from infrahub.core.branch import Branch
27
from infrahub.core.changelog.diff import DiffChangelogCollector
38
from infrahub.core.changelog.models import RelationshipCardinalityManyChangelog, RelationshipCardinalityOneChangelog
49
from infrahub.core.constants import DiffAction
510
from infrahub.core.diff.coordinator import DiffCoordinator
11+
from infrahub.core.diff.data_check_synchronizer import DiffDataCheckSynchronizer
612
from infrahub.core.diff.merger.merger import DiffMerger
13+
from infrahub.core.diff.model.path import ConflictSelection
14+
from infrahub.core.diff.repository.repository import DiffRepository
715
from infrahub.core.initialization import create_branch
816
from infrahub.core.manager import NodeManager
917
from infrahub.core.node import Node
@@ -133,3 +141,74 @@ async def test_merge_diff_changelogs(db: InfrahubDatabase, default_branch, car_p
133141
assert c2_changelog.relationships["owner"].properties["owner"].value_previous is None
134142
assert c2_changelog.relationships["owner"].properties["source"].value == p1.id
135143
assert c2_changelog.relationships["owner"].properties["source"].value_previous is None
144+
145+
146+
class TestConflict:
147+
async def _get_diff_coordinator(self, db: InfrahubDatabase, branch: Branch) -> DiffCoordinator:
148+
component_registry = get_component_registry()
149+
diff_coordinator = await component_registry.get_component(DiffCoordinator, db=db, branch=branch)
150+
diff_coordinator.data_check_synchronizer = AsyncMock(spec=DiffDataCheckSynchronizer)
151+
return diff_coordinator
152+
153+
async def _get_diff_merger(self, db: InfrahubDatabase, branch: Branch) -> DiffMerger:
154+
component_registry = get_component_registry()
155+
return await component_registry.get_component(DiffMerger, db=db, branch=branch)
156+
157+
@pytest.fixture
158+
async def diff_repository(self, db: InfrahubDatabase, default_branch: Branch) -> DiffRepository:
159+
component_registry = get_component_registry()
160+
return await component_registry.get_component(DiffRepository, db=db, branch=default_branch)
161+
162+
@pytest.mark.parametrize(
163+
"conflict_selection,expected_value",
164+
[(ConflictSelection.BASE_BRANCH, "John-main"), (ConflictSelection.DIFF_BRANCH, "John-branch")],
165+
)
166+
async def test_diff_and_merge_with_attribute_value_conflict(
167+
self,
168+
db: InfrahubDatabase,
169+
default_branch: Branch,
170+
diff_repository: DiffRepository,
171+
person_john_main: Node,
172+
person_jane_main: Node,
173+
person_alfred_main: Node,
174+
car_accord_main: Node,
175+
conflict_selection: ConflictSelection,
176+
expected_value: Literal["John-main", "John-branch"],
177+
):
178+
branch2 = await create_branch(db=db, branch_name="branch2")
179+
john_main = await NodeManager.get_one(db=db, id=person_john_main.id)
180+
john_main.name.value = "John-main"
181+
await john_main.save(db=db)
182+
john_branch = await NodeManager.get_one(db=db, branch=branch2, id=person_john_main.id)
183+
john_branch.name.value = "John-branch"
184+
await john_branch.save(db=db)
185+
186+
at = Timestamp()
187+
diff_coordinator = await self._get_diff_coordinator(db=db, branch=branch2)
188+
enriched_diff_metadata = await diff_coordinator.update_branch_diff(
189+
base_branch=default_branch, diff_branch=branch2
190+
)
191+
enriched_diff = await diff_repository.get_one(
192+
diff_branch_name=enriched_diff_metadata.diff_branch_name, diff_id=enriched_diff_metadata.uuid
193+
)
194+
conflicts_map = enriched_diff.get_all_conflicts()
195+
assert len(conflicts_map) == 1
196+
conflict = next(iter(conflicts_map.values()))
197+
await diff_repository.update_conflict_by_id(conflict_id=conflict.uuid, selection=conflict_selection)
198+
diff_merger = await self._get_diff_merger(db=db, branch=branch2)
199+
diff = await diff_merger.merge_graph(at=at)
200+
diff_events = DiffChangelogCollector(diff=diff, db=db, branch=branch2)
201+
events = diff_events.collect_changelogs()
202+
203+
match conflict_selection:
204+
case ConflictSelection.BASE_BRANCH:
205+
# When we want to keep the conflict in the base branch we don't expect to see any updates after the merge
206+
assert len(events) == 0
207+
case ConflictSelection.DIFF_BRANCH:
208+
# Expect to see changes on the diff branch when we keep changes from that branch
209+
assert len(events) == 1
210+
event = events[0]
211+
action, node_changelog = event
212+
assert action == DiffAction.UPDATED
213+
assert node_changelog.attributes["name"].value == "John-branch"
214+
assert node_changelog.attributes["name"].value_previous == "John"

0 commit comments

Comments
 (0)