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