eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/mercurial/archival.py
changeset 69 c6bca38c1cbf
equal deleted inserted replaced
68:5ff1fc726848 69:c6bca38c1cbf
       
     1 # archival.py - revision archival for mercurial
       
     2 #
       
     3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
       
     4 #
       
     5 # This software may be used and distributed according to the terms of the
       
     6 # GNU General Public License version 2 or any later version.
       
     7 
       
     8 from i18n import _
       
     9 from node import hex
       
    10 import cmdutil
       
    11 import util
       
    12 import cStringIO, os, stat, tarfile, time, zipfile
       
    13 import zlib, gzip
       
    14 
       
    15 def tidyprefix(dest, kind, prefix):
       
    16     '''choose prefix to use for names in archive.  make sure prefix is
       
    17     safe for consumers.'''
       
    18 
       
    19     if prefix:
       
    20         prefix = util.normpath(prefix)
       
    21     else:
       
    22         if not isinstance(dest, str):
       
    23             raise ValueError('dest must be string if no prefix')
       
    24         prefix = os.path.basename(dest)
       
    25         lower = prefix.lower()
       
    26         for sfx in exts.get(kind, []):
       
    27             if lower.endswith(sfx):
       
    28                 prefix = prefix[:-len(sfx)]
       
    29                 break
       
    30     lpfx = os.path.normpath(util.localpath(prefix))
       
    31     prefix = util.pconvert(lpfx)
       
    32     if not prefix.endswith('/'):
       
    33         prefix += '/'
       
    34     if prefix.startswith('../') or os.path.isabs(lpfx) or '/../' in prefix:
       
    35         raise util.Abort(_('archive prefix contains illegal components'))
       
    36     return prefix
       
    37 
       
    38 exts = {
       
    39     'tar': ['.tar'],
       
    40     'tbz2': ['.tbz2', '.tar.bz2'],
       
    41     'tgz': ['.tgz', '.tar.gz'],
       
    42     'zip': ['.zip'],
       
    43     }
       
    44 
       
    45 def guesskind(dest):
       
    46     for kind, extensions in exts.iteritems():
       
    47         if util.any(dest.endswith(ext) for ext in extensions):
       
    48             return kind
       
    49     return None
       
    50 
       
    51 
       
    52 class tarit(object):
       
    53     '''write archive to tar file or stream.  can write uncompressed,
       
    54     or compress with gzip or bzip2.'''
       
    55 
       
    56     class GzipFileWithTime(gzip.GzipFile):
       
    57 
       
    58         def __init__(self, *args, **kw):
       
    59             timestamp = None
       
    60             if 'timestamp' in kw:
       
    61                 timestamp = kw.pop('timestamp')
       
    62             if timestamp is None:
       
    63                 self.timestamp = time.time()
       
    64             else:
       
    65                 self.timestamp = timestamp
       
    66             gzip.GzipFile.__init__(self, *args, **kw)
       
    67 
       
    68         def _write_gzip_header(self):
       
    69             self.fileobj.write('\037\213')             # magic header
       
    70             self.fileobj.write('\010')                 # compression method
       
    71             # Python 2.6 deprecates self.filename
       
    72             fname = getattr(self, 'name', None) or self.filename
       
    73             if fname and fname.endswith('.gz'):
       
    74                 fname = fname[:-3]
       
    75             flags = 0
       
    76             if fname:
       
    77                 flags = gzip.FNAME
       
    78             self.fileobj.write(chr(flags))
       
    79             gzip.write32u(self.fileobj, long(self.timestamp))
       
    80             self.fileobj.write('\002')
       
    81             self.fileobj.write('\377')
       
    82             if fname:
       
    83                 self.fileobj.write(fname + '\000')
       
    84 
       
    85     def __init__(self, dest, mtime, kind=''):
       
    86         self.mtime = mtime
       
    87 
       
    88         def taropen(name, mode, fileobj=None):
       
    89             if kind == 'gz':
       
    90                 mode = mode[0]
       
    91                 if not fileobj:
       
    92                     fileobj = open(name, mode + 'b')
       
    93                 gzfileobj = self.GzipFileWithTime(name, mode + 'b',
       
    94                                                   zlib.Z_BEST_COMPRESSION,
       
    95                                                   fileobj, timestamp=mtime)
       
    96                 return tarfile.TarFile.taropen(name, mode, gzfileobj)
       
    97             else:
       
    98                 return tarfile.open(name, mode + kind, fileobj)
       
    99 
       
   100         if isinstance(dest, str):
       
   101             self.z = taropen(dest, mode='w:')
       
   102         else:
       
   103             # Python 2.5-2.5.1 have a regression that requires a name arg
       
   104             self.z = taropen(name='', mode='w|', fileobj=dest)
       
   105 
       
   106     def addfile(self, name, mode, islink, data):
       
   107         i = tarfile.TarInfo(name)
       
   108         i.mtime = self.mtime
       
   109         i.size = len(data)
       
   110         if islink:
       
   111             i.type = tarfile.SYMTYPE
       
   112             i.mode = 0777
       
   113             i.linkname = data
       
   114             data = None
       
   115             i.size = 0
       
   116         else:
       
   117             i.mode = mode
       
   118             data = cStringIO.StringIO(data)
       
   119         self.z.addfile(i, data)
       
   120 
       
   121     def done(self):
       
   122         self.z.close()
       
   123 
       
   124 class tellable(object):
       
   125     '''provide tell method for zipfile.ZipFile when writing to http
       
   126     response file object.'''
       
   127 
       
   128     def __init__(self, fp):
       
   129         self.fp = fp
       
   130         self.offset = 0
       
   131 
       
   132     def __getattr__(self, key):
       
   133         return getattr(self.fp, key)
       
   134 
       
   135     def write(self, s):
       
   136         self.fp.write(s)
       
   137         self.offset += len(s)
       
   138 
       
   139     def tell(self):
       
   140         return self.offset
       
   141 
       
   142 class zipit(object):
       
   143     '''write archive to zip file or stream.  can write uncompressed,
       
   144     or compressed with deflate.'''
       
   145 
       
   146     def __init__(self, dest, mtime, compress=True):
       
   147         if not isinstance(dest, str):
       
   148             try:
       
   149                 dest.tell()
       
   150             except (AttributeError, IOError):
       
   151                 dest = tellable(dest)
       
   152         self.z = zipfile.ZipFile(dest, 'w',
       
   153                                  compress and zipfile.ZIP_DEFLATED or
       
   154                                  zipfile.ZIP_STORED)
       
   155 
       
   156         # Python's zipfile module emits deprecation warnings if we try
       
   157         # to store files with a date before 1980.
       
   158         epoch = 315532800 # calendar.timegm((1980, 1, 1, 0, 0, 0, 1, 1, 0))
       
   159         if mtime < epoch:
       
   160             mtime = epoch
       
   161 
       
   162         self.date_time = time.gmtime(mtime)[:6]
       
   163 
       
   164     def addfile(self, name, mode, islink, data):
       
   165         i = zipfile.ZipInfo(name, self.date_time)
       
   166         i.compress_type = self.z.compression
       
   167         # unzip will not honor unix file modes unless file creator is
       
   168         # set to unix (id 3).
       
   169         i.create_system = 3
       
   170         ftype = stat.S_IFREG
       
   171         if islink:
       
   172             mode = 0777
       
   173             ftype = stat.S_IFLNK
       
   174         i.external_attr = (mode | ftype) << 16L
       
   175         self.z.writestr(i, data)
       
   176 
       
   177     def done(self):
       
   178         self.z.close()
       
   179 
       
   180 class fileit(object):
       
   181     '''write archive as files in directory.'''
       
   182 
       
   183     def __init__(self, name, mtime):
       
   184         self.basedir = name
       
   185         self.opener = util.opener(self.basedir)
       
   186 
       
   187     def addfile(self, name, mode, islink, data):
       
   188         if islink:
       
   189             self.opener.symlink(data, name)
       
   190             return
       
   191         f = self.opener(name, "w", atomictemp=True)
       
   192         f.write(data)
       
   193         f.rename()
       
   194         destfile = os.path.join(self.basedir, name)
       
   195         os.chmod(destfile, mode)
       
   196 
       
   197     def done(self):
       
   198         pass
       
   199 
       
   200 archivers = {
       
   201     'files': fileit,
       
   202     'tar': tarit,
       
   203     'tbz2': lambda name, mtime: tarit(name, mtime, 'bz2'),
       
   204     'tgz': lambda name, mtime: tarit(name, mtime, 'gz'),
       
   205     'uzip': lambda name, mtime: zipit(name, mtime, False),
       
   206     'zip': zipit,
       
   207     }
       
   208 
       
   209 def archive(repo, dest, node, kind, decode=True, matchfn=None,
       
   210             prefix=None, mtime=None, subrepos=False):
       
   211     '''create archive of repo as it was at node.
       
   212 
       
   213     dest can be name of directory, name of archive file, or file
       
   214     object to write archive to.
       
   215 
       
   216     kind is type of archive to create.
       
   217 
       
   218     decode tells whether to put files through decode filters from
       
   219     hgrc.
       
   220 
       
   221     matchfn is function to filter names of files to write to archive.
       
   222 
       
   223     prefix is name of path to put before every archive member.'''
       
   224 
       
   225     if kind == 'files':
       
   226         if prefix:
       
   227             raise util.Abort(_('cannot give prefix when archiving to files'))
       
   228     else:
       
   229         prefix = tidyprefix(dest, kind, prefix)
       
   230 
       
   231     def write(name, mode, islink, getdata):
       
   232         if matchfn and not matchfn(name):
       
   233             return
       
   234         data = getdata()
       
   235         if decode:
       
   236             data = repo.wwritedata(name, data)
       
   237         archiver.addfile(prefix + name, mode, islink, data)
       
   238 
       
   239     if kind not in archivers:
       
   240         raise util.Abort(_("unknown archive type '%s'") % kind)
       
   241 
       
   242     ctx = repo[node]
       
   243     archiver = archivers[kind](dest, mtime or ctx.date()[0])
       
   244 
       
   245     if repo.ui.configbool("ui", "archivemeta", True):
       
   246         def metadata():
       
   247             base = 'repo: %s\nnode: %s\nbranch: %s\n' % (
       
   248                 repo[0].hex(), hex(node), ctx.branch())
       
   249 
       
   250             tags = ''.join('tag: %s\n' % t for t in ctx.tags()
       
   251                            if repo.tagtype(t) == 'global')
       
   252             if not tags:
       
   253                 repo.ui.pushbuffer()
       
   254                 opts = {'template': '{latesttag}\n{latesttagdistance}',
       
   255                         'style': '', 'patch': None, 'git': None}
       
   256                 cmdutil.show_changeset(repo.ui, repo, opts).show(ctx)
       
   257                 ltags, dist = repo.ui.popbuffer().split('\n')
       
   258                 tags = ''.join('latesttag: %s\n' % t for t in ltags.split(':'))
       
   259                 tags += 'latesttagdistance: %s\n' % dist
       
   260 
       
   261             return base + tags
       
   262 
       
   263         write('.hg_archival.txt', 0644, False, metadata)
       
   264 
       
   265     for f in ctx:
       
   266         ff = ctx.flags(f)
       
   267         write(f, 'x' in ff and 0755 or 0644, 'l' in ff, ctx[f].data)
       
   268 
       
   269     if subrepos:
       
   270         for subpath in ctx.substate:
       
   271             sub = ctx.sub(subpath)
       
   272             sub.archive(archiver, prefix)
       
   273 
       
   274     archiver.done()