/tortoisehg/util/patchctx.py

https://bitbucket.org/tortoisehg/hgtk/ · Python · 186 lines · 147 code · 18 blank · 21 comment · 53 complexity · 6807b4f32efa5f4198ec98b4c3b98cf3 MD5 · raw file

  1. # patchctx.py - TortoiseHg patch context class
  2. #
  3. # Copyright 2011 Steve Borho <steve@borho.org>
  4. #
  5. # This software may be used and distributed according to the terms of the
  6. # GNU General Public License version 2 or any later version.
  7. import os
  8. import sys
  9. import shlex
  10. import binascii
  11. import cStringIO
  12. from mercurial import patch, util
  13. from mercurial import node
  14. from mercurial.util import propertycache
  15. from hgext import mq, record
  16. from tortoisehg.util import hglib
  17. class patchctx(object):
  18. def __init__(self, patchpath, repo, pf=None, rev=None):
  19. """ Read patch context from file
  20. :param pf: currently ignored
  21. The provided handle is used to read the patch and
  22. the patchpath contains the name of the patch.
  23. The handle is NOT closed.
  24. """
  25. self._path = patchpath
  26. self._patchname = os.path.basename(patchpath)
  27. self._repo = repo
  28. self._rev = rev or 'patch'
  29. self._status = [[], [], []]
  30. self._fileorder = []
  31. self._user = ''
  32. self._date = ''
  33. self._desc = ''
  34. self._branch = ''
  35. self._node = node.nullid
  36. self._mtime = None
  37. self._parseerror = None
  38. try:
  39. ph = mq.patchheader(self._path)
  40. self._ph = ph
  41. self._mtime = os.path.getmtime(patchpath)
  42. except EnvironmentError:
  43. return
  44. try:
  45. self._branch = ph.branch or ''
  46. self._node = binascii.unhexlify(ph.nodeid)
  47. except TypeError:
  48. pass
  49. except AttributeError:
  50. # hacks to try to deal with older versions of mq.py
  51. self._branch = ''
  52. ph.diffstartline = len(ph.comments)
  53. if ph.message:
  54. ph.diffstartline += 1
  55. self._user = ph.user or ''
  56. self._date = ph.date and util.parsedate(ph.date) or util.makedate()
  57. self._desc = ph.message and '\n'.join(ph.message).strip() or ''
  58. def invalidate(self):
  59. # ensure the patch contents are re-read
  60. self._mtime = 0
  61. def __contains__(self, key):
  62. return key in self._files
  63. def __str__(self): return node.short(self.node())
  64. def node(self): return self._node
  65. def files(self): return self._files.keys()
  66. def rev(self): return self._rev
  67. def hex(self): return node.hex(self.node())
  68. def user(self): return self._user
  69. def date(self): return self._date
  70. def description(self): return self._desc
  71. def branch(self): return self._branch
  72. def parents(self): return ()
  73. def tags(self): return ()
  74. def children(self): return ()
  75. def extra(self): return {}
  76. def flags(self, wfile):
  77. if wfile in self._files:
  78. for gp in patch.readgitpatch(self._files[wfile][0].header):
  79. if gp.mode:
  80. islink, isexec = gp.mode
  81. if islink:
  82. return 'l'
  83. elif wfile in self._status[1]:
  84. # Do not report exec mode change if file is added
  85. return ''
  86. elif isexec:
  87. return 'x'
  88. else:
  89. # techincally, this case could mean the file has had its
  90. # exec bit cleared OR its symlink state removed
  91. # TODO: change readgitpatch() to differentiate
  92. return '-'
  93. return ''
  94. # TortoiseHg methods
  95. def thgtags(self): return []
  96. def thgwdparent(self): return False
  97. def thgmqappliedpatch(self): return False
  98. def thgmqpatchname(self): return self._patchname
  99. def thgbranchhead(self): return False
  100. def thgmqunappliedpatch(self): return True
  101. def longsummary(self):
  102. summary = hglib.tounicode(self.description())
  103. if self._repo.ui.configbool('tortoisehg', 'longsummary'):
  104. limit = 80
  105. lines = summary.splitlines()
  106. if lines:
  107. summary = lines.pop(0)
  108. while len(summary) < limit and lines:
  109. summary += u' ' + lines.pop(0)
  110. summary = summary[0:limit]
  111. else:
  112. summary = ''
  113. else:
  114. lines = summary.splitlines()
  115. summary = lines and lines[0] or ''
  116. return summary
  117. def changesToParent(self, whichparent):
  118. if whichparent == 0 and self._files:
  119. return self._status
  120. else:
  121. return [], [], []
  122. def thgmqpatchdata(self, wfile):
  123. # return file diffs as string list without line ends
  124. if wfile in self._files:
  125. buf = cStringIO.StringIO()
  126. for chunk in self._files[wfile]:
  127. chunk.write(buf)
  128. return buf.getvalue()
  129. return []
  130. @propertycache
  131. def _files(self):
  132. if not hasattr(self, '_ph') or not self._ph.haspatch:
  133. return {}
  134. M, A, R = 0, 1, 2
  135. def get_path(a, b):
  136. type = (a == '/dev/null') and A or M
  137. type = (b == '/dev/null') and R or type
  138. rawpath = (b != '/dev/null') and b or a
  139. if not (rawpath.startswith('a/') or rawpath.startswith('b/')):
  140. return type, rawpath
  141. return type, rawpath.split('/', 1)[-1]
  142. files = {}
  143. pf = open(self._path)
  144. try:
  145. try:
  146. # consume comments and headers
  147. for i in range(self._ph.diffstartline):
  148. pf.readline()
  149. for chunk in record.parsepatch(pf):
  150. if not isinstance(chunk, record.header):
  151. continue
  152. top = patch.parsefilename(chunk.header[-2])
  153. bot = patch.parsefilename(chunk.header[-1])
  154. type, path = get_path(top, bot)
  155. if path not in chunk.files():
  156. type, path = 0, chunk.files()[-1]
  157. if path not in files:
  158. self._status[type].append(path)
  159. files[path] = [chunk]
  160. self._fileorder.append(path)
  161. files[path].extend(chunk.hunks)
  162. except patch.PatchError, e:
  163. self._parseerror = e
  164. if 'THGDEBUG' in os.environ:
  165. print e
  166. finally:
  167. pf.close()
  168. return files