diff --git a/api/segments/models.py b/api/segments/models.py
index 5aeae31a8829..8a75641eeb0a 100644
--- a/api/segments/models.py
+++ b/api/segments/models.py
@@ -189,106 +189,84 @@ def deep_clone(self) -> "Segment":
 
         return cloned_segment
 
-    def update_segment_with_matches_from_current_segment(
-        self, current_segment: "Segment"
+    def update_segment_with_matches_from_target_segment(
+        self, target_segment: "Segment"
     ) -> bool:
         """
-        Assign matching rules of the calling object (i.e., self) to the rules
-        of the given segment (i.e., current_segment) in order to update the
-        rules, subrules, and conditions of the calling object. This is done
-        from the context of a change request related to the calling object.
-
-        This method iterates through the rules of the provided `Segment` and
-        attempts to match them to the calling object's (i.e., self) rules and
-        sub-rules, updating versioning where matches are found.
-
-        This is done in order to set the `version_of` field for matched rules
-        and conditions so that the frontend can more reliably diff rules and
-        conditions between a change request for a the current segment and the
-        calling object (i.e., self) itself.
+        This method handles the matching of two segments for the purpose of
+        diffing them, for example in the context of a change request. We take
+        the segment provided as an argument (`target_segment`), compare its
+        rules to this segment instance (`self`) and update any rules (and,
+        indirectly, conditions) on this segment instance to have a pointer to
+        the target segment's rules (and conditions).
 
         Args:
-            current_segment (Segment): The segment whose rules are being matched
-                                       against the current object's rules which
-                                       will have its rules and conditions updated.
+            target_segment (Segment): The target segment we are matching against.
 
         Returns:
             bool:
-                - `True` if any rules or sub-rules match between the calling
-                  object (i.e., self) and the current segment.
+                - `True` if any rules or sub-rules match between this instance
+                   (i.e. self) and the target segment.
                 - `False` if no matches are found.
 
         Process:
-            1. Retrieve all rules associated with the calling object and the segment.
-            2. For each rule in the current segment:
-               - Check its sub-rules against the calling object's rules and sub-rules.
+            1. Retrieve all rules for both segments.
+            2. For each rule in the target segment:
+               - Check its sub-rules against this instance's rules and sub-rules.
                - A match is determined if the sub-rule's type and properties align
-                 with those of the calling object's sub-rules.
+                 with those of this instance's sub-rules.
                - If a match is found:
-                 - Update the `version_of` field for the matched sub-rule and rule.
+                 - Update the `version_of` field for this instance's matched sub-rule
+                   and rule. Do not modify the target segment rules.
                  - Track the matched rules and sub-rules to avoid duplicate processing.
             3. Perform a bulk update on matched rules and sub-rules to persist
                versioning changes.
 
         Side Effects:
-            - Updates the `version_of` field for matched rules and sub-rules for the
-              calling object (i.e., self).
+            - Updates the `version_of` field for matched rules and sub-rules belonging
+              to this instance (i.e., self).
             - Indirectly updates the `version_of` field on sub-rules' conditions.
         """
 
-        modified_rules = self.rules.all()
+        rules = self.rules.all()
+
         matched_rules = set()
         matched_sub_rules = set()
 
-        for current_rule in current_segment.rules.all():
-            for current_sub_rule in current_rule.rules.all():
-
-                sub_rule_matched = False
-                for modified_rule in modified_rules:
-                    # Because we must proceed to the next current_sub_rule
-                    # to get the next available match since it has now been
-                    # matched to a candidate modified_sub_rule we set the
-                    # sub_rule_matched bool to track the state between
-                    # iterations. Otherwise different rules would have the
-                    # same value for their version_of field.
-                    if sub_rule_matched:
-                        break
+        for target_rule in target_segment.rules.all():
+            for target_sub_rule in target_rule.rules.all():
 
-                    # Because a segment's rules can only have subrules that match
-                    # if the segment-level rules also match, we need to ensure that
-                    # the currently matched rules correspond to the current rule.
-                    # Consider a scenario where a subrule's version_of attribute
-                    # points to a different subrule, whose owning rule differs
-                    # from the subrule's version_of's parent rule. Such a mismatch
-                    # would lead to inconsistencies and unintended behavior.
-                    if (
-                        modified_rule in matched_rules
-                        and modified_rule.version_of != current_rule
-                    ):
+                target_sub_rule_matched = False
+
+                for rule in rules:
+                    if target_rule.type != rule.type:
                         continue
 
-                    # To eliminate false matches we force the types
-                    # to be the same for the rules.
-                    if current_rule.type != modified_rule.type:
+                    if target_sub_rule_matched:
+                        # We are trying to match 1:1, so we only allow a single
+                        # match for each target sub-rule.
+                        break
+
+                    # If we've already matched at least one of the rule's sub-rules
+                    # (and hence, the rule has been added to matched_rules), then we
+                    # need to ensure that any subsequent matches belong to the same
+                    # parent rule.
+                    if rule in matched_rules and rule.version_of != target_rule:
                         continue
 
-                    for modified_sub_rule in modified_rule.rules.all():
-                        # If a subrule has already been matched,
-                        # we avoid assigning conditions since it
-                        # has already been handled.
-                        if modified_sub_rule in matched_sub_rules:
-                            continue
-
-                        # If a subrule matches, we assign the parent
-                        # rule and the subrule together.
-                        if modified_sub_rule.assign_conditions_if_matching_rule(
-                            current_sub_rule
-                        ):
-                            modified_sub_rule.version_of = current_sub_rule
-                            sub_rule_matched = True
-                            matched_sub_rules.add(modified_sub_rule)
-                            modified_rule.version_of = current_rule
-                            matched_rules.add(modified_rule)
+                    for sub_rule in rule.rules.exclude(
+                        id__in=[r.id for r in matched_sub_rules]
+                    ):
+                        if sub_rule.assign_conditions_if_matching_rule(target_sub_rule):
+                            target_sub_rule_matched = True
+
+                            # If a subrule matches, we assign the parent
+                            # rule and the subrule together.
+                            sub_rule.version_of = target_sub_rule
+                            rule.version_of = target_rule
+
+                            matched_sub_rules.add(sub_rule)
+                            matched_rules.add(rule)
                             break
 
         SegmentRule.objects.bulk_update(
@@ -381,98 +359,64 @@ def get_segment(self):
         return rule.segment
 
     def assign_conditions_if_matching_rule(  # noqa: C901
-        self, current_sub_rule: "SegmentRule"
+        self, target_rule: "SegmentRule"
     ) -> bool:
         """
-        Determines whether the current object matches the given rule
-        and assigns conditions with the `version_of` field.
-
-        These assignments are done in order to allow the frontend to
-        provide a diff capability during change requests for segments.
-        By knowing which version a condition is for the frontend can
-        show a more accurate diff between the segment and the change request.
-
-        This method compares the type and conditions of the current object with
-        the specified `SegmentRule` to determine if they are compatible.
+        This method handles the matching of two rules for the purpose of
+        diffing them, for example in the context of a change request for a
+        given segment. We take the rule provided as an argument (`target_rule`),
+        and compare its conditions to this instance's conditions.
 
         Returns:
             bool:
-                - `True` if the calling object's (i.e., self) type matches the rule's type
+                - `True` if this instance's (i.e. self) type matches the rule's type
                   and the conditions are compatible.
                 - `False` if the types do not match or no conditions are compatible.
 
-        Process:
-            1. If the types do not match, return `False`.
-            2. If both the rule and calling object (i.e., self) have no conditions, return `True`.
-            3. Compare each condition in the rule against the calling object's (i.e., self) conditions:
-               - First match conditions that are an exact match of property, operator,
-                 and value.
-               - A condition matches if the `property` attributes are equal or if there
-                 is no property but has a matching operator.
-               - Mark matched conditions and update the versioning.
-            4. Return `True` if at least one condition matches; otherwise, return `False`.
-
         Side Effects:
             Updates the `version_of` field for matched conditions using a bulk update for the
-            conditions of the calling object (i.e., self).
+            conditions of this instance (i.e. self).
         """
 
-        if current_sub_rule.type != self.type:
+        if target_rule.type != self.type:
             return False
 
-        current_conditions = current_sub_rule.conditions.all()
-        modified_conditions = self.conditions.all()
+        target_conditions = target_rule.conditions.all()
+        conditions = self.conditions.all()
 
-        if not current_conditions and not modified_conditions:
+        if not target_conditions and not conditions:
             # Empty rule with the same type matches.
             return True
 
         matched_conditions = set()
 
         # In order to provide accurate diffs we first go through the conditions
-        # and collect conditions that have matching values (property, operator, value).
-        for current_condition in current_conditions:
-            for modified_condition in modified_conditions:
-                if modified_condition in matched_conditions:
-                    continue
-                if (
-                    current_condition.property == modified_condition.property
-                    and current_condition.operator == modified_condition.operator
-                    and current_condition.value == modified_condition.value
+        # and collect conditions that are exact matches (i.e. have not been modified)
+        for target_condition in target_conditions:
+            for condition in conditions:
+                if (condition not in matched_conditions) and condition.is_exact_match(
+                    target_condition
                 ):
-                    matched_conditions.add(modified_condition)
-                    modified_condition.version_of = current_condition
+                    matched_conditions.add(condition)
+                    condition.version_of = target_condition
                     break
 
-        # Next we go through the collection again and collect matching conditions
-        # with special logic to collect conditions that have no properties based
-        # on their operator equivalence.
-        for current_condition in current_conditions:
-            for modified_condition in modified_conditions:
-                if modified_condition in matched_conditions:
-                    continue
-                if not current_condition.property and not modified_condition.property:
-                    if current_condition.operator == modified_condition.operator:
-                        matched_conditions.add(modified_condition)
-                        modified_condition.version_of = current_condition
-                        break
-
-                elif current_condition.property == modified_condition.property:
-                    matched_conditions.add(modified_condition)
-                    modified_condition.version_of = current_condition
+        # Next we go through the collection again and collect conditions that have
+        # been modified from the target condition.
+        for target_condition in target_conditions:
+            for condition in conditions:
+                if (condition not in matched_conditions) and condition.is_partial_match(
+                    target_condition
+                ):
+                    matched_conditions.add(condition)
+                    condition.version_of = target_condition
                     break
 
-        # If the subrule has no matching conditions we consider the response to
-        # be False, as the subrule could be a better match for some other candidate
-        # subrule, so the calling method can try the next subrule available.
-        if not matched_conditions:
-            return False
-
-        Condition.objects.bulk_update(matched_conditions, fields=["version_of"])
+        if matched_conditions:
+            Condition.objects.bulk_update(matched_conditions, fields=["version_of"])
+            return True
 
-        # Since the subrule has at least partial condition overlap, we return True
-        # for the match indicator.
-        return True
+        return False
 
     def deep_clone(self, cloned_segment: Segment) -> "SegmentRule":
         if self.rule:
@@ -612,6 +556,30 @@ def get_delete_log_message(self, history_instance) -> typing.Optional[str]:
     def get_audit_log_related_object_id(self, history_instance) -> int:
         return self._get_segment().id
 
+    def is_exact_match(self, other: "Condition") -> bool:
+        """
+        A condition is considered an exact match for another condition
+        with regard to the diffing logic, if all of `property`, `operator`,
+        and `value` exactly match.
+        """
+        return (
+            self.property == other.property
+            and self.operator == other.operator
+            and self.value == other.value
+        )
+
+    def is_partial_match(self, other: "Condition") -> bool:
+        """
+        A condition is considered a partial match for another condition
+        if both `property` values are none, and the operator matches
+        (for example, percentage split operator conditions), or
+        if the property exactly matches.
+        """
+        if self.property is None and other.property is None:
+            return self.operator == other.operator
+
+        return self.property == other.property
+
     def _get_segment(self) -> Segment:
         """
         Temporarily cache the segment on the condition object to reduce number of queries.
diff --git a/api/tests/unit/projects/test_unit_projects_views.py b/api/tests/unit/projects/test_unit_projects_views.py
index 2c7243fb9125..7da3e75dd144 100644
--- a/api/tests/unit/projects/test_unit_projects_views.py
+++ b/api/tests/unit/projects/test_unit_projects_views.py
@@ -177,7 +177,7 @@ def test_can_list_project_permission(client: APIClient, project: Project) -> Non
     # Then
     assert response.status_code == status.HTTP_200_OK
     # Hard code how many permissions we expect there to be.
-    assert len(response.json()) == 9
+    assert len(response.json()) == 10
 
     returned_supported_permissions = [
         permission["key"]
diff --git a/api/tests/unit/segments/test_unit_segments_models.py b/api/tests/unit/segments/test_unit_segments_models.py
index e4e27d326346..c8e6337b0a9c 100644
--- a/api/tests/unit/segments/test_unit_segments_models.py
+++ b/api/tests/unit/segments/test_unit_segments_models.py
@@ -575,7 +575,7 @@ def test_saving_condition_with_version_of_set(segment: Segment) -> None:
     assert condition2.version_of == condition1
 
 
-def test_update_segment_with_matches_from_current_segment_with_two_levels_of_rules_and_two_conditions(
+def test_update_segment_with_matches_from_target_segment_with_two_levels_of_rules_and_two_conditions(
     project: Project,
 ) -> None:
     # Given
@@ -599,7 +599,7 @@ def test_update_segment_with_matches_from_current_segment_with_two_levels_of_rul
         operator=PERCENTAGE_SPLIT, value=0.2, rule=rule4
     )
     # When
-    segment1.update_segment_with_matches_from_current_segment(segment2)
+    segment1.update_segment_with_matches_from_target_segment(segment2)
 
     # Then
     rule1.refresh_from_db()
@@ -620,7 +620,7 @@ def test_update_segment_with_matches_from_current_segment_with_two_levels_of_rul
     assert condition2.version_of is None
 
 
-def test_update_segment_with_matches_from_current_segment_with_condition_operator_mismatch(
+def test_update_segment_with_matches_from_target_segment_with_condition_operator_mismatch(
     project: Project,
 ) -> None:
     # Given
@@ -654,7 +654,7 @@ def test_update_segment_with_matches_from_current_segment_with_condition_operato
     )
 
     # When
-    segment1.update_segment_with_matches_from_current_segment(segment2)
+    segment1.update_segment_with_matches_from_target_segment(segment2)
 
     # Then
     rule1.refresh_from_db()
@@ -673,7 +673,7 @@ def test_update_segment_with_matches_from_current_segment_with_condition_operato
     assert condition2.version_of is None
 
 
-def test_update_segment_with_matches_from_current_segment_with_conditions_not_matching(
+def test_update_segment_with_matches_from_target_segment_with_conditions_not_matching(
     project: Project,
 ) -> None:
     # Given
@@ -706,7 +706,7 @@ def test_update_segment_with_matches_from_current_segment_with_conditions_not_ma
     )
 
     # When
-    segment1.update_segment_with_matches_from_current_segment(segment2)
+    segment1.update_segment_with_matches_from_target_segment(segment2)
 
     # Then
     rule1.refresh_from_db()
@@ -725,7 +725,7 @@ def test_update_segment_with_matches_from_current_segment_with_conditions_not_ma
     assert condition2.version_of is None
 
 
-def test_update_segment_with_matches_from_current_segment_mismatched_rule_type(
+def test_update_segment_with_matches_from_target_segment_mismatched_rule_type(
     project: Project,
 ) -> None:
     # Given
@@ -748,7 +748,7 @@ def test_update_segment_with_matches_from_current_segment_mismatched_rule_type(
         operator=PERCENTAGE_SPLIT, value=0.2, rule=rule4
     )
     # When
-    segment1.update_segment_with_matches_from_current_segment(segment2)
+    segment1.update_segment_with_matches_from_target_segment(segment2)
 
     # Then
     rule1.refresh_from_db()
@@ -767,7 +767,7 @@ def test_update_segment_with_matches_from_current_segment_mismatched_rule_type(
     assert condition2.version_of is None
 
 
-def test_update_segment_with_matches_from_current_segment_mismatched_sub_rule_type(
+def test_update_segment_with_matches_from_target_segment_mismatched_sub_rule_type(
     project: Project,
 ) -> None:
     # Given
@@ -790,7 +790,7 @@ def test_update_segment_with_matches_from_current_segment_mismatched_sub_rule_ty
         operator=PERCENTAGE_SPLIT, value=0.2, rule=rule4
     )
     # When
-    segment1.update_segment_with_matches_from_current_segment(segment2)
+    segment1.update_segment_with_matches_from_target_segment(segment2)
 
     # Then
     rule1.refresh_from_db()
@@ -809,7 +809,7 @@ def test_update_segment_with_matches_from_current_segment_mismatched_sub_rule_ty
     assert condition2.version_of is None
 
 
-def test_update_segment_with_matches_from_current_segment_multiple_sub_rules(
+def test_update_segment_with_matches_from_target_segment_multiple_sub_rules(
     project: Project,
 ) -> None:
     # Given
@@ -838,7 +838,7 @@ def test_update_segment_with_matches_from_current_segment_multiple_sub_rules(
     rule11 = SegmentRule.objects.create(rule=rule4, type=SegmentRule.ALL_RULE)
 
     # When
-    segment1.update_segment_with_matches_from_current_segment(segment2)
+    segment1.update_segment_with_matches_from_target_segment(segment2)
 
     # Then
     rule1.refresh_from_db()
@@ -870,7 +870,7 @@ def test_update_segment_with_matches_from_current_segment_multiple_sub_rules(
     assert rule11.version_of is None
 
 
-def test_update_segment_with_matches_from_current_segment_with_multiple_conditions(
+def test_update_segment_with_matches_from_target_segment_with_multiple_conditions(
     project: Project,
 ) -> None:
     # Given
@@ -898,7 +898,7 @@ def test_update_segment_with_matches_from_current_segment_with_multiple_conditio
         operator=PERCENTAGE_SPLIT, value=0.4, rule=rule4
     )
     # When
-    segment1.update_segment_with_matches_from_current_segment(segment2)
+    segment1.update_segment_with_matches_from_target_segment(segment2)
 
     # Then
     rule1.refresh_from_db()
@@ -923,7 +923,7 @@ def test_update_segment_with_matches_from_current_segment_with_multiple_conditio
     assert condition4.version_of is None
 
 
-def test_update_segment_with_matches_from_current_segment_with_exact_condition_match(
+def test_update_segment_with_matches_from_target_segment_with_exact_condition_match(
     project: Project,
 ) -> None:
     # Given
@@ -954,7 +954,7 @@ def test_update_segment_with_matches_from_current_segment_with_exact_condition_m
         operator=PERCENTAGE_SPLIT, value=0.4, rule=rule4
     )
     # When
-    segment1.update_segment_with_matches_from_current_segment(segment2)
+    segment1.update_segment_with_matches_from_target_segment(segment2)
 
     # Then
     rule1.refresh_from_db()