|
@ -10,7 +10,9 @@ mtpatches.py [options] |
|
|
|
|
|
|
|
|
Options: |
|
|
Options: |
|
|
--skip-missing Do not list files in heads that are not in sources. |
|
|
--skip-missing Do not list files in heads that are not in sources. |
|
|
|
|
|
--color Enable console colors such as <ESC character>[32m |
|
|
''' |
|
|
''' |
|
|
|
|
|
import json |
|
|
import os |
|
|
import os |
|
|
import platform |
|
|
import platform |
|
|
import shlex |
|
|
import shlex |
|
@ -18,6 +20,7 @@ import shutil |
|
|
import sys |
|
|
import sys |
|
|
import subprocess |
|
|
import subprocess |
|
|
from binaryornot.check import is_binary |
|
|
from binaryornot.check import is_binary |
|
|
|
|
|
from collections import OrderedDict |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Fore: |
|
|
class Fore: |
|
@ -94,10 +97,12 @@ def diff_only_head(base, head, more_1char_args=None, log_level=0): |
|
|
|
|
|
|
|
|
Returns: |
|
|
Returns: |
|
|
list(dict): A list of differing files as info dicts, each with: |
|
|
list(dict): A list of differing files as info dicts, each with: |
|
|
- 'rel': The path relative to head. |
|
|
- 'rel' (str): The path relative to head. |
|
|
- 'new': True if not in base, otherwise False or not present. |
|
|
- 'new' (bool): True if not in base, otherwise False or not present. |
|
|
- 'code': Return code (1 if file in head&base differ) |
|
|
- 'code' (int): Return code (1 if file in head&base differ) |
|
|
|
|
|
- 'head_is_binary' (bool): If detected binary (implies binary |
|
|
|
|
|
comparison was used, unless 'new' is True then not actually |
|
|
|
|
|
compared but still detected head_is_binary). |
|
|
""" |
|
|
""" |
|
|
return _diff_only_head( |
|
|
return _diff_only_head( |
|
|
base, |
|
|
base, |
|
@ -237,6 +242,7 @@ def _diff_only_head(base, head, rel=None, more_1char_args=None, depth=0, |
|
|
log_level=log_level, |
|
|
log_level=log_level, |
|
|
) |
|
|
) |
|
|
else: |
|
|
else: |
|
|
|
|
|
head_is_binary = is_binary(head_path) |
|
|
# echo0('base={}:"{}"'.format(whats[0], paths[0])) |
|
|
# echo0('base={}:"{}"'.format(whats[0], paths[0])) |
|
|
# echo0('head={}:"{}"'.format(whats[1], paths[1])) |
|
|
# echo0('head={}:"{}"'.format(whats[1], paths[1])) |
|
|
# file, so actually compare |
|
|
# file, so actually compare |
|
@ -250,11 +256,9 @@ def _diff_only_head(base, head, rel=None, more_1char_args=None, depth=0, |
|
|
'code': 1, |
|
|
'code': 1, |
|
|
'rel': rel, |
|
|
'rel': rel, |
|
|
'new': True, |
|
|
'new': True, |
|
|
|
|
|
'head_is_binary': head_is_binary, |
|
|
}] |
|
|
}] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
head_is_binary = is_binary(head_path) |
|
|
|
|
|
|
|
|
|
|
|
if more_1char_args is None: |
|
|
if more_1char_args is None: |
|
|
if platform.system() == "Windows": |
|
|
if platform.system() == "Windows": |
|
|
if not head_is_binary: |
|
|
if not head_is_binary: |
|
@ -305,6 +309,7 @@ def _diff_only_head(base, head, rel=None, more_1char_args=None, depth=0, |
|
|
return [{ |
|
|
return [{ |
|
|
'code': rc, |
|
|
'code': rc, |
|
|
'rel': rel, |
|
|
'rel': rel, |
|
|
|
|
|
'head_is_binary': head_is_binary, |
|
|
}] |
|
|
}] |
|
|
return diffs # folder, so return every sub's diff(s) ([] if None) |
|
|
return diffs # folder, so return every sub's diff(s) ([] if None) |
|
|
|
|
|
|
|
@ -449,6 +454,8 @@ def usage(): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main(): |
|
|
def main(): |
|
|
|
|
|
skip_missing = False |
|
|
|
|
|
enable_color = False |
|
|
bases = ( |
|
|
bases = ( |
|
|
"/opt/minebest/assemble/bucket_game", |
|
|
"/opt/minebest/assemble/bucket_game", |
|
|
# "/opt/minebest/mtkit/minetest/src", |
|
|
# "/opt/minebest/mtkit/minetest/src", |
|
@ -462,15 +469,19 @@ def main(): |
|
|
for arg in sys.argv[1:]: |
|
|
for arg in sys.argv[1:]: |
|
|
if arg == "--skip-missing": |
|
|
if arg == "--skip-missing": |
|
|
skip_missing = True |
|
|
skip_missing = True |
|
|
|
|
|
elif arg == "--color": |
|
|
|
|
|
enable_color = True |
|
|
else: |
|
|
else: |
|
|
usage() |
|
|
usage() |
|
|
echo0("Error: unknown argument {}".format(arg)) |
|
|
echo0("Error: unknown argument {}".format(arg)) |
|
|
return 1 |
|
|
return 1 |
|
|
return check_if_head_files_applied(bases, head_parents, |
|
|
return check_if_head_files_applied(bases, head_parents, |
|
|
skip_missing=skip_missing) |
|
|
skip_missing=skip_missing, |
|
|
|
|
|
enable_color=enable_color) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_if_head_files_applied(bases, head_parents, skip_missing=False): |
|
|
def check_if_head_files_applied(bases, head_parents, skip_missing=False, |
|
|
|
|
|
enable_color=False): |
|
|
"""Check if head files are applied. |
|
|
"""Check if head files are applied. |
|
|
|
|
|
|
|
|
Args: |
|
|
Args: |
|
@ -479,21 +490,45 @@ def check_if_head_files_applied(bases, head_parents, skip_missing=False): |
|
|
head_parents (list[str]): Folders containing various patches, |
|
|
head_parents (list[str]): Folders containing various patches, |
|
|
where each sub of each parent is in the form of files to |
|
|
where each sub of each parent is in the form of files to |
|
|
overlay onto base. |
|
|
overlay onto base. |
|
|
|
|
|
enable_color (bool): Enable console colors such as |
|
|
|
|
|
"{escape_character}[32m" for green. Defaults to False. |
|
|
|
|
|
|
|
|
Returns: |
|
|
Returns: |
|
|
int: 0 on success. |
|
|
int: 0 on success. |
|
|
""" |
|
|
""" |
|
|
for base in bases: |
|
|
summary = OrderedDict( |
|
|
if not os.path.isdir(base): |
|
|
unfinished_patch_count=0, |
|
|
echo0('Warning: There is no base "{}".'.format(base)) |
|
|
unpatched_file_count=0, |
|
|
|
|
|
) |
|
|
|
|
|
reset_color = "" |
|
|
|
|
|
color = "" |
|
|
|
|
|
if enable_color: |
|
|
|
|
|
reset_color = Fore.RESET |
|
|
|
|
|
|
|
|
|
|
|
for base_root in bases: |
|
|
|
|
|
if not os.path.isdir(base_root): |
|
|
|
|
|
echo0('Warning: There is no base_root "{}".'.format(base_root)) |
|
|
continue |
|
|
continue |
|
|
for head in head_parents: |
|
|
for head_parent in head_parents: |
|
|
echo0("\n# {}".format(head)) |
|
|
echo0("\n# patches={}".format(head_parent)) |
|
|
for head_sub in os.listdir(head): |
|
|
for head_sub in os.listdir(head_parent): |
|
|
|
|
|
echo0("## patch={}".format(head_sub)) |
|
|
# Identify each head folder as an overlay to "patch" a base. |
|
|
# Identify each head folder as an overlay to "patch" a base. |
|
|
head_sub_path = os.path.join(head, head_sub) |
|
|
|
|
|
|
|
|
# *Ignore files* in each head parent! |
|
|
|
|
|
# Files there may be diff and zip files etc.! |
|
|
|
|
|
# Each sub *folder* is a overlay for base. |
|
|
|
|
|
# - This method is *not* recursive, but: |
|
|
|
|
|
# diff_only_head (recursive) is called below on each dir. |
|
|
|
|
|
# - This method does *display* each file, for diffs |
|
|
|
|
|
# returned by recursive diff_only_head. |
|
|
|
|
|
|
|
|
|
|
|
head_fuzzy_root = os.path.join(head_parent, head_sub) |
|
|
|
|
|
# ^ head_fuzzy_root is not yet known in terms of depth |
|
|
|
|
|
# which will be detected later if possible as patch_root |
|
|
|
|
|
# (if contains "mods" folder, head_parent is patch_root) |
|
|
# region skip non-patch subs |
|
|
# region skip non-patch subs |
|
|
if os.path.isfile(head_sub_path): |
|
|
if os.path.isfile(head_fuzzy_root): |
|
|
# echo0('Warning: Only folders, skipped "{}"' |
|
|
# echo0('Warning: Only folders, skipped "{}"' |
|
|
# ''.format(head_sub)) |
|
|
# ''.format(head_sub)) |
|
|
continue |
|
|
continue |
|
@ -515,12 +550,12 @@ def check_if_head_files_applied(bases, head_parents, skip_missing=False): |
|
|
|
|
|
|
|
|
# region identify patch structure |
|
|
# region identify patch structure |
|
|
mod_rel = get_shallowest_files_sub( |
|
|
mod_rel = get_shallowest_files_sub( |
|
|
head_sub_path, |
|
|
head_fuzzy_root, |
|
|
mask=["init.lua", "mod.conf", "depends.txt", |
|
|
mask=["init.lua", "mod.conf", "depends.txt", |
|
|
"description.txt"], |
|
|
"description.txt"], |
|
|
) |
|
|
) |
|
|
modpack_rel = get_shallowest_files_sub( |
|
|
modpack_rel = get_shallowest_files_sub( |
|
|
head_sub_path, |
|
|
head_fuzzy_root, |
|
|
mask=["modpack.txt", "modpack.conf"], |
|
|
mask=["modpack.txt", "modpack.conf"], |
|
|
) |
|
|
) |
|
|
game_patch_root = None |
|
|
game_patch_root = None |
|
@ -528,14 +563,13 @@ def check_if_head_files_applied(bases, head_parents, skip_missing=False): |
|
|
modpack_patch_root = None |
|
|
modpack_patch_root = None |
|
|
|
|
|
|
|
|
if head_sub.endswith("_game"): |
|
|
if head_sub.endswith("_game"): |
|
|
game_patch_root = head_sub_path |
|
|
game_patch_root = head_fuzzy_root |
|
|
elif os.path.isdir(os.path.join(head_sub_path, "mods")): |
|
|
elif os.path.isdir(os.path.join(head_fuzzy_root, "mods")): |
|
|
game_patch_root = head_sub_path |
|
|
game_patch_root = head_fuzzy_root |
|
|
echo0('game_patch_root="{}"'.format(head_sub)) |
|
|
|
|
|
elif (mod_rel is not None) and (modpack_rel is None): |
|
|
elif (mod_rel is not None) and (modpack_rel is None): |
|
|
mod_parent = os.path.dirname(os.path.join(head_sub_path, |
|
|
mod_parent = os.path.dirname(os.path.join(head_fuzzy_root, |
|
|
mod_rel)) |
|
|
mod_rel)) |
|
|
mod_parent_rel = mod_parent[len(head_sub_path)+1:] |
|
|
mod_parent_rel = mod_parent[len(head_fuzzy_root)+1:] |
|
|
# ^ +1 no os.path.sep |
|
|
# ^ +1 no os.path.sep |
|
|
_, mod_parent_name = os.path.split(mod_parent) |
|
|
_, mod_parent_name = os.path.split(mod_parent) |
|
|
if mod_parent_rel and (mod_parent_name not in ["mods"]): |
|
|
if mod_parent_rel and (mod_parent_name not in ["mods"]): |
|
@ -546,17 +580,19 @@ def check_if_head_files_applied(bases, head_parents, skip_missing=False): |
|
|
|
|
|
|
|
|
if game_patch_root: |
|
|
if game_patch_root: |
|
|
pass # Already set above. |
|
|
pass # Already set above. |
|
|
|
|
|
echo0('set game_patch_root="{}"'.format(head_sub)) |
|
|
elif modpack_patch_root: |
|
|
elif modpack_patch_root: |
|
|
pass # Already set above. |
|
|
pass # Already set above. |
|
|
|
|
|
echo0('set modpack_patch_root="{}"'.format(head_sub)) |
|
|
elif mod_rel is not None: |
|
|
elif mod_rel is not None: |
|
|
if mod_rel: |
|
|
if mod_rel: |
|
|
mod_patch_root = os.path.join(head_sub_path, mod_rel) |
|
|
mod_patch_root = os.path.join(head_fuzzy_root, mod_rel) |
|
|
else: |
|
|
else: |
|
|
# Must be "", so don't do join or will add os.path.sep |
|
|
# Must be "", so don't do join or will add os.path.sep |
|
|
mod_patch_root = head_sub_path |
|
|
mod_patch_root = head_fuzzy_root |
|
|
_, got_mod_name = os.path.split(mod_patch_root) |
|
|
_, got_mod_name = os.path.split(mod_patch_root) |
|
|
echo0('mod_patch="{}" root="{}"' |
|
|
echo0('set mod_patch="{}"'.format(got_mod_name)) |
|
|
''.format(got_mod_name, mod_patch_root)) |
|
|
echo0('set mod_patch_root="{}"'.format(mod_patch_root)) |
|
|
else: |
|
|
else: |
|
|
pass |
|
|
pass |
|
|
# echo0('Warning: mod not identified in "{}"' |
|
|
# echo0('Warning: mod not identified in "{}"' |
|
@ -565,42 +601,42 @@ def check_if_head_files_applied(bases, head_parents, skip_missing=False): |
|
|
# endregion identify patch structure |
|
|
# endregion identify patch structure |
|
|
|
|
|
|
|
|
# region check whether base has it installed |
|
|
# region check whether base has it installed |
|
|
patch_root = None |
|
|
parallel_head = None # detected depth matches parallel_base |
|
|
if game_patch_root is not None: |
|
|
if game_patch_root is not None: |
|
|
patch_root = game_patch_root |
|
|
parallel_head = game_patch_root |
|
|
_, game_name = os.path.split(base) |
|
|
_, game_name = os.path.split(base_root) |
|
|
base_sub_path = base |
|
|
parallel_base = base_root |
|
|
echo0("* Checking whether {} was applied to {} game" |
|
|
echo0("* Checking whether {} was applied to {} game" |
|
|
"".format(head_sub, game_name)) |
|
|
"".format(head_sub, game_name)) |
|
|
elif modpack_patch_root is not None: |
|
|
elif modpack_patch_root is not None: |
|
|
patch_root = modpack_patch_root |
|
|
parallel_head = modpack_patch_root |
|
|
_, modpack_name = os.path.split(modpack_patch_root) |
|
|
_, modpack_name = os.path.split(modpack_patch_root) |
|
|
modpack_rel = find_modpack(base, modpack_name) |
|
|
modpack_rel = find_modpack(base_root, modpack_name) |
|
|
if modpack_rel is None: |
|
|
if modpack_rel is None: |
|
|
echo0("Error: {} was not found in {}" |
|
|
echo0("Error: {} was not found in {}" |
|
|
"".format(modpack_name, base)) |
|
|
"".format(modpack_name, base_root)) |
|
|
continue |
|
|
continue |
|
|
if modpack_rel: |
|
|
if modpack_rel: |
|
|
base_sub_path = os.path.join(base, modpack_rel) |
|
|
parallel_base = os.path.join(base_root, modpack_rel) |
|
|
else: |
|
|
else: |
|
|
# Must be "", so avoid join to avoid adding os.path.sep |
|
|
# Must be "", so avoid join to avoid adding os.path.sep |
|
|
base_sub_path = base |
|
|
parallel_base = base_root |
|
|
echo0("* Checking whether {} was applied to" |
|
|
echo0("* Checking whether {} was applied to" |
|
|
" {} modpack in {} game" |
|
|
" {} modpack in {} game" |
|
|
"".format(head_sub, modpack_name, game_name)) |
|
|
"".format(head_sub, modpack_name, game_name)) |
|
|
elif mod_patch_root is not None: |
|
|
elif mod_patch_root is not None: |
|
|
patch_root = mod_patch_root |
|
|
parallel_head = mod_patch_root |
|
|
_, mod_name = os.path.split(mod_patch_root) |
|
|
_, mod_name = os.path.split(mod_patch_root) |
|
|
mod_rel = find_mod(base, mod_name) |
|
|
mod_rel = find_mod(base_root, mod_name) |
|
|
if mod_rel is None: |
|
|
if mod_rel is None: |
|
|
echo0("Error: {} was not found in {}" |
|
|
echo0("Error: {} was not found in {}" |
|
|
"".format(mod_name, base)) |
|
|
"".format(mod_name, base_root)) |
|
|
continue |
|
|
continue |
|
|
if modpack_rel: |
|
|
if modpack_rel: |
|
|
base_sub_path = os.path.join(base, modpack_rel) |
|
|
parallel_base = os.path.join(base_root, modpack_rel) |
|
|
else: |
|
|
else: |
|
|
# Must be "", so avoid join to avoid adding os.path.sep |
|
|
# Must be "", so avoid join to avoid adding os.path.sep |
|
|
base_sub_path = base |
|
|
parallel_base = base_root |
|
|
echo0("* Checking whether {} was applied to" |
|
|
echo0("* Checking whether {} was applied to" |
|
|
" {} mod in {} game" |
|
|
" {} mod in {} game" |
|
|
"".format(head_sub, mod_name, game_name)) |
|
|
"".format(head_sub, mod_name, game_name)) |
|
@ -608,14 +644,24 @@ def check_if_head_files_applied(bases, head_parents, skip_missing=False): |
|
|
echo0('Warning: Skipping unknown patch structure: "{}"' |
|
|
echo0('Warning: Skipping unknown patch structure: "{}"' |
|
|
''.format(head_sub)) |
|
|
''.format(head_sub)) |
|
|
|
|
|
|
|
|
diffs = diff_only_head(base_sub_path, patch_root, log_level=-1) |
|
|
diffs = diff_only_head(parallel_base, parallel_head, |
|
|
|
|
|
log_level=-1) |
|
|
|
|
|
if len(diffs) > 0: |
|
|
|
|
|
summary['unfinished_patch_count'] += 1 |
|
|
|
|
|
echo0('* differs from patch "{}": {} file(s)' |
|
|
|
|
|
''.format(head_parent, len(diffs))) |
|
|
for diff in diffs: |
|
|
for diff in diffs: |
|
|
|
|
|
summary['unpatched_file_count'] += 1 |
|
|
|
|
|
base_file = os.path.join(parallel_base, diff['rel']) |
|
|
|
|
|
head_file = os.path.join(parallel_head, diff['rel']) |
|
|
missing = bool(diff.get("new")) |
|
|
missing = bool(diff.get("new")) |
|
|
if missing: |
|
|
if missing: |
|
|
if skip_missing: |
|
|
if skip_missing: |
|
|
continue |
|
|
continue |
|
|
|
|
|
if enable_color: |
|
|
color = Fore.GREEN |
|
|
color = Fore.GREEN |
|
|
else: |
|
|
else: |
|
|
|
|
|
if enable_color: |
|
|
color = Fore.YELLOW |
|
|
color = Fore.YELLOW |
|
|
why = "MISSING" if missing else "differs" |
|
|
why = "MISSING" if missing else "differs" |
|
|
# echo0(" * {}: {}".format(why, diff)) |
|
|
# echo0(" * {}: {}".format(why, diff)) |
|
@ -626,13 +672,31 @@ def check_if_head_files_applied(bases, head_parents, skip_missing=False): |
|
|
" or it will override base or head." |
|
|
" or it will override base or head." |
|
|
"".format(os.path.sep) |
|
|
"".format(os.path.sep) |
|
|
) |
|
|
) |
|
|
echo0(" "+shlex.join([ |
|
|
|
|
|
"meld", |
|
|
|
|
|
os.path.join(base_sub_path, diff['rel']), |
|
|
|
|
|
os.path.join(head_sub_path, diff['rel']), |
|
|
|
|
|
])+"{} # {} in base (original) vs head (patch) {}".format(color, why, Fore.RESET)) |
|
|
|
|
|
# endregion check whether base has it installed |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if not os.path.isfile(head_file): |
|
|
|
|
|
raise NotImplementedError( |
|
|
|
|
|
'every head_file should be a file,' |
|
|
|
|
|
' but got "{}"'.format(head_file) |
|
|
|
|
|
) |
|
|
|
|
|
# else isdir |
|
|
|
|
|
# echo0("head_is_binary={}".format(head_is_binary)) |
|
|
|
|
|
head_is_binary = is_binary(head_file) |
|
|
|
|
|
difftool = "diffimage-gui" if head_is_binary else "meld" |
|
|
|
|
|
# ^ Poikilos' diffimage from rotocanvas |
|
|
|
|
|
# (*not* the same as nicolashahn' diffimg). |
|
|
|
|
|
|
|
|
|
|
|
difftool = difftool |
|
|
|
|
|
echo0( |
|
|
|
|
|
" "+shlex.join([ |
|
|
|
|
|
difftool, |
|
|
|
|
|
base_file, |
|
|
|
|
|
head_file, |
|
|
|
|
|
]) |
|
|
|
|
|
+ "{} # {} in base (original) vs head (patch) {}" |
|
|
|
|
|
"".format(color, why, reset_color) |
|
|
|
|
|
) |
|
|
|
|
|
# endregion check whether base has it installed |
|
|
|
|
|
echo0("summary={}".format(json.dumps(summary, indent=2))) |
|
|
return 0 |
|
|
return 0 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|