~ubuntu-branches/ubuntu/trusty/mercurial/trusty-security

« back to all changes in this revision

Viewing changes to .pc/CVE-2014-9390.pt4/mercurial/scmutil.py

  • Committer: Package Import Robot
  • Author(s): Marc Deslauriers, Jamie Strandboge, Marc Deslauriers
  • Date: 2015-06-17 10:51:42 UTC
  • Revision ID: package-import@ubuntu.com-20150617105142-5pe4odmv44b1p509
Tags: 2.8.2-1ubuntu1.3
[ Jamie Strandboge ]
* SECURITY UPDATE: fix for improperly handling case-insensitive paths on
  Windows and OS X clients
  - http://selenic.com/repo/hg-stable/rev/885bd7c5c7e3
  - http://selenic.com/repo/hg-stable/rev/c02a05cc6f5e
  - http://selenic.com/repo/hg-stable/rev/6dad422ecc5a
  - CVE-2014-9390
  - LP: #1404035

[ Marc Deslauriers ]
* SECURITY UPDATE: arbitrary command exection via crafted repository
  name in a clone command
  - d/p/from_upstream__sshpeer_more_thorough_shell_quoting.patch: add
    more thorough shell quoting to mercurial/sshpeer.py.
  - CVE-2014-9462
* debian/patches/fix_ftbfs_patchbomb_test.patch: fix patchbomb test.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# scmutil.py - Mercurial core utility functions
 
2
#
 
3
#  Copyright Matt Mackall <mpm@selenic.com>
 
4
#
 
5
# This software may be used and distributed according to the terms of the
 
6
# GNU General Public License version 2 or any later version.
 
7
 
 
8
import encoding
 
9
from i18n import _
 
10
from mercurial.node import nullrev
 
11
import util, error, osutil, revset, similar, encoding, phases, parsers
 
12
import match as matchmod
 
13
import os, errno, re, stat, glob
 
14
 
 
15
if os.name == 'nt':
 
16
    import scmwindows as scmplatform
 
17
else:
 
18
    import scmposix as scmplatform
 
19
 
 
20
systemrcpath = scmplatform.systemrcpath
 
21
userrcpath = scmplatform.userrcpath
 
22
 
 
23
def _lowerclean(s):
 
24
    return encoding.hfsignoreclean(s.lower())
 
25
 
 
26
def nochangesfound(ui, repo, excluded=None):
 
27
    '''Report no changes for push/pull, excluded is None or a list of
 
28
    nodes excluded from the push/pull.
 
29
    '''
 
30
    secretlist = []
 
31
    if excluded:
 
32
        for n in excluded:
 
33
            if n not in repo:
 
34
                # discovery should not have included the filtered revision,
 
35
                # we have to explicitly exclude it until discovery is cleanup.
 
36
                continue
 
37
            ctx = repo[n]
 
38
            if ctx.phase() >= phases.secret and not ctx.extinct():
 
39
                secretlist.append(n)
 
40
 
 
41
    if secretlist:
 
42
        ui.status(_("no changes found (ignored %d secret changesets)\n")
 
43
                  % len(secretlist))
 
44
    else:
 
45
        ui.status(_("no changes found\n"))
 
46
 
 
47
def checknewlabel(repo, lbl, kind):
 
48
    # Do not use the "kind" parameter in ui output.
 
49
    # It makes strings difficult to translate.
 
50
    if lbl in ['tip', '.', 'null']:
 
51
        raise util.Abort(_("the name '%s' is reserved") % lbl)
 
52
    for c in (':', '\0', '\n', '\r'):
 
53
        if c in lbl:
 
54
            raise util.Abort(_("%r cannot be used in a name") % c)
 
55
    try:
 
56
        int(lbl)
 
57
        raise util.Abort(_("cannot use an integer as a name"))
 
58
    except ValueError:
 
59
        pass
 
60
 
 
61
def checkfilename(f):
 
62
    '''Check that the filename f is an acceptable filename for a tracked file'''
 
63
    if '\r' in f or '\n' in f:
 
64
        raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
 
65
 
 
66
def checkportable(ui, f):
 
67
    '''Check if filename f is portable and warn or abort depending on config'''
 
68
    checkfilename(f)
 
69
    abort, warn = checkportabilityalert(ui)
 
70
    if abort or warn:
 
71
        msg = util.checkwinfilename(f)
 
72
        if msg:
 
73
            msg = "%s: %r" % (msg, f)
 
74
            if abort:
 
75
                raise util.Abort(msg)
 
76
            ui.warn(_("warning: %s\n") % msg)
 
77
 
 
78
def checkportabilityalert(ui):
 
79
    '''check if the user's config requests nothing, a warning, or abort for
 
80
    non-portable filenames'''
 
81
    val = ui.config('ui', 'portablefilenames', 'warn')
 
82
    lval = val.lower()
 
83
    bval = util.parsebool(val)
 
84
    abort = os.name == 'nt' or lval == 'abort'
 
85
    warn = bval or lval == 'warn'
 
86
    if bval is None and not (warn or abort or lval == 'ignore'):
 
87
        raise error.ConfigError(
 
88
            _("ui.portablefilenames value is invalid ('%s')") % val)
 
89
    return abort, warn
 
