##// END OF EJS Templates
py3: use raw string for file open mode...
Gregory Szorc -
r36053:788b2e85 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 cmdutil,
22 22 error,
23 23 formatter,
24 24 match as matchmod,
25 25 util,
26 26 vfs as vfsmod,
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 # {tags} on ctx includes local tags and 'tip', with no current way to limit
81 81 # that to global tags. Therefore, use {latesttag} as a substitute when
82 82 # the distance is 0, since that will be the list of global tags on ctx.
83 83 _defaultmetatemplate = br'''
84 84 repo: {root}
85 85 node: {ifcontains(rev, revset("wdir()"), "{p1node}{dirty}", "{node}")}
86 86 branch: {branch|utf8}
87 87 {ifeq(latesttagdistance, 0, join(latesttag % "tag: {tag}", "\n"),
88 88 separate("\n",
89 89 join(latesttag % "latesttag: {tag}", "\n"),
90 90 "latesttagdistance: {latesttagdistance}",
91 91 "changessincelatesttag: {changessincelatesttag}"))}
92 92 '''[1:] # drop leading '\n'
93 93
94 94 def buildmetadata(ctx):
95 95 '''build content of .hg_archival.txt'''
96 96 repo = ctx.repo()
97 97
98 98 opts = {
99 99 'template': repo.ui.config('experimental', 'archivemetatemplate',
100 100 _defaultmetatemplate)
101 101 }
102 102
103 103 out = util.stringio()
104 104
105 105 fm = formatter.formatter(repo.ui, out, 'archive', opts)
106 106 fm.startitem()
107 107 fm.context(ctx=ctx)
108 108 fm.data(root=_rootctx(repo).hex())
109 109
110 110 if ctx.rev() is None:
111 111 dirty = ''
112 112 if ctx.dirty(missing=True):
113 113 dirty = '+'
114 114 fm.data(dirty=dirty)
115 115 fm.end()
116 116
117 117 return out.getvalue()
118 118
119 119 class tarit(object):
120 120 '''write archive to tar file or stream. can write uncompressed,
121 121 or compress with gzip or bzip2.'''
122 122
123 123 class GzipFileWithTime(gzip.GzipFile):
124 124
125 125 def __init__(self, *args, **kw):
126 126 timestamp = None
127 127 if 'timestamp' in kw:
128 128 timestamp = kw.pop(r'timestamp')
129 129 if timestamp is None:
130 130 self.timestamp = time.time()
131 131 else:
132 132 self.timestamp = timestamp
133 133 gzip.GzipFile.__init__(self, *args, **kw)
134 134
135 135 def _write_gzip_header(self):
136 136 self.fileobj.write('\037\213') # magic header
137 137 self.fileobj.write('\010') # compression method
138 138 fname = self.name
139 139 if fname and fname.endswith('.gz'):
140 140 fname = fname[:-3]
141 141 flags = 0
142 142 if fname:
143 143 flags = gzip.FNAME
144 144 self.fileobj.write(chr(flags))
145 145 gzip.write32u(self.fileobj, long(self.timestamp))
146 146 self.fileobj.write('\002')
147 147 self.fileobj.write('\377')
148 148 if fname:
149 149 self.fileobj.write(fname + '\000')
150 150
151 151 def __init__(self, dest, mtime, kind=''):
152 152 self.mtime = mtime
153 153 self.fileobj = None
154 154
155 155 def taropen(mode, name='', fileobj=None):
156 156 if kind == 'gz':
157 157 mode = mode[0]
158 158 if not fileobj:
159 159 fileobj = open(name, mode + 'b')
160 160 gzfileobj = self.GzipFileWithTime(name, mode + 'b',
161 161 zlib.Z_BEST_COMPRESSION,
162 162 fileobj, timestamp=mtime)
163 163 self.fileobj = gzfileobj
164 164 return tarfile.TarFile.taropen(name, mode, gzfileobj)
165 165 else:
166 166 return tarfile.open(name, mode + kind, fileobj)
167 167
168 168 if isinstance(dest, str):
169 169 self.z = taropen('w:', name=dest)
170 170 else:
171 171 self.z = taropen('w|', fileobj=dest)
172 172
173 173 def addfile(self, name, mode, islink, data):
174 174 i = tarfile.TarInfo(name)
175 175 i.mtime = self.mtime
176 176 i.size = len(data)
177 177 if islink:
178 178 i.type = tarfile.SYMTYPE
179 179 i.mode = 0o777
180 180 i.linkname = data
181 181 data = None
182 182 i.size = 0
183 183 else:
184 184 i.mode = mode
185 185 data = stringio(data)
186 186 self.z.addfile(i, data)
187 187
188 188 def done(self):
189 189 self.z.close()
190 190 if self.fileobj:
191 191 self.fileobj.close()
192 192
193 193 class tellable(object):
194 194 '''provide tell method for zipfile.ZipFile when writing to http
195 195 response file object.'''
196 196
197 197 def __init__(self, fp):
198 198 self.fp = fp
199 199 self.offset = 0
200 200
201 201 def __getattr__(self, key):
202 202 return getattr(self.fp, key)
203 203
204 204 def write(self, s):
205 205 self.fp.write(s)
206 206 self.offset += len(s)
207 207
208 208 def tell(self):
209 209 return self.offset
210 210
211 211 class zipit(object):
212 212 '''write archive to zip file or stream. can write uncompressed,
213 213 or compressed with deflate.'''
214 214
215 215 def __init__(self, dest, mtime, compress=True):
216 216 if not isinstance(dest, str):
217 217 try:
218 218 dest.tell()
219 219 except (AttributeError, IOError):
220 220 dest = tellable(dest)
221 self.z = zipfile.ZipFile(dest, 'w',
221 self.z = zipfile.ZipFile(dest, r'w',
222 222 compress and zipfile.ZIP_DEFLATED or
223 223 zipfile.ZIP_STORED)
224 224
225 225 # Python's zipfile module emits deprecation warnings if we try
226 226 # to store files with a date before 1980.
227 227 epoch = 315532800 # calendar.timegm((1980, 1, 1, 0, 0, 0, 1, 1, 0))
228 228 if mtime < epoch:
229 229 mtime = epoch
230 230
231 231 self.mtime = mtime
232 232 self.date_time = time.gmtime(mtime)[:6]
233 233
234 234 def addfile(self, name, mode, islink, data):
235 235 i = zipfile.ZipInfo(name, self.date_time)
236 236 i.compress_type = self.z.compression
237 237 # unzip will not honor unix file modes unless file creator is
238 238 # set to unix (id 3).
239 239 i.create_system = 3
240 240 ftype = _UNX_IFREG
241 241 if islink:
242 242 mode = 0o777
243 243 ftype = _UNX_IFLNK
244 244 i.external_attr = (mode | ftype) << 16
245 245 # add "extended-timestamp" extra block, because zip archives
246 246 # without this will be extracted with unexpected timestamp,
247 247 # if TZ is not configured as GMT
248 248 i.extra += struct.pack('<hhBl',
249 249 0x5455, # block type: "extended-timestamp"
250 250 1 + 4, # size of this block
251 251 1, # "modification time is present"
252 252 int(self.mtime)) # last modification (UTC)
253 253 self.z.writestr(i, data)
254 254
255 255 def done(self):
256 256 self.z.close()
257 257
258 258 class fileit(object):
259 259 '''write archive as files in directory.'''
260 260
261 261 def __init__(self, name, mtime):
262 262 self.basedir = name
263 263 self.opener = vfsmod.vfs(self.basedir)
264 264 self.mtime = mtime
265 265
266 266 def addfile(self, name, mode, islink, data):
267 267 if islink:
268 268 self.opener.symlink(data, name)
269 269 return
270 270 f = self.opener(name, "w", atomictemp=True)
271 271 f.write(data)
272 272 f.close()
273 273 destfile = os.path.join(self.basedir, name)
274 274 os.chmod(destfile, mode)
275 275 if self.mtime is not None:
276 276 os.utime(destfile, (self.mtime, self.mtime))
277 277
278 278 def done(self):
279 279 pass
280 280
281 281 archivers = {
282 282 'files': fileit,
283 283 'tar': tarit,
284 284 'tbz2': lambda name, mtime: tarit(name, mtime, 'bz2'),
285 285 'tgz': lambda name, mtime: tarit(name, mtime, 'gz'),
286 286 'uzip': lambda name, mtime: zipit(name, mtime, False),
287 287 'zip': zipit,
288 288 }
289 289
290 290 def archive(repo, dest, node, kind, decode=True, matchfn=None,
291 291 prefix='', mtime=None, subrepos=False):
292 292 '''create archive of repo as it was at node.
293 293
294 294 dest can be name of directory, name of archive file, or file
295 295 object to write archive to.
296 296
297 297 kind is type of archive to create.
298 298
299 299 decode tells whether to put files through decode filters from
300 300 hgrc.
301 301
302 302 matchfn is function to filter names of files to write to archive.
303 303
304 304 prefix is name of path to put before every archive member.
305 305
306 306 mtime is the modified time, in seconds, or None to use the changeset time.
307 307
308 308 subrepos tells whether to include subrepos.
309 309 '''
310 310
311 311 if kind == 'files':
312 312 if prefix:
313 313 raise error.Abort(_('cannot give prefix when archiving to files'))
314 314 else:
315 315 prefix = tidyprefix(dest, kind, prefix)
316 316
317 317 def write(name, mode, islink, getdata):
318 318 data = getdata()
319 319 if decode:
320 320 data = repo.wwritedata(name, data)
321 321 archiver.addfile(prefix + name, mode, islink, data)
322 322
323 323 if kind not in archivers:
324 324 raise error.Abort(_("unknown archive type '%s'") % kind)
325 325
326 326 ctx = repo[node]
327 327 archiver = archivers[kind](dest, mtime or ctx.date()[0])
328 328
329 329 if repo.ui.configbool("ui", "archivemeta"):
330 330 name = '.hg_archival.txt'
331 331 if not matchfn or matchfn(name):
332 332 write(name, 0o644, False, lambda: buildmetadata(ctx))
333 333
334 334 if matchfn:
335 335 files = [f for f in ctx.manifest().keys() if matchfn(f)]
336 336 else:
337 337 files = ctx.manifest().keys()
338 338 total = len(files)
339 339 if total:
340 340 files.sort()
341 341 cmdutil._prefetchfiles(repo, ctx, files)
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