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