90
 
 
91
class casecollisionauditor(object):
 
92
    def __init__(self, ui, abort, dirstate):
 
93
        self._ui = ui
 
94
        self._abort = abort
 
95
        allfiles = '\0'.join(dirstate._map)
 
96
        self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
 
97
        self._dirstate = dirstate
 
98
        # The purpose of _newfiles is so that we don't complain about
 
99
        # case collisions if someone were to call this object with the
 
100
        # same filename twice.
 
101
        self._newfiles = set()
 
102
 
 
103
    def __call__(self, f):
 
104
        fl = encoding.lower(f)
 
105
        if (fl in self._loweredfiles and f not in self._dirstate and
 
106
            f not in self._newfiles):
 
107
            msg = _('possible case-folding collision for %s') % f
 
108
            if self._abort:
 
109
                raise util.Abort(msg)
 
110
            self._ui.warn(_("warning: %s\n") % msg)
 
111
        self._loweredfiles.add(fl)
 
112
        self._newfiles.add(f)
 
113
 
 
114
class pathauditor(object):
 
115
    '''ensure that a filesystem path contains no banned components.
 
116
    the following properties of a path are checked:
 
117
 
 
118
    - ends with a directory separator
 
119
    - under top-level .hg
 
120
    - starts at the root of a windows drive
 
121
    - contains ".."
 
122
    - traverses a symlink (e.g. a/symlink_here/b)
 
123
    - inside a nested repository (a callback can be used to approve
 
124
      some nested repositories, e.g., subrepositories)
 
125
    '''
 
126
 
 
127
    def __init__(self, root, callback=None):
 
128
        self.audited = set()
 
129
        self.auditeddir = set()
 
130
        self.root = root
 
131
        self.callback = callback
 
132
        if os.path.lexists(root) and not util.checkcase(root):
 
133
            self.normcase = util.normcase
 
134
        else:
 
135
            self.normcase = lambda x: x
 
136
 
 
137
    def __call__(self, path):
 
138
        '''Check the relative path.
 
139
        path may contain a pattern (e.g. foodir/**.txt)'''
 
140
 
 
141
        path = util.localpath(path)
 
142
        normpath = self.normcase(path)
 
143
        if normpath in self.audited:
 
144
            return
 
145
        # AIX ignores "/" at end of path, others raise EISDIR.
 
146
        if util.endswithsep(path):
 
147
            raise util.Abort(_("path ends in directory separator: %s") % path)
 
148
        parts = util.splitpath(path)
 
149
        if (os.path.splitdrive(path)[0]
 
150
            or _lowerclean(parts[0]) in ('.hg', '.hg.', '')
 
151
            or os.pardir in parts):
 
152
            raise util.Abort(_("path contains illegal component: %s") % path)
 
153
        if '.hg' in _lowerclean(path):
 
154
            lparts = [_lowerclean(p.lower()) for p in parts]
 
155
            for p in '.hg', '.hg.':
 
156
                if p in lparts[1:]:
 
157
                    pos = lparts.index(p)
 
158
                    base = os.path.join(*parts[:pos])
 
159
                    raise util.Abort(_("path '%s' is inside nested repo %r")
 
160
                                     % (path, base))
 
161
 
 
162
        normparts = util.splitpath(normpath)
 
163
        assert len(parts) == len(normparts)
 
164
 
 
165
        parts.pop()
 
166
        normparts.pop()
 
167
        prefixes = []
 
168
        while parts:
 
169
            prefix = os.sep.join(parts)
 
170
            normprefix = os.sep.join(normparts)
 
171
            if normprefix in self.auditeddir:
 
172
                break
 
173
            curpath = os.path.join(self.root, prefix)
 
174
            try:
 
175
                st = os.lstat(curpath)
 
176
            except OSError, err:
 
177
                # EINVAL can be raised as invalid path syntax under win32.
 
178
                # They must be ignored for patterns can be checked too.
 
179
                if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
 
180
                    raise
 
181
            else:
 
182
                if stat.S_ISLNK(st.st_mode):
 
183
                    raise util.Abort(
 
184
                        _('path %r traverses symbolic link %r')
 
185
                        % (path, prefix))
 
186
                elif (stat.S_ISDIR(st.st_mode) and
 
187
                      os.path.isdir(os.path.join(curpath, '.hg'))):
 
188
                    if not self.callback or not self.callback(curpath):
 
189
                        raise util.Abort(_("path '%s' is inside nested "
 
190
                                           "repo %r")
 
191
                                         % (path, prefix))
 
192
            prefixes.append(normprefix)
 
193
            parts.pop()
 
194
            normparts.pop()
 
195
 
 
196
        self.audited.add(normpath)
 
197
        # only add prefixes to the cache after checking everything: we don't
 
198
        # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
 
199
        self.auditeddir.update(prefixes)
 
200
 
 
201
    def check(self, path):
 
202
        try:
 
203
            self(path)
 
204
            return True
 
205
        except (OSError, util.Abort):
 
206
            return False
 
207
 
 
208
class abstractvfs(object):
 
209
    """Abstract base class; cannot be instantiated"""
 
210
 
 
211
    def __init__(self, *args, **kwargs):
 
212
        '''Prevent instantiation; don't call this from subclasses.'''
 
