##// END OF EJS Templates
commit: move 'nothing changed' test into commit()
commit: move 'nothing changed' test into commit()

File last commit:

r8396:d7a77ad9 default
r8404:a2bc39ad default
Show More
patch.py
1396 lines | 45.7 KiB | text/x-python | PythonLexer
Brendan Cully
Move patch-related code into its own module.
r2861 # patch.py - patch file parsing routines
#
Vadim Gelfer
merge git patch code.
r2865 # Copyright 2006 Brendan Cully <brendan@kublai.com>
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
Vadim Gelfer
merge git patch code.
r2865 #
Martin Geisler
updated license to be explicit about GPL version 2
r8225 # This software may be used and distributed according to the terms of the
# GNU General Public License version 2, incorporated herein by reference.
Brendan Cully
Move patch-related code into its own module.
r2861
Matt Mackall
Simplify i18n imports
r3891 from i18n import _
Joel Rosdahl
Expand import * to allow Pyflakes to find problems
r6211 from node import hex, nullid, short
Peter Arrenbrecht
cleanup: drop unused imports
r7873 import base85, cmdutil, mdiff, util, diffhelpers, copies
import cStringIO, email.Parser, os, re, math
Matt Mackall
Replace demandload with new demandimport
r3877 import sys, tempfile, zlib
Vadim Gelfer
commands.import: refactor patch parsing into patch.extract.
r2866
Dirkjan Ochtman
patch: consolidate two different regexes for parsing of git diffs
r7199 gitre = re.compile('diff --git a/(.*) b/(.*)')
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 class PatchError(Exception):
pass
Bryan O'Sullivan
patch.py: re-add the ability to use an external patch program...
r4900 class NoHunks(PatchError):
pass
Brendan Cully
Move import's working dir update code into patch.updatedir
r2933 # helper functions
Stefan Rusek
When applying a git diff, ensure that the target dir exists for new files
r7505 def copyfile(src, dst, basedir):
abssrc, absdst = [util.canonpath(basedir, basedir, x) for x in [src, dst]]
Brendan Cully
Move import's working dir update code into patch.updatedir
r2933 if os.path.exists(absdst):
raise util.Abort(_("cannot create %s: destination already exists") %
dst)
Stefan Rusek
When applying a git diff, ensure that the target dir exists for new files
r7505 dstdir = os.path.dirname(absdst)
if dstdir and not os.path.isdir(dstdir):
try:
os.makedirs(dstdir)
Patrick Mezard
patch: catch only IOError from makedirs()
r7507 except IOError:
Stefan Rusek
When applying a git diff, ensure that the target dir exists for new files
r7505 raise util.Abort(
_("cannot create %s: unable to create destination directory")
Dirkjan Ochtman
patch: kill some trailing whitespace
r7561 % dst)
Matt Mackall
util: add copyfile function
r3629
util.copyfile(abssrc, absdst)
Brendan Cully
Move import's working dir update code into patch.updatedir
r2933
# public functions
Vadim Gelfer
commands.import: refactor patch parsing into patch.extract.
r2866 def extract(ui, fileobj):
'''extract patch from data read from fileobj.
Brendan Cully
Add import --exact....
r4263 patch can be a normal patch or contained in an email message.
Vadim Gelfer
commands.import: refactor patch parsing into patch.extract.
r2866
Brendan Cully
Add import --exact....
r4263 return tuple (filename, message, user, date, node, p1, p2).
Any item in the returned tuple can be None. If filename is None,
fileobj did not contain a patch. Caller must unlink filename when done.'''
Vadim Gelfer
commands.import: refactor patch parsing into patch.extract.
r2866
# attempt to detect the start of a patch
# (this heuristic is borrowed from quilt)
Martin Geisler
patch: turned strings with backslashes into raw strings...
r7736 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
r'retrieving revision [0-9]+(\.[0-9]+)*$|'
r'(---|\*\*\*)[ \t])', re.MULTILINE)
Vadim Gelfer
commands.import: refactor patch parsing into patch.extract.
r2866
fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
tmpfp = os.fdopen(fd, 'w')
try:
msg = email.Parser.Parser().parse(fileobj)
Brendan Cully
patch.extract: do not prepend subject if the description already starts with it
r4777 subject = msg['Subject']
Vadim Gelfer
commands.import: refactor patch parsing into patch.extract.
r2866 user = msg['From']
Patrick Mezard
patch: fix git sendmail handling without proper mail headers
r5418 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
Vadim Gelfer
commands.import: refactor patch parsing into patch.extract.
r2866 # should try to parse msg['Date']
date = None
Brendan Cully
Add import --exact....
r4263 nodeid = None
Eric Hopper
Add ability to parse branch information to hg import
r4443 branch = None
Brendan Cully
Add import --exact....
r4263 parents = []
Vadim Gelfer
commands.import: refactor patch parsing into patch.extract.
r2866
Brendan Cully
patch.extract: do not prepend subject if the description already starts with it
r4777 if subject:
if subject.startswith('[PATCH'):
pend = subject.find(']')
Brendan Cully
Make [PATCH] removal slightly more robust
r4208 if pend >= 0:
Brendan Cully
patch.extract: do not prepend subject if the description already starts with it
r4777 subject = subject[pend+1:].lstrip()
subject = subject.replace('\n\t', ' ')
ui.debug('Subject: %s\n' % subject)
Vadim Gelfer
commands.import: refactor patch parsing into patch.extract.
r2866 if user:
ui.debug('From: %s\n' % user)
diffs_seen = 0
ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
Bryan O'Sullivan
patch.py: re-add the ability to use an external patch program...
r4900 message = ''
Vadim Gelfer
commands.import: refactor patch parsing into patch.extract.
r2866 for part in msg.walk():
content_type = part.get_content_type()
ui.debug('Content-Type: %s\n' % content_type)
if content_type not in ok_types:
continue
payload = part.get_payload(decode=True)
m = diffre.search(payload)
if m:
Brendan Cully
git-send-email compatibility: stop reading changelog after ^---$
r4220 hgpatch = False
ignoretext = False
Vadim Gelfer
commands.import: refactor patch parsing into patch.extract.
r2866 ui.debug(_('found patch at byte %d\n') % m.start(0))
diffs_seen += 1
cfp = cStringIO.StringIO()
for line in payload[:m.start(0)].splitlines():
if line.startswith('# HG changeset patch'):
ui.debug(_('patch generated by hg export\n'))
hgpatch = True
# drop earlier commit message content
cfp.seek(0)
cfp.truncate()
Brendan Cully
patch.extract: fix test-import breakage introduced in the previous changeset
r4778 subject = None
Vadim Gelfer
commands.import: refactor patch parsing into patch.extract.
r2866 elif hgpatch:
if line.startswith('# User '):
user = line[7:]
ui.debug('From: %s\n' % user)
elif line.startswith("# Date "):
date = line[7:]
Eric Hopper
Add ability to parse branch information to hg import
r4443 elif line.startswith("# Branch "):
branch = line[9:]
Brendan Cully
Add import --exact....
r4263 elif line.startswith("# Node ID "):
nodeid = line[10:]
elif line.startswith("# Parent "):
parents.append(line[10:])
Patrick Mezard
patch: fix git sendmail handling without proper mail headers
r5418 elif line == '---' and gitsendmail:
Brendan Cully
git-send-email compatibility: stop reading changelog after ^---$
r4220 ignoretext = True
if not line.startswith('# ') and not ignoretext:
Vadim Gelfer
commands.import: refactor patch parsing into patch.extract.
r2866 cfp.write(line)
cfp.write('\n')
message = cfp.getvalue()
if tmpfp:
tmpfp.write(payload)
if not payload.endswith('\n'):
tmpfp.write('\n')
elif not diffs_seen and message and content_type == 'text/plain':
message += '\n' + payload
except:
tmpfp.close()
os.unlink(tmpname)
raise
Brendan Cully
patch.extract: do not prepend subject if the description already starts with it
r4777 if subject and not message.startswith(subject):
message = '%s\n%s' % (subject, message)
Vadim Gelfer
commands.import: refactor patch parsing into patch.extract.
r2866 tmpfp.close()
if not diffs_seen:
os.unlink(tmpname)
Eric Hopper
Add ability to parse branch information to hg import
r4443 return None, message, user, date, branch, None, None, None
Brendan Cully
Add import --exact....
r4263 p1 = parents and parents.pop(0) or None
p2 = parents and parents.pop(0) or None
Eric Hopper
Add ability to parse branch information to hg import
r4443 return tmpname, message, user, date, branch, nodeid, p1, p2
Brendan Cully
Move patch-related code into its own module.
r2861
Alexis S. L. Carvalho
handle files with both git binary patches and copy/rename ops
r3716 GP_PATCH = 1 << 0 # we have to run patch
GP_FILTER = 1 << 1 # there's some copy/rename operation
GP_BINARY = 1 << 2 # there's a binary patch
Patrick Mezard
patch: extract and rename gitpatch into patchmeta, document
r7148 class patchmeta:
"""Patched file metadata
'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
or COPY. 'path' is patched file path. 'oldpath' is set to the
Patrick Mezard
patch: patchmeta gives (islink, isexec) tuple instead of int mode
r7149 origin file when 'op' is either COPY or RENAME, None otherwise. If
file mode is changed, 'mode' is a tuple (islink, isexec) where
'islink' is True if the file is a symlink and 'isexec' is True if
the file is executable. Otherwise, 'mode' is None.
Patrick Mezard
patch: extract and rename gitpatch into patchmeta, document
r7148 """
def __init__(self, path):
self.path = path
self.oldpath = None
self.mode = None
self.op = 'MODIFY'
self.lineno = 0
self.binary = False
Patrick Mezard
patch: patchmeta gives (islink, isexec) tuple instead of int mode
r7149 def setmode(self, mode):
islink = mode & 020000
isexec = mode & 0100
self.mode = (islink, isexec)
Patrick Mezard
patch: pass linereader to scangitpatch(), extract from iterhunks()...
r7152 def readgitpatch(lr):
Brendan Cully
Move patch-related code into its own module.
r2861 """extract git-style metadata about patches from <patchname>"""
Thomas Arendsen Hein
Whitespace/Tab cleanup
r3223
Brendan Cully
Move patch-related code into its own module.
r2861 # Filter patch for git information
gp = None
gitpatches = []
# Can have a git patch with only metadata, causing patch to complain
Alexis S. L. Carvalho
handle files with both git binary patches and copy/rename ops
r3716 dopatch = 0
Brendan Cully
Move patch-related code into its own module.
r2861
lineno = 0
Patrick Mezard
patch: pass linereader to scangitpatch(), extract from iterhunks()...
r7152 for line in lr:
Brendan Cully
Move patch-related code into its own module.
r2861 lineno += 1
if line.startswith('diff --git'):
m = gitre.match(line)
if m:
if gp:
gitpatches.append(gp)
Thomas Arendsen Hein
white space and line break cleanups
r3673 src, dst = m.group(1, 2)
Patrick Mezard
patch: extract and rename gitpatch into patchmeta, document
r7148 gp = patchmeta(dst)
Brendan Cully
Move patch-related code into its own module.
r2861 gp.lineno = lineno
elif gp:
if line.startswith('--- '):
if gp.op in ('COPY', 'RENAME'):
Alexis S. L. Carvalho
handle files with both git binary patches and copy/rename ops
r3716 dopatch |= GP_FILTER
Brendan Cully
Move patch-related code into its own module.
r2861 gitpatches.append(gp)
gp = None
Alexis S. L. Carvalho
handle files with both git binary patches and copy/rename ops
r3716 dopatch |= GP_PATCH
Brendan Cully
Move patch-related code into its own module.
r2861 continue
if line.startswith('rename from '):
gp.op = 'RENAME'
gp.oldpath = line[12:].rstrip()
elif line.startswith('rename to '):
gp.path = line[10:].rstrip()
elif line.startswith('copy from '):
gp.op = 'COPY'
gp.oldpath = line[10:].rstrip()
elif line.startswith('copy to '):
gp.path = line[8:].rstrip()
elif line.startswith('deleted file'):
gp.op = 'DELETE'
Brendan Cully
patch: handle git patches that remove symlinks (issue1438)
r7517 # is the deleted file a symlink?
gp.setmode(int(line.rstrip()[-6:], 8))
Brendan Cully
Move patch-related code into its own module.
r2861 elif line.startswith('new file mode '):
gp.op = 'ADD'
Patrick Mezard
patch: patchmeta gives (islink, isexec) tuple instead of int mode
r7149 gp.setmode(int(line.rstrip()[-6:], 8))
Brendan Cully
Move patch-related code into its own module.
r2861 elif line.startswith('new mode '):
Patrick Mezard
patch: patchmeta gives (islink, isexec) tuple instead of int mode
r7149 gp.setmode(int(line.rstrip()[-6:], 8))
Brendan Cully
Add git-1.4 binary patch support
r3367 elif line.startswith('GIT binary patch'):
Alexis S. L. Carvalho
handle files with both git binary patches and copy/rename ops
r3716 dopatch |= GP_BINARY
Brendan Cully
Add git-1.4 binary patch support
r3367 gp.binary = True
Brendan Cully
Move patch-related code into its own module.
r2861 if gp:
gitpatches.append(gp)
if not gitpatches:
Alexis S. L. Carvalho
handle files with both git binary patches and copy/rename ops
r3716 dopatch = GP_PATCH
Brendan Cully
Move patch-related code into its own module.
r2861
return (dopatch, gitpatches)
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
class patchfile:
Patrick Mezard
patch: pass an opener to patchfile
r7391 def __init__(self, ui, fname, opener, missing=False):
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 self.fname = fname
Patrick Mezard
patch: pass an opener to patchfile
r7391 self.opener = opener
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 self.ui = ui
Patrick Mezard
patch: write rej files for missing targets (issue 853)
r5652 self.lines = []
self.exists = False
self.missing = missing
if not missing:
try:
Patrick Mezard
patch: isolate patchfile filesystem calls into methods...
r7392 self.lines = self.readlines(fname)
Patrick Mezard
patch: write rej files for missing targets (issue 853)
r5652 self.exists = True
except IOError:
pass
else:
self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 self.hash = {}
self.dirty = 0
self.offset = 0
self.rej = []
self.fileprinted = False
self.printfile(False)
self.hunks = 0
Patrick Mezard
patch: isolate patchfile filesystem calls into methods...
r7392 def readlines(self, fname):
fp = self.opener(fname, 'r')
try:
return fp.readlines()
finally:
fp.close()
def writelines(self, fname, lines):
fp = self.opener(fname, 'w')
try:
fp.writelines(lines)
finally:
fp.close()
def unlink(self, fname):
os.unlink(fname)
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 def printfile(self, warn):
if self.fileprinted:
return
if warn or self.ui.verbose:
self.fileprinted = True
Bryan O'Sullivan
patch.py: fix some incorrect uses of _() for i18n
r4898 s = _("patching file %s\n") % self.fname
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 if warn:
self.ui.warn(s)
else:
self.ui.note(s)
def findlines(self, l, linenum):
# looks through the hash and finds candidate lines. The
# result is a list of line numbers sorted based on distance
# from linenum
def sorter(a, b):
vala = abs(a - linenum)
valb = abs(b - linenum)
return cmp(vala, valb)
Thomas Arendsen Hein
Remove trailing spaces, fix indentation
r5143
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 try:
cand = self.hash[l]
except:
return []
if len(cand) > 1:
# resort our list of potentials forward then back.
Jim Hague
patch: fix sort() comparator argument...
r5547 cand.sort(sorter)
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 return cand
def hashlines(self):
self.hash = {}
for x in xrange(len(self.lines)):
s = self.lines[x]
self.hash.setdefault(s, []).append(x)
def write_rej(self):
# our rejects are a little different from patch(1). This always
# creates rejects in the same form as the original patch. A file
# header is inserted so that you can run the reject through patch again
# without having to type the filename.
if not self.rej:
return
fname = self.fname + ".rej"
self.ui.warn(
Martin Geisler
i18n: avoid naive plural tricks
r6952 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
(len(self.rej), self.hunks, fname))
Patrick Mezard
patch: isolate patchfile filesystem calls into methods...
r7392
def rejlines():
base = os.path.basename(self.fname)
yield "--- %s\n+++ %s\n" % (base, base)
for x in self.rej:
for l in x.hunk:
yield l
if l[-1] != '\n':
yield "\n\ No newline at end of file\n"
self.writelines(fname, rejlines())
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897
def write(self, dest=None):
Patrick Mezard
patch: pass an opener to patchfile
r7391 if not self.dirty:
return
if not dest:
dest = self.fname
Patrick Mezard
patch: isolate patchfile filesystem calls into methods...
r7392 self.writelines(dest, self.lines)
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897
def close(self):
self.write()
self.write_rej()
def apply(self, h, reverse):
if not h.complete():
Bryan O'Sullivan
patch.py: fix some incorrect uses of _() for i18n
r4898 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 (h.number, h.desc, len(h.a), h.lena, len(h.b),
h.lenb))
self.hunks += 1
if reverse:
h.reverse()
Patrick Mezard
patch: write rej files for missing targets (issue 853)
r5652 if self.missing:
self.rej.append(h)
return -1
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 if self.exists and h.createfile():
Bryan O'Sullivan
patch.py: fix some incorrect uses of _() for i18n
r4898 self.ui.warn(_("file %s already exists\n") % self.fname)
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 self.rej.append(h)
return -1
Brendan Cully
patch: handle git patches that remove symlinks (issue1438)
r7517 if isinstance(h, githunk):
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 if h.rmfile():
Patrick Mezard
patch: isolate patchfile filesystem calls into methods...
r7392 self.unlink(self.fname)
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 else:
self.lines[:] = h.new()
self.offset += len(h.new())
self.dirty = 1
return 0
# fast case first, no offsets, no fuzz
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 diffhelpers.testhunk(old, self.lines, start) == 0:
if h.rmfile():
Patrick Mezard
patch: isolate patchfile filesystem calls into methods...
r7392 self.unlink(self.fname)
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 else:
self.lines[start : start + h.lena] = h.new()
self.offset += h.lenb - h.lena
self.dirty = 1
return 0
# ok, we couldn't match the hunk. Lets look for offsets and fuzz it
self.hashlines()
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
for fuzzlen in xrange(3):
for toponly in [ True, False ]:
old = h.old(fuzzlen, toponly)
cand = self.findlines(old[0][1:], search_start)
for l in cand:
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.dirty = 1
if fuzzlen:
fuzzstr = "with fuzz %d " % fuzzlen
f = self.ui.warn
self.printfile(True)
else:
fuzzstr = ""
f = self.ui.note
offset = l - orig_start - fuzzlen
if offset == 1:
Wagner Bruna
patch, i18n: avoid parameterized plural
r8090 msg = _("Hunk #%d succeeded at %d %s"
"(offset %d line).\n")
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 else:
Wagner Bruna
patch, i18n: avoid parameterized plural
r8090 msg = _("Hunk #%d succeeded at %d %s"
"(offset %d lines).\n")
f(msg % (h.number, l+1, fuzzstr, offset))
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 return fuzzlen
self.printfile(True)
Bryan O'Sullivan
patch.py: fix some incorrect uses of _() for i18n
r4898 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 self.rej.append(h)
return -1
class hunk:
Patrick Mezard
patch: check filename is /dev/null for creation or deletion (issue 1033)...
r6280 def __init__(self, desc, num, lr, context, create=False, remove=False):
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 self.number = num
self.desc = desc
self.hunk = [ desc ]
self.a = []
self.b = []
if context:
self.read_context_hunk(lr)
else:
self.read_unified_hunk(lr)
Patrick Mezard
patch: check filename is /dev/null for creation or deletion (issue 1033)...
r6280 self.create = create
self.remove = remove and not create
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897
def read_unified_hunk(self, lr):
m = unidesc.match(self.desc)
if not m:
Bryan O'Sullivan
patch.py: fix some incorrect uses of _() for i18n
r4898 raise PatchError(_("bad hunk #%d") % self.number)
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
if self.lena == None:
self.lena = 1
else:
self.lena = int(self.lena)
if self.lenb == None:
self.lenb = 1
else:
self.lenb = int(self.lenb)
self.starta = int(self.starta)
self.startb = int(self.startb)
Patrick Mezard
patch: pass linereader to scangitpatch(), extract from iterhunks()...
r7152 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 # 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:
Dirkjan Ochtman
fix double indentation and trailing whitespace
r6948 del self.hunk[-1]
del self.a[-1]
del self.b[-1]
self.lena -= 1
self.lenb -= 1
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897
def read_context_hunk(self, lr):
self.desc = lr.readline()
m = contextdesc.match(self.desc)
if not m:
Bryan O'Sullivan
patch.py: fix some incorrect uses of _() for i18n
r4898 raise PatchError(_("bad hunk #%d") % self.number)
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 foo, self.starta, foo2, aend, foo3 = m.groups()
self.starta = int(self.starta)
if aend == None:
aend = self.starta
self.lena = int(aend) - self.starta
if self.starta:
self.lena += 1
for x in xrange(self.lena):
l = lr.readline()
if l.startswith('---'):
lr.push(l)
break
s = l[2:]
if l.startswith('- ') or l.startswith('! '):
u = '-' + s
elif l.startswith(' '):
u = ' ' + s
else:
Bryan O'Sullivan
patch.py: fix some incorrect uses of _() for i18n
r4898 raise PatchError(_("bad hunk #%d old text line %d") %
(self.number, x))
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 self.a.append(u)
self.hunk.append(u)
l = lr.readline()
if l.startswith('\ '):
s = self.a[-1][:-1]
self.a[-1] = s
self.hunk[-1] = s
l = lr.readline()
m = contextdesc.match(l)
if not m:
Bryan O'Sullivan
patch.py: fix some incorrect uses of _() for i18n
r4898 raise PatchError(_("bad hunk #%d") % self.number)
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 foo, self.startb, foo2, bend, foo3 = m.groups()
self.startb = int(self.startb)
if bend == None:
bend = self.startb
self.lenb = int(bend) - self.startb
if self.startb:
self.lenb += 1
hunki = 1
for x in xrange(self.lenb):
l = lr.readline()
if l.startswith('\ '):
s = self.b[-1][:-1]
self.b[-1] = s
self.hunk[hunki-1] = s
continue
if not l:
lr.push(l)
break
s = l[2:]
if l.startswith('+ ') or l.startswith('! '):
u = '+' + s
elif l.startswith(' '):
u = ' ' + s
elif len(self.b) == 0:
# this can happen when the hunk does not add any lines
lr.push(l)
break
else:
Bryan O'Sullivan
patch.py: fix some incorrect uses of _() for i18n
r4898 raise PatchError(_("bad hunk #%d old text line %d") %
(self.number, x))
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 self.b.append(s)
while True:
if hunki >= len(self.hunk):
h = ""
else:
h = self.hunk[hunki]
hunki += 1
if h == u:
break
elif h.startswith('-'):
continue
else:
self.hunk.insert(hunki-1, u)
break
if not self.a:
# this happens when lines were only added to the hunk
for x in self.hunk:
if x.startswith('-') or x.startswith(' '):
self.a.append(x)
if not self.b:
# this happens when lines were only deleted from the hunk
for x in self.hunk:
if x.startswith('+') or x.startswith(' '):
self.b.append(x[1:])
# @@ -start,len +start,len @@
self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
self.startb, self.lenb)
self.hunk[0] = self.desc
def reverse(self):
Patrick Mezard
patch: check filename is /dev/null for creation or deletion (issue 1033)...
r6280 self.create, self.remove = self.remove, self.create
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 origlena = self.lena
origstarta = self.starta
self.lena = self.lenb
self.starta = self.startb
self.lenb = origlena
self.startb = origstarta
self.a = []
self.b = []
# self.hunk[0] is the @@ description
for x in xrange(1, len(self.hunk)):
o = self.hunk[x]
if o.startswith('-'):
n = '+' + o[1:]
self.b.append(o[1:])
elif o.startswith('+'):
n = '-' + o[1:]
self.a.append(n)
else:
n = o
self.b.append(o[1:])
self.a.append(o)
self.hunk[x] = o
def fix_newline(self):
diffhelpers.fix_newline(self.hunk, self.a, self.b)
def complete(self):
return len(self.a) == self.lena and len(self.b) == self.lenb
def createfile(self):
Patrick Mezard
patch: check filename is /dev/null for creation or deletion (issue 1033)...
r6280 return self.starta == 0 and self.lena == 0 and self.create
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897
def rmfile(self):
Patrick Mezard
patch: check filename is /dev/null for creation or deletion (issue 1033)...
r6280 return self.startb == 0 and self.lenb == 0 and self.remove
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897
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(l)-1)
if fuzz:
top = 0
bot = 0
hlen = len(self.hunk)
for x in xrange(hlen-1):
# the hunk starts with the @@ line, so use x+1
if self.hunk[x+1][0] == ' ':
top += 1
else:
break
if not toponly:
for x in xrange(hlen-1):
if self.hunk[hlen-bot-1][0] == ' ':
bot += 1
else:
break
# 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)
Thomas Arendsen Hein
Remove trailing spaces, fix indentation
r5143
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 def newctrl(self):
res = []
for x in self.hunk:
c = x[0]
if c == ' ' or c == '+':
res.append(x)
return res
def new(self, fuzz=0, toponly=False):
return self.fuzzit(self.b, fuzz, toponly)
Brendan Cully
patch: handle git patches that remove symlinks (issue1438)
r7517 class githunk(object):
"""A git hunk"""
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 def __init__(self, gitpatch):
self.gitpatch = gitpatch
self.text = None
Brendan Cully
patch: handle git patches that remove symlinks (issue1438)
r7517 self.hunk = []
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897
def createfile(self):
return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
def rmfile(self):
return self.gitpatch.op == 'DELETE'
def complete(self):
return self.text is not None
def new(self):
return [self.text]
Brendan Cully
patch: handle git patches that remove symlinks (issue1438)
r7517 class binhunk(githunk):
'A binary patch file. Only understands literals so far.'
def __init__(self, gitpatch):
super(binhunk, self).__init__(gitpatch)
self.hunk = ['GIT binary patch\n']
Patrick Mezard
patch: pass linereader to binaryhunk.extract() instead of wrapped fp...
r7153 def extract(self, lr):
line = lr.readline()
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 self.hunk.append(line)
Brendan Cully
Add git-1.4 binary patch support
r3367 while line and not line.startswith('literal '):
Patrick Mezard
patch: pass linereader to binaryhunk.extract() instead of wrapped fp...
r7153 line = lr.readline()
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 self.hunk.append(line)
Brendan Cully
Add git-1.4 binary patch support
r3367 if not line:
Bryan O'Sullivan
patch.py: fix some incorrect uses of _() for i18n
r4898 raise PatchError(_('could not extract binary patch'))
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 size = int(line[8:].rstrip())
Brendan Cully
Add git-1.4 binary patch support
r3367 dec = []
Patrick Mezard
patch: pass linereader to binaryhunk.extract() instead of wrapped fp...
r7153 line = lr.readline()
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 self.hunk.append(line)
while len(line) > 1:
Brendan Cully
Use line length field when extracting git binary patches
r3374 l = line[0]
if l <= 'Z' and l >= 'A':
l = ord(l) - ord('A') + 1
else:
l = ord(l) - ord('a') + 27
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 dec.append(base85.b85decode(line[1:-1])[:l])
Patrick Mezard
patch: pass linereader to binaryhunk.extract() instead of wrapped fp...
r7153 line = lr.readline()
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 self.hunk.append(line)
Brendan Cully
Add git-1.4 binary patch support
r3367 text = zlib.decompress(''.join(dec))
if len(text) != size:
Bryan O'Sullivan
patch.py: fix some incorrect uses of _() for i18n
r4898 raise PatchError(_('binary patch is %d bytes, not %d') %
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 len(text), size)
self.text = text
Brendan Cully
Add git-1.4 binary patch support
r3367
Brendan Cully
patch: handle git patches that remove symlinks (issue1438)
r7517 class symlinkhunk(githunk):
"""A git symlink hunk"""
def __init__(self, gitpatch, hunk):
super(symlinkhunk, self).__init__(gitpatch)
self.hunk = hunk
def complete(self):
return True
def fix_newline(self):
return
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 def parsefilename(str):
# --- filename \t|space stuff
Patrick Mezard
patch: remove CRLF when parsing file names
r5851 s = str[4:].rstrip('\r\n')
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 i = s.find('\t')
if i < 0:
i = s.find(' ')
if i < 0:
return s
return s[:i]
Brendan Cully
Move patch-related code into its own module.
r2861
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 def selectfile(afile_orig, bfile_orig, hunk, strip, reverse):
def pathstrip(path, count=1):
pathlen = len(path)
i = 0
if count == 0:
Benoit Boissinot
fix import with -p0
r6520 return '', path.rstrip()
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 while count > 0:
Patrick Mezard
patch: fix normalized paths separators.
r4922 i = path.find('/', i)
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 if i == -1:
Bryan O'Sullivan
patch.py: fix some incorrect uses of _() for i18n
r4898 raise PatchError(_("unable to strip away %d dirs from %s") %
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 (count, path))
i += 1
# consume '//' in the path
Patrick Mezard
patch: fix normalized paths separators.
r4922 while i < pathlen - 1 and path[i] == '/':
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 i += 1
count -= 1
Patrick Mezard
patch: fix corner case with update + copy patch handling (issue 937)...
r6295 return path[:i].lstrip(), path[i:].rstrip()
Brendan Cully
Add git-1.4 binary patch support
r3367
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 nulla = afile_orig == "/dev/null"
nullb = bfile_orig == "/dev/null"
Patrick Mezard
patch: fix corner case with update + copy patch handling (issue 937)...
r6295 abase, afile = pathstrip(afile_orig, strip)
Matt Mackall
patch: teach selectfile about symlinks (issue1438)
r7783 gooda = not nulla and util.lexists(afile)
Patrick Mezard
patch: fix corner case with update + copy patch handling (issue 937)...
r6295 bbase, bfile = pathstrip(bfile_orig, strip)
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 if afile == bfile:
goodb = gooda
else:
Patrick Mezard
patch: avoid file existence tests when possible in selectfile()
r5651 goodb = not nullb and os.path.exists(bfile)
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 createfunc = hunk.createfile
if reverse:
createfunc = hunk.rmfile
Patrick Mezard
patch: write rej files for missing targets (issue 853)
r5652 missing = not goodb and not gooda and not createfunc()
Patrick Mezard
patch: fix corner case with update + copy patch handling (issue 937)...
r6295 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
# diff is between a file and its backup. In this case, the original
# file should be patched (see original mpatch code).
isbackup = (abase == bbase and bfile.startswith(afile))
Patrick Mezard
patch: write rej files for missing targets (issue 853)
r5652 fname = None
if not missing:
if gooda and goodb:
Patrick Mezard
patch: fix corner case with update + copy patch handling (issue 937)...
r6295 fname = isbackup and afile or bfile
Patrick Mezard
patch: write rej files for missing targets (issue 853)
r5652 elif gooda:
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 fname = afile
Thomas Arendsen Hein
Removed tabs and trailing whitespace in python files
r5760
Patrick Mezard
patch: write rej files for missing targets (issue 853)
r5652 if not fname:
if not nullb:
Patrick Mezard
patch: fix corner case with update + copy patch handling (issue 937)...
r6295 fname = isbackup and afile or bfile
Patrick Mezard
patch: write rej files for missing targets (issue 853)
r5652 elif not nulla:
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 fname = afile
Patrick Mezard
patch: write rej files for missing targets (issue 853)
r5652 else:
raise PatchError(_("undefined source and destination files"))
Thomas Arendsen Hein
Removed tabs and trailing whitespace in python files
r5760
Patrick Mezard
patch: write rej files for missing targets (issue 853)
r5652 return fname, missing
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897
class linereader:
# simple class to allow pushing lines back into the input stream
def __init__(self, fp):
self.fp = fp
self.buf = []
def push(self, line):
Patrick Mezard
patch: pass linereader to scangitpatch(), extract from iterhunks()...
r7152 if line is not None:
self.buf.append(line)
Brendan Cully
Move patch-related code into its own module.
r2861
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 def readline(self):
if self.buf:
Bartosz SKOWRON
patch: simplify linereader
r7520 return self.buf.pop(0)
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 return self.fp.readline()
Patrick Mezard
patch: pass linereader to scangitpatch(), extract from iterhunks()...
r7152 def __iter__(self):
while 1:
l = self.readline()
if not l:
break
yield l
def scangitpatch(lr, firstline):
Dirkjan Ochtman
clean up trailing spaces, leading spaces in C
r7186 """
Patrick Mezard
patch: pass linereader to scangitpatch(), extract from iterhunks()...
r7152 Git patches can emit:
- rename a to b
- change b
- copy a to c
- change c
Dirkjan Ochtman
clean up trailing spaces, leading spaces in C
r7186
Patrick Mezard
patch: pass linereader to scangitpatch(), extract from iterhunks()...
r7152 We cannot apply this sequence as-is, the renamed 'a' could not be
found for it would have been renamed already. And we cannot copy
from 'b' instead because 'b' would have been changed already. So
we scan the git patch for copy and rename commands so we can
perform the copies ahead of time.
"""
pos = 0
try:
pos = lr.fp.tell()
fp = lr.fp
except IOError:
fp = cStringIO.StringIO(lr.fp.read())
gitlr = linereader(fp)
gitlr.push(firstline)
(dopatch, gitpatches) = readgitpatch(gitlr)
fp.seek(pos)
Patrick Mezard
patch: pass linereader to binaryhunk.extract() instead of wrapped fp...
r7153 return dopatch, gitpatches
Patrick Mezard
patch: pass linereader to scangitpatch(), extract from iterhunks()...
r7152
Patrick Mezard
patch: move diff parsing in iterhunks generator
r5650 def iterhunks(ui, fp, sourcefile=None):
"""Read a patch and yield the following events:
- ("file", afile, bfile, firsthunk): select a new target file.
- ("hunk", hunk): a new hunk is ready to be applied, follows a
"file" event.
- ("git", gitchanges): current diff is in git format, gitchanges
maps filenames to gitpatch records. Unique event.
"""
changed = {}
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 current_hunk = None
afile = ""
bfile = ""
state = None
hunknum = 0
Patrick Mezard
patch: move diff parsing in iterhunks generator
r5650 emitfile = False
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 git = False
Brendan Cully
Move patch-related code into its own module.
r2861
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 # our states
BFILE = 1
context = None
lr = linereader(fp)
dopatch = True
Patrick Mezard
patch: fix iterhunks() with trailing binary file removal...
r6179 # gitworkdone is True if a git operation (copy, rename, ...) was
# performed already for the current file. Useful when the file
# section may have no hunk.
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 gitworkdone = False
Brendan Cully
Move patch-related code into its own module.
r2861
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 while True:
newfile = False
x = lr.readline()
if not x:
break
if current_hunk:
if x.startswith('\ '):
current_hunk.fix_newline()
Patrick Mezard
patch: move diff parsing in iterhunks generator
r5650 yield 'hunk', current_hunk
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 current_hunk = None
gitworkdone = False
if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
((context or context == None) and x.startswith('***************')))):
try:
if context == None and x.startswith('***************'):
context = True
Dirkjan Ochtman
patch: consolidate two different regexes for parsing of git diffs
r7199 gpatch = changed.get(bfile)
Patrick Mezard
patch: check filename is /dev/null for creation or deletion (issue 1033)...
r6280 create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD'
remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE'
current_hunk = hunk(x, hunknum + 1, lr, context, create, remove)
Brendan Cully
patch: handle git patches that remove symlinks (issue1438)
r7517 if remove:
gpatch = changed.get(afile[2:])
if gpatch and gpatch.mode[0]:
current_hunk = symlinkhunk(gpatch, current_hunk)
Bryan O'Sullivan
patch.py: fix some incorrect uses of _() for i18n
r4898 except PatchError, err:
ui.debug(err)
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 current_hunk = None
continue
hunknum += 1
Patrick Mezard
patch: move diff parsing in iterhunks generator
r5650 if emitfile:
emitfile = False
yield 'file', (afile, bfile, current_hunk)
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 elif state == BFILE and x.startswith('GIT binary patch'):
Dirkjan Ochtman
patch: consolidate two different regexes for parsing of git diffs
r7199 current_hunk = binhunk(changed[bfile])
Patrick Mezard
mq: missing target files do not make qpush to fail immediately (issue 835)...
r5581 hunknum += 1
Patrick Mezard
patch: move diff parsing in iterhunks generator
r5650 if emitfile:
emitfile = False
Dirkjan Ochtman
patch: consolidate two different regexes for parsing of git diffs
r7199 yield 'file', ('a/' + afile, 'b/' + bfile, current_hunk)
Patrick Mezard
patch: pass linereader to binaryhunk.extract() instead of wrapped fp...
r7153 current_hunk.extract(lr)
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 elif x.startswith('diff --git'):
# check for git diff, scanning the whole patch file if needed
m = gitre.match(x)
if m:
afile, bfile = m.group(1, 2)
if not git:
git = True
Patrick Mezard
patch: pass linereader to binaryhunk.extract() instead of wrapped fp...
r7153 dopatch, gitpatches = scangitpatch(lr, x)
Patrick Mezard
patch: move diff parsing in iterhunks generator
r5650 yield 'git', gitpatches
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 for gp in gitpatches:
Patrick Mezard
patch: map changed files to patchmeta directly
r7150 changed[gp.path] = gp
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 # else error?
# copy/rename + modify should modify target, not source
Dirkjan Ochtman
patch: consolidate two different regexes for parsing of git diffs
r7199 gp = changed.get(bfile)
Vsevolod Solovyov
Fix issue1495, corner case of adding empty files via patching
r7971 if gp and gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD'):
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 afile = bfile
gitworkdone = True
newfile = True
elif x.startswith('---'):
# check for a unified diff
l2 = lr.readline()
if not l2.startswith('+++'):
lr.push(l2)
continue
newfile = True
context = False
afile = parsefilename(x)
bfile = parsefilename(l2)
elif x.startswith('***'):
# check for a context diff
l2 = lr.readline()
if not l2.startswith('---'):
lr.push(l2)
continue
l3 = lr.readline()
lr.push(l3)
if not l3.startswith("***************"):
lr.push(l2)
continue
newfile = True
context = True
afile = parsefilename(x)
bfile = parsefilename(l2)
Benoit Boissinot
unlink temporary patch files even when an exception is raised
r3057
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 if newfile:
Patrick Mezard
patch: move diff parsing in iterhunks generator
r5650 emitfile = True
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 state = BFILE
hunknum = 0
if current_hunk:
if current_hunk.complete():
Patrick Mezard
patch: move diff parsing in iterhunks generator
r5650 yield 'hunk', current_hunk
else:
raise PatchError(_("malformed patch %s %s") % (afile,
current_hunk.desc))
if hunknum == 0 and dopatch and not gitworkdone:
raise NoHunks
Patrick Mezard
patch: remove applydiff() useless updatedir and rejmerge arguments
r7147 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False):
Patrick Mezard
patch: move diff parsing in iterhunks generator
r5650 """reads a patch from fp and tries to apply it. The dict 'changed' is
filled in with all of the filenames changed by the patch. Returns 0
for a clean patch, -1 if any rejects were found and 1 if there was
any fuzz."""
rejects = 0
err = 0
current_file = None
gitpatches = None
Patrick Mezard
patch: pass an opener to patchfile
r7391 opener = util.opener(os.getcwd())
Patrick Mezard
patch: move diff parsing in iterhunks generator
r5650
def closefile():
if not current_file:
return 0
current_file.close()
return len(current_file.rej)
for state, values in iterhunks(ui, fp, sourcefile):
if state == 'hunk':
if not current_file:
continue
current_hunk = values
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 ret = current_file.apply(current_hunk, reverse)
Bryan O'Sullivan
patch.py: don't mark files as changed unless they have actually been changed
r4899 if ret >= 0:
Patrick Mezard
patch: map changed files to patchmeta directly
r7150 changed.setdefault(current_file.fname, None)
Bryan O'Sullivan
patch.py: don't mark files as changed unless they have actually been changed
r4899 if ret > 0:
err = 1
Patrick Mezard
patch: move diff parsing in iterhunks generator
r5650 elif state == 'file':
rejects += closefile()
afile, bfile, first_hunk = values
try:
if sourcefile:
Patrick Mezard
patch: pass an opener to patchfile
r7391 current_file = patchfile(ui, sourcefile, opener)
Patrick Mezard
patch: move diff parsing in iterhunks generator
r5650 else:
Patrick Mezard
patch: write rej files for missing targets (issue 853)
r5652 current_file, missing = selectfile(afile, bfile, first_hunk,
Patrick Mezard
patch: move diff parsing in iterhunks generator
r5650 strip, reverse)
Patrick Mezard
patch: pass an opener to patchfile
r7391 current_file = patchfile(ui, current_file, opener, missing)
Patrick Mezard
patch: move diff parsing in iterhunks generator
r5650 except PatchError, err:
ui.warn(str(err) + '\n')
current_file, current_hunk = None, None
rejects += 1
continue
elif state == 'git':
gitpatches = values
Brendan Cully
Check that git patches only touch files under root
r6791 cwd = os.getcwd()
Patrick Mezard
patch: move diff parsing in iterhunks generator
r5650 for gp in gitpatches:
if gp.op in ('COPY', 'RENAME'):
Stefan Rusek
When applying a git diff, ensure that the target dir exists for new files
r7505 copyfile(gp.oldpath, gp.path, cwd)
Patrick Mezard
patch: map changed files to patchmeta directly
r7150 changed[gp.path] = gp
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 else:
Patrick Mezard
patch: move diff parsing in iterhunks generator
r5650 raise util.Abort(_('unsupported parser state: %s') % state)
Patrick Mezard
patch: move NoHunk detection up with parsing code
r5649
Patrick Mezard
patch: move diff parsing in iterhunks generator
r5650 rejects += closefile()
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 if rejects:
return -1
return err
Vadim Gelfer
refactor text diff/patch code....
r2874
Alexis S. L. Carvalho
add untrusted argument to patch.diffopts
r3554 def diffopts(ui, opts={}, untrusted=False):
Patrick Mezard
Let --unified default to diff.unified (issue 1076)
r6467 def get(key, name=None, getter=ui.configbool):
Alexis S. L. Carvalho
add untrusted argument to patch.diffopts
r3554 return (opts.get(key) or
Patrick Mezard
Let --unified default to diff.unified (issue 1076)
r6467 getter('diff', name or key, None, untrusted=untrusted))
Matt Mackall
Move ui.diffopts to patch.diffopts where it belongs
r2888 return mdiff.diffopts(
text=opts.get('text'),
Alexis S. L. Carvalho
add untrusted argument to patch.diffopts
r3554 git=get('git'),
nodates=get('nodates'),
showfunc=get('show_function', 'showfunc'),
ignorews=get('ignore_all_space', 'ignorews'),
ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
jorendorff@mozilla.com
commands.py, patch.py: add -U option to hg diff command
r6040 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
Patrick Mezard
Let --unified default to diff.unified (issue 1076)
r6467 context=get('unified', getter=ui.config))
Matt Mackall
Move ui.diffopts to patch.diffopts where it belongs
r2888
Brendan Cully
import: add similarity option (issue295)
r7402 def updatedir(ui, repo, patches, similarity=0):
Brendan Cully
Move import's working dir update code into patch.updatedir
r2933 '''Update dirstate after patch application according to metadata'''
if not patches:
return
copies = []
Alexis S. L. Carvalho
handle git patches that rename a file to more than one destination
r3701 removes = {}
Brendan Cully
Move import's working dir update code into patch.updatedir
r2933 cfiles = patches.keys()
cwd = repo.getcwd()
if cwd:
Alexis S. L. Carvalho
pass repo.root to util.pathto() in preparation for the next patch
r4229 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
Brendan Cully
Move import's working dir update code into patch.updatedir
r2933 for f in patches:
Patrick Mezard
patch: map changed files to patchmeta directly
r7150 gp = patches[f]
if not gp:
continue
if gp.op == 'RENAME':
Alexis S. L. Carvalho
Always copy the necessary files before applying a git patch...
r5403 copies.append((gp.oldpath, gp.path))
Alexis S. L. Carvalho
handle git patches that rename a file to more than one destination
r3701 removes[gp.oldpath] = 1
Patrick Mezard
patch: map changed files to patchmeta directly
r7150 elif gp.op == 'COPY':
Alexis S. L. Carvalho
Always copy the necessary files before applying a git patch...
r5403 copies.append((gp.oldpath, gp.path))
Patrick Mezard
patch: map changed files to patchmeta directly
r7150 elif gp.op == 'DELETE':
Alexis S. L. Carvalho
handle git patches that rename a file to more than one destination
r3701 removes[gp.path] = 1
Alexis S. L. Carvalho
Always copy the necessary files before applying a git patch...
r5403 for src, dst in copies:
Matt Mackall
Make repo locks recursive, eliminate all passing of lock/wlock
r4917 repo.copy(src, dst)
Alexis S. L. Carvalho
handle git patches that rename a file to more than one destination
r3701 removes = removes.keys()
Brendan Cully
import: add similarity option (issue295)
r7402 if (not similarity) and removes:
Matt Mackall
replace util.sort with sorted built-in...
r8209 repo.remove(sorted(removes), True)
Brendan Cully
Move import's working dir update code into patch.updatedir
r2933 for f in patches:
Patrick Mezard
patch: map changed files to patchmeta directly
r7150 gp = patches[f]
Brendan Cully
Move import's working dir update code into patch.updatedir
r2933 if gp and gp.mode:
Patrick Mezard
patch: patchmeta gives (islink, isexec) tuple instead of int mode
r7149 islink, isexec = gp.mode
Martin Geisler
use repo.wjoin(f) instead of os.path.join(repo.root, f)
r7570 dst = repo.wjoin(gp.path)
Brendan Cully
git patch: create empty added files
r3588 # patch won't create empty files
Patrick Mezard
patch: map changed files to patchmeta directly
r7150 if gp.op == 'ADD' and not os.path.exists(dst):
Patrick Mezard
patch: patchmeta gives (islink, isexec) tuple instead of int mode
r7149 flags = (isexec and 'x' or '') + (islink and 'l' or '')
Matt Mackall
patch: use util.set_flags
r5706 repo.wwrite(gp.path, '', flags)
Brendan Cully
patch: handle git patches that remove symlinks (issue1438)
r7517 elif gp.op != 'DELETE':
Patrick Mezard
patch: patchmeta gives (islink, isexec) tuple instead of int mode
r7149 util.set_flags(dst, islink, isexec)
Brendan Cully
import: add similarity option (issue295)
r7402 cmdutil.addremove(repo, cfiles, similarity=similarity)
Brendan Cully
Move import's working dir update code into patch.updatedir
r2933 files = patches.keys()
files.extend([r for r in removes if r not in files])
Matt Mackall
replace util.sort with sorted built-in...
r8209 return sorted(files)
Brendan Cully
Move import's working dir update code into patch.updatedir
r2933
Patrick Mezard
patch: change functions definition order for readability
r7151 def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
"""use <patcher> to apply <patchname> to the working directory.
returns whether patch was applied with fuzz factor."""
fuzz = False
if cwd:
args.append('-d %s' % util.shellquote(cwd))
fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
util.shellquote(patchname)))
for line in fp:
line = line.rstrip()
ui.note(line + '\n')
if line.startswith('patching file '):
pf = util.parse_patch_output(line)
printed_file = False
Patrick Mezard
patch: fix patched files records in externalpatcher()
r7247 files.setdefault(pf, None)
Patrick Mezard
patch: change functions definition order for readability
r7151 elif line.find('with fuzz') >= 0:
fuzz = True
if not printed_file:
ui.warn(pf + '\n')
printed_file = True
ui.warn(line + '\n')
elif line.find('saving rejects to file') >= 0:
ui.warn(line + '\n')
elif line.find('FAILED') >= 0:
if not printed_file:
ui.warn(pf + '\n')
printed_file = True
ui.warn(line + '\n')
code = fp.close()
if code:
raise PatchError(_("patch command failed: %s") %
util.explain_exit(code)[0])
return fuzz
def internalpatch(patchobj, ui, strip, cwd, files={}):
"""use builtin patch to apply <patchobj> to the working directory.
returns whether patch was applied with fuzz factor."""
try:
fp = file(patchobj, 'rb')
except TypeError:
fp = patchobj
if cwd:
curdir = os.getcwd()
os.chdir(cwd)
try:
ret = applydiff(ui, fp, files, strip=strip)
finally:
if cwd:
os.chdir(curdir)
if ret < 0:
raise PatchError
return ret > 0
def patch(patchname, ui, strip=1, cwd=None, files={}):
"""apply <patchname> to the working directory.
returns whether patch was applied with fuzz factor."""
patcher = ui.config('ui', 'patch')
args = []
try:
if patcher:
return externalpatch(patcher, args, patchname, ui, strip, cwd,
files)
else:
try:
return internalpatch(patchname, ui, strip, cwd, files)
except NoHunks:
Mads Kiilerich
Make util.find_exe alway returns existing file, fixing issue1459...
r7732 patcher = util.find_exe('gpatch') or util.find_exe('patch') or 'patch'
Patrick Mezard
patch: change functions definition order for readability
r7151 ui.debug(_('no valid hunks found; trying with %r instead\n') %
patcher)
if util.needbinarypatch():
args.append('--binary')
return externalpatch(patcher, args, patchname, ui, strip, cwd,
files)
except PatchError, err:
s = str(err)
if s:
raise util.Abort(s)
else:
raise util.Abort(_('patch failed to apply'))
Bryan O'Sullivan
patch: remove unused parameter from b85diff
r5033 def b85diff(to, tn):
Brendan Cully
Add git-1.4 binary patch support
r3367 '''print base85-encoded binary diff'''
def gitindex(text):
if not text:
return '0' * 40
l = len(text)
Dirkjan Ochtman
python 2.6 compatibility: compatibility wrappers for hash functions
r6470 s = util.sha1('blob %d\0' % l)
Brendan Cully
Add git-1.4 binary patch support
r3367 s.update(text)
return s.hexdigest()
def fmtline(line):
l = len(line)
if l <= 26:
l = chr(ord('A') + l - 1)
else:
l = chr(l - 26 + ord('a') - 1)
return '%c%s\n' % (l, base85.b85encode(line, True))
def chunk(text, csize=52):
l = len(text)
i = 0
while i < l:
yield text[i:i+csize]
i += csize
Alexis S. L. Carvalho
git binary patches: use hashes to detect identical files
r4105 tohash = gitindex(to)
tnhash = gitindex(tn)
if tohash == tnhash:
Alexis S. L. Carvalho
git binary patches: don't print the header for identical files
r4106 return ""
Brendan Cully
Add git-1.4 binary patch support
r3367 # TODO: deltas
Alexis S. L. Carvalho
git binary patches: don't print the header for identical files
r4106 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
(tohash, tnhash, len(tn))]
for l in chunk(zlib.compress(tn)):
ret.append(fmtline(l))
ret.append('\n')
return ''.join(ret)
Brendan Cully
Add git-1.4 binary patch support
r3367
Dirkjan Ochtman
patch: extract local function addmodehdr
r7198 def _addmodehdr(header, omode, nmode):
if omode != nmode:
header.append('old mode %s\n' % omode)
header.append('new mode %s\n' % nmode)
Dirkjan Ochtman
patch: turn patch.diff() into a generator...
r7308 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None):
'''yields diff of changes to files between two nodes, or node and
Vadim Gelfer
refactor text diff/patch code....
r2874 working directory.
if node1 is None, use first dirstate parent instead.
if node2 is None, compare node1 with working directory.'''
if opts is None:
opts = mdiff.defaultopts
if not node1:
node1 = repo.dirstate.parents()[0]
Brendan Cully
Teach mq about git patches
r2934
Benoit Boissinot
patch: use contexts for diff
r3967 flcache = {}
def getfilectx(f, ctx):
flctx = ctx.filectx(f, filelog=flcache.get(f))
if f not in flcache:
flcache[f] = flctx._filelog
return flctx
Brendan Cully
Teach mq about git patches
r2934
Matt Mackall
use repo[changeid] to get a changectx
r6747 ctx1 = repo[node1]
Matt Mackall
diff: pass contexts to status...
r7090 ctx2 = repo[node2]
Vadim Gelfer
refactor text diff/patch code....
r2874
if not changes:
Matt Mackall
diff: pass contexts to status...
r7090 changes = repo.status(ctx1, ctx2, match=match)
Matt Mackall
status: clean up all users for unknown files
r6760 modified, added, removed = changes[:3]
Vadim Gelfer
refactor text diff/patch code....
r2874
if not modified and not added and not removed:
return
Matt Mackall
diff: pass contexts to status...
r7090 date1 = util.datestr(ctx1.date())
man1 = ctx1.manifest()
Benoit Boissinot
patch: use contexts for diff
r3967
Vadim Gelfer
refactor text diff/patch code....
r2874 if repo.ui.quiet:
r = None
else:
Alexis S. L. Carvalho
use short hashes with diff -v
r3387 hexfunc = repo.ui.debugflag and hex or short
Vadim Gelfer
refactor text diff/patch code....
r2874 r = [hexfunc(node) for node in [node1, node2] if node]
Brendan Cully
Add diff --git option
r2907 if opts.git:
Matt Mackall
use repo[changeid] to get a changectx
r6747 copy, diverge = copies.copies(repo, ctx1, ctx2, repo[nullid])
Matt Mackall
patch: copy copies dict before changing it (issue1651)
r8396 copy = copy.copy()
Matt Mackall
diff: use copy smarts from copies.py
r6275 for k, v in copy.items():
copy[v] = k
Brendan Cully
Add diff --git option
r2907
Alexis S. L. Carvalho
Don't generate git patches that rename a file to multiple destinations...
r3702 gone = {}
Matt Mackall
simplify flag handling...
r6743 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
Matt Mackall
exec: add execfunc to simplify exec flag support on non-exec filesystems
r3996
Matt Mackall
replace util.sort with sorted built-in...
r8209 for f in sorted(modified + added + removed):
Vadim Gelfer
refactor text diff/patch code....
r2874 to = None
tn = None
Brendan Cully
Add diff --git option
r2907 dodiff = True
Brendan Cully
Don't generate git diff header for empty diffs
r3329 header = []
Benoit Boissinot
patch: use contexts for diff
r3967 if f in man1:
to = getfilectx(f, ctx1).data()
Vadim Gelfer
refactor text diff/patch code....
r2874 if f not in removed:
Benoit Boissinot
patch: use contexts for diff
r3967 tn = getfilectx(f, ctx2).data()
Dustin Sallings
Use both the from and to name in mdiff.unidiff....
r5482 a, b = f, f
Brendan Cully
Add diff --git option
r2907 if opts.git:
if f in added:
Matt Mackall
simplify flag handling...
r6743 mode = gitmode[ctx2.flags(f)]
Matt Mackall
diff: use copy smarts from copies.py
r6275 if f in copy:
a = copy[f]
Matt Mackall
simplify flag handling...
r6743 omode = gitmode[man1.flags(a)]
Dirkjan Ochtman
patch: extract local function addmodehdr
r7198 _addmodehdr(header, omode, mode)
Alexis S. L. Carvalho
Don't generate git patches that rename a file to multiple destinations...
r3702 if a in removed and a not in gone:
op = 'rename'
gone[a] = 1
else:
op = 'copy'
Brendan Cully
Add diff --git option
r2907 header.append('%s from %s\n' % (op, a))
header.append('%s to %s\n' % (op, f))
Benoit Boissinot
patch: use contexts for diff
r3967 to = getfilectx(a, ctx1).data()
Brendan Cully
Add diff --git option
r2907 else:
header.append('new file mode %s\n' % mode)
Alexis S. L. Carvalho
git patches: handle renames of binary files
r4092 if util.binary(tn):
dodiff = 'binary'
Brendan Cully
Add diff --git option
r2907 elif f in removed:
Matt Mackall
diff: use copy smarts from copies.py
r6275 # have we already reported a copy above?
if f in copy and copy[f] in added and copy[copy[f]] == f:
Brendan Cully
Add diff --git option
r2907 dodiff = False
else:
Matt Mackall
simplify flag handling...
r6743 header.append('deleted file mode %s\n' %
gitmode[man1.flags(f)])
Brendan Cully
Add diff --git option
r2907 else:
Matt Mackall
simplify flag handling...
r6743 omode = gitmode[man1.flags(f)]
nmode = gitmode[ctx2.flags(f)]
Dirkjan Ochtman
patch: extract local function addmodehdr
r7198 _addmodehdr(header, omode, nmode)
Brendan Cully
Add git-1.4 binary patch support
r3367 if util.binary(to) or util.binary(tn):
dodiff = 'binary'
Brendan Cully
Add diff --git option
r2907 r = None
Dirkjan Ochtman
patch/diff: use a separate function to write the first line of a file diff
r7200 header.insert(0, mdiff.diffline(r, a, b, opts))
Alexis S. L. Carvalho
git binary patches: don't print the header for identical files
r4106 if dodiff:
if dodiff == 'binary':
Bryan O'Sullivan
patch: remove unused parameter from b85diff
r5033 text = b85diff(to, tn)
Alexis S. L. Carvalho
git binary patches: don't print the header for identical files
r4106 else:
Thomas Arendsen Hein
merge with crew-stable
r4108 text = mdiff.unidiff(to, date1,
# ctx2 date may be dynamic
tn, util.datestr(ctx2.date()),
Dustin Sallings
Use both the from and to name in mdiff.unidiff....
r5482 a, b, r, opts=opts)
Dirkjan Ochtman
patch: turn patch.diff() into a generator...
r7308 if header and (text or len(header) > 1):
yield ''.join(header)
if text:
yield text
Vadim Gelfer
refactor text diff/patch code....
r2874
def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
opts=None):
'''export changesets as hg patches.'''
total = len(revs)
Thomas Arendsen Hein
Don't use node length for calculating revision number length....
r3900 revwidth = max([len(str(rev)) for rev in revs])
Vadim Gelfer
refactor text diff/patch code....
r2874
Benoit Boissinot
commands.py: use contexts in export
r3970 def single(rev, seqno, fp):
Matt Mackall
use repo[changeid] to get a changectx
r6747 ctx = repo[rev]
Benoit Boissinot
commands.py: use contexts in export
r3970 node = ctx.node()
parents = [p.node() for p in ctx.parents() if p]
Eric Hopper
Add branch information to hg export.
r4436 branch = ctx.branch()
Vadim Gelfer
refactor text diff/patch code....
r2874 if switch_parent:
parents.reverse()
prev = (parents and parents[0]) or nullid
if not fp:
fp = cmdutil.make_file(repo, template, node, total=total,
Ronny Pfannschmidt
export: fixed silent output file overwriting...
r7319 seqno=seqno, revwidth=revwidth,
mode='ab')
Brendan Cully
Suppress <stdout> before hg export -v (regression from previous patch).
r4125 if fp != sys.stdout and hasattr(fp, 'name'):
Vadim Gelfer
refactor text diff/patch code....
r2874 repo.ui.note("%s\n" % fp.name)
fp.write("# HG changeset patch\n")
Benoit Boissinot
commands.py: use contexts in export
r3970 fp.write("# User %s\n" % ctx.user())
fp.write("# Date %d %d\n" % ctx.date())
Eric Hopper
Add branch information to hg export.
r4436 if branch and (branch != 'default'):
fp.write("# Branch %s\n" % branch)
Vadim Gelfer
refactor text diff/patch code....
r2874 fp.write("# Node ID %s\n" % hex(node))
fp.write("# Parent %s\n" % hex(prev))
if len(parents) > 1:
fp.write("# Parent %s\n" % hex(parents[1]))
Benoit Boissinot
commands.py: use contexts in export
r3970 fp.write(ctx.description().rstrip())
Vadim Gelfer
refactor text diff/patch code....
r2874 fp.write("\n\n")
Dirkjan Ochtman
patch: turn patch.diff() into a generator...
r7308 for chunk in diff(repo, prev, node, opts=opts):
fp.write(chunk)
Vadim Gelfer
refactor text diff/patch code....
r2874
Thomas Arendsen Hein
Don't use node length for calculating revision number length....
r3900 for seqno, rev in enumerate(revs):
Benoit Boissinot
commands.py: use contexts in export
r3970 single(rev, seqno+1, fp)
Matt Doar
Add support for diffstat in commit emails, and move diffstat from...
r3096
Alexander Solovyov
python implementation of diffstat...
r7547 def diffstatdata(lines):
Patrick Mezard
diffstat: don't fail on merges...
r7664 filename, adds, removes = None, 0, 0
Alexander Solovyov
python implementation of diffstat...
r7547 for line in lines:
if line.startswith('diff'):
if filename:
yield (filename, adds, removes)
# set numbers to 0 anyway when starting new file
Patrick Mezard
diffstat: don't fail on merges...
r7664 adds, removes = 0, 0
Alexander Solovyov
python implementation of diffstat...
r7547 if line.startswith('diff --git'):
filename = gitre.search(line).group(1)
else:
# format: "diff -r ... -r ... file name"
filename = line.split(None, 5)[-1]
elif line.startswith('+') and not line.startswith('+++'):
adds += 1
elif line.startswith('-') and not line.startswith('---'):
removes += 1
Dirkjan Ochtman
kill some trailing whitespace
r7670 if filename:
Patrick Mezard
diffstat: don't fail on merges...
r7664 yield (filename, adds, removes)
Alexander Solovyov
python implementation of diffstat...
r7547
Matt Mackall
diffstat: use width 80 by default and avoid division by zero
r7860 def diffstat(lines, width=80):
Alexander Solovyov
python implementation of diffstat...
r7547 output = []
stats = list(diffstatdata(lines))
maxtotal, maxname = 0, 0
totaladds, totalremoves = 0, 0
for filename, adds, removes in stats:
totaladds += adds
totalremoves += removes
maxname = max(maxname, len(filename))
maxtotal = max(maxtotal, adds+removes)
countwidth = len(str(maxtotal))
graphwidth = width - countwidth - maxname
if graphwidth < 10:
graphwidth = 10
Matt Mackall
diffstat: use width 80 by default and avoid division by zero
r7860 factor = max(int(math.ceil(float(maxtotal) / graphwidth)), 1)
Alexander Solovyov
python implementation of diffstat...
r7547
for filename, adds, removes in stats:
# If diffstat runs out of room it doesn't print anything, which
# isn't very useful, so always print at least one + or - if there
# were at least some changes
pluses = '+' * max(adds/factor, int(bool(adds)))
minuses = '-' * max(removes/factor, int(bool(removes)))
output.append(' %-*s | %*.d %s%s\n' % (maxname, filename, countwidth,
adds+removes, pluses, minuses))
if stats:
Matt Mackall
diffstat: use width 80 by default and avoid division by zero
r7860 output.append(' %d files changed, %d insertions(+), %d deletions(-)\n'
% (len(stats), totaladds, totalremoves))
Alexander Solovyov
python implementation of diffstat...
r7547
return ''.join(output)