##// END OF EJS Templates
archival: use absolute_import
Gregory Szorc -
r25916:c1777ece default
parent child Browse files
Show More
@@ -1,332 +1,345
1 1 # archival.py - revision archival for mercurial
2 2 #
3 3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 from i18n import _
9 import match as matchmod
10 import cmdutil
11 import scmutil, util, encoding
12 import cStringIO, os, tarfile, time, zipfile
13 import zlib, gzip
8 from __future__ import absolute_import
9
10 import cStringIO
11 import gzip
12 import os
14 13 import struct
15 import error
14 import tarfile
15 import time
16 import zipfile
17 import zlib
18
19 from .i18n import _
20
21 from . import (
22 cmdutil,
23 encoding,
24 error,
25 match as matchmod,
26 scmutil,
27 util,
28 )
16 29
17 30 # from unzip source code:
18 31 _UNX_IFREG = 0x8000
19 32 _UNX_IFLNK = 0xa000
20 33
21 34 def tidyprefix(dest, kind, prefix):
22 35 '''choose prefix to use for names in archive. make sure prefix is
23 36 safe for consumers.'''
24 37
25 38 if prefix:
26 39 prefix = util.normpath(prefix)
27 40 else:
28 41 if not isinstance(dest, str):
29 42 raise ValueError('dest must be string if no prefix')
30 43 prefix = os.path.basename(dest)
31 44 lower = prefix.lower()
32 45 for sfx in exts.get(kind, []):
33 46 if lower.endswith(sfx):
34 47 prefix = prefix[:-len(sfx)]
35 48 break
36 49 lpfx = os.path.normpath(util.localpath(prefix))
37 50 prefix = util.pconvert(lpfx)
38 51 if not prefix.endswith('/'):
39 52 prefix += '/'
40 53 # Drop the leading '.' path component if present, so Windows can read the
41 54 # zip files (issue4634)
42 55 if prefix.startswith('./'):
43 56 prefix = prefix[2:]
44 57 if prefix.startswith('../') or os.path.isabs(lpfx) or '/../' in prefix:
45 58 raise util.Abort(_('archive prefix contains illegal components'))
46 59 return prefix
47 60
48 61 exts = {
49 62 'tar': ['.tar'],
50 63 'tbz2': ['.tbz2', '.tar.bz2'],
51 64 'tgz': ['.tgz', '.tar.gz'],
52 65 'zip': ['.zip'],
53 66 }
54 67
55 68 def guesskind(dest):
56 69 for kind, extensions in exts.iteritems():
57 70 if any(dest.endswith(ext) for ext in extensions):
58 71 return kind
59 72 return None
60 73
61 74 def _rootctx(repo):
62 75 # repo[0] may be hidden
63 76 for rev in repo:
64 77 return repo[rev]
65 78 return repo['null']
66 79
67 80 def buildmetadata(ctx):
68 81 '''build content of .hg_archival.txt'''
69 82 repo = ctx.repo()
70 83 hex = ctx.hex()
71 84 if ctx.rev() is None:
72 85 hex = ctx.p1().hex()
73 86 if ctx.dirty():
74 87 hex += '+'
75 88
76 89 base = 'repo: %s\nnode: %s\nbranch: %s\n' % (
77 90 _rootctx(repo).hex(), hex, encoding.fromlocal(ctx.branch()))
78 91
79 92 tags = ''.join('tag: %s\n' % t for t in ctx.tags()
80 93 if repo.tagtype(t) == 'global')
81 94 if not tags:
82 95 repo.ui.pushbuffer()
83 96 opts = {'template': '{latesttag}\n{latesttagdistance}\n'
84 97 '{changessincelatesttag}',
85 98 'style': '', 'patch': None, 'git': None}
86 99 cmdutil.show_changeset(repo.ui, repo, opts).show(ctx)
87 100 ltags, dist, changessince = repo.ui.popbuffer().split('\n')
88 101 ltags = ltags.split(':')
89 102 tags = ''.join('latesttag: %s\n' % t for t in ltags)
90 103 tags += 'latesttagdistance: %s\n' % dist
91 104 tags += 'changessincelatesttag: %s\n' % changessince
92 105
93 106 return base + tags
94 107
95 108 class tarit(object):
96 109 '''write archive to tar file or stream. can write uncompressed,
97 110 or compress with gzip or bzip2.'''
98 111
99 112 class GzipFileWithTime(gzip.GzipFile):
100 113
101 114 def __init__(self, *args, **kw):
102 115 timestamp = None
103 116 if 'timestamp' in kw:
104 117 timestamp = kw.pop('timestamp')
105 118 if timestamp is None:
106 119 self.timestamp = time.time()
107 120 else:
108 121 self.timestamp = timestamp
109 122 gzip.GzipFile.__init__(self, *args, **kw)
110 123
111 124 def _write_gzip_header(self):
112 125 self.fileobj.write('\037\213') # magic header
113 126 self.fileobj.write('\010') # compression method
114 127 # Python 2.6 introduced self.name and deprecated self.filename
115 128 try:
116 129 fname = self.name
117 130 except AttributeError:
118 131 fname = self.filename
119 132 if fname and fname.endswith('.gz'):
120 133 fname = fname[:-3]
121 134 flags = 0
122 135 if fname:
123 136 flags = gzip.FNAME
124 137 self.fileobj.write(chr(flags))
125 138 gzip.write32u(self.fileobj, long(self.timestamp))
126 139 self.fileobj.write('\002')
127 140 self.fileobj.write('\377')
128 141 if fname:
129 142 self.fileobj.write(fname + '\000')
130 143
131 144 def __init__(self, dest, mtime, kind=''):
132 145 self.mtime = mtime
133 146 self.fileobj = None
134 147
135 148 def taropen(name, mode, fileobj=None):
136 149 if kind == 'gz':
137 150 mode = mode[0]
138 151 if not fileobj:
139 152 fileobj = open(name, mode + 'b')
140 153 gzfileobj = self.GzipFileWithTime(name, mode + 'b',
141 154 zlib.Z_BEST_COMPRESSION,
142 155 fileobj, timestamp=mtime)
143 156 self.fileobj = gzfileobj
144 157 return tarfile.TarFile.taropen(name, mode, gzfileobj)
145 158 else:
146 159 return tarfile.open(name, mode + kind, fileobj)
147 160
148 161 if isinstance(dest, str):
149 162 self.z = taropen(dest, mode='w:')
150 163 else:
151 164 # Python 2.5-2.5.1 have a regression that requires a name arg
152 165 self.z = taropen(name='', mode='w|', fileobj=dest)
153 166
154 167 def addfile(self, name, mode, islink, data):
155 168 i = tarfile.TarInfo(name)
156 169 i.mtime = self.mtime
157 170 i.size = len(data)
158 171 if islink:
159 172 i.type = tarfile.SYMTYPE
160 173 i.mode = 0o777
161 174 i.linkname = data
162 175 data = None
163 176 i.size = 0
164 177 else:
165 178 i.mode = mode
166 179 data = cStringIO.StringIO(data)
167 180 self.z.addfile(i, data)
168 181
169 182 def done(self):
170 183 self.z.close()
171 184 if self.fileobj:
172 185 self.fileobj.close()
173 186
174 187 class tellable(object):
175 188 '''provide tell method for zipfile.ZipFile when writing to http
176 189 response file object.'''
177 190
178 191 def __init__(self, fp):
179 192 self.fp = fp
180 193 self.offset = 0
181 194
182 195 def __getattr__(self, key):
183 196 return getattr(self.fp, key)
184 197
185 198 def write(self, s):
186 199 self.fp.write(s)
187 200 self.offset += len(s)
188 201
189 202 def tell(self):
190 203 return self.offset
191 204
192 205 class zipit(object):
193 206 '''write archive to zip file or stream. can write uncompressed,
194 207 or compressed with deflate.'''
195 208
196 209 def __init__(self, dest, mtime, compress=True):
197 210 if not isinstance(dest, str):
198 211 try:
199 212 dest.tell()
200 213 except (AttributeError, IOError):
201 214 dest = tellable(dest)
202 215 self.z = zipfile.ZipFile(dest, 'w',
203 216 compress and zipfile.ZIP_DEFLATED or
204 217 zipfile.ZIP_STORED)
205 218
206 219 # Python's zipfile module emits deprecation warnings if we try
207 220 # to store files with a date before 1980.
208 221 epoch = 315532800 # calendar.timegm((1980, 1, 1, 0, 0, 0, 1, 1, 0))
209 222 if mtime < epoch:
210 223 mtime = epoch
211 224
212 225 self.mtime = mtime
213 226 self.date_time = time.gmtime(mtime)[:6]
214 227
215 228 def addfile(self, name, mode, islink, data):
216 229 i = zipfile.ZipInfo(name, self.date_time)
217 230 i.compress_type = self.z.compression
218 231 # unzip will not honor unix file modes unless file creator is
219 232 # set to unix (id 3).
220 233 i.create_system = 3
221 234 ftype = _UNX_IFREG
222 235 if islink:
223 236 mode = 0o777
224 237 ftype = _UNX_IFLNK
225 238 i.external_attr = (mode | ftype) << 16L
226 239 # add "extended-timestamp" extra block, because zip archives
227 240 # without this will be extracted with unexpected timestamp,
228 241 # if TZ is not configured as GMT
229 242 i.extra += struct.pack('<hhBl',
230 243 0x5455, # block type: "extended-timestamp"
231 244 1 + 4, # size of this block
232 245 1, # "modification time is present"
233 246 int(self.mtime)) # last modification (UTC)
234 247 self.z.writestr(i, data)
235 248
236 249 def done(self):
237 250 self.z.close()
238 251
239 252 class fileit(object):
240 253 '''write archive as files in directory.'''
241 254
242 255 def __init__(self, name, mtime):
243 256 self.basedir = name
244 257 self.opener = scmutil.opener(self.basedir)
245 258
246 259 def addfile(self, name, mode, islink, data):
247 260 if islink:
248 261 self.opener.symlink(data, name)
249 262 return
250 263 f = self.opener(name, "w", atomictemp=True)
251 264 f.write(data)
252 265 f.close()
253 266 destfile = os.path.join(self.basedir, name)
254 267 os.chmod(destfile, mode)
255 268
256 269 def done(self):
257 270 pass
258 271
259 272 archivers = {
260 273 'files': fileit,
261 274 'tar': tarit,
262 275 'tbz2': lambda name, mtime: tarit(name, mtime, 'bz2'),
263 276 'tgz': lambda name, mtime: tarit(name, mtime, 'gz'),
264 277 'uzip': lambda name, mtime: zipit(name, mtime, False),
265 278 'zip': zipit,
266 279 }
267 280
268 281 def archive(repo, dest, node, kind, decode=True, matchfn=None,
269 282 prefix='', mtime=None, subrepos=False):
270 283 '''create archive of repo as it was at node.
271 284
272 285 dest can be name of directory, name of archive file, or file
273 286 object to write archive to.
274 287
275 288 kind is type of archive to create.
276 289
277 290 decode tells whether to put files through decode filters from
278 291 hgrc.
279 292
280 293 matchfn is function to filter names of files to write to archive.
281 294
282 295 prefix is name of path to put before every archive member.'''
283 296
284 297 if kind == 'files':
285 298 if prefix:
286 299 raise util.Abort(_('cannot give prefix when archiving to files'))
287 300 else:
288 301 prefix = tidyprefix(dest, kind, prefix)
289 302
290 303 def write(name, mode, islink, getdata):
291 304 data = getdata()
292 305 if decode:
293 306 data = repo.wwritedata(name, data)
294 307 archiver.addfile(prefix + name, mode, islink, data)
295 308
296 309 if kind not in archivers:
297 310 raise util.Abort(_("unknown archive type '%s'") % kind)
298 311
299 312 ctx = repo[node]
300 313 archiver = archivers[kind](dest, mtime or ctx.date()[0])
301 314
302 315 if repo.ui.configbool("ui", "archivemeta", True):
303 316 name = '.hg_archival.txt'
304 317 if not matchfn or matchfn(name):
305 318 write(name, 0o644, False, lambda: buildmetadata(ctx))
306 319
307 320 if matchfn:
308 321 files = [f for f in ctx.manifest().keys() if matchfn(f)]
309 322 else:
310 323 files = ctx.manifest().keys()
311 324 total = len(files)
312 325 if total:
313 326 files.sort()
314 327 repo.ui.progress(_('archiving'), 0, unit=_('files'), total=total)
315 328 for i, f in enumerate(files):
316 329 ff = ctx.flags(f)
317 330 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, ctx[f].data)
318 331 repo.ui.progress(_('archiving'), i + 1, item=f,
319 332 unit=_('files'), total=total)
320 333 repo.ui.progress(_('archiving'), None)
321 334
322 335 if subrepos:
323 336 for subpath in sorted(ctx.substate):
324 337 sub = ctx.workingsub(subpath)
325 338 submatch = matchmod.narrowmatcher(subpath, matchfn)
326 339 total += sub.archive(archiver, prefix, submatch)
327 340
328 341 if total == 0:
329 342 raise error.Abort(_('no files match the archive pattern'))
330 343
331 344 archiver.done()
332 345 return total
General Comments 0
You need to be logged in to leave comments. Login now