213
        raise NotImplementedError('attempted instantiating ' + str(type(self)))
 
214
 
 
215
    def tryread(self, path):
 
216
        '''gracefully return an empty string for missing files'''
 
217
        try:
 
218
            return self.read(path)
 
219
        except IOError, inst:
 
220
            if inst.errno != errno.ENOENT:
 
221
                raise
 
222
        return ""
 
223
 
 
224
    def open(self, path, mode="r", text=False, atomictemp=False):
 
225
        self.open = self.__call__
 
226
        return self.__call__(path, mode, text, atomictemp)
 
227
 
 
228
    def read(self, path):
 
229
        fp = self(path, 'rb')
 
230
        try:
 
231
            return fp.read()
 
232
        finally:
 
233
            fp.close()
 
234
 
 
235
    def write(self, path, data):
 
236
        fp = self(path, 'wb')
 
237
        try:
 
238
            return fp.write(data)
 
239
        finally:
 
240
            fp.close()
 
241
 
 
242
    def append(self, path, data):
 
243
        fp = self(path, 'ab')
 
244
        try:
 
245
            return fp.write(data)
 
246
        finally:
 
247
            fp.close()
 
248
 
 
249
    def exists(self, path=None):
 
250
        return os.path.exists(self.join(path))
 
251
 
 
252
    def fstat(self, fp):
 
253
        return util.fstat(fp)
 
254
 
 
255
    def isdir(self, path=None):
 
256
        return os.path.isdir(self.join(path))
 
257
 
 
258
    def islink(self, path=None):
 
259
        return os.path.islink(self.join(path))
 
260
 
 
261
    def lstat(self, path=None):
 
262
        return os.lstat(self.join(path))
 
263
 
 
264
    def makedir(self, path=None, notindexed=True):
 
265
        return util.makedir(self.join(path), notindexed)
 
266
 
 
267
    def makedirs(self, path=None, mode=None):
 
268
        return util.makedirs(self.join(path), mode)
 
269
 
 
270
    def mkdir(self, path=None):
 
271
        return os.mkdir(self.join(path))
 
272
 
 
273
    def readdir(self, path=None, stat=None, skip=None):
 
274
        return osutil.listdir(self.join(path), stat, skip)
 
275
 
 
276
    def rename(self, src, dst):
 
277
        return util.rename(self.join(src), self.join(dst))
 
278
 
 
279
    def readlink(self, path):
 
280
        return os.readlink(self.join(path))
 
281
 
 
282
    def setflags(self, path, l, x):
 
283
        return util.setflags(self.join(path), l, x)
 
284
 
 
285
    def stat(self, path=None):
 
286
        return os.stat(self.join(path))
 
287
 
 
288
    def unlink(self, path=None):
 
289
        return util.unlink(self.join(path))
 
290
 
 
291
    def utime(self, path=None, t=None):
 
292
        return os.utime(self.join(path), t)
 
293
 
 
294
class vfs(abstractvfs):
 
295
    '''Operate files relative to a base directory
 
296
 
 
297
    This class is used to hide the details of COW semantics and
 
298
    remote file access from higher level code.
 
299
    '''
 
300
    def __init__(self, base, audit=True, expandpath=False, realpath=False):
 
301
        if expandpath:
 
302
            base = util.expandpath(base)
 
303
        if realpath:
 
304
            base = os.path.realpath(base)
 
305
        self.base = base
 
306
        self._setmustaudit(audit)
 
307
        self.createmode = None
 
308
        self._trustnlink = None
 
309
 
 
310
    def _getmustaudit(self):
 
311
        return self._audit
 
312
 
 
313
    def _setmustaudit(self, onoff):
 
314
        self._audit = onoff
 
315
        if onoff:
 
316
            self.audit = pathauditor(self.base)
 
317
        else:
 
318
            self.audit = util.always
 
319
 
 
320
    mustaudit = property(_getmustaudit, _setmustaudit)
 
321
 
 
322
    @util.propertycache
 
323
    def _cansymlink(self):
 
324
        return util.checklink(self.base)
 
325
 
 
326
    @util.propertycache
 
327
    def _chmod(self):
 
328
        return util.checkexec(self.base)
 
329
 
 
330
    def _fixfilemode(self, name):
 
331
        if self.createmode is None or not self._chmod:
 
332
            return
 
333
        os.chmod(name, self.createmode & 0666)
 
334
 
 
335
    def __call__(self, path, mode="r", text=False, atomictemp=False):
 
336
        if self._audit:
 
337
            r = util.checkosfilename(path)
 
338
            if r:
 
339
                raise util.Abort("%s: %r" % (r, path))
 
340
        self.audit(path)
 
341
        f = self.join(path)
 
342
 
 
343
        if not text and "b" not in mode:
 
344
            mode += "b" # for that other OS
 
345
 
 
346
        nlink = -1
 
347
        if mode not in ('r', 'rb'):
 
348
            dirname, basename = util.split(f)
 
349
            # If basename is empty, then the path is malformed because it points
 
350
            # to a directory. Let the posixfile() call below raise IOError.
 
351
            if basename:
 
352
                if atomictemp:
 
353
                    util.ensuredirs(dirname, self.createmode)
 
354
                    return util.atomictempfile(f, mode, self.createmode)
 
355
                try:
 
