changeset 4868:192cd95c2ba8

merge with crew-stable
author Alexis S. L. Carvalho <alexis@cecm.usp.br>
date Wed, 11 Jul 2007 20:15:03 -0300
parents 8be7ba425621 (current diff) 0e2d0a78f81a (diff)
children be591b740e0f
files mercurial/commands.py mercurial/hgweb/hgweb_mod.py mercurial/hgweb/server.py mercurial/localrepo.py tests/test-archive tests/test-archive-symlinks tests/test-archive.out
diffstat 15 files changed, 212 insertions(+), 108 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/archival.py
+++ b/mercurial/archival.py
@@ -86,12 +86,19 @@ class tarit:
             # Python 2.5-2.5.1 have a regression that requires a name arg
             self.z = taropen(name='', mode='w|', fileobj=dest)
 
-    def addfile(self, name, mode, data):
+    def addfile(self, name, mode, islink, data):
         i = tarfile.TarInfo(self.prefix + name)
         i.mtime = self.mtime
         i.size = len(data)
-        i.mode = mode
-        self.z.addfile(i, cStringIO.StringIO(data))
+        if islink:
+            i.type = tarfile.SYMTYPE
+            i.mode = 0777
+            i.linkname = data
+            data = None
+        else:
+            i.mode = mode
+            data = cStringIO.StringIO(data)
+        self.z.addfile(i, data)
 
     def done(self):
         self.z.close()
@@ -130,13 +137,17 @@ class zipit:
                                  zipfile.ZIP_STORED)
         self.date_time = time.gmtime(mtime)[:6]
 
-    def addfile(self, name, mode, data):
+    def addfile(self, name, mode, islink, data):
         i = zipfile.ZipInfo(self.prefix + name, self.date_time)
         i.compress_type = self.z.compression
         # unzip will not honor unix file modes unless file creator is
         # set to unix (id 3).
         i.create_system = 3
-        i.external_attr = (mode | stat.S_IFREG) << 16L
+        ftype = stat.S_IFREG
+        if islink:
+            mode = 0777
+            ftype = stat.S_IFLNK
+        i.external_attr = (mode | ftype) << 16L
         self.z.writestr(i, data)
 
     def done(self):
@@ -149,19 +160,17 @@ class fileit:
         if prefix:
             raise util.Abort(_('cannot give prefix when archiving to files'))
         self.basedir = name
-        self.dirs = {}
-        self.oflags = (os.O_CREAT | os.O_EXCL | os.O_WRONLY |
-                       getattr(os, 'O_BINARY', 0) |
-                       getattr(os, 'O_NOFOLLOW', 0))
+        self.opener = util.opener(self.basedir)
 
-    def addfile(self, name, mode, data):
+    def addfile(self, name, mode, islink, data):
+        if islink:
+            self.opener.symlink(data, name)
+            return
+        f = self.opener(name, "w", atomictemp=True)
+        f.write(data)
+        f.rename()
         destfile = os.path.join(self.basedir, name)
-        destdir = os.path.dirname(destfile)
-        if destdir not in self.dirs:
-            if not os.path.isdir(destdir):
-                os.makedirs(destdir)
-            self.dirs[destdir] = 1
-        os.fdopen(os.open(destfile, self.oflags, mode), 'wb').write(data)
+        os.chmod(destfile, mode)
 
     def done(self):
         pass
