szehon-ho commented on code in PR #58288:
URL: https://github.com/apache/spark/pull/58288#discussion_r3875010858


##########
dev/merge_spark_pr.py:
##########
@@ -350,6 +350,112 @@ def additional_fix_versions(inferred_versions, 
existing_versions):
     return [version for version in inferred_versions if version not in 
existing]
 
 
+def parse_affects_versions_input(raw, available_versions):
+    """Parse a committer's comma-separated Affects Version entry and validate 
it.
+
+    Whitespace around each entry is stripped and empty segments are dropped, 
so a
+    blank line yields no names. Duplicates are removed while input order is
+    preserved. Returns ``(version_names, is_valid)`` where ``is_valid`` is True
+    only when every parsed name is in ``available_versions``; an empty entry is
+    vacuously valid, letting the caller treat blank as "keep current".
+
+    >>> parse_affects_versions_input("4.4.0", {"4.4.0", "5.0.0"})
+    (['4.4.0'], True)
+    >>> parse_affects_versions_input(" 4.4.0 , 5.0.0 ", {"4.4.0", "5.0.0"})
+    (['4.4.0', '5.0.0'], True)
+    >>> parse_affects_versions_input("9.9.9", {"4.4.0", "5.0.0"})
+    (['9.9.9'], False)
+    >>> parse_affects_versions_input("4.4.0,9.9.9", {"4.4.0", "5.0.0"})
+    (['4.4.0', '9.9.9'], False)
+    >>> parse_affects_versions_input("", {"4.4.0"})
+    ([], True)
+    >>> parse_affects_versions_input("   ", {"4.4.0"})
+    ([], True)
+    >>> parse_affects_versions_input("4.4.0, 4.4.0", {"4.4.0"})
+    (['4.4.0'], True)
+    """
+    names = [n for n in (segment.strip() for segment in raw.split(",")) if n]
+    names = list(dict.fromkeys(names))
+    return names, set(names).issubset(available_versions)
+
+
+def fix_precedes_affects(fix_version_names, affects_version_names):
+    """Whether the earliest fix ships before the earliest recorded Affects 
Version.
+
+    The Affects Version/s should reach down to at least the earliest release 
the fix
+    lands in: a fix in F but an affected floor of A > F leaves a fixed release 
the
+    ticket does not admit to affecting. This flags both a too-high affected 
version on
+    a fresh resolve (fixed 4.4.0, affects only 5.0.0) and a backport that 
extends the
+    fix to an earlier line (affects 4.4.0, backport adds fix 4.3.x). Returns 
True iff
+    min(fix) < min(affects) by numeric semver. Only dotted ``x.y.z`` names are
+    compared; other names are ignored, and the result is False when either 
side has
+    no comparable version (so an empty Affects Version/s never triggers a 
prompt).
+
+    >>> fix_precedes_affects(["4.4.0"], ["5.0.0"])
+    True
+    >>> fix_precedes_affects(["4.4.0"], ["4.3.0"])
+    False
+    >>> fix_precedes_affects(["4.4.0"], ["4.4.0"])
+    False
+    >>> fix_precedes_affects(["4.4.0", "4.3.1"], ["4.4.0"])
+    True
+    >>> fix_precedes_affects(["4.4.0"], ["4.10.0"])
+    True
+    >>> fix_precedes_affects(["5.0.0"], [])
+    False
+    >>> fix_precedes_affects([], ["5.0.0"])
+    False
+    """
+
+    def tuples(names):
+        return [
+            tuple(int(p) for p in n.split(".")) for n in names if 
re.match(r"^\d+\.\d+\.\d+$", n)
+        ]
+
+    fix = tuples(fix_version_names)
+    affects = tuples(affects_version_names)
+    if not fix or not affects:
+        return False
+    return min(fix) < min(affects)
+
+
+def suggest_affects_versions(fix_version_names, affects_version_names):
+    """Suggest Affects Version/s from the fix version(s) and the current ones.
+
+    The suggestion keeps every recorded affected version that is not later 
than the
+    highest fix version (dropping only the anomalously-high ones) and adds all 
fix
+    versions, so a fresh resolve replaces a too-high affected version with the 
fix
+    version while a backport extends the affected versions down to the newly 
fixed
+    line. The result is de-duplicated and sorted high-to-low by numeric semver.
+    Non-``x.y.z`` names are ignored.
+
+    >>> suggest_affects_versions(["4.4.0"], ["5.0.0"])
+    ['4.4.0']
+    >>> suggest_affects_versions(["4.4.0", "4.3.1"], ["4.4.0"])
+    ['4.4.0', '4.3.1']
+    >>> suggest_affects_versions(["4.4.0"], ["4.3.0"])
+    ['4.4.0', '4.3.0']
+    >>> suggest_affects_versions(["4.4.0"], [])
+    ['4.4.0']
+    >>> suggest_affects_versions([], ["5.0.0"])
+    []
+    """
+
+    def semver(name):
+        m = re.match(r"^(\d+)\.(\d+)\.(\d+)$", name)
+        return tuple(int(g) for g in m.groups()) if m else None
+
+    fix_tuples = [t for t in (semver(n) for n in fix_version_names) if t is 
not None]
+    if not fix_tuples:
+        return []
+    max_fix = max(fix_tuples)
+    kept_affects = [
+        n for n in affects_version_names if semver(n) is not None and 
semver(n) <= max_fix
+    ]
+    merged = list(dict.fromkeys(list(fix_version_names) + kept_affects))

Review Comment:
   Could we avoid adding every Fix Version directly to Affects Version/s? For a 
patch backport, the fix release is usually not itself affected. For example, a 
bug affecting 4.2.0 and fixed by a backport in 4.2.1 would make this default 
suggest 4.2.1 as affected; pressing Enter would then record incorrect Jira 
metadata. Since the merge target does not tell us when the issue was 
introduced, perhaps patch backports should require an explicit affected-version 
choice, or at least should not default the newly added patch Fix Version as 
affected.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to