##// END OF EJS Templates
cleanly abort on unknown archive type (issue966)
Dirkjan Ochtman -
r6019:b70a530b default
parent child Browse files
Show More
@@ -1,220 +1,222 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 *
9 from node import *
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 = prefix.replace('\\', '/')
18 prefix = prefix.replace('\\', '/')
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 fname = self.filename[:-3]
55 fname = self.filename[:-3]
56 flags = 0
56 flags = 0
57 if fname:
57 if fname:
58 flags = gzip.FNAME
58 flags = gzip.FNAME
59 self.fileobj.write(chr(flags))
59 self.fileobj.write(chr(flags))
60 gzip.write32u(self.fileobj, long(self.timestamp))
60 gzip.write32u(self.fileobj, long(self.timestamp))
61 self.fileobj.write('\002')
61 self.fileobj.write('\002')
62 self.fileobj.write('\377')
62 self.fileobj.write('\377')
63 if fname:
63 if fname:
64 self.fileobj.write(fname + '\000')
64 self.fileobj.write(fname + '\000')
65
65
66 def __init__(self, dest, prefix, mtime, kind=''):
66 def __init__(self, dest, prefix, mtime, kind=''):
67 self.prefix = tidyprefix(dest, prefix, ['.tar', '.tar.bz2', '.tar.gz',
67 self.prefix = tidyprefix(dest, prefix, ['.tar', '.tar.bz2', '.tar.gz',
68 '.tgz', '.tbz2'])
68 '.tgz', '.tbz2'])
69 self.mtime = mtime
69 self.mtime = mtime
70
70
71 def taropen(name, mode, fileobj=None):
71 def taropen(name, mode, fileobj=None):
72 if kind == 'gz':
72 if kind == 'gz':
73 mode = mode[0]
73 mode = mode[0]
74 if not fileobj:
74 if not fileobj:
75 fileobj = open(name, mode + 'b')
75 fileobj = open(name, mode + 'b')
76 gzfileobj = self.GzipFileWithTime(name, mode + 'b',
76 gzfileobj = self.GzipFileWithTime(name, mode + 'b',
77 zlib.Z_BEST_COMPRESSION,
77 zlib.Z_BEST_COMPRESSION,
78 fileobj, timestamp=mtime)
78 fileobj, timestamp=mtime)
79 return tarfile.TarFile.taropen(name, mode, gzfileobj)
79 return tarfile.TarFile.taropen(name, mode, gzfileobj)
80 else:
80 else:
81 return tarfile.open(name, mode + kind, fileobj)
81 return tarfile.open(name, mode + kind, fileobj)
82
82
83 if isinstance(dest, str):
83 if isinstance(dest, str):
84 self.z = taropen(dest, mode='w:')
84 self.z = taropen(dest, mode='w:')
85 else:
85 else:
86 # Python 2.5-2.5.1 have a regression that requires a name arg
86 # Python 2.5-2.5.1 have a regression that requires a name arg
87 self.z = taropen(name='', mode='w|', fileobj=dest)
87 self.z = taropen(name='', mode='w|', fileobj=dest)
88
88
89 def addfile(self, name, mode, islink, data):
89 def addfile(self, name, mode, islink, data):
90 i = tarfile.TarInfo(self.prefix + name)
90 i = tarfile.TarInfo(self.prefix + name)
91 i.mtime = self.mtime
91 i.mtime = self.mtime
92 i.size = len(data)
92 i.size = len(data)
93 if islink:
93 if islink:
94 i.type = tarfile.SYMTYPE
94 i.type = tarfile.SYMTYPE
95 i.mode = 0777
95 i.mode = 0777
96 i.linkname = data
96 i.linkname = data
97 data = None
97 data = None
98 else:
98 else:
99 i.mode = mode
99 i.mode = mode
100 data = cStringIO.StringIO(data)
100 data = cStringIO.StringIO(data)
101 self.z.addfile(i, data)
101 self.z.addfile(i, data)
102
102
103 def done(self):
103 def done(self):
104 self.z.close()
104 self.z.close()
105
105
106 class tellable:
106 class tellable:
107 '''provide tell method for zipfile.ZipFile when writing to http
107 '''provide tell method for zipfile.ZipFile when writing to http
108 response file object.'''
108 response file object.'''
109
109
110 def __init__(self, fp):
110 def __init__(self, fp):
111 self.fp = fp
111 self.fp = fp
112 self.offset = 0
112 self.offset = 0
113
113
114 def __getattr__(self, key):
114 def __getattr__(self, key):
115 return getattr(self.fp, key)
115 return getattr(self.fp, key)
116
116
117 def write(self, s):
117 def write(self, s):
118 self.fp.write(s)
118 self.fp.write(s)
119 self.offset += len(s)
119 self.offset += len(s)
120
120
121 def tell(self):
121 def tell(self):
122 return self.offset
122 return self.offset
123
123
124 class zipit:
124 class zipit:
125 '''write archive to zip file or stream. can write uncompressed,
125 '''write archive to zip file or stream. can write uncompressed,
126 or compressed with deflate.'''
126 or compressed with deflate.'''
127
127
128 def __init__(self, dest, prefix, mtime, compress=True):
128 def __init__(self, dest, prefix, mtime, compress=True):
129 self.prefix = tidyprefix(dest, prefix, ('.zip',))
129 self.prefix = tidyprefix(dest, prefix, ('.zip',))
130 if not isinstance(dest, str):
130 if not isinstance(dest, str):
131 try:
131 try:
132 dest.tell()
132 dest.tell()
133 except (AttributeError, IOError):
133 except (AttributeError, IOError):
134 dest = tellable(dest)
134 dest = tellable(dest)
135 self.z = zipfile.ZipFile(dest, 'w',
135 self.z = zipfile.ZipFile(dest, 'w',
136 compress and zipfile.ZIP_DEFLATED or
136 compress and zipfile.ZIP_DEFLATED or
137 zipfile.ZIP_STORED)
137 zipfile.ZIP_STORED)
138 self.date_time = time.gmtime(mtime)[:6]
138 self.date_time = time.gmtime(mtime)[:6]
139
139
140 def addfile(self, name, mode, islink, data):
140 def addfile(self, name, mode, islink, data):
141 i = zipfile.ZipInfo(self.prefix + name, self.date_time)
141 i = zipfile.ZipInfo(self.prefix + name, self.date_time)
142 i.compress_type = self.z.compression
142 i.compress_type = self.z.compression
143 # unzip will not honor unix file modes unless file creator is
143 # unzip will not honor unix file modes unless file creator is
144 # set to unix (id 3).
144 # set to unix (id 3).
145 i.create_system = 3
145 i.create_system = 3
146 ftype = stat.S_IFREG
146 ftype = stat.S_IFREG
147 if islink:
147 if islink:
148 mode = 0777
148 mode = 0777
149 ftype = stat.S_IFLNK
149 ftype = stat.S_IFLNK
150 i.external_attr = (mode | ftype) << 16L
150 i.external_attr = (mode | ftype) << 16L
151 self.z.writestr(i, data)
151 self.z.writestr(i, data)
152
152
153 def done(self):
153 def done(self):
154 self.z.close()
154 self.z.close()
155
155
156 class fileit:
156 class fileit:
157 '''write archive as files in directory.'''
157 '''write archive as files in directory.'''
158
158
159 def __init__(self, name, prefix, mtime):
159 def __init__(self, name, prefix, mtime):
160 if prefix:
160 if prefix:
161 raise util.Abort(_('cannot give prefix when archiving to files'))
161 raise util.Abort(_('cannot give prefix when archiving to files'))
162 self.basedir = name
162 self.basedir = name
163 self.opener = util.opener(self.basedir)
163 self.opener = util.opener(self.basedir)
164
164
165 def addfile(self, name, mode, islink, data):
165 def addfile(self, name, mode, islink, data):
166 if islink:
166 if islink:
167 self.opener.symlink(data, name)
167 self.opener.symlink(data, name)
168 return
168 return
169 f = self.opener(name, "w", atomictemp=True)
169 f = self.opener(name, "w", atomictemp=True)
170 f.write(data)
170 f.write(data)
171 f.rename()
171 f.rename()
172 destfile = os.path.join(self.basedir, name)
172 destfile = os.path.join(self.basedir, name)
173 os.chmod(destfile, mode)
173 os.chmod(destfile, mode)
174
174
175 def done(self):
175 def done(self):
176 pass
176 pass
177
177
178 archivers = {
178 archivers = {
179 'files': fileit,
179 'files': fileit,
180 'tar': tarit,
180 'tar': tarit,
181 'tbz2': lambda name, prefix, mtime: tarit(name, prefix, mtime, 'bz2'),
181 'tbz2': lambda name, prefix, mtime: tarit(name, prefix, mtime, 'bz2'),
182 'tgz': lambda name, prefix, mtime: tarit(name, prefix, mtime, 'gz'),
182 'tgz': lambda name, prefix, mtime: tarit(name, prefix, mtime, 'gz'),
183 'uzip': lambda name, prefix, mtime: zipit(name, prefix, mtime, False),
183 'uzip': lambda name, prefix, mtime: zipit(name, prefix, mtime, False),
184 'zip': zipit,
184 'zip': zipit,
185 }
185 }
186
186
187 def archive(repo, dest, node, kind, decode=True, matchfn=None,
187 def archive(repo, dest, node, kind, decode=True, matchfn=None,
188 prefix=None, mtime=None):
188 prefix=None, mtime=None):
189 '''create archive of repo as it was at node.
189 '''create archive of repo as it was at node.
190
190
191 dest can be name of directory, name of archive file, or file
191 dest can be name of directory, name of archive file, or file
192 object to write archive to.
192 object to write archive to.
193
193
194 kind is type of archive to create.
194 kind is type of archive to create.
195
195
196 decode tells whether to put files through decode filters from
196 decode tells whether to put files through decode filters from
197 hgrc.
197 hgrc.
198
198
199 matchfn is function to filter names of files to write to archive.
199 matchfn is function to filter names of files to write to archive.
200
200
201 prefix is name of path to put before every archive member.'''
201 prefix is name of path to put before every archive member.'''
202
202
203 def write(name, mode, islink, getdata):
203 def write(name, mode, islink, getdata):
204 if matchfn and not matchfn(name): return
204 if matchfn and not matchfn(name): return
205 data = getdata()
205 data = getdata()
206 if decode:
206 if decode:
207 data = repo.wwritedata(name, data)
207 data = repo.wwritedata(name, data)
208 archiver.addfile(name, mode, islink, data)
208 archiver.addfile(name, mode, islink, data)
209
209
210 ctx = repo.changectx(node)
210 ctx = repo.changectx(node)
211 if kind not in archivers:
212 raise util.Abort(_("unknown archive type '%s'" % kind))
211 archiver = archivers[kind](dest, prefix, mtime or ctx.date()[0])
213 archiver = archivers[kind](dest, prefix, mtime or ctx.date()[0])
212 m = ctx.manifest()
214 m = ctx.manifest()
213 items = m.items()
215 items = m.items()
214 items.sort()
216 items.sort()
215 write('.hg_archival.txt', 0644, False,
217 write('.hg_archival.txt', 0644, False,
216 lambda: 'repo: %s\nnode: %s\n' % (hex(repo.changelog.node(0)), hex(node)))
218 lambda: 'repo: %s\nnode: %s\n' % (hex(repo.changelog.node(0)), hex(node)))
217 for filename, filenode in items:
219 for filename, filenode in items:
218 write(filename, m.execf(filename) and 0755 or 0644, m.linkf(filename),
220 write(filename, m.execf(filename) and 0755 or 0644, m.linkf(filename),
219 lambda: repo.file(filename).read(filenode))
221 lambda: repo.file(filename).read(filenode))
220 archiver.done()
222 archiver.done()
@@ -1,76 +1,78 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 mkdir test
3 mkdir test
4 cd test
4 cd test
5 hg init
5 hg init
6 echo foo>foo
6 echo foo>foo
7 hg commit -Am 1 -d '1 0'
7 hg commit -Am 1 -d '1 0'
8 echo bar>bar
8 echo bar>bar
9 hg commit -Am 2 -d '2 0'
9 hg commit -Am 2 -d '2 0'
10 mkdir baz
10 mkdir baz
11 echo bletch>baz/bletch
11 echo bletch>baz/bletch
12 hg commit -Am 3 -d '1000000000 0'
12 hg commit -Am 3 -d '1000000000 0'
13 echo "[web]" >> .hg/hgrc
13 echo "[web]" >> .hg/hgrc
14 echo "name = test-archive" >> .hg/hgrc
14 echo "name = test-archive" >> .hg/hgrc
15 echo "allow_archive = gz bz2, zip" >> .hg/hgrc
15 echo "allow_archive = gz bz2, zip" >> .hg/hgrc
16 hg serve -p $HGPORT -d --pid-file=hg.pid
16 hg serve -p $HGPORT -d --pid-file=hg.pid
17 cat hg.pid >> $DAEMON_PIDS
17 cat hg.pid >> $DAEMON_PIDS
18
18
19 TIP=`hg id -v | cut -f1 -d' '`
19 TIP=`hg id -v | cut -f1 -d' '`
20 QTIP=`hg id -q`
20 QTIP=`hg id -q`
21 cat > getarchive.py <<EOF
21 cat > getarchive.py <<EOF
22 import os, sys, urllib2
22 import os, sys, urllib2
23 node, archive = sys.argv[1:]
23 node, archive = sys.argv[1:]
24 f = urllib2.urlopen('http://127.0.0.1:%s/?cmd=archive;node=%s;type=%s'
24 f = urllib2.urlopen('http://127.0.0.1:%s/?cmd=archive;node=%s;type=%s'
25 % (os.environ['HGPORT'], node, archive))
25 % (os.environ['HGPORT'], node, archive))
26 sys.stdout.write(f.read())
26 sys.stdout.write(f.read())
27 EOF
27 EOF
28 http_proxy= python getarchive.py "$TIP" gz | gunzip | tar tf - | sed "s/$QTIP/TIP/"
28 http_proxy= python getarchive.py "$TIP" gz | gunzip | tar tf - | sed "s/$QTIP/TIP/"
29 http_proxy= python getarchive.py "$TIP" bz2 | bunzip2 | tar tf - | sed "s/$QTIP/TIP/"
29 http_proxy= python getarchive.py "$TIP" bz2 | bunzip2 | tar tf - | sed "s/$QTIP/TIP/"
30 http_proxy= python getarchive.py "$TIP" zip > archive.zip
30 http_proxy= python getarchive.py "$TIP" zip > archive.zip
31 unzip -t archive.zip | sed "s/$QTIP/TIP/"
31 unzip -t archive.zip | sed "s/$QTIP/TIP/"
32
32
33 hg archive -t tar test.tar
33 hg archive -t tar test.tar
34 tar tf test.tar
34 tar tf test.tar
35
35
36 hg archive -t tbz2 -X baz test.tar.bz2
36 hg archive -t tbz2 -X baz test.tar.bz2
37 bunzip2 -dc test.tar.bz2 | tar tf -
37 bunzip2 -dc test.tar.bz2 | tar tf -
38
38
39 hg archive -t tgz -p %b-%h test-%h.tar.gz
39 hg archive -t tgz -p %b-%h test-%h.tar.gz
40 gzip -dc test-$QTIP.tar.gz | tar tf - | sed "s/$QTIP/TIP/"
40 gzip -dc test-$QTIP.tar.gz | tar tf - | sed "s/$QTIP/TIP/"
41
41
42 cat > md5comp.py <<EOF
42 cat > md5comp.py <<EOF
43 import md5, sys
43 import md5, sys
44 f1, f2 = sys.argv[1:3]
44 f1, f2 = sys.argv[1:3]
45 h1 = md5.md5(file(f1, 'rb').read()).hexdigest()
45 h1 = md5.md5(file(f1, 'rb').read()).hexdigest()
46 h2 = md5.md5(file(f2, 'rb').read()).hexdigest()
46 h2 = md5.md5(file(f2, 'rb').read()).hexdigest()
47 print h1 == h2 or "md5 differ: " + repr((h1, h2))
47 print h1 == h2 or "md5 differ: " + repr((h1, h2))
48 EOF
48 EOF
49
49
50 # archive name is stored in the archive, so create similar
50 # archive name is stored in the archive, so create similar
51 # archives and rename them afterwards.
51 # archives and rename them afterwards.
52 hg archive -t tgz tip.tar.gz
52 hg archive -t tgz tip.tar.gz
53 mv tip.tar.gz tip1.tar.gz
53 mv tip.tar.gz tip1.tar.gz
54 sleep 1
54 sleep 1
55 hg archive -t tgz tip.tar.gz
55 hg archive -t tgz tip.tar.gz
56 mv tip.tar.gz tip2.tar.gz
56 mv tip.tar.gz tip2.tar.gz
57 python md5comp.py tip1.tar.gz tip2.tar.gz
57 python md5comp.py tip1.tar.gz tip2.tar.gz
58
58
59 hg archive -t zip -p /illegal test.zip
59 hg archive -t zip -p /illegal test.zip
60 hg archive -t zip -p very/../bad test.zip
60 hg archive -t zip -p very/../bad test.zip
61
61
62 hg archive -t zip -r 2 test.zip
62 hg archive -t zip -r 2 test.zip
63 unzip -t test.zip
63 unzip -t test.zip
64
64
65 hg archive -t tar - | tar tf - | sed "s/$QTIP/TIP/"
65 hg archive -t tar - | tar tf - | sed "s/$QTIP/TIP/"
66
66
67 hg archive -r 0 -t tar rev-%r.tar
67 hg archive -r 0 -t tar rev-%r.tar
68 if [ -f rev-0.tar ]; then
68 if [ -f rev-0.tar ]; then
69 echo 'rev-0.tar created'
69 echo 'rev-0.tar created'
70 fi
70 fi
71
71
72 hg archive -t bogus test.bogus
73
72 echo '% empty repo'
74 echo '% empty repo'
73 hg init ../empty
75 hg init ../empty
74 cd ../empty
76 cd ../empty
75 hg archive ../test-empty
77 hg archive ../test-empty
76 exit 0
78 exit 0
@@ -1,43 +1,44 b''
1 adding foo
1 adding foo
2 adding bar
2 adding bar
3 adding baz/bletch
3 adding baz/bletch
4 test-archive-TIP/.hg_archival.txt
4 test-archive-TIP/.hg_archival.txt
5 test-archive-TIP/bar
5 test-archive-TIP/bar
6 test-archive-TIP/baz/bletch
6 test-archive-TIP/baz/bletch
7 test-archive-TIP/foo
7 test-archive-TIP/foo
8 test-archive-TIP/.hg_archival.txt
8 test-archive-TIP/.hg_archival.txt
9 test-archive-TIP/bar
9 test-archive-TIP/bar
10 test-archive-TIP/baz/bletch
10 test-archive-TIP/baz/bletch
11 test-archive-TIP/foo
11 test-archive-TIP/foo
12 Archive: archive.zip
12 Archive: archive.zip
13 testing: test-archive-TIP/.hg_archival.txt OK
13 testing: test-archive-TIP/.hg_archival.txt OK
14 testing: test-archive-TIP/bar OK
14 testing: test-archive-TIP/bar OK
15 testing: test-archive-TIP/baz/bletch OK
15 testing: test-archive-TIP/baz/bletch OK
16 testing: test-archive-TIP/foo OK
16 testing: test-archive-TIP/foo OK
17 No errors detected in compressed data of archive.zip.
17 No errors detected in compressed data of archive.zip.
18 test/.hg_archival.txt
18 test/.hg_archival.txt
19 test/bar
19 test/bar
20 test/baz/bletch
20 test/baz/bletch
21 test/foo
21 test/foo
22 test/.hg_archival.txt
22 test/.hg_archival.txt
23 test/bar
23 test/bar
24 test/foo
24 test/foo
25 test-TIP/.hg_archival.txt
25 test-TIP/.hg_archival.txt
26 test-TIP/bar
26 test-TIP/bar
27 test-TIP/baz/bletch
27 test-TIP/baz/bletch
28 test-TIP/foo
28 test-TIP/foo
29 True
29 True
30 abort: archive prefix contains illegal components
30 abort: archive prefix contains illegal components
31 Archive: test.zip
31 Archive: test.zip
32 testing: test/.hg_archival.txt OK
32 testing: test/.hg_archival.txt OK
33 testing: test/bar OK
33 testing: test/bar OK
34 testing: test/baz/bletch OK
34 testing: test/baz/bletch OK
35 testing: test/foo OK
35 testing: test/foo OK
36 No errors detected in compressed data of test.zip.
36 No errors detected in compressed data of test.zip.
37 test-TIP/.hg_archival.txt
37 test-TIP/.hg_archival.txt
38 test-TIP/bar
38 test-TIP/bar
39 test-TIP/baz/bletch
39 test-TIP/baz/bletch
40 test-TIP/foo
40 test-TIP/foo
41 rev-0.tar created
41 rev-0.tar created
42 abort: unknown archive type 'bogus'
42 % empty repo
43 % empty repo
43 abort: repository has no revisions
44 abort: repository has no revisions
General Comments 0
You need to be logged in to leave comments. Login now