@@ -191,20 +200,20 @@ def archive(repo, dest, node, kind, deco
 
     prefix is name of path to put before every archive member.'''
 
-    def write(name, mode, data):
+    def write(name, mode, islink, data):
         if matchfn and not matchfn(name): return
         if decode:
             data = repo.wwritedata(name, data)
-        archiver.addfile(name, mode, data)
+        archiver.addfile(name, mode, islink, data)
 
     ctx = repo.changectx(node)
     archiver = archivers[kind](dest, prefix, mtime or ctx.date()[0])
     m = ctx.manifest()
     items = m.items()
     items.sort()
-    write('.hg_archival.txt', 0644,
+    write('.hg_archival.txt', 0644, False,
           'repo: %s\nnode: %s\n' % (hex(repo.changelog.node(0)), hex(node)))
     for filename, filenode in items:
-        write(filename, m.execf(filename) and 0755 or 0644,
+        write(filename, m.execf(filename) and 0755 or 0644, m.linkf(filename),
               repo.file(filename).read(filenode))
     archiver.done()
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -542,9 +542,9 @@ def make_filename(repo, pat, node,
     try:
         if node:
             expander.update(node_expander)
-        if node and revwidth is not None:
+        if node:
             expander['r'] = (lambda:
-                    str(repo.changelog.rev(node)).zfill(revwidth))
+                    str(repo.changelog.rev(node)).zfill(revwidth or 0))
         if total is not None:
             expander['N'] = lambda: str(total)
         if seqno is not None:
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -548,9 +548,10 @@ def docopy(ui, repo, pats, opts, wlock):
         targets[abstarget] = abssrc
         if abstarget != origsrc:
             if repo.dirstate.state(origsrc) == 'a':
-                ui.warn(_("%s was marked for addition. "
-                          "%s will not be committed as a copy.\n")
-                        % (repo.pathto(origsrc, cwd), reltarget))
+                if not ui.quiet:
+                    ui.warn(_("%s has not been committed yet, so no copy "
+                              "data will be stored for %s.\n")
+                            % (repo.pathto(origsrc, cwd), reltarget))
                 if abstarget not in repo.dirstate and not opts.get('dry_run'):
                     repo.add([abstarget], wlock)
             elif not opts.get('dry_run'):
@@ -2471,6 +2472,8 @@ def serve(ui, repo, **opts):
     for o in optlist.split():
         if opts[o]:
             parentui.setconfig("web", o, str(opts[o]))
+            if repo.ui != parentui:
+                repo.ui.setconfig("web", o, str(opts[o]))
 
     if repo is None and not ui.config("web", "webdir_conf"):
         raise hg.RepoError(_("There is no Mercurial repository here"
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -1184,4 +1184,4 @@ class hgweb(object):
 
     def do_stream_out(self, req):
         req.httphdr("application/mercurial-0.1")
-        streamclone.stream_out(self.repo, req)
+        streamclone.stream_out(self.repo, req, untrusted=True)
--- a/mercurial/hgweb/server.py
+++ b/mercurial/hgweb/server.py
@@ -197,13 +197,13 @@ def create_server(ui, repo):
             return open(opt, 'w')
         return default
 
-    address = ui.config("web", "address", "")
-    port = int(ui.config("web", "port", 8000))
-    use_ipv6 = ui.configbool("web", "ipv6")
-    webdir_conf = ui.config("web", "webdir_conf")
-    ssl_cert = ui.config("web", "certificate")
-    accesslog = openlog(ui.config("web", "accesslog", "-"), sys.stdout)
-    errorlog = openlog(ui.config("web", "errorlog", "-"), sys.stderr)
+    address = repo.ui.config("web", "address", "")
+    port = int(repo.ui.config("web", "port", 8000))
+    use_ipv6 = repo.ui.configbool("web", "ipv6")
+    webdir_conf = repo.ui.config("web", "webdir_conf")
+    ssl_cert = repo.ui.config("web", "certificate")
+    accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
+    errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
 
     if use_threads:
         try:
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -460,15 +460,7 @@ class localrepository(repo.repository):
     def wwrite(self, filename, data, flags):
         data = self._filter("decode", filename, data)
         if "l" in flags:
-            f = self.wjoin(filename)
-            try:
-                os.unlink(f)
-            except OSError:
-                pass
-            d = os.path.dirname(f)
-            if not os.path.exists(d):
-                os.makedirs(d)
-            os.symlink(data, f)
+            self.wopener.symlink(data, filename)
         else:
             try:
                 if self._link(filename):
--- a/mercurial/streamclone.py
+++ b/mercurial/streamclone.py
@@ -56,11 +56,11 @@ def walkrepo(root):
 #
 #   server writes out raw file data.
 
-def stream_out(repo, fileobj):
+def stream_out(repo, fileobj, untrusted=False):
     '''stream out all metadata files in repository.
     writes to file-like object, must support write() and optional flush().'''
 
-    if not repo.ui.configbool('server', 'uncompressed'):
+    if not repo.ui.configbool('server', 'uncompressed', untrusted=untrusted):
         fileobj.write('1\n')
         return
 
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -540,7 +540,7 @@ def _matcher(canonroot, cwd, names, inc,
 
     return (roots, match, (inc or exc or anypats) and True)
 
-_hgexecutable = None
+_hgexecutable = 'hg'
 
 def set_hgexecutable(path):
     """remember location of the 'hg' executable if easily possible
@@ -549,7 +549,8 @@ def set_hgexecutable(path):
     fall back to 'hg' in this case.
     """
     global _hgexecutable
-    _hgexecutable = path and os.path.abspath(path) or 'hg'
+    if path:
+        _hgexecutable = os.path.abspath(path)
 
 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
     '''enhanced shell command execution.
@@ -1181,70 +1182,95 @@ def encodedopener(openerfn, fn):
         return openerfn(fn(path), *args, **kw)
     return o
 
-def opener(base, audit=True):
+def mktempcopy(name, emptyok=False):
+    """Create a temporary file with the same contents from name
+
+    The permission bits are copied from the original file.
+
+    If the temporary file is going to be truncated immediately, you
+    can use emptyok=True as an optimization.
+
+    Returns the name of the temporary file.
     """
-    return a function that opens files relative to base
+    d, fn = os.path.split(name)
+    fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
+    os.close(fd)
+    # Temporary files are created with mode 0600, which is usually not
+    # what we want.  If the original file already exists, just copy
+    # its mode.  Otherwise, manually obey umask.
+    try:
+        st_mode = os.lstat(name).st_mode
+    except OSError, inst:
+        if inst.errno != errno.ENOENT:
+            raise
+        st_mode = 0666 & ~_umask
+    os.chmod(temp, st_mode)
+    if emptyok:
+        return temp
+    try:
+        try:
+            ifp = posixfile(name, "rb")
+        except IOError, inst:
+            if inst.errno == errno.ENOENT:
+                return temp
+            if not getattr(inst, 'filename', None):
+                inst.filename = name
+            raise
+        ofp = posixfile(temp, "wb")
+        for chunk in filechunkiter(ifp):
+            ofp.write(chunk)
+        ifp.close()
+        ofp.close()
+    except:
+        try: os.unlink(temp)
+        except: pass
+        raise
+    return temp
 
-    this function is used to hide the details of COW semantics and
+class atomictempfile(posixfile):
+    """file-like object that atomically updates a file
+
+    All writes will be redirected to a temporary copy of the original
+    file.  When rename is called, the copy is renamed to the original
+    name, making the changes visible.
+    """
+    def __init__(self, name, mode):
+        self.__name = name
+        self.temp = mktempcopy(name, emptyok=('w' in mode))
+        posixfile.__init__(self, self.temp, mode)
+
+    def rename(self):
+        if not self.closed:
+            posixfile.close(self)
+            rename(self.temp, localpath(self.__name))
+
+    def __del__(self):
+        if not self.closed:
+            try:
+                os.unlink(self.temp)
+            except: pass
+            posixfile.close(self)
+
+class opener(object):
+    """Open files relative to a base directory
+
+    This class is used to hide the details of COW semantics and
     remote file access from higher level code.
     """
-    def mktempcopy(name, emptyok=False):
-        d, fn = os.path.split(name)
-        fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
-        os.close(fd)
-        # Temporary files are created with mode 0600, which is usually not
-        # what we want.  If the original file already exists, just copy
-        # its mode.  Otherwise, manually obey umask.
-        try:
-            st_mode = os.lstat(name).st_mode
-        except OSError, inst:
-            if inst.errno != errno.ENOENT:
-                raise
-            st_mode = 0666 & ~_umask
-        os.chmod(temp, st_mode)
-        if emptyok:
-            return temp
-        try:
-            try:
-                ifp = posixfile(name, "rb")
-            except IOError, inst:
-                if inst.errno == errno.ENOENT:
-                    return temp
-                if not getattr(inst, 'filename', None):
-                    inst.filename = name
-                raise
-            ofp = posixfile(temp, "wb")
-            for chunk in filechunkiter(ifp):
-                ofp.write(chunk)
-            ifp.close()
-            ofp.close()
-        except:
-            try: os.unlink(temp)
-            except: pass
-            raise
-        return temp
+    def __init__(self, base, audit=True):
+        self.base = base
+        self.audit = audit
 
-    class atomictempfile(posixfile):
-        """the file will only be copied when rename is called"""
-        def __init__(self, name, mode):
-            self.__name = name
-            self.temp = mktempcopy(name, emptyok=('w' in mode))
-            posixfile.__init__(self, self.temp, mode)
-        def rename(self):
-            if not self.closed:
-                posixfile.close(self)
-                rename(self.temp, localpath(self.__name))
-        def __del__(self):
-            if not self.closed:
-                try:
-                    os.unlink(self.temp)
-                except: pass
-                posixfile.close(self)
+    def __getattr__(self, name):
+        if name == '_can_symlink':
+            self._can_symlink = checklink(self.base)
+            return self._can_symlink
+        raise AttributeError(name)
 
-    def o(path, mode="r", text=False, atomictemp=False):
-        if audit:
+    def __call__(self, path, mode="r", text=False, atomictemp=False):
+        if self.audit:
             audit_path(path)
-        f = os.path.join(base, path)
+        f = os.path.join(self.base, path)
 
         if not text and "b" not in mode:
             mode += "b" # for that other OS
@@ -1263,7 +1289,25 @@ def opener(base, audit=True):
                 rename(mktempcopy(f), f)
         return posixfile(f, mode)
 
-    return o
+    def symlink(self, src, dst):
+        if self.audit:
+            audit_path(dst)
+        linkname = os.path.join(self.base, dst)
+        try:
+            os.unlink(linkname)
+        except OSError:
+            pass
+
+        dirname = os.path.dirname(linkname)
+        if not os.path.exists(dirname):
+            os.makedirs(dirname)
+
+        if self._can_symlink:
+            os.symlink(src, linkname)
+        else:
+            f = self(self, dst, "w")
+            f.write(src)
+            f.close()
 
 class chunkbuffer(object):
     """Allow arbitrary sized chunks of data to be efficiently read from an
--- a/tests/test-archive
+++ b/tests/test-archive
@@ -64,8 +64,13 @@ unzip -t test.zip
 
 hg archive -t tar - | tar tf - | sed "s/$QTIP/TIP/"
 
+hg archive -r 0 -t tar rev-%r.tar
+if [ -f rev-0.tar ]; then
+    echo 'rev-0.tar created'
+fi
+
 echo '% empty repo'
 hg init ../empty
 cd ../empty
 hg archive ../test-empty
-exit 0
\ No newline at end of file
+exit 0
new file mode 100755
--- /dev/null
+++ b/tests/test-archive-symlinks
@@ -0,0 +1,37 @@
+#!/bin/sh
+
+origdir=`pwd`
+
+cat >> readlink.py <<EOF
+import os
+import sys
+
+for f in sys.argv[1:]:
+    print f, '->', os.readlink(f)
+EOF
+
+hg init repo
+cd repo
+ln -s nothing dangling
+hg ci -qAm 'add symlink'
+
+hg archive -t files ../archive
+hg archive -t tar -p tar ../archive.tar
+hg archive -t zip -p zip ../archive.zip
+
+echo '% files'
+cd "$origdir"
+cd archive
+python ../readlink.py dangling
+
+echo '% tar'
+cd "$origdir"
+tar xf archive.tar
+cd tar
+python ../readlink.py dangling
+
+echo '% zip'
+cd "$origdir"
+unzip archive.zip > /dev/null
+cd zip
+python ../readlink.py dangling
new file mode 100644
--- /dev/null
+++ b/tests/test-archive-symlinks.out
@@ -0,0 +1,6 @@
+% files
+dangling -> nothing
+% tar
+dangling -> nothing
+% zip
+dangling -> nothing
--- a/tests/test-archive.out
+++ b/tests/test-archive.out
@@ -38,5 +38,6 @@ test-TIP/.hg_archival.txt
 test-TIP/bar
 test-TIP/baz/bletch
 test-TIP/foo
+rev-0.tar created
 % empty repo
 abort: repository has no revisions
--- a/tests/test-copy2.out
+++ b/tests/test-copy2.out
@@ -3,13 +3,13 @@ foo: not copying - file is not managed
 abort: no files to copy
 ? foo
 # dry-run; print a warning that this is not a real copy; foo is added
-foo was marked for addition. bar will not be committed as a copy.
+foo has not been committed yet, so no copy data will be stored for bar.
 A foo
 # should print a warning that this is not a real copy; bar is added
-foo was marked for addition. bar will not be committed as a copy.
+foo has not been committed yet, so no copy data will be stored for bar.
 A bar
 # should print a warning that this is not a real copy; foo is added
-bar was marked for addition. foo will not be committed as a copy.
+bar has not been committed yet, so no copy data will be stored for foo.
 A foo
 # dry-run; should show that foo is clean
 C foo
--- a/tests/test-serve
+++ b/tests/test-serve
@@ -3,9 +3,15 @@
 hg init test
 cd test
 
+echo '[web]' > .hg/hgrc
+echo 'accesslog = access.log' >> .hg/hgrc
+
 echo % Without -v
 hg serve -a localhost -p 20063 -d --pid-file=hg.pid
 cat hg.pid >> "$DAEMON_PIDS"
+if [ -f access.log ]; then
+    echo 'access log created - .hg/hgrc respected'
+fi
 
 echo % With -v
 hg serve -a localhost -p 20064 -d --pid-file=hg.pid -v
--- a/tests/test-serve.out
+++ b/tests/test-serve.out
@@ -1,3 +1,4 @@
 % Without -v
+access log created - .hg/hgrc respected
 % With -v
 listening at http://localhost:20064/