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