apply.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. # -*- coding: utf-8 -*-
  2. import os.path
  3. import subprocess
  4. import tempfile
  5. from .exceptions import HunkApplyException, SubprocessException
  6. from .snippets import remove, which
  7. def _apply_diff_with_subprocess(diff, lines, reverse=False):
  8. # call out to patch program
  9. patchexec = which("patch")
  10. if not patchexec:
  11. raise SubprocessException("cannot find patch program", code=-1)
  12. tempdir = tempfile.gettempdir()
  13. filepath = os.path.join(tempdir, "wtp-" + str(hash(diff.header)))
  14. oldfilepath = filepath + ".old"
  15. newfilepath = filepath + ".new"
  16. rejfilepath = filepath + ".rej"
  17. patchfilepath = filepath + ".patch"
  18. with open(oldfilepath, "w") as f:
  19. f.write("\n".join(lines) + "\n")
  20. with open(patchfilepath, "w") as f:
  21. f.write(diff.text)
  22. args = [
  23. patchexec,
  24. "--reverse" if reverse else "--forward",
  25. "--quiet",
  26. "--no-backup-if-mismatch",
  27. "-o",
  28. newfilepath,
  29. "-i",
  30. patchfilepath,
  31. "-r",
  32. rejfilepath,
  33. oldfilepath,
  34. ]
  35. ret = subprocess.call(args)
  36. with open(newfilepath) as f:
  37. lines = f.read().splitlines()
  38. try:
  39. with open(rejfilepath) as f:
  40. rejlines = f.read().splitlines()
  41. except IOError:
  42. rejlines = None
  43. remove(oldfilepath)
  44. remove(newfilepath)
  45. remove(rejfilepath)
  46. remove(patchfilepath)
  47. # do this last to ensure files get cleaned up
  48. if ret != 0:
  49. raise SubprocessException("patch program failed", code=ret)
  50. return lines, rejlines
  51. def _reverse(changes):
  52. def _reverse_change(c):
  53. return c._replace(old=c.new, new=c.old)
  54. return [_reverse_change(c) for c in changes]
  55. def apply_diff(diff, text, reverse=False, use_patch=False):
  56. try:
  57. lines = text.splitlines()
  58. except AttributeError:
  59. lines = list(text)
  60. if use_patch:
  61. return _apply_diff_with_subprocess(diff, lines, reverse)
  62. n_lines = len(lines)
  63. changes = _reverse(diff.changes) if reverse else diff.changes
  64. # check that the source text matches the context of the diff
  65. for old, new, line, hunk in changes:
  66. # might have to check for line is None here for ed scripts
  67. if old is not None and line is not None:
  68. if old > n_lines:
  69. raise HunkApplyException(
  70. 'context line {n}, "{line}" does not exist in source'.format(
  71. n=old, line=line
  72. ),
  73. hunk=hunk,
  74. )
  75. if lines[old - 1] != line:
  76. raise HunkApplyException(
  77. 'context line {n}, "{line}" does not match "{sl}"'.format(
  78. n=old, line=line, sl=lines[old - 1]
  79. ),
  80. hunk=hunk,
  81. )
  82. # for calculating the old line
  83. r = 0
  84. i = 0
  85. for old, new, line, hunk in changes:
  86. if old is not None and new is None:
  87. del lines[old - 1 - r + i]
  88. r += 1
  89. elif old is None and new is not None:
  90. lines.insert(new - 1, line)
  91. i += 1
  92. elif old is not None and new is not None:
  93. # Sometimes, people remove hunks from patches, making these
  94. # numbers completely unreliable. Because they're jerks.
  95. pass
  96. return lines