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 | demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo") |
|
12 | demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo") | |
13 | demandload(globals(), "fnmatch hgweb mdiff random signal tempfile time") |
|
13 | demandload(globals(), "fnmatch hgweb mdiff random signal tempfile time") | |
14 | demandload(globals(), "traceback errno socket version struct atexit sets bz2") |
|
14 | demandload(globals(), "traceback errno socket version struct atexit sets bz2") | |
15 | demandload(globals(), "changegroup") |
|
15 | demandload(globals(), "archival changegroup") | |
16 |
|
16 | |||
17 | class UnknownCommand(Exception): |
|
17 | class UnknownCommand(Exception): | |
18 | """Exception raised if command is not in the command table.""" |
|
18 | """Exception raised if command is not in the command table.""" | |
@@ -890,6 +890,46 b' def annotate(ui, repo, *pats, **opts):' | |||||
890 | for p, l in zip(zip(*pieces), lines): |
|
890 | for p, l in zip(zip(*pieces), lines): | |
891 | ui.write("%s: %s" % (" ".join(p), l[1])) |
|
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 | def bundle(ui, repo, fname, dest="default-push", **opts): |
|
933 | def bundle(ui, repo, fname, dest="default-push", **opts): | |
894 | """create a changegroup file |
|
934 | """create a changegroup file | |
895 |
|
935 | |||
@@ -2839,6 +2879,15 b' table = {' | |||||
2839 | ('I', 'include', [], _('include names matching the given patterns')), |
|
2879 | ('I', 'include', [], _('include names matching the given patterns')), | |
2840 | ('X', 'exclude', [], _('exclude names matching the given patterns'))], |
|
2880 | ('X', 'exclude', [], _('exclude names matching the given patterns'))], | |
2841 | _('hg annotate [-r REV] [-a] [-u] [-d] [-n] [-c] FILE...')), |
|
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 | "bundle": |
|
2891 | "bundle": | |
2843 | (bundle, |
|
2892 | (bundle, | |
2844 | [('f', 'force', None, |
|
2893 | [('f', 'force', None, | |
@@ -3249,7 +3298,7 b' def parse(ui, args):' | |||||
3249 | return (cmd, cmd and i[0] or None, args, options, cmdoptions) |
|
3298 | return (cmd, cmd and i[0] or None, args, options, cmdoptions) | |
3250 |
|
3299 | |||
3251 | def dispatch(args): |
|
3300 | def dispatch(args): | |
3252 |
for name in 'SIG |
|
3301 | for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM': | |
3253 | num = getattr(signal, name, None) |
|
3302 | num = getattr(signal, name, None) | |
3254 | if num: signal.signal(num, catchterm) |
|
3303 | if num: signal.signal(num, catchterm) | |
3255 |
|
3304 |
General Comments 0
You need to be logged in to leave comments.
Login now