Mercurial > hg
changeset 2116:366e6328d10e
Merge with upstream
author | Thomas Arendsen Hein <thomas@intevation.de> |
---|---|
date | Sat, 22 Apr 2006 09:19:27 +0200 |
parents | fd77b7ee4aac (diff) 2f3e644decd7 (current diff) |
children | e296dee1cd9a f62195054c5b |
files | |
diffstat | 9 files changed, 320 insertions(+), 52 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/archival.py Sat Apr 22 09:19:27 2006 +0200 @@ -0,0 +1,170 @@ +# archival.py - revision archival for mercurial +# +# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> +# +# This software may be used and distributed according to the terms of +# the GNU General Public License, incorporated herein by reference. + +from demandload import * +from i18n import gettext as _ +from node import * +demandload(globals(), 'cStringIO os stat tarfile time util zipfile') + +def tidyprefix(dest, prefix, suffixes): + '''choose prefix to use for names in archive. make sure prefix is + safe for consumers.''' + + if prefix: + prefix = prefix.replace('\\', '/') + else: + if not isinstance(dest, str): + raise ValueError('dest must be string if no prefix') + prefix = os.path.basename(dest) + lower = prefix.lower() + for sfx in suffixes: + if lower.endswith(sfx): + prefix = prefix[:-len(sfx)] + break + lpfx = os.path.normpath(util.localpath(prefix)) + prefix = util.pconvert(lpfx) + if not prefix.endswith('/'): + prefix += '/' + if prefix.startswith('../') or os.path.isabs(lpfx) or '/../' in prefix: + raise util.Abort(_('archive prefix contains illegal components')) + return prefix + +class tarit: + '''write archive to tar file or stream. can write uncompressed, + or compress with gzip or bzip2.''' + + def __init__(self, dest, prefix, kind=''): + self.prefix = tidyprefix(dest, prefix, ['.tar', '.tar.bz2', '.tar.gz', + '.tgz', 'tbz2']) + self.mtime = int(time.time()) + if isinstance(dest, str): + self.z = tarfile.open(dest, mode='w:'+kind) + else: + self.z = tarfile.open(mode='w|'+kind, fileobj=dest) + + def addfile(self, name, mode, 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)) + + def done(self): + self.z.close() + +class tellable: + '''provide tell method for zipfile.ZipFile when writing to http + response file object.''' + + def __init__(self, fp): + self.fp = fp + self.offset = 0 + + def __getattr__(self, key): + return getattr(self.fp, key) + + def write(self, s): + self.fp.write(s) + self.offset += len(s) + + def tell(self): + return self.offset + +class zipit: + '''write archive to zip file or stream. can write uncompressed, + or compressed with deflate.''' + + def __init__(self, dest, prefix, compress=True): + self.prefix = tidyprefix(dest, prefix, ('.zip',)) + if not isinstance(dest, str) and not hasattr(dest, 'tell'): + dest = tellable(dest) + self.z = zipfile.ZipFile(dest, 'w', + compress and zipfile.ZIP_DEFLATED or + zipfile.ZIP_STORED) + self.date_time = time.gmtime(time.time())[:6] + + def addfile(self, name, mode, data): + i = zipfile.ZipInfo(self.prefix + name, self.date_time) + i.compress_type = self.z.compression + i.flag_bits = 0x08 + # 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 + self.z.writestr(i, data) + + def done(self): + self.z.close() + +class fileit: + '''write archive as files in directory.''' + + def __init__(self, name, prefix): + 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)) + + def addfile(self, name, mode, data): + 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) + + def done(self): + pass + +archivers = { + 'files': fileit, + 'tar': tarit, + 'tbz2': lambda name, prefix: tarit(name, prefix, 'bz2'), + 'tgz': lambda name, prefix: tarit(name, prefix, 'gz'), + 'uzip': lambda name, prefix: zipit(name, prefix, False), + 'zip': zipit, + } + +def archive(repo, dest, node, kind, decode=True, matchfn=None, + prefix=None): + '''create archive of repo as it was at node. + + dest can be name of directory, name of archive file, or file + object to write archive to. + + kind is type of archive to create. + + decode tells whether to put files through decode filters from + hgrc. + + matchfn is function to filter names of files to write to archive. + + prefix is name of path to put before every archive member.''' + + def write(name, mode, data): + if matchfn and not matchfn(name): return + if decode: + fp = cStringIO.StringIO() + repo.wwrite(None, data, fp) + data = fp.getvalue() + archiver.addfile(name, mode, data) + + archiver = archivers[kind](dest, prefix) + mn = repo.changelog.read(node)[0] + mf = repo.manifest.read(mn).items() + mff = repo.manifest.readflags(mn) + mf.sort() + write('.hg_archival.txt', 0644, + 'repo: %s\nnode: %s\n' % (hex(repo.changelog.node(0)), hex(node))) + for filename, filenode in mf: + write(filename, mff[filename] and 0755 or 0644, + repo.file(filename).read(filenode)) + archiver.done()
--- a/mercurial/commands.py Fri Apr 21 16:30:49 2006 -0500 +++ b/mercurial/commands.py Sat Apr 22 09:19:27 2006 +0200 @@ -12,7 +12,7 @@ demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo") demandload(globals(), "fnmatch hgweb mdiff random signal tempfile time") demandload(globals(), "traceback errno socket version struct atexit sets bz2") -demandload(globals(), "changegroup") +demandload(globals(), "archival changegroup") class UnknownCommand(Exception): """Exception raised if command is not in the command table.""" @@ -890,6 +890,46 @@ for p, l in zip(zip(*pieces), lines): ui.write("%s: %s" % (" ".join(p), l[1])) +def archive(ui, repo, dest, **opts): + '''create unversioned archive of a repository revision + + By default, the revision used is the parent of the working + directory; use "-r" to specify a different revision. + + To specify the type of archive to create, use "-t". Valid + types are: + + "files" (default): a directory full of files + "tar": tar archive, uncompressed + "tbz2": tar archive, compressed using bzip2 + "tgz": tar archive, compressed using gzip + "uzip": zip archive, uncompressed + "zip": zip archive, compressed using deflate + + The exact name of the destination archive or directory is given + using a format string; see "hg help export" for details. + + Each member added to an archive file has a directory prefix + prepended. Use "-p" to specify a format string for the prefix. + The default is the basename of the archive, with suffixes removed. + ''' + + if opts['rev']: + node = repo.lookup(opts['rev']) + else: + node, p2 = repo.dirstate.parents() + if p2 != nullid: + raise util.Abort(_('uncommitted merge - please provide a ' + 'specific revision')) + + dest = make_filename(repo, repo.changelog, dest, node) + prefix = make_filename(repo, repo.changelog, opts['prefix'], node) + if os.path.realpath(dest) == repo.root: + raise util.Abort(_('repository root cannot be destination')) + _, matchfn, _ = matchpats(repo, [], opts) + archival.archive(repo, dest, node, opts.get('type') or 'files', + not opts['no_decode'], matchfn, prefix) + def bundle(ui, repo, fname, dest="default-push", **opts): """create a changegroup file @@ -2839,6 +2879,15 @@ ('I', 'include', [], _('include names matching the given patterns')), ('X', 'exclude', [], _('exclude names matching the given patterns'))], _('hg annotate [-r REV] [-a] [-u] [-d] [-n] [-c] FILE...')), + 'archive': + (archive, + [('', 'no-decode', None, _('do not pass files through decoders')), + ('p', 'prefix', '', _('directory prefix for files in archive')), + ('r', 'rev', '', _('revision to distribute')), + ('t', 'type', '', _('type of distribution to create')), + ('I', 'include', [], _('include names matching the given patterns')), + ('X', 'exclude', [], _('exclude names matching the given patterns'))], + _('hg archive [OPTION]... DEST')), "bundle": (bundle, [('f', 'force', None, @@ -3249,7 +3298,7 @@ return (cmd, cmd and i[0] or None, args, options, cmdoptions) def dispatch(args): - for name in 'SIGTERM', 'SIGHUP', 'SIGBREAK': + for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM': num = getattr(signal, name, None) if num: signal.signal(num, catchterm)
--- a/mercurial/hgweb.py Fri Apr 21 16:30:49 2006 -0500 +++ b/mercurial/hgweb.py Sat Apr 22 09:19:27 2006 +0200 @@ -10,8 +10,8 @@ import mimetypes from demandload import demandload demandload(globals(), "mdiff time re socket zlib errno ui hg ConfigParser") -demandload(globals(), "zipfile tempfile StringIO tarfile BaseHTTPServer util") -demandload(globals(), "mimetypes templater") +demandload(globals(), "tempfile StringIO BaseHTTPServer util") +demandload(globals(), "archival mimetypes templater") from node import * from i18n import gettext as _ @@ -682,55 +682,23 @@ child=self.siblings(cl.children(n), cl.rev), diff=diff) - def archive(self, req, cnode, type): - cs = self.repo.changelog.read(cnode) - mnode = cs[0] - mf = self.repo.manifest.read(mnode) - rev = self.repo.manifest.rev(mnode) - reponame = re.sub(r"\W+", "-", self.reponame) - name = "%s-%s/" % (reponame, short(cnode)) - - files = mf.keys() - files.sort() - - if type == 'zip': - tmp = tempfile.mkstemp()[1] - try: - zf = zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED) - - for f in files: - zf.writestr(name + f, self.repo.file(f).read(mf[f])) - zf.close() + archive_specs = { + 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', 'x-bzip2'), + 'gz': ('application/x-tar', 'tgz', '.tar.gz', 'x-gzip'), + 'zip': ('application/zip', 'zip', '.zip', None), + } - f = open(tmp, 'r') - req.httphdr('application/zip', name[:-1] + '.zip', - os.path.getsize(tmp)) - req.write(f.read()) - f.close() - finally: - os.unlink(tmp) - - else: - tf = tarfile.TarFile.open(mode='w|' + type, fileobj=req.out) - mff = self.repo.manifest.readflags(mnode) - mtime = int(time.time()) - - if type == "gz": - encoding = "gzip" - else: - encoding = "x-bzip2" - req.header([('Content-type', 'application/x-tar'), - ('Content-disposition', 'attachment; filename=%s%s%s' % - (name[:-1], '.tar.', type)), - ('Content-encoding', encoding)]) - for fname in files: - rcont = self.repo.file(fname).read(mf[fname]) - finfo = tarfile.TarInfo(name + fname) - finfo.mtime = mtime - finfo.size = len(rcont) - finfo.mode = mff[fname] and 0755 or 0644 - tf.addfile(finfo, StringIO.StringIO(rcont)) - tf.close() + def archive(self, req, cnode, type): + reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame)) + name = "%s-%s" % (reponame, short(cnode)) + mimetype, artype, extension, encoding = self.archive_specs[type] + headers = [('Content-type', mimetype), + ('Content-disposition', 'attachment; filename=%s%s' % + (name, extension))] + if encoding: + headers.append(('Content-encoding', encoding)) + req.header(headers) + archival.archive(self.repo, req.out, cnode, artype, prefix=name) # add tags to things # tags -> list of changesets corresponding to tags
--- a/mercurial/util.py Fri Apr 21 16:30:49 2006 -0500 +++ b/mercurial/util.py Sat Apr 22 09:19:27 2006 +0200 @@ -215,6 +215,30 @@ elif name == root: return '' else: + # Determine whether `name' is in the hierarchy at or beneath `root', + # by iterating name=dirname(name) until that causes no change (can't + # check name == '/', because that doesn't work on windows). For each + # `name', compare dev/inode numbers. If they match, the list `rel' + # holds the reversed list of components making up the relative file + # name we want. + root_st = os.stat(root) + rel = [] + while True: + try: + name_st = os.stat(name) + except OSError: + break + if os.path.samestat(name_st, root_st): + rel.reverse() + name = os.path.join(*rel) + audit_path(name) + return pconvert(name) + dirname, basename = os.path.split(name) + rel.append(basename) + if dirname == name: + break + name = dirname + raise Abort('%s not under root' % myname) def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
--- a/tests/test-archive Fri Apr 21 16:30:49 2006 -0500 +++ b/tests/test-archive Sat Apr 22 09:19:27 2006 +0200 @@ -36,3 +36,18 @@ kill `cat hg.pid` sleep 1 # wait for server to scream and die + +hg archive -t tar test.tar +tar tf test.tar + +hg archive -t tbz2 -X baz test.tar.bz2 +bunzip2 -dc test.tar.bz2 | tar tf - + +hg archive -t tgz -p %b-%h test-%h.tar.gz +gzip -dc test-$QTIP.tar.gz | tar tf - | sed "s/$QTIP/TIP/" + +hg archive -t zip -p /illegal test.zip +hg archive -t zip -p very/../bad test.zip + +hg archive -t zip -r 2 test.zip +unzip -t test.zip
--- a/tests/test-archive.out Fri Apr 21 16:30:49 2006 -0500 +++ b/tests/test-archive.out Sat Apr 22 09:19:27 2006 +0200 @@ -1,14 +1,35 @@ adding foo adding bar adding baz/bletch +test-archive-TIP/.hg_archival.txt test-archive-TIP/bar test-archive-TIP/baz/bletch test-archive-TIP/foo +test-archive-TIP/.hg_archival.txt test-archive-TIP/bar test-archive-TIP/baz/bletch test-archive-TIP/foo Archive: archive.zip + testing: test-archive-TIP/.hg_archival.txt OK testing: test-archive-TIP/bar OK testing: test-archive-TIP/baz/bletch OK testing: test-archive-TIP/foo OK No errors detected in compressed data of archive.zip. +test/.hg_archival.txt +test/bar +test/baz/bletch +test/foo +test/.hg_archival.txt +test/bar +test/foo +test-TIP/.hg_archival.txt +test-TIP/bar +test-TIP/baz/bletch +test-TIP/foo +abort: archive prefix contains illegal components +Archive: test.zip + testing: test/.hg_archival.txt OK + testing: test/bar OK + testing: test/baz/bletch OK + testing: test/foo OK +No errors detected in compressed data of test.zip.
--- a/tests/test-help.out Fri Apr 21 16:30:49 2006 -0500 +++ b/tests/test-help.out Sat Apr 22 09:19:27 2006 +0200 @@ -41,6 +41,7 @@ add add the specified files on the next commit addremove add all new files, delete all missing files annotate show changeset information per file line + archive create unversioned archive of a repository revision bundle create a changegroup file cat output the latest or given revisions of files clone make a copy of an existing repository @@ -83,6 +84,7 @@ add add the specified files on the next commit addremove add all new files, delete all missing files annotate show changeset information per file line + archive create unversioned archive of a repository revision bundle create a changegroup file cat output the latest or given revisions of files clone make a copy of an existing repository
--- a/tests/test-symlinks Fri Apr 21 16:30:49 2006 -0500 +++ b/tests/test-symlinks Sat Apr 22 09:19:27 2006 +0200 @@ -40,3 +40,18 @@ # it should show a.c, dir/a.o and dir/b.o deleted hg status hg status a.c + +echo '# test absolute path through symlink outside repo' +cd .. +p=`pwd` +hg init x +ln -s x y +cd x +touch f +hg add f +hg status $p/y/f + +echo '# try symlink outside repo to file inside' +ln -s x/f ../z +# this should fail +hg status ../z && { echo hg mistakenly exited with status 0; exit 1; } || :
--- a/tests/test-symlinks.out Fri Apr 21 16:30:49 2006 -0500 +++ b/tests/test-symlinks.out Sat Apr 22 09:19:27 2006 +0200 @@ -9,3 +9,7 @@ ? .hgignore a.c: unsupported file type (type is fifo) ! a.c +# test absolute path through symlink outside repo +A f +# try symlink outside repo to file inside +abort: ../z not under root