356
                    if 'w' in mode:
 
357
                        util.unlink(f)
 
358
                        nlink = 0
 
359
                    else:
 
360
                        # nlinks() may behave differently for files on Windows
 
361
                        # shares if the file is open.
 
362
                        fd = util.posixfile(f)
 
363
                        nlink = util.nlinks(f)
 
364
                        if nlink < 1:
 
365
                            nlink = 2 # force mktempcopy (issue1922)
 
366
                        fd.close()
 
367
                except (OSError, IOError), e:
 
368
                    if e.errno != errno.ENOENT:
 
369
                        raise
 
370
                    nlink = 0
 
371
                    util.ensuredirs(dirname, self.createmode)
 
372
                if nlink > 0:
 
373
                    if self._trustnlink is None:
 
374
                        self._trustnlink = nlink > 1 or util.checknlink(f)
 
375
                    if nlink > 1 or not self._trustnlink:
 
376
                        util.rename(util.mktempcopy(f), f)
 
377
        fp = util.posixfile(f, mode)
 
378
        if nlink == 0:
 
379
            self._fixfilemode(f)
 
380
        return fp
 
381
 
 
382
    def symlink(self, src, dst):
 
383
        self.audit(dst)
 
384
        linkname = self.join(dst)
 
385
        try:
 
386
            os.unlink(linkname)
 
387
        except OSError:
 
388
            pass
 
389
 
 
390
        util.ensuredirs(os.path.dirname(linkname), self.createmode)
 
391
 
 
392
        if self._cansymlink:
 
393
            try:
 
394
                os.symlink(src, linkname)
 
395
            except OSError, err:
 
396
                raise OSError(err.errno, _('could not symlink to %r: %s') %
 
397
                              (src, err.strerror), linkname)
 
398
        else:
 
399
            self.write(dst, src)
 
400
 
 
401
    def join(self, path):
 
402
        if path:
 
403
            return os.path.join(self.base, path)
 
404
        else:
 
405
            return self.base
 
406
 
 
407
opener = vfs
 
408
 
 
409
class auditvfs(object):
 
410
    def __init__(self, vfs):
 
411
        self.vfs = vfs
 
412
 
 
413
    def _getmustaudit(self):
 
414
        return self.vfs.mustaudit
 
415
 
 
416
    def _setmustaudit(self, onoff):
 
417
        self.vfs.mustaudit = onoff
 
418
 
 
419
    mustaudit = property(_getmustaudit, _setmustaudit)
 
420
 
 
421
class filtervfs(abstractvfs, auditvfs):
 
422
    '''Wrapper vfs for filtering filenames with a function.'''
 
423
 
 
424
    def __init__(self, vfs, filter):
 
425
        auditvfs.__init__(self, vfs)
 
426
        self._filter = filter
 
427
 
 
428
    def __call__(self, path, *args, **kwargs):
 
429
        return self.vfs(self._filter(path), *args, **kwargs)
 
430
 
 
431
    def join(self, path):
 
432
        if path:
 
433
            return self.vfs.join(self._filter(path))
 
434
        else:
 
435
            return self.vfs.join(path)
 
436
 
 
437
filteropener = filtervfs
 
438
 
 
439
class readonlyvfs(abstractvfs, auditvfs):
 
440
    '''Wrapper vfs preventing any writing.'''
 
441
 
 
442
    def __init__(self, vfs):
 
443
        auditvfs.__init__(self, vfs)
 
444
 
 
445
    def __call__(self, path, mode='r', *args, **kw):
 
446
        if mode not in ('r', 'rb'):
 
447
            raise util.Abort('this vfs is read only')
 
448
        return self.vfs(path, mode, *args, **kw)
 
449
 
 
450
 
 
451
def canonpath(root, cwd, myname, auditor=None):
 
452
    '''return the canonical path of myname, given cwd and root'''
 
453
    if util.endswithsep(root):
 
454
        rootsep = root
 
455
    else:
 
456
        rootsep = root + os.sep
 
457
    name = myname
 
458
    if not os.path.isabs(name):
 
459
        name = os.path.join(root, cwd, name)
 
460
    name = os.path.normpath(name)
 
461
    if auditor is None:
 
462
        auditor = pathauditor(root)
 
463
    if name != rootsep and name.startswith(rootsep):
 
464
        name = name[len(rootsep):]
 
465
        auditor(name)
 
466
        return util.pconvert(name)
 
467
    elif name == root:
 
468
        return ''
 
469
    else:
 
470
        # Determine whether `name' is in the hierarchy at or beneath `root',
 
471
        # by iterating name=dirname(name) until that causes no change (can't
 
472
        # check name == '/', because that doesn't work on windows). The list
 
473
        # `rel' holds the reversed list of components making up the relative
 
474
        # file name we want.
 
475
        rel = []
 
476
        while True:
 
477
            try:
 
478
                s = util.samefile(name, root)
 
479
            except OSError:
 
480
                s = False
 
481
            if s:
 
482
                if not rel:
 
483
                    # name was actually the same as root (maybe a symlink)
 
484
                    return ''
 
485
                rel.reverse()
 
486
                name = os.path.join(*rel)
 
487
                auditor(name)
 
488
                return util.pconvert(name)
 
489
            dirname, basename = util.split(name)
 
