Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ These commands are centered around a user making changes to a project (manually
- `cfbs analyse`: Same as `cfbs analyze`.
- `cfbs analyze`: Analyze the policy set specified by the given path.
- `cfbs clean`: Remove modules which were added as dependencies, but are no longer needed.
- `cfbs convert`: Initialize a new CFEngine Build project based on an existing policy set.
- `cfbs help`: Print the help menu.
- `cfbs info`: Print information about a module.
- `cfbs init`: Initialize a new CFEngine Build project.
Expand Down
17 changes: 16 additions & 1 deletion cfbs/analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,7 @@ class AnalyzedFiles:
def __init__(self, reference_version: Union[str, None]):
self.reference_version = reference_version

self.unmodified = []
self.missing = []
self.modified = []
self.moved_or_renamed = []
Expand All @@ -373,6 +374,10 @@ def _denormalize_origin(origin, is_parentpath, masterfiles_dir):
def denormalize(self, is_parentpath, masterfiles_dir):
"""Currently irreversible and meant to only be used once after all the files are analyzed."""

self.unmodified = [
mpf_denormalized_path(file, is_parentpath, masterfiles_dir)
for file in self.unmodified
]
self.missing = [
mpf_denormalized_path(file, is_parentpath, masterfiles_dir)
for file in self.missing
Expand Down Expand Up @@ -420,6 +425,7 @@ def denormalize(self, is_parentpath, masterfiles_dir):
]

def sort(self):
self.unmodified = filepaths_sorted(self.unmodified)
self.missing = filepaths_sorted(self.missing)
self.modified = filepaths_sorted(self.modified)
self.moved_or_renamed = filepaths_sorted(self.moved_or_renamed)
Expand All @@ -430,9 +436,14 @@ def sort(self):
)
self.not_from_any = filepaths_sorted(self.not_from_any)

def display(self):
def display(self, display_unmodified=False):
print("Reference version:", self.reference_version, "\n")

if display_unmodified:
if len(self.unmodified) > 0:
print("Files unmodified from the version:")
filepaths_display(self.unmodified)

if len(self.missing) > 0:
print("Files missing from the version:")
elif self.reference_version is not None:
Expand Down Expand Up @@ -479,6 +490,7 @@ def to_json_dict(self):

json_dict["files"] = {}

