##// END OF EJS Templates
patch: move closefile() into patchfile.close()
patch: move closefile() into patchfile.close()

File last commit:

r13701:bc38ff7c default
r13701:bc38ff7c default
Show More
patch.py
1617 lines | 53.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
Matt Mackall
Update license to GPLv2+
r10263 # GNU General Public License version 2 or any later version.
Brendan Cully
Move patch-related code into its own module.
r2861
Adrian Buehlmann
opener: always reset flags on 'w'rite...
r13112 import cStringIO, email.Parser, os, errno, re
Augie Fackler
patch: move mercurial-specific imports after stdlib imports
r10965 import tempfile, zlib
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
Martin Geisler
patch: break import cycle with cmdutil...
r12266 import base85, mdiff, util, diffhelpers, copies, encoding
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
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]]
Patrick Mezard
patch: do not overwrite broken untracked symlinks
r12341 if os.path.lexists(absdst):
Brendan Cully
Move import's working dir update code into patch.updatedir
r2933 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
Brendan Cully
import: import each patch in a file or stream as a separate change...
r10384 def split(stream):
'''return an iterator of individual patches from a stream'''
def isheader(line, inheader):
if inheader and line[0] in (' ', '\t'):
# continuation
return True
Peter Arrenbrecht
patch: don't look for headers in diff lines...
r10883 if line[0] in (' ', '-', '+'):
# diff line - don't check for header pattern in there
return False
Brendan Cully
import: import each patch in a file or stream as a separate change...
r10384 l = line.split(': ', 1)
return len(l) == 2 and ' ' not in l[0]
def chunk(lines):
return cStringIO.StringIO(''.join(lines))
def hgsplit(stream, cur):
inheader = True
for line in stream:
if not line.strip():
inheader = False
if not inheader and line.startswith('# HG changeset patch'):
yield chunk(cur)
cur = []
inheader = True
cur.append(line)
if cur:
yield chunk(cur)
def mboxsplit(stream, cur):
for line in stream:
if line.startswith('From '):
for c in split(chunk(cur[1:])):
yield c
cur = []
cur.append(line)
if cur:
for c in split(chunk(cur[1:])):
yield c
def mimesplit(stream, cur):
def msgfp(m):
fp = cStringIO.StringIO()
g = email.Generator.Generator(fp, mangle_from_=False)
g.flatten(m)
fp.seek(0)
return fp
for line in stream:
cur.append(line)
c = chunk(cur)
m = email.Parser.Parser().parse(c)
if not m.is_multipart():
yield msgfp(m)
else:
ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
for part in m.walk():
ct = part.get_content_type()
if ct not in ok_types:
continue
yield msgfp(part)
def headersplit(stream, cur):
inheader = False
for line in stream:
if not inheader and isheader(line, inheader):
yield chunk(cur)
cur = []
inheader = True
if inheader and not isheader(line, inheader):
inheader = False
cur.append(line)
if cur:
yield chunk(cur)
def remainder(cur):
yield chunk(cur)
class fiter(object):
def __init__(self, fp):
self.fp = fp
def __iter__(self):
return self
def next(self):
l = self.fp.readline()
if not l:
raise StopIteration
return l
inheader = False
cur = []
mimeheaders = ['content-type']
if not hasattr(stream, 'next'):
# http responses, for example, have readline but not next
stream = fiter(stream)
for line in stream:
cur.append(line)
if line.startswith('# HG changeset patch'):
return hgsplit(stream, cur)
elif line.startswith('From '):
return mboxsplit(stream, cur)
elif isheader(line, inheader):
inheader = True
if line.split(':', 1)[0].lower() in mimeheaders:
# let email parser handle this
return mimesplit(stream, cur)
Brendan Cully
import: if in doubt, consume stream until start of diff...
r10501 elif line.startswith('--- ') and inheader:
# No evil headers seen by diff start, split by hand
Brendan Cully
import: import each patch in a file or stream as a separate change...
r10384 return headersplit(stream, cur)
# Not enough info, keep reading
# if we are here, we have a very plain patch
return remainder(cur)
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
Dan Drake
patch: fix extract() docstring, it returns branch as well
r11645 return tuple (filename, message, user, date, branch, node, p1, p2).
Brendan Cully
Add import --exact....
r4263 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]+)*$|'
Benoit Boissinot
patch: second line of a context diff starts with '--- ', not '+++ '
r10736 r'---[ \t].*?^\+\+\+[ \t]|'
r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
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: do not swallow header-like patch first line (issue1859)...
r9573 if not subject and not user:
# Not an email, restore parsed headers if any
subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
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:
Matt Mackall
many, many trivial check-code fixups
r10282 subject = subject[pend + 1:].lstrip()
Brendan Cully
patch.extract: do not prepend subject if the description already starts with it
r4777 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
Mads Kiilerich
import: don't strip '#' lines from patch descriptions (issue 2417)...
r12645 hgpatchheader = False
Brendan Cully
git-send-email compatibility: stop reading changelog after ^---$
r4220 ignoretext = False
Martin Geisler
do not attempt to translate ui.debug output
r9467 ui.debug('found patch at byte %d\n' % m.start(0))
Vadim Gelfer
commands.import: refactor patch parsing into patch.extract.
r2866 diffs_seen += 1
cfp = cStringIO.StringIO()
for line in payload[:m.start(0)].splitlines():
Mads Kiilerich
import: only the first hg patch marker should be processed (issue2417)...
r12728 if line.startswith('# HG changeset patch') and not hgpatch:
Martin Geisler
do not attempt to translate ui.debug output
r9467 ui.debug('patch generated by hg export\n')
Mads Kiilerich
import: only the first hg patch marker should be processed (issue2417)...
r12728 hgpatch = True
Mads Kiilerich
import: don't strip '#' lines from patch descriptions (issue 2417)...
r12645 hgpatchheader = True
Vadim Gelfer
commands.import: refactor patch parsing into patch.extract.
r2866 # 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
Mads Kiilerich
import: don't strip '#' lines from patch descriptions (issue 2417)...
r12645 elif hgpatchheader:
Vadim Gelfer
commands.import: refactor patch parsing into patch.extract.
r2866 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:])
Mads Kiilerich
import: don't strip '#' lines from patch descriptions (issue 2417)...
r12645 elif not line.startswith("# "):
hgpatchheader = False
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
Mads Kiilerich
import: don't strip '#' lines from patch descriptions (issue 2417)...
r12645 if not hgpatchheader 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
Benoit Boissinot
use new style classes
r8778 class patchmeta(object):
Patrick Mezard
patch: extract and rename gitpatch into patchmeta, document
r7148 """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.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)
Mads Kiilerich
patch: descriptive patchmeta.__repr__ to help debugging
r11018 def __repr__(self):
return "<patchmeta %s %r>" % (self.op, self.path)
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 = []
Patrick Mezard
patch: pass linereader to scangitpatch(), extract from iterhunks()...
r7152 for line in lr:
Bill Barry
fix issue 1763: strip chars from end of line when parsing gitpatch lines
r9243 line = line.rstrip(' \r\n')
Brendan Cully
Move patch-related code into its own module.
r2861 if line.startswith('diff --git'):
m = gitre.match(line)
if m:
if gp:
gitpatches.append(gp)
Nicolas Dumazet
patch: readgitpatch: remove unused variable 'src'
r9392 dst = m.group(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 elif gp:
if line.startswith('--- '):
gitpatches.append(gp)
gp = None
continue
if line.startswith('rename from '):
gp.op = 'RENAME'
Bill Barry
fix issue 1763: strip chars from end of line when parsing gitpatch lines
r9243 gp.oldpath = line[12:]
Brendan Cully
Move patch-related code into its own module.
r2861 elif line.startswith('rename to '):
Bill Barry
fix issue 1763: strip chars from end of line when parsing gitpatch lines
r9243 gp.path = line[10:]
Brendan Cully
Move patch-related code into its own module.
r2861 elif line.startswith('copy from '):
gp.op = 'COPY'
Bill Barry
fix issue 1763: strip chars from end of line when parsing gitpatch lines
r9243 gp.oldpath = line[10:]
Brendan Cully
Move patch-related code into its own module.
r2861 elif line.startswith('copy to '):
Bill Barry
fix issue 1763: strip chars from end of line when parsing gitpatch lines
r9243 gp.path = line[8:]
Brendan Cully
Move patch-related code into its own module.
r2861 elif line.startswith('deleted file'):
gp.op = 'DELETE'
elif line.startswith('new file mode '):
gp.op = 'ADD'
Bill Barry
fix issue 1763: strip chars from end of line when parsing gitpatch lines
r9243 gp.setmode(int(line[-6:], 8))
Brendan Cully
Move patch-related code into its own module.
r2861 elif line.startswith('new mode '):
Bill Barry
fix issue 1763: strip chars from end of line when parsing gitpatch lines
r9243 gp.setmode(int(line[-6:], 8))
Brendan Cully
Add git-1.4 binary patch support
r3367 elif line.startswith('GIT binary patch'):
gp.binary = True
Brendan Cully
Move patch-related code into its own module.
r2861 if gp:
gitpatches.append(gp)
Patrick Mezard
patch: remove unused flags from readgitpatch()
r12669 return gitpatches
Brendan Cully
Move patch-related code into its own module.
r2861
Simon Heimberg
patch: use new style class in linereader
r8891 class linereader(object):
Patrick Mezard
Add patch.eol to ignore EOLs when patching (issue1019)...
r8810 # simple class to allow pushing lines back into the input stream
def __init__(self, fp, textmode=False):
self.fp = fp
self.buf = []
self.textmode = textmode
Martin Geisler
patch: implement patch.eol=auto mode...
r10102 self.eol = None
Patrick Mezard
Add patch.eol to ignore EOLs when patching (issue1019)...
r8810
def push(self, line):
if line is not None:
self.buf.append(line)
def readline(self):
if self.buf:
l = self.buf[0]
del self.buf[0]
return l
l = self.fp.readline()
Martin Geisler
patch: implement patch.eol=auto mode...
r10102 if not self.eol:
if l.endswith('\r\n'):
self.eol = '\r\n'
elif l.endswith('\n'):
self.eol = '\n'
Patrick Mezard
Add patch.eol to ignore EOLs when patching (issue1019)...
r8810 if self.textmode and l.endswith('\r\n'):
l = l[:-2] + '\n'
return l
def __iter__(self):
while 1:
l = self.readline()
if not l:
break
yield l
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+))? (---|\*\*\*)')
Martin Geisler
patch: implement patch.eol=auto mode...
r10102 eolmodes = ['strict', 'crlf', 'lf', 'auto']
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897
Benoit Boissinot
use new style classes
r8778 class patchfile(object):
Martin Geisler
patch: propagate eolmode down to patchfile...
r10101 def __init__(self, ui, fname, opener, missing=False, eolmode='strict'):
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 self.fname = fname
Martin Geisler
patch: propagate eolmode down to patchfile...
r10101 self.eolmode = eolmode
Martin Geisler
patch: implement patch.eol=auto mode...
r10102 self.eol = None
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
Greg Onufer
patch: better handling of sequence of offset patch hunks (issue1941)...
r10135 self.skew = 0
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 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):
Patrick Mezard
patch: handle symlinks without symlinkhunk...
r9585 if os.path.islink(fname):
return [os.readlink(fname)]
Patrick Mezard
patch: isolate patchfile filesystem calls into methods...
r7392 fp = self.opener(fname, 'r')
try:
Martin Geisler
patch: implement patch.eol=auto mode...
r10102 lr = linereader(fp, self.eolmode != 'strict')
lines = list(lr)
self.eol = lr.eol
return lines
Patrick Mezard
patch: isolate patchfile filesystem calls into methods...
r7392 finally:
fp.close()
Dirkjan Ochtman
kill trailing whitespace
r9712 def writelines(self, fname, lines):
Patrick Mezard
patch: handle symlink updates/replacements (issue1785)
r9586 # Ensure supplied data ends in fname, being a regular file or
Martin Geisler
patch: break import cycle with cmdutil...
r12266 # a symlink. cmdutil.updatedir will -too magically- take care
# of setting it to the proper type afterwards.
Adrian Buehlmann
opener: always reset flags on 'w'rite...
r13112 st_mode = None
Patrick Mezard
patch: handle symlink updates/replacements (issue1785)
r9586 islink = os.path.islink(fname)
if islink:
fp = cStringIO.StringIO()
else:
Adrian Buehlmann
opener: always reset flags on 'w'rite...
r13112 try:
st_mode = os.lstat(fname).st_mode & 0777
except OSError, e:
if e.errno != errno.ENOENT:
raise
Patrick Mezard
patch: handle symlink updates/replacements (issue1785)
r9586 fp = self.opener(fname, 'w')
Patrick Mezard
patch: isolate patchfile filesystem calls into methods...
r7392 try:
Patrick Mezard
patch: fix eolmode=auto with new files...
r10127 if self.eolmode == 'auto':
Martin Geisler
patch: implement patch.eol=auto mode...
r10102 eol = self.eol
elif self.eolmode == 'crlf':
eol = '\r\n'
else:
eol = '\n'
Patrick Mezard
patch: fix eolmode=auto with new files...
r10127 if self.eolmode != 'strict' and eol and eol != '\n':
Patrick Mezard
Add patch.eol to ignore EOLs when patching (issue1019)...
r8810 for l in lines:
if l and l[-1] == '\n':
Martin Geisler
patch: implement patch.eol=auto mode...
r10102 l = l[:-1] + eol
Patrick Mezard
Add patch.eol to ignore EOLs when patching (issue1019)...
r8810 fp.write(l)
else:
fp.writelines(lines)
Patrick Mezard
patch: handle symlink updates/replacements (issue1785)
r9586 if islink:
self.opener.symlink(fp.getvalue(), fname)
Adrian Buehlmann
opener: always reset flags on 'w'rite...
r13112 if st_mode is not None:
os.chmod(fname, st_mode)
Patrick Mezard
patch: isolate patchfile filesystem calls into methods...
r7392 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
Thomas Arendsen Hein
Remove trailing spaces, fix indentation
r5143
Benoit Boissinot
patch: simplify logic
r9681 cand = self.hash.get(l, [])
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 if len(cand) > 1:
# resort our list of potentials forward then back.
Alejandro Santos
compat: use 'key' argument instead of 'cmp' when sorting a list
r9032 cand.sort(key=lambda x: abs(x - linenum))
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 return cand
Patrick Mezard
patch: write .rej files without rewriting EOLs...
r13100 def makerejlines(self, fname):
base = os.path.basename(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"
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 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
Patrick Mezard
patch: write .rej files without rewriting EOLs...
r13100 fp = self.opener(fname, 'w')
fp.writelines(self.makerejlines(self.fname))
fp.close()
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897
Nicolas Dumazet
patch: remove the unused, broken reverse() function
r9393 def apply(self, h):
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 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
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
Patrick Mezard
patch: handle symlinks without symlinkhunk...
r9585 if isinstance(h, binhunk):
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
Patrick Mezard
patch: fix eolmode=auto with new files...
r10127 horig = h
Patrick Mezard
patch: drop eol normalization fast-path for 'lf' and 'crlf'...
r10128 if (self.eolmode in ('crlf', 'lf')
or self.eolmode == 'auto' and self.eol):
# If new eols are going to be normalized, then normalize
# hunk data before patching. Otherwise, preserve input
# line-endings.
Patrick Mezard
patch: fix eolmode=auto with new files...
r10127 h = h.getnormalized()
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 # 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
Greg Onufer
patch: better handling of sequence of offset patch hunks (issue1941)...
r10135 # 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, start) == 0:
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[start : start + h.lena] = h.new()
self.offset += h.lenb - h.lena
self.dirty = 1
return 0
Patrick Mezard
patch: inline patchfile.hashlines()
r13700 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
self.hash = {}
for x, s in enumerate(self.lines):
self.hash.setdefault(s, []).append(x)
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 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:
Greg Onufer
patch: better handling of sequence of offset patch hunks (issue1941)...
r10135 search_start = orig_start + self.skew
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897
for fuzzlen in xrange(3):
Matt Mackall
many, many trivial check-code fixups
r10282 for toponly in [True, False]:
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 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)
Greg Onufer
patch: better handling of sequence of offset patch hunks (issue1941)...
r10135 self.skew = l - orig_start
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 self.dirty = 1
Wagner Bruna
patch, i18n: avoid parameterized messages...
r10518 offset = l - orig_start - fuzzlen
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 if fuzzlen:
Wagner Bruna
patch, i18n: avoid parameterized messages...
r10518 msg = _("Hunk #%d succeeded at %d "
"with fuzz %d "
"(offset %d lines).\n")
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 self.printfile(True)
Wagner Bruna
patch, i18n: avoid parameterized messages...
r10518 self.ui.warn(msg %
(h.number, l + 1, fuzzlen, offset))
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 else:
Wagner Bruna
patch, i18n: avoid parameterized messages...
r10518 msg = _("Hunk #%d succeeded at %d "
Wagner Bruna
patch, i18n: avoid parameterized plural
r8090 "(offset %d lines).\n")
Wagner Bruna
patch, i18n: avoid parameterized messages...
r10518 self.ui.note(msg % (h.number, l + 1, 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))
Patrick Mezard
patch: fix eolmode=auto with new files...
r10127 self.rej.append(horig)
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 return -1
Patrick Mezard
patch: move closefile() into patchfile.close()
r13701 def close(self):
if self.dirty:
self.writelines(self.fname, self.lines)
self.write_rej()
return len(self.rej)
Benoit Boissinot
use new style classes
r8778 class hunk(object):
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
Matt Mackall
many, many trivial check-code fixups
r10282 self.hunk = [desc]
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 self.a = []
self.b = []
Benoit Boissinot
patch: initialize all attributes of the hunk class
r9682 self.starta = self.lena = None
self.startb = self.lenb = None
Patrick Mezard
patch: fix eolmode=auto with new files...
r10127 if lr is not None:
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
Patrick Mezard
patch: fix eolmode=auto with new files...
r10127 def getnormalized(self):
"""Return a copy with line endings normalized to LF."""
def normalize(lines):
nlines = []
for line in lines:
if line.endswith('\r\n'):
line = line[:-2] + '\n'
nlines.append(line)
return nlines
# Dummy object, it is rebuilt manually
nh = hunk(self.desc, self.number, None, None, False, False)
nh.number = self.number
nh.desc = self.desc
Patrick Mezard
patch: fix patching with fuzz and eol normalization
r10524 nh.hunk = self.hunk
Patrick Mezard
patch: fix eolmode=auto with new files...
r10127 nh.a = normalize(self.a)
nh.b = normalize(self.b)
nh.starta = self.starta
nh.startb = self.startb
nh.lena = self.lena
nh.lenb = self.lenb
nh.create = self.create
nh.remove = self.remove
return nh
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()
Martin Geisler
use 'x is None' instead of 'x == None'...
r8527 if self.lena is None:
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 self.lena = 1
else:
self.lena = int(self.lena)
Martin Geisler
use 'x is None' instead of 'x == None'...
r8527 if self.lenb is None:
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 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
Patrick Mezard
patch: fix hunk newlines when parsing hunks, not in iterhunks()
r13699 self._fixnewline(lr)
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)
Martin Geisler
use 'x is None' instead of 'x == None'...
r8527 if aend is None:
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 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('---'):
Patrick Mezard
Test applying context diffs
r12825 # lines addition, old block is empty
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 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)
Martin Geisler
use 'x is None' instead of 'x == None'...
r8527 if bend is None:
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 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('\ '):
Patrick Mezard
Test applying context diffs
r12825 # XXX: the only way to hit this is with an invalid line range.
# The no-eol marker is not counted in the line range, but I
# guess there are diff(1) out there which behave differently.
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 s = self.b[-1][:-1]
self.b[-1] = s
Matt Mackall
many, many trivial check-code fixups
r10282 self.hunk[hunki - 1] = s
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 continue
if not l:
Patrick Mezard
Test applying context diffs
r12825 # line deletions, new block is empty and we hit EOF
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 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:
Patrick Mezard
Test applying context diffs
r12825 # line deletions, new block is empty
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 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:
Matt Mackall
many, many trivial check-code fixups
r10282 self.hunk.insert(hunki - 1, u)
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 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
Patrick Mezard
patch: fix hunk newlines when parsing hunks, not in iterhunks()
r13699 self._fixnewline(lr)
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897
Patrick Mezard
patch: fix hunk newlines when parsing hunks, not in iterhunks()
r13699 def _fixnewline(self, lr):
l = lr.readline()
if l.startswith('\ '):
diffhelpers.fix_newline(self.hunk, self.a, self.b)
else:
lr.push(l)
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897
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)
Matt Mackall
many, many trivial check-code fixups
r10282 for x in xrange(hlen - 1):
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 # the hunk starts with the @@ line, so use x+1
Matt Mackall
many, many trivial check-code fixups
r10282 if self.hunk[x + 1][0] == ' ':
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 top += 1
else:
break
if not toponly:
Matt Mackall
many, many trivial check-code fixups
r10282 for x in xrange(hlen - 1):
if self.hunk[hlen - bot - 1][0] == ' ':
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 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 new(self, fuzz=0, toponly=False):
return self.fuzzit(self.b, fuzz, toponly)
Patrick Mezard
patch: handle symlinks without symlinkhunk...
r9585 class binhunk:
'A binary patch file. Only understands literals so far.'
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 def __init__(self, gitpatch):
self.gitpatch = gitpatch
self.text = None
Patrick Mezard
patch: handle symlinks without symlinkhunk...
r9585 self.hunk = ['GIT binary patch\n']
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]
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
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
Mads Kiilerich
patch: strip paths in leaked git patchmeta objects
r11022 def pathstrip(path, strip):
pathlen = len(path)
i = 0
if strip == 0:
return '', path.rstrip()
count = strip
while count > 0:
i = path.find('/', i)
if i == -1:
raise PatchError(_("unable to strip away %d of %d dirs from %s") %
(count, strip, path))
i += 1
# consume '//' in the path
while i < pathlen - 1 and path[i] == '/':
i += 1
count -= 1
return path[:i].lstrip(), path[i:].rstrip()
Nicolas Dumazet
patch: remove the unused, broken reverse() function
r9393 def selectfile(afile_orig, bfile_orig, hunk, strip):
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)
Martin Geisler
util: remove lexists, Python 2.4 introduced os.path.lexists
r12032 gooda = not nulla and os.path.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: fix target when patching broken symlinks (issue2368)
r12340 goodb = not nullb and os.path.lexists(bfile)
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 createfunc = hunk.createfile
Patrick Mezard
patch: write rej files for missing targets (issue 853)
r5652 missing = not goodb and not gooda and not createfunc()
Brendan Cully
patch: create file even if source is not /dev/null...
r9328
Martin Geisler
patch: fix typo in comment
r11820 # some diff programs apparently produce patches where the afile is
# not /dev/null, but afile starts with bfile
Benoit Boissinot
patch: try harder to find the file to patch on file creation (issue2041)...
r10745 abasedir = afile[:afile.rfind('/') + 1]
bbasedir = bfile[:bfile.rfind('/') + 1]
if missing and abasedir == bbasedir and afile.startswith(bfile):
Brendan Cully
patch: create file even if source is not /dev/null...
r9328 # this isn't very pretty
hunk.create = True
if createfunc():
missing = False
else:
hunk.create = False
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
Patrick Mezard
patch: pass linereader to scangitpatch(), extract from iterhunks()...
r7152 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())
Patrick Mezard
Add patch.eol to ignore EOLs when patching (issue1019)...
r8810 gitlr = linereader(fp, lr.textmode)
Patrick Mezard
patch: pass linereader to scangitpatch(), extract from iterhunks()...
r7152 gitlr.push(firstline)
Patrick Mezard
patch: remove unused flags from readgitpatch()
r12669 gitpatches = readgitpatch(gitlr)
Patrick Mezard
patch: pass linereader to scangitpatch(), extract from iterhunks()...
r7152 fp.seek(pos)
Patrick Mezard
patch: remove unused flags from readgitpatch()
r12669 return gitpatches
Patrick Mezard
patch: pass linereader to scangitpatch(), extract from iterhunks()...
r7152
Patrick Mezard
patch: remove unused applydiff() sourcefile argument
r12916 def iterhunks(ui, fp):
Patrick Mezard
patch: move diff parsing in iterhunks generator
r5650 """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 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
Patrick Mezard
patch: drop eol normalization fast-path for 'lf' and 'crlf'...
r10128 lr = linereader(fp)
Brendan Cully
Move patch-related code into its own module.
r2861
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 while True:
Benoit Boissinot
patch: differentiate start of file with diff --git vs '--- '
r10747 newfile = newgitfile = False
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 x = lr.readline()
if not x:
break
Patrick Mezard
patch: remove unused applydiff() sourcefile argument
r12916 if (state == BFILE and ((not context and x[0] == '@') or
Martin Geisler
patch: simplify Boolean expression slightly...
r8526 ((context is not False) and x.startswith('***************')))):
Patrick Mezard
patch: fails immediately upon malformed hunk...
r12675 if context is None and x.startswith('***************'):
context = True
gpatch = changed.get(bfile)
create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD'
remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE'
Patrick Mezard
patch: fix hunk newlines when parsing hunks, not in iterhunks()
r13699 h = hunk(x, hunknum + 1, lr, context, create, remove)
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 hunknum += 1
Patrick Mezard
patch: move diff parsing in iterhunks generator
r5650 if emitfile:
emitfile = False
Patrick Mezard
patch: fix hunk newlines when parsing hunks, not in iterhunks()
r13699 yield 'file', (afile, bfile, h)
yield 'hunk', h
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 elif state == BFILE and x.startswith('GIT binary patch'):
Patrick Mezard
patch: fix hunk newlines when parsing hunks, not in iterhunks()
r13699 h = 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
Patrick Mezard
patch: fix hunk newlines when parsing hunks, not in iterhunks()
r13699 yield 'file', ('a/' + afile, 'b/' + bfile, h)
h.extract(lr)
yield 'hunk', h
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: remove unused flags from readgitpatch()
r12669 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)
Benoit Boissinot
patch: more precise NoHunk, raised for every file (issue2102)
r10748 if gp and (gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD')
or gp.mode):
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 afile = bfile
Benoit Boissinot
patch: differentiate start of file with diff --git vs '--- '
r10747 newgitfile = True
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 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
Benoit Boissinot
patch: differentiate start of file with diff --git vs '--- '
r10747 if newgitfile or 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
Patrick Mezard
patch: move diff parsing in iterhunks generator
r5650
Patrick Mezard
patch: remove unused applydiff() sourcefile argument
r12916 def applydiff(ui, fp, changed, strip=1, eolmode='strict'):
Augie Fackler
patch: refactor applydiff to allow for mempatching
r10966 """Reads a patch from fp and tries to apply it.
Patrick Mezard
patch: move diff parsing in iterhunks generator
r5650
Patrick Mezard
Add patch.eol to ignore EOLs when patching (issue1019)...
r8810 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.
Martin Geisler
patch: propagate eolmode down to patchfile...
r10101 If 'eolmode' is 'strict', the patch content and patched file are
read in binary mode. Otherwise, line endings are ignored when
patching then normalized according to 'eolmode'.
Augie Fackler
patch: refactor applydiff to allow for mempatching
r10966
Martin Geisler
patch: break import cycle with cmdutil...
r12266 Callers probably want to call 'cmdutil.updatedir' after this to
apply certain categories of changes not done by this function.
Patrick Mezard
Add patch.eol to ignore EOLs when patching (issue1019)...
r8810 """
Patrick Mezard
patch: remove unused applydiff() sourcefile argument
r12916 return _applydiff(ui, fp, patchfile, copyfile, changed, strip=strip,
eolmode=eolmode)
Augie Fackler
patch: refactor applydiff to allow for mempatching
r10966
Patrick Mezard
patch: remove unused applydiff() sourcefile argument
r12916 def _applydiff(ui, fp, patcher, copyfn, changed, strip=1, eolmode='strict'):
Patrick Mezard
patch: move diff parsing in iterhunks generator
r5650 rejects = 0
err = 0
current_file = None
Mads Kiilerich
patch: minor cleanup of _applydiff
r11021 cwd = os.getcwd()
opener = util.opener(cwd)
Patrick Mezard
patch: move diff parsing in iterhunks generator
r5650
Patrick Mezard
patch: remove unused applydiff() sourcefile argument
r12916 for state, values in iterhunks(ui, fp):
Patrick Mezard
patch: move diff parsing in iterhunks generator
r5650 if state == 'hunk':
if not current_file:
continue
Mads Kiilerich
patch: minor cleanup of _applydiff
r11021 ret = current_file.apply(values)
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':
Patrick Mezard
patch: move closefile() into patchfile.close()
r13701 if current_file:
rejects += current_file.close()
Patrick Mezard
patch: move diff parsing in iterhunks generator
r5650 afile, bfile, first_hunk = values
try:
Patrick Mezard
patch: remove unused applydiff() sourcefile argument
r12916 current_file, missing = selectfile(afile, bfile,
first_hunk, strip)
current_file = patcher(ui, current_file, opener,
missing=missing, eolmode=eolmode)
Patrick Mezard
patch: move diff parsing in iterhunks generator
r5650 except PatchError, err:
ui.warn(str(err) + '\n')
Mads Kiilerich
patch: minor cleanup of _applydiff
r11021 current_file = None
Patrick Mezard
patch: move diff parsing in iterhunks generator
r5650 rejects += 1
continue
elif state == 'git':
Mads Kiilerich
patch: minor cleanup of _applydiff
r11021 for gp in values:
Mads Kiilerich
patch: strip paths in leaked git patchmeta objects
r11022 gp.path = pathstrip(gp.path, strip - 1)[1]
if gp.oldpath:
gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
Patrick Mezard
patch: fix rename text to binary file (issue2400)
r12574 # Binary patches really overwrite target files, copying them
# will just make it fails with "target file exists"
if gp.op in ('COPY', 'RENAME') and not gp.binary:
Augie Fackler
patch: refactor applydiff to allow for mempatching
r10966 copyfn(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 closefile() into patchfile.close()
r13701 if current_file:
rejects += current_file.close()
Patrick Mezard
patch: move diff parsing in iterhunks generator
r5650
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 if rejects:
return -1
return err
Vadim Gelfer
refactor text diff/patch code....
r2874
Patrick Mezard
patch: simplify externalpatch() arguments
r12673 def externalpatch(patcher, patchname, ui, strip, cwd, files):
Patrick Mezard
patch: change functions definition order for readability
r7151 """use <patcher> to apply <patchname> to the working directory.
returns whether patch was applied with fuzz factor."""
fuzz = False
Patrick Mezard
patch: simplify externalpatch() arguments
r12673 args = []
Patrick Mezard
patch: change functions definition order for readability
r7151 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
Benoit Boissinot
patch: don't use mutable object as default argument
r9683 def internalpatch(patchobj, ui, strip, cwd, files=None, eolmode='strict'):
Patrick Mezard
patch: change functions definition order for readability
r7151 """use builtin patch to apply <patchobj> to the working directory.
returns whether patch was applied with fuzz factor."""
Patrick Mezard
Add patch.eol to ignore EOLs when patching (issue1019)...
r8810
Benoit Boissinot
patch: don't use mutable object as default argument
r9683 if files is None:
files = {}
Patrick Mezard
Add patch.eol to ignore EOLs when patching (issue1019)...
r8810 if eolmode is None:
eolmode = ui.config('patch', 'eol', 'strict')
Martin Geisler
patch: propagate eolmode down to patchfile...
r10101 if eolmode.lower() not in eolmodes:
Martin Geisler
Lowercase error messages
r12067 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
Martin Geisler
patch: propagate eolmode down to patchfile...
r10101 eolmode = eolmode.lower()
Dirkjan Ochtman
kill trailing whitespace
r8843
Patrick Mezard
patch: change functions definition order for readability
r7151 try:
Alejandro Santos
compat: use open() instead of file() everywhere
r9031 fp = open(patchobj, 'rb')
Patrick Mezard
patch: change functions definition order for readability
r7151 except TypeError:
fp = patchobj
if cwd:
curdir = os.getcwd()
os.chdir(cwd)
try:
Martin Geisler
patch: propagate eolmode down to patchfile...
r10101 ret = applydiff(ui, fp, files, strip=strip, eolmode=eolmode)
Patrick Mezard
patch: change functions definition order for readability
r7151 finally:
if cwd:
os.chdir(curdir)
Patrick Mezard
patch: explicitely close input patch files when leaving...
r10203 if fp != patchobj:
fp.close()
Patrick Mezard
patch: change functions definition order for readability
r7151 if ret < 0:
Patrick Mezard
patch: always raise PatchError with a message, simplify handling
r12674 raise PatchError(_('patch failed to apply'))
Patrick Mezard
patch: change functions definition order for readability
r7151 return ret > 0
Benoit Boissinot
patch: don't use mutable object as default argument
r9683 def patch(patchname, ui, strip=1, cwd=None, files=None, eolmode='strict'):
Patrick Mezard
Add patch.eol to ignore EOLs when patching (issue1019)...
r8810 """Apply <patchname> to the working directory.
'eolmode' specifies how end of lines should be handled. It can be:
- 'strict': inputs are read in binary mode, EOLs are preserved
- 'crlf': EOLs are ignored when patching and reset to CRLF
- 'lf': EOLs are ignored when patching and reset to LF
- None: get it from user settings, default to 'strict'
'eolmode' is ignored when using an external patcher program.
Returns whether patch was applied with fuzz factor.
"""
Patrick Mezard
patch: change functions definition order for readability
r7151 patcher = ui.config('ui', 'patch')
Benoit Boissinot
patch: don't use mutable object as default argument
r9683 if files is None:
files = {}
Patrick Mezard
patch: change functions definition order for readability
r7151 try:
if patcher:
Patrick Mezard
patch: simplify externalpatch() arguments
r12673 return externalpatch(patcher, patchname, ui, strip, cwd, files)
Patrick Mezard
patch: remove internal patcher fallback and NoHunk error...
r12671 return internalpatch(patchname, ui, strip, cwd, files, eolmode)
Patrick Mezard
patch: change functions definition order for readability
r7151 except PatchError, err:
Patrick Mezard
patch: always raise PatchError with a message, simplify handling
r12674 raise util.Abort(str(err))
Patrick Mezard
patch: change functions definition order for readability
r7151
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:
Martin Geisler
clone, patch, convert: use hex(nullid) instead of '0'*40
r12144 return hex(nullid)
Brendan Cully
Add git-1.4 binary patch support
r3367 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:
Matt Mackall
many, many trivial check-code fixups
r10282 yield text[i:i + csize]
Brendan Cully
Add git-1.4 binary patch support
r3367 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
Patrick Mezard
patch: support diff data loss detection and upgrade...
r10189 class GitDiffRequired(Exception):
pass
Dirkjan Ochtman
patch: extract local function addmodehdr
r7198
Benoit Boissinot
patch/diff: move diff related code next to each other
r10615 def diffopts(ui, opts=None, untrusted=False):
def get(key, name=None, getter=ui.configbool):
return ((opts and opts.get(key)) or
getter('diff', name or key, None, untrusted=untrusted))
return mdiff.diffopts(
text=opts and opts.get('text'),
git=get('git'),
nodates=get('nodates'),
showfunc=get('show_function', 'showfunc'),
ignorews=get('ignore_all_space', 'ignorews'),
ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
context=get('unified', getter=ui.config))
Patrick Mezard
patch: support diff data loss detection and upgrade...
r10189 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
Martin Geisler
diff: recurse into subrepositories with --subrepos/-S flag
r12167 losedatafn=None, prefix=''):
Dirkjan Ochtman
patch: turn patch.diff() into a generator...
r7308 '''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.
Patrick Mezard
patch: support diff data loss detection and upgrade...
r10189 if node2 is None, compare node1 with working directory.
losedatafn(**kwarg) is a callable run when opts.upgrade=True and
every time some change cannot be represented with the current
patch format. Return False to upgrade to git patch format, True to
accept the loss or raise an exception to abort the diff. It is
called with the name of current file being diffed as 'fn'. If set
to None, patches will always be upgraded to git format when
necessary.
Martin Geisler
diff: recurse into subrepositories with --subrepos/-S flag
r12167
prefix is a filename prefix that is prepended to all filenames on
display (used for subrepos).
Patrick Mezard
patch: support diff data loss detection and upgrade...
r10189 '''
Vadim Gelfer
refactor text diff/patch code....
r2874
if opts is None:
opts = mdiff.defaultopts
Yannick Gingras
diff: add --inverse option...
r9725 if not node1 and not node2:
Vadim Gelfer
refactor text diff/patch code....
r2874 node1 = repo.dirstate.parents()[0]
Brendan Cully
Teach mq about git patches
r2934
Brendan Cully
Make patch.diff filelog cache LRU of 20 files. Fixes issue1738....
r9123 def lrugetfilectx():
cache = {}
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.pop(0)]
Benoit Boissinot
patch: use the public ctx API instead of the internals
r9684 cache[f] = fctx.filelog()
Brendan Cully
Make patch.diff filelog cache LRU of 20 files. Fixes issue1738....
r9123 else:
order.remove(f)
order.append(f)
return fctx
return getfilectx
getfilectx = lrugetfilectx()
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:
Patrick Mezard
patch: support diff data loss detection and upgrade...
r10189 return []
revs = None
if not repo.ui.quiet:
hexfunc = repo.ui.debugflag and hex or short
revs = [hexfunc(node) for node in [node1, node2] if node]
copy = {}
if opts.git or opts.upgrade:
copy = copies.copies(repo, ctx1, ctx2, repo[nullid])[0]
difffn = lambda opts, losedata: trydiff(repo, revs, ctx1, ctx2,
Martin Geisler
diff: recurse into subrepositories with --subrepos/-S flag
r12167 modified, added, removed, copy, getfilectx, opts, losedata, prefix)
Patrick Mezard
patch: support diff data loss detection and upgrade...
r10189 if opts.upgrade and not opts.git:
try:
def losedata(fn):
if not losedatafn or not losedatafn(fn=fn):
raise GitDiffRequired()
# Buffer the whole output until we are sure it can be generated
return list(difffn(opts.copy(git=False), losedata))
except GitDiffRequired:
return difffn(opts.copy(git=True), None)
else:
return difffn(opts, None)
Brodie Rao
diff: make use of output labeling
r10818 def difflabel(func, *args, **kw):
'''yields 2-tuples of (output, label) based on the output of func()'''
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', '')
stripline = line
if line and line[0] in '+-':
# highlight trailing whitespace, but only in changed lines
stripline = line.rstrip()
for prefix, label in prefixes:
if stripline.startswith(prefix):
yield (stripline, label)
break
else:
yield (line, '')
if line != stripline:
yield (line[len(stripline):], 'diff.trailingwhitespace')
def diffui(*args, **kw):
'''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
return difflabel(diff, *args, **kw)
Patrick Mezard
patch: support diff data loss detection and upgrade...
r10189 def _addmodehdr(header, omode, nmode):
if omode != nmode:
header.append('old mode %s\n' % omode)
header.append('new mode %s\n' % nmode)
def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
Martin Geisler
diff: recurse into subrepositories with --subrepos/-S flag
r12167 copy, getfilectx, opts, losedatafn, prefix):
def join(f):
return os.path.join(prefix, f)
Vadim Gelfer
refactor text diff/patch code....
r2874
Matt Mackall
diff: pass contexts to status...
r7090 date1 = util.datestr(ctx1.date())
man1 = ctx1.manifest()
Benoit Boissinot
patch: use contexts for diff
r3967
Patrick Mezard
patch: support diff data loss detection and upgrade...
r10189 gone = set()
gitmode = {'l': '120000', 'x': '100755', '': '100644'}
Vadim Gelfer
refactor text diff/patch code....
r2874
Wagner Bruna
patch: separate reverse copy data (issue1959)
r10466 copyto = dict([(v, k) for k, v in copy.items()])
Brendan Cully
Add diff --git option
r2907 if opts.git:
Patrick Mezard
patch: support diff data loss detection and upgrade...
r10189 revs = None
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
Patrick Mezard
patch: support diff data loss detection and upgrade...
r10189 if opts.git or losedatafn:
Brendan Cully
Add diff --git option
r2907 if f in added:
Matt Mackall
simplify flag handling...
r6743 mode = gitmode[ctx2.flags(f)]
Wagner Bruna
patch: separate reverse copy data (issue1959)
r10466 if f in copy or f in copyto:
Patrick Mezard
patch: support diff data loss detection and upgrade...
r10189 if opts.git:
Wagner Bruna
patch: separate reverse copy data (issue1959)
r10466 if f in copy:
a = copy[f]
else:
a = copyto[f]
Patrick Mezard
patch: support diff data loss detection and upgrade...
r10189 omode = gitmode[man1.flags(a)]
_addmodehdr(header, omode, mode)
if a in removed and a not in gone:
op = 'rename'
gone.add(a)
else:
op = 'copy'
Martin Geisler
diff: recurse into subrepositories with --subrepos/-S flag
r12167 header.append('%s from %s\n' % (op, join(a)))
header.append('%s to %s\n' % (op, join(f)))
Patrick Mezard
patch: support diff data loss detection and upgrade...
r10189 to = getfilectx(a, ctx1).data()
Alexis S. L. Carvalho
Don't generate git patches that rename a file to multiple destinations...
r3702 else:
Patrick Mezard
patch: support diff data loss detection and upgrade...
r10189 losedatafn(f)
Brendan Cully
Add diff --git option
r2907 else:
Patrick Mezard
patch: support diff data loss detection and upgrade...
r10189 if opts.git:
header.append('new file mode %s\n' % mode)
elif ctx2.flags(f):
losedatafn(f)
Patrick Mezard
patch: test and document a bit binary to regular file upgrade
r12576 # In theory, if tn was copied or renamed we should check
# if the source is binary too but the copy record already
# forces git mode.
Alexis S. L. Carvalho
git patches: handle renames of binary files
r4092 if util.binary(tn):
Patrick Mezard
patch: support diff data loss detection and upgrade...
r10189 if opts.git:
dodiff = 'binary'
else:
losedatafn(f)
if not opts.git and not tn:
# regular diffs cannot represent new empty file
losedatafn(f)
Brendan Cully
Add diff --git option
r2907 elif f in removed:
Patrick Mezard
patch: support diff data loss detection and upgrade...
r10189 if opts.git:
# have we already reported a copy above?
Patrick Mezard
patch: remove useless copy, cleanup
r10467 if ((f in copy and copy[f] in added
and copyto[copy[f]] == f) or
(f in copyto and copyto[f] in added
and copy[copyto[f]] == f)):
Patrick Mezard
patch: support diff data loss detection and upgrade...
r10189 dodiff = False
else:
header.append('deleted file mode %s\n' %
gitmode[man1.flags(f)])
Patrick Mezard
patch: upgrade to git patch when removing binary file...
r12575 elif not to or util.binary(to):
Patrick Mezard
patch: support diff data loss detection and upgrade...
r10189 # regular diffs cannot represent empty file deletion
losedatafn(f)
Brendan Cully
Add diff --git option
r2907 else:
Patrick Mezard
patch: support diff data loss detection and upgrade...
r10189 oflag = man1.flags(f)
nflag = ctx2.flags(f)
binary = util.binary(to) or util.binary(tn)
if opts.git:
_addmodehdr(header, gitmode[oflag], gitmode[nflag])
if binary:
dodiff = 'binary'
elif binary or nflag != oflag:
losedatafn(f)
if opts.git:
Martin Geisler
diff: recurse into subrepositories with --subrepos/-S flag
r12167 header.insert(0, mdiff.diffline(revs, join(a), join(b), opts))
Patrick Mezard
patch: support diff data loss detection and upgrade...
r10189
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()),
Martin Geisler
diff: recurse into subrepositories with --subrepos/-S flag
r12167 join(a), join(b), revs, 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
Alexander Solovyov
python implementation of diffstat...
r7547 def diffstatdata(lines):
Gastón Kleiman
diffstat: fix parsing of filenames with spaces...
r13395 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
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:
Brodie Rao
diffstat: with --git, mark binary files with Bin...
r9642 isbinary = adds == 0 and removes == 0
yield (filename, adds, removes, isbinary)
Alexander Solovyov
python implementation of diffstat...
r7547 # 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)
Gastón Kleiman
diffstat: fix parsing of filenames with spaces...
r13395 elif line.startswith('diff -r'):
timeless
Generally replace "file name" with "filename" in help and comments.
r8761 # format: "diff -r ... -r ... filename"
Gastón Kleiman
diffstat: fix parsing of filenames with spaces...
r13395 filename = diffre.search(line).group(1)
Alexander Solovyov
python implementation of diffstat...
r7547 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:
Brodie Rao
diffstat: with --git, mark binary files with Bin...
r9642 isbinary = adds == 0 and removes == 0
yield (filename, adds, removes, isbinary)
Alexander Solovyov
python implementation of diffstat...
r7547
Brodie Rao
diffstat: with --git, mark binary files with Bin...
r9642 def diffstat(lines, width=80, git=False):
Alexander Solovyov
python implementation of diffstat...
r7547 output = []
stats = list(diffstatdata(lines))
maxtotal, maxname = 0, 0
totaladds, totalremoves = 0, 0
Brodie Rao
diffstat: with --git, mark binary files with Bin...
r9642 hasbinary = False
FUJIWARA Katsunori
i18n: use encoding.colwidth() for correct column width...
r11611
sized = [(filename, adds, removes, isbinary, encoding.colwidth(filename))
for filename, adds, removes, isbinary in stats]
for filename, adds, removes, isbinary, namewidth in sized:
Alexander Solovyov
python implementation of diffstat...
r7547 totaladds += adds
totalremoves += removes
FUJIWARA Katsunori
i18n: use encoding.colwidth() for correct column width...
r11611 maxname = max(maxname, namewidth)
Matt Mackall
many, many trivial check-code fixups
r10282 maxtotal = max(maxtotal, adds + removes)
Brodie Rao
diffstat: with --git, mark binary files with Bin...
r9642 if isbinary:
hasbinary = True
Alexander Solovyov
python implementation of diffstat...
r7547
countwidth = len(str(maxtotal))
Brodie Rao
diffstat: with --git, mark binary files with Bin...
r9642 if hasbinary and countwidth < 3:
countwidth = 3
Brodie Rao
diffstat: scale adds/removes proportionally to graph width...
r9330 graphwidth = width - countwidth - maxname - 6
Alexander Solovyov
python implementation of diffstat...
r7547 if graphwidth < 10:
graphwidth = 10
Brodie Rao
diffstat: scale adds/removes proportionally to graph width...
r9330 def scale(i):
if maxtotal <= graphwidth:
return i
# 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.
return max(i * graphwidth // maxtotal, int(bool(i)))
Alexander Solovyov
python implementation of diffstat...
r7547
FUJIWARA Katsunori
i18n: use encoding.colwidth() for correct column width...
r11611 for filename, adds, removes, isbinary, namewidth in sized:
Brodie Rao
diffstat: with --git, mark binary files with Bin...
r9642 if git and isbinary:
count = 'Bin'
else:
count = adds + removes
Brodie Rao
diffstat: scale adds/removes proportionally to graph width...
r9330 pluses = '+' * scale(adds)
minuses = '-' * scale(removes)
FUJIWARA Katsunori
i18n: use encoding.colwidth() for correct column width...
r11611 output.append(' %s%s | %*s %s%s\n' %
(filename, ' ' * (maxname - namewidth),
countwidth, count,
pluses, minuses))
Alexander Solovyov
python implementation of diffstat...
r7547
if stats:
Martin Geisler
patch: marked string for translation
r9331 output.append(_(' %d files changed, %d insertions(+), %d deletions(-)\n')
Matt Mackall
diffstat: use width 80 by default and avoid division by zero
r7860 % (len(stats), totaladds, totalremoves))
Alexander Solovyov
python implementation of diffstat...
r7547
return ''.join(output)
Brodie Rao
diff: make use of output labeling
r10818
def diffstatui(*args, **kw):
'''like diffstat(), but yields 2-tuples of (output, label) for
ui.write()
'''
for line in diffstat(*args, **kw).splitlines():
if line and line[-1] in '+-':
name, graph = line.rsplit(' ', 1)
yield (name + ' ', '')
m = re.search(r'\++', graph)
if m:
yield (m.group(0), 'diffstat.inserted')
m = re.search(r'-+', graph)
if m:
yield (m.group(0), 'diffstat.deleted')
else:
yield (line, '')
yield ('\n', '')