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