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