merge with mpm
authorMartin Geisler <mg@lazybytes.net>
Mon, 01 Jun 2009 01:24:01 +0200
changeset 8686 ad1907abf897
parent 8685 5306c6b00e69 (current diff)
parent 8684 5bb7780b57c7 (diff)
child 8687 78ab2a12b4d9
merge with mpm
--- a/hgext/acl.py	Mon Jun 01 00:56:29 2009 +0200
+++ b/hgext/acl.py	Mon Jun 01 01:24:01 2009 +0200
@@ -61,7 +61,7 @@
              (key, len(pats), user))
     if pats:
         return match.match(repo.root, '', pats)
-    return match.never(repo.root, '')
+    return match.exact(repo.root, '', [])
 
 
 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
--- a/mercurial/cmdutil.py	Mon Jun 01 00:56:29 2009 +0200
+++ b/mercurial/cmdutil.py	Mon Jun 01 01:24:01 2009 +0200
@@ -256,7 +256,6 @@
                     opts.get('include'), opts.get('exclude'), default)
     def badfn(f, msg):
         repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
-        return False
     m.bad = badfn
     return m
 
--- a/mercurial/commands.py	Mon Jun 01 00:56:29 2009 +0200
+++ b/mercurial/commands.py	Mon Jun 01 01:24:01 2009 +0200
@@ -29,24 +29,22 @@
     If no names are given, add all files to the repository.
     """
 
-    rejected = None
+    bad = []
     exacts = {}
     names = []
     m = cmdutil.match(repo, pats, opts)
-    m.bad = lambda x,y: True
-    for abs in repo.walk(m):
-        if m.exact(abs):
-            if ui.verbose:
-                ui.status(_('adding %s\n') % m.rel(abs))
-            names.append(abs)
-            exacts[abs] = 1
-        elif abs not in repo.dirstate:
-            ui.status(_('adding %s\n') % m.rel(abs))
-            names.append(abs)
+    oldbad = m.bad
+    m.bad = lambda x,y: bad.append(x) or oldbad(x,y)
+
+    for f in repo.walk(m):
+        exact = m.exact(f)
+        if exact or f not in repo.dirstate:
+            names.append(f)
+            if ui.verbose or not exact:
+                ui.status(_('adding %s\n') % m.rel(f))
     if not opts.get('dry_run'):
-        rejected = repo.add(names)
-        rejected = [p for p in rejected if p in exacts]
-    return rejected and 1 or 0
+        bad += [f for f in repo.add(names) if f in m.files()]
+    return bad and 1 or 0
 
 def addremove(ui, repo, *pats, **opts):
     """add all new files, delete all missing files
@@ -2486,13 +2484,12 @@
 
         def badfn(path, msg):
             if path in names:
-                return False
+                return
             path_ = path + '/'
             for f in names:
                 if f.startswith(path_):
-                    return False
+                    return
             ui.warn("%s: %s\n" % (m.rel(path), msg))
-            return False
 
         m = cmdutil.match(repo, pats, opts)
         m.bad = badfn
--- a/mercurial/dirstate.py	Mon Jun 01 00:56:29 2009 +0200
+++ b/mercurial/dirstate.py	Mon Jun 01 01:24:01 2009 +0200
@@ -425,19 +425,15 @@
         def fwarn(f, msg):
             self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
             return False
-        badfn = fwarn
-        if hasattr(match, 'bad'):
-            badfn = match.bad
 
-        def badtype(f, mode):
+        def badtype(mode):
             kind = _('unknown')
             if stat.S_ISCHR(mode): kind = _('character device')
             elif stat.S_ISBLK(mode): kind = _('block device')
             elif stat.S_ISFIFO(mode): kind = _('fifo')
             elif stat.S_ISSOCK(mode): kind = _('socket')
             elif stat.S_ISDIR(mode): kind = _('directory')
-            self._ui.warn(_('%s: unsupported file type (type is %s)\n')
-                          % (self.pathto(f), kind))
+            return _('unsupported file type (type is %s)') % kind
 
         ignore = self._ignore
         dirignore = self._dirignore
