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]