Mercurial > hg > mercurial-crew
comparison mercurial/archival.py @ 2112:2b03c6733efa
add "archive" command, like "cvs export" only better.
most code in mercurial/archival.py module, for sharing with hgweb.
author | Vadim Gelfer <vadim.gelfer@gmail.com> |
---|---|
date | Fri, 21 Apr 2006 15:27:57 -0700 |
parents | |
children | dd4ec4576cc8 |
comparison
equal
deleted
inserted
replaced
2110:25a8d116ab6a | 2112:2b03c6733efa |
---|---|
1 # archival.py - revision archival for mercurial | |
2 # | |
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> | |
4 # | |
5 # This software may be used and distributed according to the terms of | |
6 # the GNU General Public License, incorporated herein by reference. | |
7 | |
8 from demandload import * | |
9 from i18n import gettext as _ | |
10 from node import * | |
11 demandload(globals(), 'cStringIO os stat tarfile time util zipfile') | |
12 | |
13 def tidyprefix(dest, prefix, suffixes): | |
14 '''choose prefix to use for names in archive. make sure prefix is | |
15 safe for consumers.''' | |
16 | |
17 if prefix: | |
18 prefix = prefix.replace('\\', '/') | |
19 else: | |
20 if not isinstance(dest, str): | |
21 raise ValueError('dest must be string if no prefix') | |
22 prefix = os.path.basename(dest) | |
23 lower = prefix.lower() | |
24 for sfx in suffixes: | |
25 if lower.endswith(sfx): | |
26 prefix = prefix[:-len(sfx)] | |
27 break | |
28 lpfx = os.path.normpath(util.localpath(prefix)) | |
29 prefix = util.pconvert(lpfx) | |
30 if not prefix.endswith('/'): | |
31 prefix += '/' | |
32 if prefix.startswith('../') or os.path.isabs(lpfx) or '/../' in prefix: | |
33 raise util.Abort(_('archive prefix contains illegal components')) | |
34 return prefix | |
35 | |
36 class tarit: | |
37 '''write archive to tar file or stream. can write uncompressed, | |
38 or compress with gzip or bzip2.''' | |
39 | |
40 def __init__(self, dest, prefix, kind=''): | |
41 self.prefix = tidyprefix(dest, prefix, ['.tar', '.tar.bz2', '.tar.gz', | |
42 '.tgz', 'tbz2']) | |
43 self.mtime = int(time.time()) | |
44 if isinstance(dest, str): | |
45 self.z = tarfile.open(dest, mode='w:'+kind) | |
46 else: | |
47 self.z = tarfile.open(mode='w|'+kind, fileobj=dest) | |
48 | |
49 def addfile(self, name, mode, data): | |
50 i = tarfile.TarInfo(self.prefix + name) | |
51 i.mtime = self.mtime | |
52 i.size = len(data) | |
53 i.mode = mode | |
54 self.z.addfile(i, cStringIO.StringIO(data)) | |
55 | |
56 def done(self): | |
57 self.z.close() | |
58 | |
59 class tellable: | |
60 '''provide tell method for zipfile.ZipFile when writing to http | |
61 response file object.''' | |
62 | |
63 def __init__(self, fp): | |
64 self.fp = fp | |
65 self.offset = 0 | |
66 | |
67 def __getattr__(self, key): | |
68 return getattr(self.fp, key) | |
69 | |
70 def write(self, s): | |
71 self.fp.write(s) | |
72 self.offset += len(s) | |
73 | |
74 def tell(self): | |
75 return self.offset | |
76 | |
77 class zipit: | |
78 '''write archive to zip file or stream. can write uncompressed, | |
79 or compressed with deflate.''' | |
80 | |
81 def __init__(self, dest, prefix, compress=True): | |
82 self.prefix = tidyprefix(dest, prefix, ('.zip',)) | |
83 if not isinstance(dest, str) and not hasattr(dest, 'tell'): | |
84 dest = tellable(dest) | |
85 self.z = zipfile.ZipFile(dest, 'w', | |
86 compress and zipfile.ZIP_DEFLATED or | |
87 zipfile.ZIP_STORED) | |
88 self.date_time = time.gmtime(time.time())[:6] | |
89 | |
90 def addfile(self, name, mode, data): | |
91 i = zipfile.ZipInfo(self.prefix + name, self.date_time) | |
92 i.compress_type = self.z.compression | |
93 i.flag_bits = 0x08 | |
94 # unzip will not honor unix file modes unless file creator is | |
95 # set to unix (id 3). | |
96 i.create_system = 3 | |
97 i.external_attr = (mode | stat.S_IFREG) << 16L | |
98 self.z.writestr(i, data) | |
99 | |
100 def done(self): | |
101 self.z.close() | |
102 | |
103 class fileit: | |
104 '''write archive as files in directory.''' | |
105 | |
106 def __init__(self, name, prefix): | |
107 if prefix: | |
108 raise util.Abort(_('cannot give prefix when archiving to files')) | |
109 self.basedir = name | |
110 self.dirs = {} | |
111 self.oflags = (os.O_CREAT | os.O_EXCL | os.O_WRONLY | | |
112 getattr(os, 'O_BINARY', 0) | | |
113 getattr(os, 'O_NOFOLLOW', 0)) | |
114 | |
115 def addfile(self, name, mode, data): | |
116 destfile = os.path.join(self.basedir, name) | |
117 destdir = os.path.dirname(destfile) | |
118 if destdir not in self.dirs: | |
119 if not os.path.isdir(destdir): | |
120 os.makedirs(destdir) | |
121 self.dirs[destdir] = 1 | |
122 os.fdopen(os.open(destfile, self.oflags, mode), 'wb').write(data) | |
123 | |
124 def done(self): | |
125 pass | |
126 | |
127 archivers = { | |
128 'files': fileit, | |
129 'tar': tarit, | |
130 'tbz2': lambda name, prefix: tarit(name, prefix, 'bz2'), | |
131 'tgz': lambda name, prefix: tarit(name, prefix, 'gz'), | |
132 'uzip': lambda name, prefix: zipit(name, prefix, False), | |
133 'zip': zipit, | |
134 } | |
135 | |
136 def archive(repo, dest, node, kind, decode=True, matchfn=None, | |
137 prefix=None): | |
138 '''create archive of repo as it was at node. | |
139 | |
140 dest can be name of directory, name of archive file, or file | |
141 object to write archive to. | |
142 | |
143 kind is type of archive to create. | |
144 | |
145 decode tells whether to put files through decode filters from | |
146 hgrc. | |
147 | |
148 matchfn is function to filter names of files to write to archive. | |
149 | |
150 prefix is name of path to put before every archive member.''' | |
151 | |
152 def write(name, mode, data): | |
153 if matchfn and not matchfn(name): return | |
154 if decode: | |
155 fp = cStringIO.StringIO() | |
156 repo.wwrite(None, data, fp) | |
157 data = fp.getvalue() | |
158 archiver.addfile(name, mode, data) | |
159 | |
160 archiver = archivers[kind](dest, prefix) | |
161 mn = repo.changelog.read(node)[0] | |
162 mf = repo.manifest.read(mn).items() | |
163 mff = repo.manifest.readflags(mn) | |
164 mf.sort() | |
165 write('.hg_archival.txt', 0644, | |
166 'repo: %s\nnode: %s\n' % (hex(repo.changelog.node(0)), hex(node))) | |
167 for filename, filenode in mf: | |
168 write(filename, mff[filename] and 0755 or 0644, | |
169 repo.file(filename).read(filenode)) | |
170 archiver.done() |