@@ -450,6 +446,7 @@
             dirignore = util.always
 
         matchfn = match.matchfn
+        badfn = match.bad
         dmap = self._map
         normpath = util.normpath
         normalize = self.normalize
@@ -463,22 +460,12 @@
         work = []
         wadd = work.append
 
-        if match.anypats():
-            #match.match with patterns
-            dostep3 = True
-            nomatches = False
-        elif not match.files():
-            #match.always or match.never
-            dostep3 = matchfn('')
-            nomatches = not dostep3
-        else:
-            #match.exact or match.match without pattern
-            dostep3 = False
-            nomatches = matchfn == match.exact
-
-        if nomatches:
-            #skip step 2
-            dirignore = util.always
+        exact = skipstep3 = False
+        if matchfn == match.exact: # match.exact
+            exact = True
+            dirignore = util.always # skip step 2
+        elif match.files() and not match.anypats(): # match.match, no patterns
+            skipstep3 = True
 
         files = set(match.files())
         if not files or '.' in files:
@@ -495,7 +482,7 @@
                 st = lstat(join(nf))
                 kind = getkind(st.st_mode)
                 if kind == dirkind:
-                    dostep3 = True
+                    skipstep3 = False
                     if nf in dmap:
                         #file deleted on disk but still in dirstate
                         results[nf] = None
@@ -504,28 +491,20 @@
                 elif kind == regkind or kind == lnkkind:
                     results[nf] = st
                 else:
-                    badtype(ff, kind)
+                    badfn(ff, badtype(kind))
                     if nf in dmap:
                         results[nf] = None
             except OSError, inst:
-                keep = False
-                prefix = nf + "/"
-                for fn in dmap:
-                    if nf == fn:
-                        if matchfn(nf):
-                            results[nf] = None
-                        keep = True
-                        break
-                    elif fn.startswith(prefix):
-                        dostep3 = True
-                        keep = True
-                        break
-                if not keep:
-                    if inst.errno != errno.ENOENT:
-                        fwarn(ff, inst.strerror)
-                    elif badfn(ff, inst.strerror):
-                        if nf not in results and not ignore(nf) and matchfn(nf):
-                            results[nf] = None
+                if nf in dmap: # does it exactly match a file?
+                    results[nf] = None
+                else: # does it match a directory?
+                    prefix = nf + "/"
+                    for fn in dmap:
+                        if fn.startswith(prefix):
+                            skipstep3 = False
+                            break
+                    else:
+                        badfn(ff, inst.strerror)
 
         # step 2: visit subdirectories
         while work:
@@ -562,7 +541,7 @@
                         results[nf] = None
 
         # step 3: report unseen items in the dmap hash
-        if dostep3 and not nomatches:
+        if not skipstep3 and not exact:
             visit = sorted([f for f in dmap if f not in results and matchfn(f)])
             for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
                 if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
--- a/mercurial/localrepo.py	Mon Jun 01 00:56:29 2009 +0200
+++ b/mercurial/localrepo.py	Mon Jun 01 01:24:01 2009 +0200
@@ -948,7 +948,6 @@
             def bad(f, msg):
                 if f not in ctx1:
                     self.ui.warn('%s: %s\n' % (self.dirstate.pathto(f), msg))
-                return False
             match.bad = bad
 
         if working: # we need to scan the working dir
--- a/mercurial/match.py	Mon Jun 01 00:56:29 2009 +0200
+++ b/mercurial/match.py	Mon Jun 01 01:24:01 2009 +0200
@@ -81,7 +81,10 @@
         for f in self._files:
             yield f
     def bad(self, f, msg):
-        return True
+        '''callback for each explicit file that can't be
+        found/accessed, with an error message
+        '''
+        pass
     def dir(self, f):
         pass
     def missing(self, f):
@@ -103,10 +106,6 @@
     def __init__(self, root, cwd):
         match.__init__(self, root, cwd, [])
 
-class never(match):
-    def __init__(self, root, cwd):
-        match.__init__(self, root, cwd, [], exact = True)
-
 def patkind(pat):
     return _patsplit(pat, None)[0]
 
