def generate_unified_diff(original_file, revised_file, original_lines, patch, context_size=3): """ Convert the patch into unified diff format :param original_file: the name of the original file :param revised_file: the name of the changed file :param original_lines: the content of the original file :param patch: the patch to output :param context_size: the number of context lines to put around each difference :return: the patch as a list of lines in unified diff format """ return output.generate_unified_diff(original_file, revised_file, original_lines, patch, context_size)
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 fix_patch(patch_file: Path, original_file: Path, strict=False, context=5): """Fixes errors detected in the patch, by leniently parsing it and then re-emitting it""" if not patch_file.is_file(): if patch_file.exists(): raise CommandError( "Patch file is a directory: {}".format(patch_file)) else: raise CommandError( "Patch file doesn't exist: {}".format(patch_file)) if not original_file.is_file(): if original_file.exists(): raise CommandError( "Original file is a directory: {}".format(original_file)) else: raise CommandError( "Original file doesn't exist: {}".format(original_file)) patch_lines = [] # TODO: Make a public API for parsing original_name and revised_name original_name, revised_name = None, None with open(patch_file, 'rt') as f: for line in f: if original_name is None and line.startswith('---'): original_name = line[3:].split()[0] elif revised_name is None and line.startswith('+++'): revised_name = line[3:].split()[0] patch_lines.append(line.rstrip("\r\n")) original_lines = [] with open(original_file, 'rt') as f: for line in f: original_lines.append(line.rstrip("\r\n")) if original_name is None: raise CommandError( "Unable to detect original file name in {}".format(patch_file)) elif revised_name is None: raise CommandError( "Unable to detect revised file name in {}".format(patch_file)) patch = parse_unified_diff(patch_lines, lenient=not strict) with open(patch_file, 'wt') as f: for line in generate_unified_diff(original_name, revised_name, original_lines, patch, context_size=context): f.write(line) f.write('\n')
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)