summaryrefslogtreecommitdiff
path: root/mercurial/patch.py
diff options
context:
space:
mode:
Diffstat (limited to 'mercurial/patch.py')
-rw-r--r--mercurial/patch.py280
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)