def generate_fixes(): """Generate compilation fixing patches""" unfixed_sources = Path(WORK_DIR, "unfixed/net/minecraft/server") unmapped_sources = Path(WORK_DIR, "unmapped/net/minecraft/server") fixes = Path(ROOT_DIR, "buildData/fixes") fixes.mkdir(exist_ok=True) existing_fixes = list(fixes.iterdir()) if existing_fixes: print("Removing existing fixes") for p in existing_fixes: os.remove(p) engine = DiffEngine.create() for unfixed_file in unfixed_sources.iterdir(): unmapped_file = Path(unmapped_sources, unfixed_file.name) original_lines = read_file(unfixed_file) fixed_lines = read_file(unmapped_file) patch = engine.diff(original_lines, fixed_lines) patch_lines = list( generate_unified_diff(str(unfixed_file.relative_to(ROOT_DIR)), str(unmapped_file.relative_to(ROOT_DIR)), original_lines, patch)) if patch_lines: print(f"Found diff for {unfixed_file.name}") fix_file = Path(fixes, unfixed_file.name + ".patch") write_file(fix_file, patch_lines)
def do_diff( engine: DiffEngine, original: Path, revised: Path, output: Path, context_size=5, force=False, ): original_lines = [] revised_lines = [] with open(original, "rt") as f: for line in f: original_lines.append(line.rstrip("\r\n")) with open(revised, "rt") as f: for line in f: revised_lines.append(line.rstrip("\r\n")) result = engine.diff(original_lines, revised_lines) if not original.is_absolute(): original_name = str(original) else: original_name = str(original.relative_to(Path.cwd())) if not revised.is_absolute(): revised_name = str(revised) else: revised_name = str(revised.relative_to(Path.cwd())) try: result_lines = [] empty = True for line in generate_unified_diff( original_name, revised_name, original_lines, result, context_size=context_size, ): if empty and line.strip(): empty = False result_lines.append(line) if empty: return False with open(output, "wt" if force else "xt") as f: for line in result_lines: f.write(line) f.write("\n") return True except FileExistsError: raise CommandError("Output file already exists: {}".format(output))
def test_plain(): do_test_engine(DiffEngine.create(name="plain"))
def test_native(): do_test_engine(DiffEngine.create(name="native"))
def diff(original: Path, revised: Path, output: Path, ignore_missing=False, implementation=None, context=5, unrestricted=False, force=False): """Compute the difference between the original and revised text""" if not original.exists(): raise CommandError("Original file doesn't exist: {}".format(original)) if not revised.exists(): raise CommandError("Revised file doesn't exist: {}".format(revised)) try: engine = DiffEngine.create(name=implementation) except ImportError as e: raise CommandError("Unable to import {} implementation!".format( implementation)) from e if original.is_dir(): if not revised.is_dir(): raise CommandError( "Original {} is a directory, but revised {} is a file!".format( original, revised)) for revised_root, dirs, files in os.walk(str(revised)): for revised_file_name in files: if not unrestricted and revised_file_name.startswith('.'): continue revised_file = Path(revised_root, revised_file_name) relative_path = revised_file.relative_to(revised) original_file = Path(original, relative_path) if not original_file.exists(): if ignore_missing: continue else: raise CommandError( "Revised file {} doesn't have matching original {}!" .format(revised_file, original_file)) output_file = Path(output, relative_path.parent, relative_path.name + ".patch") output_file.parent.mkdir(parents=True, exist_ok=True) if do_diff(engine, original_file, revised_file, output_file, context_size=context, force=force): print("Computed diff: {}".format(relative_path)) if not unrestricted: hidden_dirs = [d for d in dirs if d.startswith('.')] for d in hidden_dirs: dirs.remove(d) else: if not revised.is_file(): raise CommandError( "Original {} is a file, but revised {} is a directory!".format( original, revised)) do_diff(engine, original, revised, output, context_size=context, force=force)
def diff(quiet=False, context=5, implementation=None): """Regenerates the patch files from the contents of the working directory.""" unpatched_sources = Path(WORK_DIR, "unpatched") if not unpatched_sources.exists(): raise CommandError("Couldn't find unpatched sources!") patched_dir = Path(Path.cwd(), "patched") if not patched_dir.exists(): raise CommandError("No patched files found!") patches = Path(Path.cwd(), "patches") patches.mkdir(exist_ok=True) if implementation is not None: try: engine = DiffEngine.create(implementation) print(f"Using {repr(engine)} diff implementation.") except ImportError as e: raise CommandError( f"Unable to import {implementation} engine: {e}") else: try: engine = DiffEngine.create('native') except ImportError: print("WARNING: Unable to import native diff implementation", file=stderr) print("Calculating diffs will be over 10 times slower!", file=stderr) engine = DiffEngine.create('plain') print("---- Recomputing Fountain patches via DiffUtils") for revised_root, dirs, files in os.walk(str(patched_dir)): for revised_file_name in files: if revised_file_name.startswith('.'): continue # Ignore dotfiles revised_file = Path(revised_root, revised_file_name) relative_path = revised_file.relative_to(patched_dir) original_file = Path(unpatched_sources, relative_path) if not original_file.exists(): raise CommandError( f"Revised file {revised_file} doesn't have matching original!" ) patch_file = Path(patches, relative_path.parent, relative_path.name + ".patch") patch_file.parent.mkdir(parents=True, exist_ok=True) original_lines = [] revised_lines = [] with open(original_file, 'rt') as f: for line in f: original_lines.append(line.rstrip('\r\n')) with open(revised_file, 'rt') as f: for line in f: revised_lines.append(line.rstrip('\r\n')) result = engine.diff(original_lines, revised_lines) original_name = str(original_file.absolute().relative_to(ROOT_DIR)) revised_name = str(revised_file.absolute().relative_to(ROOT_DIR)) result_lines = [] empty = True for line in generate_unified_diff(original_name, revised_name, original_lines, result, context_size=context): if empty and line.strip(): empty = False result_lines.append(line) if empty: continue elif not quiet: print(f"Found diff for {relative_path}") with open(patch_file, 'wt') as f: for line in result_lines: f.write(line) f.write('\n') # Strip hidden dotfile dirs hidden_dirs = [d for d in dirs if d.startswith('.')] for d in hidden_dirs: dirs.remove(d)
def test_plain(): do_test_engine(DiffEngine.create(name='plain'))
def test_native(): do_test_engine(DiffEngine.create(name='native'))
def main(): parser = ArgumentParser(description="Benchmarks DiffUtils") available_targets = frozenset(bench_methods.keys()) parser.add_argument( "targets", nargs="+", choices=[*available_targets, "all"], help="The items to benchmark", ) parser.add_argument( "--iterations", "-i", default=10, help="The number of benchmark iterations to perform on each", ) parser.add_argument("--repeat", "-r", default=3, help="The number of times to repeat the benchmark") parser.add_argument( "--data-dir", dest="data_dir", default="{}/data".format(dirname(__file__)), help="The location of the benchmarking data", ) args = parser.parse_args() iterations = args.iterations repeat = args.repeat targets = args.targets data_dir = args.data_dir if "all" in targets: if len(targets) > 1: print( "When the 'all' target is specfied, no other targets may be specified", file=stderr, ) exit(1) targets = available_targets max_target_length = max(len(target) for target in targets) for target in sorted(targets): padded_target = target.ljust(max_target_length) for (original_name, revised_name) in test_data: original_lines = test_data_lines(original_name, data_dir=data_dir) revised_lines = test_data_lines(revised_name, data_dir=data_dir) setup_code, bench_code = bench_methods[target] setup_code = textwrap.dedent(setup_code) bench_code = textwrap.dedent(bench_code) if target == "diff": engines = DiffEngine.available_engines() else: engines = None bench_env = dict(globals()) for local_param in ( "original_name", "revised_name", "original_lines", "revised_lines", ): existing_value = bench_env.get(local_param) if existing_value is not None: raise AssertionError("{} already present: {}".format( local_param, repr(existing_value))) bench_env[local_param] = locals()[local_param] def run_bench(bench_env, engine_name=None): timer = Timer(setup=setup_code, stmt=bench_code, globals=bench_env) try: result = min(timer.repeat(repeat=repeat, number=iterations)) except Exception: message = "Unable to run {} between {} and {}".format( target, original_name, revised_name) if engine_name: message += " with {}".format(engine_name) print(message, file=stderr) timer.print_exc() exit(1) result *= 1000 message = "{} {:.3f} ms -- {} and {}".format( padded_target, result, original_name, revised_name) if engine_name: message += " with {}".format(engine_name) print(message) assert "engine" not in bench_env, "engine already present: " + repr( bench_env["engine"]) if engines is not None: for engine in DiffEngine.available_engines(): bench_env["engine"] = engine run_bench(bench_env, engine_name=repr(engine)) else: run_bench(bench_env)