490
            rel.append(basename)
 
491
            if dirname == name:
 
492
                break
 
493
            name = dirname
 
494
 
 
495
        raise util.Abort(_("%s not under root '%s'") % (myname, root))
 
496
 
 
497
def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
 
498
    '''yield every hg repository under path, always recursively.
 
499
    The recurse flag will only control recursion into repo working dirs'''
 
500
    def errhandler(err):
 
501
        if err.filename == path:
 
502
            raise err
 
503
    samestat = getattr(os.path, 'samestat', None)
 
504
    if followsym and samestat is not None:
 
505
        def adddir(dirlst, dirname):
 
506
            match = False
 
507
            dirstat = os.stat(dirname)
 
508
            for lstdirstat in dirlst:
 
509
                if samestat(dirstat, lstdirstat):
 
510
                    match = True
 
511
                    break
 
512
            if not match:
 
513
                dirlst.append(dirstat)
 
514
            return not match
 
515
    else:
 
516
        followsym = False
 
517
 
 
518
    if (seen_dirs is None) and followsym:
 
519
        seen_dirs = []
 
520
        adddir(seen_dirs, path)
 
521
    for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
 
522
        dirs.sort()
 
523
        if '.hg' in dirs:
 
524
            yield root # found a repository
 
525
            qroot = os.path.join(root, '.hg', 'patches')
 
526
            if os.path.isdir(os.path.join(qroot, '.hg')):
 
527
                yield qroot # we have a patch queue repo here
 
528
            if recurse:
 
529
                # avoid recursing inside the .hg directory
 
530
                dirs.remove('.hg')
 
531
            else:
 
532
                dirs[:] = [] # don't descend further
 
533
        elif followsym:
 
534
            newdirs = []
 
535
            for d in dirs:
 
536
                fname = os.path.join(root, d)
 
537
                if adddir(seen_dirs, fname):
 
538
                    if os.path.islink(fname):
 
539
                        for hgname in walkrepos(fname, True, seen_dirs):
 
540
                            yield hgname
 
541
                    else:
 
542
                        newdirs.append(d)
 
543
            dirs[:] = newdirs
 
544
 
 
545
def osrcpath():
 
546
    '''return default os-specific hgrc search path'''
 
547
    path = systemrcpath()
 
548
    path.extend(userrcpath())
 
549
    path = [os.path.normpath(f) for f in path]
 
550
    return path
 
551
 
 
552
_rcpath = None
 
553
 
 
554
def rcpath():
 
555
    '''return hgrc search path. if env var HGRCPATH is set, use it.
 
556
    for each item in path, if directory, use files ending in .rc,
 
557
    else use item.
 
558
    make HGRCPATH empty to only look in .hg/hgrc of current repo.
 
559
    if no HGRCPATH, use default os-specific path.'''
 
560
    global _rcpath
 
561
    if _rcpath is None:
 
562
        if 'HGRCPATH' in os.environ:
 
563
            _rcpath = []
 
564
            for p in os.environ['HGRCPATH'].split(os.pathsep):
 
565
                if not p:
 
566
                    continue
 
567
                p = util.expandpath(p)
 
568
                if os.path.isdir(p):
 
569
                    for f, kind in osutil.listdir(p):
 
570
                        if f.endswith('.rc'):
 
571
                            _rcpath.append(os.path.join(p, f))
 
572
                else:
 
573
                    _rcpath.append(p)
 
574
        else:
 
575
            _rcpath = osrcpath()
 
576
    return _rcpath
 
577
 
 
578
def revsingle(repo, revspec, default='.'):
 
579
    if not revspec and revspec != 0:
 
580
        return repo[default]
 
581
 
 
582
    l = revrange(repo, [revspec])
 
583
    if len(l) < 1:
 
584
        raise util.Abort(_('empty revision set'))
 
585
    return repo[l[-1]]
 
586
 
 
587
def revpair(repo, revs):
 
588
    if not revs:
 
589
        return repo.dirstate.p1(), None
 
590
 
 
591
    l = revrange(repo, revs)
 
592
 
 
593
    if len(l) == 0:
 
594
        if revs:
 
595
            raise util.Abort(_('empty revision range'))
 
596
        return repo.dirstate.p1(), None
 
597
 
 
598
    if len(l) == 1 and len(revs) == 1 and _revrangesep not in revs[0]:
 
599
        return repo.lookup(l[0]), None
 
600
 
 
601
    return repo.lookup(l[0]), repo.lookup(l[-1])
 
602
 
 
603
_revrangesep = ':'
 
604
 
 
605
def revrange(repo, revs):
 
606
    """Yield revision as strings from a list of revision specifications."""
 
607
 
 
608
    def revfix(repo, val, defval):
 
609
        if not val and val != 0 and defval is not None:
 
610
            return defval
 
611
        return repo[val].rev()
 
612
 
 
613
    seen, l = set(), []
 
614
    for spec in revs:
 
615
        if l and not seen:
 
616
            seen = set(l)
 
617
        # attempt to parse old-style ranges first to deal with
 
618
        # things like old-tag which contain query metacharacters
 
619
        try:
 
620
            if isinstance(spec, int):
 
621
                seen.add(spec)
 
622
                l.append(spec)
 
623
                continue
 
