##// END OF EJS Templates
vfs: use 'vfs' module directly in 'mercurial.archival'...
Pierre-Yves David -
r31235:7feab0e7 default
parent child Browse files
Show More
@@ -1,340 +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 scmutil,
26 25 util,
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 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 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 158 self.z = taropen('w:', name=dest)
159 159 else:
160 160 self.z = taropen('w|', fileobj=dest)
161 161
162 162 def addfile(self, name, mode, islink, data):
163 163 i = tarfile.TarInfo(name)
164 164 i.mtime = self.mtime
165 165 i.size = len(data)
166 166 if islink:
167 167 i.type = tarfile.SYMTYPE
168 168 i.mode = 0o777
169 169 i.linkname = data
170 170 data = None
171 171 i.size = 0
172 172 else:
173 173 i.mode = mode
174 174 data = stringio(data)
175 175 self.z.addfile(i, data)
176 176
177 177 def done(self):
178 178 self.z.close()
179 179 if self.fileobj:
180 180 self.fileobj.close()
181 181
182 182 class tellable(object):
183 183 '''provide tell method for zipfile.ZipFile when writing to http
184 184 response file object.'''
185 185
186 186 def __init__(self, fp):
187 187 self.fp = fp
188 188 self.offset = 0
189 189
190 190 def __getattr__(self, key):
191 191 return getattr(self.fp, key)
192 192
193 193 def write(self, s):
194 194 self.fp.write(s)
195 195 self.offset += len(s)
196 196
197 197 def tell(self):
198 198 return self.offset
199 199
200 200 class zipit(object):
201 201 '''write archive to zip file or stream. can write uncompressed,
202 202 or compressed with deflate.'''
203 203
204 204 def __init__(self, dest, mtime, compress=True):
205 205 if not isinstance(dest, str):
206 206 try:
207 207 dest.tell()
208 208 except (AttributeError, IOError):
209 209 dest = tellable(dest)
210 210 self.z = zipfile.ZipFile(dest, 'w',
211 211 compress and zipfile.ZIP_DEFLATED or
212 212 zipfile.ZIP_STORED)
213 213
214 214 # Python's zipfile module emits deprecation warnings if we try
215 215 # to store files with a date before 1980.
216 216 epoch = 315532800 # calendar.timegm((1980, 1, 1, 0, 0, 0, 1, 1, 0))
217 217 if mtime < epoch:
218 218 mtime = epoch
219 219
220 220 self.mtime = mtime
221 221 self.date_time = time.gmtime(mtime)[:6]
222 222
223 223 def addfile(self, name, mode, islink, data):
224 224 i = zipfile.ZipInfo(name, self.date_time)
225 225 i.compress_type = self.z.compression
226 226 # unzip will not honor unix file modes unless file creator is
227 227 # set to unix (id 3).
228 228 i.create_system = 3
229 229 ftype = _UNX_IFREG
230 230 if islink:
231 231 mode = 0o777
232 232 ftype = _UNX_IFLNK
233 233 i.external_attr = (mode | ftype) << 16
234 234 # add "extended-timestamp" extra block, because zip archives
235 235 # without this will be extracted with unexpected timestamp,
236 236 # if TZ is not configured as GMT
237 237 i.extra += struct.pack('<hhBl',
238 238 0x5455, # block type: "extended-timestamp"
239 239 1 + 4, # size of this block
240 240 1, # "modification time is present"
241 241 int(self.mtime)) # last modification (UTC)
242 242 self.z.writestr(i, data)
243 243
244 244 def done(self):
245 245 self.z.close()
246 246
247 247 class fileit(object):
248 248 '''write archive as files in directory.'''
249 249
250 250 def __init__(self, name, mtime):
251 251 self.basedir = name
252 self.opener = scmutil.vfs(self.basedir)
252 self.opener = vfsmod.vfs(self.basedir)
253 253
254 254 def addfile(self, name, mode, islink, data):
255 255 if islink:
256 256 self.opener.symlink(data, name)
257 257 return
258 258 f = self.opener(name, "w", atomictemp=True)
259 259 f.write(data)
260 260 f.close()
261 261 destfile = os.path.join(self.basedir, name)
262 262 os.chmod(destfile, mode)
263 263
264 264 def done(self):
265 265 pass
266 266
267 267 archivers = {
268 268 'files': fileit,
269 269 'tar': tarit,
270 270 'tbz2': lambda name, mtime: tarit(name, mtime, 'bz2'),
271 271 'tgz': lambda name, mtime: tarit(name, mtime, 'gz'),
272 272 'uzip': lambda name, mtime: zipit(name, mtime, False),
273 273 'zip': zipit,
274 274 }
275 275
276 276 def archive(repo, dest, node, kind, decode=True, matchfn=None,
277 277 prefix='', mtime=None, subrepos=False):
278 278 '''create archive of repo as it was at node.
279 279
280 280 dest can be name of directory, name of archive file, or file
281 281 object to write archive to.
282 282
283 283 kind is type of archive to create.
284 284
285 285 decode tells whether to put files through decode filters from
286 286 hgrc.
287 287
288 288 matchfn is function to filter names of files to write to archive.
289 289
290 290 prefix is name of path to put before every archive member.'''
291 291
292 292 if kind == 'files':
293 293 if prefix:
294 294 raise error.Abort(_('cannot give prefix when archiving to files'))
295 295 else:
296 296 prefix = tidyprefix(dest, kind, prefix)
297 297
298 298 def write(name, mode, islink, getdata):
299 299 data = getdata()
300 300 if decode:
301 301 data = repo.wwritedata(name, data)
302 302 archiver.addfile(prefix + name, mode, islink, data)
303 303
304 304 if kind not in archivers:
305 305 raise error.Abort(_("unknown archive type '%s'") % kind)
306 306
307 307 ctx = repo[node]
308 308 archiver = archivers[kind](dest, mtime or ctx.date()[0])
309 309
310 310 if repo.ui.configbool("ui", "archivemeta", True):
311 311 name = '.hg_archival.txt'
312 312 if not matchfn or matchfn(name):
313 313 write(name, 0o644, False, lambda: buildmetadata(ctx))
314 314
315 315 if matchfn:
316 316 files = [f for f in ctx.manifest().keys() if matchfn(f)]
317 317 else:
318 318 files = ctx.manifest().keys()
319 319 total = len(files)
320 320 if total:
321 321 files.sort()
322 322 repo.ui.progress(_('archiving'), 0, unit=_('files'), total=total)
323 323 for i, f in enumerate(files):
324 324 ff = ctx.flags(f)
325 325 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, ctx[f].data)
326 326 repo.ui.progress(_('archiving'), i + 1, item=f,
327 327 unit=_('files'), total=total)
328 328 repo.ui.progress(_('archiving'), None)
329 329
330 330 if subrepos:
331 331 for subpath in sorted(ctx.substate):
332 332 sub = ctx.workingsub(subpath)
333 333 submatch = matchmod.subdirmatcher(subpath, matchfn)
334 334 total += sub.archive(archiver, prefix, submatch, decode)
335 335
336 336 if total == 0:
337 337 raise error.Abort(_('no files match the archive pattern'))
338 338
339 339 archiver.done()
340 340 return total
General Comments 0
You need to be logged in to leave comments. Login now