Spaces:
Build error
Build error
# -*- coding: utf-8 -*- | |
import os.path | |
import subprocess | |
import tempfile | |
from .exceptions import HunkApplyException, SubprocessException | |
from .patch import Change, diffobj | |
from .snippets import remove, which | |
def _apply_diff_with_subprocess( | |
diff: diffobj, lines: list[str], reverse: bool = False | |
) -> tuple[list[str], list[str] | None]: | |
# call out to patch program | |
patchexec = which('patch') | |
if not patchexec: | |
raise SubprocessException('cannot find patch program', code=-1) | |
tempdir = tempfile.gettempdir() | |
filepath = os.path.join(tempdir, 'wtp-' + str(hash(diff.header))) | |
oldfilepath = filepath + '.old' | |
newfilepath = filepath + '.new' | |
rejfilepath = filepath + '.rej' | |
patchfilepath = filepath + '.patch' | |
with open(oldfilepath, 'w') as f: | |
f.write('\n'.join(lines) + '\n') | |
with open(patchfilepath, 'w') as f: | |
f.write(diff.text) | |
args = [ | |
patchexec, | |
'--reverse' if reverse else '--forward', | |
'--quiet', | |
'--no-backup-if-mismatch', | |
'-o', | |
newfilepath, | |
'-i', | |
patchfilepath, | |
'-r', | |
rejfilepath, | |
oldfilepath, | |
] | |
ret = subprocess.call(args) | |
with open(newfilepath) as f: | |
lines = f.read().splitlines() | |
try: | |
with open(rejfilepath) as f: | |
rejlines = f.read().splitlines() | |
except IOError: | |
rejlines = None | |
remove(oldfilepath) | |
remove(newfilepath) | |
remove(rejfilepath) | |
remove(patchfilepath) | |
# do this last to ensure files get cleaned up | |
if ret != 0: | |
raise SubprocessException('patch program failed', code=ret) | |
return lines, rejlines | |
def _reverse(changes: list[Change]) -> list[Change]: | |
def _reverse_change(c: Change) -> Change: | |
return c._replace(old=c.new, new=c.old) | |
return [_reverse_change(c) for c in changes] | |
def apply_diff( | |
diff: diffobj, text: str | list[str], reverse: bool = False, use_patch: bool = False | |
) -> list[str]: | |
lines = text.splitlines() if isinstance(text, str) else list(text) | |
if use_patch: | |
lines, _ = _apply_diff_with_subprocess(diff, lines, reverse) | |
return lines | |
n_lines = len(lines) | |
changes = _reverse(diff.changes) if reverse else diff.changes | |
# check that the source text matches the context of the diff | |
for old, new, line, hunk in changes: | |
# might have to check for line is None here for ed scripts | |
if old is not None and line is not None: | |
if old > n_lines: | |
raise HunkApplyException( | |
'context line {n}, "{line}" does not exist in source'.format( | |
n=old, line=line | |
), | |
hunk=hunk, | |
) | |
if lines[old - 1] != line: | |
# Try to normalize whitespace by replacing multiple spaces with a single space | |
# This helps with patches that have different indentation levels | |
normalized_line = ' '.join(line.split()) | |
normalized_source = ' '.join(lines[old - 1].split()) | |
if normalized_line != normalized_source: | |
raise HunkApplyException( | |
'context line {n}, "{line}" does not match "{sl}"'.format( | |
n=old, line=line, sl=lines[old - 1] | |
), | |
hunk=hunk, | |
) | |
# for calculating the old line | |
r = 0 | |
i = 0 | |
for old, new, line, hunk in changes: | |
if old is not None and new is None: | |
del lines[old - 1 - r + i] | |
r += 1 | |
elif old is None and new is not None: | |
lines.insert(new - 1, line) | |
i += 1 | |
elif old is not None and new is not None: | |
# Sometimes, people remove hunks from patches, making these | |
# numbers completely unreliable. Because they're jerks. | |
pass | |
return lines | |