json_dict["files"]["unmodified"] = self.unmodified
json_dict["files"]["missing"] = self.missing
json_dict["files"]["modified"] = self.modified
json_dict["files"]["moved_or_renamed"] = self.moved_or_renamed
Expand Down Expand Up @@ -682,6 +694,9 @@ def analyze_policyset(
other_versions = mpf_checksums_dict[checksum][filepath]
# since MPF data is sorted, so is `other_versions`
analyzed_files.different.append((filepath, other_versions))
else:
# 1A1B. the file is unmodified and present in the reference version
analyzed_files.unmodified.append(filepath)
else:
# 1A2. checksum is known but there's no matching filepath with that checksum:
# therefore, it must be a rename/move
Expand Down
2 changes: 1 addition & 1 deletion cfbs/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ def get_arg_parser(whitespace_for_manual=False):
)
parser.add_argument(
"--offline",
help="Do not connect to the Internet to download the latest version of MPF release information during 'cfbs analyze'",
help="Do not connect to the Internet to download the latest version of MPF release information during 'cfbs analyze' and 'cfbs convert'",
action="store_true",
)
parser.add_argument(
Expand Down
3 changes: 2 additions & 1 deletion cfbs/cfbs.1
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ CFEngine Build System.

.TP
\fBcmd\fR
The command to perform (pretty, init, status, search, add, remove, clean, update, validate, download, build, install, help, info, show, analyse, analyze, input, set\-input, get\-input, generate\-release\-information)
The command to perform (pretty, init, status, search, add, remove, clean, update, validate, download, build, install, help, info, show,
analyse, analyze, convert, input, set\-input, get\-input, generate\-release\-information)

.TP
\fBargs\fR
Expand Down
130 changes: 130 additions & 0 deletions cfbs/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def search_command(terms: List[str]):
from collections import OrderedDict
from cfbs.analyze import analyze_policyset
from cfbs.args import get_args
from typing import Iterable

from cfbs.cfbs_json import CFBSJson
from cfbs.cfbs_types import CFBSCommandExitCode, CFBSCommandGitResult
Expand Down Expand Up @@ -92,6 +93,7 @@ def search_command(terms: List[str]):
from cfbs.validate import (
validate_config,
validate_config_raise_exceptions,
validate_module_name_content,
validate_single_module,
)
from cfbs.internal_file_management import (
Expand Down Expand Up @@ -1087,6 +1089,134 @@ def analyze_command(
return 0


@cfbs_command("convert")
def convert_command(non_interactive=False, offline=False):
def cfbs_convert_cleanup():
os.unlink(cfbs_filename())
rm(".git", missing_ok=True)

def cfbs_convert_git_commit(
commit_message: str, add_scope: Union[str, Iterable[str]] = "all"
):
try:
git_commit_maybe_prompt(commit_message, non_interactive, scope=add_scope)
except CFBSGitError:
cfbs_convert_cleanup()
raise

dir_content = [f.name for f in os.scandir(".")]

if not (len(dir_content) == 1 and dir_content[0].startswith("masterfiles-")):
raise CFBSUserError(
"cfbs convert must be run in a directory containing only one item, a subdirectory named masterfiles-<some-name>"
)

dir_name = dir_content[0]
path_string = "./" + dir_name + "/"

# validate the local module
validate_module_name_content(path_string)

promises_cf_path = os.path.join(dir_name, "promises.cf")
if not os.path.isfile(promises_cf_path):
raise CFBSUserError(
"The file '"
+ promises_cf_path
+ "' does not exist - make sure '"
+ path_string
+ "' is a policy set based on masterfiles."
)

print(
"Found policy set in '%s' with 'promises.cf' in the expected location."
% path_string
)

print("Analyzing '" + path_string + "'...")
analyzed_files, _ = analyze_policyset(
path=dir_name,
is_parentpath=False,
reference_version=None,
masterfiles_dir=dir_name,
ignored_path_components=None,
offline=offline,
)

current_index = CFBSConfig.get_instance().index
default_version = current_index.get_module_object("masterfiles")["version"]

reference_version = analyzed_files.reference_version
if reference_version is None:
print(
"Did not detect any version of masterfiles, proceeding using the default version (%s)."
% default_version
)
masterfiles_version = default_version
else:
print("Detected version %s of masterfiles." % reference_version)
masterfiles_version = reference_version

if not prompt_user_yesno(
non_interactive,
"Do you want to continue making a new CFEngine Build project based on masterfiles %s?"
% masterfiles_version,
):
raise CFBSExitError("User did not proceed, exiting.")

print("Initializing a new CFBS project...")
# since there should be no other files than the masterfiles-name directory, there shouldn't be a .git directory
assert not is_git_repo()
r = init_command(masterfiles="no", non_interactive=non_interactive, use_git=True)
# the cfbs-init should've created a Git repository
assert is_git_repo()
if r != 0:
print("Initializing a new CFBS project failed, aborting conversion.")
cfbs_convert_cleanup()
return r

print("Adding masterfiles %s to the project..." % masterfiles_version)
masterfiles_to_add = ["masterfiles@%s" % masterfiles_version]
r = add_command(masterfiles_to_add, added_by="cfbs convert")
if r != 0:
print("Adding the masterfiles module failed, aborting conversion.")
cfbs_convert_cleanup()
return r

print("Adding the policy files...")
local_module_to_add = [path_string]
r = add_command(
local_module_to_add,
added_by="cfbs convert",
explicit_build_steps=["copy ./ ./"],
)
if r != 0:
print("Adding the policy files module failed, aborting conversion.")
cfbs_convert_cleanup()
return r

# here, matching files are files that have identical (filepath, checksum)
if len(analyzed_files.unmodified) != 0:
print(
"Deleting matching files between masterfiles %s and '%s'..."
% (masterfiles_version, path_string)
)
for unmodified_mpf_file in analyzed_files.unmodified:
rm(os.path.join(dir_name, unmodified_mpf_file))

print("Creating Git commit...")
cfbs_convert_git_commit("Deleted unmodified policy files")

print(
"Your project is now functional, can be built, and will produce a version of masterfiles %s with your modifications."
% masterfiles_version
)
print(
"The next conversion step is to handle modified files and files from other versions of masterfiles."
)
print("This is not implemented yet.")
return 0


@cfbs_command("input")
@commit_after_command("Added input for module%s", [PLURAL_S])
def input_command(args, input_from="cfbs input"):
Expand Down
4 changes: 3 additions & 1 deletion cfbs/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ def _main() -> int:
% args.command
)

if args.offline and args.command not in ("analyze", "analyse"):
if args.offline and args.command not in ("analyze", "analyse", "convert"):
raise CFBSUserError(
"The option --offline is only for 'cfbs analyze', not 'cfbs %s'"
% args.command
Expand Down Expand Up @@ -184,6 +184,8 @@ def _main() -> int:
args.offline,
does_log_info(args.loglevel),
)
if args.command == "convert":
return commands.convert_command(args.non_interactive, args.offline)

if args.command == "generate-release-information":
return commands.generate_release_information_command(
Expand Down
2 changes: 1 addition & 1 deletion cfbs/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@


def is_module_added_manually(added_by: str):
return added_by in ("cfbs add", "cfbs init")
return added_by in ("cfbs add", "cfbs init", "cfbs convert")


def is_module_local(name: str):
Expand Down