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