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