# HG changeset patch # User Alexis S. L. Carvalho # Date 1184195703 10800 # Node ID 192cd95c2ba833805c8c762a6da14571eeb663c0 # Parent 8be7ba42562152310603e3eaa5e3c453bf1b7702# Parent 0e2d0a78f81a7a818d48dae8c6e3fdd7bdccf9dc merge with crew-stable diff --git a/mercurial/archival.py b/mercurial/archival.py --- 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() diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py --- 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: diff --git a/mercurial/commands.py b/mercurial/commands.py --- 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" diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py --- 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) diff --git a/mercurial/hgweb/server.py b/mercurial/hgweb/server.py --- 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: diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py --- 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): diff --git a/mercurial/streamclone.py b/mercurial/streamclone.py --- 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 diff --git a/mercurial/util.py b/mercurial/util.py --- 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 diff --git a/tests/test-archive b/tests/test-archive --- 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 diff --git a/tests/test-archive-symlinks b/tests/test-archive-symlinks new file mode 100755 --- /dev/null +++ b/tests/test-archive-symlinks @@ -0,0 +1,37 @@ +#!/bin/sh + +origdir=`pwd` + +cat >> readlink.py <', 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 diff --git a/tests/test-archive-symlinks.out b/tests/test-archive-symlinks.out 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 diff --git a/tests/test-archive.out b/tests/test-archive.out --- 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 diff --git a/tests/test-copy2.out b/tests/test-copy2.out --- 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 diff --git a/tests/test-serve b/tests/test-serve --- 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 diff --git a/tests/test-serve.out b/tests/test-serve.out --- 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/