624
 
 
625
            if _revrangesep in spec:
 
626
                start, end = spec.split(_revrangesep, 1)
 
627
                start = revfix(repo, start, 0)
 
628
                end = revfix(repo, end, len(repo) - 1)
 
629
                if end == nullrev and start <= 0:
 
630
                    start = nullrev
 
631
                rangeiter = repo.changelog.revs(start, end)
 
632
                if not seen and not l:
 
633
                    # by far the most common case: revs = ["-1:0"]
 
634
                    l = list(rangeiter)
 
635
                    # defer syncing seen until next iteration
 
636
                    continue
 
637
                newrevs = set(rangeiter)
 
638
                if seen:
 
639
                    newrevs.difference_update(seen)
 
640
                    seen.update(newrevs)
 
641
                else:
 
642
                    seen = newrevs
 
643
                l.extend(sorted(newrevs, reverse=start > end))
 
644
                continue
 
645
            elif spec and spec in repo: # single unquoted rev
 
646
                rev = revfix(repo, spec, None)
 
647
                if rev in seen:
 
648
                    continue
 
649
                seen.add(rev)
 
650
                l.append(rev)
 
651
                continue
 
652
        except error.RepoLookupError:
 
653
            pass
 
654
 
 
655
        # fall through to new-style queries if old-style fails
 
656
        m = revset.match(repo.ui, spec)
 
657
        dl = [r for r in m(repo, list(repo)) if r not in seen]
 
658
        l.extend(dl)
 
659
        seen.update(dl)
 
660
 
 
661
    return l
 
662
 
 
663
def expandpats(pats):
 
664
    if not util.expandglobs:
 
665
        return list(pats)
 
666
    ret = []
 
667
    for p in pats:
 
668
        kind, name = matchmod._patsplit(p, None)
 
669
        if kind is None:
 
670
            try:
 
671
                globbed = glob.glob(name)
 
672
            except re.error:
 
673
                globbed = [name]
 
674
            if globbed:
 
675
                ret.extend(globbed)
 
676
                continue
 
677
        ret.append(p)
 
678
    return ret
 
679
 
 
680
def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath'):
 
681
    if pats == ("",):
 
682
        pats = []
 
683
    if not globbed and default == 'relpath':
 
684
        pats = expandpats(pats or [])
 
685
 
 
686
    m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
 
687
                         default)
 
688
    def badfn(f, msg):
 
689
        ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
 
690
    m.bad = badfn
 
691
    return m, pats
 
692
 
 
693
def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
 
694
    return matchandpats(ctx, pats, opts, globbed, default)[0]
 
695
 
 
696
def matchall(repo):
 
697
    return matchmod.always(repo.root, repo.getcwd())
 
698
 
 
699
def matchfiles(repo, files):
 
700
    return matchmod.exact(repo.root, repo.getcwd(), files)
 
701
 
 
702
def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
 
703
    if dry_run is None:
 
704
        dry_run = opts.get('dry_run')
 
705
    if similarity is None:
 
706
        similarity = float(opts.get('similarity') or 0)
 
707
    # we'd use status here, except handling of symlinks and ignore is tricky
 
708
    m = match(repo[None], pats, opts)
 
709
    rejected = []
 
710
    m.bad = lambda x, y: rejected.append(x)
 
711
 
 
712
    added, unknown, deleted, removed = _interestingfiles(repo, m)
 
713
 
 
714
    unknownset = set(unknown)
 
715
    toprint = unknownset.copy()
 
716
    toprint.update(deleted)
 
717
    for abs in sorted(toprint):
 
718
        if repo.ui.verbose or not m.exact(abs):
 
719
            rel = m.rel(abs)
 
720
            if abs in unknownset:
 
721
                status = _('adding %s\n') % ((pats and rel) or abs)
 
722
            else:
 
723
                status = _('removing %s\n') % ((pats and rel) or abs)
 
724
            repo.ui.status(status)
 
725
 
 
726
    renames = _findrenames(repo, m, added + unknown, removed + deleted,
 
727
                           similarity)
 
728
 
 
729
    if not dry_run:
 
730
        _markchanges(repo, unknown, deleted, renames)
 
731
 
 
732
    for f in rejected:
 
733
        if f in m.files():
 
734
            return 1
 
735
    return 0
 
736
 
 
737
def marktouched(repo, files, similarity=0.0):
 
738
    '''Assert that files have somehow been operated upon. files are relative to
 
739
    the repo root.'''
 
740
    m = matchfiles(repo, files)
 
741
    rejected = []
 
742
    m.bad = lambda x, y: rejected.append(x)
 
743
 
 
744
    added, unknown, deleted, removed = _interestingfiles(repo, m)
 
745
 
 
746
    if repo.ui.verbose:
 
747
        unknownset = set(unknown)
 
748
        toprint = unknownset.copy()
 
749
        toprint.update(deleted)
 
750
        for abs in sorted(toprint):
 
751
            if abs in unknownset:
 
752
                status = _('adding %s\n') % abs
 
753
            else:
 
754
                status = _('removing %s\n') % abs
 
755
            repo.ui.status(status)
 
756
 
 
757
    renames = _findrenames(repo, m, added + unknown, removed + deleted,
 
758
                           similarity)
 
