~ubuntu-branches/ubuntu/precise/mercurial/precise-updates

« back to all changes in this revision

Viewing changes to mercurial/patch.py

  • Committer: Bazaar Package Importer
  • Author(s): Vincent Danjean, Javi Merino, Vincent Danjean
  • Date: 2010-07-04 09:55:28 UTC
  • mfrom: (1.2.12 upstream)
  • Revision ID: james.westby@ubuntu.com-20100704095528-bzag1mhfylss9zth
Tags: 1.6-1
[ Javi Merino ]
* New upstream release (1.6). Many bug fixes and improvements. Among
    them:
  - push: break infinite http recursion bug with Python 2.6.5
       (issue2179 and issue2255) (Closes: #586907)
  - zeroconf: Don't use string exceptions (Closes: #585250)
* Removed patch for_upstream__bashism_in_examples.patch since a fix for
    #581122 is included upstream.
* Updated Standards-Version to 3.9 (no change needed)

[ Vincent Danjean ]
* debian/control:
  + Use Breaks instead of Conflicts
  + Use a fixed version in Replaces
    I put 1.4 but it has been a long time since nothing has been moved
    from mercurial to mercurial-common

Show diffs side-by-side

added added

removed removed

Lines of Context:
6
6
# This software may be used and distributed according to the terms of the
7
7
# GNU General Public License version 2 or any later version.
8
8
 
 
9
import cStringIO, email.Parser, os, re
 
10
import tempfile, zlib
 
11
 
9
12
from i18n import _
10
13
from node import hex, nullid, short
11
14
import base85, cmdutil, mdiff, util, diffhelpers, copies
12
 
import cStringIO, email.Parser, os, re
13
 
import sys, tempfile, zlib
14
15
 
15
16
gitre = re.compile('diff --git a/(.*) b/(.*)')
16
17
 
302
303
        isexec = mode & 0100
303
304
        self.mode = (islink, isexec)
304
305
 
 
306
    def __repr__(self):
 
307
        return "<patchmeta %s %r>" % (self.op, self.path)
 
308
 
305
309
def readgitpatch(lr):
306
310
    """extract git-style metadata about patches from <patchname>"""
307
311
 
343
347
                gp.path = line[8:]
344
348
            elif line.startswith('deleted file'):
345
349
                gp.op = 'DELETE'
346
 
                # is the deleted file a symlink?
347
 
                gp.setmode(int(line[-6:], 8))
348
350
            elif line.startswith('new file mode '):
349
351
                gp.op = 'ADD'
350
352
                gp.setmode(int(line[-6:], 8))
525
527
 
526
528
        self.writelines(fname, rejlines())
527
529
 
528
 
    def write(self, dest=None):
529
 
        if not self.dirty:
530
 
            return
531
 
        if not dest:
532
 
            dest = self.fname
533
 
        self.writelines(dest, self.lines)
534
 
 
535
 
    def close(self):
536
 
        self.write()
537
 
        self.write_rej()
538
 
 
539
530
    def apply(self, h):
540
531
        if not h.complete():
541
532
            raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
905
896
            return s
906
897
    return s[:i]
907
898
 
 
899
def pathstrip(path, strip):
 
900
    pathlen = len(path)
 
901
    i = 0
 
902
    if strip == 0:
 
903
        return '', path.rstrip()
 
904
    count = strip
 
905
    while count > 0:
 
906
        i = path.find('/', i)
 
907
        if i == -1:
 
908
            raise PatchError(_("unable to strip away %d of %d dirs from %s") %
 
909
                             (count, strip, path))
 
910
        i += 1
 
911
        # consume '//' in the path
 
912
        while i < pathlen - 1 and path[i] == '/':
 
913
            i += 1
 
914
        count -= 1
 
915
    return path[:i].lstrip(), path[i:].rstrip()
 
916
 
908
917
def selectfile(afile_orig, bfile_orig, hunk, strip):
909
 
    def pathstrip(path, count=1):
910
 
        pathlen = len(path)
911
 
        i = 0
912
 
        if count == 0:
913
 
            return '', path.rstrip()
914
 
        while count > 0:
915
 
            i = path.find('/', i)
916
 
            if i == -1:
917
 
                raise PatchError(_("unable to strip away %d dirs from %s") %
918
 
                                 (count, path))
919
 
            i += 1
920
 
            # consume '//' in the path
921
 
            while i < pathlen - 1 and path[i] == '/':
922
 
                i += 1
923
 
            count -= 1
924
 
        return path[:i].lstrip(), path[i:].rstrip()
925
 
 
926
918
    nulla = afile_orig == "/dev/null"
927
919
    nullb = bfile_orig == "/dev/null"
928
920
    abase, afile = pathstrip(afile_orig, strip)
1125
1117
    if (empty is None and not gitworkdone) or empty:
1126
1118
        raise NoHunks
1127
1119
 
 
1120
 
1128
1121
def applydiff(ui, fp, changed, strip=1, sourcefile=None, eolmode='strict'):
1129
 
    """
1130
 
    Reads a patch from fp and tries to apply it.
 
1122
    """Reads a patch from fp and tries to apply it.
1131
1123
 
1132
1124
    The dict 'changed' is filled in with all of the filenames changed
1133
1125
    by the patch. Returns 0 for a clean patch, -1 if any rejects were
1136
1128
    If 'eolmode' is 'strict', the patch content and patched file are
1137
1129
    read in binary mode. Otherwise, line endings are ignored when
1138
1130
    patching then normalized according to 'eolmode'.
 
1131
 
 
1132
    Callers probably want to call 'updatedir' after this to apply
 
1133
    certain categories of changes not done by this function.
1139
1134
    """
 
1135
    return _applydiff(
 
1136
        ui, fp, patchfile, copyfile,
 
1137
        changed, strip=strip, sourcefile=sourcefile, eolmode=eolmode)
 
1138
 
 
1139
 
 
1140
def _applydiff(ui, fp, patcher, copyfn, changed, strip=1,
 
1141
               sourcefile=None, eolmode='strict'):
1140
1142
    rejects = 0
1141
1143
    err = 0
1142
1144
    current_file = None
1143
 
    gitpatches = None
1144
 
    opener = util.opener(os.getcwd())
 
1145
    cwd = os.getcwd()
 
1146
    opener = util.opener(cwd)
1145
1147
 
1146
1148
    def closefile():
1147
1149
        if not current_file:
1148
1150
            return 0
1149
 
        current_file.close()
 
1151
        if current_file.dirty:
 
1152
            current_file.writelines(current_file.fname, current_file.lines)
 
1153
        current_file.write_rej()
1150
1154
        return len(current_file.rej)
1151
1155
 
1152
1156
    for state, values in iterhunks(ui, fp, sourcefile):
1153
1157
        if state == 'hunk':
1154
1158
            if not current_file:
1155
1159
                continue
1156
 
            current_hunk = values
1157
 
            ret = current_file.apply(current_hunk)
 
1160
            ret = current_file.apply(values)
1158
1161
            if ret >= 0:
1159
1162
                changed.setdefault(current_file.fname, None)
1160
1163
                if ret > 0:
1164
1167
            afile, bfile, first_hunk = values
1165
1168
            try:
1166
1169
                if sourcefile:
1167
 
                    current_file = patchfile(ui, sourcefile, opener,
1168
 
                                             eolmode=eolmode)
 
1170
                    current_file = patcher(ui, sourcefile, opener,
 
1171
                                           eolmode=eolmode)
1169
1172
                else:
1170
1173
                    current_file, missing = selectfile(afile, bfile,
1171
1174
                                                       first_hunk, strip)
1172
 
                    current_file = patchfile(ui, current_file, opener,
1173
 
                                             missing, eolmode)
 
1175
                    current_file = patcher(ui, current_file, opener,
 
1176
                                           missing=missing, eolmode=eolmode)
1174
1177
            except PatchError, err:
1175
1178
                ui.warn(str(err) + '\n')
1176
 
                current_file, current_hunk = None, None
 
1179
                current_file = None
1177
1180
                rejects += 1
1178
1181
                continue
1179
1182
        elif state == 'git':
1180
 
            gitpatches = values
1181
 
            cwd = os.getcwd()
1182
 
            for gp in gitpatches:
 
1183
            for gp in values:
 
1184
                gp.path = pathstrip(gp.path, strip - 1)[1]
 
1185
                if gp.oldpath:
 
1186
                    gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
1183
1187
                if gp.op in ('COPY', 'RENAME'):
1184
 
                    copyfile(gp.oldpath, gp.path, cwd)
 
1188
                    copyfn(gp.oldpath, gp.path, cwd)
1185
1189
                changed[gp.path] = gp
1186
1190
        else:
1187
1191
            raise util.Abort(_('unsupported parser state: %s') % state)
1192
1196
        return -1
1193
1197
    return err
1194
1198
 
1195
 
def diffopts(ui, opts=None, untrusted=False):
1196
 
    def get(key, name=None, getter=ui.configbool):
1197
 
        return ((opts and opts.get(key)) or
1198
 
                getter('diff', name or key, None, untrusted=untrusted))
1199
 
    return mdiff.diffopts(
1200
 
        text=opts and opts.get('text'),
1201
 
        git=get('git'),
1202
 
        nodates=get('nodates'),
1203
 
        showfunc=get('show_function', 'showfunc'),
1204
 
        ignorews=get('ignore_all_space', 'ignorews'),
1205
 
        ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1206
 
        ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1207
 
        context=get('unified', getter=ui.config))
1208
 
 
1209
1199
def updatedir(ui, repo, patches, similarity=0):
1210
1200
    '''Update dirstate after patch application according to metadata'''
1211
1201
    if not patches:
1227
1217
            copies.append((gp.oldpath, gp.path))
1228
1218
        elif gp.op == 'DELETE':
1229
1219
            removes.add(gp.path)
 
1220
 
 
1221
    wctx = repo[None]
1230
1222
    for src, dst in copies:
1231
 
        repo.copy(src, dst)
 
1223
        wctx.copy(src, dst)
1232
1224
    if (not similarity) and removes:
1233
 
        repo.remove(sorted(removes), True)
 
1225
        wctx.remove(sorted(removes), True)
 
1226
 
1234
1227
    for f in patches:
1235
1228
        gp = patches[f]
1236
1229
        if gp and gp.mode:
1240
1233
            if gp.op == 'ADD' and not os.path.exists(dst):
1241
1234
                flags = (isexec and 'x' or '') + (islink and 'l' or '')
1242
1235
                repo.wwrite(gp.path, '', flags)
1243
 
            elif gp.op != 'DELETE':
1244
 
                util.set_flags(dst, islink, isexec)
 
1236
            util.set_flags(dst, islink, isexec)
1245
1237
    cmdutil.addremove(repo, cfiles, similarity=similarity)
1246
1238
    files = patches.keys()
1247
1239
    files.extend([r for r in removes if r not in files])
1337
1329
            try:
1338
1330
                return internalpatch(patchname, ui, strip, cwd, files, eolmode)
1339
1331
            except NoHunks:
 
1332
                ui.warn(_('internal patcher failed\n'
 
1333
                          'please report details to '
 
1334
                          'http://mercurial.selenic.com/bts/\n'
 
1335
                          'or mercurial@selenic.com\n'))
1340
1336
                patcher = (util.find_exe('gpatch') or util.find_exe('patch')
1341
1337
                           or 'patch')
1342
1338
                ui.debug('no valid hunks found; trying with %r instead\n' %
1393
1389
class GitDiffRequired(Exception):
1394
1390
    pass
1395
1391
 
 
1392
def diffopts(ui, opts=None, untrusted=False):
 
1393
    def get(key, name=None, getter=ui.configbool):
 
1394
        return ((opts and opts.get(key)) or
 
1395
                getter('diff', name or key, None, untrusted=untrusted))
 
1396
    return mdiff.diffopts(
 
1397
        text=opts and opts.get('text'),
 
1398
        git=get('git'),
 
1399
        nodates=get('nodates'),
 
1400
        showfunc=get('show_function', 'showfunc'),
 
1401
        ignorews=get('ignore_all_space', 'ignorews'),
 
1402
        ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
 
1403
        ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
 
1404
        context=get('unified', getter=ui.config))
 
1405
 
1396
1406
def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1397
1407
         losedatafn=None):
1398
1408
    '''yields diff of changes to files between two nodes, or node and
1465
1475
    else:
1466
1476
        return difffn(opts, None)
1467
1477
 
 
1478
def difflabel(func, *args, **kw):
 
1479
    '''yields 2-tuples of (output, label) based on the output of func()'''
 
1480
    prefixes = [('diff', 'diff.diffline'),
 
1481
                ('copy', 'diff.extended'),
 
1482
                ('rename', 'diff.extended'),
 
1483
                ('old', 'diff.extended'),
 
1484
                ('new', 'diff.extended'),
 
1485
                ('deleted', 'diff.extended'),
 
1486
                ('---', 'diff.file_a'),
 
1487
                ('+++', 'diff.file_b'),
 
1488
                ('@@', 'diff.hunk'),
 
1489
                ('-', 'diff.deleted'),
 
1490
                ('+', 'diff.inserted')]
 
1491
 
 
1492
    for chunk in func(*args, **kw):
 
1493
        lines = chunk.split('\n')
 
1494
        for i, line in enumerate(lines):
 
1495
            if i != 0:
 
1496
                yield ('\n', '')
 
1497
            stripline = line
 
1498
            if line and line[0] in '+-':
 
1499
                # highlight trailing whitespace, but only in changed lines
 
1500
                stripline = line.rstrip()
 
1501
            for prefix, label in prefixes:
 
1502
                if stripline.startswith(prefix):
 
1503
                    yield (stripline, label)
 
1504
                    break
 
1505
            else:
 
1506
                yield (line, '')
 
1507
            if line != stripline:
 
1508
                yield (line[len(stripline):], 'diff.trailingwhitespace')
 
1509
 
 
1510
def diffui(*args, **kw):
 
1511
    '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
 
1512
    return difflabel(diff, *args, **kw)
 
1513
 
 
1514
 
1468
1515
def _addmodehdr(header, omode, nmode):
1469
1516
    if omode != nmode:
1470
1517
        header.append('old mode %s\n' % omode)
1568
1615
            if text:
1569
1616
                yield text
1570
1617
 
1571
 
def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1572
 
           opts=None):
1573
 
    '''export changesets as hg patches.'''
1574
 
 
1575
 
    total = len(revs)
1576
 
    revwidth = max([len(str(rev)) for rev in revs])
1577
 
 
1578
 
    def single(rev, seqno, fp):
1579
 
        ctx = repo[rev]
1580
 
        node = ctx.node()
1581
 
        parents = [p.node() for p in ctx.parents() if p]
1582
 
        branch = ctx.branch()
1583
 
        if switch_parent:
1584
 
            parents.reverse()
1585
 
        prev = (parents and parents[0]) or nullid
1586
 
 
1587
 
        if not fp:
1588
 
            fp = cmdutil.make_file(repo, template, node, total=total,
1589
 
                                   seqno=seqno, revwidth=revwidth,
1590
 
                                   mode='ab')
1591
 
        if fp != sys.stdout and hasattr(fp, 'name'):
1592
 
            repo.ui.note("%s\n" % fp.name)
1593
 
 
1594
 
        fp.write("# HG changeset patch\n")
1595
 
        fp.write("# User %s\n" % ctx.user())
1596
 
        fp.write("# Date %d %d\n" % ctx.date())
1597
 
        if branch and (branch != 'default'):
1598
 
            fp.write("# Branch %s\n" % branch)
1599
 
        fp.write("# Node ID %s\n" % hex(node))
1600
 
        fp.write("# Parent  %s\n" % hex(prev))
1601
 
        if len(parents) > 1:
1602
 
            fp.write("# Parent  %s\n" % hex(parents[1]))
1603
 
        fp.write(ctx.description().rstrip())
1604
 
        fp.write("\n\n")
1605
 
 
1606
 
        for chunk in diff(repo, prev, node, opts=opts):
1607
 
            fp.write(chunk)
1608
 
 
1609
 
    for seqno, rev in enumerate(revs):
1610
 
        single(rev, seqno + 1, fp)
1611
 
 
1612
1618
def diffstatdata(lines):
1613
1619
    filename, adds, removes = None, 0, 0
1614
1620
    for line in lines:
1676
1682
                      % (len(stats), totaladds, totalremoves))
1677
1683
 
1678
1684
    return ''.join(output)
 
1685
 
 
1686
def diffstatui(*args, **kw):
 
1687
    '''like diffstat(), but yields 2-tuples of (output, label) for
 
1688
    ui.write()
 
1689
    '''
 
1690
 
 
1691
    for line in diffstat(*args, **kw).splitlines():
 
1692
        if line and line[-1] in '+-':
 
1693
            name, graph = line.rsplit(' ', 1)
 
1694
            yield (name + ' ', '')
 
1695
            m = re.search(r'\++', graph)
 
1696
            if m:
 
1697
                yield (m.group(0), 'diffstat.inserted')
 
1698
            m = re.search(r'-+', graph)
 
1699
            if m:
 
1700
                yield (m.group(0), 'diffstat.deleted')
 
1701
        else:
 
1702
            yield (line, '')
 
1703
        yield ('\n', '')