##// END OF EJS Templates
test-hgweb: fix css update
test-hgweb: fix css update

File last commit:

r5669:a0eb8a41 default
r5681:40db4f03 default
Show More
patch.py
1384 lines | 45.0 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 #
Brendan Cully
Move patch-related code into its own module.
r2861 # This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.
Matt Mackall
Simplify i18n imports
r3891 from i18n import _
Vadim Gelfer
refactor text diff/patch code....
r2874 from node import *
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 import base85, cmdutil, mdiff, util, context, revlog, diffhelpers
Thomas Arendsen Hein
Only set mode of new patch if the target file was removed before....
r5477 import cStringIO, email.Parser, os, popen2, re, sha, errno
Matt Mackall
Replace demandload with new demandimport
r3877 import sys, tempfile, zlib
Vadim Gelfer
commands.import: refactor patch parsing into patch.extract.
r2866
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
def copyfile(src, dst, basedir=None):
if not basedir:
basedir = os.getcwd()
abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
if os.path.exists(absdst):
raise util.Abort(_("cannot create %s: destination already exists") %
dst)
targetdir = os.path.dirname(absdst)
if not os.path.isdir(targetdir):
os.makedirs(targetdir)
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)
diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
'retrieving revision [0-9]+(\.[0-9]+)*$|' +
'(---|\*\*\*)[ \t])', re.MULTILINE)
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
Bryan O'Sullivan
patch: make internal code a bit friendlier to use
r5035 def readgitpatch(fp, firstline=None):
Brendan Cully
Move patch-related code into its own module.
r2861 """extract git-style metadata about patches from <patchname>"""
class gitpatch:
"op is one of ADD, DELETE, RENAME, MODIFY or COPY"
def __init__(self, path):
self.path = path
self.oldpath = None
self.mode = None
self.op = 'MODIFY'
self.lineno = 0
Brendan Cully
Add git-1.4 binary patch support
r3367 self.binary = False
Thomas Arendsen Hein
Whitespace/Tab cleanup
r3223
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 def reader(fp, firstline):
Bryan O'Sullivan
patch: make internal code a bit friendlier to use
r5035 if firstline is not None:
yield firstline
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 for line in fp:
yield line
Brendan Cully
Move patch-related code into its own module.
r2861 # Filter patch for git information
gitre = re.compile('diff --git a/(.*) b/(.*)')
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
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 for line in reader(fp, firstline):
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)
Brendan Cully
Move patch-related code into its own module.
r2861 gp = gitpatch(dst)
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'
elif line.startswith('new file mode '):
gp.op = 'ADD'
Brendan Cully
patch: add git symlink support
r5116 gp.mode = int(line.rstrip()[-6:], 8)
Brendan Cully
Move patch-related code into its own module.
r2861 elif line.startswith('new mode '):
Brendan Cully
patch: add git symlink support
r5116 gp.mode = 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 def patch(patchname, ui, strip=1, cwd=None, files={}):
Bryan O'Sullivan
patch.py: re-add the ability to use an external patch program...
r4900 """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:
patcher = util.find_exe('gpatch') or util.find_exe('patch')
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'))
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))
Patrick Mezard
Fix Windows os.popen bug with interleaved stdout/stderr output...
r5481 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
Bryan O'Sullivan
patch.py: re-add the ability to use an external patch program...
r4900 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
files.setdefault(pf, (None, None))
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
Bryan O'Sullivan
patch: make internal code a bit friendlier to use
r5035 def internalpatch(patchobj, ui, strip, cwd, files={}):
"""use builtin patch to apply <patchobj> to the working directory.
Bryan O'Sullivan
patch.py: re-add the ability to use an external patch program...
r4900 returns whether patch was applied with fuzz factor."""
Bryan O'Sullivan
patch: make internal code a bit friendlier to use
r5035 try:
fp = file(patchobj, 'rb')
except TypeError:
fp = patchobj
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 if cwd:
curdir = os.getcwd()
os.chdir(cwd)
try:
ret = applydiff(ui, fp, files, strip=strip)
Bryan O'Sullivan
patch.py: re-add the ability to use an external patch program...
r4900 finally:
if cwd:
os.chdir(curdir)
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 if ret < 0:
Bryan O'Sullivan
patch.py: re-add the ability to use an external patch program...
r4900 raise PatchError
return ret > 0
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: write rej files for missing targets (issue 853)
r5652 def __init__(self, ui, fname, missing=False):
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 self.fname = fname
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:
fp = file(fname, 'rb')
self.lines = fp.readlines()
self.exists = True
except IOError:
pass
else:
self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
if not self.exists:
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 dirname = os.path.dirname(fname)
if dirname and not os.path.isdir(dirname):
Patrick Mezard
patch: simplify directory creation
r5653 os.makedirs(dirname)
Thomas Arendsen Hein
Remove trailing spaces, fix indentation
r5143
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
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
if self.hunks != 1:
hunkstr = "s"
else:
hunkstr = ""
fname = self.fname + ".rej"
self.ui.warn(
Bryan O'Sullivan
patch.py: fix some incorrect uses of _() for i18n
r4898 _("%d out of %d hunk%s FAILED -- saving rejects to file %s\n") %
(len(self.rej), self.hunks, hunkstr, fname))
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 try: os.unlink(fname)
except:
pass
Patrick Mezard
patch: patches should be read and written in binary mode when possible.
r4923 fp = file(fname, 'wb')
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 base = os.path.basename(self.fname)
fp.write("--- %s\n+++ %s\n" % (base, base))
for x in self.rej:
for l in x.hunk:
fp.write(l)
if l[-1] != '\n':
fp.write("\n\ No newline at end of file\n")
def write(self, dest=None):
if self.dirty:
if not dest:
dest = self.fname
st = None
try:
st = os.lstat(dest)
Thomas Arendsen Hein
Only set mode of new patch if the target file was removed before....
r5477 except OSError, inst:
if inst.errno != errno.ENOENT:
raise
if st and st.st_nlink > 1:
os.unlink(dest)
Patrick Mezard
patch: patches should be read and written in binary mode when possible.
r4923 fp = file(dest, 'wb')
Thomas Arendsen Hein
Only set mode of new patch if the target file was removed before....
r5477 if st and st.st_nlink > 1:
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 os.chmod(dest, st.st_mode)
fp.writelines(self.lines)
fp.close()
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
if isinstance(h, binhunk):
if h.rmfile():
os.unlink(self.fname)
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():
os.unlink(self.fname)
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:
linestr = "line"
else:
linestr = "lines"
Bryan O'Sullivan
patch.py: fix some incorrect uses of _() for i18n
r4898 f(_("Hunk #%d succeeded at %d %s(offset %d %s).\n") %
(h.number, l+1, fuzzstr, offset, linestr))
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:
def __init__(self, desc, num, lr, context):
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)
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)
diffhelpers.addlines(lr.fp, self.hunk, self.lena, self.lenb, self.a, self.b)
# if we hit eof before finishing out the hunk, the last line will
# be zero length. Lets try to fix it up.
while len(self.hunk[-1]) == 0:
del self.hunk[-1]
del self.a[-1]
del self.b[-1]
self.lena -= 1
self.lenb -= 1
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):
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):
return self.starta == 0 and self.lena == 0
def rmfile(self):
return self.startb == 0 and self.lenb == 0
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)
class binhunk:
'A binary patch file. Only understands literals so far.'
def __init__(self, gitpatch):
self.gitpatch = gitpatch
self.text = None
self.hunk = ['GIT binary patch\n']
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]
def extract(self, fp):
line = fp.readline()
self.hunk.append(line)
Brendan Cully
Add git-1.4 binary patch support
r3367 while line and not line.startswith('literal '):
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 line = fp.readline()
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 = []
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 line = fp.readline()
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])
line = fp.readline()
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
s = str[4:]
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:
return path.rstrip()
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
return 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"
afile = pathstrip(afile_orig, strip)
Patrick Mezard
patch: avoid file existence tests when possible in selectfile()
r5651 gooda = not nulla and os.path.exists(afile)
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 bfile = pathstrip(bfile_orig, strip)
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()
fname = None
if not missing:
if gooda and goodb:
fname = (afile in bfile) and afile or bfile
elif gooda:
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 fname = afile
Patrick Mezard
patch: write rej files for missing targets (issue 853)
r5652
if not fname:
if not nullb:
fname = (afile in bfile) and afile or bfile
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"))
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):
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:
l = self.buf[0]
del self.buf[0]
return l
return self.fp.readline()
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.
"""
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897
Patrick Mezard
patch: move diff parsing in iterhunks generator
r5650 def scangitpatch(fp, firstline):
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 '''git patches can modify a file, then copy that file to
a new file, but expect the source to be the unmodified form.
So we scan the patch looking for that case so we can do
the copies ahead of time.'''
Brendan Cully
Move patch-related code into its own module.
r2861
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 pos = 0
try:
pos = fp.tell()
except IOError:
fp = cStringIO.StringIO(fp.read())
(dopatch, gitpatches) = readgitpatch(fp, firstline)
fp.seek(pos)
Brendan Cully
Move patch-related code into its own module.
r2861
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 return fp, dopatch, gitpatches
Patrick Mezard
patch: move diff parsing in iterhunks generator
r5650 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
gitre = re.compile('diff --git (a/.*) (b/.*)')
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
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
current_hunk = hunk(x, hunknum + 1, lr, context)
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'):
current_hunk = binhunk(changed[bfile[2:]][1])
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
yield 'file', (afile, bfile, current_hunk)
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 current_hunk.extract(fp)
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
fp, dopatch, gitpatches = scangitpatch(fp, 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:
changed[gp.path] = (gp.op, gp)
# else error?
# copy/rename + modify should modify target, not source
if changed.get(bfile[2:], (None, None))[0] in ('COPY',
'RENAME'):
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
def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False,
rejmerge=None, updatedir=None):
"""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
def closefile():
if not current_file:
return 0
current_file.close()
if rejmerge:
rejmerge(current_file)
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:
changed.setdefault(current_file.fname, (None, None))
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:
current_file = patchfile(ui, sourcefile)
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: write rej files for missing targets (issue 853)
r5652 current_file = patchfile(ui, current_file, 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
for gp in gitpatches:
if gp.op in ('COPY', 'RENAME'):
copyfile(gp.oldpath, gp.path)
changed[gp.path] = (gp.op, 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()
if updatedir and gitpatches:
Bryan O'Sullivan
Add Chris Mason's mpatch library....
r4897 updatedir(gitpatches)
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):
def get(key, name=None):
return (opts.get(key) or
ui.configbool('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'),
ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'))
Matt Mackall
Move ui.diffopts to patch.diffopts where it belongs
r2888
Matt Mackall
Make repo locks recursive, eliminate all passing of lock/wlock
r4917 def updatedir(ui, repo, patches):
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:
ctype, gp = patches[f]
if ctype == '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
Brendan Cully
Move import's working dir update code into patch.updatedir
r2933 elif ctype == 'COPY':
Alexis S. L. Carvalho
Always copy the necessary files before applying a git patch...
r5403 copies.append((gp.oldpath, gp.path))
Brendan Cully
Move import's working dir update code into patch.updatedir
r2933 elif ctype == '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
Move import's working dir update code into patch.updatedir
r2933 if removes:
Alexis S. L. Carvalho
handle git patches that rename a file to more than one destination
r3701 removes.sort()
Matt Mackall
Make repo locks recursive, eliminate all passing of lock/wlock
r4917 repo.remove(removes, True)
Brendan Cully
Move import's working dir update code into patch.updatedir
r2933 for f in patches:
ctype, gp = patches[f]
if gp and gp.mode:
x = gp.mode & 0100 != 0
Brendan Cully
patch: add git symlink support
r5116 l = gp.mode & 020000 != 0
Brendan Cully
Move import's working dir update code into patch.updatedir
r2933 dst = os.path.join(repo.root, gp.path)
Brendan Cully
git patch: create empty added files
r3588 # patch won't create empty files
if ctype == 'ADD' and not os.path.exists(dst):
Matt Mackall
symlinks: add flags param to wwrite...
r4006 repo.wwrite(gp.path, '', x and 'x' or '')
else:
Brendan Cully
patch: add git symlink support
r5116 util.set_link(dst, l)
if not l:
util.set_exec(dst, x)
Matt Mackall
Make repo locks recursive, eliminate all passing of lock/wlock
r4917 cmdutil.addremove(repo, cfiles)
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])
files.sort()
return files
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)
s = sha.new('blob %d\0' % l)
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
Vadim Gelfer
refactor text diff/patch code....
r2874 def diff(repo, node1=None, node2=None, files=None, match=util.always,
fp=None, changes=None, opts=None):
'''print diff of changes to files between two nodes, or node and
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 fp is None:
fp = repo.ui
if not node1:
node1 = repo.dirstate.parents()[0]
Brendan Cully
Teach mq about git patches
r2934
Benoit Boissinot
patch: use contexts for diff
r3967 ccache = {}
def getctx(r):
if r not in ccache:
ccache[r] = context.changectx(repo, r)
return ccache[r]
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
Vadim Gelfer
refactor text diff/patch code....
r2874 # reading the data for node1 early allows it to play nicely
Vadim Gelfer
remove localrepository.changes....
r2875 # with repo.status and the revlog cache.
Benoit Boissinot
patch: use contexts for diff
r3967 ctx1 = context.changectx(repo, node1)
# force manifest reading
man1 = ctx1.manifest()
date1 = util.datestr(ctx1.date())
Vadim Gelfer
refactor text diff/patch code....
r2874
if not changes:
Vadim Gelfer
remove localrepository.changes....
r2875 changes = repo.status(node1, node2, files, match=match)[:5]
Vadim Gelfer
refactor text diff/patch code....
r2874 modified, added, removed, deleted, unknown = changes
if not modified and not added and not removed:
return
Benoit Boissinot
patch: use contexts for diff
r3967 if node2:
ctx2 = context.changectx(repo, node2)
Alexis S. L. Carvalho
patch.diff: avoid calling workingctx().manifest()...
r4496 execf2 = ctx2.manifest().execf
Brendan Cully
patch: add git symlink support
r5116 linkf2 = ctx2.manifest().linkf
Benoit Boissinot
patch: use contexts for diff
r3967 else:
ctx2 = context.workingctx(repo)
Alexis S. L. Carvalho
patch.diff: avoid calling workingctx().manifest()...
r4496 execf2 = util.execfunc(repo.root, None)
Brendan Cully
patch: add git symlink support
r5116 linkf2 = util.linkfunc(repo.root, None)
Alexis S. L. Carvalho
patch.diff: avoid calling workingctx().manifest()...
r4496 if execf2 is None:
Brendan Cully
patch: add git symlink support
r5116 mc = ctx2.parents()[0].manifest().copy()
execf2 = mc.execf
linkf2 = mc.linkf
Benoit Boissinot
patch: use contexts for diff
r3967
# returns False if there was no rename between ctx1 and ctx2
# returns None if the file was created between ctx1 and ctx2
# returns the (file, node) present in ctx1 that was renamed to f in ctx2
Alexis S. L. Carvalho
Make hg diff --git -r revA:revB detect (inverted) copies if revA > revB
r5264 # This will only really work if c1 is the Nth 1st parent of c2.
def renamed(c1, c2, man, f):
startrev = c1.rev()
c = c2
Benoit Boissinot
patch: use contexts for diff
r3967 crev = c.rev()
if crev is None:
crev = repo.changelog.count()
Alexis S. L. Carvalho
renamedbetween: only return (file, node) pairs that exist in the original rev
r3694 orig = f
Alexis S. L. Carvalho
Avoid a working dir walk while trying to detect copies for diff --git
r5265 files = (f,)
Benoit Boissinot
patch: use contexts for diff
r3967 while crev > startrev:
Alexis S. L. Carvalho
Avoid a working dir walk while trying to detect copies for diff --git
r5265 if f in files:
Alexis S. L. Carvalho
diff: improve detection of renames when diffing across many revisions
r3693 try:
Benoit Boissinot
patch: use contexts for diff
r3967 src = getfilectx(f, c).renamed()
except revlog.LookupError:
Alexis S. L. Carvalho
diff: improve detection of renames when diffing across many revisions
r3693 return None
if src:
f = src[0]
Benoit Boissinot
patch: use contexts for diff
r3967 crev = c.parents()[0].rev()
# try to reuse
c = getctx(crev)
Alexis S. L. Carvalho
Avoid a working dir walk while trying to detect copies for diff --git
r5265 files = c.files()
Alexis S. L. Carvalho
Make hg diff --git -r revA:revB detect (inverted) copies if revA > revB
r5264 if f not in man:
Alexis S. L. Carvalho
renamedbetween: only return (file, node) pairs that exist in the original rev
r3694 return None
Alexis S. L. Carvalho
diff: better detection of renames when comparing with the working dir.
r3696 if f == orig:
return False
Benoit Boissinot
patch: use contexts for diff
r3967 return f
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:
copied = {}
Alexis S. L. Carvalho
Make hg diff --git -r revA:revB detect (inverted) copies if revA > revB
r5264 c1, c2 = ctx1, ctx2
files = added
man = man1
if node2 and ctx1.rev() >= ctx2.rev():
# renamed() starts at c2 and walks back in history until c1.
# Since ctx1.rev() >= ctx2.rev(), invert ctx2 and ctx1 to
# detect (inverted) copies.
c1, c2 = ctx2, ctx1
files = removed
man = ctx2.manifest()
for f in files:
src = renamed(c1, c2, man, f)
Brendan Cully
Add diff --git option
r2907 if src:
copied[f] = src
Alexis S. L. Carvalho
Make hg diff --git -r revA:revB detect (inverted) copies if revA > revB
r5264 if ctx1 == c2:
# invert the copied dict
copied = dict([(v, k) for (k, v) in copied.iteritems()])
# If we've renamed file foo to bar (copied['bar'] = 'foo'),
# avoid showing a diff for foo if we're going to show
# the rename to bar.
srcs = [x[1] for x in copied.iteritems() if x[0] in added]
Brendan Cully
Add diff --git option
r2907
Vadim Gelfer
refactor text diff/patch code....
r2874 all = modified + added + removed
all.sort()
Alexis S. L. Carvalho
Don't generate git patches that rename a file to multiple destinations...
r3702 gone = {}
Matt Mackall
exec: add execfunc to simplify exec flag support on non-exec filesystems
r3996
Vadim Gelfer
refactor text diff/patch code....
r2874 for f in all:
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:
Brendan Cully
patch: add git symlink support
r5116 def gitmode(x, l):
return l and '120000' or (x and '100755' or '100644')
Brendan Cully
Add diff --git option
r2907 def addmodehdr(header, omode, nmode):
if omode != nmode:
header.append('old mode %s\n' % omode)
header.append('new mode %s\n' % nmode)
if f in added:
Brendan Cully
patch: add git symlink support
r5116 mode = gitmode(execf2(f), linkf2(f))
Brendan Cully
Add diff --git option
r2907 if f in copied:
Benoit Boissinot
patch: use contexts for diff
r3967 a = copied[f]
Brendan Cully
patch: add git symlink support
r5116 omode = gitmode(man1.execf(a), man1.linkf(a))
Brendan Cully
Add diff --git option
r2907 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:
if f in srcs:
dodiff = False
else:
Brendan Cully
patch: add git symlink support
r5116 mode = gitmode(man1.execf(f), man1.linkf(f))
Brendan Cully
Add diff --git option
r2907 header.append('deleted file mode %s\n' % mode)
else:
Brendan Cully
patch: add git symlink support
r5116 omode = gitmode(man1.execf(f), man1.linkf(f))
nmode = gitmode(execf2(f), linkf2(f))
Brendan Cully
Add diff --git option
r2907 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
Brendan Cully
Don't generate git diff header for empty diffs
r3329 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
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)
Brendan Cully
Don't generate git diff header for empty diffs
r3329 if text or len(header) > 1:
Brendan Cully
Add diff --git option
r2907 fp.write(''.join(header))
Brendan Cully
Don't generate git diff header for empty diffs
r3329 fp.write(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):
ctx = repo.changectx(rev)
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,
seqno=seqno, revwidth=revwidth)
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")
diff(repo, prev, node, fp=fp, opts=opts)
if fp not in (sys.stdout, repo.ui):
fp.close()
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
def diffstat(patchlines):
Bryan O'Sullivan
Introduce find_exe. Use instead of find_in_path for programs....
r4488 if not util.find_exe('diffstat'):
Alexis S. L. Carvalho
Try to find diffstat in PATH before calling it...
r4316 return
Matt Doar
Add support for diffstat in commit emails, and move diffstat from...
r3096 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
try:
p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
try:
for line in patchlines: print >> p.tochild, line
p.tochild.close()
if p.wait(): return
fp = os.fdopen(fd, 'r')
stat = []
for line in fp: stat.append(line.lstrip())
last = stat.pop()
stat.insert(0, last)
stat = ''.join(stat)
if stat.startswith('0 files'): raise ValueError
return stat
except: raise
finally:
try: os.unlink(name)
except: pass