759
 
 
760
    _markchanges(repo, unknown, deleted, renames)
 
761
 
 
762
    for f in rejected:
 
763
        if f in m.files():
 
764
            return 1
 
765
    return 0
 
766
 
 
767
def _interestingfiles(repo, matcher):
 
768
    '''Walk dirstate with matcher, looking for files that addremove would care
 
769
    about.
 
770
 
 
771
    This is different from dirstate.status because it doesn't care about
 
772
    whether files are modified or clean.'''
 
773
    added, unknown, deleted, removed = [], [], [], []
 
774
    audit_path = pathauditor(repo.root)
 
775
 
 
776
    ctx = repo[None]
 
777
    dirstate = repo.dirstate
 
778
    walkresults = dirstate.walk(matcher, sorted(ctx.substate), True, False,
 
779
                                full=False)
 
780
    for abs, st in walkresults.iteritems():
 
781
        dstate = dirstate[abs]
 
782
        if dstate == '?' and audit_path.check(abs):
 
783
            unknown.append(abs)
 
784
        elif dstate != 'r' and not st:
 
785
            deleted.append(abs)
 
786
        # for finding renames
 
787
        elif dstate == 'r':
 
788
            removed.append(abs)
 
789
        elif dstate == 'a':
 
790
            added.append(abs)
 
791
 
 
792
    return added, unknown, deleted, removed
 
793
 
 
794
def _findrenames(repo, matcher, added, removed, similarity):
 
795
    '''Find renames from removed files to added ones.'''
 
796
    renames = {}
 
797
    if similarity > 0:
 
798
        for old, new, score in similar.findrenames(repo, added, removed,
 
799
                                                   similarity):
 
800
            if (repo.ui.verbose or not matcher.exact(old)
 
801
                or not matcher.exact(new)):
 
802
                repo.ui.status(_('recording removal of %s as rename to %s '
 
803
                                 '(%d%% similar)\n') %
 
804
                               (matcher.rel(old), matcher.rel(new),
 
805
                                score * 100))
 
806
            renames[new] = old
 
807
    return renames
 
808
 
 
809
def _markchanges(repo, unknown, deleted, renames):
 
810
    '''Marks the files in unknown as added, the files in deleted as removed,
 
811
    and the files in renames as copied.'''
 
812
    wctx = repo[None]
 
813
    wlock = repo.wlock()
 
814
    try:
 
815
        wctx.forget(deleted)
 
816
        wctx.add(unknown)
 
817
        for new, old in renames.iteritems():
 
818
            wctx.copy(old, new)
 
819
    finally:
 
820
        wlock.release()
 
821
 
 
822
def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
 
823
    """Update the dirstate to reflect the intent of copying src to dst. For
 
824
    different reasons it might not end with dst being marked as copied from src.
 
825
    """
 
826
    origsrc = repo.dirstate.copied(src) or src
 
827
    if dst == origsrc: # copying back a copy?
 
828
        if repo.dirstate[dst] not in 'mn' and not dryrun:
 
829
            repo.dirstate.normallookup(dst)
 
830
    else:
 
831
        if repo.dirstate[origsrc] == 'a' and origsrc == src:
 
832
            if not ui.quiet:
 
