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