--- a/tests/run-tests.py	Mon Jun 01 00:56:29 2009 +0200
+++ b/tests/run-tests.py	Mon Jun 01 01:24:01 2009 +0200
@@ -7,6 +7,38 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2, incorporated herein by reference.
 
+# Modifying this script is tricky because it has many modes:
+#   - serial (default) vs parallel (-jN, N > 1)
+#   - no coverage (default) vs coverage (-c, -C, -s)
+#   - temp install (default) vs specific hg script (--with-hg, --local)
+#   - tests are a mix of shell scripts and Python scripts
+#
+# If you change this script, it is recommended that you ensure you
+# haven't broken it by running it in various modes with a representative
+# sample of test scripts.  For example:
+# 
+#  1) serial, no coverage, temp install:
+#      ./run-tests.py test-s*
+#  2) serial, no coverage, local hg:
+#      ./run-tests.py --local test-s*
+#  3) serial, coverage, temp install:
+#      ./run-tests.py -c test-s*
+#  4) serial, coverage, local hg:
+#      ./run-tests.py -c --local test-s*      # unsupported
+#  5) parallel, no coverage, temp install:
+#      ./run-tests.py -j2 test-s*
+#  6) parallel, no coverage, local hg:
+#      ./run-tests.py -j2 --local test-s*
+#  7) parallel, coverage, temp install:
+#      ./run-tests.py -j2 -c test-s*          # currently broken
+#  8) parallel, coverage, local install
+#      ./run-tests.py -j2 -c --local test-s*  # unsupported (and broken)
+#
+# (You could use any subset of the tests: test-s* happens to match
+# enough that it's worth doing parallel runs, few enough that it
+# completes fairly quickly, includes both shell and Python scripts, and
+# includes some scripts that run daemon processes.)
+
 import difflib
 import errno
 import optparse
@@ -34,7 +66,6 @@
 SKIPPED_PREFIX = 'skipped: '
 FAILED_PREFIX  = 'hghave check failed: '
 PYTHON = sys.executable
-hgpkg = None
 
 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
 
@@ -81,7 +112,11 @@
     parser.add_option("-n", "--nodiff", action="store_true",
         help="skip showing test changes")
     parser.add_option("--with-hg", type="string",
-        help="test existing install at given location")
+        metavar="HG",
+        help="test using specified hg script rather than a "
+             "temporary installation")
+    parser.add_option("--local", action="store_true",
+        help="shortcut for --with-hg=<testdir>/../hg")
     parser.add_option("--pure", action="store_true",
         help="use pure Python code instead of C extensions")
 
@@ -90,13 +125,40 @@
     parser.set_defaults(**defaults)
     (options, args) = parser.parse_args()
 
-    global vlog
+    if options.with_hg:
+        if not (os.path.isfile(options.with_hg) and
+                os.access(options.with_hg, os.X_OK)):
+            parser.error('--with-hg must specify an executable hg script')
+        if not os.path.basename(options.with_hg) == 'hg':
+            sys.stderr.write('warning: --with-hg should specify an hg script')
+    if options.local:
+        testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
+        hgbin = os.path.join(os.path.dirname(testdir), 'hg')
+        if not os.access(hgbin, os.X_OK):
+            parser.error('--local specified, but %r not found or not executable'
+                         % hgbin)
+        options.with_hg = hgbin
+
     options.anycoverage = (options.cover or
                            options.cover_stdlib or
                            options.annotate)
 
+    if options.anycoverage and options.with_hg:
+        # I'm not sure if this is a fundamental limitation or just a
+        # bug.  But I don't want to waste people's time and energy doing
+        # test runs that don't give the results they want.
+        parser.error("sorry, coverage options do not work when --with-hg "
+                     "or --local specified")
+
+    global vlog
     if options.verbose:
+        if options.jobs > 1 or options.child is not None:
+            pid = "[%d]" % os.getpid()
+        else:
+            pid = None
         def vlog(*msg):
+            if pid:
+                print pid,
             for m in msg:
                 print m,
             print
@@ -178,8 +240,7 @@
 
 def cleanup(options):
     if not options.keep_tmpdir:
