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