PageRenderTime 36ms CodeModel.GetById 16ms app.highlight 17ms RepoModel.GetById 1ms app.codeStats 0ms

/tortoisehg/util/patchctx.py

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