-        if options.verbose:
-            print "# Cleaning up HGTMP", HGTMP
+        vlog("# Cleaning up HGTMP", HGTMP)
         shutil.rmtree(HGTMP, True)
 
 def usecorrectpython():
@@ -200,7 +261,6 @@
         shutil.copymode(sys.executable, mypython)
 
 def installhg(options):
-    global PYTHON
     vlog("# Performing temporary installation of HG")
     installerrs = os.path.join("tests", "install.err")
     pure = options.pure and "--pure" or ""
@@ -223,19 +283,7 @@
         sys.exit(1)
     os.chdir(TESTDIR)
 
-    os.environ["PATH"] = "%s%s%s" % (BINDIR, os.pathsep, os.environ["PATH"])
-
-    pydir = os.pathsep.join([PYTHONDIR, TESTDIR])
-    pythonpath = os.environ.get("PYTHONPATH")
-    if pythonpath:
-        pythonpath = pydir + os.pathsep + pythonpath
-    else:
-        pythonpath = pydir
-    os.environ["PYTHONPATH"] = pythonpath
-
     usecorrectpython()
-    global hgpkg
-    hgpkg = _hgpath()
 
     vlog("# Installing dummy diffstat")
     f = open(os.path.join(BINDIR, 'diffstat'), 'w')
@@ -264,15 +312,6 @@
                  os.path.join(BINDIR, '_hg.py')))
         f.close()
         os.chmod(os.path.join(BINDIR, 'hg'), 0700)
-        PYTHON = '"%s" "%s" -x -p' % (sys.executable,
-                                      os.path.join(TESTDIR, 'coverage.py'))
-
-def _hgpath():
-    cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
-    hgpath = os.popen(cmd % PYTHON)
-    path = hgpath.read().strip()
-    hgpath.close()
-    return path
 
 def outputcoverage(options):
 
@@ -491,15 +530,42 @@
         return None
     return ret == 0
 
-def runchildren(options, expecthg, tests):
-    if not options.with_hg:
+_hgpath = None
+
+def _gethgpath():
+    """Return the path to the mercurial package that is actually found by
+    the current Python interpreter."""
+    global _hgpath
+    if _hgpath is not None:
+        return _hgpath
+
+    cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
+    pipe = os.popen(cmd % PYTHON)
+    try:
+        _hgpath = pipe.read().strip()
+    finally:
+        pipe.close()
+    return _hgpath
+
+def _checkhglib(verb):
+    """Ensure that the 'mercurial' package imported by python is
+    the one we expect it to be.  If not, print a warning to stderr."""
+    expecthg = os.path.join(PYTHONDIR, 'mercurial')
+    actualhg = _gethgpath()
+    if actualhg != expecthg:
+        sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
+                         '         (expected %s)\n'
+                         % (verb, actualhg, expecthg))
+
+def runchildren(options, tests):
+    if INST:
         installhg(options)
-        if hgpkg != expecthg:
-            print '# Testing unexpected mercurial: %s' % hgpkg
+        _checkhglib("Testing")
 
     optcopy = dict(options.__dict__)
     optcopy['jobs'] = 1
-    optcopy['with_hg'] = INST
+    if optcopy['with_hg'] is None:
+        optcopy['with_hg'] = os.path.join(BINDIR, "hg")
     opts = []
     for opt, value in optcopy.iteritems():
         name = '--' + opt.replace('_', '-')
@@ -549,23 +615,20 @@
     for s in fails:
         print "Failed %s: %s" % (s[0], s[1])
 
-    if hgpkg != expecthg:
-        print '# Tested unexpected mercurial: %s' % hgpkg
+    _checkhglib("Tested")
     print "# Ran %d tests, %d skipped, %d failed." % (
         tested, skipped, failed)
     sys.exit(failures != 0)
 
-def runtests(options, expecthg, tests):
+def runtests(options, tests):
     global DAEMON_PIDS, HGRCPATH
     DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
     HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
 
     try:
-        if not options.with_hg:
+        if INST:
             installhg(options)
-
-            if hgpkg != expecthg:
-                print '# Testing unexpected mercurial: %s' % hgpkg
+            _checkhglib("Testing")
 
         if options.timeout > 0:
             try:
