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