Example #1
0
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)
Example #2
0
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))
Example #3
0
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')
Example #4
0
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)