@@ -627,8 +690,7 @@
                 print "Skipped %s: %s" % s
             for s in fails:
                 print "Failed %s: %s" % s
-            if hgpkg != expecthg:
-                print '# Tested unexpected mercurial: %s' % hgpkg
+            _checkhglib("Tested")
             print "# Ran %d tests, %d skipped, %d failed." % (
                 tested, skipped, failed)
 
@@ -672,15 +734,33 @@
     os.environ["HGPORT2"] = str(options.port + 2)
 
     if options.with_hg:
-        INST = options.with_hg
+        INST = None
+        BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
+
+        # This looks redundant with how Python initializes sys.path from
+        # the location of the script being executed.  Needed because the
+        # "hg" specified by --with-hg is not the only Python script
+        # executed in the test suite that needs to import 'mercurial'
+        # ... which means it's not really redundant at all.
+        PYTHONDIR = BINDIR
     else:
         INST = os.path.join(HGTMP, "install")
-    BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
-    PYTHONDIR = os.path.join(INST, "lib", "python")
+        BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
+        PYTHONDIR = os.path.join(INST, "lib", "python")
+
+    os.environ["BINDIR"] = BINDIR
+    os.environ["PYTHON"] = PYTHON
+
+    if not options.child:
+        path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
+        os.environ["PATH"] = os.pathsep.join(path)
+
+        # Deliberately override existing PYTHONPATH: do not want success
+        # to depend on what happens to be in caller's environment.
+        os.environ["PYTHONPATH"] = PYTHONDIR
+
     COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
 
-    expecthg = os.path.join(HGTMP, 'install', 'lib', 'python', 'mercurial')
-
     if len(args) == 0:
         args = os.listdir(".")
         args.sort()
@@ -697,12 +777,14 @@
 
     vlog("# Using TESTDIR", TESTDIR)
     vlog("# Using HGTMP", HGTMP)
+    vlog("# Using PATH", os.environ["PATH"])
+    vlog("# Using PYTHONPATH", os.environ["PYTHONPATH"])
 
     try:
         if len(tests) > 1 and options.jobs > 1:
-            runchildren(options, expecthg, tests)
+            runchildren(options, tests)
         else:
-            runtests(options, expecthg, tests)
+            runtests(options, tests)
     finally:
         cleanup(options)
 
--- a/tests/test-add.out	Mon Jun 01 00:56:29 2009 +0200
+++ b/tests/test-add.out	Mon Jun 01 01:24:01 2009 +0200
@@ -30,8 +30,8 @@
 ? a.orig
 M a
 ? a.orig
-c does not exist!
-d does not exist!
+c: No such file or directory
+d: No such file or directory
 M a
 A c
 ? a.orig
--- a/tests/test-convert	Mon Jun 01 00:56:29 2009 +0200
+++ b/tests/test-convert	Mon Jun 01 01:24:01 2009 +0200
@@ -47,4 +47,6 @@
 
 echo % converting empty dir should fail "nicely"
 mkdir emptydir
-PATH=$BINDIR hg convert emptydir 2>&1 | sed 's,file://.*/emptydir,.../emptydir,g'
+# override $PATH to ensure p4 not visible; use $PYTHON in case we're
+# running from a devel copy, not a temp installation
+PATH=$BINDIR $PYTHON $BINDIR/hg convert emptydir 2>&1 | sed 's,file://.*/emptydir,.../emptydir,g'
--- a/tests/test-merge-tools	Mon Jun 01 00:56:29 2009 +0200
+++ b/tests/test-merge-tools	Mon Jun 01 01:24:01 2009 +0200
@@ -60,7 +60,9 @@
 echo "# default is internal merge:"
 beforemerge
 echo "# hg merge -r 2"
-PATH=$BINDIR hg merge -r 2
+# override $PATH to ensure hgmerge not visible; use $PYTHON in case we're
+# running from a devel copy, not a temp installation
+PATH=$BINDIR $PYTHON $BINDIR/hg merge -r 2
 aftermerge
 
 echo "# simplest hgrc using false for merge:"