2006-04-22 02:27:57 +04:00
|
|
|
# archival.py - revision archival for mercurial
|
|
|
|
#
|
|
|
|
# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
|
|
|
|
#
|
2009-04-26 03:08:54 +04:00
|
|
|
# This software may be used and distributed according to the terms of the
|
2010-01-20 07:20:08 +03:00
|
|
|
# GNU General Public License version 2 or any later version.
|
2006-04-22 02:27:57 +04:00
|
|
|
|
2006-12-15 05:25:19 +03:00
|
|
|
from i18n import _
|
2008-03-07 00:23:26 +03:00
|
|
|
from node import hex
|
2012-06-17 06:34:06 +04:00
|
|
|
import match as matchmod
|
2009-08-11 11:04:02 +04:00
|
|
|
import cmdutil
|
2011-04-20 21:54:57 +04:00
|
|
|
import scmutil, util, encoding
|
2011-03-17 01:54:55 +03:00
|
|
|
import cStringIO, os, tarfile, time, zipfile
|
2007-06-11 21:09:01 +04:00
|
|
|
import zlib, gzip
|
2012-09-18 14:46:15 +04:00
|
|
|
import struct
|
2006-04-22 02:27:57 +04:00
|
|
|
|
2012-08-28 01:16:22 +04:00
|
|
|
# from unzip source code:
|
|
|
|
_UNX_IFREG = 0x8000
|
|
|
|
_UNX_IFLNK = 0xa000
|
|
|
|
|
2010-07-14 22:25:31 +04:00
|
|
|
def tidyprefix(dest, kind, prefix):
|
2006-04-22 02:27:57 +04:00
|
|
|
'''choose prefix to use for names in archive. make sure prefix is
|
|
|
|
safe for consumers.'''
|
|
|
|
|
|
|
|
if prefix:
|
2008-01-09 15:30:13 +03:00
|
|
|
prefix = util.normpath(prefix)
|
2006-04-22 02:27:57 +04:00
|
|
|
else:
|
|
|
|
if not isinstance(dest, str):
|
|
|
|
raise ValueError('dest must be string if no prefix')
|
|
|
|
prefix = os.path.basename(dest)
|
|
|
|
lower = prefix.lower()
|
2010-07-14 22:25:31 +04:00
|
|
|
for sfx in exts.get(kind, []):
|
2006-04-22 02:27:57 +04:00
|
|
|
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
|
|
|
|
|
2010-07-14 22:25:31 +04:00
|
|
|
exts = {
|
|
|
|
'tar': ['.tar'],
|
|
|
|
'tbz2': ['.tbz2', '.tar.bz2'],
|
|
|
|
'tgz': ['.tgz', '.tar.gz'],
|
|
|
|
'zip': ['.zip'],
|
|
|
|
}
|
|
|
|
|
|
|
|
def guesskind(dest):
|
|
|
|
for kind, extensions in exts.iteritems():
|
|
|
|
if util.any(dest.endswith(ext) for ext in extensions):
|
|
|
|
return kind
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
2009-06-10 17:10:21 +04:00
|
|
|
class tarit(object):
|
2006-04-22 02:27:57 +04:00
|
|
|
'''write archive to tar file or stream. can write uncompressed,
|
|
|
|
or compress with gzip or bzip2.'''
|
|
|
|
|
2007-06-11 21:09:01 +04:00
|
|
|
class GzipFileWithTime(gzip.GzipFile):
|
|
|
|
|
|
|
|
def __init__(self, *args, **kw):
|
|
|
|
timestamp = None
|
|
|
|
if 'timestamp' in kw:
|
|
|
|
timestamp = kw.pop('timestamp')
|
2009-05-20 02:52:46 +04:00
|
|
|
if timestamp is None:
|
2007-06-11 21:09:01 +04:00
|
|
|
self.timestamp = time.time()
|
|
|
|
else:
|
|
|
|
self.timestamp = timestamp
|
|
|
|
gzip.GzipFile.__init__(self, *args, **kw)
|
|
|
|
|
|
|
|
def _write_gzip_header(self):
|
|
|
|
self.fileobj.write('\037\213') # magic header
|
|
|
|
self.fileobj.write('\010') # compression method
|
2008-04-08 17:42:43 +04:00
|
|
|
# Python 2.6 deprecates self.filename
|
2008-04-08 20:40:11 +04:00
|
|
|
fname = getattr(self, 'name', None) or self.filename
|
2010-12-07 11:47:53 +03:00
|
|
|
if fname and fname.endswith('.gz'):
|
|
|
|
fname = fname[:-3]
|
2007-06-11 21:09:01 +04:00
|
|
|
flags = 0
|
|
|
|
if fname:
|
|
|
|
flags = gzip.FNAME
|
|
|
|
self.fileobj.write(chr(flags))
|
|
|
|
gzip.write32u(self.fileobj, long(self.timestamp))
|
|
|
|
self.fileobj.write('\002')
|
|
|
|
self.fileobj.write('\377')
|
|
|
|
if fname:
|
|
|
|
self.fileobj.write(fname + '\000')
|
|
|
|
|
2010-07-14 22:25:31 +04:00
|
|
|
def __init__(self, dest, mtime, kind=''):
|
2006-06-22 02:31:23 +04:00
|
|
|
self.mtime = mtime
|
2010-12-24 17:23:01 +03:00
|
|
|
self.fileobj = None
|
2007-06-11 21:09:01 +04:00
|
|
|
|
|
|
|
def taropen(name, mode, fileobj=None):
|
|
|
|
if kind == 'gz':
|
|
|
|
mode = mode[0]
|
|
|
|
if not fileobj:
|
2007-06-27 19:35:26 +04:00
|
|
|
fileobj = open(name, mode + 'b')
|
2007-06-11 21:09:01 +04:00
|
|
|
gzfileobj = self.GzipFileWithTime(name, mode + 'b',
|
|
|
|
zlib.Z_BEST_COMPRESSION,
|
|
|
|
fileobj, timestamp=mtime)
|
2010-12-24 17:23:01 +03:00
|
|
|
self.fileobj = gzfileobj
|
2007-06-11 21:09:01 +04:00
|
|
|
return tarfile.TarFile.taropen(name, mode, gzfileobj)
|
|
|
|
else:
|
2010-12-24 17:23:01 +03:00
|
|
|
self.fileobj = fileobj
|
2007-06-11 21:09:01 +04:00
|
|
|
return tarfile.open(name, mode + kind, fileobj)
|
|
|
|
|
2006-04-22 02:27:57 +04:00
|
|
|
if isinstance(dest, str):
|
2007-06-11 21:09:01 +04:00
|
|
|
self.z = taropen(dest, mode='w:')
|
2006-04-22 02:27:57 +04:00
|
|
|
else:
|
2007-04-24 21:44:13 +04:00
|
|
|
# Python 2.5-2.5.1 have a regression that requires a name arg
|
2007-06-11 21:09:01 +04:00
|
|
|
self.z = taropen(name='', mode='w|', fileobj=dest)
|
2006-04-22 02:27:57 +04:00
|
|
|
|
2007-07-12 00:40:41 +04:00
|
|
|
def addfile(self, name, mode, islink, data):
|
2010-07-14 22:25:31 +04:00
|
|
|
i = tarfile.TarInfo(name)
|
2006-04-22 02:27:57 +04:00
|
|
|
i.mtime = self.mtime
|
|
|
|
i.size = len(data)
|
2007-07-12 00:40:41 +04:00
|
|
|
if islink:
|
|
|
|
i.type = tarfile.SYMTYPE
|
|
|
|
i.mode = 0777
|
|
|
|
i.linkname = data
|
|
|
|
data = None
|
2009-02-15 20:14:20 +03:00
|
|
|
i.size = 0
|
2007-07-12 00:40:41 +04:00
|
|
|
else:
|
|
|
|
i.mode = mode
|
|
|
|
data = cStringIO.StringIO(data)
|
|
|
|
self.z.addfile(i, data)
|
2006-04-22 02:27:57 +04:00
|
|
|
|
|
|
|
def done(self):
|
|
|
|
self.z.close()
|
2010-12-24 17:23:01 +03:00
|
|
|
if self.fileobj:
|
|
|
|
self.fileobj.close()
|
2006-04-22 02:27:57 +04:00
|
|
|
|
2009-06-10 17:10:21 +04:00
|
|
|
class tellable(object):
|
2006-04-22 02:27:57 +04:00
|
|
|
'''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
|
|
|
|
|
2009-06-10 17:10:21 +04:00
|
|
|
class zipit(object):
|
2006-04-22 02:27:57 +04:00
|
|
|
'''write archive to zip file or stream. can write uncompressed,
|
|
|
|
or compressed with deflate.'''
|
|
|
|
|
2010-07-14 22:25:31 +04:00
|
|
|
def __init__(self, dest, mtime, compress=True):
|
2006-05-01 00:43:41 +04:00
|
|
|
if not isinstance(dest, str):
|
|
|
|
try:
|
|
|
|
dest.tell()
|
2006-05-01 00:46:54 +04:00
|
|
|
except (AttributeError, IOError):
|
2006-05-01 00:43:41 +04:00
|
|
|
dest = tellable(dest)
|
2006-04-22 02:27:57 +04:00
|
|
|
self.z = zipfile.ZipFile(dest, 'w',
|
|
|
|
compress and zipfile.ZIP_DEFLATED or
|
|
|
|
zipfile.ZIP_STORED)
|
2010-09-20 17:33:39 +04:00
|
|
|
|
|
|
|
# Python's zipfile module emits deprecation warnings if we try
|
|
|
|
# to store files with a date before 1980.
|
|
|
|
epoch = 315532800 # calendar.timegm((1980, 1, 1, 0, 0, 0, 1, 1, 0))
|
|
|
|
if mtime < epoch:
|
|
|
|
mtime = epoch
|
|
|
|
|
2012-09-18 14:46:15 +04:00
|
|
|
self.mtime = mtime
|
2006-06-22 02:31:23 +04:00
|
|
|
self.date_time = time.gmtime(mtime)[:6]
|
2006-04-22 02:27:57 +04:00
|
|
|
|
2007-07-12 00:40:41 +04:00
|
|
|
def addfile(self, name, mode, islink, data):
|
2010-07-14 22:25:31 +04:00
|
|
|
i = zipfile.ZipInfo(name, self.date_time)
|
2006-04-22 02:27:57 +04:00
|
|
|
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
|
2012-08-28 01:16:22 +04:00
|
|
|
ftype = _UNX_IFREG
|
2007-07-12 00:40:41 +04:00
|
|
|
if islink:
|
|
|
|
mode = 0777
|
2012-08-28 01:16:22 +04:00
|
|
|
ftype = _UNX_IFLNK
|
2007-07-12 00:40:41 +04:00
|
|
|
i.external_attr = (mode | ftype) << 16L
|
2012-09-18 14:46:15 +04:00
|
|
|
# add "extended-timestamp" extra block, because zip archives
|
|
|
|
# without this will be extracted with unexpected timestamp,
|
|
|
|
# if TZ is not configured as GMT
|
|
|
|
i.extra += struct.pack('<hhBl',
|
|
|
|
0x5455, # block type: "extended-timestamp"
|
|
|
|
1 + 4, # size of this block
|
|
|
|
1, # "modification time is present"
|
|
|
|
self.mtime) # time of last modification (UTC)
|
2006-04-22 02:27:57 +04:00
|
|
|
self.z.writestr(i, data)
|
|
|
|
|
|
|
|
def done(self):
|
|
|
|
self.z.close()
|
|
|
|
|
2009-06-10 17:10:21 +04:00
|
|
|
class fileit(object):
|
2006-04-22 02:27:57 +04:00
|
|
|
'''write archive as files in directory.'''
|
|
|
|
|
2010-07-14 22:25:31 +04:00
|
|
|
def __init__(self, name, mtime):
|
2006-04-22 02:27:57 +04:00
|
|
|
self.basedir = name
|
2011-04-20 21:54:57 +04:00
|
|
|
self.opener = scmutil.opener(self.basedir)
|
2006-04-22 02:27:57 +04:00
|
|
|
|
2007-07-12 00:40:41 +04:00
|
|
|
def addfile(self, name, mode, islink, data):
|
|
|
|
if islink:
|
|
|
|
self.opener.symlink(data, name)
|
|
|
|
return
|
2007-07-12 00:40:41 +04:00
|
|
|
f = self.opener(name, "w", atomictemp=True)
|
|
|
|
f.write(data)
|
2011-08-26 04:21:04 +04:00
|
|
|
f.close()
|
2006-04-22 02:27:57 +04:00
|
|
|
destfile = os.path.join(self.basedir, name)
|
2007-07-12 00:40:41 +04:00
|
|
|
os.chmod(destfile, mode)
|
2006-04-22 02:27:57 +04:00
|
|
|
|
|
|
|
def done(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
archivers = {
|
|
|
|
'files': fileit,
|
|
|
|
'tar': tarit,
|
2010-07-14 22:25:31 +04:00
|
|
|
'tbz2': lambda name, mtime: tarit(name, mtime, 'bz2'),
|
|
|
|
'tgz': lambda name, mtime: tarit(name, mtime, 'gz'),
|
|
|
|
'uzip': lambda name, mtime: zipit(name, mtime, False),
|
2006-04-22 02:27:57 +04:00
|
|
|
'zip': zipit,
|
|
|
|
}
|
|
|
|
|
|
|
|
def archive(repo, dest, node, kind, decode=True, matchfn=None,
|
2010-09-20 17:46:17 +04:00
|
|
|
prefix=None, mtime=None, subrepos=False):
|
2006-04-22 02:27:57 +04:00
|
|
|
'''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.'''
|
|
|
|
|
2010-07-14 22:25:31 +04:00
|
|
|
if kind == 'files':
|
|
|
|
if prefix:
|
|
|
|
raise util.Abort(_('cannot give prefix when archiving to files'))
|
|
|
|
else:
|
|
|
|
prefix = tidyprefix(dest, kind, prefix)
|
|
|
|
|
2007-07-20 02:43:25 +04:00
|
|
|
def write(name, mode, islink, getdata):
|
|
|
|
data = getdata()
|
2006-04-22 02:27:57 +04:00
|
|
|
if decode:
|
2006-12-30 05:04:31 +03:00
|
|
|
data = repo.wwritedata(name, data)
|
2010-07-14 22:25:31 +04:00
|
|
|
archiver.addfile(prefix + name, mode, islink, data)
|
2006-04-22 02:27:57 +04:00
|
|
|
|
2008-02-05 17:54:42 +03:00
|
|
|
if kind not in archivers:
|
2008-08-16 16:46:56 +04:00
|
|
|
raise util.Abort(_("unknown archive type '%s'") % kind)
|
2008-06-26 23:35:50 +04:00
|
|
|
|
|
|
|
ctx = repo[node]
|
2010-07-14 22:25:31 +04:00
|
|
|
archiver = archivers[kind](dest, mtime or ctx.date()[0])
|
2008-06-26 23:35:50 +04:00
|
|
|
|
2008-02-29 00:39:59 +03:00
|
|
|
if repo.ui.configbool("ui", "archivemeta", True):
|
2009-08-11 11:04:02 +04:00
|
|
|
def metadata():
|
|
|
|
base = 'repo: %s\nnode: %s\nbranch: %s\n' % (
|
2010-11-25 00:56:32 +03:00
|
|
|
repo[0].hex(), hex(node), encoding.fromlocal(ctx.branch()))
|
2009-08-11 11:04:02 +04:00
|
|
|
|
|
|
|
tags = ''.join('tag: %s\n' % t for t in ctx.tags()
|
|
|
|
if repo.tagtype(t) == 'global')
|
|
|
|
if not tags:
|
|
|
|
repo.ui.pushbuffer()
|
|
|
|
opts = {'template': '{latesttag}\n{latesttagdistance}',
|
|
|
|
'style': '', 'patch': None, 'git': None}
|
|
|
|
cmdutil.show_changeset(repo.ui, repo, opts).show(ctx)
|
|
|
|
ltags, dist = repo.ui.popbuffer().split('\n')
|
|
|
|
tags = ''.join('latesttag: %s\n' % t for t in ltags.split(':'))
|
|
|
|
tags += 'latesttagdistance: %s\n' % dist
|
|
|
|
|
|
|
|
return base + tags
|
|
|
|
|
2012-06-12 14:05:52 +04:00
|
|
|
name = '.hg_archival.txt'
|
|
|
|
if not matchfn or matchfn(name):
|
|
|
|
write(name, 0644, False, metadata)
|
2009-08-11 11:04:02 +04:00
|
|
|
|
2012-06-12 14:05:52 +04:00
|
|
|
if matchfn:
|
|
|
|
files = [f for f in ctx.manifest().keys() if matchfn(f)]
|
|
|
|
else:
|
|
|
|
files = ctx.manifest().keys()
|
|
|
|
files.sort()
|
|
|
|
total = len(files)
|
2010-11-29 18:17:05 +03:00
|
|
|
repo.ui.progress(_('archiving'), 0, unit=_('files'), total=total)
|
2012-06-12 14:05:52 +04:00
|
|
|
for i, f in enumerate(files):
|
2008-06-26 23:35:50 +04:00
|
|
|
ff = ctx.flags(f)
|
|
|
|
write(f, 'x' in ff and 0755 or 0644, 'l' in ff, ctx[f].data)
|
2010-11-29 18:17:05 +03:00
|
|
|
repo.ui.progress(_('archiving'), i + 1, item=f,
|
|
|
|
unit=_('files'), total=total)
|
|
|
|
repo.ui.progress(_('archiving'), None)
|
2010-09-20 17:46:17 +04:00
|
|
|
|
|
|
|
if subrepos:
|
|
|
|
for subpath in ctx.substate:
|
|
|
|
sub = ctx.sub(subpath)
|
2012-06-17 06:34:06 +04:00
|
|
|
submatch = matchmod.narrowmatcher(subpath, matchfn)
|
|
|
|
sub.archive(repo.ui, archiver, prefix, submatch)
|
2010-09-20 17:46:17 +04:00
|
|
|
|
2006-04-22 02:27:57 +04:00
|
|
|
archiver.done()
|