From 2a7f31497fcc53695170a868b98fdc23b4c4e71b Mon Sep 17 00:00:00 2001 From: jakub-nt <175944085+jakub-nt@users.noreply.github.com> Date: Thu, 21 Aug 2025 14:59:23 +0200 Subject: [PATCH 1/2] Added a function to find the single most relevant version; added some type hints Signed-off-by: jakub-nt <175944085+jakub-nt@users.noreply.github.com> --- cfbs/masterfiles/analyze.py | 47 ++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/cfbs/masterfiles/analyze.py b/cfbs/masterfiles/analyze.py index 106852b4..5f9a7afb 100644 --- a/cfbs/masterfiles/analyze.py +++ b/cfbs/masterfiles/analyze.py @@ -1,8 +1,11 @@ from collections import OrderedDict import os +from typing import Iterable, List from cfbs.utils import dict_sorted_by_key, file_sha256 +Version = str + def initialize_vcf(): versions_dict = {"versions": {}} @@ -141,5 +144,47 @@ def sort_versions(versions: list, reverse: bool = True): ) -def highest_version(versions): +def highest_version(versions: Iterable[Version]): return max(versions, key=version_as_comparable_list, default=None) + + +def lowest_version(versions: Iterable[Version]): + return min(versions, key=version_as_comparable_list, default=None) + + +def is_lower_version(version_a: Version, version_b: Version): + """Returns `True` if and only if `version_a` is lower than `version_b`.""" + + return version_as_comparable_list(version_a) < version_as_comparable_list(version_b) + + +def most_relevant_version( + other_versions: List[Version], reference_version: Version +) -> Version: + """ + The most relevant version is the highest version among older other versions, + or if there are no older other versions, the lowest version among other versions. + + `other_versions` is assumed to be non-empty and not contain `reference_version`.""" + assert len(other_versions) > 0 + assert reference_version not in other_versions + + highest_other_version = highest_version(other_versions) + lowest_other_version = lowest_version(other_versions) + # Unfortunately, Pyright can not infer that these can't be `None` when `other_versions` is non-empty. + assert highest_other_version is not None + assert lowest_other_version is not None + + if is_lower_version(highest_other_version, reference_version): + # all other versions are older + return highest_other_version + if is_lower_version(reference_version, lowest_other_version): + # all other versions are newer + return lowest_other_version + # there are both older and newer versions + lower_other_versions = [ + o_v for o_v in other_versions if is_lower_version(o_v, reference_version) + ] + highest_lower = highest_version(lower_other_versions) + assert highest_lower is not None + return highest_lower From 83a6e5397d92fb6c7b5c28ba8d94b59c1a67cce3 Mon Sep 17 00:00:00 2001 From: jakub-nt <175944085+jakub-nt@users.noreply.github.com> Date: Thu, 21 Aug 2025 15:04:32 +0200 Subject: [PATCH 2/2] Implemented Step 2 of cfbs-convert Signed-off-by: jakub-nt <175944085+jakub-nt@users.noreply.github.com> --- cfbs/commands.py | 50 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/cfbs/commands.py b/cfbs/commands.py index 773fb66e..cd8a80d2 100644 --- a/cfbs/commands.py +++ b/cfbs/commands.py @@ -59,6 +59,7 @@ def search_command(terms: List[str]): from cfbs.cfbs_json import CFBSJson from cfbs.cfbs_types import CFBSCommandExitCode, CFBSCommandGitResult +from cfbs.masterfiles.analyze import most_relevant_version from cfbs.updates import ModuleUpdates, update_module from cfbs.utils import ( CFBSNetworkError, @@ -1211,7 +1212,54 @@ def cfbs_convert_git_commit( % masterfiles_version ) print( - "The next conversion step is to handle modified files and files from other versions of masterfiles." + "The next conversion step is to handle files from other versions of masterfiles." + ) + if not prompt_user_yesno(non_interactive, "Do you want to continue?"): + raise CFBSExitError("User did not proceed, exiting.") + other_versions_files = analyzed_files.different + if len(other_versions_files) > 0: + print( + "The following files are taken from other versions of masterfiles (not %s):" + % masterfiles_version + ) + other_versions_files = sorted(other_versions_files) + files_to_delete = [] + for other_version_file, other_versions in other_versions_files: + # don't display all versions (which is an arbitrarily-shaped sequence), instead choose the most relevant one: + relevant_version_text = most_relevant_version( + other_versions, masterfiles_version + ) + if len(other_versions) > 1: + relevant_version_text += ", ..." + print("-", other_version_file, "(%s)" % relevant_version_text) + + files_to_delete.append(other_version_file) + print( + "This usually indicates that someone made mistakes during past upgrades and it's recommended to delete these." + ) + print( + "Your policy set will include the versions from %s instead (if they exist)." + % masterfiles_version + ) + print( + "(Deletions will be visible in Git history so you can review or revert later)." + ) + if prompt_user_yesno( + non_interactive, "Delete files from other versions? (Recommended)" + ): + print("Deleting %s files." % len(files_to_delete)) + for file_d in files_to_delete: + rm(os.path.join(dir_name, file_d)) + + print( + "Creating Git commit with deletion of policy files from other versions..." + ) + cfbs_convert_git_commit("Deleted policy files from other versions") + print("Done.", end=" ") + else: + print("There are no files from other versions of masterfiles.") + print( + "The next conversion step is to handle files which have custom modifications." ) print("This is not implemented yet.") return 0