| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120 |
- # -*- coding: utf-8 -*-
- import os.path
- import subprocess
- import tempfile
- from .exceptions import HunkApplyException, SubprocessException
- from .snippets import remove, which
- def _apply_diff_with_subprocess(diff, lines, reverse=False):
- # 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):
- def _reverse_change(c):
- return c._replace(old=c.new, new=c.old)
- return [_reverse_change(c) for c in changes]
- def apply_diff(diff, text, reverse=False, use_patch=False):
- try:
- lines = text.splitlines()
- except AttributeError:
- lines = list(text)
- if use_patch:
- return _apply_diff_with_subprocess(diff, lines, reverse)
- 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:
- 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
|