833
                ui.warn(_("%s has not been committed yet, so no copy "
 
834
                          "data will be stored for %s.\n")
 
835
                        % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
 
836
            if repo.dirstate[dst] in '?r' and not dryrun:
 
837
                wctx.add([dst])
 
838
        elif not dryrun:
 
839
            wctx.copy(origsrc, dst)
 
840
 
 
841
def readrequires(opener, supported):
 
842
    '''Reads and parses .hg/requires and checks if all entries found
 
843
    are in the list of supported features.'''
 
844
    requirements = set(opener.read("requires").splitlines())
 
845
    missings = []
 
846
    for r in requirements:
 
847
        if r not in supported:
 
848
            if not r or not r[0].isalnum():
 
849
                raise error.RequirementError(_(".hg/requires file is corrupt"))
 
850
            missings.append(r)
 
851
    missings.sort()
 
852
    if missings:
 
853
        raise error.RequirementError(
 
854
            _("unknown repository format: requires features '%s' (upgrade "
 
855
              "Mercurial)") % "', '".join(missings))
 
856
    return requirements
 
857
 
 
858
class filecacheentry(object):
 
859
    def __init__(self, path, stat=True):
 
860
        self.path = path
 
861
        self.cachestat = None
 
862
        self._cacheable = None
 
863
 
 
864
        if stat:
 
865
            self.cachestat = filecacheentry.stat(self.path)
 
866
 
 
867
            if self.cachestat:
 
868
                self._cacheable = self.cachestat.cacheable()
 
869
            else:
 
870
                # None means we don't know yet
 
871
                self._cacheable = None
 
872
 
 
873
    def refresh(self):
 
874
        if self.cacheable():
 
875
            self.cachestat = filecacheentry.stat(self.path)
 
876
 
 
877
    def cacheable(self):
 
878
        if self._cacheable is not None:
 
879
            return self._cacheable
 
880
 
 
881
        # we don't know yet, assume it is for now
 
882
        return True
 
883
 
 
884
    def changed(self):
 
885
        # no point in going further if we can't cache it
 
886
        if not self.cacheable():
 
887
            return True
 
888
 
 
889
        newstat = filecacheentry.stat(self.path)
 
890
 
 
891
        # we may not know if it's cacheable yet, check again now
 
892
        if newstat and self._cacheable is None:
 
893
            self._cacheable = newstat.cacheable()
 
894
 
 
895
            # check again
 
896
            if not self._cacheable:
 
897
                return True
 
898
 
 
899
        if self.cachestat != newstat:
 
900
            self.cachestat = newstat
 
901
            return True
 
902
        else:
 
903
            return False
 
904
 
 
905
    @staticmethod
 
906
    def stat(path):
 
907
        try:
 
908
            return util.cachestat(path)
 
909
        except OSError, e:
 
910
            if e.errno != errno.ENOENT:
 
911
                raise
 
912
 
 
913
class filecache(object):
 
914
    '''A property like decorator that tracks a file under .hg/ for updates.
 
915
 
 
916
    Records stat info when called in _filecache.
 
917
 
 
918
    On subsequent calls, compares old stat info with new info, and recreates
 
919
    the object when needed, updating the new stat info in _filecache.
 
920
 
 
921
    Mercurial either atomic renames or appends for files under .hg,
 
922
    so to ensure the cache is reliable we need the filesystem to be able
 
923
    to tell us if a file has been replaced. If it can't, we fallback to
 
924
    recreating the object on every call (essentially the same behaviour as
 
925
    propertycache).'''
 
926
    def __init__(self, path):
 
927
        self.path = path
 
928
 
 
929
    def join(self, obj, fname):
 
930
        """Used to compute the runtime path of the cached file.
 
931
 
 
932
        Users should subclass filecache and provide their own version of this
 
933
        function to call the appropriate join function on 'obj' (an instance
 
934
        of the class that its member function was decorated).
 
935
        """
 
936
        return obj.join(fname)
 
937
 
 
938
    def __call__(self, func):
 
939
        self.func = func
 
940
        self.name = func.__name__
 
941
        return self
 
942
 
 
943
    def __get__(self, obj, type=None):
 
944
        # do we need to check if the file changed?
 
945
        if self.name in obj.__dict__:
 
946
            assert self.name in obj._filecache, self.name
 
947
            return obj.__dict__[self.name]
 
948
 
 
949
        entry = obj._filecache.get(self.name)
 
950
 
 
951
        if entry:
 
952
            if entry.changed():
 
953
                entry.obj = self.func(obj)
 
954
        else:
 
955
            path = self.join(obj, self.path)
 
956
 
 
957
            # We stat -before- creating the object so our cache doesn't lie if
 
958
            # a writer modified between the time we read and stat
 
959
            entry = filecacheentry(path)
 
960
            entry.obj = self.func(obj)
 
961
 
 
962
            obj._filecache[self.name] = entry
 
963
 
 
964
        obj.__dict__[self.name] = entry.obj
 
965
        return entry.obj
 
966
 
 
967
    def __set__(self, obj, value):
 
968
        if self.name not in obj._filecache:
 
969
            # we add an entry for the missing value because X in __dict__
 
970
            # implies X in _filecache
 
971
            ce = filecacheentry(self.join(obj, self.path), False)
 
972
            obj._filecache[self.name] = ce
 
973
        else:
 
974
            ce = obj._filecache[self.name]
 
975
 
 
976
        ce.obj = value # update cached copy
 
977
        obj.__dict__[self.name] = value # update copy returned by obj.x
 
978
 
 
979
    def __delete__(self, obj):
 
980
        try:
 
981
            del obj.__dict__[self.name]
 
982
        except KeyError:
 
983
            raise AttributeError(self.name)
 
984
 
 
985
class dirs(object):
 
986
    '''a multiset of directory names from a dirstate or manifest'''
 
987
 
 
988
    def __init__(self, map, skip=None):
 
989
        self._dirs = {}
 
990
        addpath = self.addpath
 
991
        if util.safehasattr(map, 'iteritems') and skip is not None:
 
992
            for f, s in map.iteritems():
 
993
                if s[0] != skip:
 
994
                    addpath(f)
 
995
        else:
 
996
            for f in map:
 
997
                addpath(f)
 
998
 
 
999
    def addpath(self, path):
 
1000
        dirs = self._dirs
 
1001
        for base in finddirs(path):
 
1002
            if base in dirs:
 
1003
                dirs[base] += 1
 
1004
                return
 
1005
            dirs[base] = 1
 
1006
 
 
1007
    def delpath(self, path):
 
1008
        dirs = self._dirs
 
1009
        for base in finddirs(path):
 
1010
            if dirs[base] > 1:
 
1011
                dirs[base] -= 1
 
1012
                return
 
1013
            del dirs[base]
 
1014
 
 
1015
    def __iter__(self):
 
1016
        return self._dirs.iterkeys()
 
1017
 
 
1018
    def __contains__(self, d):
 
1019
        return d in self._dirs
 
1020
 
 
1021
if util.safehasattr(parsers, 'dirs'):
 
1022
    dirs = parsers.dirs
 
1023
 
 
1024
def finddirs(path):
 
1025
    pos = path.rfind('/')
 
1026
    while pos != -1:
 
1027
        yield path[:pos]
 
1028
        pos = path.rfind('/', 0, pos)