Show More
@@ -0,0 +1,170 b'' | |||
|
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() |
@@ -12,7 +12,7 b' demandload(globals(), "os re sys signal ' | |||
|
12 | 12 | demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo") |
|
13 | 13 | demandload(globals(), "fnmatch hgweb mdiff random signal tempfile time") |
|
14 | 14 | demandload(globals(), "traceback errno socket version struct atexit sets bz2") |
|
15 | demandload(globals(), "changegroup") | |
|
15 | demandload(globals(), "archival changegroup") | |
|
16 | 16 | |
|
17 | 17 | class UnknownCommand(Exception): |
|
18 | 18 | """Exception raised if command is not in the command table.""" |
@@ -890,6 +890,46 b' def annotate(ui, repo, *pats, **opts):' | |||
|
890 | 890 | for p, l in zip(zip(*pieces), lines): |
|
891 | 891 | ui.write("%s: %s" % (" ".join(p), l[1])) |
|
892 | 892 | |
|
893 | def archive(ui, repo, dest, **opts): | |
|
894 | '''create unversioned archive of a repository revision | |
|
895 | ||
|
896 | By default, the revision used is the parent of the working | |
|
897 | directory; use "-r" to specify a different revision. | |
|
898 | ||
|
899 | To specify the type of archive to create, use "-t". Valid | |
|
900 | types are: | |
|
901 | ||
|
902 | "files" (default): a directory full of files | |
|
903 | "tar": tar archive, uncompressed | |
|
904 | "tbz2": tar archive, compressed using bzip2 | |
|
905 | "tgz": tar archive, compressed using gzip | |
|
906 | "uzip": zip archive, uncompressed | |
|
907 | "zip": zip archive, compressed using deflate | |
|
908 | ||
|
909 | The exact name of the destination archive or directory is given | |
|
910 | using a format string; see "hg help export" for details. | |
|
911 | ||
|
912 | Each member added to an archive file has a directory prefix | |
|
913 | prepended. Use "-p" to specify a format string for the prefix. | |
|
914 | The default is the basename of the archive, with suffixes removed. | |
|
915 | ''' | |
|
916 | ||
|
917 | if opts['rev']: | |
|
918 | node = repo.lookup(opts['rev']) | |
|
919 | else: | |
|
920 | node, p2 = repo.dirstate.parents() | |
|
921 | if p2 != nullid: | |
|
922 | raise util.Abort(_('uncommitted merge - please provide a ' | |
|
923 | 'specific revision')) | |
|
924 | ||
|
925 | dest = make_filename(repo, repo.changelog, dest, node) | |
|
926 | prefix = make_filename(repo, repo.changelog, opts['prefix'], node) | |
|
927 | if os.path.realpath(dest) == repo.root: | |
|
928 | raise util.Abort(_('repository root cannot be destination')) | |
|
929 | _, matchfn, _ = matchpats(repo, [], opts) | |
|
930 | archival.archive(repo, dest, node, opts.get('type') or 'files', | |
|
931 | not opts['no_decode'], matchfn, prefix) | |
|
932 | ||
|
893 | 933 | def bundle(ui, repo, fname, dest="default-push", **opts): |
|
894 | 934 | """create a changegroup file |
|
895 | 935 | |
@@ -2839,6 +2879,15 b' table = {' | |||
|
2839 | 2879 | ('I', 'include', [], _('include names matching the given patterns')), |
|
2840 | 2880 | ('X', 'exclude', [], _('exclude names matching the given patterns'))], |
|
2841 | 2881 | _('hg annotate [-r REV] [-a] [-u] [-d] [-n] [-c] FILE...')), |
|
2882 | 'archive': | |
|
2883 | (archive, | |
|
2884 | [('', 'no-decode', None, _('do not pass files through decoders')), | |
|
2885 | ('p', 'prefix', '', _('directory prefix for files in archive')), | |
|
2886 | ('r', 'rev', '', _('revision to distribute')), | |
|
2887 | ('t', 'type', '', _('type of distribution to create')), | |
|
2888 | ('I', 'include', [], _('include names matching the given patterns')), | |
|
2889 | ('X', 'exclude', [], _('exclude names matching the given patterns'))], | |
|
2890 | _('hg archive [OPTION]... DEST')), | |
|
2842 | 2891 | "bundle": |
|
2843 | 2892 | (bundle, |
|
2844 | 2893 | [('f', 'force', None, |
@@ -3249,7 +3298,7 b' def parse(ui, args):' | |||
|
3249 | 3298 | return (cmd, cmd and i[0] or None, args, options, cmdoptions) |
|
3250 | 3299 | |
|
3251 | 3300 | def dispatch(args): |
|
3252 |
for name in 'SIG |
|
|
3301 | for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM': | |
|
3253 | 3302 | num = getattr(signal, name, None) |
|
3254 | 3303 | if num: signal.signal(num, catchterm) |
|
3255 | 3304 |
General Comments 0
You need to be logged in to leave comments.
Login now