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