diff options
author | Lorry Tar Creator <lorry-tar-importer@baserock.org> | 2011-10-01 20:49:36 +0000 |
---|---|---|
committer | Lorry <lorry@roadtrain.codethink.co.uk> | 2012-09-27 13:27:51 +0000 |
commit | 921ced43c48c1d170452a7b251b94cc96ec8dd44 (patch) | |
tree | 3c4a89176ea67fe4c7bf7b375488361a823c95fa /mercurial/patch.py | |
parent | 9039c805b0a7e36220101323f82735f08a104b37 (diff) | |
download | mercurial-tarball-master.tar.gz |
Imported from /srv/lorry/lorry-area/mercurial-tarball/mercurial-1.9.3.tar.gz.HEADmercurial-1.9.3master
Diffstat (limited to 'mercurial/patch.py')
-rw-r--r-- | mercurial/patch.py | 280 |
1 files changed, 120 insertions, 160 deletions
diff --git a/mercurial/patch.py b/mercurial/patch.py index b216734..6c224ee 100644 --- a/mercurial/patch.py +++ b/mercurial/patch.py @@ -126,7 +126,7 @@ def split(stream): mimeheaders = ['content-type'] - if not util.safehasattr(stream, 'next'): + if not hasattr(stream, 'next'): # http responses, for example, have readline but not next stream = fiter(stream) @@ -230,7 +230,7 @@ def extract(ui, fileobj): elif line.startswith("# Node ID "): nodeid = line[10:] elif line.startswith("# Parent "): - parents.append(line[9:].lstrip()) + parents.append(line[10:]) elif not line.startswith("# "): hgpatchheader = False elif line == '---' and gitsendmail: @@ -245,7 +245,7 @@ def extract(ui, fileobj): tmpfp.write('\n') elif not diffs_seen and message and content_type == 'text/plain': message += '\n' + payload - except: # re-raises + except: tmpfp.close() os.unlink(tmpname) raise @@ -290,19 +290,6 @@ class patchmeta(object): other.binary = self.binary return other - def _ispatchinga(self, afile): - if afile == '/dev/null': - return self.op == 'ADD' - return afile == 'a/' + (self.oldpath or self.path) - - def _ispatchingb(self, bfile): - if bfile == '/dev/null': - return self.op == 'DELETE' - return bfile == 'b/' + self.path - - def ispatching(self, afile, bfile): - return self._ispatchinga(afile) and self._ispatchingb(bfile) - def __repr__(self): return "<patchmeta %s %r>" % (self.op, self.path) @@ -488,15 +475,9 @@ class workingbackend(fsbackend): addremoved = set(self.changed) for src, dst in self.copied: scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst) - if self.removed: + addremoved.discard(src) + if (not self.similarity) and self.removed: wctx.forget(sorted(self.removed)) - for f in self.removed: - if f not in self.repo.dirstate: - # File was deleted and no longer belongs to the - # dirstate, it was probably marked added then - # deleted, and should not be considered by - # addremove(). - addremoved.discard(f) if addremoved: cwd = self.repo.getcwd() if cwd: @@ -534,7 +515,7 @@ class filestore(object): if fname in self.data: return self.data[fname] if not self.opener or fname not in self.files: - raise IOError + raise IOError() fn, mode, copied = self.files[fname] return self.opener.read(fn), mode, copied @@ -560,7 +541,7 @@ class repobackend(abstractbackend): try: fctx = self.ctx[fname] except error.LookupError: - raise IOError + raise IOError() flags = fctx.flags() return fctx.data(), ('l' in flags, 'x' in flags) @@ -585,8 +566,8 @@ class repobackend(abstractbackend): return self.changed | self.removed # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1 -unidesc = re.compile('@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@') -contextdesc = re.compile('(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)') +unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@') +contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)') eolmodes = ['strict', 'crlf', 'lf', 'auto'] class patchfile(object): @@ -634,7 +615,7 @@ class patchfile(object): if self.mode is None: self.mode = (False, False) if self.missing: - self.ui.warn(_("unable to find '%s' for patching\n") % self.fname) + self.ui.warn(_("unable to find '%s' for patching\n") % self.fname) self.hash = {} self.dirty = 0 @@ -741,19 +722,22 @@ class patchfile(object): h = h.getnormalized() # fast case first, no offsets, no fuzz - old, oldstart, new, newstart = h.fuzzit(0, False) - oldstart += self.offset - orig_start = oldstart + old = h.old() + # patch starts counting at 1 unless we are adding the file + if h.starta == 0: + start = 0 + else: + start = h.starta + self.offset - 1 + orig_start = start # if there's skew we want to emit the "(offset %d lines)" even # when the hunk cleanly applies at start + skew, so skip the # fast case code - if (self.skew == 0 and - diffhelpers.testhunk(old, self.lines, oldstart) == 0): + if self.skew == 0 and diffhelpers.testhunk(old, self.lines, start) == 0: if self.remove: self.backend.unlink(self.fname) else: - self.lines[oldstart:oldstart + len(old)] = new - self.offset += len(new) - len(old) + self.lines[start : start + h.lena] = h.new() + self.offset += h.lenb - h.lena self.dirty = True return 0 @@ -761,23 +745,23 @@ class patchfile(object): self.hash = {} for x, s in enumerate(self.lines): self.hash.setdefault(s, []).append(x) + if h.hunk[-1][0] != ' ': + # if the hunk tried to put something at the bottom of the file + # override the start line and use eof here + search_start = len(self.lines) + else: + search_start = orig_start + self.skew for fuzzlen in xrange(3): for toponly in [True, False]: - old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly) - oldstart = oldstart + self.offset + self.skew - oldstart = min(oldstart, len(self.lines)) - if old: - cand = self.findlines(old[0][1:], oldstart) - else: - # Only adding lines with no or fuzzed context, just - # take the skew in account - cand = [oldstart] + old = h.old(fuzzlen, toponly) + cand = self.findlines(old[0][1:], search_start) for l in cand: - if not old or diffhelpers.testhunk(old, self.lines, l) == 0: - self.lines[l : l + len(old)] = new - self.offset += len(new) - len(old) + if diffhelpers.testhunk(old, self.lines, l) == 0: + newlines = h.new(fuzzlen, toponly) + self.lines[l : l + len(old)] = newlines + self.offset += len(newlines) - len(old) self.skew = l - orig_start self.dirty = True offset = l - orig_start - fuzzlen @@ -847,7 +831,7 @@ class hunk(object): m = unidesc.match(self.desc) if not m: raise PatchError(_("bad hunk #%d") % self.number) - self.starta, self.lena, self.startb, self.lenb = m.groups() + self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups() if self.lena is None: self.lena = 1 else: @@ -858,8 +842,7 @@ class hunk(object): self.lenb = int(self.lenb) self.starta = int(self.starta) self.startb = int(self.startb) - diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, - self.b) + diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b) # if we hit eof before finishing out the hunk, the last line will # be zero length. Lets try to fix it up. while len(self.hunk[-1]) == 0: @@ -875,7 +858,7 @@ class hunk(object): m = contextdesc.match(self.desc) if not m: raise PatchError(_("bad hunk #%d") % self.number) - self.starta, aend = m.groups() + foo, self.starta, foo2, aend, foo3 = m.groups() self.starta = int(self.starta) if aend is None: aend = self.starta @@ -908,7 +891,7 @@ class hunk(object): m = contextdesc.match(l) if not m: raise PatchError(_("bad hunk #%d") % self.number) - self.startb, bend = m.groups() + foo, self.startb, foo2, bend, foo3 = m.groups() self.startb = int(self.startb) if bend is None: bend = self.startb @@ -983,11 +966,11 @@ class hunk(object): def complete(self): return len(self.a) == self.lena and len(self.b) == self.lenb - def _fuzzit(self, old, new, fuzz, toponly): + def fuzzit(self, l, fuzz, toponly): # this removes context lines from the top and bottom of list 'l'. It # checks the hunk to make sure only context lines are removed, and then # returns a new shortened list of lines. - fuzz = min(fuzz, len(old)) + fuzz = min(fuzz, len(l)-1) if fuzz: top = 0 bot = 0 @@ -1005,28 +988,32 @@ class hunk(object): else: break - bot = min(fuzz, bot) - top = min(fuzz, top) - return old[top:len(old)-bot], new[top:len(new)-bot], top - return old, new, 0 - - def fuzzit(self, fuzz, toponly): - old, new, top = self._fuzzit(self.a, self.b, fuzz, toponly) - oldstart = self.starta + top - newstart = self.startb + top - # zero length hunk ranges already have their start decremented - if self.lena and oldstart > 0: - oldstart -= 1 - if self.lenb and newstart > 0: - newstart -= 1 - return old, oldstart, new, newstart + # top and bot now count context in the hunk + # adjust them if either one is short + context = max(top, bot, 3) + if bot < context: + bot = max(0, fuzz - (context - bot)) + else: + bot = min(fuzz, bot) + if top < context: + top = max(0, fuzz - (context - top)) + else: + top = min(fuzz, top) + + return l[top:len(l)-bot] + return l + + def old(self, fuzz=0, toponly=False): + return self.fuzzit(self.a, fuzz, toponly) + + def new(self, fuzz=0, toponly=False): + return self.fuzzit(self.b, fuzz, toponly) class binhunk(object): 'A binary patch file. Only understands literals so far.' - def __init__(self, lr, fname): + def __init__(self, lr): self.text = None self.hunk = ['GIT binary patch\n'] - self._fname = fname self._read(lr) def complete(self): @@ -1036,37 +1023,30 @@ class binhunk(object): return [self.text] def _read(self, lr): - def getline(lr, hunk): - l = lr.readline() - hunk.append(l) - return l.rstrip('\r\n') - - while True: - line = getline(lr, self.hunk) - if not line: - raise PatchError(_('could not extract "%s" binary data') - % self._fname) - if line.startswith('literal '): - break + line = lr.readline() + self.hunk.append(line) + while line and not line.startswith('literal '): + line = lr.readline() + self.hunk.append(line) + if not line: + raise PatchError(_('could not extract binary patch')) size = int(line[8:].rstrip()) dec = [] - line = getline(lr, self.hunk) + line = lr.readline() + self.hunk.append(line) while len(line) > 1: l = line[0] if l <= 'Z' and l >= 'A': l = ord(l) - ord('A') + 1 else: l = ord(l) - ord('a') + 27 - try: - dec.append(base85.b85decode(line[1:])[:l]) - except ValueError, e: - raise PatchError(_('could not decode "%s" binary patch: %s') - % (self._fname, str(e))) - line = getline(lr, self.hunk) + dec.append(base85.b85decode(line[1:-1])[:l]) + line = lr.readline() + self.hunk.append(line) text = zlib.decompress(''.join(dec)) if len(text) != size: - raise PatchError(_('"%s" length is %d bytes, should be %d') - % (self._fname, len(text), size)) + raise PatchError(_('binary patch is %d bytes, not %d') % + len(text), size) self.text = text def parsefilename(str): @@ -1202,10 +1182,10 @@ def iterhunks(fp): or x.startswith('GIT binary patch')): gp = None if (gitpatches and - gitpatches[-1].ispatching(afile, bfile)): - gp = gitpatches.pop() + (gitpatches[-1][0] == afile or gitpatches[-1][1] == bfile)): + gp = gitpatches.pop()[2] if x.startswith('GIT binary patch'): - h = binhunk(lr, gp.path) + h = binhunk(lr) else: if context is None and x.startswith('***************'): context = True @@ -1216,24 +1196,25 @@ def iterhunks(fp): yield 'file', (afile, bfile, h, gp and gp.copy() or None) yield 'hunk', h elif x.startswith('diff --git'): - m = gitre.match(x.rstrip(' \r\n')) + m = gitre.match(x) if not m: continue - if gitpatches is None: + if not gitpatches: # scan whole input for git metadata - gitpatches = scangitpatch(lr, x) - yield 'git', [g.copy() for g in gitpatches - if g.op in ('COPY', 'RENAME')] + gitpatches = [('a/' + gp.path, 'b/' + gp.path, gp) for gp + in scangitpatch(lr, x)] + yield 'git', [g[2].copy() for g in gitpatches + if g[2].op in ('COPY', 'RENAME')] gitpatches.reverse() afile = 'a/' + m.group(1) bfile = 'b/' + m.group(2) - while gitpatches and not gitpatches[-1].ispatching(afile, bfile): - gp = gitpatches.pop() + while afile != gitpatches[-1][0] and bfile != gitpatches[-1][1]: + gp = gitpatches.pop()[2] yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy()) - if not gitpatches: - raise PatchError(_('failed to synchronize metadata for "%s"') - % afile[2:]) - gp = gitpatches[-1] + gp = gitpatches[-1][2] + # copy/rename + modify should modify target, not source + if gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD') or gp.mode: + afile = bfile newfile = True elif x.startswith('---'): # check for a unified diff @@ -1268,7 +1249,7 @@ def iterhunks(fp): hunknum = 0 while gitpatches: - gp = gitpatches.pop() + gp = gitpatches.pop()[2] yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy()) def applydiff(ui, fp, backend, store, strip=1, eolmode='strict'): @@ -1307,6 +1288,7 @@ def _applydiff(ui, fp, patcher, backend, store, strip=1, current_file = None afile, bfile, first_hunk, gp = values if gp: + path = pstrip(gp.path) gp.path = pstrip(gp.path) if gp.oldpath: gp.oldpath = pstrip(gp.oldpath) @@ -1345,17 +1327,8 @@ def _applydiff(ui, fp, patcher, backend, store, strip=1, elif state == 'git': for gp in values: path = pstrip(gp.oldpath) - try: - data, mode = backend.getfile(path) - except IOError, e: - if e.errno != errno.ENOENT: - raise - # The error ignored here will trigger a getfile() - # error in a place more appropriate for error - # handling, and will not interrupt the patching - # process. - else: - store.setfile(path, data, mode) + data, mode = backend.getfile(path) + store.setfile(path, data, mode) else: raise util.Abort(_('unsupported parser state: %s') % state) @@ -1555,10 +1528,10 @@ def b85diff(to, tn): class GitDiffRequired(Exception): pass -def diffopts(ui, opts=None, untrusted=False, section='diff'): +def diffopts(ui, opts=None, untrusted=False): def get(key, name=None, getter=ui.configbool): return ((opts and opts.get(key)) or - getter(section, name or key, None, untrusted=untrusted)) + getter('diff', name or key, None, untrusted=untrusted)) return mdiff.diffopts( text=opts and opts.get('text'), git=get('git'), @@ -1597,12 +1570,12 @@ def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None, def lrugetfilectx(): cache = {} - order = util.deque() + order = [] def getfilectx(f, ctx): fctx = ctx.filectx(f, filelog=cache.get(f)) if f not in cache: if len(cache) > 20: - del cache[order.popleft()] + del cache[order.pop(0)] cache[f] = fctx.filelog() else: order.remove(f) @@ -1628,16 +1601,15 @@ def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None, copy = {} if opts.git or opts.upgrade: - copy = copies.pathcopies(ctx1, ctx2) + copy = copies.copies(repo, ctx1, ctx2, repo[nullid])[0] - def difffn(opts, losedata): - return trydiff(repo, revs, ctx1, ctx2, modified, added, removed, - copy, getfilectx, opts, losedata, prefix) + difffn = lambda opts, losedata: trydiff(repo, revs, ctx1, ctx2, + modified, added, removed, copy, getfilectx, opts, losedata, prefix) if opts.upgrade and not opts.git: try: def losedata(fn): if not losedatafn or not losedatafn(fn=fn): - raise GitDiffRequired + raise GitDiffRequired() # Buffer the whole output until we are sure it can be generated return list(difffn(opts.copy(git=False), losedata)) except GitDiffRequired: @@ -1647,36 +1619,27 @@ def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None, def difflabel(func, *args, **kw): '''yields 2-tuples of (output, label) based on the output of func()''' - headprefixes = [('diff', 'diff.diffline'), - ('copy', 'diff.extended'), - ('rename', 'diff.extended'), - ('old', 'diff.extended'), - ('new', 'diff.extended'), - ('deleted', 'diff.extended'), - ('---', 'diff.file_a'), - ('+++', 'diff.file_b')] - textprefixes = [('@', 'diff.hunk'), - ('-', 'diff.deleted'), - ('+', 'diff.inserted')] - head = False + prefixes = [('diff', 'diff.diffline'), + ('copy', 'diff.extended'), + ('rename', 'diff.extended'), + ('old', 'diff.extended'), + ('new', 'diff.extended'), + ('deleted', 'diff.extended'), + ('---', 'diff.file_a'), + ('+++', 'diff.file_b'), + ('@@', 'diff.hunk'), + ('-', 'diff.deleted'), + ('+', 'diff.inserted')] + for chunk in func(*args, **kw): lines = chunk.split('\n') for i, line in enumerate(lines): if i != 0: yield ('\n', '') - if head: - if line.startswith('@'): - head = False - else: - if line and line[0] not in ' +-@\\': - head = True stripline = line - if not head and line and line[0] in '+-': + if line and line[0] in '+-': # highlight trailing whitespace, but only in changed lines stripline = line.rstrip() - prefixes = textprefixes - if head: - prefixes = headprefixes for prefix, label in prefixes: if stripline.startswith(prefix): yield (stripline, label) @@ -1815,29 +1778,27 @@ def diffstatdata(lines): diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$') results = [] - filename, adds, removes, isbinary = None, 0, 0, False + filename, adds, removes = None, 0, 0 def addresult(): if filename: + isbinary = adds == 0 and removes == 0 results.append((filename, adds, removes, isbinary)) for line in lines: if line.startswith('diff'): addresult() # set numbers to 0 anyway when starting new file - adds, removes, isbinary = 0, 0, False + adds, removes = 0, 0 if line.startswith('diff --git'): filename = gitre.search(line).group(1) elif line.startswith('diff -r'): # format: "diff -r ... -r ... filename" filename = diffre.search(line).group(1) - elif line.startswith('+') and not line.startswith('+++ '): + elif line.startswith('+') and not line.startswith('+++'): adds += 1 - elif line.startswith('-') and not line.startswith('--- '): + elif line.startswith('-') and not line.startswith('---'): removes += 1 - elif (line.startswith('GIT binary patch') or - line.startswith('Binary file')): - isbinary = True addresult() return results @@ -1862,7 +1823,7 @@ def diffstat(lines, width=80, git=False): return max(i * graphwidth // maxtotal, int(bool(i))) for filename, adds, removes, isbinary in stats: - if isbinary: + if git and isbinary: count = 'Bin' else: count = adds + removes @@ -1873,8 +1834,7 @@ def diffstat(lines, width=80, git=False): countwidth, count, pluses, minuses)) if stats: - output.append(_(' %d files changed, %d insertions(+), ' - '%d deletions(-)\n') + output.append(_(' %d files changed, %d insertions(+), %d deletions(-)\n') % (len(stats), totaladds, totalremoves)) return ''.join(output) |