Show More
@@ -1,102 +1,102 | |||
|
1 | 1 | # __init__.py - asv benchmark suite |
|
2 | 2 | # |
|
3 | 3 | # Copyright 2016 Logilab SA <contact@logilab.fr> |
|
4 | 4 | # |
|
5 | 5 | # This software may be used and distributed according to the terms of the |
|
6 | 6 | # GNU General Public License version 2 or any later version. |
|
7 | 7 | |
|
8 | 8 | '''ASV (https://asv.readthedocs.io) benchmark suite |
|
9 | 9 | |
|
10 | 10 | Benchmark are parameterized against reference repositories found in the |
|
11 | 11 | directory pointed by the REPOS_DIR environment variable. |
|
12 | 12 | |
|
13 | 13 | Invocation example: |
|
14 | 14 | |
|
15 | 15 | $ export REPOS_DIR=~/hgperf/repos |
|
16 | 16 | # run suite on given revision |
|
17 | 17 | $ asv --config contrib/asv.conf.json run REV |
|
18 | 18 | # run suite on new changesets found in stable and default branch |
|
19 | 19 | $ asv --config contrib/asv.conf.json run NEW |
|
20 | 20 | # display a comparative result table of benchmark results between two given |
|
21 | 21 | # revisions |
|
22 | 22 | $ asv --config contrib/asv.conf.json compare REV1 REV2 |
|
23 | 23 | # compute regression detection and generate ASV static website |
|
24 | 24 | $ asv --config contrib/asv.conf.json publish |
|
25 | 25 | # serve the static website |
|
26 | 26 | $ asv --config contrib/asv.conf.json preview |
|
27 | 27 | ''' |
|
28 | 28 | |
|
29 | 29 | from __future__ import absolute_import |
|
30 | 30 | |
|
31 | 31 | import functools |
|
32 | 32 | import os |
|
33 | 33 | import re |
|
34 | 34 | |
|
35 | 35 | from mercurial import ( |
|
36 | 36 | extensions, |
|
37 | 37 | hg, |
|
38 | 38 | ui as uimod, |
|
39 | 39 | ) |
|
40 | 40 | |
|
41 | 41 | basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), |
|
42 | 42 | os.path.pardir, os.path.pardir)) |
|
43 | 43 | reposdir = os.environ['REPOS_DIR'] |
|
44 | 44 | reposnames = [name for name in os.listdir(reposdir) |
|
45 | 45 | if os.path.isdir(os.path.join(reposdir, name, ".hg"))] |
|
46 | 46 | if not reposnames: |
|
47 | 47 | raise ValueError("No repositories found in $REPO_DIR") |
|
48 | 48 | outputre = re.compile((r'! wall (\d+.\d+) comb \d+.\d+ user \d+.\d+ sys ' |
|
49 | 49 | r'\d+.\d+ \(best of \d+\)')) |
|
50 | 50 | |
|
51 | 51 | def runperfcommand(reponame, command, *args, **kwargs): |
|
52 | 52 | os.environ["HGRCPATH"] = os.environ.get("ASVHGRCPATH", "") |
|
53 | ui = uimod.ui() | |
|
53 | ui = uimod.ui.load() | |
|
54 | 54 | repo = hg.repository(ui, os.path.join(reposdir, reponame)) |
|
55 | 55 | perfext = extensions.load(ui, 'perfext', |
|
56 | 56 | os.path.join(basedir, 'contrib', 'perf.py')) |
|
57 | 57 | cmd = getattr(perfext, command) |
|
58 | 58 | ui.pushbuffer() |
|
59 | 59 | cmd(ui, repo, *args, **kwargs) |
|
60 | 60 | output = ui.popbuffer() |
|
61 | 61 | match = outputre.search(output) |
|
62 | 62 | if not match: |
|
63 | 63 | raise ValueError("Invalid output {0}".format(output)) |
|
64 | 64 | return float(match.group(1)) |
|
65 | 65 | |
|
66 | 66 | def perfbench(repos=reposnames, name=None, params=None): |
|
67 | 67 | """decorator to declare ASV benchmark based on contrib/perf.py extension |
|
68 | 68 | |
|
69 | 69 | An ASV benchmark is a python function with the given attributes: |
|
70 | 70 | |
|
71 | 71 | __name__: should start with track_, time_ or mem_ to be collected by ASV |
|
72 | 72 | params and param_name: parameter matrix to display multiple graphs on the |
|
73 | 73 | same page. |
|
74 | 74 | pretty_name: If defined it's displayed in web-ui instead of __name__ |
|
75 | 75 | (useful for revsets) |
|
76 | 76 | the module name is prepended to the benchmark name and displayed as |
|
77 | 77 | "category" in webui. |
|
78 | 78 | |
|
79 | 79 | Benchmarks are automatically parameterized with repositories found in the |
|
80 | 80 | REPOS_DIR environment variable. |
|
81 | 81 | |
|
82 | 82 | `params` is the param matrix in the form of a list of tuple |
|
83 | 83 | (param_name, [value0, value1]) |
|
84 | 84 | |
|
85 | 85 | For example [(x, [a, b]), (y, [c, d])] declare benchmarks for |
|
86 | 86 | (a, c), (a, d), (b, c) and (b, d). |
|
87 | 87 | """ |
|
88 | 88 | params = list(params or []) |
|
89 | 89 | params.insert(0, ("repo", repos)) |
|
90 | 90 | |
|
91 | 91 | def decorator(func): |
|
92 | 92 | @functools.wraps(func) |
|
93 | 93 | def wrapped(repo, *args): |
|
94 | 94 | def perf(command, *a, **kw): |
|
95 | 95 | return runperfcommand(repo, command, *a, **kw) |
|
96 | 96 | return func(perf, *args) |
|
97 | 97 | |
|
98 | 98 | wrapped.params = [p[1] for p in params] |
|
99 | 99 | wrapped.param_names = [p[0] for p in params] |
|
100 | 100 | wrapped.pretty_name = name |
|
101 | 101 | return wrapped |
|
102 | 102 | return decorator |
@@ -1,66 +1,66 | |||
|
1 | 1 | #!/usr/bin/env python |
|
2 | 2 | |
|
3 | 3 | from mercurial import demandimport |
|
4 | 4 | demandimport.enable() |
|
5 | 5 | |
|
6 | 6 | import sys |
|
7 | 7 | from mercurial.i18n import _ |
|
8 | 8 | from mercurial import error, simplemerge, fancyopts, util, ui |
|
9 | 9 | |
|
10 | 10 | options = [('L', 'label', [], _('labels to use on conflict markers')), |
|
11 | 11 | ('a', 'text', None, _('treat all files as text')), |
|
12 | 12 | ('p', 'print', None, |
|
13 | 13 | _('print results instead of overwriting LOCAL')), |
|
14 | 14 | ('', 'no-minimal', None, _('no effect (DEPRECATED)')), |
|
15 | 15 | ('h', 'help', None, _('display help and exit')), |
|
16 | 16 | ('q', 'quiet', None, _('suppress output'))] |
|
17 | 17 | |
|
18 | 18 | usage = _('''simplemerge [OPTS] LOCAL BASE OTHER |
|
19 | 19 | |
|
20 | 20 | Simple three-way file merge utility with a minimal feature set. |
|
21 | 21 | |
|
22 | 22 | Apply to LOCAL the changes necessary to go from BASE to OTHER. |
|
23 | 23 | |
|
24 | 24 | By default, LOCAL is overwritten with the results of this operation. |
|
25 | 25 | ''') |
|
26 | 26 | |
|
27 | 27 | class ParseError(Exception): |
|
28 | 28 | """Exception raised on errors in parsing the command line.""" |
|
29 | 29 | |
|
30 | 30 | def showhelp(): |
|
31 | 31 | sys.stdout.write(usage) |
|
32 | 32 | sys.stdout.write('\noptions:\n') |
|
33 | 33 | |
|
34 | 34 | out_opts = [] |
|
35 | 35 | for shortopt, longopt, default, desc in options: |
|
36 | 36 | out_opts.append(('%2s%s' % (shortopt and '-%s' % shortopt, |
|
37 | 37 | longopt and ' --%s' % longopt), |
|
38 | 38 | '%s' % desc)) |
|
39 | 39 | opts_len = max([len(opt[0]) for opt in out_opts]) |
|
40 | 40 | for first, second in out_opts: |
|
41 | 41 | sys.stdout.write(' %-*s %s\n' % (opts_len, first, second)) |
|
42 | 42 | |
|
43 | 43 | try: |
|
44 | 44 | for fp in (sys.stdin, sys.stdout, sys.stderr): |
|
45 | 45 | util.setbinary(fp) |
|
46 | 46 | |
|
47 | 47 | opts = {} |
|
48 | 48 | try: |
|
49 | 49 | args = fancyopts.fancyopts(sys.argv[1:], options, opts) |
|
50 | 50 | except fancyopts.getopt.GetoptError as e: |
|
51 | 51 | raise ParseError(e) |
|
52 | 52 | if opts['help']: |
|
53 | 53 | showhelp() |
|
54 | 54 | sys.exit(0) |
|
55 | 55 | if len(args) != 3: |
|
56 | 56 | raise ParseError(_('wrong number of arguments')) |
|
57 | sys.exit(simplemerge.simplemerge(ui.ui(), *args, **opts)) | |
|
57 | sys.exit(simplemerge.simplemerge(ui.ui.load(), *args, **opts)) | |
|
58 | 58 | except ParseError as e: |
|
59 | 59 | sys.stdout.write("%s: %s\n" % (sys.argv[0], e)) |
|
60 | 60 | showhelp() |
|
61 | 61 | sys.exit(1) |
|
62 | 62 | except error.Abort as e: |
|
63 | 63 | sys.stderr.write("abort: %s\n" % e) |
|
64 | 64 | sys.exit(255) |
|
65 | 65 | except KeyboardInterrupt: |
|
66 | 66 | sys.exit(255) |
@@ -1,173 +1,173 | |||
|
1 | 1 | #!/usr/bin/env python |
|
2 | 2 | # |
|
3 | 3 | # checkseclevel - checking section title levels in each online help document |
|
4 | 4 | |
|
5 | 5 | from __future__ import absolute_import |
|
6 | 6 | |
|
7 | 7 | import optparse |
|
8 | 8 | import os |
|
9 | 9 | import sys |
|
10 | 10 | |
|
11 | 11 | # import from the live mercurial repo |
|
12 | 12 | os.environ['HGMODULEPOLICY'] = 'py' |
|
13 | 13 | sys.path.insert(0, "..") |
|
14 | 14 | from mercurial import demandimport; demandimport.enable() |
|
15 | 15 | from mercurial import ( |
|
16 | 16 | commands, |
|
17 | 17 | extensions, |
|
18 | 18 | help, |
|
19 | 19 | minirst, |
|
20 | 20 | ui as uimod, |
|
21 | 21 | ) |
|
22 | 22 | |
|
23 | 23 | table = commands.table |
|
24 | 24 | helptable = help.helptable |
|
25 | 25 | |
|
26 | 26 | level2mark = ['"', '=', '-', '.', '#'] |
|
27 | 27 | reservedmarks = ['"'] |
|
28 | 28 | |
|
29 | 29 | mark2level = {} |
|
30 | 30 | for m, l in zip(level2mark, xrange(len(level2mark))): |
|
31 | 31 | if m not in reservedmarks: |
|
32 | 32 | mark2level[m] = l |
|
33 | 33 | |
|
34 | 34 | initlevel_topic = 0 |
|
35 | 35 | initlevel_cmd = 1 |
|
36 | 36 | initlevel_ext = 1 |
|
37 | 37 | initlevel_ext_cmd = 3 |
|
38 | 38 | |
|
39 | 39 | def showavailables(ui, initlevel): |
|
40 | 40 | ui.warn((' available marks and order of them in this help: %s\n') % |
|
41 | 41 | (', '.join(['%r' % (m * 4) for m in level2mark[initlevel + 1:]]))) |
|
42 | 42 | |
|
43 | 43 | def checkseclevel(ui, doc, name, initlevel): |
|
44 | 44 | ui.note(('checking "%s"\n') % name) |
|
45 | 45 | blocks, pruned = minirst.parse(doc, 0, ['verbose']) |
|
46 | 46 | errorcnt = 0 |
|
47 | 47 | curlevel = initlevel |
|
48 | 48 | for block in blocks: |
|
49 | 49 | if block['type'] != 'section': |
|
50 | 50 | continue |
|
51 | 51 | mark = block['underline'] |
|
52 | 52 | title = block['lines'][0] |
|
53 | 53 | if (mark not in mark2level) or (mark2level[mark] <= initlevel): |
|
54 | 54 | ui.warn(('invalid section mark %r for "%s" of %s\n') % |
|
55 | 55 | (mark * 4, title, name)) |
|
56 | 56 | showavailables(ui, initlevel) |
|
57 | 57 | errorcnt += 1 |
|
58 | 58 | continue |
|
59 | 59 | nextlevel = mark2level[mark] |
|
60 | 60 | if curlevel < nextlevel and curlevel + 1 != nextlevel: |
|
61 | 61 | ui.warn(('gap of section level at "%s" of %s\n') % |
|
62 | 62 | (title, name)) |
|
63 | 63 | showavailables(ui, initlevel) |
|
64 | 64 | errorcnt += 1 |
|
65 | 65 | continue |
|
66 | 66 | ui.note(('appropriate section level for "%s %s"\n') % |
|
67 | 67 | (mark * (nextlevel * 2), title)) |
|
68 | 68 | curlevel = nextlevel |
|
69 | 69 | |
|
70 | 70 | return errorcnt |
|
71 | 71 | |
|
72 | 72 | def checkcmdtable(ui, cmdtable, namefmt, initlevel): |
|
73 | 73 | errorcnt = 0 |
|
74 | 74 | for k, entry in cmdtable.items(): |
|
75 | 75 | name = k.split("|")[0].lstrip("^") |
|
76 | 76 | if not entry[0].__doc__: |
|
77 | 77 | ui.note(('skip checking %s: no help document\n') % |
|
78 | 78 | (namefmt % name)) |
|
79 | 79 | continue |
|
80 | 80 | errorcnt += checkseclevel(ui, entry[0].__doc__, |
|
81 | 81 | namefmt % name, |
|
82 | 82 | initlevel) |
|
83 | 83 | return errorcnt |
|
84 | 84 | |
|
85 | 85 | def checkhghelps(ui): |
|
86 | 86 | errorcnt = 0 |
|
87 | 87 | for names, sec, doc in helptable: |
|
88 | 88 | if callable(doc): |
|
89 | 89 | doc = doc(ui) |
|
90 | 90 | errorcnt += checkseclevel(ui, doc, |
|
91 | 91 | '%s help topic' % names[0], |
|
92 | 92 | initlevel_topic) |
|
93 | 93 | |
|
94 | 94 | errorcnt += checkcmdtable(ui, table, '%s command', initlevel_cmd) |
|
95 | 95 | |
|
96 | 96 | for name in sorted(extensions.enabled().keys() + |
|
97 | 97 | extensions.disabled().keys()): |
|
98 | 98 | mod = extensions.load(ui, name, None) |
|
99 | 99 | if not mod.__doc__: |
|
100 | 100 | ui.note(('skip checking %s extension: no help document\n') % name) |
|
101 | 101 | continue |
|
102 | 102 | errorcnt += checkseclevel(ui, mod.__doc__, |
|
103 | 103 | '%s extension' % name, |
|
104 | 104 | initlevel_ext) |
|
105 | 105 | |
|
106 | 106 | cmdtable = getattr(mod, 'cmdtable', None) |
|
107 | 107 | if cmdtable: |
|
108 | 108 | errorcnt += checkcmdtable(ui, cmdtable, |
|
109 | 109 | '%s command of ' + name + ' extension', |
|
110 | 110 | initlevel_ext_cmd) |
|
111 | 111 | return errorcnt |
|
112 | 112 | |
|
113 | 113 | def checkfile(ui, filename, initlevel): |
|
114 | 114 | if filename == '-': |
|
115 | 115 | filename = 'stdin' |
|
116 | 116 | doc = sys.stdin.read() |
|
117 | 117 | else: |
|
118 | 118 | with open(filename) as fp: |
|
119 | 119 | doc = fp.read() |
|
120 | 120 | |
|
121 | 121 | ui.note(('checking input from %s with initlevel %d\n') % |
|
122 | 122 | (filename, initlevel)) |
|
123 | 123 | return checkseclevel(ui, doc, 'input from %s' % filename, initlevel) |
|
124 | 124 | |
|
125 | 125 | def main(): |
|
126 | 126 | optparser = optparse.OptionParser("""%prog [options] |
|
127 | 127 | |
|
128 | 128 | This checks all help documents of Mercurial (topics, commands, |
|
129 | 129 | extensions and commands of them), if no file is specified by --file |
|
130 | 130 | option. |
|
131 | 131 | """) |
|
132 | 132 | optparser.add_option("-v", "--verbose", |
|
133 | 133 | help="enable additional output", |
|
134 | 134 | action="store_true") |
|
135 | 135 | optparser.add_option("-d", "--debug", |
|
136 | 136 | help="debug mode", |
|
137 | 137 | action="store_true") |
|
138 | 138 | optparser.add_option("-f", "--file", |
|
139 | 139 | help="filename to read in (or '-' for stdin)", |
|
140 | 140 | action="store", default="") |
|
141 | 141 | |
|
142 | 142 | optparser.add_option("-t", "--topic", |
|
143 | 143 | help="parse file as help topic", |
|
144 | 144 | action="store_const", dest="initlevel", const=0) |
|
145 | 145 | optparser.add_option("-c", "--command", |
|
146 | 146 | help="parse file as help of core command", |
|
147 | 147 | action="store_const", dest="initlevel", const=1) |
|
148 | 148 | optparser.add_option("-e", "--extension", |
|
149 | 149 | help="parse file as help of extension", |
|
150 | 150 | action="store_const", dest="initlevel", const=1) |
|
151 | 151 | optparser.add_option("-C", "--extension-command", |
|
152 | 152 | help="parse file as help of extension command", |
|
153 | 153 | action="store_const", dest="initlevel", const=3) |
|
154 | 154 | |
|
155 | 155 | optparser.add_option("-l", "--initlevel", |
|
156 | 156 | help="set initial section level manually", |
|
157 | 157 | action="store", type="int", default=0) |
|
158 | 158 | |
|
159 | 159 | (options, args) = optparser.parse_args() |
|
160 | 160 | |
|
161 | ui = uimod.ui() | |
|
161 | ui = uimod.ui.load() | |
|
162 | 162 | ui.setconfig('ui', 'verbose', options.verbose, '--verbose') |
|
163 | 163 | ui.setconfig('ui', 'debug', options.debug, '--debug') |
|
164 | 164 | |
|
165 | 165 | if options.file: |
|
166 | 166 | if checkfile(ui, options.file, options.initlevel): |
|
167 | 167 | sys.exit(1) |
|
168 | 168 | else: |
|
169 | 169 | if checkhghelps(ui): |
|
170 | 170 | sys.exit(1) |
|
171 | 171 | |
|
172 | 172 | if __name__ == "__main__": |
|
173 | 173 | main() |
@@ -1,224 +1,224 | |||
|
1 | 1 | #!/usr/bin/env python |
|
2 | 2 | """usage: %s DOC ... |
|
3 | 3 | |
|
4 | 4 | where DOC is the name of a document |
|
5 | 5 | """ |
|
6 | 6 | |
|
7 | 7 | from __future__ import absolute_import |
|
8 | 8 | |
|
9 | 9 | import os |
|
10 | 10 | import sys |
|
11 | 11 | import textwrap |
|
12 | 12 | |
|
13 | 13 | # This script is executed during installs and may not have C extensions |
|
14 | 14 | # available. Relax C module requirements. |
|
15 | 15 | os.environ['HGMODULEPOLICY'] = 'allow' |
|
16 | 16 | # import from the live mercurial repo |
|
17 | 17 | sys.path.insert(0, "..") |
|
18 | 18 | from mercurial import demandimport; demandimport.enable() |
|
19 | 19 | from mercurial import ( |
|
20 | 20 | commands, |
|
21 | 21 | extensions, |
|
22 | 22 | help, |
|
23 | 23 | minirst, |
|
24 | 24 | ui as uimod, |
|
25 | 25 | ) |
|
26 | 26 | from mercurial.i18n import ( |
|
27 | 27 | gettext, |
|
28 | 28 | _, |
|
29 | 29 | ) |
|
30 | 30 | |
|
31 | 31 | table = commands.table |
|
32 | 32 | globalopts = commands.globalopts |
|
33 | 33 | helptable = help.helptable |
|
34 | 34 | loaddoc = help.loaddoc |
|
35 | 35 | |
|
36 | 36 | def get_desc(docstr): |
|
37 | 37 | if not docstr: |
|
38 | 38 | return "", "" |
|
39 | 39 | # sanitize |
|
40 | 40 | docstr = docstr.strip("\n") |
|
41 | 41 | docstr = docstr.rstrip() |
|
42 | 42 | shortdesc = docstr.splitlines()[0].strip() |
|
43 | 43 | |
|
44 | 44 | i = docstr.find("\n") |
|
45 | 45 | if i != -1: |
|
46 | 46 | desc = docstr[i + 2:] |
|
47 | 47 | else: |
|
48 | 48 | desc = shortdesc |
|
49 | 49 | |
|
50 | 50 | desc = textwrap.dedent(desc) |
|
51 | 51 | |
|
52 | 52 | return (shortdesc, desc) |
|
53 | 53 | |
|
54 | 54 | def get_opts(opts): |
|
55 | 55 | for opt in opts: |
|
56 | 56 | if len(opt) == 5: |
|
57 | 57 | shortopt, longopt, default, desc, optlabel = opt |
|
58 | 58 | else: |
|
59 | 59 | shortopt, longopt, default, desc = opt |
|
60 | 60 | optlabel = _("VALUE") |
|
61 | 61 | allopts = [] |
|
62 | 62 | if shortopt: |
|
63 | 63 | allopts.append("-%s" % shortopt) |
|
64 | 64 | if longopt: |
|
65 | 65 | allopts.append("--%s" % longopt) |
|
66 | 66 | if isinstance(default, list): |
|
67 | 67 | allopts[-1] += " <%s[+]>" % optlabel |
|
68 | 68 | elif (default is not None) and not isinstance(default, bool): |
|
69 | 69 | allopts[-1] += " <%s>" % optlabel |
|
70 | 70 | if '\n' in desc: |
|
71 | 71 | # only remove line breaks and indentation |
|
72 | 72 | desc = ' '.join(l.lstrip() for l in desc.split('\n')) |
|
73 | 73 | desc += default and _(" (default: %s)") % default or "" |
|
74 | 74 | yield (", ".join(allopts), desc) |
|
75 | 75 | |
|
76 | 76 | def get_cmd(cmd, cmdtable): |
|
77 | 77 | d = {} |
|
78 | 78 | attr = cmdtable[cmd] |
|
79 | 79 | cmds = cmd.lstrip("^").split("|") |
|
80 | 80 | |
|
81 | 81 | d['cmd'] = cmds[0] |
|
82 | 82 | d['aliases'] = cmd.split("|")[1:] |
|
83 | 83 | d['desc'] = get_desc(gettext(attr[0].__doc__)) |
|
84 | 84 | d['opts'] = list(get_opts(attr[1])) |
|
85 | 85 | |
|
86 | 86 | s = 'hg ' + cmds[0] |
|
87 | 87 | if len(attr) > 2: |
|
88 | 88 | if not attr[2].startswith('hg'): |
|
89 | 89 | s += ' ' + attr[2] |
|
90 | 90 | else: |
|
91 | 91 | s = attr[2] |
|
92 | 92 | d['synopsis'] = s.strip() |
|
93 | 93 | |
|
94 | 94 | return d |
|
95 | 95 | |
|
96 | 96 | def showdoc(ui): |
|
97 | 97 | # print options |
|
98 | 98 | ui.write(minirst.section(_("Options"))) |
|
99 | 99 | multioccur = False |
|
100 | 100 | for optstr, desc in get_opts(globalopts): |
|
101 | 101 | ui.write("%s\n %s\n\n" % (optstr, desc)) |
|
102 | 102 | if optstr.endswith("[+]>"): |
|
103 | 103 | multioccur = True |
|
104 | 104 | if multioccur: |
|
105 | 105 | ui.write(_("\n[+] marked option can be specified multiple times\n")) |
|
106 | 106 | ui.write("\n") |
|
107 | 107 | |
|
108 | 108 | # print cmds |
|
109 | 109 | ui.write(minirst.section(_("Commands"))) |
|
110 | 110 | commandprinter(ui, table, minirst.subsection) |
|
111 | 111 | |
|
112 | 112 | # print help topics |
|
113 | 113 | # The config help topic is included in the hgrc.5 man page. |
|
114 | 114 | helpprinter(ui, helptable, minirst.section, exclude=['config']) |
|
115 | 115 | |
|
116 | 116 | ui.write(minirst.section(_("Extensions"))) |
|
117 | 117 | ui.write(_("This section contains help for extensions that are " |
|
118 | 118 | "distributed together with Mercurial. Help for other " |
|
119 | 119 | "extensions is available in the help system.")) |
|
120 | 120 | ui.write(("\n\n" |
|
121 | 121 | ".. contents::\n" |
|
122 | 122 | " :class: htmlonly\n" |
|
123 | 123 | " :local:\n" |
|
124 | 124 | " :depth: 1\n\n")) |
|
125 | 125 | |
|
126 | 126 | for extensionname in sorted(allextensionnames()): |
|
127 | 127 | mod = extensions.load(ui, extensionname, None) |
|
128 | 128 | ui.write(minirst.subsection(extensionname)) |
|
129 | 129 | ui.write("%s\n\n" % gettext(mod.__doc__)) |
|
130 | 130 | cmdtable = getattr(mod, 'cmdtable', None) |
|
131 | 131 | if cmdtable: |
|
132 | 132 | ui.write(minirst.subsubsection(_('Commands'))) |
|
133 | 133 | commandprinter(ui, cmdtable, minirst.subsubsubsection) |
|
134 | 134 | |
|
135 | 135 | def showtopic(ui, topic): |
|
136 | 136 | extrahelptable = [ |
|
137 | 137 | (["common"], '', loaddoc('common')), |
|
138 | 138 | (["hg.1"], '', loaddoc('hg.1')), |
|
139 | 139 | (["hg-ssh.8"], '', loaddoc('hg-ssh.8')), |
|
140 | 140 | (["hgignore.5"], '', loaddoc('hgignore.5')), |
|
141 | 141 | (["hgrc.5"], '', loaddoc('hgrc.5')), |
|
142 | 142 | (["hgignore.5.gendoc"], '', loaddoc('hgignore')), |
|
143 | 143 | (["hgrc.5.gendoc"], '', loaddoc('config')), |
|
144 | 144 | ] |
|
145 | 145 | helpprinter(ui, helptable + extrahelptable, None, include=[topic]) |
|
146 | 146 | |
|
147 | 147 | def helpprinter(ui, helptable, sectionfunc, include=[], exclude=[]): |
|
148 | 148 | for names, sec, doc in helptable: |
|
149 | 149 | if exclude and names[0] in exclude: |
|
150 | 150 | continue |
|
151 | 151 | if include and names[0] not in include: |
|
152 | 152 | continue |
|
153 | 153 | for name in names: |
|
154 | 154 | ui.write(".. _%s:\n" % name) |
|
155 | 155 | ui.write("\n") |
|
156 | 156 | if sectionfunc: |
|
157 | 157 | ui.write(sectionfunc(sec)) |
|
158 | 158 | if callable(doc): |
|
159 | 159 | doc = doc(ui) |
|
160 | 160 | ui.write(doc) |
|
161 | 161 | ui.write("\n") |
|
162 | 162 | |
|
163 | 163 | def commandprinter(ui, cmdtable, sectionfunc): |
|
164 | 164 | h = {} |
|
165 | 165 | for c, attr in cmdtable.items(): |
|
166 | 166 | f = c.split("|")[0] |
|
167 | 167 | f = f.lstrip("^") |
|
168 | 168 | h[f] = c |
|
169 | 169 | cmds = h.keys() |
|
170 | 170 | cmds.sort() |
|
171 | 171 | |
|
172 | 172 | for f in cmds: |
|
173 | 173 | if f.startswith("debug"): |
|
174 | 174 | continue |
|
175 | 175 | d = get_cmd(h[f], cmdtable) |
|
176 | 176 | ui.write(sectionfunc(d['cmd'])) |
|
177 | 177 | # short description |
|
178 | 178 | ui.write(d['desc'][0]) |
|
179 | 179 | # synopsis |
|
180 | 180 | ui.write("::\n\n") |
|
181 | 181 | synopsislines = d['synopsis'].splitlines() |
|
182 | 182 | for line in synopsislines: |
|
183 | 183 | # some commands (such as rebase) have a multi-line |
|
184 | 184 | # synopsis |
|
185 | 185 | ui.write(" %s\n" % line) |
|
186 | 186 | ui.write('\n') |
|
187 | 187 | # description |
|
188 | 188 | ui.write("%s\n\n" % d['desc'][1]) |
|
189 | 189 | # options |
|
190 | 190 | opt_output = list(d['opts']) |
|
191 | 191 | if opt_output: |
|
192 | 192 | opts_len = max([len(line[0]) for line in opt_output]) |
|
193 | 193 | ui.write(_("Options:\n\n")) |
|
194 | 194 | multioccur = False |
|
195 | 195 | for optstr, desc in opt_output: |
|
196 | 196 | if desc: |
|
197 | 197 | s = "%-*s %s" % (opts_len, optstr, desc) |
|
198 | 198 | else: |
|
199 | 199 | s = optstr |
|
200 | 200 | ui.write("%s\n" % s) |
|
201 | 201 | if optstr.endswith("[+]>"): |
|
202 | 202 | multioccur = True |
|
203 | 203 | if multioccur: |
|
204 | 204 | ui.write(_("\n[+] marked option can be specified" |
|
205 | 205 | " multiple times\n")) |
|
206 | 206 | ui.write("\n") |
|
207 | 207 | # aliases |
|
208 | 208 | if d['aliases']: |
|
209 | 209 | ui.write(_(" aliases: %s\n\n") % " ".join(d['aliases'])) |
|
210 | 210 | |
|
211 | 211 | |
|
212 | 212 | def allextensionnames(): |
|
213 | 213 | return extensions.enabled().keys() + extensions.disabled().keys() |
|
214 | 214 | |
|
215 | 215 | if __name__ == "__main__": |
|
216 | 216 | doc = 'hg.1.gendoc' |
|
217 | 217 | if len(sys.argv) > 1: |
|
218 | 218 | doc = sys.argv[1] |
|
219 | 219 | |
|
220 | ui = uimod.ui() | |
|
220 | ui = uimod.ui.load() | |
|
221 | 221 | if doc == 'hg.1.gendoc': |
|
222 | 222 | showdoc(ui) |
|
223 | 223 | else: |
|
224 | 224 | showtopic(ui, sys.argv[1]) |
@@ -1,885 +1,885 | |||
|
1 | 1 | # dispatch.py - command dispatching for mercurial |
|
2 | 2 | # |
|
3 | 3 | # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> |
|
4 | 4 | # |
|
5 | 5 | # This software may be used and distributed according to the terms of the |
|
6 | 6 | # GNU General Public License version 2 or any later version. |
|
7 | 7 | |
|
8 | 8 | from __future__ import absolute_import, print_function |
|
9 | 9 | |
|
10 | 10 | import atexit |
|
11 | 11 | import difflib |
|
12 | 12 | import errno |
|
13 | 13 | import os |
|
14 | 14 | import pdb |
|
15 | 15 | import re |
|
16 | 16 | import shlex |
|
17 | 17 | import signal |
|
18 | 18 | import sys |
|
19 | 19 | import time |
|
20 | 20 | import traceback |
|
21 | 21 | |
|
22 | 22 | |
|
23 | 23 | from .i18n import _ |
|
24 | 24 | |
|
25 | 25 | from . import ( |
|
26 | 26 | cmdutil, |
|
27 | 27 | commands, |
|
28 | 28 | debugcommands, |
|
29 | 29 | demandimport, |
|
30 | 30 | encoding, |
|
31 | 31 | error, |
|
32 | 32 | extensions, |
|
33 | 33 | fancyopts, |
|
34 | 34 | fileset, |
|
35 | 35 | hg, |
|
36 | 36 | hook, |
|
37 | 37 | profiling, |
|
38 | 38 | pycompat, |
|
39 | 39 | revset, |
|
40 | 40 | scmutil, |
|
41 | 41 | templatefilters, |
|
42 | 42 | templatekw, |
|
43 | 43 | templater, |
|
44 | 44 | ui as uimod, |
|
45 | 45 | util, |
|
46 | 46 | ) |
|
47 | 47 | |
|
48 | 48 | class request(object): |
|
49 | 49 | def __init__(self, args, ui=None, repo=None, fin=None, fout=None, |
|
50 | 50 | ferr=None): |
|
51 | 51 | self.args = args |
|
52 | 52 | self.ui = ui |
|
53 | 53 | self.repo = repo |
|
54 | 54 | |
|
55 | 55 | # input/output/error streams |
|
56 | 56 | self.fin = fin |
|
57 | 57 | self.fout = fout |
|
58 | 58 | self.ferr = ferr |
|
59 | 59 | |
|
60 | 60 | def run(): |
|
61 | 61 | "run the command in sys.argv" |
|
62 | 62 | sys.exit((dispatch(request(pycompat.sysargv[1:])) or 0) & 255) |
|
63 | 63 | |
|
64 | 64 | def _getsimilar(symbols, value): |
|
65 | 65 | sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio() |
|
66 | 66 | # The cutoff for similarity here is pretty arbitrary. It should |
|
67 | 67 | # probably be investigated and tweaked. |
|
68 | 68 | return [s for s in symbols if sim(s) > 0.6] |
|
69 | 69 | |
|
70 | 70 | def _reportsimilar(write, similar): |
|
71 | 71 | if len(similar) == 1: |
|
72 | 72 | write(_("(did you mean %s?)\n") % similar[0]) |
|
73 | 73 | elif similar: |
|
74 | 74 | ss = ", ".join(sorted(similar)) |
|
75 | 75 | write(_("(did you mean one of %s?)\n") % ss) |
|
76 | 76 | |
|
77 | 77 | def _formatparse(write, inst): |
|
78 | 78 | similar = [] |
|
79 | 79 | if isinstance(inst, error.UnknownIdentifier): |
|
80 | 80 | # make sure to check fileset first, as revset can invoke fileset |
|
81 | 81 | similar = _getsimilar(inst.symbols, inst.function) |
|
82 | 82 | if len(inst.args) > 1: |
|
83 | 83 | write(_("hg: parse error at %s: %s\n") % |
|
84 | 84 | (inst.args[1], inst.args[0])) |
|
85 | 85 | if (inst.args[0][0] == ' '): |
|
86 | 86 | write(_("unexpected leading whitespace\n")) |
|
87 | 87 | else: |
|
88 | 88 | write(_("hg: parse error: %s\n") % inst.args[0]) |
|
89 | 89 | _reportsimilar(write, similar) |
|
90 | 90 | if inst.hint: |
|
91 | 91 | write(_("(%s)\n") % inst.hint) |
|
92 | 92 | |
|
93 | 93 | def dispatch(req): |
|
94 | 94 | "run the command specified in req.args" |
|
95 | 95 | if req.ferr: |
|
96 | 96 | ferr = req.ferr |
|
97 | 97 | elif req.ui: |
|
98 | 98 | ferr = req.ui.ferr |
|
99 | 99 | else: |
|
100 | 100 | ferr = util.stderr |
|
101 | 101 | |
|
102 | 102 | try: |
|
103 | 103 | if not req.ui: |
|
104 | req.ui = uimod.ui() | |
|
104 | req.ui = uimod.ui.load() | |
|
105 | 105 | if '--traceback' in req.args: |
|
106 | 106 | req.ui.setconfig('ui', 'traceback', 'on', '--traceback') |
|
107 | 107 | |
|
108 | 108 | # set ui streams from the request |
|
109 | 109 | if req.fin: |
|
110 | 110 | req.ui.fin = req.fin |
|
111 | 111 | if req.fout: |
|
112 | 112 | req.ui.fout = req.fout |
|
113 | 113 | if req.ferr: |
|
114 | 114 | req.ui.ferr = req.ferr |
|
115 | 115 | except error.Abort as inst: |
|
116 | 116 | ferr.write(_("abort: %s\n") % inst) |
|
117 | 117 | if inst.hint: |
|
118 | 118 | ferr.write(_("(%s)\n") % inst.hint) |
|
119 | 119 | return -1 |
|
120 | 120 | except error.ParseError as inst: |
|
121 | 121 | _formatparse(ferr.write, inst) |
|
122 | 122 | return -1 |
|
123 | 123 | |
|
124 | 124 | msg = ' '.join(' ' in a and repr(a) or a for a in req.args) |
|
125 | 125 | starttime = time.time() |
|
126 | 126 | ret = None |
|
127 | 127 | try: |
|
128 | 128 | ret = _runcatch(req) |
|
129 | 129 | except KeyboardInterrupt: |
|
130 | 130 | try: |
|
131 | 131 | req.ui.warn(_("interrupted!\n")) |
|
132 | 132 | except IOError as inst: |
|
133 | 133 | if inst.errno != errno.EPIPE: |
|
134 | 134 | raise |
|
135 | 135 | ret = -1 |
|
136 | 136 | finally: |
|
137 | 137 | duration = time.time() - starttime |
|
138 | 138 | req.ui.flush() |
|
139 | 139 | req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n", |
|
140 | 140 | msg, ret or 0, duration) |
|
141 | 141 | return ret |
|
142 | 142 | |
|
143 | 143 | def _runcatch(req): |
|
144 | 144 | def catchterm(*args): |
|
145 | 145 | raise error.SignalInterrupt |
|
146 | 146 | |
|
147 | 147 | ui = req.ui |
|
148 | 148 | try: |
|
149 | 149 | for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM': |
|
150 | 150 | num = getattr(signal, name, None) |
|
151 | 151 | if num: |
|
152 | 152 | signal.signal(num, catchterm) |
|
153 | 153 | except ValueError: |
|
154 | 154 | pass # happens if called in a thread |
|
155 | 155 | |
|
156 | 156 | def _runcatchfunc(): |
|
157 | 157 | try: |
|
158 | 158 | debugger = 'pdb' |
|
159 | 159 | debugtrace = { |
|
160 | 160 | 'pdb' : pdb.set_trace |
|
161 | 161 | } |
|
162 | 162 | debugmortem = { |
|
163 | 163 | 'pdb' : pdb.post_mortem |
|
164 | 164 | } |
|
165 | 165 | |
|
166 | 166 | # read --config before doing anything else |
|
167 | 167 | # (e.g. to change trust settings for reading .hg/hgrc) |
|
168 | 168 | cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args)) |
|
169 | 169 | |
|
170 | 170 | if req.repo: |
|
171 | 171 | # copy configs that were passed on the cmdline (--config) to |
|
172 | 172 | # the repo ui |
|
173 | 173 | for sec, name, val in cfgs: |
|
174 | 174 | req.repo.ui.setconfig(sec, name, val, source='--config') |
|
175 | 175 | |
|
176 | 176 | # developer config: ui.debugger |
|
177 | 177 | debugger = ui.config("ui", "debugger") |
|
178 | 178 | debugmod = pdb |
|
179 | 179 | if not debugger or ui.plain(): |
|
180 | 180 | # if we are in HGPLAIN mode, then disable custom debugging |
|
181 | 181 | debugger = 'pdb' |
|
182 | 182 | elif '--debugger' in req.args: |
|
183 | 183 | # This import can be slow for fancy debuggers, so only |
|
184 | 184 | # do it when absolutely necessary, i.e. when actual |
|
185 | 185 | # debugging has been requested |
|
186 | 186 | with demandimport.deactivated(): |
|
187 | 187 | try: |
|
188 | 188 | debugmod = __import__(debugger) |
|
189 | 189 | except ImportError: |
|
190 | 190 | pass # Leave debugmod = pdb |
|
191 | 191 | |
|
192 | 192 | debugtrace[debugger] = debugmod.set_trace |
|
193 | 193 | debugmortem[debugger] = debugmod.post_mortem |
|
194 | 194 | |
|
195 | 195 | # enter the debugger before command execution |
|
196 | 196 | if '--debugger' in req.args: |
|
197 | 197 | ui.warn(_("entering debugger - " |
|
198 | 198 | "type c to continue starting hg or h for help\n")) |
|
199 | 199 | |
|
200 | 200 | if (debugger != 'pdb' and |
|
201 | 201 | debugtrace[debugger] == debugtrace['pdb']): |
|
202 | 202 | ui.warn(_("%s debugger specified " |
|
203 | 203 | "but its module was not found\n") % debugger) |
|
204 | 204 | with demandimport.deactivated(): |
|
205 | 205 | debugtrace[debugger]() |
|
206 | 206 | try: |
|
207 | 207 | return _dispatch(req) |
|
208 | 208 | finally: |
|
209 | 209 | ui.flush() |
|
210 | 210 | except: # re-raises |
|
211 | 211 | # enter the debugger when we hit an exception |
|
212 | 212 | if '--debugger' in req.args: |
|
213 | 213 | traceback.print_exc() |
|
214 | 214 | debugmortem[debugger](sys.exc_info()[2]) |
|
215 | 215 | ui.traceback() |
|
216 | 216 | raise |
|
217 | 217 | |
|
218 | 218 | return callcatch(ui, _runcatchfunc) |
|
219 | 219 | |
|
220 | 220 | def callcatch(ui, func): |
|
221 | 221 | """like scmutil.callcatch but handles more high-level exceptions about |
|
222 | 222 | config parsing and commands. besides, use handlecommandexception to handle |
|
223 | 223 | uncaught exceptions. |
|
224 | 224 | """ |
|
225 | 225 | try: |
|
226 | 226 | return scmutil.callcatch(ui, func) |
|
227 | 227 | except error.AmbiguousCommand as inst: |
|
228 | 228 | ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") % |
|
229 | 229 | (inst.args[0], " ".join(inst.args[1]))) |
|
230 | 230 | except error.CommandError as inst: |
|
231 | 231 | if inst.args[0]: |
|
232 | 232 | ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1])) |
|
233 | 233 | commands.help_(ui, inst.args[0], full=False, command=True) |
|
234 | 234 | else: |
|
235 | 235 | ui.warn(_("hg: %s\n") % inst.args[1]) |
|
236 | 236 | commands.help_(ui, 'shortlist') |
|
237 | 237 | except error.ParseError as inst: |
|
238 | 238 | _formatparse(ui.warn, inst) |
|
239 | 239 | return -1 |
|
240 | 240 | except error.UnknownCommand as inst: |
|
241 | 241 | ui.warn(_("hg: unknown command '%s'\n") % inst.args[0]) |
|
242 | 242 | try: |
|
243 | 243 | # check if the command is in a disabled extension |
|
244 | 244 | # (but don't check for extensions themselves) |
|
245 | 245 | commands.help_(ui, inst.args[0], unknowncmd=True) |
|
246 | 246 | except (error.UnknownCommand, error.Abort): |
|
247 | 247 | suggested = False |
|
248 | 248 | if len(inst.args) == 2: |
|
249 | 249 | sim = _getsimilar(inst.args[1], inst.args[0]) |
|
250 | 250 | if sim: |
|
251 | 251 | _reportsimilar(ui.warn, sim) |
|
252 | 252 | suggested = True |
|
253 | 253 | if not suggested: |
|
254 | 254 | commands.help_(ui, 'shortlist') |
|
255 | 255 | except IOError: |
|
256 | 256 | raise |
|
257 | 257 | except KeyboardInterrupt: |
|
258 | 258 | raise |
|
259 | 259 | except: # probably re-raises |
|
260 | 260 | if not handlecommandexception(ui): |
|
261 | 261 | raise |
|
262 | 262 | |
|
263 | 263 | return -1 |
|
264 | 264 | |
|
265 | 265 | def aliasargs(fn, givenargs): |
|
266 | 266 | args = getattr(fn, 'args', []) |
|
267 | 267 | if args: |
|
268 | 268 | cmd = ' '.join(map(util.shellquote, args)) |
|
269 | 269 | |
|
270 | 270 | nums = [] |
|
271 | 271 | def replacer(m): |
|
272 | 272 | num = int(m.group(1)) - 1 |
|
273 | 273 | nums.append(num) |
|
274 | 274 | if num < len(givenargs): |
|
275 | 275 | return givenargs[num] |
|
276 | 276 | raise error.Abort(_('too few arguments for command alias')) |
|
277 | 277 | cmd = re.sub(r'\$(\d+|\$)', replacer, cmd) |
|
278 | 278 | givenargs = [x for i, x in enumerate(givenargs) |
|
279 | 279 | if i not in nums] |
|
280 | 280 | args = shlex.split(cmd) |
|
281 | 281 | return args + givenargs |
|
282 | 282 | |
|
283 | 283 | def aliasinterpolate(name, args, cmd): |
|
284 | 284 | '''interpolate args into cmd for shell aliases |
|
285 | 285 | |
|
286 | 286 | This also handles $0, $@ and "$@". |
|
287 | 287 | ''' |
|
288 | 288 | # util.interpolate can't deal with "$@" (with quotes) because it's only |
|
289 | 289 | # built to match prefix + patterns. |
|
290 | 290 | replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args)) |
|
291 | 291 | replacemap['$0'] = name |
|
292 | 292 | replacemap['$$'] = '$' |
|
293 | 293 | replacemap['$@'] = ' '.join(args) |
|
294 | 294 | # Typical Unix shells interpolate "$@" (with quotes) as all the positional |
|
295 | 295 | # parameters, separated out into words. Emulate the same behavior here by |
|
296 | 296 | # quoting the arguments individually. POSIX shells will then typically |
|
297 | 297 | # tokenize each argument into exactly one word. |
|
298 | 298 | replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args) |
|
299 | 299 | # escape '\$' for regex |
|
300 | 300 | regex = '|'.join(replacemap.keys()).replace('$', r'\$') |
|
301 | 301 | r = re.compile(regex) |
|
302 | 302 | return r.sub(lambda x: replacemap[x.group()], cmd) |
|
303 | 303 | |
|
304 | 304 | class cmdalias(object): |
|
305 | 305 | def __init__(self, name, definition, cmdtable, source): |
|
306 | 306 | self.name = self.cmd = name |
|
307 | 307 | self.cmdname = '' |
|
308 | 308 | self.definition = definition |
|
309 | 309 | self.fn = None |
|
310 | 310 | self.givenargs = [] |
|
311 | 311 | self.opts = [] |
|
312 | 312 | self.help = '' |
|
313 | 313 | self.badalias = None |
|
314 | 314 | self.unknowncmd = False |
|
315 | 315 | self.source = source |
|
316 | 316 | |
|
317 | 317 | try: |
|
318 | 318 | aliases, entry = cmdutil.findcmd(self.name, cmdtable) |
|
319 | 319 | for alias, e in cmdtable.iteritems(): |
|
320 | 320 | if e is entry: |
|
321 | 321 | self.cmd = alias |
|
322 | 322 | break |
|
323 | 323 | self.shadows = True |
|
324 | 324 | except error.UnknownCommand: |
|
325 | 325 | self.shadows = False |
|
326 | 326 | |
|
327 | 327 | if not self.definition: |
|
328 | 328 | self.badalias = _("no definition for alias '%s'") % self.name |
|
329 | 329 | return |
|
330 | 330 | |
|
331 | 331 | if self.definition.startswith('!'): |
|
332 | 332 | self.shell = True |
|
333 | 333 | def fn(ui, *args): |
|
334 | 334 | env = {'HG_ARGS': ' '.join((self.name,) + args)} |
|
335 | 335 | def _checkvar(m): |
|
336 | 336 | if m.groups()[0] == '$': |
|
337 | 337 | return m.group() |
|
338 | 338 | elif int(m.groups()[0]) <= len(args): |
|
339 | 339 | return m.group() |
|
340 | 340 | else: |
|
341 | 341 | ui.debug("No argument found for substitution " |
|
342 | 342 | "of %i variable in alias '%s' definition." |
|
343 | 343 | % (int(m.groups()[0]), self.name)) |
|
344 | 344 | return '' |
|
345 | 345 | cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:]) |
|
346 | 346 | cmd = aliasinterpolate(self.name, args, cmd) |
|
347 | 347 | return ui.system(cmd, environ=env) |
|
348 | 348 | self.fn = fn |
|
349 | 349 | return |
|
350 | 350 | |
|
351 | 351 | try: |
|
352 | 352 | args = shlex.split(self.definition) |
|
353 | 353 | except ValueError as inst: |
|
354 | 354 | self.badalias = (_("error in definition for alias '%s': %s") |
|
355 | 355 | % (self.name, inst)) |
|
356 | 356 | return |
|
357 | 357 | self.cmdname = cmd = args.pop(0) |
|
358 | 358 | self.givenargs = args |
|
359 | 359 | |
|
360 | 360 | for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"): |
|
361 | 361 | if _earlygetopt([invalidarg], args): |
|
362 | 362 | self.badalias = (_("error in definition for alias '%s': %s may " |
|
363 | 363 | "only be given on the command line") |
|
364 | 364 | % (self.name, invalidarg)) |
|
365 | 365 | return |
|
366 | 366 | |
|
367 | 367 | try: |
|
368 | 368 | tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1] |
|
369 | 369 | if len(tableentry) > 2: |
|
370 | 370 | self.fn, self.opts, self.help = tableentry |
|
371 | 371 | else: |
|
372 | 372 | self.fn, self.opts = tableentry |
|
373 | 373 | |
|
374 | 374 | if self.help.startswith("hg " + cmd): |
|
375 | 375 | # drop prefix in old-style help lines so hg shows the alias |
|
376 | 376 | self.help = self.help[4 + len(cmd):] |
|
377 | 377 | self.__doc__ = self.fn.__doc__ |
|
378 | 378 | |
|
379 | 379 | except error.UnknownCommand: |
|
380 | 380 | self.badalias = (_("alias '%s' resolves to unknown command '%s'") |
|
381 | 381 | % (self.name, cmd)) |
|
382 | 382 | self.unknowncmd = True |
|
383 | 383 | except error.AmbiguousCommand: |
|
384 | 384 | self.badalias = (_("alias '%s' resolves to ambiguous command '%s'") |
|
385 | 385 | % (self.name, cmd)) |
|
386 | 386 | |
|
387 | 387 | @property |
|
388 | 388 | def args(self): |
|
389 | 389 | args = map(util.expandpath, self.givenargs) |
|
390 | 390 | return aliasargs(self.fn, args) |
|
391 | 391 | |
|
392 | 392 | def __getattr__(self, name): |
|
393 | 393 | adefaults = {'norepo': True, 'optionalrepo': False, 'inferrepo': False} |
|
394 | 394 | if name not in adefaults: |
|
395 | 395 | raise AttributeError(name) |
|
396 | 396 | if self.badalias or util.safehasattr(self, 'shell'): |
|
397 | 397 | return adefaults[name] |
|
398 | 398 | return getattr(self.fn, name) |
|
399 | 399 | |
|
400 | 400 | def __call__(self, ui, *args, **opts): |
|
401 | 401 | if self.badalias: |
|
402 | 402 | hint = None |
|
403 | 403 | if self.unknowncmd: |
|
404 | 404 | try: |
|
405 | 405 | # check if the command is in a disabled extension |
|
406 | 406 | cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2] |
|
407 | 407 | hint = _("'%s' is provided by '%s' extension") % (cmd, ext) |
|
408 | 408 | except error.UnknownCommand: |
|
409 | 409 | pass |
|
410 | 410 | raise error.Abort(self.badalias, hint=hint) |
|
411 | 411 | if self.shadows: |
|
412 | 412 | ui.debug("alias '%s' shadows command '%s'\n" % |
|
413 | 413 | (self.name, self.cmdname)) |
|
414 | 414 | |
|
415 | 415 | ui.log('commandalias', "alias '%s' expands to '%s'\n", |
|
416 | 416 | self.name, self.definition) |
|
417 | 417 | if util.safehasattr(self, 'shell'): |
|
418 | 418 | return self.fn(ui, *args, **opts) |
|
419 | 419 | else: |
|
420 | 420 | try: |
|
421 | 421 | return util.checksignature(self.fn)(ui, *args, **opts) |
|
422 | 422 | except error.SignatureError: |
|
423 | 423 | args = ' '.join([self.cmdname] + self.args) |
|
424 | 424 | ui.debug("alias '%s' expands to '%s'\n" % (self.name, args)) |
|
425 | 425 | raise |
|
426 | 426 | |
|
427 | 427 | def addaliases(ui, cmdtable): |
|
428 | 428 | # aliases are processed after extensions have been loaded, so they |
|
429 | 429 | # may use extension commands. Aliases can also use other alias definitions, |
|
430 | 430 | # but only if they have been defined prior to the current definition. |
|
431 | 431 | for alias, definition in ui.configitems('alias'): |
|
432 | 432 | source = ui.configsource('alias', alias) |
|
433 | 433 | aliasdef = cmdalias(alias, definition, cmdtable, source) |
|
434 | 434 | |
|
435 | 435 | try: |
|
436 | 436 | olddef = cmdtable[aliasdef.cmd][0] |
|
437 | 437 | if olddef.definition == aliasdef.definition: |
|
438 | 438 | continue |
|
439 | 439 | except (KeyError, AttributeError): |
|
440 | 440 | # definition might not exist or it might not be a cmdalias |
|
441 | 441 | pass |
|
442 | 442 | |
|
443 | 443 | cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help) |
|
444 | 444 | |
|
445 | 445 | def _parse(ui, args): |
|
446 | 446 | options = {} |
|
447 | 447 | cmdoptions = {} |
|
448 | 448 | |
|
449 | 449 | try: |
|
450 | 450 | args = fancyopts.fancyopts(args, commands.globalopts, options) |
|
451 | 451 | except fancyopts.getopt.GetoptError as inst: |
|
452 | 452 | raise error.CommandError(None, inst) |
|
453 | 453 | |
|
454 | 454 | if args: |
|
455 | 455 | cmd, args = args[0], args[1:] |
|
456 | 456 | aliases, entry = cmdutil.findcmd(cmd, commands.table, |
|
457 | 457 | ui.configbool("ui", "strict")) |
|
458 | 458 | cmd = aliases[0] |
|
459 | 459 | args = aliasargs(entry[0], args) |
|
460 | 460 | defaults = ui.config("defaults", cmd) |
|
461 | 461 | if defaults: |
|
462 | 462 | args = map(util.expandpath, shlex.split(defaults)) + args |
|
463 | 463 | c = list(entry[1]) |
|
464 | 464 | else: |
|
465 | 465 | cmd = None |
|
466 | 466 | c = [] |
|
467 | 467 | |
|
468 | 468 | # combine global options into local |
|
469 | 469 | for o in commands.globalopts: |
|
470 | 470 | c.append((o[0], o[1], options[o[1]], o[3])) |
|
471 | 471 | |
|
472 | 472 | try: |
|
473 | 473 | args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True) |
|
474 | 474 | except fancyopts.getopt.GetoptError as inst: |
|
475 | 475 | raise error.CommandError(cmd, inst) |
|
476 | 476 | |
|
477 | 477 | # separate global options back out |
|
478 | 478 | for o in commands.globalopts: |
|
479 | 479 | n = o[1] |
|
480 | 480 | options[n] = cmdoptions[n] |
|
481 | 481 | del cmdoptions[n] |
|
482 | 482 | |
|
483 | 483 | return (cmd, cmd and entry[0] or None, args, options, cmdoptions) |
|
484 | 484 | |
|
485 | 485 | def _parseconfig(ui, config): |
|
486 | 486 | """parse the --config options from the command line""" |
|
487 | 487 | configs = [] |
|
488 | 488 | |
|
489 | 489 | for cfg in config: |
|
490 | 490 | try: |
|
491 | 491 | name, value = [cfgelem.strip() |
|
492 | 492 | for cfgelem in cfg.split('=', 1)] |
|
493 | 493 | section, name = name.split('.', 1) |
|
494 | 494 | if not section or not name: |
|
495 | 495 | raise IndexError |
|
496 | 496 | ui.setconfig(section, name, value, '--config') |
|
497 | 497 | configs.append((section, name, value)) |
|
498 | 498 | except (IndexError, ValueError): |
|
499 | 499 | raise error.Abort(_('malformed --config option: %r ' |
|
500 | 500 | '(use --config section.name=value)') % cfg) |
|
501 | 501 | |
|
502 | 502 | return configs |
|
503 | 503 | |
|
504 | 504 | def _earlygetopt(aliases, args): |
|
505 | 505 | """Return list of values for an option (or aliases). |
|
506 | 506 | |
|
507 | 507 | The values are listed in the order they appear in args. |
|
508 | 508 | The options and values are removed from args. |
|
509 | 509 | |
|
510 | 510 | >>> args = ['x', '--cwd', 'foo', 'y'] |
|
511 | 511 | >>> _earlygetopt(['--cwd'], args), args |
|
512 | 512 | (['foo'], ['x', 'y']) |
|
513 | 513 | |
|
514 | 514 | >>> args = ['x', '--cwd=bar', 'y'] |
|
515 | 515 | >>> _earlygetopt(['--cwd'], args), args |
|
516 | 516 | (['bar'], ['x', 'y']) |
|
517 | 517 | |
|
518 | 518 | >>> args = ['x', '-R', 'foo', 'y'] |
|
519 | 519 | >>> _earlygetopt(['-R'], args), args |
|
520 | 520 | (['foo'], ['x', 'y']) |
|
521 | 521 | |
|
522 | 522 | >>> args = ['x', '-Rbar', 'y'] |
|
523 | 523 | >>> _earlygetopt(['-R'], args), args |
|
524 | 524 | (['bar'], ['x', 'y']) |
|
525 | 525 | """ |
|
526 | 526 | try: |
|
527 | 527 | argcount = args.index("--") |
|
528 | 528 | except ValueError: |
|
529 | 529 | argcount = len(args) |
|
530 | 530 | shortopts = [opt for opt in aliases if len(opt) == 2] |
|
531 | 531 | values = [] |
|
532 | 532 | pos = 0 |
|
533 | 533 | while pos < argcount: |
|
534 | 534 | fullarg = arg = args[pos] |
|
535 | 535 | equals = arg.find('=') |
|
536 | 536 | if equals > -1: |
|
537 | 537 | arg = arg[:equals] |
|
538 | 538 | if arg in aliases: |
|
539 | 539 | del args[pos] |
|
540 | 540 | if equals > -1: |
|
541 | 541 | values.append(fullarg[equals + 1:]) |
|
542 | 542 | argcount -= 1 |
|
543 | 543 | else: |
|
544 | 544 | if pos + 1 >= argcount: |
|
545 | 545 | # ignore and let getopt report an error if there is no value |
|
546 | 546 | break |
|
547 | 547 | values.append(args.pop(pos)) |
|
548 | 548 | argcount -= 2 |
|
549 | 549 | elif arg[:2] in shortopts: |
|
550 | 550 | # short option can have no following space, e.g. hg log -Rfoo |
|
551 | 551 | values.append(args.pop(pos)[2:]) |
|
552 | 552 | argcount -= 1 |
|
553 | 553 | else: |
|
554 | 554 | pos += 1 |
|
555 | 555 | return values |
|
556 | 556 | |
|
557 | 557 | def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions): |
|
558 | 558 | # run pre-hook, and abort if it fails |
|
559 | 559 | hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs), |
|
560 | 560 | pats=cmdpats, opts=cmdoptions) |
|
561 | 561 | try: |
|
562 | 562 | ret = _runcommand(ui, options, cmd, d) |
|
563 | 563 | # run post-hook, passing command result |
|
564 | 564 | hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs), |
|
565 | 565 | result=ret, pats=cmdpats, opts=cmdoptions) |
|
566 | 566 | except Exception: |
|
567 | 567 | # run failure hook and re-raise |
|
568 | 568 | hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs), |
|
569 | 569 | pats=cmdpats, opts=cmdoptions) |
|
570 | 570 | raise |
|
571 | 571 | return ret |
|
572 | 572 | |
|
573 | 573 | def _getlocal(ui, rpath, wd=None): |
|
574 | 574 | """Return (path, local ui object) for the given target path. |
|
575 | 575 | |
|
576 | 576 | Takes paths in [cwd]/.hg/hgrc into account." |
|
577 | 577 | """ |
|
578 | 578 | if wd is None: |
|
579 | 579 | try: |
|
580 | 580 | wd = pycompat.getcwd() |
|
581 | 581 | except OSError as e: |
|
582 | 582 | raise error.Abort(_("error getting current working directory: %s") % |
|
583 | 583 | e.strerror) |
|
584 | 584 | path = cmdutil.findrepo(wd) or "" |
|
585 | 585 | if not path: |
|
586 | 586 | lui = ui |
|
587 | 587 | else: |
|
588 | 588 | lui = ui.copy() |
|
589 | 589 | lui.readconfig(os.path.join(path, ".hg", "hgrc"), path) |
|
590 | 590 | |
|
591 | 591 | if rpath and rpath[-1]: |
|
592 | 592 | path = lui.expandpath(rpath[-1]) |
|
593 | 593 | lui = ui.copy() |
|
594 | 594 | lui.readconfig(os.path.join(path, ".hg", "hgrc"), path) |
|
595 | 595 | |
|
596 | 596 | return path, lui |
|
597 | 597 | |
|
598 | 598 | def _checkshellalias(lui, ui, args): |
|
599 | 599 | """Return the function to run the shell alias, if it is required""" |
|
600 | 600 | options = {} |
|
601 | 601 | |
|
602 | 602 | try: |
|
603 | 603 | args = fancyopts.fancyopts(args, commands.globalopts, options) |
|
604 | 604 | except fancyopts.getopt.GetoptError: |
|
605 | 605 | return |
|
606 | 606 | |
|
607 | 607 | if not args: |
|
608 | 608 | return |
|
609 | 609 | |
|
610 | 610 | cmdtable = commands.table |
|
611 | 611 | |
|
612 | 612 | cmd = args[0] |
|
613 | 613 | try: |
|
614 | 614 | strict = ui.configbool("ui", "strict") |
|
615 | 615 | aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict) |
|
616 | 616 | except (error.AmbiguousCommand, error.UnknownCommand): |
|
617 | 617 | return |
|
618 | 618 | |
|
619 | 619 | cmd = aliases[0] |
|
620 | 620 | fn = entry[0] |
|
621 | 621 | |
|
622 | 622 | if cmd and util.safehasattr(fn, 'shell'): |
|
623 | 623 | d = lambda: fn(ui, *args[1:]) |
|
624 | 624 | return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d, |
|
625 | 625 | [], {}) |
|
626 | 626 | |
|
627 | 627 | _loaded = set() |
|
628 | 628 | |
|
629 | 629 | # list of (objname, loadermod, loadername) tuple: |
|
630 | 630 | # - objname is the name of an object in extension module, from which |
|
631 | 631 | # extra information is loaded |
|
632 | 632 | # - loadermod is the module where loader is placed |
|
633 | 633 | # - loadername is the name of the function, which takes (ui, extensionname, |
|
634 | 634 | # extraobj) arguments |
|
635 | 635 | extraloaders = [ |
|
636 | 636 | ('cmdtable', commands, 'loadcmdtable'), |
|
637 | 637 | ('filesetpredicate', fileset, 'loadpredicate'), |
|
638 | 638 | ('revsetpredicate', revset, 'loadpredicate'), |
|
639 | 639 | ('templatefilter', templatefilters, 'loadfilter'), |
|
640 | 640 | ('templatefunc', templater, 'loadfunction'), |
|
641 | 641 | ('templatekeyword', templatekw, 'loadkeyword'), |
|
642 | 642 | ] |
|
643 | 643 | |
|
644 | 644 | def _dispatch(req): |
|
645 | 645 | args = req.args |
|
646 | 646 | ui = req.ui |
|
647 | 647 | |
|
648 | 648 | # check for cwd |
|
649 | 649 | cwd = _earlygetopt(['--cwd'], args) |
|
650 | 650 | if cwd: |
|
651 | 651 | os.chdir(cwd[-1]) |
|
652 | 652 | |
|
653 | 653 | rpath = _earlygetopt(["-R", "--repository", "--repo"], args) |
|
654 | 654 | path, lui = _getlocal(ui, rpath) |
|
655 | 655 | |
|
656 | 656 | # Configure extensions in phases: uisetup, extsetup, cmdtable, and |
|
657 | 657 | # reposetup. Programs like TortoiseHg will call _dispatch several |
|
658 | 658 | # times so we keep track of configured extensions in _loaded. |
|
659 | 659 | extensions.loadall(lui) |
|
660 | 660 | exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded] |
|
661 | 661 | # Propagate any changes to lui.__class__ by extensions |
|
662 | 662 | ui.__class__ = lui.__class__ |
|
663 | 663 | |
|
664 | 664 | # (uisetup and extsetup are handled in extensions.loadall) |
|
665 | 665 | |
|
666 | 666 | for name, module in exts: |
|
667 | 667 | for objname, loadermod, loadername in extraloaders: |
|
668 | 668 | extraobj = getattr(module, objname, None) |
|
669 | 669 | if extraobj is not None: |
|
670 | 670 | getattr(loadermod, loadername)(ui, name, extraobj) |
|
671 | 671 | _loaded.add(name) |
|
672 | 672 | |
|
673 | 673 | # (reposetup is handled in hg.repository) |
|
674 | 674 | |
|
675 | 675 | # Side-effect of accessing is debugcommands module is guaranteed to be |
|
676 | 676 | # imported and commands.table is populated. |
|
677 | 677 | debugcommands.command |
|
678 | 678 | |
|
679 | 679 | addaliases(lui, commands.table) |
|
680 | 680 | |
|
681 | 681 | # All aliases and commands are completely defined, now. |
|
682 | 682 | # Check abbreviation/ambiguity of shell alias. |
|
683 | 683 | shellaliasfn = _checkshellalias(lui, ui, args) |
|
684 | 684 | if shellaliasfn: |
|
685 | 685 | with profiling.maybeprofile(lui): |
|
686 | 686 | return shellaliasfn() |
|
687 | 687 | |
|
688 | 688 | # check for fallback encoding |
|
689 | 689 | fallback = lui.config('ui', 'fallbackencoding') |
|
690 | 690 | if fallback: |
|
691 | 691 | encoding.fallbackencoding = fallback |
|
692 | 692 | |
|
693 | 693 | fullargs = args |
|
694 | 694 | cmd, func, args, options, cmdoptions = _parse(lui, args) |
|
695 | 695 | |
|
696 | 696 | if options["config"]: |
|
697 | 697 | raise error.Abort(_("option --config may not be abbreviated!")) |
|
698 | 698 | if options["cwd"]: |
|
699 | 699 | raise error.Abort(_("option --cwd may not be abbreviated!")) |
|
700 | 700 | if options["repository"]: |
|
701 | 701 | raise error.Abort(_( |
|
702 | 702 | "option -R has to be separated from other options (e.g. not -qR) " |
|
703 | 703 | "and --repository may only be abbreviated as --repo!")) |
|
704 | 704 | |
|
705 | 705 | if options["encoding"]: |
|
706 | 706 | encoding.encoding = options["encoding"] |
|
707 | 707 | if options["encodingmode"]: |
|
708 | 708 | encoding.encodingmode = options["encodingmode"] |
|
709 | 709 | if options["time"]: |
|
710 | 710 | def get_times(): |
|
711 | 711 | t = os.times() |
|
712 | 712 | if t[4] == 0.0: # Windows leaves this as zero, so use time.clock() |
|
713 | 713 | t = (t[0], t[1], t[2], t[3], time.clock()) |
|
714 | 714 | return t |
|
715 | 715 | s = get_times() |
|
716 | 716 | def print_time(): |
|
717 | 717 | t = get_times() |
|
718 | 718 | ui.warn(_("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") % |
|
719 | 719 | (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3])) |
|
720 | 720 | atexit.register(print_time) |
|
721 | 721 | |
|
722 | 722 | uis = set([ui, lui]) |
|
723 | 723 | |
|
724 | 724 | if req.repo: |
|
725 | 725 | uis.add(req.repo.ui) |
|
726 | 726 | |
|
727 | 727 | if options['verbose'] or options['debug'] or options['quiet']: |
|
728 | 728 | for opt in ('verbose', 'debug', 'quiet'): |
|
729 | 729 | val = str(bool(options[opt])) |
|
730 | 730 | for ui_ in uis: |
|
731 | 731 | ui_.setconfig('ui', opt, val, '--' + opt) |
|
732 | 732 | |
|
733 | 733 | if options['profile']: |
|
734 | 734 | for ui_ in uis: |
|
735 | 735 | ui_.setconfig('profiling', 'enabled', 'true', '--profile') |
|
736 | 736 | |
|
737 | 737 | if options['traceback']: |
|
738 | 738 | for ui_ in uis: |
|
739 | 739 | ui_.setconfig('ui', 'traceback', 'on', '--traceback') |
|
740 | 740 | |
|
741 | 741 | if options['noninteractive']: |
|
742 | 742 | for ui_ in uis: |
|
743 | 743 | ui_.setconfig('ui', 'interactive', 'off', '-y') |
|
744 | 744 | |
|
745 | 745 | if cmdoptions.get('insecure', False): |
|
746 | 746 | for ui_ in uis: |
|
747 | 747 | ui_.insecureconnections = True |
|
748 | 748 | |
|
749 | 749 | if options['version']: |
|
750 | 750 | return commands.version_(ui) |
|
751 | 751 | if options['help']: |
|
752 | 752 | return commands.help_(ui, cmd, command=cmd is not None) |
|
753 | 753 | elif not cmd: |
|
754 | 754 | return commands.help_(ui, 'shortlist') |
|
755 | 755 | |
|
756 | 756 | with profiling.maybeprofile(lui): |
|
757 | 757 | repo = None |
|
758 | 758 | cmdpats = args[:] |
|
759 | 759 | if not func.norepo: |
|
760 | 760 | # use the repo from the request only if we don't have -R |
|
761 | 761 | if not rpath and not cwd: |
|
762 | 762 | repo = req.repo |
|
763 | 763 | |
|
764 | 764 | if repo: |
|
765 | 765 | # set the descriptors of the repo ui to those of ui |
|
766 | 766 | repo.ui.fin = ui.fin |
|
767 | 767 | repo.ui.fout = ui.fout |
|
768 | 768 | repo.ui.ferr = ui.ferr |
|
769 | 769 | else: |
|
770 | 770 | try: |
|
771 | 771 | repo = hg.repository(ui, path=path) |
|
772 | 772 | if not repo.local(): |
|
773 | 773 | raise error.Abort(_("repository '%s' is not local") |
|
774 | 774 | % path) |
|
775 | 775 | repo.ui.setconfig("bundle", "mainreporoot", repo.root, |
|
776 | 776 | 'repo') |
|
777 | 777 | except error.RequirementError: |
|
778 | 778 | raise |
|
779 | 779 | except error.RepoError: |
|
780 | 780 | if rpath and rpath[-1]: # invalid -R path |
|
781 | 781 | raise |
|
782 | 782 | if not func.optionalrepo: |
|
783 | 783 | if func.inferrepo and args and not path: |
|
784 | 784 | # try to infer -R from command args |
|
785 | 785 | repos = map(cmdutil.findrepo, args) |
|
786 | 786 | guess = repos[0] |
|
787 | 787 | if guess and repos.count(guess) == len(repos): |
|
788 | 788 | req.args = ['--repository', guess] + fullargs |
|
789 | 789 | return _dispatch(req) |
|
790 | 790 | if not path: |
|
791 | 791 | raise error.RepoError(_("no repository found in" |
|
792 | 792 | " '%s' (.hg not found)") |
|
793 | 793 | % pycompat.getcwd()) |
|
794 | 794 | raise |
|
795 | 795 | if repo: |
|
796 | 796 | ui = repo.ui |
|
797 | 797 | if options['hidden']: |
|
798 | 798 | repo = repo.unfiltered() |
|
799 | 799 | args.insert(0, repo) |
|
800 | 800 | elif rpath: |
|
801 | 801 | ui.warn(_("warning: --repository ignored\n")) |
|
802 | 802 | |
|
803 | 803 | msg = ' '.join(' ' in a and repr(a) or a for a in fullargs) |
|
804 | 804 | ui.log("command", '%s\n', msg) |
|
805 | 805 | d = lambda: util.checksignature(func)(ui, *args, **cmdoptions) |
|
806 | 806 | try: |
|
807 | 807 | return runcommand(lui, repo, cmd, fullargs, ui, options, d, |
|
808 | 808 | cmdpats, cmdoptions) |
|
809 | 809 | finally: |
|
810 | 810 | if repo and repo != req.repo: |
|
811 | 811 | repo.close() |
|
812 | 812 | |
|
813 | 813 | def _runcommand(ui, options, cmd, cmdfunc): |
|
814 | 814 | """Run a command function, possibly with profiling enabled.""" |
|
815 | 815 | try: |
|
816 | 816 | return cmdfunc() |
|
817 | 817 | except error.SignatureError: |
|
818 | 818 | raise error.CommandError(cmd, _('invalid arguments')) |
|
819 | 819 | |
|
820 | 820 | def _exceptionwarning(ui): |
|
821 | 821 | """Produce a warning message for the current active exception""" |
|
822 | 822 | |
|
823 | 823 | # For compatibility checking, we discard the portion of the hg |
|
824 | 824 | # version after the + on the assumption that if a "normal |
|
825 | 825 | # user" is running a build with a + in it the packager |
|
826 | 826 | # probably built from fairly close to a tag and anyone with a |
|
827 | 827 | # 'make local' copy of hg (where the version number can be out |
|
828 | 828 | # of date) will be clueful enough to notice the implausible |
|
829 | 829 | # version number and try updating. |
|
830 | 830 | ct = util.versiontuple(n=2) |
|
831 | 831 | worst = None, ct, '' |
|
832 | 832 | if ui.config('ui', 'supportcontact', None) is None: |
|
833 | 833 | for name, mod in extensions.extensions(): |
|
834 | 834 | testedwith = getattr(mod, 'testedwith', '') |
|
835 | 835 | report = getattr(mod, 'buglink', _('the extension author.')) |
|
836 | 836 | if not testedwith.strip(): |
|
837 | 837 | # We found an untested extension. It's likely the culprit. |
|
838 | 838 | worst = name, 'unknown', report |
|
839 | 839 | break |
|
840 | 840 | |
|
841 | 841 | # Never blame on extensions bundled with Mercurial. |
|
842 | 842 | if extensions.ismoduleinternal(mod): |
|
843 | 843 | continue |
|
844 | 844 | |
|
845 | 845 | tested = [util.versiontuple(t, 2) for t in testedwith.split()] |
|
846 | 846 | if ct in tested: |
|
847 | 847 | continue |
|
848 | 848 | |
|
849 | 849 | lower = [t for t in tested if t < ct] |
|
850 | 850 | nearest = max(lower or tested) |
|
851 | 851 | if worst[0] is None or nearest < worst[1]: |
|
852 | 852 | worst = name, nearest, report |
|
853 | 853 | if worst[0] is not None: |
|
854 | 854 | name, testedwith, report = worst |
|
855 | 855 | if not isinstance(testedwith, str): |
|
856 | 856 | testedwith = '.'.join([str(c) for c in testedwith]) |
|
857 | 857 | warning = (_('** Unknown exception encountered with ' |
|
858 | 858 | 'possibly-broken third-party extension %s\n' |
|
859 | 859 | '** which supports versions %s of Mercurial.\n' |
|
860 | 860 | '** Please disable %s and try your action again.\n' |
|
861 | 861 | '** If that fixes the bug please report it to %s\n') |
|
862 | 862 | % (name, testedwith, name, report)) |
|
863 | 863 | else: |
|
864 | 864 | bugtracker = ui.config('ui', 'supportcontact', None) |
|
865 | 865 | if bugtracker is None: |
|
866 | 866 | bugtracker = _("https://mercurial-scm.org/wiki/BugTracker") |
|
867 | 867 | warning = (_("** unknown exception encountered, " |
|
868 | 868 | "please report by visiting\n** ") + bugtracker + '\n') |
|
869 | 869 | warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) + |
|
870 | 870 | (_("** Mercurial Distributed SCM (version %s)\n") % |
|
871 | 871 | util.version()) + |
|
872 | 872 | (_("** Extensions loaded: %s\n") % |
|
873 | 873 | ", ".join([x[0] for x in extensions.extensions()]))) |
|
874 | 874 | return warning |
|
875 | 875 | |
|
876 | 876 | def handlecommandexception(ui): |
|
877 | 877 | """Produce a warning message for broken commands |
|
878 | 878 | |
|
879 | 879 | Called when handling an exception; the exception is reraised if |
|
880 | 880 | this function returns False, ignored otherwise. |
|
881 | 881 | """ |
|
882 | 882 | warning = _exceptionwarning(ui) |
|
883 | 883 | ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc()) |
|
884 | 884 | ui.warn(warning) |
|
885 | 885 | return False # re-raise the exception |
@@ -1,470 +1,469 | |||
|
1 | 1 | # hgweb/hgweb_mod.py - Web interface for a repository. |
|
2 | 2 | # |
|
3 | 3 | # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net> |
|
4 | 4 | # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> |
|
5 | 5 | # |
|
6 | 6 | # This software may be used and distributed according to the terms of the |
|
7 | 7 | # GNU General Public License version 2 or any later version. |
|
8 | 8 | |
|
9 | 9 | from __future__ import absolute_import |
|
10 | 10 | |
|
11 | 11 | import contextlib |
|
12 | 12 | import os |
|
13 | 13 | |
|
14 | 14 | from .common import ( |
|
15 | 15 | ErrorResponse, |
|
16 | 16 | HTTP_BAD_REQUEST, |
|
17 | 17 | HTTP_NOT_FOUND, |
|
18 | 18 | HTTP_NOT_MODIFIED, |
|
19 | 19 | HTTP_OK, |
|
20 | 20 | HTTP_SERVER_ERROR, |
|
21 | 21 | caching, |
|
22 | 22 | permhooks, |
|
23 | 23 | ) |
|
24 | 24 | from .request import wsgirequest |
|
25 | 25 | |
|
26 | 26 | from .. import ( |
|
27 | 27 | encoding, |
|
28 | 28 | error, |
|
29 | 29 | hg, |
|
30 | 30 | hook, |
|
31 | 31 | profiling, |
|
32 | 32 | repoview, |
|
33 | 33 | templatefilters, |
|
34 | 34 | templater, |
|
35 | 35 | ui as uimod, |
|
36 | 36 | util, |
|
37 | 37 | ) |
|
38 | 38 | |
|
39 | 39 | from . import ( |
|
40 | 40 | protocol, |
|
41 | 41 | webcommands, |
|
42 | 42 | webutil, |
|
43 | 43 | wsgicgi, |
|
44 | 44 | ) |
|
45 | 45 | |
|
46 | 46 | perms = { |
|
47 | 47 | 'changegroup': 'pull', |
|
48 | 48 | 'changegroupsubset': 'pull', |
|
49 | 49 | 'getbundle': 'pull', |
|
50 | 50 | 'stream_out': 'pull', |
|
51 | 51 | 'listkeys': 'pull', |
|
52 | 52 | 'unbundle': 'push', |
|
53 | 53 | 'pushkey': 'push', |
|
54 | 54 | } |
|
55 | 55 | |
|
56 | 56 | def makebreadcrumb(url, prefix=''): |
|
57 | 57 | '''Return a 'URL breadcrumb' list |
|
58 | 58 | |
|
59 | 59 | A 'URL breadcrumb' is a list of URL-name pairs, |
|
60 | 60 | corresponding to each of the path items on a URL. |
|
61 | 61 | This can be used to create path navigation entries. |
|
62 | 62 | ''' |
|
63 | 63 | if url.endswith('/'): |
|
64 | 64 | url = url[:-1] |
|
65 | 65 | if prefix: |
|
66 | 66 | url = '/' + prefix + url |
|
67 | 67 | relpath = url |
|
68 | 68 | if relpath.startswith('/'): |
|
69 | 69 | relpath = relpath[1:] |
|
70 | 70 | |
|
71 | 71 | breadcrumb = [] |
|
72 | 72 | urlel = url |
|
73 | 73 | pathitems = [''] + relpath.split('/') |
|
74 | 74 | for pathel in reversed(pathitems): |
|
75 | 75 | if not pathel or not urlel: |
|
76 | 76 | break |
|
77 | 77 | breadcrumb.append({'url': urlel, 'name': pathel}) |
|
78 | 78 | urlel = os.path.dirname(urlel) |
|
79 | 79 | return reversed(breadcrumb) |
|
80 | 80 | |
|
81 | 81 | class requestcontext(object): |
|
82 | 82 | """Holds state/context for an individual request. |
|
83 | 83 | |
|
84 | 84 | Servers can be multi-threaded. Holding state on the WSGI application |
|
85 | 85 | is prone to race conditions. Instances of this class exist to hold |
|
86 | 86 | mutable and race-free state for requests. |
|
87 | 87 | """ |
|
88 | 88 | def __init__(self, app, repo): |
|
89 | 89 | self.repo = repo |
|
90 | 90 | self.reponame = app.reponame |
|
91 | 91 | |
|
92 | 92 | self.archives = ('zip', 'gz', 'bz2') |
|
93 | 93 | |
|
94 | 94 | self.maxchanges = self.configint('web', 'maxchanges', 10) |
|
95 | 95 | self.stripecount = self.configint('web', 'stripes', 1) |
|
96 | 96 | self.maxshortchanges = self.configint('web', 'maxshortchanges', 60) |
|
97 | 97 | self.maxfiles = self.configint('web', 'maxfiles', 10) |
|
98 | 98 | self.allowpull = self.configbool('web', 'allowpull', True) |
|
99 | 99 | |
|
100 | 100 | # we use untrusted=False to prevent a repo owner from using |
|
101 | 101 | # web.templates in .hg/hgrc to get access to any file readable |
|
102 | 102 | # by the user running the CGI script |
|
103 | 103 | self.templatepath = self.config('web', 'templates', untrusted=False) |
|
104 | 104 | |
|
105 | 105 | # This object is more expensive to build than simple config values. |
|
106 | 106 | # It is shared across requests. The app will replace the object |
|
107 | 107 | # if it is updated. Since this is a reference and nothing should |
|
108 | 108 | # modify the underlying object, it should be constant for the lifetime |
|
109 | 109 | # of the request. |
|
110 | 110 | self.websubtable = app.websubtable |
|
111 | 111 | |
|
112 | 112 | # Trust the settings from the .hg/hgrc files by default. |
|
113 | 113 | def config(self, section, name, default=None, untrusted=True): |
|
114 | 114 | return self.repo.ui.config(section, name, default, |
|
115 | 115 | untrusted=untrusted) |
|
116 | 116 | |
|
117 | 117 | def configbool(self, section, name, default=False, untrusted=True): |
|
118 | 118 | return self.repo.ui.configbool(section, name, default, |
|
119 | 119 | untrusted=untrusted) |
|
120 | 120 | |
|
121 | 121 | def configint(self, section, name, default=None, untrusted=True): |
|
122 | 122 | return self.repo.ui.configint(section, name, default, |
|
123 | 123 | untrusted=untrusted) |
|
124 | 124 | |
|
125 | 125 | def configlist(self, section, name, default=None, untrusted=True): |
|
126 | 126 | return self.repo.ui.configlist(section, name, default, |
|
127 | 127 | untrusted=untrusted) |
|
128 | 128 | |
|
129 | 129 | archivespecs = { |
|
130 | 130 | 'bz2': ('application/x-bzip2', 'tbz2', '.tar.bz2', None), |
|
131 | 131 | 'gz': ('application/x-gzip', 'tgz', '.tar.gz', None), |
|
132 | 132 | 'zip': ('application/zip', 'zip', '.zip', None), |
|
133 | 133 | } |
|
134 | 134 | |
|
135 | 135 | def archivelist(self, nodeid): |
|
136 | 136 | allowed = self.configlist('web', 'allow_archive') |
|
137 | 137 | for typ, spec in self.archivespecs.iteritems(): |
|
138 | 138 | if typ in allowed or self.configbool('web', 'allow%s' % typ): |
|
139 | 139 | yield {'type': typ, 'extension': spec[2], 'node': nodeid} |
|
140 | 140 | |
|
141 | 141 | def templater(self, req): |
|
142 | 142 | # determine scheme, port and server name |
|
143 | 143 | # this is needed to create absolute urls |
|
144 | 144 | |
|
145 | 145 | proto = req.env.get('wsgi.url_scheme') |
|
146 | 146 | if proto == 'https': |
|
147 | 147 | proto = 'https' |
|
148 | 148 | default_port = '443' |
|
149 | 149 | else: |
|
150 | 150 | proto = 'http' |
|
151 | 151 | default_port = '80' |
|
152 | 152 | |
|
153 | 153 | port = req.env['SERVER_PORT'] |
|
154 | 154 | port = port != default_port and (':' + port) or '' |
|
155 | 155 | urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port) |
|
156 | 156 | logourl = self.config('web', 'logourl', 'https://mercurial-scm.org/') |
|
157 | 157 | logoimg = self.config('web', 'logoimg', 'hglogo.png') |
|
158 | 158 | staticurl = self.config('web', 'staticurl') or req.url + 'static/' |
|
159 | 159 | if not staticurl.endswith('/'): |
|
160 | 160 | staticurl += '/' |
|
161 | 161 | |
|
162 | 162 | # some functions for the templater |
|
163 | 163 | |
|
164 | 164 | def motd(**map): |
|
165 | 165 | yield self.config('web', 'motd', '') |
|
166 | 166 | |
|
167 | 167 | # figure out which style to use |
|
168 | 168 | |
|
169 | 169 | vars = {} |
|
170 | 170 | styles = ( |
|
171 | 171 | req.form.get('style', [None])[0], |
|
172 | 172 | self.config('web', 'style'), |
|
173 | 173 | 'paper', |
|
174 | 174 | ) |
|
175 | 175 | style, mapfile = templater.stylemap(styles, self.templatepath) |
|
176 | 176 | if style == styles[0]: |
|
177 | 177 | vars['style'] = style |
|
178 | 178 | |
|
179 | 179 | start = req.url[-1] == '?' and '&' or '?' |
|
180 | 180 | sessionvars = webutil.sessionvars(vars, start) |
|
181 | 181 | |
|
182 | 182 | if not self.reponame: |
|
183 | 183 | self.reponame = (self.config('web', 'name') |
|
184 | 184 | or req.env.get('REPO_NAME') |
|
185 | 185 | or req.url.strip('/') or self.repo.root) |
|
186 | 186 | |
|
187 | 187 | def websubfilter(text): |
|
188 | 188 | return templatefilters.websub(text, self.websubtable) |
|
189 | 189 | |
|
190 | 190 | # create the templater |
|
191 | 191 | |
|
192 | 192 | defaults = { |
|
193 | 193 | 'url': req.url, |
|
194 | 194 | 'logourl': logourl, |
|
195 | 195 | 'logoimg': logoimg, |
|
196 | 196 | 'staticurl': staticurl, |
|
197 | 197 | 'urlbase': urlbase, |
|
198 | 198 | 'repo': self.reponame, |
|
199 | 199 | 'encoding': encoding.encoding, |
|
200 | 200 | 'motd': motd, |
|
201 | 201 | 'sessionvars': sessionvars, |
|
202 | 202 | 'pathdef': makebreadcrumb(req.url), |
|
203 | 203 | 'style': style, |
|
204 | 204 | } |
|
205 | 205 | tmpl = templater.templater.frommapfile(mapfile, |
|
206 | 206 | filters={'websub': websubfilter}, |
|
207 | 207 | defaults=defaults) |
|
208 | 208 | return tmpl |
|
209 | 209 | |
|
210 | 210 | |
|
211 | 211 | class hgweb(object): |
|
212 | 212 | """HTTP server for individual repositories. |
|
213 | 213 | |
|
214 | 214 | Instances of this class serve HTTP responses for a particular |
|
215 | 215 | repository. |
|
216 | 216 | |
|
217 | 217 | Instances are typically used as WSGI applications. |
|
218 | 218 | |
|
219 | 219 | Some servers are multi-threaded. On these servers, there may |
|
220 | 220 | be multiple active threads inside __call__. |
|
221 | 221 | """ |
|
222 | 222 | def __init__(self, repo, name=None, baseui=None): |
|
223 | 223 | if isinstance(repo, str): |
|
224 | 224 | if baseui: |
|
225 | 225 | u = baseui.copy() |
|
226 | 226 | else: |
|
227 | u = uimod.ui() | |
|
227 | u = uimod.ui.load() | |
|
228 | 228 | r = hg.repository(u, repo) |
|
229 | 229 | else: |
|
230 | 230 | # we trust caller to give us a private copy |
|
231 | 231 | r = repo |
|
232 | 232 | |
|
233 | 233 | r.ui.setconfig('ui', 'report_untrusted', 'off', 'hgweb') |
|
234 | 234 | r.baseui.setconfig('ui', 'report_untrusted', 'off', 'hgweb') |
|
235 | 235 | r.ui.setconfig('ui', 'nontty', 'true', 'hgweb') |
|
236 | 236 | r.baseui.setconfig('ui', 'nontty', 'true', 'hgweb') |
|
237 | 237 | # resolve file patterns relative to repo root |
|
238 | 238 | r.ui.setconfig('ui', 'forcecwd', r.root, 'hgweb') |
|
239 | 239 | r.baseui.setconfig('ui', 'forcecwd', r.root, 'hgweb') |
|
240 | 240 | # displaying bundling progress bar while serving feel wrong and may |
|
241 | 241 | # break some wsgi implementation. |
|
242 | 242 | r.ui.setconfig('progress', 'disable', 'true', 'hgweb') |
|
243 | 243 | r.baseui.setconfig('progress', 'disable', 'true', 'hgweb') |
|
244 | 244 | self._repos = [hg.cachedlocalrepo(self._webifyrepo(r))] |
|
245 | 245 | self._lastrepo = self._repos[0] |
|
246 | 246 | hook.redirect(True) |
|
247 | 247 | self.reponame = name |
|
248 | 248 | |
|
249 | 249 | def _webifyrepo(self, repo): |
|
250 | 250 | repo = getwebview(repo) |
|
251 | 251 | self.websubtable = webutil.getwebsubs(repo) |
|
252 | 252 | return repo |
|
253 | 253 | |
|
254 | 254 | @contextlib.contextmanager |
|
255 | 255 | def _obtainrepo(self): |
|
256 | 256 | """Obtain a repo unique to the caller. |
|
257 | 257 | |
|
258 | 258 | Internally we maintain a stack of cachedlocalrepo instances |
|
259 | 259 | to be handed out. If one is available, we pop it and return it, |
|
260 | 260 | ensuring it is up to date in the process. If one is not available, |
|
261 | 261 | we clone the most recently used repo instance and return it. |
|
262 | 262 | |
|
263 | 263 | It is currently possible for the stack to grow without bounds |
|
264 | 264 | if the server allows infinite threads. However, servers should |
|
265 | 265 | have a thread limit, thus establishing our limit. |
|
266 | 266 | """ |
|
267 | 267 | if self._repos: |
|
268 | 268 | cached = self._repos.pop() |
|
269 | 269 | r, created = cached.fetch() |
|
270 | 270 | else: |
|
271 | 271 | cached = self._lastrepo.copy() |
|
272 | 272 | r, created = cached.fetch() |
|
273 | 273 | if created: |
|
274 | 274 | r = self._webifyrepo(r) |
|
275 | 275 | |
|
276 | 276 | self._lastrepo = cached |
|
277 | 277 | self.mtime = cached.mtime |
|
278 | 278 | try: |
|
279 | 279 | yield r |
|
280 | 280 | finally: |
|
281 | 281 | self._repos.append(cached) |
|
282 | 282 | |
|
283 | 283 | def run(self): |
|
284 | 284 | """Start a server from CGI environment. |
|
285 | 285 | |
|
286 | 286 | Modern servers should be using WSGI and should avoid this |
|
287 | 287 | method, if possible. |
|
288 | 288 | """ |
|
289 | 289 | if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."): |
|
290 | 290 | raise RuntimeError("This function is only intended to be " |
|
291 | 291 | "called while running as a CGI script.") |
|
292 | 292 | wsgicgi.launch(self) |
|
293 | 293 | |
|
294 | 294 | def __call__(self, env, respond): |
|
295 | 295 | """Run the WSGI application. |
|
296 | 296 | |
|
297 | 297 | This may be called by multiple threads. |
|
298 | 298 | """ |
|
299 | 299 | req = wsgirequest(env, respond) |
|
300 | 300 | return self.run_wsgi(req) |
|
301 | 301 | |
|
302 | 302 | def run_wsgi(self, req): |
|
303 | 303 | """Internal method to run the WSGI application. |
|
304 | 304 | |
|
305 | 305 | This is typically only called by Mercurial. External consumers |
|
306 | 306 | should be using instances of this class as the WSGI application. |
|
307 | 307 | """ |
|
308 | 308 | with self._obtainrepo() as repo: |
|
309 | 309 | with profiling.maybeprofile(repo.ui): |
|
310 | 310 | for r in self._runwsgi(req, repo): |
|
311 | 311 | yield r |
|
312 | 312 | |
|
313 | 313 | def _runwsgi(self, req, repo): |
|
314 | 314 | rctx = requestcontext(self, repo) |
|
315 | 315 | |
|
316 | 316 | # This state is global across all threads. |
|
317 | 317 | encoding.encoding = rctx.config('web', 'encoding', encoding.encoding) |
|
318 | 318 | rctx.repo.ui.environ = req.env |
|
319 | 319 | |
|
320 | 320 | # work with CGI variables to create coherent structure |
|
321 | 321 | # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME |
|
322 | 322 | |
|
323 | 323 | req.url = req.env['SCRIPT_NAME'] |
|
324 | 324 | if not req.url.endswith('/'): |
|
325 | 325 | req.url += '/' |
|
326 | 326 | if 'REPO_NAME' in req.env: |
|
327 | 327 | req.url += req.env['REPO_NAME'] + '/' |
|
328 | 328 | |
|
329 | 329 | if 'PATH_INFO' in req.env: |
|
330 | 330 | parts = req.env['PATH_INFO'].strip('/').split('/') |
|
331 | 331 | repo_parts = req.env.get('REPO_NAME', '').split('/') |
|
332 | 332 | if parts[:len(repo_parts)] == repo_parts: |
|
333 | 333 | parts = parts[len(repo_parts):] |
|
334 | 334 | query = '/'.join(parts) |
|
335 | 335 | else: |
|
336 | 336 | query = req.env['QUERY_STRING'].partition('&')[0] |
|
337 | 337 | query = query.partition(';')[0] |
|
338 | 338 | |
|
339 | 339 | # process this if it's a protocol request |
|
340 | 340 | # protocol bits don't need to create any URLs |
|
341 | 341 | # and the clients always use the old URL structure |
|
342 | 342 | |
|
343 | 343 | cmd = req.form.get('cmd', [''])[0] |
|
344 | 344 | if protocol.iscmd(cmd): |
|
345 | 345 | try: |
|
346 | 346 | if query: |
|
347 | 347 | raise ErrorResponse(HTTP_NOT_FOUND) |
|
348 | 348 | if cmd in perms: |
|
349 | 349 | self.check_perm(rctx, req, perms[cmd]) |
|
350 | 350 | return protocol.call(rctx.repo, req, cmd) |
|
351 | 351 | except ErrorResponse as inst: |
|
352 | 352 | # A client that sends unbundle without 100-continue will |
|
353 | 353 | # break if we respond early. |
|
354 | 354 | if (cmd == 'unbundle' and |
|
355 | 355 | (req.env.get('HTTP_EXPECT', |
|
356 | 356 | '').lower() != '100-continue') or |
|
357 | 357 | req.env.get('X-HgHttp2', '')): |
|
358 | 358 | req.drain() |
|
359 | 359 | else: |
|
360 | 360 | req.headers.append(('Connection', 'Close')) |
|
361 | 361 | req.respond(inst, protocol.HGTYPE, |
|
362 | 362 | body='0\n%s\n' % inst) |
|
363 | 363 | return '' |
|
364 | 364 | |
|
365 | 365 | # translate user-visible url structure to internal structure |
|
366 | 366 | |
|
367 | 367 | args = query.split('/', 2) |
|
368 | 368 | if 'cmd' not in req.form and args and args[0]: |
|
369 | 369 | |
|
370 | 370 | cmd = args.pop(0) |
|
371 | 371 | style = cmd.rfind('-') |
|
372 | 372 | if style != -1: |
|
373 | 373 | req.form['style'] = [cmd[:style]] |
|
374 | 374 | cmd = cmd[style + 1:] |
|
375 | 375 | |
|
376 | 376 | # avoid accepting e.g. style parameter as command |
|
377 | 377 | if util.safehasattr(webcommands, cmd): |
|
378 | 378 | req.form['cmd'] = [cmd] |
|
379 | 379 | |
|
380 | 380 | if cmd == 'static': |
|
381 | 381 | req.form['file'] = ['/'.join(args)] |
|
382 | 382 | else: |
|
383 | 383 | if args and args[0]: |
|
384 | 384 | node = args.pop(0).replace('%2F', '/') |
|
385 | 385 | req.form['node'] = [node] |
|
386 | 386 | if args: |
|
387 | 387 | req.form['file'] = args |
|
388 | 388 | |
|
389 | 389 | ua = req.env.get('HTTP_USER_AGENT', '') |
|
390 | 390 | if cmd == 'rev' and 'mercurial' in ua: |
|
391 | 391 | req.form['style'] = ['raw'] |
|
392 | 392 | |
|
393 | 393 | if cmd == 'archive': |
|
394 | 394 | fn = req.form['node'][0] |
|
395 | 395 | for type_, spec in rctx.archivespecs.iteritems(): |
|
396 | 396 | ext = spec[2] |
|
397 | 397 | if fn.endswith(ext): |
|
398 | 398 | req.form['node'] = [fn[:-len(ext)]] |
|
399 | 399 | req.form['type'] = [type_] |
|
400 | 400 | |
|
401 | 401 | # process the web interface request |
|
402 | 402 | |
|
403 | 403 | try: |
|
404 | 404 | tmpl = rctx.templater(req) |
|
405 | 405 | ctype = tmpl('mimetype', encoding=encoding.encoding) |
|
406 | 406 | ctype = templater.stringify(ctype) |
|
407 | 407 | |
|
408 | 408 | # check read permissions non-static content |
|
409 | 409 | if cmd != 'static': |
|
410 | 410 | self.check_perm(rctx, req, None) |
|
411 | 411 | |
|
412 | 412 | if cmd == '': |
|
413 | 413 | req.form['cmd'] = [tmpl.cache['default']] |
|
414 | 414 | cmd = req.form['cmd'][0] |
|
415 | 415 | |
|
416 | 416 | if rctx.configbool('web', 'cache', True): |
|
417 | 417 | caching(self, req) # sets ETag header or raises NOT_MODIFIED |
|
418 | 418 | if cmd not in webcommands.__all__: |
|
419 | 419 | msg = 'no such method: %s' % cmd |
|
420 | 420 | raise ErrorResponse(HTTP_BAD_REQUEST, msg) |
|
421 | 421 | elif cmd == 'file' and 'raw' in req.form.get('style', []): |
|
422 | 422 | rctx.ctype = ctype |
|
423 | 423 | content = webcommands.rawfile(rctx, req, tmpl) |
|
424 | 424 | else: |
|
425 | 425 | content = getattr(webcommands, cmd)(rctx, req, tmpl) |
|
426 | 426 | req.respond(HTTP_OK, ctype) |
|
427 | 427 | |
|
428 | 428 | return content |
|
429 | 429 | |
|
430 | 430 | except (error.LookupError, error.RepoLookupError) as err: |
|
431 | 431 | req.respond(HTTP_NOT_FOUND, ctype) |
|
432 | 432 | msg = str(err) |
|
433 | 433 | if (util.safehasattr(err, 'name') and |
|
434 | 434 | not isinstance(err, error.ManifestLookupError)): |
|
435 | 435 | msg = 'revision not found: %s' % err.name |
|
436 | 436 | return tmpl('error', error=msg) |
|
437 | 437 | except (error.RepoError, error.RevlogError) as inst: |
|
438 | 438 | req.respond(HTTP_SERVER_ERROR, ctype) |
|
439 | 439 | return tmpl('error', error=str(inst)) |
|
440 | 440 | except ErrorResponse as inst: |
|
441 | 441 | req.respond(inst, ctype) |
|
442 | 442 | if inst.code == HTTP_NOT_MODIFIED: |
|
443 | 443 | # Not allowed to return a body on a 304 |
|
444 | 444 | return [''] |
|
445 | 445 | return tmpl('error', error=str(inst)) |
|
446 | 446 | |
|
447 | 447 | def check_perm(self, rctx, req, op): |
|
448 | 448 | for permhook in permhooks: |
|
449 | 449 | permhook(rctx, req, op) |
|
450 | 450 | |
|
451 | 451 | def getwebview(repo): |
|
452 | 452 | """The 'web.view' config controls changeset filter to hgweb. Possible |
|
453 | 453 | values are ``served``, ``visible`` and ``all``. Default is ``served``. |
|
454 | 454 | The ``served`` filter only shows changesets that can be pulled from the |
|
455 | 455 | hgweb instance. The``visible`` filter includes secret changesets but |
|
456 | 456 | still excludes "hidden" one. |
|
457 | 457 | |
|
458 | 458 | See the repoview module for details. |
|
459 | 459 | |
|
460 | 460 | The option has been around undocumented since Mercurial 2.5, but no |
|
461 | 461 | user ever asked about it. So we better keep it undocumented for now.""" |
|
462 | 462 | viewconfig = repo.ui.config('web', 'view', 'served', |
|
463 | 463 | untrusted=True) |
|
464 | 464 | if viewconfig == 'all': |
|
465 | 465 | return repo.unfiltered() |
|
466 | 466 | elif viewconfig in repoview.filtertable: |
|
467 | 467 | return repo.filtered(viewconfig) |
|
468 | 468 | else: |
|
469 | 469 | return repo.filtered('served') |
|
470 |
@@ -1,521 +1,521 | |||
|
1 | 1 | # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories. |
|
2 | 2 | # |
|
3 | 3 | # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net> |
|
4 | 4 | # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com> |
|
5 | 5 | # |
|
6 | 6 | # This software may be used and distributed according to the terms of the |
|
7 | 7 | # GNU General Public License version 2 or any later version. |
|
8 | 8 | |
|
9 | 9 | from __future__ import absolute_import |
|
10 | 10 | |
|
11 | 11 | import os |
|
12 | 12 | import re |
|
13 | 13 | import time |
|
14 | 14 | |
|
15 | 15 | from ..i18n import _ |
|
16 | 16 | |
|
17 | 17 | from .common import ( |
|
18 | 18 | ErrorResponse, |
|
19 | 19 | HTTP_NOT_FOUND, |
|
20 | 20 | HTTP_OK, |
|
21 | 21 | HTTP_SERVER_ERROR, |
|
22 | 22 | get_contact, |
|
23 | 23 | get_mtime, |
|
24 | 24 | ismember, |
|
25 | 25 | paritygen, |
|
26 | 26 | staticfile, |
|
27 | 27 | ) |
|
28 | 28 | from .request import wsgirequest |
|
29 | 29 | |
|
30 | 30 | from .. import ( |
|
31 | 31 | encoding, |
|
32 | 32 | error, |
|
33 | 33 | hg, |
|
34 | 34 | profiling, |
|
35 | 35 | scmutil, |
|
36 | 36 | templater, |
|
37 | 37 | ui as uimod, |
|
38 | 38 | util, |
|
39 | 39 | ) |
|
40 | 40 | |
|
41 | 41 | from . import ( |
|
42 | 42 | hgweb_mod, |
|
43 | 43 | webutil, |
|
44 | 44 | wsgicgi, |
|
45 | 45 | ) |
|
46 | 46 | |
|
47 | 47 | def cleannames(items): |
|
48 | 48 | return [(util.pconvert(name).strip('/'), path) for name, path in items] |
|
49 | 49 | |
|
50 | 50 | def findrepos(paths): |
|
51 | 51 | repos = [] |
|
52 | 52 | for prefix, root in cleannames(paths): |
|
53 | 53 | roothead, roottail = os.path.split(root) |
|
54 | 54 | # "foo = /bar/*" or "foo = /bar/**" lets every repo /bar/N in or below |
|
55 | 55 | # /bar/ be served as as foo/N . |
|
56 | 56 | # '*' will not search inside dirs with .hg (except .hg/patches), |
|
57 | 57 | # '**' will search inside dirs with .hg (and thus also find subrepos). |
|
58 | 58 | try: |
|
59 | 59 | recurse = {'*': False, '**': True}[roottail] |
|
60 | 60 | except KeyError: |
|
61 | 61 | repos.append((prefix, root)) |
|
62 | 62 | continue |
|
63 | 63 | roothead = os.path.normpath(os.path.abspath(roothead)) |
|
64 | 64 | paths = scmutil.walkrepos(roothead, followsym=True, recurse=recurse) |
|
65 | 65 | repos.extend(urlrepos(prefix, roothead, paths)) |
|
66 | 66 | return repos |
|
67 | 67 | |
|
68 | 68 | def urlrepos(prefix, roothead, paths): |
|
69 | 69 | """yield url paths and filesystem paths from a list of repo paths |
|
70 | 70 | |
|
71 | 71 | >>> conv = lambda seq: [(v, util.pconvert(p)) for v,p in seq] |
|
72 | 72 | >>> conv(urlrepos('hg', '/opt', ['/opt/r', '/opt/r/r', '/opt'])) |
|
73 | 73 | [('hg/r', '/opt/r'), ('hg/r/r', '/opt/r/r'), ('hg', '/opt')] |
|
74 | 74 | >>> conv(urlrepos('', '/opt', ['/opt/r', '/opt/r/r', '/opt'])) |
|
75 | 75 | [('r', '/opt/r'), ('r/r', '/opt/r/r'), ('', '/opt')] |
|
76 | 76 | """ |
|
77 | 77 | for path in paths: |
|
78 | 78 | path = os.path.normpath(path) |
|
79 | 79 | yield (prefix + '/' + |
|
80 | 80 | util.pconvert(path[len(roothead):]).lstrip('/')).strip('/'), path |
|
81 | 81 | |
|
82 | 82 | def geturlcgivars(baseurl, port): |
|
83 | 83 | """ |
|
84 | 84 | Extract CGI variables from baseurl |
|
85 | 85 | |
|
86 | 86 | >>> geturlcgivars("http://host.org/base", "80") |
|
87 | 87 | ('host.org', '80', '/base') |
|
88 | 88 | >>> geturlcgivars("http://host.org:8000/base", "80") |
|
89 | 89 | ('host.org', '8000', '/base') |
|
90 | 90 | >>> geturlcgivars('/base', 8000) |
|
91 | 91 | ('', '8000', '/base') |
|
92 | 92 | >>> geturlcgivars("base", '8000') |
|
93 | 93 | ('', '8000', '/base') |
|
94 | 94 | >>> geturlcgivars("http://host", '8000') |
|
95 | 95 | ('host', '8000', '/') |
|
96 | 96 | >>> geturlcgivars("http://host/", '8000') |
|
97 | 97 | ('host', '8000', '/') |
|
98 | 98 | """ |
|
99 | 99 | u = util.url(baseurl) |
|
100 | 100 | name = u.host or '' |
|
101 | 101 | if u.port: |
|
102 | 102 | port = u.port |
|
103 | 103 | path = u.path or "" |
|
104 | 104 | if not path.startswith('/'): |
|
105 | 105 | path = '/' + path |
|
106 | 106 | |
|
107 | 107 | return name, str(port), path |
|
108 | 108 | |
|
109 | 109 | class hgwebdir(object): |
|
110 | 110 | """HTTP server for multiple repositories. |
|
111 | 111 | |
|
112 | 112 | Given a configuration, different repositories will be served depending |
|
113 | 113 | on the request path. |
|
114 | 114 | |
|
115 | 115 | Instances are typically used as WSGI applications. |
|
116 | 116 | """ |
|
117 | 117 | def __init__(self, conf, baseui=None): |
|
118 | 118 | self.conf = conf |
|
119 | 119 | self.baseui = baseui |
|
120 | 120 | self.ui = None |
|
121 | 121 | self.lastrefresh = 0 |
|
122 | 122 | self.motd = None |
|
123 | 123 | self.refresh() |
|
124 | 124 | |
|
125 | 125 | def refresh(self): |
|
126 | 126 | refreshinterval = 20 |
|
127 | 127 | if self.ui: |
|
128 | 128 | refreshinterval = self.ui.configint('web', 'refreshinterval', |
|
129 | 129 | refreshinterval) |
|
130 | 130 | |
|
131 | 131 | # refreshinterval <= 0 means to always refresh. |
|
132 | 132 | if (refreshinterval > 0 and |
|
133 | 133 | self.lastrefresh + refreshinterval > time.time()): |
|
134 | 134 | return |
|
135 | 135 | |
|
136 | 136 | if self.baseui: |
|
137 | 137 | u = self.baseui.copy() |
|
138 | 138 | else: |
|
139 | u = uimod.ui() | |
|
139 | u = uimod.ui.load() | |
|
140 | 140 | u.setconfig('ui', 'report_untrusted', 'off', 'hgwebdir') |
|
141 | 141 | u.setconfig('ui', 'nontty', 'true', 'hgwebdir') |
|
142 | 142 | # displaying bundling progress bar while serving feels wrong and may |
|
143 | 143 | # break some wsgi implementations. |
|
144 | 144 | u.setconfig('progress', 'disable', 'true', 'hgweb') |
|
145 | 145 | |
|
146 | 146 | if not isinstance(self.conf, (dict, list, tuple)): |
|
147 | 147 | map = {'paths': 'hgweb-paths'} |
|
148 | 148 | if not os.path.exists(self.conf): |
|
149 | 149 | raise error.Abort(_('config file %s not found!') % self.conf) |
|
150 | 150 | u.readconfig(self.conf, remap=map, trust=True) |
|
151 | 151 | paths = [] |
|
152 | 152 | for name, ignored in u.configitems('hgweb-paths'): |
|
153 | 153 | for path in u.configlist('hgweb-paths', name): |
|
154 | 154 | paths.append((name, path)) |
|
155 | 155 | elif isinstance(self.conf, (list, tuple)): |
|
156 | 156 | paths = self.conf |
|
157 | 157 | elif isinstance(self.conf, dict): |
|
158 | 158 | paths = self.conf.items() |
|
159 | 159 | |
|
160 | 160 | repos = findrepos(paths) |
|
161 | 161 | for prefix, root in u.configitems('collections'): |
|
162 | 162 | prefix = util.pconvert(prefix) |
|
163 | 163 | for path in scmutil.walkrepos(root, followsym=True): |
|
164 | 164 | repo = os.path.normpath(path) |
|
165 | 165 | name = util.pconvert(repo) |
|
166 | 166 | if name.startswith(prefix): |
|
167 | 167 | name = name[len(prefix):] |
|
168 | 168 | repos.append((name.lstrip('/'), repo)) |
|
169 | 169 | |
|
170 | 170 | self.repos = repos |
|
171 | 171 | self.ui = u |
|
172 | 172 | encoding.encoding = self.ui.config('web', 'encoding', |
|
173 | 173 | encoding.encoding) |
|
174 | 174 | self.style = self.ui.config('web', 'style', 'paper') |
|
175 | 175 | self.templatepath = self.ui.config('web', 'templates', None) |
|
176 | 176 | self.stripecount = self.ui.config('web', 'stripes', 1) |
|
177 | 177 | if self.stripecount: |
|
178 | 178 | self.stripecount = int(self.stripecount) |
|
179 | 179 | self._baseurl = self.ui.config('web', 'baseurl') |
|
180 | 180 | prefix = self.ui.config('web', 'prefix', '') |
|
181 | 181 | if prefix.startswith('/'): |
|
182 | 182 | prefix = prefix[1:] |
|
183 | 183 | if prefix.endswith('/'): |
|
184 | 184 | prefix = prefix[:-1] |
|
185 | 185 | self.prefix = prefix |
|
186 | 186 | self.lastrefresh = time.time() |
|
187 | 187 | |
|
188 | 188 | def run(self): |
|
189 | 189 | if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."): |
|
190 | 190 | raise RuntimeError("This function is only intended to be " |
|
191 | 191 | "called while running as a CGI script.") |
|
192 | 192 | wsgicgi.launch(self) |
|
193 | 193 | |
|
194 | 194 | def __call__(self, env, respond): |
|
195 | 195 | req = wsgirequest(env, respond) |
|
196 | 196 | return self.run_wsgi(req) |
|
197 | 197 | |
|
198 | 198 | def read_allowed(self, ui, req): |
|
199 | 199 | """Check allow_read and deny_read config options of a repo's ui object |
|
200 | 200 | to determine user permissions. By default, with neither option set (or |
|
201 | 201 | both empty), allow all users to read the repo. There are two ways a |
|
202 | 202 | user can be denied read access: (1) deny_read is not empty, and the |
|
203 | 203 | user is unauthenticated or deny_read contains user (or *), and (2) |
|
204 | 204 | allow_read is not empty and the user is not in allow_read. Return True |
|
205 | 205 | if user is allowed to read the repo, else return False.""" |
|
206 | 206 | |
|
207 | 207 | user = req.env.get('REMOTE_USER') |
|
208 | 208 | |
|
209 | 209 | deny_read = ui.configlist('web', 'deny_read', untrusted=True) |
|
210 | 210 | if deny_read and (not user or ismember(ui, user, deny_read)): |
|
211 | 211 | return False |
|
212 | 212 | |
|
213 | 213 | allow_read = ui.configlist('web', 'allow_read', untrusted=True) |
|
214 | 214 | # by default, allow reading if no allow_read option has been set |
|
215 | 215 | if (not allow_read) or ismember(ui, user, allow_read): |
|
216 | 216 | return True |
|
217 | 217 | |
|
218 | 218 | return False |
|
219 | 219 | |
|
220 | 220 | def run_wsgi(self, req): |
|
221 | 221 | with profiling.maybeprofile(self.ui): |
|
222 | 222 | for r in self._runwsgi(req): |
|
223 | 223 | yield r |
|
224 | 224 | |
|
225 | 225 | def _runwsgi(self, req): |
|
226 | 226 | try: |
|
227 | 227 | self.refresh() |
|
228 | 228 | |
|
229 | 229 | virtual = req.env.get("PATH_INFO", "").strip('/') |
|
230 | 230 | tmpl = self.templater(req) |
|
231 | 231 | ctype = tmpl('mimetype', encoding=encoding.encoding) |
|
232 | 232 | ctype = templater.stringify(ctype) |
|
233 | 233 | |
|
234 | 234 | # a static file |
|
235 | 235 | if virtual.startswith('static/') or 'static' in req.form: |
|
236 | 236 | if virtual.startswith('static/'): |
|
237 | 237 | fname = virtual[7:] |
|
238 | 238 | else: |
|
239 | 239 | fname = req.form['static'][0] |
|
240 | 240 | static = self.ui.config("web", "static", None, |
|
241 | 241 | untrusted=False) |
|
242 | 242 | if not static: |
|
243 | 243 | tp = self.templatepath or templater.templatepaths() |
|
244 | 244 | if isinstance(tp, str): |
|
245 | 245 | tp = [tp] |
|
246 | 246 | static = [os.path.join(p, 'static') for p in tp] |
|
247 | 247 | staticfile(static, fname, req) |
|
248 | 248 | return [] |
|
249 | 249 | |
|
250 | 250 | # top-level index |
|
251 | 251 | elif not virtual: |
|
252 | 252 | req.respond(HTTP_OK, ctype) |
|
253 | 253 | return self.makeindex(req, tmpl) |
|
254 | 254 | |
|
255 | 255 | # nested indexes and hgwebs |
|
256 | 256 | |
|
257 | 257 | repos = dict(self.repos) |
|
258 | 258 | virtualrepo = virtual |
|
259 | 259 | while virtualrepo: |
|
260 | 260 | real = repos.get(virtualrepo) |
|
261 | 261 | if real: |
|
262 | 262 | req.env['REPO_NAME'] = virtualrepo |
|
263 | 263 | try: |
|
264 | 264 | # ensure caller gets private copy of ui |
|
265 | 265 | repo = hg.repository(self.ui.copy(), real) |
|
266 | 266 | return hgweb_mod.hgweb(repo).run_wsgi(req) |
|
267 | 267 | except IOError as inst: |
|
268 | 268 | msg = inst.strerror |
|
269 | 269 | raise ErrorResponse(HTTP_SERVER_ERROR, msg) |
|
270 | 270 | except error.RepoError as inst: |
|
271 | 271 | raise ErrorResponse(HTTP_SERVER_ERROR, str(inst)) |
|
272 | 272 | |
|
273 | 273 | up = virtualrepo.rfind('/') |
|
274 | 274 | if up < 0: |
|
275 | 275 | break |
|
276 | 276 | virtualrepo = virtualrepo[:up] |
|
277 | 277 | |
|
278 | 278 | # browse subdirectories |
|
279 | 279 | subdir = virtual + '/' |
|
280 | 280 | if [r for r in repos if r.startswith(subdir)]: |
|
281 | 281 | req.respond(HTTP_OK, ctype) |
|
282 | 282 | return self.makeindex(req, tmpl, subdir) |
|
283 | 283 | |
|
284 | 284 | # prefixes not found |
|
285 | 285 | req.respond(HTTP_NOT_FOUND, ctype) |
|
286 | 286 | return tmpl("notfound", repo=virtual) |
|
287 | 287 | |
|
288 | 288 | except ErrorResponse as err: |
|
289 | 289 | req.respond(err, ctype) |
|
290 | 290 | return tmpl('error', error=err.message or '') |
|
291 | 291 | finally: |
|
292 | 292 | tmpl = None |
|
293 | 293 | |
|
294 | 294 | def makeindex(self, req, tmpl, subdir=""): |
|
295 | 295 | |
|
296 | 296 | def archivelist(ui, nodeid, url): |
|
297 | 297 | allowed = ui.configlist("web", "allow_archive", untrusted=True) |
|
298 | 298 | archives = [] |
|
299 | 299 | for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]: |
|
300 | 300 | if i[0] in allowed or ui.configbool("web", "allow" + i[0], |
|
301 | 301 | untrusted=True): |
|
302 | 302 | archives.append({"type" : i[0], "extension": i[1], |
|
303 | 303 | "node": nodeid, "url": url}) |
|
304 | 304 | return archives |
|
305 | 305 | |
|
306 | 306 | def rawentries(subdir="", **map): |
|
307 | 307 | |
|
308 | 308 | descend = self.ui.configbool('web', 'descend', True) |
|
309 | 309 | collapse = self.ui.configbool('web', 'collapse', False) |
|
310 | 310 | seenrepos = set() |
|
311 | 311 | seendirs = set() |
|
312 | 312 | for name, path in self.repos: |
|
313 | 313 | |
|
314 | 314 | if not name.startswith(subdir): |
|
315 | 315 | continue |
|
316 | 316 | name = name[len(subdir):] |
|
317 | 317 | directory = False |
|
318 | 318 | |
|
319 | 319 | if '/' in name: |
|
320 | 320 | if not descend: |
|
321 | 321 | continue |
|
322 | 322 | |
|
323 | 323 | nameparts = name.split('/') |
|
324 | 324 | rootname = nameparts[0] |
|
325 | 325 | |
|
326 | 326 | if not collapse: |
|
327 | 327 | pass |
|
328 | 328 | elif rootname in seendirs: |
|
329 | 329 | continue |
|
330 | 330 | elif rootname in seenrepos: |
|
331 | 331 | pass |
|
332 | 332 | else: |
|
333 | 333 | directory = True |
|
334 | 334 | name = rootname |
|
335 | 335 | |
|
336 | 336 | # redefine the path to refer to the directory |
|
337 | 337 | discarded = '/'.join(nameparts[1:]) |
|
338 | 338 | |
|
339 | 339 | # remove name parts plus accompanying slash |
|
340 | 340 | path = path[:-len(discarded) - 1] |
|
341 | 341 | |
|
342 | 342 | try: |
|
343 | 343 | r = hg.repository(self.ui, path) |
|
344 | 344 | directory = False |
|
345 | 345 | except (IOError, error.RepoError): |
|
346 | 346 | pass |
|
347 | 347 | |
|
348 | 348 | parts = [name] |
|
349 | 349 | if 'PATH_INFO' in req.env: |
|
350 | 350 | parts.insert(0, req.env['PATH_INFO'].rstrip('/')) |
|
351 | 351 | if req.env['SCRIPT_NAME']: |
|
352 | 352 | parts.insert(0, req.env['SCRIPT_NAME']) |
|
353 | 353 | url = re.sub(r'/+', '/', '/'.join(parts) + '/') |
|
354 | 354 | |
|
355 | 355 | # show either a directory entry or a repository |
|
356 | 356 | if directory: |
|
357 | 357 | # get the directory's time information |
|
358 | 358 | try: |
|
359 | 359 | d = (get_mtime(path), util.makedate()[1]) |
|
360 | 360 | except OSError: |
|
361 | 361 | continue |
|
362 | 362 | |
|
363 | 363 | # add '/' to the name to make it obvious that |
|
364 | 364 | # the entry is a directory, not a regular repository |
|
365 | 365 | row = {'contact': "", |
|
366 | 366 | 'contact_sort': "", |
|
367 | 367 | 'name': name + '/', |
|
368 | 368 | 'name_sort': name, |
|
369 | 369 | 'url': url, |
|
370 | 370 | 'description': "", |
|
371 | 371 | 'description_sort': "", |
|
372 | 372 | 'lastchange': d, |
|
373 | 373 | 'lastchange_sort': d[1]-d[0], |
|
374 | 374 | 'archives': [], |
|
375 | 375 | 'isdirectory': True, |
|
376 | 376 | 'labels': [], |
|
377 | 377 | } |
|
378 | 378 | |
|
379 | 379 | seendirs.add(name) |
|
380 | 380 | yield row |
|
381 | 381 | continue |
|
382 | 382 | |
|
383 | 383 | u = self.ui.copy() |
|
384 | 384 | try: |
|
385 | 385 | u.readconfig(os.path.join(path, '.hg', 'hgrc')) |
|
386 | 386 | except Exception as e: |
|
387 | 387 | u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e)) |
|
388 | 388 | continue |
|
389 | 389 | def get(section, name, default=None): |
|
390 | 390 | return u.config(section, name, default, untrusted=True) |
|
391 | 391 | |
|
392 | 392 | if u.configbool("web", "hidden", untrusted=True): |
|
393 | 393 | continue |
|
394 | 394 | |
|
395 | 395 | if not self.read_allowed(u, req): |
|
396 | 396 | continue |
|
397 | 397 | |
|
398 | 398 | # update time with local timezone |
|
399 | 399 | try: |
|
400 | 400 | r = hg.repository(self.ui, path) |
|
401 | 401 | except IOError: |
|
402 | 402 | u.warn(_('error accessing repository at %s\n') % path) |
|
403 | 403 | continue |
|
404 | 404 | except error.RepoError: |
|
405 | 405 | u.warn(_('error accessing repository at %s\n') % path) |
|
406 | 406 | continue |
|
407 | 407 | try: |
|
408 | 408 | d = (get_mtime(r.spath), util.makedate()[1]) |
|
409 | 409 | except OSError: |
|
410 | 410 | continue |
|
411 | 411 | |
|
412 | 412 | contact = get_contact(get) |
|
413 | 413 | description = get("web", "description", "") |
|
414 | 414 | seenrepos.add(name) |
|
415 | 415 | name = get("web", "name", name) |
|
416 | 416 | row = {'contact': contact or "unknown", |
|
417 | 417 | 'contact_sort': contact.upper() or "unknown", |
|
418 | 418 | 'name': name, |
|
419 | 419 | 'name_sort': name, |
|
420 | 420 | 'url': url, |
|
421 | 421 | 'description': description or "unknown", |
|
422 | 422 | 'description_sort': description.upper() or "unknown", |
|
423 | 423 | 'lastchange': d, |
|
424 | 424 | 'lastchange_sort': d[1]-d[0], |
|
425 | 425 | 'archives': archivelist(u, "tip", url), |
|
426 | 426 | 'isdirectory': None, |
|
427 | 427 | 'labels': u.configlist('web', 'labels', untrusted=True), |
|
428 | 428 | } |
|
429 | 429 | |
|
430 | 430 | yield row |
|
431 | 431 | |
|
432 | 432 | sortdefault = None, False |
|
433 | 433 | def entries(sortcolumn="", descending=False, subdir="", **map): |
|
434 | 434 | rows = rawentries(subdir=subdir, **map) |
|
435 | 435 | |
|
436 | 436 | if sortcolumn and sortdefault != (sortcolumn, descending): |
|
437 | 437 | sortkey = '%s_sort' % sortcolumn |
|
438 | 438 | rows = sorted(rows, key=lambda x: x[sortkey], |
|
439 | 439 | reverse=descending) |
|
440 | 440 | for row, parity in zip(rows, paritygen(self.stripecount)): |
|
441 | 441 | row['parity'] = parity |
|
442 | 442 | yield row |
|
443 | 443 | |
|
444 | 444 | self.refresh() |
|
445 | 445 | sortable = ["name", "description", "contact", "lastchange"] |
|
446 | 446 | sortcolumn, descending = sortdefault |
|
447 | 447 | if 'sort' in req.form: |
|
448 | 448 | sortcolumn = req.form['sort'][0] |
|
449 | 449 | descending = sortcolumn.startswith('-') |
|
450 | 450 | if descending: |
|
451 | 451 | sortcolumn = sortcolumn[1:] |
|
452 | 452 | if sortcolumn not in sortable: |
|
453 | 453 | sortcolumn = "" |
|
454 | 454 | |
|
455 | 455 | sort = [("sort_%s" % column, |
|
456 | 456 | "%s%s" % ((not descending and column == sortcolumn) |
|
457 | 457 | and "-" or "", column)) |
|
458 | 458 | for column in sortable] |
|
459 | 459 | |
|
460 | 460 | self.refresh() |
|
461 | 461 | self.updatereqenv(req.env) |
|
462 | 462 | |
|
463 | 463 | return tmpl("index", entries=entries, subdir=subdir, |
|
464 | 464 | pathdef=hgweb_mod.makebreadcrumb('/' + subdir, self.prefix), |
|
465 | 465 | sortcolumn=sortcolumn, descending=descending, |
|
466 | 466 | **dict(sort)) |
|
467 | 467 | |
|
468 | 468 | def templater(self, req): |
|
469 | 469 | |
|
470 | 470 | def motd(**map): |
|
471 | 471 | if self.motd is not None: |
|
472 | 472 | yield self.motd |
|
473 | 473 | else: |
|
474 | 474 | yield config('web', 'motd', '') |
|
475 | 475 | |
|
476 | 476 | def config(section, name, default=None, untrusted=True): |
|
477 | 477 | return self.ui.config(section, name, default, untrusted) |
|
478 | 478 | |
|
479 | 479 | self.updatereqenv(req.env) |
|
480 | 480 | |
|
481 | 481 | url = req.env.get('SCRIPT_NAME', '') |
|
482 | 482 | if not url.endswith('/'): |
|
483 | 483 | url += '/' |
|
484 | 484 | |
|
485 | 485 | vars = {} |
|
486 | 486 | styles = ( |
|
487 | 487 | req.form.get('style', [None])[0], |
|
488 | 488 | config('web', 'style'), |
|
489 | 489 | 'paper' |
|
490 | 490 | ) |
|
491 | 491 | style, mapfile = templater.stylemap(styles, self.templatepath) |
|
492 | 492 | if style == styles[0]: |
|
493 | 493 | vars['style'] = style |
|
494 | 494 | |
|
495 | 495 | start = url[-1] == '?' and '&' or '?' |
|
496 | 496 | sessionvars = webutil.sessionvars(vars, start) |
|
497 | 497 | logourl = config('web', 'logourl', 'https://mercurial-scm.org/') |
|
498 | 498 | logoimg = config('web', 'logoimg', 'hglogo.png') |
|
499 | 499 | staticurl = config('web', 'staticurl') or url + 'static/' |
|
500 | 500 | if not staticurl.endswith('/'): |
|
501 | 501 | staticurl += '/' |
|
502 | 502 | |
|
503 | 503 | defaults = { |
|
504 | 504 | "encoding": encoding.encoding, |
|
505 | 505 | "motd": motd, |
|
506 | 506 | "url": url, |
|
507 | 507 | "logourl": logourl, |
|
508 | 508 | "logoimg": logoimg, |
|
509 | 509 | "staticurl": staticurl, |
|
510 | 510 | "sessionvars": sessionvars, |
|
511 | 511 | "style": style, |
|
512 | 512 | } |
|
513 | 513 | tmpl = templater.templater.frommapfile(mapfile, defaults=defaults) |
|
514 | 514 | return tmpl |
|
515 | 515 | |
|
516 | 516 | def updatereqenv(self, env): |
|
517 | 517 | if self._baseurl is not None: |
|
518 | 518 | name, port, path = geturlcgivars(self._baseurl, env['SERVER_PORT']) |
|
519 | 519 | env['SERVER_NAME'] = name |
|
520 | 520 | env['SERVER_PORT'] = port |
|
521 | 521 | env['SCRIPT_NAME'] = path |
@@ -1,1326 +1,1326 | |||
|
1 | 1 | # |
|
2 | 2 | # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net> |
|
3 | 3 | # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> |
|
4 | 4 | # |
|
5 | 5 | # This software may be used and distributed according to the terms of the |
|
6 | 6 | # GNU General Public License version 2 or any later version. |
|
7 | 7 | |
|
8 | 8 | from __future__ import absolute_import |
|
9 | 9 | |
|
10 | 10 | import cgi |
|
11 | 11 | import copy |
|
12 | 12 | import mimetypes |
|
13 | 13 | import os |
|
14 | 14 | import re |
|
15 | 15 | |
|
16 | 16 | from ..i18n import _ |
|
17 | 17 | from ..node import hex, short |
|
18 | 18 | |
|
19 | 19 | from .common import ( |
|
20 | 20 | ErrorResponse, |
|
21 | 21 | HTTP_FORBIDDEN, |
|
22 | 22 | HTTP_NOT_FOUND, |
|
23 | 23 | HTTP_OK, |
|
24 | 24 | get_contact, |
|
25 | 25 | paritygen, |
|
26 | 26 | staticfile, |
|
27 | 27 | ) |
|
28 | 28 | |
|
29 | 29 | from .. import ( |
|
30 | 30 | archival, |
|
31 | 31 | encoding, |
|
32 | 32 | error, |
|
33 | 33 | graphmod, |
|
34 | 34 | revset, |
|
35 | 35 | scmutil, |
|
36 | 36 | templatefilters, |
|
37 | 37 | templater, |
|
38 | 38 | util, |
|
39 | 39 | ) |
|
40 | 40 | |
|
41 | 41 | from . import ( |
|
42 | 42 | webutil, |
|
43 | 43 | ) |
|
44 | 44 | |
|
45 | 45 | __all__ = [] |
|
46 | 46 | commands = {} |
|
47 | 47 | |
|
48 | 48 | class webcommand(object): |
|
49 | 49 | """Decorator used to register a web command handler. |
|
50 | 50 | |
|
51 | 51 | The decorator takes as its positional arguments the name/path the |
|
52 | 52 | command should be accessible under. |
|
53 | 53 | |
|
54 | 54 | Usage: |
|
55 | 55 | |
|
56 | 56 | @webcommand('mycommand') |
|
57 | 57 | def mycommand(web, req, tmpl): |
|
58 | 58 | pass |
|
59 | 59 | """ |
|
60 | 60 | |
|
61 | 61 | def __init__(self, name): |
|
62 | 62 | self.name = name |
|
63 | 63 | |
|
64 | 64 | def __call__(self, func): |
|
65 | 65 | __all__.append(self.name) |
|
66 | 66 | commands[self.name] = func |
|
67 | 67 | return func |
|
68 | 68 | |
|
69 | 69 | @webcommand('log') |
|
70 | 70 | def log(web, req, tmpl): |
|
71 | 71 | """ |
|
72 | 72 | /log[/{revision}[/{path}]] |
|
73 | 73 | -------------------------- |
|
74 | 74 | |
|
75 | 75 | Show repository or file history. |
|
76 | 76 | |
|
77 | 77 | For URLs of the form ``/log/{revision}``, a list of changesets starting at |
|
78 | 78 | the specified changeset identifier is shown. If ``{revision}`` is not |
|
79 | 79 | defined, the default is ``tip``. This form is equivalent to the |
|
80 | 80 | ``changelog`` handler. |
|
81 | 81 | |
|
82 | 82 | For URLs of the form ``/log/{revision}/{file}``, the history for a specific |
|
83 | 83 | file will be shown. This form is equivalent to the ``filelog`` handler. |
|
84 | 84 | """ |
|
85 | 85 | |
|
86 | 86 | if 'file' in req.form and req.form['file'][0]: |
|
87 | 87 | return filelog(web, req, tmpl) |
|
88 | 88 | else: |
|
89 | 89 | return changelog(web, req, tmpl) |
|
90 | 90 | |
|
91 | 91 | @webcommand('rawfile') |
|
92 | 92 | def rawfile(web, req, tmpl): |
|
93 | 93 | guessmime = web.configbool('web', 'guessmime', False) |
|
94 | 94 | |
|
95 | 95 | path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0]) |
|
96 | 96 | if not path: |
|
97 | 97 | content = manifest(web, req, tmpl) |
|
98 | 98 | req.respond(HTTP_OK, web.ctype) |
|
99 | 99 | return content |
|
100 | 100 | |
|
101 | 101 | try: |
|
102 | 102 | fctx = webutil.filectx(web.repo, req) |
|
103 | 103 | except error.LookupError as inst: |
|
104 | 104 | try: |
|
105 | 105 | content = manifest(web, req, tmpl) |
|
106 | 106 | req.respond(HTTP_OK, web.ctype) |
|
107 | 107 | return content |
|
108 | 108 | except ErrorResponse: |
|
109 | 109 | raise inst |
|
110 | 110 | |
|
111 | 111 | path = fctx.path() |
|
112 | 112 | text = fctx.data() |
|
113 | 113 | mt = 'application/binary' |
|
114 | 114 | if guessmime: |
|
115 | 115 | mt = mimetypes.guess_type(path)[0] |
|
116 | 116 | if mt is None: |
|
117 | 117 | if util.binary(text): |
|
118 | 118 | mt = 'application/binary' |
|
119 | 119 | else: |
|
120 | 120 | mt = 'text/plain' |
|
121 | 121 | if mt.startswith('text/'): |
|
122 | 122 | mt += '; charset="%s"' % encoding.encoding |
|
123 | 123 | |
|
124 | 124 | req.respond(HTTP_OK, mt, path, body=text) |
|
125 | 125 | return [] |
|
126 | 126 | |
|
127 | 127 | def _filerevision(web, req, tmpl, fctx): |
|
128 | 128 | f = fctx.path() |
|
129 | 129 | text = fctx.data() |
|
130 | 130 | parity = paritygen(web.stripecount) |
|
131 | 131 | |
|
132 | 132 | if util.binary(text): |
|
133 | 133 | mt = mimetypes.guess_type(f)[0] or 'application/octet-stream' |
|
134 | 134 | text = '(binary:%s)' % mt |
|
135 | 135 | |
|
136 | 136 | def lines(): |
|
137 | 137 | for lineno, t in enumerate(text.splitlines(True)): |
|
138 | 138 | yield {"line": t, |
|
139 | 139 | "lineid": "l%d" % (lineno + 1), |
|
140 | 140 | "linenumber": "% 6d" % (lineno + 1), |
|
141 | 141 | "parity": next(parity)} |
|
142 | 142 | |
|
143 | 143 | return tmpl("filerevision", |
|
144 | 144 | file=f, |
|
145 | 145 | path=webutil.up(f), |
|
146 | 146 | text=lines(), |
|
147 | 147 | symrev=webutil.symrevorshortnode(req, fctx), |
|
148 | 148 | rename=webutil.renamelink(fctx), |
|
149 | 149 | permissions=fctx.manifest().flags(f), |
|
150 | 150 | **webutil.commonentry(web.repo, fctx)) |
|
151 | 151 | |
|
152 | 152 | @webcommand('file') |
|
153 | 153 | def file(web, req, tmpl): |
|
154 | 154 | """ |
|
155 | 155 | /file/{revision}[/{path}] |
|
156 | 156 | ------------------------- |
|
157 | 157 | |
|
158 | 158 | Show information about a directory or file in the repository. |
|
159 | 159 | |
|
160 | 160 | Info about the ``path`` given as a URL parameter will be rendered. |
|
161 | 161 | |
|
162 | 162 | If ``path`` is a directory, information about the entries in that |
|
163 | 163 | directory will be rendered. This form is equivalent to the ``manifest`` |
|
164 | 164 | handler. |
|
165 | 165 | |
|
166 | 166 | If ``path`` is a file, information about that file will be shown via |
|
167 | 167 | the ``filerevision`` template. |
|
168 | 168 | |
|
169 | 169 | If ``path`` is not defined, information about the root directory will |
|
170 | 170 | be rendered. |
|
171 | 171 | """ |
|
172 | 172 | path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0]) |
|
173 | 173 | if not path: |
|
174 | 174 | return manifest(web, req, tmpl) |
|
175 | 175 | try: |
|
176 | 176 | return _filerevision(web, req, tmpl, webutil.filectx(web.repo, req)) |
|
177 | 177 | except error.LookupError as inst: |
|
178 | 178 | try: |
|
179 | 179 | return manifest(web, req, tmpl) |
|
180 | 180 | except ErrorResponse: |
|
181 | 181 | raise inst |
|
182 | 182 | |
|
183 | 183 | def _search(web, req, tmpl): |
|
184 | 184 | MODE_REVISION = 'rev' |
|
185 | 185 | MODE_KEYWORD = 'keyword' |
|
186 | 186 | MODE_REVSET = 'revset' |
|
187 | 187 | |
|
188 | 188 | def revsearch(ctx): |
|
189 | 189 | yield ctx |
|
190 | 190 | |
|
191 | 191 | def keywordsearch(query): |
|
192 | 192 | lower = encoding.lower |
|
193 | 193 | qw = lower(query).split() |
|
194 | 194 | |
|
195 | 195 | def revgen(): |
|
196 | 196 | cl = web.repo.changelog |
|
197 | 197 | for i in xrange(len(web.repo) - 1, 0, -100): |
|
198 | 198 | l = [] |
|
199 | 199 | for j in cl.revs(max(0, i - 99), i): |
|
200 | 200 | ctx = web.repo[j] |
|
201 | 201 | l.append(ctx) |
|
202 | 202 | l.reverse() |
|
203 | 203 | for e in l: |
|
204 | 204 | yield e |
|
205 | 205 | |
|
206 | 206 | for ctx in revgen(): |
|
207 | 207 | miss = 0 |
|
208 | 208 | for q in qw: |
|
209 | 209 | if not (q in lower(ctx.user()) or |
|
210 | 210 | q in lower(ctx.description()) or |
|
211 | 211 | q in lower(" ".join(ctx.files()))): |
|
212 | 212 | miss = 1 |
|
213 | 213 | break |
|
214 | 214 | if miss: |
|
215 | 215 | continue |
|
216 | 216 | |
|
217 | 217 | yield ctx |
|
218 | 218 | |
|
219 | 219 | def revsetsearch(revs): |
|
220 | 220 | for r in revs: |
|
221 | 221 | yield web.repo[r] |
|
222 | 222 | |
|
223 | 223 | searchfuncs = { |
|
224 | 224 | MODE_REVISION: (revsearch, 'exact revision search'), |
|
225 | 225 | MODE_KEYWORD: (keywordsearch, 'literal keyword search'), |
|
226 | 226 | MODE_REVSET: (revsetsearch, 'revset expression search'), |
|
227 | 227 | } |
|
228 | 228 | |
|
229 | 229 | def getsearchmode(query): |
|
230 | 230 | try: |
|
231 | 231 | ctx = web.repo[query] |
|
232 | 232 | except (error.RepoError, error.LookupError): |
|
233 | 233 | # query is not an exact revision pointer, need to |
|
234 | 234 | # decide if it's a revset expression or keywords |
|
235 | 235 | pass |
|
236 | 236 | else: |
|
237 | 237 | return MODE_REVISION, ctx |
|
238 | 238 | |
|
239 | 239 | revdef = 'reverse(%s)' % query |
|
240 | 240 | try: |
|
241 | 241 | tree = revset.parse(revdef) |
|
242 | 242 | except error.ParseError: |
|
243 | 243 | # can't parse to a revset tree |
|
244 | 244 | return MODE_KEYWORD, query |
|
245 | 245 | |
|
246 | 246 | if revset.depth(tree) <= 2: |
|
247 | 247 | # no revset syntax used |
|
248 | 248 | return MODE_KEYWORD, query |
|
249 | 249 | |
|
250 | 250 | if any((token, (value or '')[:3]) == ('string', 're:') |
|
251 | 251 | for token, value, pos in revset.tokenize(revdef)): |
|
252 | 252 | return MODE_KEYWORD, query |
|
253 | 253 | |
|
254 | 254 | funcsused = revset.funcsused(tree) |
|
255 | 255 | if not funcsused.issubset(revset.safesymbols): |
|
256 | 256 | return MODE_KEYWORD, query |
|
257 | 257 | |
|
258 | 258 | mfunc = revset.match(web.repo.ui, revdef) |
|
259 | 259 | try: |
|
260 | 260 | revs = mfunc(web.repo) |
|
261 | 261 | return MODE_REVSET, revs |
|
262 | 262 | # ParseError: wrongly placed tokens, wrongs arguments, etc |
|
263 | 263 | # RepoLookupError: no such revision, e.g. in 'revision:' |
|
264 | 264 | # Abort: bookmark/tag not exists |
|
265 | 265 | # LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo |
|
266 | 266 | except (error.ParseError, error.RepoLookupError, error.Abort, |
|
267 | 267 | LookupError): |
|
268 | 268 | return MODE_KEYWORD, query |
|
269 | 269 | |
|
270 | 270 | def changelist(**map): |
|
271 | 271 | count = 0 |
|
272 | 272 | |
|
273 | 273 | for ctx in searchfunc[0](funcarg): |
|
274 | 274 | count += 1 |
|
275 | 275 | n = ctx.node() |
|
276 | 276 | showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n) |
|
277 | 277 | files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles) |
|
278 | 278 | |
|
279 | 279 | yield tmpl('searchentry', |
|
280 | 280 | parity=next(parity), |
|
281 | 281 | changelogtag=showtags, |
|
282 | 282 | files=files, |
|
283 | 283 | **webutil.commonentry(web.repo, ctx)) |
|
284 | 284 | |
|
285 | 285 | if count >= revcount: |
|
286 | 286 | break |
|
287 | 287 | |
|
288 | 288 | query = req.form['rev'][0] |
|
289 | 289 | revcount = web.maxchanges |
|
290 | 290 | if 'revcount' in req.form: |
|
291 | 291 | try: |
|
292 | 292 | revcount = int(req.form.get('revcount', [revcount])[0]) |
|
293 | 293 | revcount = max(revcount, 1) |
|
294 | 294 | tmpl.defaults['sessionvars']['revcount'] = revcount |
|
295 | 295 | except ValueError: |
|
296 | 296 | pass |
|
297 | 297 | |
|
298 | 298 | lessvars = copy.copy(tmpl.defaults['sessionvars']) |
|
299 | 299 | lessvars['revcount'] = max(revcount / 2, 1) |
|
300 | 300 | lessvars['rev'] = query |
|
301 | 301 | morevars = copy.copy(tmpl.defaults['sessionvars']) |
|
302 | 302 | morevars['revcount'] = revcount * 2 |
|
303 | 303 | morevars['rev'] = query |
|
304 | 304 | |
|
305 | 305 | mode, funcarg = getsearchmode(query) |
|
306 | 306 | |
|
307 | 307 | if 'forcekw' in req.form: |
|
308 | 308 | showforcekw = '' |
|
309 | 309 | showunforcekw = searchfuncs[mode][1] |
|
310 | 310 | mode = MODE_KEYWORD |
|
311 | 311 | funcarg = query |
|
312 | 312 | else: |
|
313 | 313 | if mode != MODE_KEYWORD: |
|
314 | 314 | showforcekw = searchfuncs[MODE_KEYWORD][1] |
|
315 | 315 | else: |
|
316 | 316 | showforcekw = '' |
|
317 | 317 | showunforcekw = '' |
|
318 | 318 | |
|
319 | 319 | searchfunc = searchfuncs[mode] |
|
320 | 320 | |
|
321 | 321 | tip = web.repo['tip'] |
|
322 | 322 | parity = paritygen(web.stripecount) |
|
323 | 323 | |
|
324 | 324 | return tmpl('search', query=query, node=tip.hex(), symrev='tip', |
|
325 | 325 | entries=changelist, archives=web.archivelist("tip"), |
|
326 | 326 | morevars=morevars, lessvars=lessvars, |
|
327 | 327 | modedesc=searchfunc[1], |
|
328 | 328 | showforcekw=showforcekw, showunforcekw=showunforcekw) |
|
329 | 329 | |
|
330 | 330 | @webcommand('changelog') |
|
331 | 331 | def changelog(web, req, tmpl, shortlog=False): |
|
332 | 332 | """ |
|
333 | 333 | /changelog[/{revision}] |
|
334 | 334 | ----------------------- |
|
335 | 335 | |
|
336 | 336 | Show information about multiple changesets. |
|
337 | 337 | |
|
338 | 338 | If the optional ``revision`` URL argument is absent, information about |
|
339 | 339 | all changesets starting at ``tip`` will be rendered. If the ``revision`` |
|
340 | 340 | argument is present, changesets will be shown starting from the specified |
|
341 | 341 | revision. |
|
342 | 342 | |
|
343 | 343 | If ``revision`` is absent, the ``rev`` query string argument may be |
|
344 | 344 | defined. This will perform a search for changesets. |
|
345 | 345 | |
|
346 | 346 | The argument for ``rev`` can be a single revision, a revision set, |
|
347 | 347 | or a literal keyword to search for in changeset data (equivalent to |
|
348 | 348 | :hg:`log -k`). |
|
349 | 349 | |
|
350 | 350 | The ``revcount`` query string argument defines the maximum numbers of |
|
351 | 351 | changesets to render. |
|
352 | 352 | |
|
353 | 353 | For non-searches, the ``changelog`` template will be rendered. |
|
354 | 354 | """ |
|
355 | 355 | |
|
356 | 356 | query = '' |
|
357 | 357 | if 'node' in req.form: |
|
358 | 358 | ctx = webutil.changectx(web.repo, req) |
|
359 | 359 | symrev = webutil.symrevorshortnode(req, ctx) |
|
360 | 360 | elif 'rev' in req.form: |
|
361 | 361 | return _search(web, req, tmpl) |
|
362 | 362 | else: |
|
363 | 363 | ctx = web.repo['tip'] |
|
364 | 364 | symrev = 'tip' |
|
365 | 365 | |
|
366 | 366 | def changelist(): |
|
367 | 367 | revs = [] |
|
368 | 368 | if pos != -1: |
|
369 | 369 | revs = web.repo.changelog.revs(pos, 0) |
|
370 | 370 | curcount = 0 |
|
371 | 371 | for rev in revs: |
|
372 | 372 | curcount += 1 |
|
373 | 373 | if curcount > revcount + 1: |
|
374 | 374 | break |
|
375 | 375 | |
|
376 | 376 | entry = webutil.changelistentry(web, web.repo[rev], tmpl) |
|
377 | 377 | entry['parity'] = next(parity) |
|
378 | 378 | yield entry |
|
379 | 379 | |
|
380 | 380 | if shortlog: |
|
381 | 381 | revcount = web.maxshortchanges |
|
382 | 382 | else: |
|
383 | 383 | revcount = web.maxchanges |
|
384 | 384 | |
|
385 | 385 | if 'revcount' in req.form: |
|
386 | 386 | try: |
|
387 | 387 | revcount = int(req.form.get('revcount', [revcount])[0]) |
|
388 | 388 | revcount = max(revcount, 1) |
|
389 | 389 | tmpl.defaults['sessionvars']['revcount'] = revcount |
|
390 | 390 | except ValueError: |
|
391 | 391 | pass |
|
392 | 392 | |
|
393 | 393 | lessvars = copy.copy(tmpl.defaults['sessionvars']) |
|
394 | 394 | lessvars['revcount'] = max(revcount / 2, 1) |
|
395 | 395 | morevars = copy.copy(tmpl.defaults['sessionvars']) |
|
396 | 396 | morevars['revcount'] = revcount * 2 |
|
397 | 397 | |
|
398 | 398 | count = len(web.repo) |
|
399 | 399 | pos = ctx.rev() |
|
400 | 400 | parity = paritygen(web.stripecount) |
|
401 | 401 | |
|
402 | 402 | changenav = webutil.revnav(web.repo).gen(pos, revcount, count) |
|
403 | 403 | |
|
404 | 404 | entries = list(changelist()) |
|
405 | 405 | latestentry = entries[:1] |
|
406 | 406 | if len(entries) > revcount: |
|
407 | 407 | nextentry = entries[-1:] |
|
408 | 408 | entries = entries[:-1] |
|
409 | 409 | else: |
|
410 | 410 | nextentry = [] |
|
411 | 411 | |
|
412 | 412 | return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav, |
|
413 | 413 | node=ctx.hex(), rev=pos, symrev=symrev, changesets=count, |
|
414 | 414 | entries=entries, |
|
415 | 415 | latestentry=latestentry, nextentry=nextentry, |
|
416 | 416 | archives=web.archivelist("tip"), revcount=revcount, |
|
417 | 417 | morevars=morevars, lessvars=lessvars, query=query) |
|
418 | 418 | |
|
419 | 419 | @webcommand('shortlog') |
|
420 | 420 | def shortlog(web, req, tmpl): |
|
421 | 421 | """ |
|
422 | 422 | /shortlog |
|
423 | 423 | --------- |
|
424 | 424 | |
|
425 | 425 | Show basic information about a set of changesets. |
|
426 | 426 | |
|
427 | 427 | This accepts the same parameters as the ``changelog`` handler. The only |
|
428 | 428 | difference is the ``shortlog`` template will be rendered instead of the |
|
429 | 429 | ``changelog`` template. |
|
430 | 430 | """ |
|
431 | 431 | return changelog(web, req, tmpl, shortlog=True) |
|
432 | 432 | |
|
433 | 433 | @webcommand('changeset') |
|
434 | 434 | def changeset(web, req, tmpl): |
|
435 | 435 | """ |
|
436 | 436 | /changeset[/{revision}] |
|
437 | 437 | ----------------------- |
|
438 | 438 | |
|
439 | 439 | Show information about a single changeset. |
|
440 | 440 | |
|
441 | 441 | A URL path argument is the changeset identifier to show. See ``hg help |
|
442 | 442 | revisions`` for possible values. If not defined, the ``tip`` changeset |
|
443 | 443 | will be shown. |
|
444 | 444 | |
|
445 | 445 | The ``changeset`` template is rendered. Contents of the ``changesettag``, |
|
446 | 446 | ``changesetbookmark``, ``filenodelink``, ``filenolink``, and the many |
|
447 | 447 | templates related to diffs may all be used to produce the output. |
|
448 | 448 | """ |
|
449 | 449 | ctx = webutil.changectx(web.repo, req) |
|
450 | 450 | |
|
451 | 451 | return tmpl('changeset', **webutil.changesetentry(web, req, tmpl, ctx)) |
|
452 | 452 | |
|
453 | 453 | rev = webcommand('rev')(changeset) |
|
454 | 454 | |
|
455 | 455 | def decodepath(path): |
|
456 | 456 | """Hook for mapping a path in the repository to a path in the |
|
457 | 457 | working copy. |
|
458 | 458 | |
|
459 | 459 | Extensions (e.g., largefiles) can override this to remap files in |
|
460 | 460 | the virtual file system presented by the manifest command below.""" |
|
461 | 461 | return path |
|
462 | 462 | |
|
463 | 463 | @webcommand('manifest') |
|
464 | 464 | def manifest(web, req, tmpl): |
|
465 | 465 | """ |
|
466 | 466 | /manifest[/{revision}[/{path}]] |
|
467 | 467 | ------------------------------- |
|
468 | 468 | |
|
469 | 469 | Show information about a directory. |
|
470 | 470 | |
|
471 | 471 | If the URL path arguments are omitted, information about the root |
|
472 | 472 | directory for the ``tip`` changeset will be shown. |
|
473 | 473 | |
|
474 | 474 | Because this handler can only show information for directories, it |
|
475 | 475 | is recommended to use the ``file`` handler instead, as it can handle both |
|
476 | 476 | directories and files. |
|
477 | 477 | |
|
478 | 478 | The ``manifest`` template will be rendered for this handler. |
|
479 | 479 | """ |
|
480 | 480 | if 'node' in req.form: |
|
481 | 481 | ctx = webutil.changectx(web.repo, req) |
|
482 | 482 | symrev = webutil.symrevorshortnode(req, ctx) |
|
483 | 483 | else: |
|
484 | 484 | ctx = web.repo['tip'] |
|
485 | 485 | symrev = 'tip' |
|
486 | 486 | path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0]) |
|
487 | 487 | mf = ctx.manifest() |
|
488 | 488 | node = ctx.node() |
|
489 | 489 | |
|
490 | 490 | files = {} |
|
491 | 491 | dirs = {} |
|
492 | 492 | parity = paritygen(web.stripecount) |
|
493 | 493 | |
|
494 | 494 | if path and path[-1] != "/": |
|
495 | 495 | path += "/" |
|
496 | 496 | l = len(path) |
|
497 | 497 | abspath = "/" + path |
|
498 | 498 | |
|
499 | 499 | for full, n in mf.iteritems(): |
|
500 | 500 | # the virtual path (working copy path) used for the full |
|
501 | 501 | # (repository) path |
|
502 | 502 | f = decodepath(full) |
|
503 | 503 | |
|
504 | 504 | if f[:l] != path: |
|
505 | 505 | continue |
|
506 | 506 | remain = f[l:] |
|
507 | 507 | elements = remain.split('/') |
|
508 | 508 | if len(elements) == 1: |
|
509 | 509 | files[remain] = full |
|
510 | 510 | else: |
|
511 | 511 | h = dirs # need to retain ref to dirs (root) |
|
512 | 512 | for elem in elements[0:-1]: |
|
513 | 513 | if elem not in h: |
|
514 | 514 | h[elem] = {} |
|
515 | 515 | h = h[elem] |
|
516 | 516 | if len(h) > 1: |
|
517 | 517 | break |
|
518 | 518 | h[None] = None # denotes files present |
|
519 | 519 | |
|
520 | 520 | if mf and not files and not dirs: |
|
521 | 521 | raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path) |
|
522 | 522 | |
|
523 | 523 | def filelist(**map): |
|
524 | 524 | for f in sorted(files): |
|
525 | 525 | full = files[f] |
|
526 | 526 | |
|
527 | 527 | fctx = ctx.filectx(full) |
|
528 | 528 | yield {"file": full, |
|
529 | 529 | "parity": next(parity), |
|
530 | 530 | "basename": f, |
|
531 | 531 | "date": fctx.date(), |
|
532 | 532 | "size": fctx.size(), |
|
533 | 533 | "permissions": mf.flags(full)} |
|
534 | 534 | |
|
535 | 535 | def dirlist(**map): |
|
536 | 536 | for d in sorted(dirs): |
|
537 | 537 | |
|
538 | 538 | emptydirs = [] |
|
539 | 539 | h = dirs[d] |
|
540 | 540 | while isinstance(h, dict) and len(h) == 1: |
|
541 | 541 | k, v = h.items()[0] |
|
542 | 542 | if v: |
|
543 | 543 | emptydirs.append(k) |
|
544 | 544 | h = v |
|
545 | 545 | |
|
546 | 546 | path = "%s%s" % (abspath, d) |
|
547 | 547 | yield {"parity": next(parity), |
|
548 | 548 | "path": path, |
|
549 | 549 | "emptydirs": "/".join(emptydirs), |
|
550 | 550 | "basename": d} |
|
551 | 551 | |
|
552 | 552 | return tmpl("manifest", |
|
553 | 553 | symrev=symrev, |
|
554 | 554 | path=abspath, |
|
555 | 555 | up=webutil.up(abspath), |
|
556 | 556 | upparity=next(parity), |
|
557 | 557 | fentries=filelist, |
|
558 | 558 | dentries=dirlist, |
|
559 | 559 | archives=web.archivelist(hex(node)), |
|
560 | 560 | **webutil.commonentry(web.repo, ctx)) |
|
561 | 561 | |
|
562 | 562 | @webcommand('tags') |
|
563 | 563 | def tags(web, req, tmpl): |
|
564 | 564 | """ |
|
565 | 565 | /tags |
|
566 | 566 | ----- |
|
567 | 567 | |
|
568 | 568 | Show information about tags. |
|
569 | 569 | |
|
570 | 570 | No arguments are accepted. |
|
571 | 571 | |
|
572 | 572 | The ``tags`` template is rendered. |
|
573 | 573 | """ |
|
574 | 574 | i = list(reversed(web.repo.tagslist())) |
|
575 | 575 | parity = paritygen(web.stripecount) |
|
576 | 576 | |
|
577 | 577 | def entries(notip, latestonly, **map): |
|
578 | 578 | t = i |
|
579 | 579 | if notip: |
|
580 | 580 | t = [(k, n) for k, n in i if k != "tip"] |
|
581 | 581 | if latestonly: |
|
582 | 582 | t = t[:1] |
|
583 | 583 | for k, n in t: |
|
584 | 584 | yield {"parity": next(parity), |
|
585 | 585 | "tag": k, |
|
586 | 586 | "date": web.repo[n].date(), |
|
587 | 587 | "node": hex(n)} |
|
588 | 588 | |
|
589 | 589 | return tmpl("tags", |
|
590 | 590 | node=hex(web.repo.changelog.tip()), |
|
591 | 591 | entries=lambda **x: entries(False, False, **x), |
|
592 | 592 | entriesnotip=lambda **x: entries(True, False, **x), |
|
593 | 593 | latestentry=lambda **x: entries(True, True, **x)) |
|
594 | 594 | |
|
595 | 595 | @webcommand('bookmarks') |
|
596 | 596 | def bookmarks(web, req, tmpl): |
|
597 | 597 | """ |
|
598 | 598 | /bookmarks |
|
599 | 599 | ---------- |
|
600 | 600 | |
|
601 | 601 | Show information about bookmarks. |
|
602 | 602 | |
|
603 | 603 | No arguments are accepted. |
|
604 | 604 | |
|
605 | 605 | The ``bookmarks`` template is rendered. |
|
606 | 606 | """ |
|
607 | 607 | i = [b for b in web.repo._bookmarks.items() if b[1] in web.repo] |
|
608 | 608 | sortkey = lambda b: (web.repo[b[1]].rev(), b[0]) |
|
609 | 609 | i = sorted(i, key=sortkey, reverse=True) |
|
610 | 610 | parity = paritygen(web.stripecount) |
|
611 | 611 | |
|
612 | 612 | def entries(latestonly, **map): |
|
613 | 613 | t = i |
|
614 | 614 | if latestonly: |
|
615 | 615 | t = i[:1] |
|
616 | 616 | for k, n in t: |
|
617 | 617 | yield {"parity": next(parity), |
|
618 | 618 | "bookmark": k, |
|
619 | 619 | "date": web.repo[n].date(), |
|
620 | 620 | "node": hex(n)} |
|
621 | 621 | |
|
622 | 622 | if i: |
|
623 | 623 | latestrev = i[0][1] |
|
624 | 624 | else: |
|
625 | 625 | latestrev = -1 |
|
626 | 626 | |
|
627 | 627 | return tmpl("bookmarks", |
|
628 | 628 | node=hex(web.repo.changelog.tip()), |
|
629 | 629 | lastchange=[{"date": web.repo[latestrev].date()}], |
|
630 | 630 | entries=lambda **x: entries(latestonly=False, **x), |
|
631 | 631 | latestentry=lambda **x: entries(latestonly=True, **x)) |
|
632 | 632 | |
|
633 | 633 | @webcommand('branches') |
|
634 | 634 | def branches(web, req, tmpl): |
|
635 | 635 | """ |
|
636 | 636 | /branches |
|
637 | 637 | --------- |
|
638 | 638 | |
|
639 | 639 | Show information about branches. |
|
640 | 640 | |
|
641 | 641 | All known branches are contained in the output, even closed branches. |
|
642 | 642 | |
|
643 | 643 | No arguments are accepted. |
|
644 | 644 | |
|
645 | 645 | The ``branches`` template is rendered. |
|
646 | 646 | """ |
|
647 | 647 | entries = webutil.branchentries(web.repo, web.stripecount) |
|
648 | 648 | latestentry = webutil.branchentries(web.repo, web.stripecount, 1) |
|
649 | 649 | return tmpl('branches', node=hex(web.repo.changelog.tip()), |
|
650 | 650 | entries=entries, latestentry=latestentry) |
|
651 | 651 | |
|
652 | 652 | @webcommand('summary') |
|
653 | 653 | def summary(web, req, tmpl): |
|
654 | 654 | """ |
|
655 | 655 | /summary |
|
656 | 656 | -------- |
|
657 | 657 | |
|
658 | 658 | Show a summary of repository state. |
|
659 | 659 | |
|
660 | 660 | Information about the latest changesets, bookmarks, tags, and branches |
|
661 | 661 | is captured by this handler. |
|
662 | 662 | |
|
663 | 663 | The ``summary`` template is rendered. |
|
664 | 664 | """ |
|
665 | 665 | i = reversed(web.repo.tagslist()) |
|
666 | 666 | |
|
667 | 667 | def tagentries(**map): |
|
668 | 668 | parity = paritygen(web.stripecount) |
|
669 | 669 | count = 0 |
|
670 | 670 | for k, n in i: |
|
671 | 671 | if k == "tip": # skip tip |
|
672 | 672 | continue |
|
673 | 673 | |
|
674 | 674 | count += 1 |
|
675 | 675 | if count > 10: # limit to 10 tags |
|
676 | 676 | break |
|
677 | 677 | |
|
678 | 678 | yield tmpl("tagentry", |
|
679 | 679 | parity=next(parity), |
|
680 | 680 | tag=k, |
|
681 | 681 | node=hex(n), |
|
682 | 682 | date=web.repo[n].date()) |
|
683 | 683 | |
|
684 | 684 | def bookmarks(**map): |
|
685 | 685 | parity = paritygen(web.stripecount) |
|
686 | 686 | marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo] |
|
687 | 687 | sortkey = lambda b: (web.repo[b[1]].rev(), b[0]) |
|
688 | 688 | marks = sorted(marks, key=sortkey, reverse=True) |
|
689 | 689 | for k, n in marks[:10]: # limit to 10 bookmarks |
|
690 | 690 | yield {'parity': next(parity), |
|
691 | 691 | 'bookmark': k, |
|
692 | 692 | 'date': web.repo[n].date(), |
|
693 | 693 | 'node': hex(n)} |
|
694 | 694 | |
|
695 | 695 | def changelist(**map): |
|
696 | 696 | parity = paritygen(web.stripecount, offset=start - end) |
|
697 | 697 | l = [] # build a list in forward order for efficiency |
|
698 | 698 | revs = [] |
|
699 | 699 | if start < end: |
|
700 | 700 | revs = web.repo.changelog.revs(start, end - 1) |
|
701 | 701 | for i in revs: |
|
702 | 702 | ctx = web.repo[i] |
|
703 | 703 | |
|
704 | 704 | l.append(tmpl( |
|
705 | 705 | 'shortlogentry', |
|
706 | 706 | parity=next(parity), |
|
707 | 707 | **webutil.commonentry(web.repo, ctx))) |
|
708 | 708 | |
|
709 | 709 | for entry in reversed(l): |
|
710 | 710 | yield entry |
|
711 | 711 | |
|
712 | 712 | tip = web.repo['tip'] |
|
713 | 713 | count = len(web.repo) |
|
714 | 714 | start = max(0, count - web.maxchanges) |
|
715 | 715 | end = min(count, start + web.maxchanges) |
|
716 | 716 | |
|
717 | 717 | return tmpl("summary", |
|
718 | 718 | desc=web.config("web", "description", "unknown"), |
|
719 | 719 | owner=get_contact(web.config) or "unknown", |
|
720 | 720 | lastchange=tip.date(), |
|
721 | 721 | tags=tagentries, |
|
722 | 722 | bookmarks=bookmarks, |
|
723 | 723 | branches=webutil.branchentries(web.repo, web.stripecount, 10), |
|
724 | 724 | shortlog=changelist, |
|
725 | 725 | node=tip.hex(), |
|
726 | 726 | symrev='tip', |
|
727 | 727 | archives=web.archivelist("tip"), |
|
728 | 728 | labels=web.configlist('web', 'labels')) |
|
729 | 729 | |
|
730 | 730 | @webcommand('filediff') |
|
731 | 731 | def filediff(web, req, tmpl): |
|
732 | 732 | """ |
|
733 | 733 | /diff/{revision}/{path} |
|
734 | 734 | ----------------------- |
|
735 | 735 | |
|
736 | 736 | Show how a file changed in a particular commit. |
|
737 | 737 | |
|
738 | 738 | The ``filediff`` template is rendered. |
|
739 | 739 | |
|
740 | 740 | This handler is registered under both the ``/diff`` and ``/filediff`` |
|
741 | 741 | paths. ``/diff`` is used in modern code. |
|
742 | 742 | """ |
|
743 | 743 | fctx, ctx = None, None |
|
744 | 744 | try: |
|
745 | 745 | fctx = webutil.filectx(web.repo, req) |
|
746 | 746 | except LookupError: |
|
747 | 747 | ctx = webutil.changectx(web.repo, req) |
|
748 | 748 | path = webutil.cleanpath(web.repo, req.form['file'][0]) |
|
749 | 749 | if path not in ctx.files(): |
|
750 | 750 | raise |
|
751 | 751 | |
|
752 | 752 | if fctx is not None: |
|
753 | 753 | path = fctx.path() |
|
754 | 754 | ctx = fctx.changectx() |
|
755 | 755 | |
|
756 | 756 | parity = paritygen(web.stripecount) |
|
757 | 757 | style = web.config('web', 'style', 'paper') |
|
758 | 758 | if 'style' in req.form: |
|
759 | 759 | style = req.form['style'][0] |
|
760 | 760 | |
|
761 | 761 | diffs = webutil.diffs(web.repo, tmpl, ctx, None, [path], parity, style) |
|
762 | 762 | if fctx is not None: |
|
763 | 763 | rename = webutil.renamelink(fctx) |
|
764 | 764 | ctx = fctx |
|
765 | 765 | else: |
|
766 | 766 | rename = [] |
|
767 | 767 | ctx = ctx |
|
768 | 768 | return tmpl("filediff", |
|
769 | 769 | file=path, |
|
770 | 770 | symrev=webutil.symrevorshortnode(req, ctx), |
|
771 | 771 | rename=rename, |
|
772 | 772 | diff=diffs, |
|
773 | 773 | **webutil.commonentry(web.repo, ctx)) |
|
774 | 774 | |
|
775 | 775 | diff = webcommand('diff')(filediff) |
|
776 | 776 | |
|
777 | 777 | @webcommand('comparison') |
|
778 | 778 | def comparison(web, req, tmpl): |
|
779 | 779 | """ |
|
780 | 780 | /comparison/{revision}/{path} |
|
781 | 781 | ----------------------------- |
|
782 | 782 | |
|
783 | 783 | Show a comparison between the old and new versions of a file from changes |
|
784 | 784 | made on a particular revision. |
|
785 | 785 | |
|
786 | 786 | This is similar to the ``diff`` handler. However, this form features |
|
787 | 787 | a split or side-by-side diff rather than a unified diff. |
|
788 | 788 | |
|
789 | 789 | The ``context`` query string argument can be used to control the lines of |
|
790 | 790 | context in the diff. |
|
791 | 791 | |
|
792 | 792 | The ``filecomparison`` template is rendered. |
|
793 | 793 | """ |
|
794 | 794 | ctx = webutil.changectx(web.repo, req) |
|
795 | 795 | if 'file' not in req.form: |
|
796 | 796 | raise ErrorResponse(HTTP_NOT_FOUND, 'file not given') |
|
797 | 797 | path = webutil.cleanpath(web.repo, req.form['file'][0]) |
|
798 | 798 | |
|
799 | 799 | parsecontext = lambda v: v == 'full' and -1 or int(v) |
|
800 | 800 | if 'context' in req.form: |
|
801 | 801 | context = parsecontext(req.form['context'][0]) |
|
802 | 802 | else: |
|
803 | 803 | context = parsecontext(web.config('web', 'comparisoncontext', '5')) |
|
804 | 804 | |
|
805 | 805 | def filelines(f): |
|
806 | 806 | if util.binary(f.data()): |
|
807 | 807 | mt = mimetypes.guess_type(f.path())[0] |
|
808 | 808 | if not mt: |
|
809 | 809 | mt = 'application/octet-stream' |
|
810 | 810 | return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))] |
|
811 | 811 | return f.data().splitlines() |
|
812 | 812 | |
|
813 | 813 | fctx = None |
|
814 | 814 | parent = ctx.p1() |
|
815 | 815 | leftrev = parent.rev() |
|
816 | 816 | leftnode = parent.node() |
|
817 | 817 | rightrev = ctx.rev() |
|
818 | 818 | rightnode = ctx.node() |
|
819 | 819 | if path in ctx: |
|
820 | 820 | fctx = ctx[path] |
|
821 | 821 | rightlines = filelines(fctx) |
|
822 | 822 | if path not in parent: |
|
823 | 823 | leftlines = () |
|
824 | 824 | else: |
|
825 | 825 | pfctx = parent[path] |
|
826 | 826 | leftlines = filelines(pfctx) |
|
827 | 827 | else: |
|
828 | 828 | rightlines = () |
|
829 | 829 | pfctx = ctx.parents()[0][path] |
|
830 | 830 | leftlines = filelines(pfctx) |
|
831 | 831 | |
|
832 | 832 | comparison = webutil.compare(tmpl, context, leftlines, rightlines) |
|
833 | 833 | if fctx is not None: |
|
834 | 834 | rename = webutil.renamelink(fctx) |
|
835 | 835 | ctx = fctx |
|
836 | 836 | else: |
|
837 | 837 | rename = [] |
|
838 | 838 | ctx = ctx |
|
839 | 839 | return tmpl('filecomparison', |
|
840 | 840 | file=path, |
|
841 | 841 | symrev=webutil.symrevorshortnode(req, ctx), |
|
842 | 842 | rename=rename, |
|
843 | 843 | leftrev=leftrev, |
|
844 | 844 | leftnode=hex(leftnode), |
|
845 | 845 | rightrev=rightrev, |
|
846 | 846 | rightnode=hex(rightnode), |
|
847 | 847 | comparison=comparison, |
|
848 | 848 | **webutil.commonentry(web.repo, ctx)) |
|
849 | 849 | |
|
850 | 850 | @webcommand('annotate') |
|
851 | 851 | def annotate(web, req, tmpl): |
|
852 | 852 | """ |
|
853 | 853 | /annotate/{revision}/{path} |
|
854 | 854 | --------------------------- |
|
855 | 855 | |
|
856 | 856 | Show changeset information for each line in a file. |
|
857 | 857 | |
|
858 | 858 | The ``fileannotate`` template is rendered. |
|
859 | 859 | """ |
|
860 | 860 | fctx = webutil.filectx(web.repo, req) |
|
861 | 861 | f = fctx.path() |
|
862 | 862 | parity = paritygen(web.stripecount) |
|
863 | 863 | |
|
864 | 864 | # parents() is called once per line and several lines likely belong to |
|
865 | 865 | # same revision. So it is worth caching. |
|
866 | 866 | # TODO there are still redundant operations within basefilectx.parents() |
|
867 | 867 | # and from the fctx.annotate() call itself that could be cached. |
|
868 | 868 | parentscache = {} |
|
869 | 869 | def parents(f): |
|
870 | 870 | rev = f.rev() |
|
871 | 871 | if rev not in parentscache: |
|
872 | 872 | parentscache[rev] = [] |
|
873 | 873 | for p in f.parents(): |
|
874 | 874 | entry = { |
|
875 | 875 | 'node': p.hex(), |
|
876 | 876 | 'rev': p.rev(), |
|
877 | 877 | } |
|
878 | 878 | parentscache[rev].append(entry) |
|
879 | 879 | |
|
880 | 880 | for p in parentscache[rev]: |
|
881 | 881 | yield p |
|
882 | 882 | |
|
883 | 883 | def annotate(**map): |
|
884 | 884 | if util.binary(fctx.data()): |
|
885 | 885 | mt = (mimetypes.guess_type(fctx.path())[0] |
|
886 | 886 | or 'application/octet-stream') |
|
887 | 887 | lines = [((fctx.filectx(fctx.filerev()), 1), '(binary:%s)' % mt)] |
|
888 | 888 | else: |
|
889 | 889 | lines = webutil.annotate(fctx, web.repo.ui) |
|
890 | 890 | |
|
891 | 891 | previousrev = None |
|
892 | 892 | blockparitygen = paritygen(1) |
|
893 | 893 | for lineno, ((f, targetline), l) in enumerate(lines): |
|
894 | 894 | rev = f.rev() |
|
895 | 895 | if rev != previousrev: |
|
896 | 896 | blockhead = True |
|
897 | 897 | blockparity = next(blockparitygen) |
|
898 | 898 | else: |
|
899 | 899 | blockhead = None |
|
900 | 900 | previousrev = rev |
|
901 | 901 | yield {"parity": next(parity), |
|
902 | 902 | "node": f.hex(), |
|
903 | 903 | "rev": rev, |
|
904 | 904 | "author": f.user(), |
|
905 | 905 | "parents": parents(f), |
|
906 | 906 | "desc": f.description(), |
|
907 | 907 | "extra": f.extra(), |
|
908 | 908 | "file": f.path(), |
|
909 | 909 | "blockhead": blockhead, |
|
910 | 910 | "blockparity": blockparity, |
|
911 | 911 | "targetline": targetline, |
|
912 | 912 | "line": l, |
|
913 | 913 | "lineno": lineno + 1, |
|
914 | 914 | "lineid": "l%d" % (lineno + 1), |
|
915 | 915 | "linenumber": "% 6d" % (lineno + 1), |
|
916 | 916 | "revdate": f.date()} |
|
917 | 917 | |
|
918 | 918 | return tmpl("fileannotate", |
|
919 | 919 | file=f, |
|
920 | 920 | annotate=annotate, |
|
921 | 921 | path=webutil.up(f), |
|
922 | 922 | symrev=webutil.symrevorshortnode(req, fctx), |
|
923 | 923 | rename=webutil.renamelink(fctx), |
|
924 | 924 | permissions=fctx.manifest().flags(f), |
|
925 | 925 | **webutil.commonentry(web.repo, fctx)) |
|
926 | 926 | |
|
927 | 927 | @webcommand('filelog') |
|
928 | 928 | def filelog(web, req, tmpl): |
|
929 | 929 | """ |
|
930 | 930 | /filelog/{revision}/{path} |
|
931 | 931 | -------------------------- |
|
932 | 932 | |
|
933 | 933 | Show information about the history of a file in the repository. |
|
934 | 934 | |
|
935 | 935 | The ``revcount`` query string argument can be defined to control the |
|
936 | 936 | maximum number of entries to show. |
|
937 | 937 | |
|
938 | 938 | The ``filelog`` template will be rendered. |
|
939 | 939 | """ |
|
940 | 940 | |
|
941 | 941 | try: |
|
942 | 942 | fctx = webutil.filectx(web.repo, req) |
|
943 | 943 | f = fctx.path() |
|
944 | 944 | fl = fctx.filelog() |
|
945 | 945 | except error.LookupError: |
|
946 | 946 | f = webutil.cleanpath(web.repo, req.form['file'][0]) |
|
947 | 947 | fl = web.repo.file(f) |
|
948 | 948 | numrevs = len(fl) |
|
949 | 949 | if not numrevs: # file doesn't exist at all |
|
950 | 950 | raise |
|
951 | 951 | rev = webutil.changectx(web.repo, req).rev() |
|
952 | 952 | first = fl.linkrev(0) |
|
953 | 953 | if rev < first: # current rev is from before file existed |
|
954 | 954 | raise |
|
955 | 955 | frev = numrevs - 1 |
|
956 | 956 | while fl.linkrev(frev) > rev: |
|
957 | 957 | frev -= 1 |
|
958 | 958 | fctx = web.repo.filectx(f, fl.linkrev(frev)) |
|
959 | 959 | |
|
960 | 960 | revcount = web.maxshortchanges |
|
961 | 961 | if 'revcount' in req.form: |
|
962 | 962 | try: |
|
963 | 963 | revcount = int(req.form.get('revcount', [revcount])[0]) |
|
964 | 964 | revcount = max(revcount, 1) |
|
965 | 965 | tmpl.defaults['sessionvars']['revcount'] = revcount |
|
966 | 966 | except ValueError: |
|
967 | 967 | pass |
|
968 | 968 | |
|
969 | 969 | lessvars = copy.copy(tmpl.defaults['sessionvars']) |
|
970 | 970 | lessvars['revcount'] = max(revcount / 2, 1) |
|
971 | 971 | morevars = copy.copy(tmpl.defaults['sessionvars']) |
|
972 | 972 | morevars['revcount'] = revcount * 2 |
|
973 | 973 | |
|
974 | 974 | count = fctx.filerev() + 1 |
|
975 | 975 | start = max(0, fctx.filerev() - revcount + 1) # first rev on this page |
|
976 | 976 | end = min(count, start + revcount) # last rev on this page |
|
977 | 977 | parity = paritygen(web.stripecount, offset=start - end) |
|
978 | 978 | |
|
979 | 979 | def entries(): |
|
980 | 980 | l = [] |
|
981 | 981 | |
|
982 | 982 | repo = web.repo |
|
983 | 983 | revs = fctx.filelog().revs(start, end - 1) |
|
984 | 984 | for i in revs: |
|
985 | 985 | iterfctx = fctx.filectx(i) |
|
986 | 986 | |
|
987 | 987 | l.append(dict( |
|
988 | 988 | parity=next(parity), |
|
989 | 989 | filerev=i, |
|
990 | 990 | file=f, |
|
991 | 991 | rename=webutil.renamelink(iterfctx), |
|
992 | 992 | **webutil.commonentry(repo, iterfctx))) |
|
993 | 993 | for e in reversed(l): |
|
994 | 994 | yield e |
|
995 | 995 | |
|
996 | 996 | entries = list(entries()) |
|
997 | 997 | latestentry = entries[:1] |
|
998 | 998 | |
|
999 | 999 | revnav = webutil.filerevnav(web.repo, fctx.path()) |
|
1000 | 1000 | nav = revnav.gen(end - 1, revcount, count) |
|
1001 | 1001 | return tmpl("filelog", |
|
1002 | 1002 | file=f, |
|
1003 | 1003 | nav=nav, |
|
1004 | 1004 | symrev=webutil.symrevorshortnode(req, fctx), |
|
1005 | 1005 | entries=entries, |
|
1006 | 1006 | latestentry=latestentry, |
|
1007 | 1007 | revcount=revcount, |
|
1008 | 1008 | morevars=morevars, |
|
1009 | 1009 | lessvars=lessvars, |
|
1010 | 1010 | **webutil.commonentry(web.repo, fctx)) |
|
1011 | 1011 | |
|
1012 | 1012 | @webcommand('archive') |
|
1013 | 1013 | def archive(web, req, tmpl): |
|
1014 | 1014 | """ |
|
1015 | 1015 | /archive/{revision}.{format}[/{path}] |
|
1016 | 1016 | ------------------------------------- |
|
1017 | 1017 | |
|
1018 | 1018 | Obtain an archive of repository content. |
|
1019 | 1019 | |
|
1020 | 1020 | The content and type of the archive is defined by a URL path parameter. |
|
1021 | 1021 | ``format`` is the file extension of the archive type to be generated. e.g. |
|
1022 | 1022 | ``zip`` or ``tar.bz2``. Not all archive types may be allowed by your |
|
1023 | 1023 | server configuration. |
|
1024 | 1024 | |
|
1025 | 1025 | The optional ``path`` URL parameter controls content to include in the |
|
1026 | 1026 | archive. If omitted, every file in the specified revision is present in the |
|
1027 | 1027 | archive. If included, only the specified file or contents of the specified |
|
1028 | 1028 | directory will be included in the archive. |
|
1029 | 1029 | |
|
1030 | 1030 | No template is used for this handler. Raw, binary content is generated. |
|
1031 | 1031 | """ |
|
1032 | 1032 | |
|
1033 | 1033 | type_ = req.form.get('type', [None])[0] |
|
1034 | 1034 | allowed = web.configlist("web", "allow_archive") |
|
1035 | 1035 | key = req.form['node'][0] |
|
1036 | 1036 | |
|
1037 | 1037 | if type_ not in web.archives: |
|
1038 | 1038 | msg = 'Unsupported archive type: %s' % type_ |
|
1039 | 1039 | raise ErrorResponse(HTTP_NOT_FOUND, msg) |
|
1040 | 1040 | |
|
1041 | 1041 | if not ((type_ in allowed or |
|
1042 | 1042 | web.configbool("web", "allow" + type_, False))): |
|
1043 | 1043 | msg = 'Archive type not allowed: %s' % type_ |
|
1044 | 1044 | raise ErrorResponse(HTTP_FORBIDDEN, msg) |
|
1045 | 1045 | |
|
1046 | 1046 | reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame)) |
|
1047 | 1047 | cnode = web.repo.lookup(key) |
|
1048 | 1048 | arch_version = key |
|
1049 | 1049 | if cnode == key or key == 'tip': |
|
1050 | 1050 | arch_version = short(cnode) |
|
1051 | 1051 | name = "%s-%s" % (reponame, arch_version) |
|
1052 | 1052 | |
|
1053 | 1053 | ctx = webutil.changectx(web.repo, req) |
|
1054 | 1054 | pats = [] |
|
1055 | 1055 | matchfn = scmutil.match(ctx, []) |
|
1056 | 1056 | file = req.form.get('file', None) |
|
1057 | 1057 | if file: |
|
1058 | 1058 | pats = ['path:' + file[0]] |
|
1059 | 1059 | matchfn = scmutil.match(ctx, pats, default='path') |
|
1060 | 1060 | if pats: |
|
1061 | 1061 | files = [f for f in ctx.manifest().keys() if matchfn(f)] |
|
1062 | 1062 | if not files: |
|
1063 | 1063 | raise ErrorResponse(HTTP_NOT_FOUND, |
|
1064 | 1064 | 'file(s) not found: %s' % file[0]) |
|
1065 | 1065 | |
|
1066 | 1066 | mimetype, artype, extension, encoding = web.archivespecs[type_] |
|
1067 | 1067 | headers = [ |
|
1068 | 1068 | ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension)) |
|
1069 | 1069 | ] |
|
1070 | 1070 | if encoding: |
|
1071 | 1071 | headers.append(('Content-Encoding', encoding)) |
|
1072 | 1072 | req.headers.extend(headers) |
|
1073 | 1073 | req.respond(HTTP_OK, mimetype) |
|
1074 | 1074 | |
|
1075 | 1075 | archival.archive(web.repo, req, cnode, artype, prefix=name, |
|
1076 | 1076 | matchfn=matchfn, |
|
1077 | 1077 | subrepos=web.configbool("web", "archivesubrepos")) |
|
1078 | 1078 | return [] |
|
1079 | 1079 | |
|
1080 | 1080 | |
|
1081 | 1081 | @webcommand('static') |
|
1082 | 1082 | def static(web, req, tmpl): |
|
1083 | 1083 | fname = req.form['file'][0] |
|
1084 | 1084 | # a repo owner may set web.static in .hg/hgrc to get any file |
|
1085 | 1085 | # readable by the user running the CGI script |
|
1086 | 1086 | static = web.config("web", "static", None, untrusted=False) |
|
1087 | 1087 | if not static: |
|
1088 | 1088 | tp = web.templatepath or templater.templatepaths() |
|
1089 | 1089 | if isinstance(tp, str): |
|
1090 | 1090 | tp = [tp] |
|
1091 | 1091 | static = [os.path.join(p, 'static') for p in tp] |
|
1092 | 1092 | staticfile(static, fname, req) |
|
1093 | 1093 | return [] |
|
1094 | 1094 | |
|
1095 | 1095 | @webcommand('graph') |
|
1096 | 1096 | def graph(web, req, tmpl): |
|
1097 | 1097 | """ |
|
1098 | 1098 | /graph[/{revision}] |
|
1099 | 1099 | ------------------- |
|
1100 | 1100 | |
|
1101 | 1101 | Show information about the graphical topology of the repository. |
|
1102 | 1102 | |
|
1103 | 1103 | Information rendered by this handler can be used to create visual |
|
1104 | 1104 | representations of repository topology. |
|
1105 | 1105 | |
|
1106 | 1106 | The ``revision`` URL parameter controls the starting changeset. |
|
1107 | 1107 | |
|
1108 | 1108 | The ``revcount`` query string argument can define the number of changesets |
|
1109 | 1109 | to show information for. |
|
1110 | 1110 | |
|
1111 | 1111 | This handler will render the ``graph`` template. |
|
1112 | 1112 | """ |
|
1113 | 1113 | |
|
1114 | 1114 | if 'node' in req.form: |
|
1115 | 1115 | ctx = webutil.changectx(web.repo, req) |
|
1116 | 1116 | symrev = webutil.symrevorshortnode(req, ctx) |
|
1117 | 1117 | else: |
|
1118 | 1118 | ctx = web.repo['tip'] |
|
1119 | 1119 | symrev = 'tip' |
|
1120 | 1120 | rev = ctx.rev() |
|
1121 | 1121 | |
|
1122 | 1122 | bg_height = 39 |
|
1123 | 1123 | revcount = web.maxshortchanges |
|
1124 | 1124 | if 'revcount' in req.form: |
|
1125 | 1125 | try: |
|
1126 | 1126 | revcount = int(req.form.get('revcount', [revcount])[0]) |
|
1127 | 1127 | revcount = max(revcount, 1) |
|
1128 | 1128 | tmpl.defaults['sessionvars']['revcount'] = revcount |
|
1129 | 1129 | except ValueError: |
|
1130 | 1130 | pass |
|
1131 | 1131 | |
|
1132 | 1132 | lessvars = copy.copy(tmpl.defaults['sessionvars']) |
|
1133 | 1133 | lessvars['revcount'] = max(revcount / 2, 1) |
|
1134 | 1134 | morevars = copy.copy(tmpl.defaults['sessionvars']) |
|
1135 | 1135 | morevars['revcount'] = revcount * 2 |
|
1136 | 1136 | |
|
1137 | 1137 | count = len(web.repo) |
|
1138 | 1138 | pos = rev |
|
1139 | 1139 | |
|
1140 | 1140 | uprev = min(max(0, count - 1), rev + revcount) |
|
1141 | 1141 | downrev = max(0, rev - revcount) |
|
1142 | 1142 | changenav = webutil.revnav(web.repo).gen(pos, revcount, count) |
|
1143 | 1143 | |
|
1144 | 1144 | tree = [] |
|
1145 | 1145 | if pos != -1: |
|
1146 | 1146 | allrevs = web.repo.changelog.revs(pos, 0) |
|
1147 | 1147 | revs = [] |
|
1148 | 1148 | for i in allrevs: |
|
1149 | 1149 | revs.append(i) |
|
1150 | 1150 | if len(revs) >= revcount: |
|
1151 | 1151 | break |
|
1152 | 1152 | |
|
1153 | 1153 | # We have to feed a baseset to dagwalker as it is expecting smartset |
|
1154 | 1154 | # object. This does not have a big impact on hgweb performance itself |
|
1155 | 1155 | # since hgweb graphing code is not itself lazy yet. |
|
1156 | 1156 | dag = graphmod.dagwalker(web.repo, revset.baseset(revs)) |
|
1157 | 1157 | # As we said one line above... not lazy. |
|
1158 | 1158 | tree = list(graphmod.colored(dag, web.repo)) |
|
1159 | 1159 | |
|
1160 | 1160 | def getcolumns(tree): |
|
1161 | 1161 | cols = 0 |
|
1162 | 1162 | for (id, type, ctx, vtx, edges) in tree: |
|
1163 | 1163 | if type != graphmod.CHANGESET: |
|
1164 | 1164 | continue |
|
1165 | 1165 | cols = max(cols, max([edge[0] for edge in edges] or [0]), |
|
1166 | 1166 | max([edge[1] for edge in edges] or [0])) |
|
1167 | 1167 | return cols |
|
1168 | 1168 | |
|
1169 | 1169 | def graphdata(usetuples, encodestr): |
|
1170 | 1170 | data = [] |
|
1171 | 1171 | |
|
1172 | 1172 | row = 0 |
|
1173 | 1173 | for (id, type, ctx, vtx, edges) in tree: |
|
1174 | 1174 | if type != graphmod.CHANGESET: |
|
1175 | 1175 | continue |
|
1176 | 1176 | node = str(ctx) |
|
1177 | 1177 | age = encodestr(templatefilters.age(ctx.date())) |
|
1178 | 1178 | desc = templatefilters.firstline(encodestr(ctx.description())) |
|
1179 | 1179 | desc = cgi.escape(templatefilters.nonempty(desc)) |
|
1180 | 1180 | user = cgi.escape(templatefilters.person(encodestr(ctx.user()))) |
|
1181 | 1181 | branch = cgi.escape(encodestr(ctx.branch())) |
|
1182 | 1182 | try: |
|
1183 | 1183 | branchnode = web.repo.branchtip(branch) |
|
1184 | 1184 | except error.RepoLookupError: |
|
1185 | 1185 | branchnode = None |
|
1186 | 1186 | branch = branch, branchnode == ctx.node() |
|
1187 | 1187 | |
|
1188 | 1188 | if usetuples: |
|
1189 | 1189 | data.append((node, vtx, edges, desc, user, age, branch, |
|
1190 | 1190 | [cgi.escape(encodestr(x)) for x in ctx.tags()], |
|
1191 | 1191 | [cgi.escape(encodestr(x)) |
|
1192 | 1192 | for x in ctx.bookmarks()])) |
|
1193 | 1193 | else: |
|
1194 | 1194 | edgedata = [{'col': edge[0], 'nextcol': edge[1], |
|
1195 | 1195 | 'color': (edge[2] - 1) % 6 + 1, |
|
1196 | 1196 | 'width': edge[3], 'bcolor': edge[4]} |
|
1197 | 1197 | for edge in edges] |
|
1198 | 1198 | |
|
1199 | 1199 | data.append( |
|
1200 | 1200 | {'node': node, |
|
1201 | 1201 | 'col': vtx[0], |
|
1202 | 1202 | 'color': (vtx[1] - 1) % 6 + 1, |
|
1203 | 1203 | 'edges': edgedata, |
|
1204 | 1204 | 'row': row, |
|
1205 | 1205 | 'nextrow': row + 1, |
|
1206 | 1206 | 'desc': desc, |
|
1207 | 1207 | 'user': user, |
|
1208 | 1208 | 'age': age, |
|
1209 | 1209 | 'bookmarks': webutil.nodebookmarksdict( |
|
1210 | 1210 | web.repo, ctx.node()), |
|
1211 | 1211 | 'branches': webutil.nodebranchdict(web.repo, ctx), |
|
1212 | 1212 | 'inbranch': webutil.nodeinbranch(web.repo, ctx), |
|
1213 | 1213 | 'tags': webutil.nodetagsdict(web.repo, ctx.node())}) |
|
1214 | 1214 | |
|
1215 | 1215 | row += 1 |
|
1216 | 1216 | |
|
1217 | 1217 | return data |
|
1218 | 1218 | |
|
1219 | 1219 | cols = getcolumns(tree) |
|
1220 | 1220 | rows = len(tree) |
|
1221 | 1221 | canvasheight = (rows + 1) * bg_height - 27 |
|
1222 | 1222 | |
|
1223 | 1223 | return tmpl('graph', rev=rev, symrev=symrev, revcount=revcount, |
|
1224 | 1224 | uprev=uprev, |
|
1225 | 1225 | lessvars=lessvars, morevars=morevars, downrev=downrev, |
|
1226 | 1226 | cols=cols, rows=rows, |
|
1227 | 1227 | canvaswidth=(cols + 1) * bg_height, |
|
1228 | 1228 | truecanvasheight=rows * bg_height, |
|
1229 | 1229 | canvasheight=canvasheight, bg_height=bg_height, |
|
1230 | 1230 | # {jsdata} will be passed to |json, so it must be in utf-8 |
|
1231 | 1231 | jsdata=lambda **x: graphdata(True, encoding.fromlocal), |
|
1232 | 1232 | nodes=lambda **x: graphdata(False, str), |
|
1233 | 1233 | node=ctx.hex(), changenav=changenav) |
|
1234 | 1234 | |
|
1235 | 1235 | def _getdoc(e): |
|
1236 | 1236 | doc = e[0].__doc__ |
|
1237 | 1237 | if doc: |
|
1238 | 1238 | doc = _(doc).partition('\n')[0] |
|
1239 | 1239 | else: |
|
1240 | 1240 | doc = _('(no help text available)') |
|
1241 | 1241 | return doc |
|
1242 | 1242 | |
|
1243 | 1243 | @webcommand('help') |
|
1244 | 1244 | def help(web, req, tmpl): |
|
1245 | 1245 | """ |
|
1246 | 1246 | /help[/{topic}] |
|
1247 | 1247 | --------------- |
|
1248 | 1248 | |
|
1249 | 1249 | Render help documentation. |
|
1250 | 1250 | |
|
1251 | 1251 | This web command is roughly equivalent to :hg:`help`. If a ``topic`` |
|
1252 | 1252 | is defined, that help topic will be rendered. If not, an index of |
|
1253 | 1253 | available help topics will be rendered. |
|
1254 | 1254 | |
|
1255 | 1255 | The ``help`` template will be rendered when requesting help for a topic. |
|
1256 | 1256 | ``helptopics`` will be rendered for the index of help topics. |
|
1257 | 1257 | """ |
|
1258 | 1258 | from .. import commands, help as helpmod # avoid cycle |
|
1259 | 1259 | |
|
1260 | 1260 | topicname = req.form.get('node', [None])[0] |
|
1261 | 1261 | if not topicname: |
|
1262 | 1262 | def topics(**map): |
|
1263 | 1263 | for entries, summary, _doc in helpmod.helptable: |
|
1264 | 1264 | yield {'topic': entries[0], 'summary': summary} |
|
1265 | 1265 | |
|
1266 | 1266 | early, other = [], [] |
|
1267 | 1267 | primary = lambda s: s.partition('|')[0] |
|
1268 | 1268 | for c, e in commands.table.iteritems(): |
|
1269 | 1269 | doc = _getdoc(e) |
|
1270 | 1270 | if 'DEPRECATED' in doc or c.startswith('debug'): |
|
1271 | 1271 | continue |
|
1272 | 1272 | cmd = primary(c) |
|
1273 | 1273 | if cmd.startswith('^'): |
|
1274 | 1274 | early.append((cmd[1:], doc)) |
|
1275 | 1275 | else: |
|
1276 | 1276 | other.append((cmd, doc)) |
|
1277 | 1277 | |
|
1278 | 1278 | early.sort() |
|
1279 | 1279 | other.sort() |
|
1280 | 1280 | |
|
1281 | 1281 | def earlycommands(**map): |
|
1282 | 1282 | for c, doc in early: |
|
1283 | 1283 | yield {'topic': c, 'summary': doc} |
|
1284 | 1284 | |
|
1285 | 1285 | def othercommands(**map): |
|
1286 | 1286 | for c, doc in other: |
|
1287 | 1287 | yield {'topic': c, 'summary': doc} |
|
1288 | 1288 | |
|
1289 | 1289 | return tmpl('helptopics', topics=topics, earlycommands=earlycommands, |
|
1290 | 1290 | othercommands=othercommands, title='Index') |
|
1291 | 1291 | |
|
1292 | 1292 | # Render an index of sub-topics. |
|
1293 | 1293 | if topicname in helpmod.subtopics: |
|
1294 | 1294 | topics = [] |
|
1295 | 1295 | for entries, summary, _doc in helpmod.subtopics[topicname]: |
|
1296 | 1296 | topics.append({ |
|
1297 | 1297 | 'topic': '%s.%s' % (topicname, entries[0]), |
|
1298 | 1298 | 'basename': entries[0], |
|
1299 | 1299 | 'summary': summary, |
|
1300 | 1300 | }) |
|
1301 | 1301 | |
|
1302 | 1302 | return tmpl('helptopics', topics=topics, title=topicname, |
|
1303 | 1303 | subindex=True) |
|
1304 | 1304 | |
|
1305 | u = webutil.wsgiui() | |
|
1305 | u = webutil.wsgiui.load() | |
|
1306 | 1306 | u.verbose = True |
|
1307 | 1307 | |
|
1308 | 1308 | # Render a page from a sub-topic. |
|
1309 | 1309 | if '.' in topicname: |
|
1310 | 1310 | # TODO implement support for rendering sections, like |
|
1311 | 1311 | # `hg help` works. |
|
1312 | 1312 | topic, subtopic = topicname.split('.', 1) |
|
1313 | 1313 | if topic not in helpmod.subtopics: |
|
1314 | 1314 | raise ErrorResponse(HTTP_NOT_FOUND) |
|
1315 | 1315 | else: |
|
1316 | 1316 | topic = topicname |
|
1317 | 1317 | subtopic = None |
|
1318 | 1318 | |
|
1319 | 1319 | try: |
|
1320 | 1320 | doc = helpmod.help_(u, topic, subtopic=subtopic) |
|
1321 | 1321 | except error.UnknownCommand: |
|
1322 | 1322 | raise ErrorResponse(HTTP_NOT_FOUND) |
|
1323 | 1323 | return tmpl('help', topic=topicname, doc=doc) |
|
1324 | 1324 | |
|
1325 | 1325 | # tell hggettext to extract docstrings from these functions: |
|
1326 | 1326 | i18nfunctions = commands.values() |
@@ -1,1394 +1,1406 | |||
|
1 | 1 | # ui.py - user interface bits for mercurial |
|
2 | 2 | # |
|
3 | 3 | # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> |
|
4 | 4 | # |
|
5 | 5 | # This software may be used and distributed according to the terms of the |
|
6 | 6 | # GNU General Public License version 2 or any later version. |
|
7 | 7 | |
|
8 | 8 | from __future__ import absolute_import |
|
9 | 9 | |
|
10 | 10 | import contextlib |
|
11 | 11 | import errno |
|
12 | 12 | import getpass |
|
13 | 13 | import inspect |
|
14 | 14 | import os |
|
15 | 15 | import re |
|
16 | 16 | import socket |
|
17 | 17 | import sys |
|
18 | 18 | import tempfile |
|
19 | 19 | import traceback |
|
20 | 20 | |
|
21 | 21 | from .i18n import _ |
|
22 | 22 | from .node import hex |
|
23 | 23 | |
|
24 | 24 | from . import ( |
|
25 | 25 | config, |
|
26 | 26 | encoding, |
|
27 | 27 | error, |
|
28 | 28 | formatter, |
|
29 | 29 | progress, |
|
30 | 30 | pycompat, |
|
31 | 31 | scmutil, |
|
32 | 32 | util, |
|
33 | 33 | ) |
|
34 | 34 | |
|
35 | 35 | urlreq = util.urlreq |
|
36 | 36 | |
|
37 | 37 | samplehgrcs = { |
|
38 | 38 | 'user': |
|
39 | 39 | """# example user config (see 'hg help config' for more info) |
|
40 | 40 | [ui] |
|
41 | 41 | # name and email, e.g. |
|
42 | 42 | # username = Jane Doe <jdoe@example.com> |
|
43 | 43 | username = |
|
44 | 44 | |
|
45 | 45 | [extensions] |
|
46 | 46 | # uncomment these lines to enable some popular extensions |
|
47 | 47 | # (see 'hg help extensions' for more info) |
|
48 | 48 | # |
|
49 | 49 | # pager = |
|
50 | 50 | # color =""", |
|
51 | 51 | |
|
52 | 52 | 'cloned': |
|
53 | 53 | """# example repository config (see 'hg help config' for more info) |
|
54 | 54 | [paths] |
|
55 | 55 | default = %s |
|
56 | 56 | |
|
57 | 57 | # path aliases to other clones of this repo in URLs or filesystem paths |
|
58 | 58 | # (see 'hg help config.paths' for more info) |
|
59 | 59 | # |
|
60 | 60 | # default-push = ssh://jdoe@example.net/hg/jdoes-fork |
|
61 | 61 | # my-fork = ssh://jdoe@example.net/hg/jdoes-fork |
|
62 | 62 | # my-clone = /home/jdoe/jdoes-clone |
|
63 | 63 | |
|
64 | 64 | [ui] |
|
65 | 65 | # name and email (local to this repository, optional), e.g. |
|
66 | 66 | # username = Jane Doe <jdoe@example.com> |
|
67 | 67 | """, |
|
68 | 68 | |
|
69 | 69 | 'local': |
|
70 | 70 | """# example repository config (see 'hg help config' for more info) |
|
71 | 71 | [paths] |
|
72 | 72 | # path aliases to other clones of this repo in URLs or filesystem paths |
|
73 | 73 | # (see 'hg help config.paths' for more info) |
|
74 | 74 | # |
|
75 | 75 | # default = http://example.com/hg/example-repo |
|
76 | 76 | # default-push = ssh://jdoe@example.net/hg/jdoes-fork |
|
77 | 77 | # my-fork = ssh://jdoe@example.net/hg/jdoes-fork |
|
78 | 78 | # my-clone = /home/jdoe/jdoes-clone |
|
79 | 79 | |
|
80 | 80 | [ui] |
|
81 | 81 | # name and email (local to this repository, optional), e.g. |
|
82 | 82 | # username = Jane Doe <jdoe@example.com> |
|
83 | 83 | """, |
|
84 | 84 | |
|
85 | 85 | 'global': |
|
86 | 86 | """# example system-wide hg config (see 'hg help config' for more info) |
|
87 | 87 | |
|
88 | 88 | [extensions] |
|
89 | 89 | # uncomment these lines to enable some popular extensions |
|
90 | 90 | # (see 'hg help extensions' for more info) |
|
91 | 91 | # |
|
92 | 92 | # blackbox = |
|
93 | 93 | # color = |
|
94 | 94 | # pager =""", |
|
95 | 95 | } |
|
96 | 96 | |
|
97 | 97 | class ui(object): |
|
98 | 98 | def __init__(self, src=None): |
|
99 | """Create a fresh new ui object if no src given | |
|
100 | ||
|
101 | Use uimod.ui.load() to create a ui which knows global and user configs. | |
|
102 | In most cases, you should use ui.copy() to create a copy of an existing | |
|
103 | ui object. | |
|
104 | """ | |
|
99 | 105 | # _buffers: used for temporary capture of output |
|
100 | 106 | self._buffers = [] |
|
101 | 107 | # 3-tuple describing how each buffer in the stack behaves. |
|
102 | 108 | # Values are (capture stderr, capture subprocesses, apply labels). |
|
103 | 109 | self._bufferstates = [] |
|
104 | 110 | # When a buffer is active, defines whether we are expanding labels. |
|
105 | 111 | # This exists to prevent an extra list lookup. |
|
106 | 112 | self._bufferapplylabels = None |
|
107 | 113 | self.quiet = self.verbose = self.debugflag = self.tracebackflag = False |
|
108 | 114 | self._reportuntrusted = True |
|
109 | 115 | self._ocfg = config.config() # overlay |
|
110 | 116 | self._tcfg = config.config() # trusted |
|
111 | 117 | self._ucfg = config.config() # untrusted |
|
112 | 118 | self._trustusers = set() |
|
113 | 119 | self._trustgroups = set() |
|
114 | 120 | self.callhooks = True |
|
115 | 121 | # Insecure server connections requested. |
|
116 | 122 | self.insecureconnections = False |
|
117 | 123 | |
|
118 | 124 | if src: |
|
119 | 125 | self.fout = src.fout |
|
120 | 126 | self.ferr = src.ferr |
|
121 | 127 | self.fin = src.fin |
|
122 | 128 | |
|
123 | 129 | self._tcfg = src._tcfg.copy() |
|
124 | 130 | self._ucfg = src._ucfg.copy() |
|
125 | 131 | self._ocfg = src._ocfg.copy() |
|
126 | 132 | self._trustusers = src._trustusers.copy() |
|
127 | 133 | self._trustgroups = src._trustgroups.copy() |
|
128 | 134 | self.environ = src.environ |
|
129 | 135 | self.callhooks = src.callhooks |
|
130 | 136 | self.insecureconnections = src.insecureconnections |
|
131 | 137 | self.fixconfig() |
|
132 | 138 | |
|
133 | 139 | self.httppasswordmgrdb = src.httppasswordmgrdb |
|
134 | 140 | else: |
|
135 | 141 | self.fout = util.stdout |
|
136 | 142 | self.ferr = util.stderr |
|
137 | 143 | self.fin = util.stdin |
|
138 | 144 | |
|
139 | 145 | # shared read-only environment |
|
140 | 146 | self.environ = os.environ |
|
147 | ||
|
148 | self.httppasswordmgrdb = urlreq.httppasswordmgrwithdefaultrealm() | |
|
149 | ||
|
150 | @classmethod | |
|
151 | def load(cls): | |
|
152 | """Create a ui and load global and user configs""" | |
|
153 | u = cls() | |
|
141 | 154 |
|
|
142 | 155 |
|
|
143 |
|
|
|
144 | ||
|
145 | self.httppasswordmgrdb = urlreq.httppasswordmgrwithdefaultrealm() | |
|
156 | u.readconfig(f, trust=True) | |
|
157 | return u | |
|
146 | 158 | |
|
147 | 159 | def copy(self): |
|
148 | 160 | return self.__class__(self) |
|
149 | 161 | |
|
150 | 162 | def resetstate(self): |
|
151 | 163 | """Clear internal state that shouldn't persist across commands""" |
|
152 | 164 | if self._progbar: |
|
153 | 165 | self._progbar.resetstate() # reset last-print time of progress bar |
|
154 | 166 | self.httppasswordmgrdb = urlreq.httppasswordmgrwithdefaultrealm() |
|
155 | 167 | |
|
156 | 168 | def formatter(self, topic, opts): |
|
157 | 169 | return formatter.formatter(self, topic, opts) |
|
158 | 170 | |
|
159 | 171 | def _trusted(self, fp, f): |
|
160 | 172 | st = util.fstat(fp) |
|
161 | 173 | if util.isowner(st): |
|
162 | 174 | return True |
|
163 | 175 | |
|
164 | 176 | tusers, tgroups = self._trustusers, self._trustgroups |
|
165 | 177 | if '*' in tusers or '*' in tgroups: |
|
166 | 178 | return True |
|
167 | 179 | |
|
168 | 180 | user = util.username(st.st_uid) |
|
169 | 181 | group = util.groupname(st.st_gid) |
|
170 | 182 | if user in tusers or group in tgroups or user == util.username(): |
|
171 | 183 | return True |
|
172 | 184 | |
|
173 | 185 | if self._reportuntrusted: |
|
174 | 186 | self.warn(_('not trusting file %s from untrusted ' |
|
175 | 187 | 'user %s, group %s\n') % (f, user, group)) |
|
176 | 188 | return False |
|
177 | 189 | |
|
178 | 190 | def readconfig(self, filename, root=None, trust=False, |
|
179 | 191 | sections=None, remap=None): |
|
180 | 192 | try: |
|
181 | 193 | fp = open(filename, u'rb') |
|
182 | 194 | except IOError: |
|
183 | 195 | if not sections: # ignore unless we were looking for something |
|
184 | 196 | return |
|
185 | 197 | raise |
|
186 | 198 | |
|
187 | 199 | cfg = config.config() |
|
188 | 200 | trusted = sections or trust or self._trusted(fp, filename) |
|
189 | 201 | |
|
190 | 202 | try: |
|
191 | 203 | cfg.read(filename, fp, sections=sections, remap=remap) |
|
192 | 204 | fp.close() |
|
193 | 205 | except error.ConfigError as inst: |
|
194 | 206 | if trusted: |
|
195 | 207 | raise |
|
196 | 208 | self.warn(_("ignored: %s\n") % str(inst)) |
|
197 | 209 | |
|
198 | 210 | if self.plain(): |
|
199 | 211 | for k in ('debug', 'fallbackencoding', 'quiet', 'slash', |
|
200 | 212 | 'logtemplate', 'statuscopies', 'style', |
|
201 | 213 | 'traceback', 'verbose'): |
|
202 | 214 | if k in cfg['ui']: |
|
203 | 215 | del cfg['ui'][k] |
|
204 | 216 | for k, v in cfg.items('defaults'): |
|
205 | 217 | del cfg['defaults'][k] |
|
206 | 218 | # Don't remove aliases from the configuration if in the exceptionlist |
|
207 | 219 | if self.plain('alias'): |
|
208 | 220 | for k, v in cfg.items('alias'): |
|
209 | 221 | del cfg['alias'][k] |
|
210 | 222 | if self.plain('revsetalias'): |
|
211 | 223 | for k, v in cfg.items('revsetalias'): |
|
212 | 224 | del cfg['revsetalias'][k] |
|
213 | 225 | if self.plain('templatealias'): |
|
214 | 226 | for k, v in cfg.items('templatealias'): |
|
215 | 227 | del cfg['templatealias'][k] |
|
216 | 228 | |
|
217 | 229 | if trusted: |
|
218 | 230 | self._tcfg.update(cfg) |
|
219 | 231 | self._tcfg.update(self._ocfg) |
|
220 | 232 | self._ucfg.update(cfg) |
|
221 | 233 | self._ucfg.update(self._ocfg) |
|
222 | 234 | |
|
223 | 235 | if root is None: |
|
224 | 236 | root = os.path.expanduser('~') |
|
225 | 237 | self.fixconfig(root=root) |
|
226 | 238 | |
|
227 | 239 | def fixconfig(self, root=None, section=None): |
|
228 | 240 | if section in (None, 'paths'): |
|
229 | 241 | # expand vars and ~ |
|
230 | 242 | # translate paths relative to root (or home) into absolute paths |
|
231 | 243 | root = root or pycompat.getcwd() |
|
232 | 244 | for c in self._tcfg, self._ucfg, self._ocfg: |
|
233 | 245 | for n, p in c.items('paths'): |
|
234 | 246 | # Ignore sub-options. |
|
235 | 247 | if ':' in n: |
|
236 | 248 | continue |
|
237 | 249 | if not p: |
|
238 | 250 | continue |
|
239 | 251 | if '%%' in p: |
|
240 | 252 | self.warn(_("(deprecated '%%' in path %s=%s from %s)\n") |
|
241 | 253 | % (n, p, self.configsource('paths', n))) |
|
242 | 254 | p = p.replace('%%', '%') |
|
243 | 255 | p = util.expandpath(p) |
|
244 | 256 | if not util.hasscheme(p) and not os.path.isabs(p): |
|
245 | 257 | p = os.path.normpath(os.path.join(root, p)) |
|
246 | 258 | c.set("paths", n, p) |
|
247 | 259 | |
|
248 | 260 | if section in (None, 'ui'): |
|
249 | 261 | # update ui options |
|
250 | 262 | self.debugflag = self.configbool('ui', 'debug') |
|
251 | 263 | self.verbose = self.debugflag or self.configbool('ui', 'verbose') |
|
252 | 264 | self.quiet = not self.debugflag and self.configbool('ui', 'quiet') |
|
253 | 265 | if self.verbose and self.quiet: |
|
254 | 266 | self.quiet = self.verbose = False |
|
255 | 267 | self._reportuntrusted = self.debugflag or self.configbool("ui", |
|
256 | 268 | "report_untrusted", True) |
|
257 | 269 | self.tracebackflag = self.configbool('ui', 'traceback', False) |
|
258 | 270 | |
|
259 | 271 | if section in (None, 'trusted'): |
|
260 | 272 | # update trust information |
|
261 | 273 | self._trustusers.update(self.configlist('trusted', 'users')) |
|
262 | 274 | self._trustgroups.update(self.configlist('trusted', 'groups')) |
|
263 | 275 | |
|
264 | 276 | def backupconfig(self, section, item): |
|
265 | 277 | return (self._ocfg.backup(section, item), |
|
266 | 278 | self._tcfg.backup(section, item), |
|
267 | 279 | self._ucfg.backup(section, item),) |
|
268 | 280 | def restoreconfig(self, data): |
|
269 | 281 | self._ocfg.restore(data[0]) |
|
270 | 282 | self._tcfg.restore(data[1]) |
|
271 | 283 | self._ucfg.restore(data[2]) |
|
272 | 284 | |
|
273 | 285 | def setconfig(self, section, name, value, source=''): |
|
274 | 286 | for cfg in (self._ocfg, self._tcfg, self._ucfg): |
|
275 | 287 | cfg.set(section, name, value, source) |
|
276 | 288 | self.fixconfig(section=section) |
|
277 | 289 | |
|
278 | 290 | def _data(self, untrusted): |
|
279 | 291 | return untrusted and self._ucfg or self._tcfg |
|
280 | 292 | |
|
281 | 293 | def configsource(self, section, name, untrusted=False): |
|
282 | 294 | return self._data(untrusted).source(section, name) or 'none' |
|
283 | 295 | |
|
284 | 296 | def config(self, section, name, default=None, untrusted=False): |
|
285 | 297 | if isinstance(name, list): |
|
286 | 298 | alternates = name |
|
287 | 299 | else: |
|
288 | 300 | alternates = [name] |
|
289 | 301 | |
|
290 | 302 | for n in alternates: |
|
291 | 303 | value = self._data(untrusted).get(section, n, None) |
|
292 | 304 | if value is not None: |
|
293 | 305 | name = n |
|
294 | 306 | break |
|
295 | 307 | else: |
|
296 | 308 | value = default |
|
297 | 309 | |
|
298 | 310 | if self.debugflag and not untrusted and self._reportuntrusted: |
|
299 | 311 | for n in alternates: |
|
300 | 312 | uvalue = self._ucfg.get(section, n) |
|
301 | 313 | if uvalue is not None and uvalue != value: |
|
302 | 314 | self.debug("ignoring untrusted configuration option " |
|
303 | 315 | "%s.%s = %s\n" % (section, n, uvalue)) |
|
304 | 316 | return value |
|
305 | 317 | |
|
306 | 318 | def configsuboptions(self, section, name, default=None, untrusted=False): |
|
307 | 319 | """Get a config option and all sub-options. |
|
308 | 320 | |
|
309 | 321 | Some config options have sub-options that are declared with the |
|
310 | 322 | format "key:opt = value". This method is used to return the main |
|
311 | 323 | option and all its declared sub-options. |
|
312 | 324 | |
|
313 | 325 | Returns a 2-tuple of ``(option, sub-options)``, where `sub-options`` |
|
314 | 326 | is a dict of defined sub-options where keys and values are strings. |
|
315 | 327 | """ |
|
316 | 328 | data = self._data(untrusted) |
|
317 | 329 | main = data.get(section, name, default) |
|
318 | 330 | if self.debugflag and not untrusted and self._reportuntrusted: |
|
319 | 331 | uvalue = self._ucfg.get(section, name) |
|
320 | 332 | if uvalue is not None and uvalue != main: |
|
321 | 333 | self.debug('ignoring untrusted configuration option ' |
|
322 | 334 | '%s.%s = %s\n' % (section, name, uvalue)) |
|
323 | 335 | |
|
324 | 336 | sub = {} |
|
325 | 337 | prefix = '%s:' % name |
|
326 | 338 | for k, v in data.items(section): |
|
327 | 339 | if k.startswith(prefix): |
|
328 | 340 | sub[k[len(prefix):]] = v |
|
329 | 341 | |
|
330 | 342 | if self.debugflag and not untrusted and self._reportuntrusted: |
|
331 | 343 | for k, v in sub.items(): |
|
332 | 344 | uvalue = self._ucfg.get(section, '%s:%s' % (name, k)) |
|
333 | 345 | if uvalue is not None and uvalue != v: |
|
334 | 346 | self.debug('ignoring untrusted configuration option ' |
|
335 | 347 | '%s:%s.%s = %s\n' % (section, name, k, uvalue)) |
|
336 | 348 | |
|
337 | 349 | return main, sub |
|
338 | 350 | |
|
339 | 351 | def configpath(self, section, name, default=None, untrusted=False): |
|
340 | 352 | 'get a path config item, expanded relative to repo root or config file' |
|
341 | 353 | v = self.config(section, name, default, untrusted) |
|
342 | 354 | if v is None: |
|
343 | 355 | return None |
|
344 | 356 | if not os.path.isabs(v) or "://" not in v: |
|
345 | 357 | src = self.configsource(section, name, untrusted) |
|
346 | 358 | if ':' in src: |
|
347 | 359 | base = os.path.dirname(src.rsplit(':')[0]) |
|
348 | 360 | v = os.path.join(base, os.path.expanduser(v)) |
|
349 | 361 | return v |
|
350 | 362 | |
|
351 | 363 | def configbool(self, section, name, default=False, untrusted=False): |
|
352 | 364 | """parse a configuration element as a boolean |
|
353 | 365 | |
|
354 | 366 | >>> u = ui(); s = 'foo' |
|
355 | 367 | >>> u.setconfig(s, 'true', 'yes') |
|
356 | 368 | >>> u.configbool(s, 'true') |
|
357 | 369 | True |
|
358 | 370 | >>> u.setconfig(s, 'false', 'no') |
|
359 | 371 | >>> u.configbool(s, 'false') |
|
360 | 372 | False |
|
361 | 373 | >>> u.configbool(s, 'unknown') |
|
362 | 374 | False |
|
363 | 375 | >>> u.configbool(s, 'unknown', True) |
|
364 | 376 | True |
|
365 | 377 | >>> u.setconfig(s, 'invalid', 'somevalue') |
|
366 | 378 | >>> u.configbool(s, 'invalid') |
|
367 | 379 | Traceback (most recent call last): |
|
368 | 380 | ... |
|
369 | 381 | ConfigError: foo.invalid is not a boolean ('somevalue') |
|
370 | 382 | """ |
|
371 | 383 | |
|
372 | 384 | v = self.config(section, name, None, untrusted) |
|
373 | 385 | if v is None: |
|
374 | 386 | return default |
|
375 | 387 | if isinstance(v, bool): |
|
376 | 388 | return v |
|
377 | 389 | b = util.parsebool(v) |
|
378 | 390 | if b is None: |
|
379 | 391 | raise error.ConfigError(_("%s.%s is not a boolean ('%s')") |
|
380 | 392 | % (section, name, v)) |
|
381 | 393 | return b |
|
382 | 394 | |
|
383 | 395 | def configint(self, section, name, default=None, untrusted=False): |
|
384 | 396 | """parse a configuration element as an integer |
|
385 | 397 | |
|
386 | 398 | >>> u = ui(); s = 'foo' |
|
387 | 399 | >>> u.setconfig(s, 'int1', '42') |
|
388 | 400 | >>> u.configint(s, 'int1') |
|
389 | 401 | 42 |
|
390 | 402 | >>> u.setconfig(s, 'int2', '-42') |
|
391 | 403 | >>> u.configint(s, 'int2') |
|
392 | 404 | -42 |
|
393 | 405 | >>> u.configint(s, 'unknown', 7) |
|
394 | 406 | 7 |
|
395 | 407 | >>> u.setconfig(s, 'invalid', 'somevalue') |
|
396 | 408 | >>> u.configint(s, 'invalid') |
|
397 | 409 | Traceback (most recent call last): |
|
398 | 410 | ... |
|
399 | 411 | ConfigError: foo.invalid is not an integer ('somevalue') |
|
400 | 412 | """ |
|
401 | 413 | |
|
402 | 414 | v = self.config(section, name, None, untrusted) |
|
403 | 415 | if v is None: |
|
404 | 416 | return default |
|
405 | 417 | try: |
|
406 | 418 | return int(v) |
|
407 | 419 | except ValueError: |
|
408 | 420 | raise error.ConfigError(_("%s.%s is not an integer ('%s')") |
|
409 | 421 | % (section, name, v)) |
|
410 | 422 | |
|
411 | 423 | def configbytes(self, section, name, default=0, untrusted=False): |
|
412 | 424 | """parse a configuration element as a quantity in bytes |
|
413 | 425 | |
|
414 | 426 | Units can be specified as b (bytes), k or kb (kilobytes), m or |
|
415 | 427 | mb (megabytes), g or gb (gigabytes). |
|
416 | 428 | |
|
417 | 429 | >>> u = ui(); s = 'foo' |
|
418 | 430 | >>> u.setconfig(s, 'val1', '42') |
|
419 | 431 | >>> u.configbytes(s, 'val1') |
|
420 | 432 | 42 |
|
421 | 433 | >>> u.setconfig(s, 'val2', '42.5 kb') |
|
422 | 434 | >>> u.configbytes(s, 'val2') |
|
423 | 435 | 43520 |
|
424 | 436 | >>> u.configbytes(s, 'unknown', '7 MB') |
|
425 | 437 | 7340032 |
|
426 | 438 | >>> u.setconfig(s, 'invalid', 'somevalue') |
|
427 | 439 | >>> u.configbytes(s, 'invalid') |
|
428 | 440 | Traceback (most recent call last): |
|
429 | 441 | ... |
|
430 | 442 | ConfigError: foo.invalid is not a byte quantity ('somevalue') |
|
431 | 443 | """ |
|
432 | 444 | |
|
433 | 445 | value = self.config(section, name) |
|
434 | 446 | if value is None: |
|
435 | 447 | if not isinstance(default, str): |
|
436 | 448 | return default |
|
437 | 449 | value = default |
|
438 | 450 | try: |
|
439 | 451 | return util.sizetoint(value) |
|
440 | 452 | except error.ParseError: |
|
441 | 453 | raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')") |
|
442 | 454 | % (section, name, value)) |
|
443 | 455 | |
|
444 | 456 | def configlist(self, section, name, default=None, untrusted=False): |
|
445 | 457 | """parse a configuration element as a list of comma/space separated |
|
446 | 458 | strings |
|
447 | 459 | |
|
448 | 460 | >>> u = ui(); s = 'foo' |
|
449 | 461 | >>> u.setconfig(s, 'list1', 'this,is "a small" ,test') |
|
450 | 462 | >>> u.configlist(s, 'list1') |
|
451 | 463 | ['this', 'is', 'a small', 'test'] |
|
452 | 464 | """ |
|
453 | 465 | |
|
454 | 466 | def _parse_plain(parts, s, offset): |
|
455 | 467 | whitespace = False |
|
456 | 468 | while offset < len(s) and (s[offset].isspace() or s[offset] == ','): |
|
457 | 469 | whitespace = True |
|
458 | 470 | offset += 1 |
|
459 | 471 | if offset >= len(s): |
|
460 | 472 | return None, parts, offset |
|
461 | 473 | if whitespace: |
|
462 | 474 | parts.append('') |
|
463 | 475 | if s[offset] == '"' and not parts[-1]: |
|
464 | 476 | return _parse_quote, parts, offset + 1 |
|
465 | 477 | elif s[offset] == '"' and parts[-1][-1] == '\\': |
|
466 | 478 | parts[-1] = parts[-1][:-1] + s[offset] |
|
467 | 479 | return _parse_plain, parts, offset + 1 |
|
468 | 480 | parts[-1] += s[offset] |
|
469 | 481 | return _parse_plain, parts, offset + 1 |
|
470 | 482 | |
|
471 | 483 | def _parse_quote(parts, s, offset): |
|
472 | 484 | if offset < len(s) and s[offset] == '"': # "" |
|
473 | 485 | parts.append('') |
|
474 | 486 | offset += 1 |
|
475 | 487 | while offset < len(s) and (s[offset].isspace() or |
|
476 | 488 | s[offset] == ','): |
|
477 | 489 | offset += 1 |
|
478 | 490 | return _parse_plain, parts, offset |
|
479 | 491 | |
|
480 | 492 | while offset < len(s) and s[offset] != '"': |
|
481 | 493 | if (s[offset] == '\\' and offset + 1 < len(s) |
|
482 | 494 | and s[offset + 1] == '"'): |
|
483 | 495 | offset += 1 |
|
484 | 496 | parts[-1] += '"' |
|
485 | 497 | else: |
|
486 | 498 | parts[-1] += s[offset] |
|
487 | 499 | offset += 1 |
|
488 | 500 | |
|
489 | 501 | if offset >= len(s): |
|
490 | 502 | real_parts = _configlist(parts[-1]) |
|
491 | 503 | if not real_parts: |
|
492 | 504 | parts[-1] = '"' |
|
493 | 505 | else: |
|
494 | 506 | real_parts[0] = '"' + real_parts[0] |
|
495 | 507 | parts = parts[:-1] |
|
496 | 508 | parts.extend(real_parts) |
|
497 | 509 | return None, parts, offset |
|
498 | 510 | |
|
499 | 511 | offset += 1 |
|
500 | 512 | while offset < len(s) and s[offset] in [' ', ',']: |
|
501 | 513 | offset += 1 |
|
502 | 514 | |
|
503 | 515 | if offset < len(s): |
|
504 | 516 | if offset + 1 == len(s) and s[offset] == '"': |
|
505 | 517 | parts[-1] += '"' |
|
506 | 518 | offset += 1 |
|
507 | 519 | else: |
|
508 | 520 | parts.append('') |
|
509 | 521 | else: |
|
510 | 522 | return None, parts, offset |
|
511 | 523 | |
|
512 | 524 | return _parse_plain, parts, offset |
|
513 | 525 | |
|
514 | 526 | def _configlist(s): |
|
515 | 527 | s = s.rstrip(' ,') |
|
516 | 528 | if not s: |
|
517 | 529 | return [] |
|
518 | 530 | parser, parts, offset = _parse_plain, [''], 0 |
|
519 | 531 | while parser: |
|
520 | 532 | parser, parts, offset = parser(parts, s, offset) |
|
521 | 533 | return parts |
|
522 | 534 | |
|
523 | 535 | result = self.config(section, name, untrusted=untrusted) |
|
524 | 536 | if result is None: |
|
525 | 537 | result = default or [] |
|
526 | 538 | if isinstance(result, basestring): |
|
527 | 539 | result = _configlist(result.lstrip(' ,\n')) |
|
528 | 540 | if result is None: |
|
529 | 541 | result = default or [] |
|
530 | 542 | return result |
|
531 | 543 | |
|
532 | 544 | def hasconfig(self, section, name, untrusted=False): |
|
533 | 545 | return self._data(untrusted).hasitem(section, name) |
|
534 | 546 | |
|
535 | 547 | def has_section(self, section, untrusted=False): |
|
536 | 548 | '''tell whether section exists in config.''' |
|
537 | 549 | return section in self._data(untrusted) |
|
538 | 550 | |
|
539 | 551 | def configitems(self, section, untrusted=False, ignoresub=False): |
|
540 | 552 | items = self._data(untrusted).items(section) |
|
541 | 553 | if ignoresub: |
|
542 | 554 | newitems = {} |
|
543 | 555 | for k, v in items: |
|
544 | 556 | if ':' not in k: |
|
545 | 557 | newitems[k] = v |
|
546 | 558 | items = newitems.items() |
|
547 | 559 | if self.debugflag and not untrusted and self._reportuntrusted: |
|
548 | 560 | for k, v in self._ucfg.items(section): |
|
549 | 561 | if self._tcfg.get(section, k) != v: |
|
550 | 562 | self.debug("ignoring untrusted configuration option " |
|
551 | 563 | "%s.%s = %s\n" % (section, k, v)) |
|
552 | 564 | return items |
|
553 | 565 | |
|
554 | 566 | def walkconfig(self, untrusted=False): |
|
555 | 567 | cfg = self._data(untrusted) |
|
556 | 568 | for section in cfg.sections(): |
|
557 | 569 | for name, value in self.configitems(section, untrusted): |
|
558 | 570 | yield section, name, value |
|
559 | 571 | |
|
560 | 572 | def plain(self, feature=None): |
|
561 | 573 | '''is plain mode active? |
|
562 | 574 | |
|
563 | 575 | Plain mode means that all configuration variables which affect |
|
564 | 576 | the behavior and output of Mercurial should be |
|
565 | 577 | ignored. Additionally, the output should be stable, |
|
566 | 578 | reproducible and suitable for use in scripts or applications. |
|
567 | 579 | |
|
568 | 580 | The only way to trigger plain mode is by setting either the |
|
569 | 581 | `HGPLAIN' or `HGPLAINEXCEPT' environment variables. |
|
570 | 582 | |
|
571 | 583 | The return value can either be |
|
572 | 584 | - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT |
|
573 | 585 | - True otherwise |
|
574 | 586 | ''' |
|
575 | 587 | if ('HGPLAIN' not in encoding.environ and |
|
576 | 588 | 'HGPLAINEXCEPT' not in encoding.environ): |
|
577 | 589 | return False |
|
578 | 590 | exceptions = encoding.environ.get('HGPLAINEXCEPT', |
|
579 | 591 | '').strip().split(',') |
|
580 | 592 | if feature and exceptions: |
|
581 | 593 | return feature not in exceptions |
|
582 | 594 | return True |
|
583 | 595 | |
|
584 | 596 | def username(self): |
|
585 | 597 | """Return default username to be used in commits. |
|
586 | 598 | |
|
587 | 599 | Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL |
|
588 | 600 | and stop searching if one of these is set. |
|
589 | 601 | If not found and ui.askusername is True, ask the user, else use |
|
590 | 602 | ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname". |
|
591 | 603 | """ |
|
592 | 604 | user = encoding.environ.get("HGUSER") |
|
593 | 605 | if user is None: |
|
594 | 606 | user = self.config("ui", ["username", "user"]) |
|
595 | 607 | if user is not None: |
|
596 | 608 | user = os.path.expandvars(user) |
|
597 | 609 | if user is None: |
|
598 | 610 | user = encoding.environ.get("EMAIL") |
|
599 | 611 | if user is None and self.configbool("ui", "askusername"): |
|
600 | 612 | user = self.prompt(_("enter a commit username:"), default=None) |
|
601 | 613 | if user is None and not self.interactive(): |
|
602 | 614 | try: |
|
603 | 615 | user = '%s@%s' % (util.getuser(), socket.getfqdn()) |
|
604 | 616 | self.warn(_("no username found, using '%s' instead\n") % user) |
|
605 | 617 | except KeyError: |
|
606 | 618 | pass |
|
607 | 619 | if not user: |
|
608 | 620 | raise error.Abort(_('no username supplied'), |
|
609 | 621 | hint=_("use 'hg config --edit' " |
|
610 | 622 | 'to set your username')) |
|
611 | 623 | if "\n" in user: |
|
612 | 624 | raise error.Abort(_("username %s contains a newline\n") |
|
613 | 625 | % repr(user)) |
|
614 | 626 | return user |
|
615 | 627 | |
|
616 | 628 | def shortuser(self, user): |
|
617 | 629 | """Return a short representation of a user name or email address.""" |
|
618 | 630 | if not self.verbose: |
|
619 | 631 | user = util.shortuser(user) |
|
620 | 632 | return user |
|
621 | 633 | |
|
622 | 634 | def expandpath(self, loc, default=None): |
|
623 | 635 | """Return repository location relative to cwd or from [paths]""" |
|
624 | 636 | try: |
|
625 | 637 | p = self.paths.getpath(loc) |
|
626 | 638 | if p: |
|
627 | 639 | return p.rawloc |
|
628 | 640 | except error.RepoError: |
|
629 | 641 | pass |
|
630 | 642 | |
|
631 | 643 | if default: |
|
632 | 644 | try: |
|
633 | 645 | p = self.paths.getpath(default) |
|
634 | 646 | if p: |
|
635 | 647 | return p.rawloc |
|
636 | 648 | except error.RepoError: |
|
637 | 649 | pass |
|
638 | 650 | |
|
639 | 651 | return loc |
|
640 | 652 | |
|
641 | 653 | @util.propertycache |
|
642 | 654 | def paths(self): |
|
643 | 655 | return paths(self) |
|
644 | 656 | |
|
645 | 657 | def pushbuffer(self, error=False, subproc=False, labeled=False): |
|
646 | 658 | """install a buffer to capture standard output of the ui object |
|
647 | 659 | |
|
648 | 660 | If error is True, the error output will be captured too. |
|
649 | 661 | |
|
650 | 662 | If subproc is True, output from subprocesses (typically hooks) will be |
|
651 | 663 | captured too. |
|
652 | 664 | |
|
653 | 665 | If labeled is True, any labels associated with buffered |
|
654 | 666 | output will be handled. By default, this has no effect |
|
655 | 667 | on the output returned, but extensions and GUI tools may |
|
656 | 668 | handle this argument and returned styled output. If output |
|
657 | 669 | is being buffered so it can be captured and parsed or |
|
658 | 670 | processed, labeled should not be set to True. |
|
659 | 671 | """ |
|
660 | 672 | self._buffers.append([]) |
|
661 | 673 | self._bufferstates.append((error, subproc, labeled)) |
|
662 | 674 | self._bufferapplylabels = labeled |
|
663 | 675 | |
|
664 | 676 | def popbuffer(self): |
|
665 | 677 | '''pop the last buffer and return the buffered output''' |
|
666 | 678 | self._bufferstates.pop() |
|
667 | 679 | if self._bufferstates: |
|
668 | 680 | self._bufferapplylabels = self._bufferstates[-1][2] |
|
669 | 681 | else: |
|
670 | 682 | self._bufferapplylabels = None |
|
671 | 683 | |
|
672 | 684 | return "".join(self._buffers.pop()) |
|
673 | 685 | |
|
674 | 686 | def write(self, *args, **opts): |
|
675 | 687 | '''write args to output |
|
676 | 688 | |
|
677 | 689 | By default, this method simply writes to the buffer or stdout, |
|
678 | 690 | but extensions or GUI tools may override this method, |
|
679 | 691 | write_err(), popbuffer(), and label() to style output from |
|
680 | 692 | various parts of hg. |
|
681 | 693 | |
|
682 | 694 | An optional keyword argument, "label", can be passed in. |
|
683 | 695 | This should be a string containing label names separated by |
|
684 | 696 | space. Label names take the form of "topic.type". For example, |
|
685 | 697 | ui.debug() issues a label of "ui.debug". |
|
686 | 698 | |
|
687 | 699 | When labeling output for a specific command, a label of |
|
688 | 700 | "cmdname.type" is recommended. For example, status issues |
|
689 | 701 | a label of "status.modified" for modified files. |
|
690 | 702 | ''' |
|
691 | 703 | if self._buffers and not opts.get('prompt', False): |
|
692 | 704 | self._buffers[-1].extend(a for a in args) |
|
693 | 705 | else: |
|
694 | 706 | self._progclear() |
|
695 | 707 | for a in args: |
|
696 | 708 | self.fout.write(a) |
|
697 | 709 | |
|
698 | 710 | def write_err(self, *args, **opts): |
|
699 | 711 | self._progclear() |
|
700 | 712 | try: |
|
701 | 713 | if self._bufferstates and self._bufferstates[-1][0]: |
|
702 | 714 | return self.write(*args, **opts) |
|
703 | 715 | if not getattr(self.fout, 'closed', False): |
|
704 | 716 | self.fout.flush() |
|
705 | 717 | for a in args: |
|
706 | 718 | self.ferr.write(a) |
|
707 | 719 | # stderr may be buffered under win32 when redirected to files, |
|
708 | 720 | # including stdout. |
|
709 | 721 | if not getattr(self.ferr, 'closed', False): |
|
710 | 722 | self.ferr.flush() |
|
711 | 723 | except IOError as inst: |
|
712 | 724 | if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF): |
|
713 | 725 | raise |
|
714 | 726 | |
|
715 | 727 | def flush(self): |
|
716 | 728 | try: self.fout.flush() |
|
717 | 729 | except (IOError, ValueError): pass |
|
718 | 730 | try: self.ferr.flush() |
|
719 | 731 | except (IOError, ValueError): pass |
|
720 | 732 | |
|
721 | 733 | def _isatty(self, fh): |
|
722 | 734 | if self.configbool('ui', 'nontty', False): |
|
723 | 735 | return False |
|
724 | 736 | return util.isatty(fh) |
|
725 | 737 | |
|
726 | 738 | def interface(self, feature): |
|
727 | 739 | """what interface to use for interactive console features? |
|
728 | 740 | |
|
729 | 741 | The interface is controlled by the value of `ui.interface` but also by |
|
730 | 742 | the value of feature-specific configuration. For example: |
|
731 | 743 | |
|
732 | 744 | ui.interface.histedit = text |
|
733 | 745 | ui.interface.chunkselector = curses |
|
734 | 746 | |
|
735 | 747 | Here the features are "histedit" and "chunkselector". |
|
736 | 748 | |
|
737 | 749 | The configuration above means that the default interfaces for commands |
|
738 | 750 | is curses, the interface for histedit is text and the interface for |
|
739 | 751 | selecting chunk is crecord (the best curses interface available). |
|
740 | 752 | |
|
741 | 753 | Consider the following example: |
|
742 | 754 | ui.interface = curses |
|
743 | 755 | ui.interface.histedit = text |
|
744 | 756 | |
|
745 | 757 | Then histedit will use the text interface and chunkselector will use |
|
746 | 758 | the default curses interface (crecord at the moment). |
|
747 | 759 | """ |
|
748 | 760 | alldefaults = frozenset(["text", "curses"]) |
|
749 | 761 | |
|
750 | 762 | featureinterfaces = { |
|
751 | 763 | "chunkselector": [ |
|
752 | 764 | "text", |
|
753 | 765 | "curses", |
|
754 | 766 | ] |
|
755 | 767 | } |
|
756 | 768 | |
|
757 | 769 | # Feature-specific interface |
|
758 | 770 | if feature not in featureinterfaces.keys(): |
|
759 | 771 | # Programming error, not user error |
|
760 | 772 | raise ValueError("Unknown feature requested %s" % feature) |
|
761 | 773 | |
|
762 | 774 | availableinterfaces = frozenset(featureinterfaces[feature]) |
|
763 | 775 | if alldefaults > availableinterfaces: |
|
764 | 776 | # Programming error, not user error. We need a use case to |
|
765 | 777 | # define the right thing to do here. |
|
766 | 778 | raise ValueError( |
|
767 | 779 | "Feature %s does not handle all default interfaces" % |
|
768 | 780 | feature) |
|
769 | 781 | |
|
770 | 782 | if self.plain(): |
|
771 | 783 | return "text" |
|
772 | 784 | |
|
773 | 785 | # Default interface for all the features |
|
774 | 786 | defaultinterface = "text" |
|
775 | 787 | i = self.config("ui", "interface", None) |
|
776 | 788 | if i in alldefaults: |
|
777 | 789 | defaultinterface = i |
|
778 | 790 | |
|
779 | 791 | choseninterface = defaultinterface |
|
780 | 792 | f = self.config("ui", "interface.%s" % feature, None) |
|
781 | 793 | if f in availableinterfaces: |
|
782 | 794 | choseninterface = f |
|
783 | 795 | |
|
784 | 796 | if i is not None and defaultinterface != i: |
|
785 | 797 | if f is not None: |
|
786 | 798 | self.warn(_("invalid value for ui.interface: %s\n") % |
|
787 | 799 | (i,)) |
|
788 | 800 | else: |
|
789 | 801 | self.warn(_("invalid value for ui.interface: %s (using %s)\n") % |
|
790 | 802 | (i, choseninterface)) |
|
791 | 803 | if f is not None and choseninterface != f: |
|
792 | 804 | self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") % |
|
793 | 805 | (feature, f, choseninterface)) |
|
794 | 806 | |
|
795 | 807 | return choseninterface |
|
796 | 808 | |
|
797 | 809 | def interactive(self): |
|
798 | 810 | '''is interactive input allowed? |
|
799 | 811 | |
|
800 | 812 | An interactive session is a session where input can be reasonably read |
|
801 | 813 | from `sys.stdin'. If this function returns false, any attempt to read |
|
802 | 814 | from stdin should fail with an error, unless a sensible default has been |
|
803 | 815 | specified. |
|
804 | 816 | |
|
805 | 817 | Interactiveness is triggered by the value of the `ui.interactive' |
|
806 | 818 | configuration variable or - if it is unset - when `sys.stdin' points |
|
807 | 819 | to a terminal device. |
|
808 | 820 | |
|
809 | 821 | This function refers to input only; for output, see `ui.formatted()'. |
|
810 | 822 | ''' |
|
811 | 823 | i = self.configbool("ui", "interactive", None) |
|
812 | 824 | if i is None: |
|
813 | 825 | # some environments replace stdin without implementing isatty |
|
814 | 826 | # usually those are non-interactive |
|
815 | 827 | return self._isatty(self.fin) |
|
816 | 828 | |
|
817 | 829 | return i |
|
818 | 830 | |
|
819 | 831 | def termwidth(self): |
|
820 | 832 | '''how wide is the terminal in columns? |
|
821 | 833 | ''' |
|
822 | 834 | if 'COLUMNS' in encoding.environ: |
|
823 | 835 | try: |
|
824 | 836 | return int(encoding.environ['COLUMNS']) |
|
825 | 837 | except ValueError: |
|
826 | 838 | pass |
|
827 | 839 | return scmutil.termsize(self)[0] |
|
828 | 840 | |
|
829 | 841 | def formatted(self): |
|
830 | 842 | '''should formatted output be used? |
|
831 | 843 | |
|
832 | 844 | It is often desirable to format the output to suite the output medium. |
|
833 | 845 | Examples of this are truncating long lines or colorizing messages. |
|
834 | 846 | However, this is not often not desirable when piping output into other |
|
835 | 847 | utilities, e.g. `grep'. |
|
836 | 848 | |
|
837 | 849 | Formatted output is triggered by the value of the `ui.formatted' |
|
838 | 850 | configuration variable or - if it is unset - when `sys.stdout' points |
|
839 | 851 | to a terminal device. Please note that `ui.formatted' should be |
|
840 | 852 | considered an implementation detail; it is not intended for use outside |
|
841 | 853 | Mercurial or its extensions. |
|
842 | 854 | |
|
843 | 855 | This function refers to output only; for input, see `ui.interactive()'. |
|
844 | 856 | This function always returns false when in plain mode, see `ui.plain()'. |
|
845 | 857 | ''' |
|
846 | 858 | if self.plain(): |
|
847 | 859 | return False |
|
848 | 860 | |
|
849 | 861 | i = self.configbool("ui", "formatted", None) |
|
850 | 862 | if i is None: |
|
851 | 863 | # some environments replace stdout without implementing isatty |
|
852 | 864 | # usually those are non-interactive |
|
853 | 865 | return self._isatty(self.fout) |
|
854 | 866 | |
|
855 | 867 | return i |
|
856 | 868 | |
|
857 | 869 | def _readline(self, prompt=''): |
|
858 | 870 | if self._isatty(self.fin): |
|
859 | 871 | try: |
|
860 | 872 | # magically add command line editing support, where |
|
861 | 873 | # available |
|
862 | 874 | import readline |
|
863 | 875 | # force demandimport to really load the module |
|
864 | 876 | readline.read_history_file |
|
865 | 877 | # windows sometimes raises something other than ImportError |
|
866 | 878 | except Exception: |
|
867 | 879 | pass |
|
868 | 880 | |
|
869 | 881 | # call write() so output goes through subclassed implementation |
|
870 | 882 | # e.g. color extension on Windows |
|
871 | 883 | self.write(prompt, prompt=True) |
|
872 | 884 | |
|
873 | 885 | # instead of trying to emulate raw_input, swap (self.fin, |
|
874 | 886 | # self.fout) with (sys.stdin, sys.stdout) |
|
875 | 887 | oldin = sys.stdin |
|
876 | 888 | oldout = sys.stdout |
|
877 | 889 | sys.stdin = self.fin |
|
878 | 890 | sys.stdout = self.fout |
|
879 | 891 | # prompt ' ' must exist; otherwise readline may delete entire line |
|
880 | 892 | # - http://bugs.python.org/issue12833 |
|
881 | 893 | line = raw_input(' ') |
|
882 | 894 | sys.stdin = oldin |
|
883 | 895 | sys.stdout = oldout |
|
884 | 896 | |
|
885 | 897 | # When stdin is in binary mode on Windows, it can cause |
|
886 | 898 | # raw_input() to emit an extra trailing carriage return |
|
887 | 899 | if os.linesep == '\r\n' and line and line[-1] == '\r': |
|
888 | 900 | line = line[:-1] |
|
889 | 901 | return line |
|
890 | 902 | |
|
891 | 903 | def prompt(self, msg, default="y"): |
|
892 | 904 | """Prompt user with msg, read response. |
|
893 | 905 | If ui is not interactive, the default is returned. |
|
894 | 906 | """ |
|
895 | 907 | if not self.interactive(): |
|
896 | 908 | self.write(msg, ' ', default or '', "\n") |
|
897 | 909 | return default |
|
898 | 910 | try: |
|
899 | 911 | r = self._readline(self.label(msg, 'ui.prompt')) |
|
900 | 912 | if not r: |
|
901 | 913 | r = default |
|
902 | 914 | if self.configbool('ui', 'promptecho'): |
|
903 | 915 | self.write(r, "\n") |
|
904 | 916 | return r |
|
905 | 917 | except EOFError: |
|
906 | 918 | raise error.ResponseExpected() |
|
907 | 919 | |
|
908 | 920 | @staticmethod |
|
909 | 921 | def extractchoices(prompt): |
|
910 | 922 | """Extract prompt message and list of choices from specified prompt. |
|
911 | 923 | |
|
912 | 924 | This returns tuple "(message, choices)", and "choices" is the |
|
913 | 925 | list of tuple "(response character, text without &)". |
|
914 | 926 | |
|
915 | 927 | >>> ui.extractchoices("awake? $$ &Yes $$ &No") |
|
916 | 928 | ('awake? ', [('y', 'Yes'), ('n', 'No')]) |
|
917 | 929 | >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No") |
|
918 | 930 | ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')]) |
|
919 | 931 | >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o") |
|
920 | 932 | ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')]) |
|
921 | 933 | """ |
|
922 | 934 | |
|
923 | 935 | # Sadly, the prompt string may have been built with a filename |
|
924 | 936 | # containing "$$" so let's try to find the first valid-looking |
|
925 | 937 | # prompt to start parsing. Sadly, we also can't rely on |
|
926 | 938 | # choices containing spaces, ASCII, or basically anything |
|
927 | 939 | # except an ampersand followed by a character. |
|
928 | 940 | m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt) |
|
929 | 941 | msg = m.group(1) |
|
930 | 942 | choices = [p.strip(' ') for p in m.group(2).split('$$')] |
|
931 | 943 | return (msg, |
|
932 | 944 | [(s[s.index('&') + 1].lower(), s.replace('&', '', 1)) |
|
933 | 945 | for s in choices]) |
|
934 | 946 | |
|
935 | 947 | def promptchoice(self, prompt, default=0): |
|
936 | 948 | """Prompt user with a message, read response, and ensure it matches |
|
937 | 949 | one of the provided choices. The prompt is formatted as follows: |
|
938 | 950 | |
|
939 | 951 | "would you like fries with that (Yn)? $$ &Yes $$ &No" |
|
940 | 952 | |
|
941 | 953 | The index of the choice is returned. Responses are case |
|
942 | 954 | insensitive. If ui is not interactive, the default is |
|
943 | 955 | returned. |
|
944 | 956 | """ |
|
945 | 957 | |
|
946 | 958 | msg, choices = self.extractchoices(prompt) |
|
947 | 959 | resps = [r for r, t in choices] |
|
948 | 960 | while True: |
|
949 | 961 | r = self.prompt(msg, resps[default]) |
|
950 | 962 | if r.lower() in resps: |
|
951 | 963 | return resps.index(r.lower()) |
|
952 | 964 | self.write(_("unrecognized response\n")) |
|
953 | 965 | |
|
954 | 966 | def getpass(self, prompt=None, default=None): |
|
955 | 967 | if not self.interactive(): |
|
956 | 968 | return default |
|
957 | 969 | try: |
|
958 | 970 | self.write_err(self.label(prompt or _('password: '), 'ui.prompt')) |
|
959 | 971 | # disable getpass() only if explicitly specified. it's still valid |
|
960 | 972 | # to interact with tty even if fin is not a tty. |
|
961 | 973 | if self.configbool('ui', 'nontty'): |
|
962 | 974 | return self.fin.readline().rstrip('\n') |
|
963 | 975 | else: |
|
964 | 976 | return getpass.getpass('') |
|
965 | 977 | except EOFError: |
|
966 | 978 | raise error.ResponseExpected() |
|
967 | 979 | def status(self, *msg, **opts): |
|
968 | 980 | '''write status message to output (if ui.quiet is False) |
|
969 | 981 | |
|
970 | 982 | This adds an output label of "ui.status". |
|
971 | 983 | ''' |
|
972 | 984 | if not self.quiet: |
|
973 | 985 | opts['label'] = opts.get('label', '') + ' ui.status' |
|
974 | 986 | self.write(*msg, **opts) |
|
975 | 987 | def warn(self, *msg, **opts): |
|
976 | 988 | '''write warning message to output (stderr) |
|
977 | 989 | |
|
978 | 990 | This adds an output label of "ui.warning". |
|
979 | 991 | ''' |
|
980 | 992 | opts['label'] = opts.get('label', '') + ' ui.warning' |
|
981 | 993 | self.write_err(*msg, **opts) |
|
982 | 994 | def note(self, *msg, **opts): |
|
983 | 995 | '''write note to output (if ui.verbose is True) |
|
984 | 996 | |
|
985 | 997 | This adds an output label of "ui.note". |
|
986 | 998 | ''' |
|
987 | 999 | if self.verbose: |
|
988 | 1000 | opts['label'] = opts.get('label', '') + ' ui.note' |
|
989 | 1001 | self.write(*msg, **opts) |
|
990 | 1002 | def debug(self, *msg, **opts): |
|
991 | 1003 | '''write debug message to output (if ui.debugflag is True) |
|
992 | 1004 | |
|
993 | 1005 | This adds an output label of "ui.debug". |
|
994 | 1006 | ''' |
|
995 | 1007 | if self.debugflag: |
|
996 | 1008 | opts['label'] = opts.get('label', '') + ' ui.debug' |
|
997 | 1009 | self.write(*msg, **opts) |
|
998 | 1010 | |
|
999 | 1011 | def edit(self, text, user, extra=None, editform=None, pending=None): |
|
1000 | 1012 | extra_defaults = { |
|
1001 | 1013 | 'prefix': 'editor', |
|
1002 | 1014 | 'suffix': '.txt', |
|
1003 | 1015 | } |
|
1004 | 1016 | if extra is not None: |
|
1005 | 1017 | extra_defaults.update(extra) |
|
1006 | 1018 | extra = extra_defaults |
|
1007 | 1019 | (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-', |
|
1008 | 1020 | suffix=extra['suffix'], text=True) |
|
1009 | 1021 | try: |
|
1010 | 1022 | f = os.fdopen(fd, "w") |
|
1011 | 1023 | f.write(text) |
|
1012 | 1024 | f.close() |
|
1013 | 1025 | |
|
1014 | 1026 | environ = {'HGUSER': user} |
|
1015 | 1027 | if 'transplant_source' in extra: |
|
1016 | 1028 | environ.update({'HGREVISION': hex(extra['transplant_source'])}) |
|
1017 | 1029 | for label in ('intermediate-source', 'source', 'rebase_source'): |
|
1018 | 1030 | if label in extra: |
|
1019 | 1031 | environ.update({'HGREVISION': extra[label]}) |
|
1020 | 1032 | break |
|
1021 | 1033 | if editform: |
|
1022 | 1034 | environ.update({'HGEDITFORM': editform}) |
|
1023 | 1035 | if pending: |
|
1024 | 1036 | environ.update({'HG_PENDING': pending}) |
|
1025 | 1037 | |
|
1026 | 1038 | editor = self.geteditor() |
|
1027 | 1039 | |
|
1028 | 1040 | self.system("%s \"%s\"" % (editor, name), |
|
1029 | 1041 | environ=environ, |
|
1030 | 1042 | onerr=error.Abort, errprefix=_("edit failed")) |
|
1031 | 1043 | |
|
1032 | 1044 | f = open(name) |
|
1033 | 1045 | t = f.read() |
|
1034 | 1046 | f.close() |
|
1035 | 1047 | finally: |
|
1036 | 1048 | os.unlink(name) |
|
1037 | 1049 | |
|
1038 | 1050 | return t |
|
1039 | 1051 | |
|
1040 | 1052 | def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None): |
|
1041 | 1053 | '''execute shell command with appropriate output stream. command |
|
1042 | 1054 | output will be redirected if fout is not stdout. |
|
1043 | 1055 | ''' |
|
1044 | 1056 | out = self.fout |
|
1045 | 1057 | if any(s[1] for s in self._bufferstates): |
|
1046 | 1058 | out = self |
|
1047 | 1059 | return util.system(cmd, environ=environ, cwd=cwd, onerr=onerr, |
|
1048 | 1060 | errprefix=errprefix, out=out) |
|
1049 | 1061 | |
|
1050 | 1062 | def traceback(self, exc=None, force=False): |
|
1051 | 1063 | '''print exception traceback if traceback printing enabled or forced. |
|
1052 | 1064 | only to call in exception handler. returns true if traceback |
|
1053 | 1065 | printed.''' |
|
1054 | 1066 | if self.tracebackflag or force: |
|
1055 | 1067 | if exc is None: |
|
1056 | 1068 | exc = sys.exc_info() |
|
1057 | 1069 | cause = getattr(exc[1], 'cause', None) |
|
1058 | 1070 | |
|
1059 | 1071 | if cause is not None: |
|
1060 | 1072 | causetb = traceback.format_tb(cause[2]) |
|
1061 | 1073 | exctb = traceback.format_tb(exc[2]) |
|
1062 | 1074 | exconly = traceback.format_exception_only(cause[0], cause[1]) |
|
1063 | 1075 | |
|
1064 | 1076 | # exclude frame where 'exc' was chained and rethrown from exctb |
|
1065 | 1077 | self.write_err('Traceback (most recent call last):\n', |
|
1066 | 1078 | ''.join(exctb[:-1]), |
|
1067 | 1079 | ''.join(causetb), |
|
1068 | 1080 | ''.join(exconly)) |
|
1069 | 1081 | else: |
|
1070 | 1082 | output = traceback.format_exception(exc[0], exc[1], exc[2]) |
|
1071 | 1083 | self.write_err(''.join(output)) |
|
1072 | 1084 | return self.tracebackflag or force |
|
1073 | 1085 | |
|
1074 | 1086 | def geteditor(self): |
|
1075 | 1087 | '''return editor to use''' |
|
1076 | 1088 | if sys.platform == 'plan9': |
|
1077 | 1089 | # vi is the MIPS instruction simulator on Plan 9. We |
|
1078 | 1090 | # instead default to E to plumb commit messages to |
|
1079 | 1091 | # avoid confusion. |
|
1080 | 1092 | editor = 'E' |
|
1081 | 1093 | else: |
|
1082 | 1094 | editor = 'vi' |
|
1083 | 1095 | return (encoding.environ.get("HGEDITOR") or |
|
1084 | 1096 | self.config("ui", "editor") or |
|
1085 | 1097 | encoding.environ.get("VISUAL") or |
|
1086 | 1098 | encoding.environ.get("EDITOR", editor)) |
|
1087 | 1099 | |
|
1088 | 1100 | @util.propertycache |
|
1089 | 1101 | def _progbar(self): |
|
1090 | 1102 | """setup the progbar singleton to the ui object""" |
|
1091 | 1103 | if (self.quiet or self.debugflag |
|
1092 | 1104 | or self.configbool('progress', 'disable', False) |
|
1093 | 1105 | or not progress.shouldprint(self)): |
|
1094 | 1106 | return None |
|
1095 | 1107 | return getprogbar(self) |
|
1096 | 1108 | |
|
1097 | 1109 | def _progclear(self): |
|
1098 | 1110 | """clear progress bar output if any. use it before any output""" |
|
1099 | 1111 | if '_progbar' not in vars(self): # nothing loaded yet |
|
1100 | 1112 | return |
|
1101 | 1113 | if self._progbar is not None and self._progbar.printed: |
|
1102 | 1114 | self._progbar.clear() |
|
1103 | 1115 | |
|
1104 | 1116 | def progress(self, topic, pos, item="", unit="", total=None): |
|
1105 | 1117 | '''show a progress message |
|
1106 | 1118 | |
|
1107 | 1119 | By default a textual progress bar will be displayed if an operation |
|
1108 | 1120 | takes too long. 'topic' is the current operation, 'item' is a |
|
1109 | 1121 | non-numeric marker of the current position (i.e. the currently |
|
1110 | 1122 | in-process file), 'pos' is the current numeric position (i.e. |
|
1111 | 1123 | revision, bytes, etc.), unit is a corresponding unit label, |
|
1112 | 1124 | and total is the highest expected pos. |
|
1113 | 1125 | |
|
1114 | 1126 | Multiple nested topics may be active at a time. |
|
1115 | 1127 | |
|
1116 | 1128 | All topics should be marked closed by setting pos to None at |
|
1117 | 1129 | termination. |
|
1118 | 1130 | ''' |
|
1119 | 1131 | if self._progbar is not None: |
|
1120 | 1132 | self._progbar.progress(topic, pos, item=item, unit=unit, |
|
1121 | 1133 | total=total) |
|
1122 | 1134 | if pos is None or not self.configbool('progress', 'debug'): |
|
1123 | 1135 | return |
|
1124 | 1136 | |
|
1125 | 1137 | if unit: |
|
1126 | 1138 | unit = ' ' + unit |
|
1127 | 1139 | if item: |
|
1128 | 1140 | item = ' ' + item |
|
1129 | 1141 | |
|
1130 | 1142 | if total: |
|
1131 | 1143 | pct = 100.0 * pos / total |
|
1132 | 1144 | self.debug('%s:%s %s/%s%s (%4.2f%%)\n' |
|
1133 | 1145 | % (topic, item, pos, total, unit, pct)) |
|
1134 | 1146 | else: |
|
1135 | 1147 | self.debug('%s:%s %s%s\n' % (topic, item, pos, unit)) |
|
1136 | 1148 | |
|
1137 | 1149 | def log(self, service, *msg, **opts): |
|
1138 | 1150 | '''hook for logging facility extensions |
|
1139 | 1151 | |
|
1140 | 1152 | service should be a readily-identifiable subsystem, which will |
|
1141 | 1153 | allow filtering. |
|
1142 | 1154 | |
|
1143 | 1155 | *msg should be a newline-terminated format string to log, and |
|
1144 | 1156 | then any values to %-format into that format string. |
|
1145 | 1157 | |
|
1146 | 1158 | **opts currently has no defined meanings. |
|
1147 | 1159 | ''' |
|
1148 | 1160 | |
|
1149 | 1161 | def label(self, msg, label): |
|
1150 | 1162 | '''style msg based on supplied label |
|
1151 | 1163 | |
|
1152 | 1164 | Like ui.write(), this just returns msg unchanged, but extensions |
|
1153 | 1165 | and GUI tools can override it to allow styling output without |
|
1154 | 1166 | writing it. |
|
1155 | 1167 | |
|
1156 | 1168 | ui.write(s, 'label') is equivalent to |
|
1157 | 1169 | ui.write(ui.label(s, 'label')). |
|
1158 | 1170 | ''' |
|
1159 | 1171 | return msg |
|
1160 | 1172 | |
|
1161 | 1173 | def develwarn(self, msg, stacklevel=1, config=None): |
|
1162 | 1174 | """issue a developer warning message |
|
1163 | 1175 | |
|
1164 | 1176 | Use 'stacklevel' to report the offender some layers further up in the |
|
1165 | 1177 | stack. |
|
1166 | 1178 | """ |
|
1167 | 1179 | if not self.configbool('devel', 'all-warnings'): |
|
1168 | 1180 | if config is not None and not self.configbool('devel', config): |
|
1169 | 1181 | return |
|
1170 | 1182 | msg = 'devel-warn: ' + msg |
|
1171 | 1183 | stacklevel += 1 # get in develwarn |
|
1172 | 1184 | if self.tracebackflag: |
|
1173 | 1185 | util.debugstacktrace(msg, stacklevel, self.ferr, self.fout) |
|
1174 | 1186 | self.log('develwarn', '%s at:\n%s' % |
|
1175 | 1187 | (msg, ''.join(util.getstackframes(stacklevel)))) |
|
1176 | 1188 | else: |
|
1177 | 1189 | curframe = inspect.currentframe() |
|
1178 | 1190 | calframe = inspect.getouterframes(curframe, 2) |
|
1179 | 1191 | self.write_err('%s at: %s:%s (%s)\n' |
|
1180 | 1192 | % ((msg,) + calframe[stacklevel][1:4])) |
|
1181 | 1193 | self.log('develwarn', '%s at: %s:%s (%s)\n', |
|
1182 | 1194 | msg, *calframe[stacklevel][1:4]) |
|
1183 | 1195 | curframe = calframe = None # avoid cycles |
|
1184 | 1196 | |
|
1185 | 1197 | def deprecwarn(self, msg, version): |
|
1186 | 1198 | """issue a deprecation warning |
|
1187 | 1199 | |
|
1188 | 1200 | - msg: message explaining what is deprecated and how to upgrade, |
|
1189 | 1201 | - version: last version where the API will be supported, |
|
1190 | 1202 | """ |
|
1191 | 1203 | if not (self.configbool('devel', 'all-warnings') |
|
1192 | 1204 | or self.configbool('devel', 'deprec-warn')): |
|
1193 | 1205 | return |
|
1194 | 1206 | msg += ("\n(compatibility will be dropped after Mercurial-%s," |
|
1195 | 1207 | " update your code.)") % version |
|
1196 | 1208 | self.develwarn(msg, stacklevel=2, config='deprec-warn') |
|
1197 | 1209 | |
|
1198 | 1210 | @contextlib.contextmanager |
|
1199 | 1211 | def configoverride(self, overrides, source=""): |
|
1200 | 1212 | """Context manager for temporary config overrides |
|
1201 | 1213 | `overrides` must be a dict of the following structure: |
|
1202 | 1214 | {(section, name) : value}""" |
|
1203 | 1215 | backups = {} |
|
1204 | 1216 | try: |
|
1205 | 1217 | for (section, name), value in overrides.items(): |
|
1206 | 1218 | backups[(section, name)] = self.backupconfig(section, name) |
|
1207 | 1219 | self.setconfig(section, name, value, source) |
|
1208 | 1220 | yield |
|
1209 | 1221 | finally: |
|
1210 | 1222 | for __, backup in backups.items(): |
|
1211 | 1223 | self.restoreconfig(backup) |
|
1212 | 1224 | # just restoring ui.quiet config to the previous value is not enough |
|
1213 | 1225 | # as it does not update ui.quiet class member |
|
1214 | 1226 | if ('ui', 'quiet') in overrides: |
|
1215 | 1227 | self.fixconfig(section='ui') |
|
1216 | 1228 | |
|
1217 | 1229 | class paths(dict): |
|
1218 | 1230 | """Represents a collection of paths and their configs. |
|
1219 | 1231 | |
|
1220 | 1232 | Data is initially derived from ui instances and the config files they have |
|
1221 | 1233 | loaded. |
|
1222 | 1234 | """ |
|
1223 | 1235 | def __init__(self, ui): |
|
1224 | 1236 | dict.__init__(self) |
|
1225 | 1237 | |
|
1226 | 1238 | for name, loc in ui.configitems('paths', ignoresub=True): |
|
1227 | 1239 | # No location is the same as not existing. |
|
1228 | 1240 | if not loc: |
|
1229 | 1241 | continue |
|
1230 | 1242 | loc, sub = ui.configsuboptions('paths', name) |
|
1231 | 1243 | self[name] = path(ui, name, rawloc=loc, suboptions=sub) |
|
1232 | 1244 | |
|
1233 | 1245 | def getpath(self, name, default=None): |
|
1234 | 1246 | """Return a ``path`` from a string, falling back to default. |
|
1235 | 1247 | |
|
1236 | 1248 | ``name`` can be a named path or locations. Locations are filesystem |
|
1237 | 1249 | paths or URIs. |
|
1238 | 1250 | |
|
1239 | 1251 | Returns None if ``name`` is not a registered path, a URI, or a local |
|
1240 | 1252 | path to a repo. |
|
1241 | 1253 | """ |
|
1242 | 1254 | # Only fall back to default if no path was requested. |
|
1243 | 1255 | if name is None: |
|
1244 | 1256 | if not default: |
|
1245 | 1257 | default = () |
|
1246 | 1258 | elif not isinstance(default, (tuple, list)): |
|
1247 | 1259 | default = (default,) |
|
1248 | 1260 | for k in default: |
|
1249 | 1261 | try: |
|
1250 | 1262 | return self[k] |
|
1251 | 1263 | except KeyError: |
|
1252 | 1264 | continue |
|
1253 | 1265 | return None |
|
1254 | 1266 | |
|
1255 | 1267 | # Most likely empty string. |
|
1256 | 1268 | # This may need to raise in the future. |
|
1257 | 1269 | if not name: |
|
1258 | 1270 | return None |
|
1259 | 1271 | |
|
1260 | 1272 | try: |
|
1261 | 1273 | return self[name] |
|
1262 | 1274 | except KeyError: |
|
1263 | 1275 | # Try to resolve as a local path or URI. |
|
1264 | 1276 | try: |
|
1265 | 1277 | # We don't pass sub-options in, so no need to pass ui instance. |
|
1266 | 1278 | return path(None, None, rawloc=name) |
|
1267 | 1279 | except ValueError: |
|
1268 | 1280 | raise error.RepoError(_('repository %s does not exist') % |
|
1269 | 1281 | name) |
|
1270 | 1282 | |
|
1271 | 1283 | _pathsuboptions = {} |
|
1272 | 1284 | |
|
1273 | 1285 | def pathsuboption(option, attr): |
|
1274 | 1286 | """Decorator used to declare a path sub-option. |
|
1275 | 1287 | |
|
1276 | 1288 | Arguments are the sub-option name and the attribute it should set on |
|
1277 | 1289 | ``path`` instances. |
|
1278 | 1290 | |
|
1279 | 1291 | The decorated function will receive as arguments a ``ui`` instance, |
|
1280 | 1292 | ``path`` instance, and the string value of this option from the config. |
|
1281 | 1293 | The function should return the value that will be set on the ``path`` |
|
1282 | 1294 | instance. |
|
1283 | 1295 | |
|
1284 | 1296 | This decorator can be used to perform additional verification of |
|
1285 | 1297 | sub-options and to change the type of sub-options. |
|
1286 | 1298 | """ |
|
1287 | 1299 | def register(func): |
|
1288 | 1300 | _pathsuboptions[option] = (attr, func) |
|
1289 | 1301 | return func |
|
1290 | 1302 | return register |
|
1291 | 1303 | |
|
1292 | 1304 | @pathsuboption('pushurl', 'pushloc') |
|
1293 | 1305 | def pushurlpathoption(ui, path, value): |
|
1294 | 1306 | u = util.url(value) |
|
1295 | 1307 | # Actually require a URL. |
|
1296 | 1308 | if not u.scheme: |
|
1297 | 1309 | ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name) |
|
1298 | 1310 | return None |
|
1299 | 1311 | |
|
1300 | 1312 | # Don't support the #foo syntax in the push URL to declare branch to |
|
1301 | 1313 | # push. |
|
1302 | 1314 | if u.fragment: |
|
1303 | 1315 | ui.warn(_('("#fragment" in paths.%s:pushurl not supported; ' |
|
1304 | 1316 | 'ignoring)\n') % path.name) |
|
1305 | 1317 | u.fragment = None |
|
1306 | 1318 | |
|
1307 | 1319 | return str(u) |
|
1308 | 1320 | |
|
1309 | 1321 | @pathsuboption('pushrev', 'pushrev') |
|
1310 | 1322 | def pushrevpathoption(ui, path, value): |
|
1311 | 1323 | return value |
|
1312 | 1324 | |
|
1313 | 1325 | class path(object): |
|
1314 | 1326 | """Represents an individual path and its configuration.""" |
|
1315 | 1327 | |
|
1316 | 1328 | def __init__(self, ui, name, rawloc=None, suboptions=None): |
|
1317 | 1329 | """Construct a path from its config options. |
|
1318 | 1330 | |
|
1319 | 1331 | ``ui`` is the ``ui`` instance the path is coming from. |
|
1320 | 1332 | ``name`` is the symbolic name of the path. |
|
1321 | 1333 | ``rawloc`` is the raw location, as defined in the config. |
|
1322 | 1334 | ``pushloc`` is the raw locations pushes should be made to. |
|
1323 | 1335 | |
|
1324 | 1336 | If ``name`` is not defined, we require that the location be a) a local |
|
1325 | 1337 | filesystem path with a .hg directory or b) a URL. If not, |
|
1326 | 1338 | ``ValueError`` is raised. |
|
1327 | 1339 | """ |
|
1328 | 1340 | if not rawloc: |
|
1329 | 1341 | raise ValueError('rawloc must be defined') |
|
1330 | 1342 | |
|
1331 | 1343 | # Locations may define branches via syntax <base>#<branch>. |
|
1332 | 1344 | u = util.url(rawloc) |
|
1333 | 1345 | branch = None |
|
1334 | 1346 | if u.fragment: |
|
1335 | 1347 | branch = u.fragment |
|
1336 | 1348 | u.fragment = None |
|
1337 | 1349 | |
|
1338 | 1350 | self.url = u |
|
1339 | 1351 | self.branch = branch |
|
1340 | 1352 | |
|
1341 | 1353 | self.name = name |
|
1342 | 1354 | self.rawloc = rawloc |
|
1343 | 1355 | self.loc = str(u) |
|
1344 | 1356 | |
|
1345 | 1357 | # When given a raw location but not a symbolic name, validate the |
|
1346 | 1358 | # location is valid. |
|
1347 | 1359 | if not name and not u.scheme and not self._isvalidlocalpath(self.loc): |
|
1348 | 1360 | raise ValueError('location is not a URL or path to a local ' |
|
1349 | 1361 | 'repo: %s' % rawloc) |
|
1350 | 1362 | |
|
1351 | 1363 | suboptions = suboptions or {} |
|
1352 | 1364 | |
|
1353 | 1365 | # Now process the sub-options. If a sub-option is registered, its |
|
1354 | 1366 | # attribute will always be present. The value will be None if there |
|
1355 | 1367 | # was no valid sub-option. |
|
1356 | 1368 | for suboption, (attr, func) in _pathsuboptions.iteritems(): |
|
1357 | 1369 | if suboption not in suboptions: |
|
1358 | 1370 | setattr(self, attr, None) |
|
1359 | 1371 | continue |
|
1360 | 1372 | |
|
1361 | 1373 | value = func(ui, self, suboptions[suboption]) |
|
1362 | 1374 | setattr(self, attr, value) |
|
1363 | 1375 | |
|
1364 | 1376 | def _isvalidlocalpath(self, path): |
|
1365 | 1377 | """Returns True if the given path is a potentially valid repository. |
|
1366 | 1378 | This is its own function so that extensions can change the definition of |
|
1367 | 1379 | 'valid' in this case (like when pulling from a git repo into a hg |
|
1368 | 1380 | one).""" |
|
1369 | 1381 | return os.path.isdir(os.path.join(path, '.hg')) |
|
1370 | 1382 | |
|
1371 | 1383 | @property |
|
1372 | 1384 | def suboptions(self): |
|
1373 | 1385 | """Return sub-options and their values for this path. |
|
1374 | 1386 | |
|
1375 | 1387 | This is intended to be used for presentation purposes. |
|
1376 | 1388 | """ |
|
1377 | 1389 | d = {} |
|
1378 | 1390 | for subopt, (attr, _func) in _pathsuboptions.iteritems(): |
|
1379 | 1391 | value = getattr(self, attr) |
|
1380 | 1392 | if value is not None: |
|
1381 | 1393 | d[subopt] = value |
|
1382 | 1394 | return d |
|
1383 | 1395 | |
|
1384 | 1396 | # we instantiate one globally shared progress bar to avoid |
|
1385 | 1397 | # competing progress bars when multiple UI objects get created |
|
1386 | 1398 | _progresssingleton = None |
|
1387 | 1399 | |
|
1388 | 1400 | def getprogbar(ui): |
|
1389 | 1401 | global _progresssingleton |
|
1390 | 1402 | if _progresssingleton is None: |
|
1391 | 1403 | # passing 'ui' object to the singleton is fishy, |
|
1392 | 1404 | # this is how the extension used to work but feel free to rework it. |
|
1393 | 1405 | _progresssingleton = progress.progbar(ui) |
|
1394 | 1406 | return _progresssingleton |
@@ -1,82 +1,82 | |||
|
1 | 1 | #!/usr/bin/env python |
|
2 | 2 | |
|
3 | 3 | """dummy SMTP server for use in tests""" |
|
4 | 4 | |
|
5 | 5 | from __future__ import absolute_import |
|
6 | 6 | |
|
7 | 7 | import asyncore |
|
8 | 8 | import optparse |
|
9 | 9 | import smtpd |
|
10 | 10 | import ssl |
|
11 | 11 | import sys |
|
12 | 12 | |
|
13 | 13 | from mercurial import ( |
|
14 | 14 | server, |
|
15 | 15 | sslutil, |
|
16 | 16 | ui as uimod, |
|
17 | 17 | ) |
|
18 | 18 | |
|
19 | 19 | def log(msg): |
|
20 | 20 | sys.stdout.write(msg) |
|
21 | 21 | sys.stdout.flush() |
|
22 | 22 | |
|
23 | 23 | class dummysmtpserver(smtpd.SMTPServer): |
|
24 | 24 | def __init__(self, localaddr): |
|
25 | 25 | smtpd.SMTPServer.__init__(self, localaddr, remoteaddr=None) |
|
26 | 26 | |
|
27 | 27 | def process_message(self, peer, mailfrom, rcpttos, data): |
|
28 | 28 | log('%s from=%s to=%s\n' % (peer[0], mailfrom, ', '.join(rcpttos))) |
|
29 | 29 | |
|
30 | 30 | class dummysmtpsecureserver(dummysmtpserver): |
|
31 | 31 | def __init__(self, localaddr, certfile): |
|
32 | 32 | dummysmtpserver.__init__(self, localaddr) |
|
33 | 33 | self._certfile = certfile |
|
34 | 34 | |
|
35 | 35 | def handle_accept(self): |
|
36 | 36 | pair = self.accept() |
|
37 | 37 | if not pair: |
|
38 | 38 | return |
|
39 | 39 | conn, addr = pair |
|
40 | ui = uimod.ui() | |
|
40 | ui = uimod.ui.load() | |
|
41 | 41 | try: |
|
42 | 42 | # wrap_socket() would block, but we don't care |
|
43 | 43 | conn = sslutil.wrapserversocket(conn, ui, certfile=self._certfile) |
|
44 | 44 | except ssl.SSLError: |
|
45 | 45 | log('%s ssl error\n' % addr[0]) |
|
46 | 46 | conn.close() |
|
47 | 47 | return |
|
48 | 48 | smtpd.SMTPChannel(self, conn, addr) |
|
49 | 49 | |
|
50 | 50 | def run(): |
|
51 | 51 | try: |
|
52 | 52 | asyncore.loop() |
|
53 | 53 | except KeyboardInterrupt: |
|
54 | 54 | pass |
|
55 | 55 | |
|
56 | 56 | def main(): |
|
57 | 57 | op = optparse.OptionParser() |
|
58 | 58 | op.add_option('-d', '--daemon', action='store_true') |
|
59 | 59 | op.add_option('--daemon-postexec', action='append') |
|
60 | 60 | op.add_option('-p', '--port', type=int, default=8025) |
|
61 | 61 | op.add_option('-a', '--address', default='localhost') |
|
62 | 62 | op.add_option('--pid-file', metavar='FILE') |
|
63 | 63 | op.add_option('--tls', choices=['none', 'smtps'], default='none') |
|
64 | 64 | op.add_option('--certificate', metavar='FILE') |
|
65 | 65 | |
|
66 | 66 | opts, args = op.parse_args() |
|
67 | 67 | if opts.tls == 'smtps' and not opts.certificate: |
|
68 | 68 | op.error('--certificate must be specified') |
|
69 | 69 | |
|
70 | 70 | addr = (opts.address, opts.port) |
|
71 | 71 | def init(): |
|
72 | 72 | if opts.tls == 'none': |
|
73 | 73 | dummysmtpserver(addr) |
|
74 | 74 | else: |
|
75 | 75 | dummysmtpsecureserver(addr, opts.certificate) |
|
76 | 76 | log('listening at %s:%d\n' % addr) |
|
77 | 77 | |
|
78 | 78 | server.runservice(vars(opts), initfn=init, runfn=run, |
|
79 | 79 | runargs=[sys.executable, __file__] + sys.argv[1:]) |
|
80 | 80 | |
|
81 | 81 | if __name__ == '__main__': |
|
82 | 82 | main() |
@@ -1,621 +1,621 | |||
|
1 | 1 | from __future__ import absolute_import |
|
2 | 2 | |
|
3 | 3 | import errno |
|
4 | 4 | import os |
|
5 | 5 | import re |
|
6 | 6 | import socket |
|
7 | 7 | import stat |
|
8 | 8 | import subprocess |
|
9 | 9 | import sys |
|
10 | 10 | import tempfile |
|
11 | 11 | |
|
12 | 12 | tempprefix = 'hg-hghave-' |
|
13 | 13 | |
|
14 | 14 | checks = { |
|
15 | 15 | "true": (lambda: True, "yak shaving"), |
|
16 | 16 | "false": (lambda: False, "nail clipper"), |
|
17 | 17 | } |
|
18 | 18 | |
|
19 | 19 | def check(name, desc): |
|
20 | 20 | """Registers a check function for a feature.""" |
|
21 | 21 | def decorator(func): |
|
22 | 22 | checks[name] = (func, desc) |
|
23 | 23 | return func |
|
24 | 24 | return decorator |
|
25 | 25 | |
|
26 | 26 | def checkvers(name, desc, vers): |
|
27 | 27 | """Registers a check function for each of a series of versions. |
|
28 | 28 | |
|
29 | 29 | vers can be a list or an iterator""" |
|
30 | 30 | def decorator(func): |
|
31 | 31 | def funcv(v): |
|
32 | 32 | def f(): |
|
33 | 33 | return func(v) |
|
34 | 34 | return f |
|
35 | 35 | for v in vers: |
|
36 | 36 | v = str(v) |
|
37 | 37 | f = funcv(v) |
|
38 | 38 | checks['%s%s' % (name, v.replace('.', ''))] = (f, desc % v) |
|
39 | 39 | return func |
|
40 | 40 | return decorator |
|
41 | 41 | |
|
42 | 42 | def checkfeatures(features): |
|
43 | 43 | result = { |
|
44 | 44 | 'error': [], |
|
45 | 45 | 'missing': [], |
|
46 | 46 | 'skipped': [], |
|
47 | 47 | } |
|
48 | 48 | |
|
49 | 49 | for feature in features: |
|
50 | 50 | negate = feature.startswith('no-') |
|
51 | 51 | if negate: |
|
52 | 52 | feature = feature[3:] |
|
53 | 53 | |
|
54 | 54 | if feature not in checks: |
|
55 | 55 | result['missing'].append(feature) |
|
56 | 56 | continue |
|
57 | 57 | |
|
58 | 58 | check, desc = checks[feature] |
|
59 | 59 | try: |
|
60 | 60 | available = check() |
|
61 | 61 | except Exception: |
|
62 | 62 | result['error'].append('hghave check failed: %s' % feature) |
|
63 | 63 | continue |
|
64 | 64 | |
|
65 | 65 | if not negate and not available: |
|
66 | 66 | result['skipped'].append('missing feature: %s' % desc) |
|
67 | 67 | elif negate and available: |
|
68 | 68 | result['skipped'].append('system supports %s' % desc) |
|
69 | 69 | |
|
70 | 70 | return result |
|
71 | 71 | |
|
72 | 72 | def require(features): |
|
73 | 73 | """Require that features are available, exiting if not.""" |
|
74 | 74 | result = checkfeatures(features) |
|
75 | 75 | |
|
76 | 76 | for missing in result['missing']: |
|
77 | 77 | sys.stderr.write('skipped: unknown feature: %s\n' % missing) |
|
78 | 78 | for msg in result['skipped']: |
|
79 | 79 | sys.stderr.write('skipped: %s\n' % msg) |
|
80 | 80 | for msg in result['error']: |
|
81 | 81 | sys.stderr.write('%s\n' % msg) |
|
82 | 82 | |
|
83 | 83 | if result['missing']: |
|
84 | 84 | sys.exit(2) |
|
85 | 85 | |
|
86 | 86 | if result['skipped'] or result['error']: |
|
87 | 87 | sys.exit(1) |
|
88 | 88 | |
|
89 | 89 | def matchoutput(cmd, regexp, ignorestatus=False): |
|
90 | 90 | """Return the match object if cmd executes successfully and its output |
|
91 | 91 | is matched by the supplied regular expression. |
|
92 | 92 | """ |
|
93 | 93 | r = re.compile(regexp) |
|
94 | 94 | try: |
|
95 | 95 | p = subprocess.Popen( |
|
96 | 96 | cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) |
|
97 | 97 | except OSError as e: |
|
98 | 98 | if e.errno != errno.ENOENT: |
|
99 | 99 | raise |
|
100 | 100 | ret = -1 |
|
101 | 101 | ret = p.wait() |
|
102 | 102 | s = p.stdout.read() |
|
103 | 103 | return (ignorestatus or not ret) and r.search(s) |
|
104 | 104 | |
|
105 | 105 | @check("baz", "GNU Arch baz client") |
|
106 | 106 | def has_baz(): |
|
107 | 107 | return matchoutput('baz --version 2>&1', br'baz Bazaar version') |
|
108 | 108 | |
|
109 | 109 | @check("bzr", "Canonical's Bazaar client") |
|
110 | 110 | def has_bzr(): |
|
111 | 111 | try: |
|
112 | 112 | import bzrlib |
|
113 | 113 | import bzrlib.bzrdir |
|
114 | 114 | import bzrlib.errors |
|
115 | 115 | import bzrlib.revision |
|
116 | 116 | import bzrlib.revisionspec |
|
117 | 117 | bzrlib.revisionspec.RevisionSpec |
|
118 | 118 | return bzrlib.__doc__ is not None |
|
119 | 119 | except (AttributeError, ImportError): |
|
120 | 120 | return False |
|
121 | 121 | |
|
122 | 122 | @checkvers("bzr", "Canonical's Bazaar client >= %s", (1.14,)) |
|
123 | 123 | def has_bzr_range(v): |
|
124 | 124 | major, minor = v.split('.')[0:2] |
|
125 | 125 | try: |
|
126 | 126 | import bzrlib |
|
127 | 127 | return (bzrlib.__doc__ is not None |
|
128 | 128 | and bzrlib.version_info[:2] >= (int(major), int(minor))) |
|
129 | 129 | except ImportError: |
|
130 | 130 | return False |
|
131 | 131 | |
|
132 | 132 | @check("chg", "running with chg") |
|
133 | 133 | def has_chg(): |
|
134 | 134 | return 'CHGHG' in os.environ |
|
135 | 135 | |
|
136 | 136 | @check("cvs", "cvs client/server") |
|
137 | 137 | def has_cvs(): |
|
138 | 138 | re = br'Concurrent Versions System.*?server' |
|
139 | 139 | return matchoutput('cvs --version 2>&1', re) and not has_msys() |
|
140 | 140 | |
|
141 | 141 | @check("cvs112", "cvs client/server 1.12.* (not cvsnt)") |
|
142 | 142 | def has_cvs112(): |
|
143 | 143 | re = br'Concurrent Versions System \(CVS\) 1.12.*?server' |
|
144 | 144 | return matchoutput('cvs --version 2>&1', re) and not has_msys() |
|
145 | 145 | |
|
146 | 146 | @check("cvsnt", "cvsnt client/server") |
|
147 | 147 | def has_cvsnt(): |
|
148 | 148 | re = br'Concurrent Versions System \(CVSNT\) (\d+).(\d+).*\(client/server\)' |
|
149 | 149 | return matchoutput('cvsnt --version 2>&1', re) |
|
150 | 150 | |
|
151 | 151 | @check("darcs", "darcs client") |
|
152 | 152 | def has_darcs(): |
|
153 | 153 | return matchoutput('darcs --version', br'\b2\.([2-9]|\d{2})', True) |
|
154 | 154 | |
|
155 | 155 | @check("mtn", "monotone client (>= 1.0)") |
|
156 | 156 | def has_mtn(): |
|
157 | 157 | return matchoutput('mtn --version', br'monotone', True) and not matchoutput( |
|
158 | 158 | 'mtn --version', br'monotone 0\.', True) |
|
159 | 159 | |
|
160 | 160 | @check("eol-in-paths", "end-of-lines in paths") |
|
161 | 161 | def has_eol_in_paths(): |
|
162 | 162 | try: |
|
163 | 163 | fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r') |
|
164 | 164 | os.close(fd) |
|
165 | 165 | os.remove(path) |
|
166 | 166 | return True |
|
167 | 167 | except (IOError, OSError): |
|
168 | 168 | return False |
|
169 | 169 | |
|
170 | 170 | @check("execbit", "executable bit") |
|
171 | 171 | def has_executablebit(): |
|
172 | 172 | try: |
|
173 | 173 | EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH |
|
174 | 174 | fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix) |
|
175 | 175 | try: |
|
176 | 176 | os.close(fh) |
|
177 | 177 | m = os.stat(fn).st_mode & 0o777 |
|
178 | 178 | new_file_has_exec = m & EXECFLAGS |
|
179 | 179 | os.chmod(fn, m ^ EXECFLAGS) |
|
180 | 180 | exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0o777) == m) |
|
181 | 181 | finally: |
|
182 | 182 | os.unlink(fn) |
|
183 | 183 | except (IOError, OSError): |
|
184 | 184 | # we don't care, the user probably won't be able to commit anyway |
|
185 | 185 | return False |
|
186 | 186 | return not (new_file_has_exec or exec_flags_cannot_flip) |
|
187 | 187 | |
|
188 | 188 | @check("icasefs", "case insensitive file system") |
|
189 | 189 | def has_icasefs(): |
|
190 | 190 | # Stolen from mercurial.util |
|
191 | 191 | fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix) |
|
192 | 192 | os.close(fd) |
|
193 | 193 | try: |
|
194 | 194 | s1 = os.stat(path) |
|
195 | 195 | d, b = os.path.split(path) |
|
196 | 196 | p2 = os.path.join(d, b.upper()) |
|
197 | 197 | if path == p2: |
|
198 | 198 | p2 = os.path.join(d, b.lower()) |
|
199 | 199 | try: |
|
200 | 200 | s2 = os.stat(p2) |
|
201 | 201 | return s2 == s1 |
|
202 | 202 | except OSError: |
|
203 | 203 | return False |
|
204 | 204 | finally: |
|
205 | 205 | os.remove(path) |
|
206 | 206 | |
|
207 | 207 | @check("fifo", "named pipes") |
|
208 | 208 | def has_fifo(): |
|
209 | 209 | if getattr(os, "mkfifo", None) is None: |
|
210 | 210 | return False |
|
211 | 211 | name = tempfile.mktemp(dir='.', prefix=tempprefix) |
|
212 | 212 | try: |
|
213 | 213 | os.mkfifo(name) |
|
214 | 214 | os.unlink(name) |
|
215 | 215 | return True |
|
216 | 216 | except OSError: |
|
217 | 217 | return False |
|
218 | 218 | |
|
219 | 219 | @check("killdaemons", 'killdaemons.py support') |
|
220 | 220 | def has_killdaemons(): |
|
221 | 221 | return True |
|
222 | 222 | |
|
223 | 223 | @check("cacheable", "cacheable filesystem") |
|
224 | 224 | def has_cacheable_fs(): |
|
225 | 225 | from mercurial import util |
|
226 | 226 | |
|
227 | 227 | fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix) |
|
228 | 228 | os.close(fd) |
|
229 | 229 | try: |
|
230 | 230 | return util.cachestat(path).cacheable() |
|
231 | 231 | finally: |
|
232 | 232 | os.remove(path) |
|
233 | 233 | |
|
234 | 234 | @check("lsprof", "python lsprof module") |
|
235 | 235 | def has_lsprof(): |
|
236 | 236 | try: |
|
237 | 237 | import _lsprof |
|
238 | 238 | _lsprof.Profiler # silence unused import warning |
|
239 | 239 | return True |
|
240 | 240 | except ImportError: |
|
241 | 241 | return False |
|
242 | 242 | |
|
243 | 243 | def gethgversion(): |
|
244 | 244 | m = matchoutput('hg --version --quiet 2>&1', br'(\d+)\.(\d+)') |
|
245 | 245 | if not m: |
|
246 | 246 | return (0, 0) |
|
247 | 247 | return (int(m.group(1)), int(m.group(2))) |
|
248 | 248 | |
|
249 | 249 | @checkvers("hg", "Mercurial >= %s", |
|
250 | 250 | list([(1.0 * x) / 10 for x in range(9, 40)])) |
|
251 | 251 | def has_hg_range(v): |
|
252 | 252 | major, minor = v.split('.')[0:2] |
|
253 | 253 | return gethgversion() >= (int(major), int(minor)) |
|
254 | 254 | |
|
255 | 255 | @check("hg08", "Mercurial >= 0.8") |
|
256 | 256 | def has_hg08(): |
|
257 | 257 | if checks["hg09"][0](): |
|
258 | 258 | return True |
|
259 | 259 | return matchoutput('hg help annotate 2>&1', '--date') |
|
260 | 260 | |
|
261 | 261 | @check("hg07", "Mercurial >= 0.7") |
|
262 | 262 | def has_hg07(): |
|
263 | 263 | if checks["hg08"][0](): |
|
264 | 264 | return True |
|
265 | 265 | return matchoutput('hg --version --quiet 2>&1', 'Mercurial Distributed SCM') |
|
266 | 266 | |
|
267 | 267 | @check("hg06", "Mercurial >= 0.6") |
|
268 | 268 | def has_hg06(): |
|
269 | 269 | if checks["hg07"][0](): |
|
270 | 270 | return True |
|
271 | 271 | return matchoutput('hg --version --quiet 2>&1', 'Mercurial version') |
|
272 | 272 | |
|
273 | 273 | @check("gettext", "GNU Gettext (msgfmt)") |
|
274 | 274 | def has_gettext(): |
|
275 | 275 | return matchoutput('msgfmt --version', br'GNU gettext-tools') |
|
276 | 276 | |
|
277 | 277 | @check("git", "git command line client") |
|
278 | 278 | def has_git(): |
|
279 | 279 | return matchoutput('git --version 2>&1', br'^git version') |
|
280 | 280 | |
|
281 | 281 | @check("docutils", "Docutils text processing library") |
|
282 | 282 | def has_docutils(): |
|
283 | 283 | try: |
|
284 | 284 | import docutils.core |
|
285 | 285 | docutils.core.publish_cmdline # silence unused import |
|
286 | 286 | return True |
|
287 | 287 | except ImportError: |
|
288 | 288 | return False |
|
289 | 289 | |
|
290 | 290 | def getsvnversion(): |
|
291 | 291 | m = matchoutput('svn --version --quiet 2>&1', br'^(\d+)\.(\d+)') |
|
292 | 292 | if not m: |
|
293 | 293 | return (0, 0) |
|
294 | 294 | return (int(m.group(1)), int(m.group(2))) |
|
295 | 295 | |
|
296 | 296 | @checkvers("svn", "subversion client and admin tools >= %s", (1.3, 1.5)) |
|
297 | 297 | def has_svn_range(v): |
|
298 | 298 | major, minor = v.split('.')[0:2] |
|
299 | 299 | return getsvnversion() >= (int(major), int(minor)) |
|
300 | 300 | |
|
301 | 301 | @check("svn", "subversion client and admin tools") |
|
302 | 302 | def has_svn(): |
|
303 | 303 | return matchoutput('svn --version 2>&1', br'^svn, version') and \ |
|
304 | 304 | matchoutput('svnadmin --version 2>&1', br'^svnadmin, version') |
|
305 | 305 | |
|
306 | 306 | @check("svn-bindings", "subversion python bindings") |
|
307 | 307 | def has_svn_bindings(): |
|
308 | 308 | try: |
|
309 | 309 | import svn.core |
|
310 | 310 | version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR |
|
311 | 311 | if version < (1, 4): |
|
312 | 312 | return False |
|
313 | 313 | return True |
|
314 | 314 | except ImportError: |
|
315 | 315 | return False |
|
316 | 316 | |
|
317 | 317 | @check("p4", "Perforce server and client") |
|
318 | 318 | def has_p4(): |
|
319 | 319 | return (matchoutput('p4 -V', br'Rev\. P4/') and |
|
320 | 320 | matchoutput('p4d -V', br'Rev\. P4D/')) |
|
321 | 321 | |
|
322 | 322 | @check("symlink", "symbolic links") |
|
323 | 323 | def has_symlink(): |
|
324 | 324 | if getattr(os, "symlink", None) is None: |
|
325 | 325 | return False |
|
326 | 326 | name = tempfile.mktemp(dir='.', prefix=tempprefix) |
|
327 | 327 | try: |
|
328 | 328 | os.symlink(".", name) |
|
329 | 329 | os.unlink(name) |
|
330 | 330 | return True |
|
331 | 331 | except (OSError, AttributeError): |
|
332 | 332 | return False |
|
333 | 333 | |
|
334 | 334 | @check("hardlink", "hardlinks") |
|
335 | 335 | def has_hardlink(): |
|
336 | 336 | from mercurial import util |
|
337 | 337 | fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix) |
|
338 | 338 | os.close(fh) |
|
339 | 339 | name = tempfile.mktemp(dir='.', prefix=tempprefix) |
|
340 | 340 | try: |
|
341 | 341 | util.oslink(fn, name) |
|
342 | 342 | os.unlink(name) |
|
343 | 343 | return True |
|
344 | 344 | except OSError: |
|
345 | 345 | return False |
|
346 | 346 | finally: |
|
347 | 347 | os.unlink(fn) |
|
348 | 348 | |
|
349 | 349 | @check("rmcwd", "can remove current working directory") |
|
350 | 350 | def has_rmcwd(): |
|
351 | 351 | ocwd = os.getcwd() |
|
352 | 352 | temp = tempfile.mkdtemp(dir='.', prefix=tempprefix) |
|
353 | 353 | try: |
|
354 | 354 | os.chdir(temp) |
|
355 | 355 | # On Linux, 'rmdir .' isn't allowed, but the other names are okay. |
|
356 | 356 | # On Solaris and Windows, the cwd can't be removed by any names. |
|
357 | 357 | os.rmdir(os.getcwd()) |
|
358 | 358 | return True |
|
359 | 359 | except OSError: |
|
360 | 360 | return False |
|
361 | 361 | finally: |
|
362 | 362 | os.chdir(ocwd) |
|
363 | 363 | # clean up temp dir on platforms where cwd can't be removed |
|
364 | 364 | try: |
|
365 | 365 | os.rmdir(temp) |
|
366 | 366 | except OSError: |
|
367 | 367 | pass |
|
368 | 368 | |
|
369 | 369 | @check("tla", "GNU Arch tla client") |
|
370 | 370 | def has_tla(): |
|
371 | 371 | return matchoutput('tla --version 2>&1', br'The GNU Arch Revision') |
|
372 | 372 | |
|
373 | 373 | @check("gpg", "gpg client") |
|
374 | 374 | def has_gpg(): |
|
375 | 375 | return matchoutput('gpg --version 2>&1', br'GnuPG') |
|
376 | 376 | |
|
377 | 377 | @check("gpg2", "gpg client v2") |
|
378 | 378 | def has_gpg2(): |
|
379 | 379 | return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.') |
|
380 | 380 | |
|
381 | 381 | @check("gpg21", "gpg client v2.1+") |
|
382 | 382 | def has_gpg21(): |
|
383 | 383 | return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.(?!0)') |
|
384 | 384 | |
|
385 | 385 | @check("unix-permissions", "unix-style permissions") |
|
386 | 386 | def has_unix_permissions(): |
|
387 | 387 | d = tempfile.mkdtemp(dir='.', prefix=tempprefix) |
|
388 | 388 | try: |
|
389 | 389 | fname = os.path.join(d, 'foo') |
|
390 | 390 | for umask in (0o77, 0o07, 0o22): |
|
391 | 391 | os.umask(umask) |
|
392 | 392 | f = open(fname, 'w') |
|
393 | 393 | f.close() |
|
394 | 394 | mode = os.stat(fname).st_mode |
|
395 | 395 | os.unlink(fname) |
|
396 | 396 | if mode & 0o777 != ~umask & 0o666: |
|
397 | 397 | return False |
|
398 | 398 | return True |
|
399 | 399 | finally: |
|
400 | 400 | os.rmdir(d) |
|
401 | 401 | |
|
402 | 402 | @check("unix-socket", "AF_UNIX socket family") |
|
403 | 403 | def has_unix_socket(): |
|
404 | 404 | return getattr(socket, 'AF_UNIX', None) is not None |
|
405 | 405 | |
|
406 | 406 | @check("root", "root permissions") |
|
407 | 407 | def has_root(): |
|
408 | 408 | return getattr(os, 'geteuid', None) and os.geteuid() == 0 |
|
409 | 409 | |
|
410 | 410 | @check("pyflakes", "Pyflakes python linter") |
|
411 | 411 | def has_pyflakes(): |
|
412 | 412 | return matchoutput("sh -c \"echo 'import re' 2>&1 | pyflakes\"", |
|
413 | 413 | br"<stdin>:1: 're' imported but unused", |
|
414 | 414 | True) |
|
415 | 415 | |
|
416 | 416 | @check("pygments", "Pygments source highlighting library") |
|
417 | 417 | def has_pygments(): |
|
418 | 418 | try: |
|
419 | 419 | import pygments |
|
420 | 420 | pygments.highlight # silence unused import warning |
|
421 | 421 | return True |
|
422 | 422 | except ImportError: |
|
423 | 423 | return False |
|
424 | 424 | |
|
425 | 425 | @check("outer-repo", "outer repo") |
|
426 | 426 | def has_outer_repo(): |
|
427 | 427 | # failing for other reasons than 'no repo' imply that there is a repo |
|
428 | 428 | return not matchoutput('hg root 2>&1', |
|
429 | 429 | br'abort: no repository found', True) |
|
430 | 430 | |
|
431 | 431 | @check("ssl", "ssl module available") |
|
432 | 432 | def has_ssl(): |
|
433 | 433 | try: |
|
434 | 434 | import ssl |
|
435 | 435 | ssl.CERT_NONE |
|
436 | 436 | return True |
|
437 | 437 | except ImportError: |
|
438 | 438 | return False |
|
439 | 439 | |
|
440 | 440 | @check("sslcontext", "python >= 2.7.9 ssl") |
|
441 | 441 | def has_sslcontext(): |
|
442 | 442 | try: |
|
443 | 443 | import ssl |
|
444 | 444 | ssl.SSLContext |
|
445 | 445 | return True |
|
446 | 446 | except (ImportError, AttributeError): |
|
447 | 447 | return False |
|
448 | 448 | |
|
449 | 449 | @check("defaultcacerts", "can verify SSL certs by system's CA certs store") |
|
450 | 450 | def has_defaultcacerts(): |
|
451 | 451 | from mercurial import sslutil, ui as uimod |
|
452 | ui = uimod.ui() | |
|
452 | ui = uimod.ui.load() | |
|
453 | 453 | return sslutil._defaultcacerts(ui) or sslutil._canloaddefaultcerts |
|
454 | 454 | |
|
455 | 455 | @check("defaultcacertsloaded", "detected presence of loaded system CA certs") |
|
456 | 456 | def has_defaultcacertsloaded(): |
|
457 | 457 | import ssl |
|
458 | 458 | from mercurial import sslutil, ui as uimod |
|
459 | 459 | |
|
460 | 460 | if not has_defaultcacerts(): |
|
461 | 461 | return False |
|
462 | 462 | if not has_sslcontext(): |
|
463 | 463 | return False |
|
464 | 464 | |
|
465 | ui = uimod.ui() | |
|
465 | ui = uimod.ui.load() | |
|
466 | 466 | cafile = sslutil._defaultcacerts(ui) |
|
467 | 467 | ctx = ssl.create_default_context() |
|
468 | 468 | if cafile: |
|
469 | 469 | ctx.load_verify_locations(cafile=cafile) |
|
470 | 470 | else: |
|
471 | 471 | ctx.load_default_certs() |
|
472 | 472 | |
|
473 | 473 | return len(ctx.get_ca_certs()) > 0 |
|
474 | 474 | |
|
475 | 475 | @check("tls1.2", "TLS 1.2 protocol support") |
|
476 | 476 | def has_tls1_2(): |
|
477 | 477 | from mercurial import sslutil |
|
478 | 478 | return 'tls1.2' in sslutil.supportedprotocols |
|
479 | 479 | |
|
480 | 480 | @check("windows", "Windows") |
|
481 | 481 | def has_windows(): |
|
482 | 482 | return os.name == 'nt' |
|
483 | 483 | |
|
484 | 484 | @check("system-sh", "system() uses sh") |
|
485 | 485 | def has_system_sh(): |
|
486 | 486 | return os.name != 'nt' |
|
487 | 487 | |
|
488 | 488 | @check("serve", "platform and python can manage 'hg serve -d'") |
|
489 | 489 | def has_serve(): |
|
490 | 490 | return os.name != 'nt' # gross approximation |
|
491 | 491 | |
|
492 | 492 | @check("test-repo", "running tests from repository") |
|
493 | 493 | def has_test_repo(): |
|
494 | 494 | t = os.environ["TESTDIR"] |
|
495 | 495 | return os.path.isdir(os.path.join(t, "..", ".hg")) |
|
496 | 496 | |
|
497 | 497 | @check("tic", "terminfo compiler and curses module") |
|
498 | 498 | def has_tic(): |
|
499 | 499 | try: |
|
500 | 500 | import curses |
|
501 | 501 | curses.COLOR_BLUE |
|
502 | 502 | return matchoutput('test -x "`which tic`"', br'') |
|
503 | 503 | except ImportError: |
|
504 | 504 | return False |
|
505 | 505 | |
|
506 | 506 | @check("msys", "Windows with MSYS") |
|
507 | 507 | def has_msys(): |
|
508 | 508 | return os.getenv('MSYSTEM') |
|
509 | 509 | |
|
510 | 510 | @check("aix", "AIX") |
|
511 | 511 | def has_aix(): |
|
512 | 512 | return sys.platform.startswith("aix") |
|
513 | 513 | |
|
514 | 514 | @check("osx", "OS X") |
|
515 | 515 | def has_osx(): |
|
516 | 516 | return sys.platform == 'darwin' |
|
517 | 517 | |
|
518 | 518 | @check("osxpackaging", "OS X packaging tools") |
|
519 | 519 | def has_osxpackaging(): |
|
520 | 520 | try: |
|
521 | 521 | return (matchoutput('pkgbuild', br'Usage: pkgbuild ', ignorestatus=1) |
|
522 | 522 | and matchoutput( |
|
523 | 523 | 'productbuild', br'Usage: productbuild ', |
|
524 | 524 | ignorestatus=1) |
|
525 | 525 | and matchoutput('lsbom', br'Usage: lsbom', ignorestatus=1) |
|
526 | 526 | and matchoutput( |
|
527 | 527 | 'xar --help', br'Usage: xar', ignorestatus=1)) |
|
528 | 528 | except ImportError: |
|
529 | 529 | return False |
|
530 | 530 | |
|
531 | 531 | @check("docker", "docker support") |
|
532 | 532 | def has_docker(): |
|
533 | 533 | pat = br'A self-sufficient runtime for' |
|
534 | 534 | if matchoutput('docker --help', pat): |
|
535 | 535 | if 'linux' not in sys.platform: |
|
536 | 536 | # TODO: in theory we should be able to test docker-based |
|
537 | 537 | # package creation on non-linux using boot2docker, but in |
|
538 | 538 | # practice that requires extra coordination to make sure |
|
539 | 539 | # $TESTTEMP is going to be visible at the same path to the |
|
540 | 540 | # boot2docker VM. If we figure out how to verify that, we |
|
541 | 541 | # can use the following instead of just saying False: |
|
542 | 542 | # return 'DOCKER_HOST' in os.environ |
|
543 | 543 | return False |
|
544 | 544 | |
|
545 | 545 | return True |
|
546 | 546 | return False |
|
547 | 547 | |
|
548 | 548 | @check("debhelper", "debian packaging tools") |
|
549 | 549 | def has_debhelper(): |
|
550 | 550 | dpkg = matchoutput('dpkg --version', |
|
551 | 551 | br"Debian `dpkg' package management program") |
|
552 | 552 | dh = matchoutput('dh --help', |
|
553 | 553 | br'dh is a part of debhelper.', ignorestatus=True) |
|
554 | 554 | dh_py2 = matchoutput('dh_python2 --help', |
|
555 | 555 | br'other supported Python versions') |
|
556 | 556 | return dpkg and dh and dh_py2 |
|
557 | 557 | |
|
558 | 558 | @check("demandimport", "demandimport enabled") |
|
559 | 559 | def has_demandimport(): |
|
560 | 560 | return os.environ.get('HGDEMANDIMPORT') != 'disable' |
|
561 | 561 | |
|
562 | 562 | @check("absimport", "absolute_import in __future__") |
|
563 | 563 | def has_absimport(): |
|
564 | 564 | import __future__ |
|
565 | 565 | from mercurial import util |
|
566 | 566 | return util.safehasattr(__future__, "absolute_import") |
|
567 | 567 | |
|
568 | 568 | @check("py27+", "running with Python 2.7+") |
|
569 | 569 | def has_python27ornewer(): |
|
570 | 570 | return sys.version_info[0:2] >= (2, 7) |
|
571 | 571 | |
|
572 | 572 | @check("py3k", "running with Python 3.x") |
|
573 | 573 | def has_py3k(): |
|
574 | 574 | return 3 == sys.version_info[0] |
|
575 | 575 | |
|
576 | 576 | @check("py3exe", "a Python 3.x interpreter is available") |
|
577 | 577 | def has_python3exe(): |
|
578 | 578 | return 'PYTHON3' in os.environ |
|
579 | 579 | |
|
580 | 580 | @check("py3pygments", "Pygments available on Python 3.x") |
|
581 | 581 | def has_py3pygments(): |
|
582 | 582 | if has_py3k(): |
|
583 | 583 | return has_pygments() |
|
584 | 584 | elif has_python3exe(): |
|
585 | 585 | # just check exit status (ignoring output) |
|
586 | 586 | py3 = os.environ['PYTHON3'] |
|
587 | 587 | return matchoutput('%s -c "import pygments"' % py3, br'') |
|
588 | 588 | return False |
|
589 | 589 | |
|
590 | 590 | @check("pure", "running with pure Python code") |
|
591 | 591 | def has_pure(): |
|
592 | 592 | return any([ |
|
593 | 593 | os.environ.get("HGMODULEPOLICY") == "py", |
|
594 | 594 | os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure", |
|
595 | 595 | ]) |
|
596 | 596 | |
|
597 | 597 | @check("slow", "allow slow tests") |
|
598 | 598 | def has_slow(): |
|
599 | 599 | return os.environ.get('HGTEST_SLOW') == 'slow' |
|
600 | 600 | |
|
601 | 601 | @check("hypothesis", "Hypothesis automated test generation") |
|
602 | 602 | def has_hypothesis(): |
|
603 | 603 | try: |
|
604 | 604 | import hypothesis |
|
605 | 605 | hypothesis.given |
|
606 | 606 | return True |
|
607 | 607 | except ImportError: |
|
608 | 608 | return False |
|
609 | 609 | |
|
610 | 610 | @check("unziplinks", "unzip(1) understands and extracts symlinks") |
|
611 | 611 | def unzip_understands_symlinks(): |
|
612 | 612 | return matchoutput('unzip --help', br'Info-ZIP') |
|
613 | 613 | |
|
614 | 614 | @check("zstd", "zstd Python module available") |
|
615 | 615 | def has_zstd(): |
|
616 | 616 | try: |
|
617 | 617 | import mercurial.zstd |
|
618 | 618 | mercurial.zstd.__version__ |
|
619 | 619 | return True |
|
620 | 620 | except ImportError: |
|
621 | 621 | return False |
@@ -1,262 +1,262 | |||
|
1 | 1 | from __future__ import absolute_import, print_function |
|
2 | 2 | |
|
3 | 3 | import binascii |
|
4 | 4 | import getopt |
|
5 | 5 | import math |
|
6 | 6 | import os |
|
7 | 7 | import random |
|
8 | 8 | import sys |
|
9 | 9 | import time |
|
10 | 10 | |
|
11 | 11 | from mercurial.node import nullrev |
|
12 | 12 | from mercurial import ( |
|
13 | 13 | ancestor, |
|
14 | 14 | debugcommands, |
|
15 | 15 | hg, |
|
16 | 16 | ui as uimod, |
|
17 | 17 | util, |
|
18 | 18 | ) |
|
19 | 19 | |
|
20 | 20 | def buildgraph(rng, nodes=100, rootprob=0.05, mergeprob=0.2, prevprob=0.7): |
|
21 | 21 | '''nodes: total number of nodes in the graph |
|
22 | 22 | rootprob: probability that a new node (not 0) will be a root |
|
23 | 23 | mergeprob: probability that, excluding a root a node will be a merge |
|
24 | 24 | prevprob: probability that p1 will be the previous node |
|
25 | 25 | |
|
26 | 26 | return value is a graph represented as an adjacency list. |
|
27 | 27 | ''' |
|
28 | 28 | graph = [None] * nodes |
|
29 | 29 | for i in xrange(nodes): |
|
30 | 30 | if i == 0 or rng.random() < rootprob: |
|
31 | 31 | graph[i] = [nullrev] |
|
32 | 32 | elif i == 1: |
|
33 | 33 | graph[i] = [0] |
|
34 | 34 | elif rng.random() < mergeprob: |
|
35 | 35 | if i == 2 or rng.random() < prevprob: |
|
36 | 36 | # p1 is prev |
|
37 | 37 | p1 = i - 1 |
|
38 | 38 | else: |
|
39 | 39 | p1 = rng.randrange(i - 1) |
|
40 | 40 | p2 = rng.choice(range(0, p1) + range(p1 + 1, i)) |
|
41 | 41 | graph[i] = [p1, p2] |
|
42 | 42 | elif rng.random() < prevprob: |
|
43 | 43 | graph[i] = [i - 1] |
|
44 | 44 | else: |
|
45 | 45 | graph[i] = [rng.randrange(i - 1)] |
|
46 | 46 | |
|
47 | 47 | return graph |
|
48 | 48 | |
|
49 | 49 | def buildancestorsets(graph): |
|
50 | 50 | ancs = [None] * len(graph) |
|
51 | 51 | for i in xrange(len(graph)): |
|
52 | 52 | ancs[i] = set([i]) |
|
53 | 53 | if graph[i] == [nullrev]: |
|
54 | 54 | continue |
|
55 | 55 | for p in graph[i]: |
|
56 | 56 | ancs[i].update(ancs[p]) |
|
57 | 57 | return ancs |
|
58 | 58 | |
|
59 | 59 | class naiveincrementalmissingancestors(object): |
|
60 | 60 | def __init__(self, ancs, bases): |
|
61 | 61 | self.ancs = ancs |
|
62 | 62 | self.bases = set(bases) |
|
63 | 63 | def addbases(self, newbases): |
|
64 | 64 | self.bases.update(newbases) |
|
65 | 65 | def removeancestorsfrom(self, revs): |
|
66 | 66 | for base in self.bases: |
|
67 | 67 | if base != nullrev: |
|
68 | 68 | revs.difference_update(self.ancs[base]) |
|
69 | 69 | revs.discard(nullrev) |
|
70 | 70 | def missingancestors(self, revs): |
|
71 | 71 | res = set() |
|
72 | 72 | for rev in revs: |
|
73 | 73 | if rev != nullrev: |
|
74 | 74 | res.update(self.ancs[rev]) |
|
75 | 75 | for base in self.bases: |
|
76 | 76 | if base != nullrev: |
|
77 | 77 | res.difference_update(self.ancs[base]) |
|
78 | 78 | return sorted(res) |
|
79 | 79 | |
|
80 | 80 | def test_missingancestors(seed, rng): |
|
81 | 81 | # empirically observed to take around 1 second |
|
82 | 82 | graphcount = 100 |
|
83 | 83 | testcount = 10 |
|
84 | 84 | inccount = 10 |
|
85 | 85 | nerrs = [0] |
|
86 | 86 | # the default mu and sigma give us a nice distribution of mostly |
|
87 | 87 | # single-digit counts (including 0) with some higher ones |
|
88 | 88 | def lognormrandom(mu, sigma): |
|
89 | 89 | return int(math.floor(rng.lognormvariate(mu, sigma))) |
|
90 | 90 | |
|
91 | 91 | def samplerevs(nodes, mu=1.1, sigma=0.8): |
|
92 | 92 | count = min(lognormrandom(mu, sigma), len(nodes)) |
|
93 | 93 | return rng.sample(nodes, count) |
|
94 | 94 | |
|
95 | 95 | def err(seed, graph, bases, seq, output, expected): |
|
96 | 96 | if nerrs[0] == 0: |
|
97 | 97 | print('seed:', hex(seed)[:-1], file=sys.stderr) |
|
98 | 98 | if gerrs[0] == 0: |
|
99 | 99 | print('graph:', graph, file=sys.stderr) |
|
100 | 100 | print('* bases:', bases, file=sys.stderr) |
|
101 | 101 | print('* seq: ', seq, file=sys.stderr) |
|
102 | 102 | print('* output: ', output, file=sys.stderr) |
|
103 | 103 | print('* expected:', expected, file=sys.stderr) |
|
104 | 104 | nerrs[0] += 1 |
|
105 | 105 | gerrs[0] += 1 |
|
106 | 106 | |
|
107 | 107 | for g in xrange(graphcount): |
|
108 | 108 | graph = buildgraph(rng) |
|
109 | 109 | ancs = buildancestorsets(graph) |
|
110 | 110 | gerrs = [0] |
|
111 | 111 | for _ in xrange(testcount): |
|
112 | 112 | # start from nullrev to include it as a possibility |
|
113 | 113 | graphnodes = range(nullrev, len(graph)) |
|
114 | 114 | bases = samplerevs(graphnodes) |
|
115 | 115 | |
|
116 | 116 | # fast algorithm |
|
117 | 117 | inc = ancestor.incrementalmissingancestors(graph.__getitem__, bases) |
|
118 | 118 | # reference slow algorithm |
|
119 | 119 | naiveinc = naiveincrementalmissingancestors(ancs, bases) |
|
120 | 120 | seq = [] |
|
121 | 121 | revs = [] |
|
122 | 122 | for _ in xrange(inccount): |
|
123 | 123 | if rng.random() < 0.2: |
|
124 | 124 | newbases = samplerevs(graphnodes) |
|
125 | 125 | seq.append(('addbases', newbases)) |
|
126 | 126 | inc.addbases(newbases) |
|
127 | 127 | naiveinc.addbases(newbases) |
|
128 | 128 | if rng.random() < 0.4: |
|
129 | 129 | # larger set so that there are more revs to remove from |
|
130 | 130 | revs = samplerevs(graphnodes, mu=1.5) |
|
131 | 131 | seq.append(('removeancestorsfrom', revs)) |
|
132 | 132 | hrevs = set(revs) |
|
133 | 133 | rrevs = set(revs) |
|
134 | 134 | inc.removeancestorsfrom(hrevs) |
|
135 | 135 | naiveinc.removeancestorsfrom(rrevs) |
|
136 | 136 | if hrevs != rrevs: |
|
137 | 137 | err(seed, graph, bases, seq, sorted(hrevs), |
|
138 | 138 | sorted(rrevs)) |
|
139 | 139 | else: |
|
140 | 140 | revs = samplerevs(graphnodes) |
|
141 | 141 | seq.append(('missingancestors', revs)) |
|
142 | 142 | h = inc.missingancestors(revs) |
|
143 | 143 | r = naiveinc.missingancestors(revs) |
|
144 | 144 | if h != r: |
|
145 | 145 | err(seed, graph, bases, seq, h, r) |
|
146 | 146 | |
|
147 | 147 | # graph is a dict of child->parent adjacency lists for this graph: |
|
148 | 148 | # o 13 |
|
149 | 149 | # | |
|
150 | 150 | # | o 12 |
|
151 | 151 | # | | |
|
152 | 152 | # | | o 11 |
|
153 | 153 | # | | |\ |
|
154 | 154 | # | | | | o 10 |
|
155 | 155 | # | | | | | |
|
156 | 156 | # | o---+ | 9 |
|
157 | 157 | # | | | | | |
|
158 | 158 | # o | | | | 8 |
|
159 | 159 | # / / / / |
|
160 | 160 | # | | o | 7 |
|
161 | 161 | # | | | | |
|
162 | 162 | # o---+ | 6 |
|
163 | 163 | # / / / |
|
164 | 164 | # | | o 5 |
|
165 | 165 | # | |/ |
|
166 | 166 | # | o 4 |
|
167 | 167 | # | | |
|
168 | 168 | # o | 3 |
|
169 | 169 | # | | |
|
170 | 170 | # | o 2 |
|
171 | 171 | # |/ |
|
172 | 172 | # o 1 |
|
173 | 173 | # | |
|
174 | 174 | # o 0 |
|
175 | 175 | |
|
176 | 176 | graph = {0: [-1], 1: [0], 2: [1], 3: [1], 4: [2], 5: [4], 6: [4], |
|
177 | 177 | 7: [4], 8: [-1], 9: [6, 7], 10: [5], 11: [3, 7], 12: [9], |
|
178 | 178 | 13: [8]} |
|
179 | 179 | |
|
180 | 180 | def genlazyancestors(revs, stoprev=0, inclusive=False): |
|
181 | 181 | print(("%% lazy ancestor set for %s, stoprev = %s, inclusive = %s" % |
|
182 | 182 | (revs, stoprev, inclusive))) |
|
183 | 183 | return ancestor.lazyancestors(graph.get, revs, stoprev=stoprev, |
|
184 | 184 | inclusive=inclusive) |
|
185 | 185 | |
|
186 | 186 | def printlazyancestors(s, l): |
|
187 | 187 | print('membership: %r' % [n for n in l if n in s]) |
|
188 | 188 | print('iteration: %r' % list(s)) |
|
189 | 189 | |
|
190 | 190 | def test_lazyancestors(): |
|
191 | 191 | # Empty revs |
|
192 | 192 | s = genlazyancestors([]) |
|
193 | 193 | printlazyancestors(s, [3, 0, -1]) |
|
194 | 194 | |
|
195 | 195 | # Standard example |
|
196 | 196 | s = genlazyancestors([11, 13]) |
|
197 | 197 | printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0]) |
|
198 | 198 | |
|
199 | 199 | # Standard with ancestry in the initial set (1 is ancestor of 3) |
|
200 | 200 | s = genlazyancestors([1, 3]) |
|
201 | 201 | printlazyancestors(s, [1, -1, 0]) |
|
202 | 202 | |
|
203 | 203 | # Including revs |
|
204 | 204 | s = genlazyancestors([11, 13], inclusive=True) |
|
205 | 205 | printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0]) |
|
206 | 206 | |
|
207 | 207 | # Test with stoprev |
|
208 | 208 | s = genlazyancestors([11, 13], stoprev=6) |
|
209 | 209 | printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0]) |
|
210 | 210 | s = genlazyancestors([11, 13], stoprev=6, inclusive=True) |
|
211 | 211 | printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0]) |
|
212 | 212 | |
|
213 | 213 | |
|
214 | 214 | # The C gca algorithm requires a real repo. These are textual descriptions of |
|
215 | 215 | # DAGs that have been known to be problematic. |
|
216 | 216 | dagtests = [ |
|
217 | 217 | '+2*2*2/*3/2', |
|
218 | 218 | '+3*3/*2*2/*4*4/*4/2*4/2*2', |
|
219 | 219 | ] |
|
220 | 220 | def test_gca(): |
|
221 | u = uimod.ui() | |
|
221 | u = uimod.ui.load() | |
|
222 | 222 | for i, dag in enumerate(dagtests): |
|
223 | 223 | repo = hg.repository(u, 'gca%d' % i, create=1) |
|
224 | 224 | cl = repo.changelog |
|
225 | 225 | if not util.safehasattr(cl.index, 'ancestors'): |
|
226 | 226 | # C version not available |
|
227 | 227 | return |
|
228 | 228 | |
|
229 | 229 | debugcommands.debugbuilddag(u, repo, dag) |
|
230 | 230 | # Compare the results of the Python and C versions. This does not |
|
231 | 231 | # include choosing a winner when more than one gca exists -- we make |
|
232 | 232 | # sure both return exactly the same set of gcas. |
|
233 | 233 | for a in cl: |
|
234 | 234 | for b in cl: |
|
235 | 235 | cgcas = sorted(cl.index.ancestors(a, b)) |
|
236 | 236 | pygcas = sorted(ancestor.ancestors(cl.parentrevs, a, b)) |
|
237 | 237 | if cgcas != pygcas: |
|
238 | 238 | print("test_gca: for dag %s, gcas for %d, %d:" |
|
239 | 239 | % (dag, a, b)) |
|
240 | 240 | print(" C returned: %s" % cgcas) |
|
241 | 241 | print(" Python returned: %s" % pygcas) |
|
242 | 242 | |
|
243 | 243 | def main(): |
|
244 | 244 | seed = None |
|
245 | 245 | opts, args = getopt.getopt(sys.argv[1:], 's:', ['seed=']) |
|
246 | 246 | for o, a in opts: |
|
247 | 247 | if o in ('-s', '--seed'): |
|
248 | 248 | seed = long(a, base=0) # accepts base 10 or 16 strings |
|
249 | 249 | |
|
250 | 250 | if seed is None: |
|
251 | 251 | try: |
|
252 | 252 | seed = long(binascii.hexlify(os.urandom(16)), 16) |
|
253 | 253 | except AttributeError: |
|
254 | 254 | seed = long(time.time() * 1000) |
|
255 | 255 | |
|
256 | 256 | rng = random.Random(seed) |
|
257 | 257 | test_missingancestors(seed, rng) |
|
258 | 258 | test_lazyancestors() |
|
259 | 259 | test_gca() |
|
260 | 260 | |
|
261 | 261 | if __name__ == '__main__': |
|
262 | 262 | main() |
@@ -1,68 +1,68 | |||
|
1 | 1 | Create a repository: |
|
2 | 2 | |
|
3 | 3 | $ hg config |
|
4 | 4 | defaults.backout=-d "0 0" |
|
5 | 5 | defaults.commit=-d "0 0" |
|
6 | 6 | defaults.shelve=--date "0 0" |
|
7 | 7 | defaults.tag=-d "0 0" |
|
8 | 8 | devel.all-warnings=true |
|
9 | 9 | largefiles.usercache=$TESTTMP/.cache/largefiles (glob) |
|
10 | 10 | ui.slash=True |
|
11 | 11 | ui.interactive=False |
|
12 | 12 | ui.mergemarkers=detailed |
|
13 | 13 | ui.promptecho=True |
|
14 | 14 | $ hg init t |
|
15 | 15 | $ cd t |
|
16 | 16 | |
|
17 | 17 | Make a changeset: |
|
18 | 18 | |
|
19 | 19 | $ echo a > a |
|
20 | 20 | $ hg add a |
|
21 | 21 | $ hg commit -m test |
|
22 | 22 | |
|
23 | 23 | This command is ancient: |
|
24 | 24 | |
|
25 | 25 | $ hg history |
|
26 | 26 | changeset: 0:acb14030fe0a |
|
27 | 27 | tag: tip |
|
28 | 28 | user: test |
|
29 | 29 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
30 | 30 | summary: test |
|
31 | 31 | |
|
32 | 32 | |
|
33 | 33 | Verify that updating to revision 0 via commands.update() works properly |
|
34 | 34 | |
|
35 | 35 | $ cat <<EOF > update_to_rev0.py |
|
36 | 36 | > from mercurial import ui, hg, commands |
|
37 | > myui = ui.ui() | |
|
37 | > myui = ui.ui.load() | |
|
38 | 38 | > repo = hg.repository(myui, path='.') |
|
39 | 39 | > commands.update(myui, repo, rev=0) |
|
40 | 40 | > EOF |
|
41 | 41 | $ hg up null |
|
42 | 42 | 0 files updated, 0 files merged, 1 files removed, 0 files unresolved |
|
43 | 43 | $ python ./update_to_rev0.py |
|
44 | 44 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
45 | 45 | $ hg identify -n |
|
46 | 46 | 0 |
|
47 | 47 | |
|
48 | 48 | |
|
49 | 49 | Poke around at hashes: |
|
50 | 50 | |
|
51 | 51 | $ hg manifest --debug |
|
52 | 52 | b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 644 a |
|
53 | 53 | |
|
54 | 54 | $ hg cat a |
|
55 | 55 | a |
|
56 | 56 | |
|
57 | 57 | Verify should succeed: |
|
58 | 58 | |
|
59 | 59 | $ hg verify |
|
60 | 60 | checking changesets |
|
61 | 61 | checking manifests |
|
62 | 62 | crosschecking files in changesets and manifests |
|
63 | 63 | checking files |
|
64 | 64 | 1 files, 1 changesets, 1 total revisions |
|
65 | 65 | |
|
66 | 66 | At the end... |
|
67 | 67 | |
|
68 | 68 | $ cd .. |
@@ -1,606 +1,606 | |||
|
1 | 1 | $ hg init |
|
2 | 2 | |
|
3 | 3 | |
|
4 | 4 | committing changes |
|
5 | 5 | |
|
6 | 6 | $ count=0 |
|
7 | 7 | $ echo > a |
|
8 | 8 | $ while test $count -lt 32 ; do |
|
9 | 9 | > echo 'a' >> a |
|
10 | 10 | > test $count -eq 0 && hg add |
|
11 | 11 | > hg ci -m "msg $count" -d "$count 0" |
|
12 | 12 | > count=`expr $count + 1` |
|
13 | 13 | > done |
|
14 | 14 | adding a |
|
15 | 15 | |
|
16 | 16 | |
|
17 | 17 | $ hg log |
|
18 | 18 | changeset: 31:58c80a7c8a40 |
|
19 | 19 | tag: tip |
|
20 | 20 | user: test |
|
21 | 21 | date: Thu Jan 01 00:00:31 1970 +0000 |
|
22 | 22 | summary: msg 31 |
|
23 | 23 | |
|
24 | 24 | changeset: 30:ed2d2f24b11c |
|
25 | 25 | user: test |
|
26 | 26 | date: Thu Jan 01 00:00:30 1970 +0000 |
|
27 | 27 | summary: msg 30 |
|
28 | 28 | |
|
29 | 29 | changeset: 29:b5bd63375ab9 |
|
30 | 30 | user: test |
|
31 | 31 | date: Thu Jan 01 00:00:29 1970 +0000 |
|
32 | 32 | summary: msg 29 |
|
33 | 33 | |
|
34 | 34 | changeset: 28:8e0c2264c8af |
|
35 | 35 | user: test |
|
36 | 36 | date: Thu Jan 01 00:00:28 1970 +0000 |
|
37 | 37 | summary: msg 28 |
|
38 | 38 | |
|
39 | 39 | changeset: 27:288867a866e9 |
|
40 | 40 | user: test |
|
41 | 41 | date: Thu Jan 01 00:00:27 1970 +0000 |
|
42 | 42 | summary: msg 27 |
|
43 | 43 | |
|
44 | 44 | changeset: 26:3efc6fd51aeb |
|
45 | 45 | user: test |
|
46 | 46 | date: Thu Jan 01 00:00:26 1970 +0000 |
|
47 | 47 | summary: msg 26 |
|
48 | 48 | |
|
49 | 49 | changeset: 25:02a84173a97a |
|
50 | 50 | user: test |
|
51 | 51 | date: Thu Jan 01 00:00:25 1970 +0000 |
|
52 | 52 | summary: msg 25 |
|
53 | 53 | |
|
54 | 54 | changeset: 24:10e0acd3809e |
|
55 | 55 | user: test |
|
56 | 56 | date: Thu Jan 01 00:00:24 1970 +0000 |
|
57 | 57 | summary: msg 24 |
|
58 | 58 | |
|
59 | 59 | changeset: 23:5ec79163bff4 |
|
60 | 60 | user: test |
|
61 | 61 | date: Thu Jan 01 00:00:23 1970 +0000 |
|
62 | 62 | summary: msg 23 |
|
63 | 63 | |
|
64 | 64 | changeset: 22:06c7993750ce |
|
65 | 65 | user: test |
|
66 | 66 | date: Thu Jan 01 00:00:22 1970 +0000 |
|
67 | 67 | summary: msg 22 |
|
68 | 68 | |
|
69 | 69 | changeset: 21:e5db6aa3fe2a |
|
70 | 70 | user: test |
|
71 | 71 | date: Thu Jan 01 00:00:21 1970 +0000 |
|
72 | 72 | summary: msg 21 |
|
73 | 73 | |
|
74 | 74 | changeset: 20:7128fb4fdbc9 |
|
75 | 75 | user: test |
|
76 | 76 | date: Thu Jan 01 00:00:20 1970 +0000 |
|
77 | 77 | summary: msg 20 |
|
78 | 78 | |
|
79 | 79 | changeset: 19:52798545b482 |
|
80 | 80 | user: test |
|
81 | 81 | date: Thu Jan 01 00:00:19 1970 +0000 |
|
82 | 82 | summary: msg 19 |
|
83 | 83 | |
|
84 | 84 | changeset: 18:86977a90077e |
|
85 | 85 | user: test |
|
86 | 86 | date: Thu Jan 01 00:00:18 1970 +0000 |
|
87 | 87 | summary: msg 18 |
|
88 | 88 | |
|
89 | 89 | changeset: 17:03515f4a9080 |
|
90 | 90 | user: test |
|
91 | 91 | date: Thu Jan 01 00:00:17 1970 +0000 |
|
92 | 92 | summary: msg 17 |
|
93 | 93 | |
|
94 | 94 | changeset: 16:a2e6ea4973e9 |
|
95 | 95 | user: test |
|
96 | 96 | date: Thu Jan 01 00:00:16 1970 +0000 |
|
97 | 97 | summary: msg 16 |
|
98 | 98 | |
|
99 | 99 | changeset: 15:e7fa0811edb0 |
|
100 | 100 | user: test |
|
101 | 101 | date: Thu Jan 01 00:00:15 1970 +0000 |
|
102 | 102 | summary: msg 15 |
|
103 | 103 | |
|
104 | 104 | changeset: 14:ce8f0998e922 |
|
105 | 105 | user: test |
|
106 | 106 | date: Thu Jan 01 00:00:14 1970 +0000 |
|
107 | 107 | summary: msg 14 |
|
108 | 108 | |
|
109 | 109 | changeset: 13:9d7d07bc967c |
|
110 | 110 | user: test |
|
111 | 111 | date: Thu Jan 01 00:00:13 1970 +0000 |
|
112 | 112 | summary: msg 13 |
|
113 | 113 | |
|
114 | 114 | changeset: 12:1941b52820a5 |
|
115 | 115 | user: test |
|
116 | 116 | date: Thu Jan 01 00:00:12 1970 +0000 |
|
117 | 117 | summary: msg 12 |
|
118 | 118 | |
|
119 | 119 | changeset: 11:7b4cd9578619 |
|
120 | 120 | user: test |
|
121 | 121 | date: Thu Jan 01 00:00:11 1970 +0000 |
|
122 | 122 | summary: msg 11 |
|
123 | 123 | |
|
124 | 124 | changeset: 10:7c5eff49a6b6 |
|
125 | 125 | user: test |
|
126 | 126 | date: Thu Jan 01 00:00:10 1970 +0000 |
|
127 | 127 | summary: msg 10 |
|
128 | 128 | |
|
129 | 129 | changeset: 9:eb44510ef29a |
|
130 | 130 | user: test |
|
131 | 131 | date: Thu Jan 01 00:00:09 1970 +0000 |
|
132 | 132 | summary: msg 9 |
|
133 | 133 | |
|
134 | 134 | changeset: 8:453eb4dba229 |
|
135 | 135 | user: test |
|
136 | 136 | date: Thu Jan 01 00:00:08 1970 +0000 |
|
137 | 137 | summary: msg 8 |
|
138 | 138 | |
|
139 | 139 | changeset: 7:03750880c6b5 |
|
140 | 140 | user: test |
|
141 | 141 | date: Thu Jan 01 00:00:07 1970 +0000 |
|
142 | 142 | summary: msg 7 |
|
143 | 143 | |
|
144 | 144 | changeset: 6:a3d5c6fdf0d3 |
|
145 | 145 | user: test |
|
146 | 146 | date: Thu Jan 01 00:00:06 1970 +0000 |
|
147 | 147 | summary: msg 6 |
|
148 | 148 | |
|
149 | 149 | changeset: 5:7874a09ea728 |
|
150 | 150 | user: test |
|
151 | 151 | date: Thu Jan 01 00:00:05 1970 +0000 |
|
152 | 152 | summary: msg 5 |
|
153 | 153 | |
|
154 | 154 | changeset: 4:9b2ba8336a65 |
|
155 | 155 | user: test |
|
156 | 156 | date: Thu Jan 01 00:00:04 1970 +0000 |
|
157 | 157 | summary: msg 4 |
|
158 | 158 | |
|
159 | 159 | changeset: 3:b53bea5e2fcb |
|
160 | 160 | user: test |
|
161 | 161 | date: Thu Jan 01 00:00:03 1970 +0000 |
|
162 | 162 | summary: msg 3 |
|
163 | 163 | |
|
164 | 164 | changeset: 2:db07c04beaca |
|
165 | 165 | user: test |
|
166 | 166 | date: Thu Jan 01 00:00:02 1970 +0000 |
|
167 | 167 | summary: msg 2 |
|
168 | 168 | |
|
169 | 169 | changeset: 1:5cd978ea5149 |
|
170 | 170 | user: test |
|
171 | 171 | date: Thu Jan 01 00:00:01 1970 +0000 |
|
172 | 172 | summary: msg 1 |
|
173 | 173 | |
|
174 | 174 | changeset: 0:b99c7b9c8e11 |
|
175 | 175 | user: test |
|
176 | 176 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
177 | 177 | summary: msg 0 |
|
178 | 178 | |
|
179 | 179 | |
|
180 | 180 | $ hg up -C |
|
181 | 181 | 0 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
182 | 182 | |
|
183 | 183 | bisect test |
|
184 | 184 | |
|
185 | 185 | $ hg bisect -r |
|
186 | 186 | $ hg bisect -b |
|
187 | 187 | $ hg summary |
|
188 | 188 | parent: 31:58c80a7c8a40 tip |
|
189 | 189 | msg 31 |
|
190 | 190 | branch: default |
|
191 | 191 | commit: (clean) |
|
192 | 192 | update: (current) |
|
193 | 193 | phases: 32 draft |
|
194 | 194 | $ hg bisect -g 1 |
|
195 | 195 | Testing changeset 16:a2e6ea4973e9 (30 changesets remaining, ~4 tests) |
|
196 | 196 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
197 | 197 | $ hg bisect -g |
|
198 | 198 | Testing changeset 23:5ec79163bff4 (15 changesets remaining, ~3 tests) |
|
199 | 199 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
200 | 200 | |
|
201 | 201 | skip |
|
202 | 202 | |
|
203 | 203 | $ hg bisect -s |
|
204 | 204 | Testing changeset 24:10e0acd3809e (15 changesets remaining, ~3 tests) |
|
205 | 205 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
206 | 206 | $ hg bisect -g |
|
207 | 207 | Testing changeset 27:288867a866e9 (7 changesets remaining, ~2 tests) |
|
208 | 208 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
209 | 209 | $ hg bisect -g |
|
210 | 210 | Testing changeset 29:b5bd63375ab9 (4 changesets remaining, ~2 tests) |
|
211 | 211 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
212 | 212 | $ hg bisect -b |
|
213 | 213 | Testing changeset 28:8e0c2264c8af (2 changesets remaining, ~1 tests) |
|
214 | 214 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
215 | 215 | $ hg bisect -g |
|
216 | 216 | The first bad revision is: |
|
217 | 217 | changeset: 29:b5bd63375ab9 |
|
218 | 218 | user: test |
|
219 | 219 | date: Thu Jan 01 00:00:29 1970 +0000 |
|
220 | 220 | summary: msg 29 |
|
221 | 221 | |
|
222 | 222 | |
|
223 | 223 | mark revsets instead of single revs |
|
224 | 224 | |
|
225 | 225 | $ hg bisect -r |
|
226 | 226 | $ hg bisect -b "0::3" |
|
227 | 227 | $ hg bisect -s "13::16" |
|
228 | 228 | $ hg bisect -g "26::tip" |
|
229 | 229 | Testing changeset 12:1941b52820a5 (23 changesets remaining, ~4 tests) |
|
230 | 230 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
231 | 231 | $ cat .hg/bisect.state |
|
232 | 232 | bad b99c7b9c8e11558adef3fad9af211c58d46f325b |
|
233 | 233 | bad 5cd978ea51499179507ee7b6f340d2dbaa401185 |
|
234 | 234 | bad db07c04beaca44cf24832541e7f4a2346a95275b |
|
235 | 235 | bad b53bea5e2fcb30d3e00bd3409507a5659ce0fd8b |
|
236 | 236 | current 1941b52820a544549596820a8ae006842b0e2c64 |
|
237 | 237 | good 3efc6fd51aeb8594398044c6c846ca59ae021203 |
|
238 | 238 | good 288867a866e9adb7a29880b66936c874b80f4651 |
|
239 | 239 | good 8e0c2264c8af790daf3585ada0669d93dee09c83 |
|
240 | 240 | good b5bd63375ab9a290419f2024b7f4ee9ea7ce90a8 |
|
241 | 241 | good ed2d2f24b11c368fa8aa0da9f4e1db580abade59 |
|
242 | 242 | good 58c80a7c8a4025a94cedaf7b4a4e3124e8909a96 |
|
243 | 243 | skip 9d7d07bc967ca98ad0600c24953fd289ad5fa991 |
|
244 | 244 | skip ce8f0998e922c179e80819d5066fbe46e2998784 |
|
245 | 245 | skip e7fa0811edb063f6319531f0d0a865882138e180 |
|
246 | 246 | skip a2e6ea4973e9196ddd3386493b0c214b41fd97d3 |
|
247 | 247 | |
|
248 | 248 | bisect reverse test |
|
249 | 249 | |
|
250 | 250 | $ hg bisect -r |
|
251 | 251 | $ hg bisect -b null |
|
252 | 252 | $ hg bisect -g tip |
|
253 | 253 | Testing changeset 15:e7fa0811edb0 (32 changesets remaining, ~5 tests) |
|
254 | 254 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
255 | 255 | $ hg bisect -g |
|
256 | 256 | Testing changeset 7:03750880c6b5 (16 changesets remaining, ~4 tests) |
|
257 | 257 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
258 | 258 | |
|
259 | 259 | skip |
|
260 | 260 | |
|
261 | 261 | $ hg bisect -s |
|
262 | 262 | Testing changeset 6:a3d5c6fdf0d3 (16 changesets remaining, ~4 tests) |
|
263 | 263 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
264 | 264 | $ hg bisect -g |
|
265 | 265 | Testing changeset 2:db07c04beaca (7 changesets remaining, ~2 tests) |
|
266 | 266 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
267 | 267 | $ hg bisect -g |
|
268 | 268 | Testing changeset 0:b99c7b9c8e11 (3 changesets remaining, ~1 tests) |
|
269 | 269 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
270 | 270 | $ hg bisect -b |
|
271 | 271 | Testing changeset 1:5cd978ea5149 (2 changesets remaining, ~1 tests) |
|
272 | 272 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
273 | 273 | $ hg bisect -g |
|
274 | 274 | The first good revision is: |
|
275 | 275 | changeset: 1:5cd978ea5149 |
|
276 | 276 | user: test |
|
277 | 277 | date: Thu Jan 01 00:00:01 1970 +0000 |
|
278 | 278 | summary: msg 1 |
|
279 | 279 | |
|
280 | 280 | |
|
281 | 281 | $ hg bisect -r |
|
282 | 282 | $ hg bisect -g tip |
|
283 | 283 | $ hg bisect -b tip |
|
284 | 284 | abort: inconsistent state, 31:58c80a7c8a40 is good and bad |
|
285 | 285 | [255] |
|
286 | 286 | |
|
287 | 287 | $ hg bisect -r |
|
288 | 288 | $ hg bisect -g null |
|
289 | 289 | $ hg bisect -bU tip |
|
290 | 290 | Testing changeset 15:e7fa0811edb0 (32 changesets remaining, ~5 tests) |
|
291 | 291 | $ hg id |
|
292 | 292 | 5cd978ea5149 |
|
293 | 293 | |
|
294 | 294 | |
|
295 | 295 | Issue1228: hg bisect crashes when you skip the last rev in bisection |
|
296 | 296 | Issue1182: hg bisect exception |
|
297 | 297 | |
|
298 | 298 | $ hg bisect -r |
|
299 | 299 | $ hg bisect -b 4 |
|
300 | 300 | $ hg bisect -g 0 |
|
301 | 301 | Testing changeset 2:db07c04beaca (4 changesets remaining, ~2 tests) |
|
302 | 302 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
303 | 303 | $ hg bisect -s |
|
304 | 304 | Testing changeset 1:5cd978ea5149 (4 changesets remaining, ~2 tests) |
|
305 | 305 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
306 | 306 | $ hg bisect -s |
|
307 | 307 | Testing changeset 3:b53bea5e2fcb (4 changesets remaining, ~2 tests) |
|
308 | 308 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
309 | 309 | $ hg bisect -s |
|
310 | 310 | Due to skipped revisions, the first bad revision could be any of: |
|
311 | 311 | changeset: 1:5cd978ea5149 |
|
312 | 312 | user: test |
|
313 | 313 | date: Thu Jan 01 00:00:01 1970 +0000 |
|
314 | 314 | summary: msg 1 |
|
315 | 315 | |
|
316 | 316 | changeset: 2:db07c04beaca |
|
317 | 317 | user: test |
|
318 | 318 | date: Thu Jan 01 00:00:02 1970 +0000 |
|
319 | 319 | summary: msg 2 |
|
320 | 320 | |
|
321 | 321 | changeset: 3:b53bea5e2fcb |
|
322 | 322 | user: test |
|
323 | 323 | date: Thu Jan 01 00:00:03 1970 +0000 |
|
324 | 324 | summary: msg 3 |
|
325 | 325 | |
|
326 | 326 | changeset: 4:9b2ba8336a65 |
|
327 | 327 | user: test |
|
328 | 328 | date: Thu Jan 01 00:00:04 1970 +0000 |
|
329 | 329 | summary: msg 4 |
|
330 | 330 | |
|
331 | 331 | |
|
332 | 332 | |
|
333 | 333 | reproduce non converging bisect, issue1182 |
|
334 | 334 | |
|
335 | 335 | $ hg bisect -r |
|
336 | 336 | $ hg bisect -g 0 |
|
337 | 337 | $ hg bisect -b 2 |
|
338 | 338 | Testing changeset 1:5cd978ea5149 (2 changesets remaining, ~1 tests) |
|
339 | 339 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
340 | 340 | $ hg bisect -s |
|
341 | 341 | Due to skipped revisions, the first bad revision could be any of: |
|
342 | 342 | changeset: 1:5cd978ea5149 |
|
343 | 343 | user: test |
|
344 | 344 | date: Thu Jan 01 00:00:01 1970 +0000 |
|
345 | 345 | summary: msg 1 |
|
346 | 346 | |
|
347 | 347 | changeset: 2:db07c04beaca |
|
348 | 348 | user: test |
|
349 | 349 | date: Thu Jan 01 00:00:02 1970 +0000 |
|
350 | 350 | summary: msg 2 |
|
351 | 351 | |
|
352 | 352 | |
|
353 | 353 | |
|
354 | 354 | test no action |
|
355 | 355 | |
|
356 | 356 | $ hg bisect -r |
|
357 | 357 | $ hg bisect |
|
358 | 358 | abort: cannot bisect (no known good revisions) |
|
359 | 359 | [255] |
|
360 | 360 | |
|
361 | 361 | |
|
362 | 362 | reproduce AssertionError, issue1445 |
|
363 | 363 | |
|
364 | 364 | $ hg bisect -r |
|
365 | 365 | $ hg bisect -b 6 |
|
366 | 366 | $ hg bisect -g 0 |
|
367 | 367 | Testing changeset 3:b53bea5e2fcb (6 changesets remaining, ~2 tests) |
|
368 | 368 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
369 | 369 | $ hg bisect -s |
|
370 | 370 | Testing changeset 2:db07c04beaca (6 changesets remaining, ~2 tests) |
|
371 | 371 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
372 | 372 | $ hg bisect -s |
|
373 | 373 | Testing changeset 4:9b2ba8336a65 (6 changesets remaining, ~2 tests) |
|
374 | 374 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
375 | 375 | $ hg bisect -s |
|
376 | 376 | Testing changeset 1:5cd978ea5149 (6 changesets remaining, ~2 tests) |
|
377 | 377 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
378 | 378 | $ hg bisect -s |
|
379 | 379 | Testing changeset 5:7874a09ea728 (6 changesets remaining, ~2 tests) |
|
380 | 380 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
381 | 381 | $ hg bisect -g |
|
382 | 382 | The first bad revision is: |
|
383 | 383 | changeset: 6:a3d5c6fdf0d3 |
|
384 | 384 | user: test |
|
385 | 385 | date: Thu Jan 01 00:00:06 1970 +0000 |
|
386 | 386 | summary: msg 6 |
|
387 | 387 | |
|
388 | 388 | $ hg log -r "bisect(good)" |
|
389 | 389 | changeset: 0:b99c7b9c8e11 |
|
390 | 390 | user: test |
|
391 | 391 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
392 | 392 | summary: msg 0 |
|
393 | 393 | |
|
394 | 394 | changeset: 5:7874a09ea728 |
|
395 | 395 | user: test |
|
396 | 396 | date: Thu Jan 01 00:00:05 1970 +0000 |
|
397 | 397 | summary: msg 5 |
|
398 | 398 | |
|
399 | 399 | $ hg log -r "bisect(bad)" |
|
400 | 400 | changeset: 6:a3d5c6fdf0d3 |
|
401 | 401 | user: test |
|
402 | 402 | date: Thu Jan 01 00:00:06 1970 +0000 |
|
403 | 403 | summary: msg 6 |
|
404 | 404 | |
|
405 | 405 | $ hg log -r "bisect(current)" |
|
406 | 406 | changeset: 5:7874a09ea728 |
|
407 | 407 | user: test |
|
408 | 408 | date: Thu Jan 01 00:00:05 1970 +0000 |
|
409 | 409 | summary: msg 5 |
|
410 | 410 | |
|
411 | 411 | $ hg log -r "bisect(skip)" |
|
412 | 412 | changeset: 1:5cd978ea5149 |
|
413 | 413 | user: test |
|
414 | 414 | date: Thu Jan 01 00:00:01 1970 +0000 |
|
415 | 415 | summary: msg 1 |
|
416 | 416 | |
|
417 | 417 | changeset: 2:db07c04beaca |
|
418 | 418 | user: test |
|
419 | 419 | date: Thu Jan 01 00:00:02 1970 +0000 |
|
420 | 420 | summary: msg 2 |
|
421 | 421 | |
|
422 | 422 | changeset: 3:b53bea5e2fcb |
|
423 | 423 | user: test |
|
424 | 424 | date: Thu Jan 01 00:00:03 1970 +0000 |
|
425 | 425 | summary: msg 3 |
|
426 | 426 | |
|
427 | 427 | changeset: 4:9b2ba8336a65 |
|
428 | 428 | user: test |
|
429 | 429 | date: Thu Jan 01 00:00:04 1970 +0000 |
|
430 | 430 | summary: msg 4 |
|
431 | 431 | |
|
432 | 432 | |
|
433 | 433 | test legacy bisected() keyword |
|
434 | 434 | |
|
435 | 435 | $ hg log -r "bisected(bad)" |
|
436 | 436 | changeset: 6:a3d5c6fdf0d3 |
|
437 | 437 | user: test |
|
438 | 438 | date: Thu Jan 01 00:00:06 1970 +0000 |
|
439 | 439 | summary: msg 6 |
|
440 | 440 | |
|
441 | 441 | |
|
442 | 442 | $ set +e |
|
443 | 443 | |
|
444 | 444 | test invalid command |
|
445 | 445 | assuming that the shell returns 127 if command not found ... |
|
446 | 446 | |
|
447 | 447 | $ hg bisect -r |
|
448 | 448 | $ hg bisect --command 'exit 127' |
|
449 | 449 | abort: failed to execute exit 127 |
|
450 | 450 | [255] |
|
451 | 451 | |
|
452 | 452 | |
|
453 | 453 | test bisecting command |
|
454 | 454 | |
|
455 | 455 | $ cat > script.py <<EOF |
|
456 | 456 | > #!/usr/bin/env python |
|
457 | 457 | > import sys |
|
458 | 458 | > from mercurial import ui, hg |
|
459 | > repo = hg.repository(ui.ui(), '.') | |
|
459 | > repo = hg.repository(ui.ui.load(), '.') | |
|
460 | 460 | > if repo['.'].rev() < 6: |
|
461 | 461 | > sys.exit(1) |
|
462 | 462 | > EOF |
|
463 | 463 | $ chmod +x script.py |
|
464 | 464 | $ hg bisect -r |
|
465 | 465 | $ hg up -qr tip |
|
466 | 466 | $ hg bisect --command "python \"$TESTTMP/script.py\" and some parameters" |
|
467 | 467 | changeset 31:58c80a7c8a40: good |
|
468 | 468 | abort: cannot bisect (no known bad revisions) |
|
469 | 469 | [255] |
|
470 | 470 | $ hg up -qr 0 |
|
471 | 471 | $ hg bisect --command "python \"$TESTTMP/script.py\" and some parameters" |
|
472 | 472 | changeset 0:b99c7b9c8e11: bad |
|
473 | 473 | changeset 15:e7fa0811edb0: good |
|
474 | 474 | changeset 7:03750880c6b5: good |
|
475 | 475 | changeset 3:b53bea5e2fcb: bad |
|
476 | 476 | changeset 5:7874a09ea728: bad |
|
477 | 477 | changeset 6:a3d5c6fdf0d3: good |
|
478 | 478 | The first good revision is: |
|
479 | 479 | changeset: 6:a3d5c6fdf0d3 |
|
480 | 480 | user: test |
|
481 | 481 | date: Thu Jan 01 00:00:06 1970 +0000 |
|
482 | 482 | summary: msg 6 |
|
483 | 483 | |
|
484 | 484 | |
|
485 | 485 | |
|
486 | 486 | test bisecting via a command without updating the working dir, and |
|
487 | 487 | ensure that the bisect state file is updated before running a test |
|
488 | 488 | command |
|
489 | 489 | |
|
490 | 490 | $ hg update null |
|
491 | 491 | 0 files updated, 0 files merged, 1 files removed, 0 files unresolved |
|
492 | 492 | $ cat > script.sh <<'EOF' |
|
493 | 493 | > #!/bin/sh |
|
494 | 494 | > test -n "$HG_NODE" || (echo HG_NODE missing; exit 127) |
|
495 | 495 | > current="`hg log -r \"bisect(current)\" --template {node}`" |
|
496 | 496 | > test "$current" = "$HG_NODE" || (echo current is bad: $current; exit 127) |
|
497 | 497 | > rev="`hg log -r $HG_NODE --template {rev}`" |
|
498 | 498 | > test "$rev" -ge 6 |
|
499 | 499 | > EOF |
|
500 | 500 | $ chmod +x script.sh |
|
501 | 501 | $ hg bisect -r |
|
502 | 502 | $ hg bisect --good tip --noupdate |
|
503 | 503 | $ hg bisect --bad 0 --noupdate |
|
504 | 504 | Testing changeset 15:e7fa0811edb0 (31 changesets remaining, ~4 tests) |
|
505 | 505 | $ hg bisect --command "sh \"$TESTTMP/script.sh\" and some params" --noupdate |
|
506 | 506 | changeset 15:e7fa0811edb0: good |
|
507 | 507 | changeset 7:03750880c6b5: good |
|
508 | 508 | changeset 3:b53bea5e2fcb: bad |
|
509 | 509 | changeset 5:7874a09ea728: bad |
|
510 | 510 | changeset 6:a3d5c6fdf0d3: good |
|
511 | 511 | The first good revision is: |
|
512 | 512 | changeset: 6:a3d5c6fdf0d3 |
|
513 | 513 | user: test |
|
514 | 514 | date: Thu Jan 01 00:00:06 1970 +0000 |
|
515 | 515 | summary: msg 6 |
|
516 | 516 | |
|
517 | 517 | |
|
518 | 518 | ensure that we still don't have a working dir |
|
519 | 519 | |
|
520 | 520 | $ hg parents |
|
521 | 521 | |
|
522 | 522 | |
|
523 | 523 | test the same case, this time with updating |
|
524 | 524 | |
|
525 | 525 | $ cat > script.sh <<'EOF' |
|
526 | 526 | > #!/bin/sh |
|
527 | 527 | > test -n "$HG_NODE" || (echo HG_NODE missing; exit 127) |
|
528 | 528 | > current="`hg log -r \"bisect(current)\" --template {node}`" |
|
529 | 529 | > test "$current" = "$HG_NODE" || (echo current is bad: $current; exit 127) |
|
530 | 530 | > rev="`hg log -r . --template {rev}`" |
|
531 | 531 | > test "$rev" -ge 6 |
|
532 | 532 | > EOF |
|
533 | 533 | $ chmod +x script.sh |
|
534 | 534 | $ hg bisect -r |
|
535 | 535 | $ hg up -qr tip |
|
536 | 536 | $ hg bisect --command "sh \"$TESTTMP/script.sh\" and some params" |
|
537 | 537 | changeset 31:58c80a7c8a40: good |
|
538 | 538 | abort: cannot bisect (no known bad revisions) |
|
539 | 539 | [255] |
|
540 | 540 | $ hg up -qr 0 |
|
541 | 541 | $ hg bisect --command "sh \"$TESTTMP/script.sh\" and some params" |
|
542 | 542 | changeset 0:b99c7b9c8e11: bad |
|
543 | 543 | changeset 15:e7fa0811edb0: good |
|
544 | 544 | changeset 7:03750880c6b5: good |
|
545 | 545 | changeset 3:b53bea5e2fcb: bad |
|
546 | 546 | changeset 5:7874a09ea728: bad |
|
547 | 547 | changeset 6:a3d5c6fdf0d3: good |
|
548 | 548 | The first good revision is: |
|
549 | 549 | changeset: 6:a3d5c6fdf0d3 |
|
550 | 550 | user: test |
|
551 | 551 | date: Thu Jan 01 00:00:06 1970 +0000 |
|
552 | 552 | summary: msg 6 |
|
553 | 553 | |
|
554 | 554 | |
|
555 | 555 | |
|
556 | 556 | Check that bisect does not break on obsolete changesets |
|
557 | 557 | ========================================================= |
|
558 | 558 | |
|
559 | 559 | $ cat >> $HGRCPATH << EOF |
|
560 | 560 | > [experimental] |
|
561 | 561 | > evolution=createmarkers |
|
562 | 562 | > EOF |
|
563 | 563 | |
|
564 | 564 | tip is obsolete |
|
565 | 565 | --------------------- |
|
566 | 566 | |
|
567 | 567 | $ hg debugobsolete `hg id --debug -i -r tip` |
|
568 | 568 | $ hg bisect --reset |
|
569 | 569 | $ hg bisect --good 15 |
|
570 | 570 | $ hg bisect --bad 30 |
|
571 | 571 | Testing changeset 22:06c7993750ce (15 changesets remaining, ~3 tests) |
|
572 | 572 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
573 | 573 | $ hg bisect --command true |
|
574 | 574 | changeset 22:06c7993750ce: good |
|
575 | 575 | changeset 26:3efc6fd51aeb: good |
|
576 | 576 | changeset 28:8e0c2264c8af: good |
|
577 | 577 | changeset 29:b5bd63375ab9: good |
|
578 | 578 | The first bad revision is: |
|
579 | 579 | changeset: 30:ed2d2f24b11c |
|
580 | 580 | tag: tip |
|
581 | 581 | user: test |
|
582 | 582 | date: Thu Jan 01 00:00:30 1970 +0000 |
|
583 | 583 | summary: msg 30 |
|
584 | 584 | |
|
585 | 585 | |
|
586 | 586 | Changeset in the bad:good range is obsolete |
|
587 | 587 | --------------------------------------------- |
|
588 | 588 | |
|
589 | 589 | $ hg up 30 |
|
590 | 590 | 0 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
591 | 591 | $ echo 'a' >> a |
|
592 | 592 | $ hg ci -m "msg 32" -d "32 0" |
|
593 | 593 | $ hg bisect --reset |
|
594 | 594 | $ hg bisect --good . |
|
595 | 595 | $ hg bisect --bad 25 |
|
596 | 596 | Testing changeset 28:8e0c2264c8af (6 changesets remaining, ~2 tests) |
|
597 | 597 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
598 | 598 | $ hg bisect --command true |
|
599 | 599 | changeset 28:8e0c2264c8af: good |
|
600 | 600 | changeset 26:3efc6fd51aeb: good |
|
601 | 601 | The first good revision is: |
|
602 | 602 | changeset: 26:3efc6fd51aeb |
|
603 | 603 | user: test |
|
604 | 604 | date: Thu Jan 01 00:00:26 1970 +0000 |
|
605 | 605 | summary: msg 26 |
|
606 | 606 |
@@ -1,1094 +1,1094 | |||
|
1 | 1 | Prepare repo a: |
|
2 | 2 | |
|
3 | 3 | $ hg init a |
|
4 | 4 | $ cd a |
|
5 | 5 | $ echo a > a |
|
6 | 6 | $ hg add a |
|
7 | 7 | $ hg commit -m test |
|
8 | 8 | $ echo first line > b |
|
9 | 9 | $ hg add b |
|
10 | 10 | |
|
11 | 11 | Create a non-inlined filelog: |
|
12 | 12 | |
|
13 | 13 | $ $PYTHON -c 'file("data1", "wb").write("".join("%s\n" % x for x in range(10000)))' |
|
14 | 14 | $ for j in 0 1 2 3 4 5 6 7 8 9; do |
|
15 | 15 | > cat data1 >> b |
|
16 | 16 | > hg commit -m test |
|
17 | 17 | > done |
|
18 | 18 | |
|
19 | 19 | List files in store/data (should show a 'b.d'): |
|
20 | 20 | |
|
21 | 21 | $ for i in .hg/store/data/*; do |
|
22 | 22 | > echo $i |
|
23 | 23 | > done |
|
24 | 24 | .hg/store/data/a.i |
|
25 | 25 | .hg/store/data/b.d |
|
26 | 26 | .hg/store/data/b.i |
|
27 | 27 | |
|
28 | 28 | Trigger branchcache creation: |
|
29 | 29 | |
|
30 | 30 | $ hg branches |
|
31 | 31 | default 10:a7949464abda |
|
32 | 32 | $ ls .hg/cache |
|
33 | 33 | branch2-served |
|
34 | 34 | checkisexec |
|
35 | 35 | checklink |
|
36 | 36 | checklink-target |
|
37 | 37 | checknoexec |
|
38 | 38 | rbc-names-v1 |
|
39 | 39 | rbc-revs-v1 |
|
40 | 40 | |
|
41 | 41 | Default operation: |
|
42 | 42 | |
|
43 | 43 | $ hg clone . ../b |
|
44 | 44 | updating to branch default |
|
45 | 45 | 2 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
46 | 46 | $ cd ../b |
|
47 | 47 | |
|
48 | 48 | Ensure branchcache got copied over: |
|
49 | 49 | |
|
50 | 50 | $ ls .hg/cache |
|
51 | 51 | branch2-served |
|
52 | 52 | checkisexec |
|
53 | 53 | checklink |
|
54 | 54 | checklink-target |
|
55 | 55 | |
|
56 | 56 | $ cat a |
|
57 | 57 | a |
|
58 | 58 | $ hg verify |
|
59 | 59 | checking changesets |
|
60 | 60 | checking manifests |
|
61 | 61 | crosschecking files in changesets and manifests |
|
62 | 62 | checking files |
|
63 | 63 | 2 files, 11 changesets, 11 total revisions |
|
64 | 64 | |
|
65 | 65 | Invalid dest '' must abort: |
|
66 | 66 | |
|
67 | 67 | $ hg clone . '' |
|
68 | 68 | abort: empty destination path is not valid |
|
69 | 69 | [255] |
|
70 | 70 | |
|
71 | 71 | No update, with debug option: |
|
72 | 72 | |
|
73 | 73 | #if hardlink |
|
74 | 74 | $ hg --debug clone -U . ../c --config progress.debug=true |
|
75 | 75 | linking: 1 |
|
76 | 76 | linking: 2 |
|
77 | 77 | linking: 3 |
|
78 | 78 | linking: 4 |
|
79 | 79 | linking: 5 |
|
80 | 80 | linking: 6 |
|
81 | 81 | linking: 7 |
|
82 | 82 | linking: 8 |
|
83 | 83 | linked 8 files |
|
84 | 84 | #else |
|
85 | 85 | $ hg --debug clone -U . ../c --config progress.debug=true |
|
86 | 86 | linking: 1 |
|
87 | 87 | copying: 2 |
|
88 | 88 | copying: 3 |
|
89 | 89 | copying: 4 |
|
90 | 90 | copying: 5 |
|
91 | 91 | copying: 6 |
|
92 | 92 | copying: 7 |
|
93 | 93 | copying: 8 |
|
94 | 94 | copied 8 files |
|
95 | 95 | #endif |
|
96 | 96 | $ cd ../c |
|
97 | 97 | |
|
98 | 98 | Ensure branchcache got copied over: |
|
99 | 99 | |
|
100 | 100 | $ ls .hg/cache |
|
101 | 101 | branch2-served |
|
102 | 102 | |
|
103 | 103 | $ cat a 2>/dev/null || echo "a not present" |
|
104 | 104 | a not present |
|
105 | 105 | $ hg verify |
|
106 | 106 | checking changesets |
|
107 | 107 | checking manifests |
|
108 | 108 | crosschecking files in changesets and manifests |
|
109 | 109 | checking files |
|
110 | 110 | 2 files, 11 changesets, 11 total revisions |
|
111 | 111 | |
|
112 | 112 | Default destination: |
|
113 | 113 | |
|
114 | 114 | $ mkdir ../d |
|
115 | 115 | $ cd ../d |
|
116 | 116 | $ hg clone ../a |
|
117 | 117 | destination directory: a |
|
118 | 118 | updating to branch default |
|
119 | 119 | 2 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
120 | 120 | $ cd a |
|
121 | 121 | $ hg cat a |
|
122 | 122 | a |
|
123 | 123 | $ cd ../.. |
|
124 | 124 | |
|
125 | 125 | Check that we drop the 'file:' from the path before writing the .hgrc: |
|
126 | 126 | |
|
127 | 127 | $ hg clone file:a e |
|
128 | 128 | updating to branch default |
|
129 | 129 | 2 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
130 | 130 | $ grep 'file:' e/.hg/hgrc |
|
131 | 131 | [1] |
|
132 | 132 | |
|
133 | 133 | Check that path aliases are expanded: |
|
134 | 134 | |
|
135 | 135 | $ hg clone -q -U --config 'paths.foobar=a#0' foobar f |
|
136 | 136 | $ hg -R f showconfig paths.default |
|
137 | 137 | $TESTTMP/a#0 (glob) |
|
138 | 138 | |
|
139 | 139 | Use --pull: |
|
140 | 140 | |
|
141 | 141 | $ hg clone --pull a g |
|
142 | 142 | requesting all changes |
|
143 | 143 | adding changesets |
|
144 | 144 | adding manifests |
|
145 | 145 | adding file changes |
|
146 | 146 | added 11 changesets with 11 changes to 2 files |
|
147 | 147 | updating to branch default |
|
148 | 148 | 2 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
149 | 149 | $ hg -R g verify |
|
150 | 150 | checking changesets |
|
151 | 151 | checking manifests |
|
152 | 152 | crosschecking files in changesets and manifests |
|
153 | 153 | checking files |
|
154 | 154 | 2 files, 11 changesets, 11 total revisions |
|
155 | 155 | |
|
156 | 156 | Invalid dest '' with --pull must abort (issue2528): |
|
157 | 157 | |
|
158 | 158 | $ hg clone --pull a '' |
|
159 | 159 | abort: empty destination path is not valid |
|
160 | 160 | [255] |
|
161 | 161 | |
|
162 | 162 | Clone to '.': |
|
163 | 163 | |
|
164 | 164 | $ mkdir h |
|
165 | 165 | $ cd h |
|
166 | 166 | $ hg clone ../a . |
|
167 | 167 | updating to branch default |
|
168 | 168 | 2 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
169 | 169 | $ cd .. |
|
170 | 170 | |
|
171 | 171 | |
|
172 | 172 | *** Tests for option -u *** |
|
173 | 173 | |
|
174 | 174 | Adding some more history to repo a: |
|
175 | 175 | |
|
176 | 176 | $ cd a |
|
177 | 177 | $ hg tag ref1 |
|
178 | 178 | $ echo the quick brown fox >a |
|
179 | 179 | $ hg ci -m "hacked default" |
|
180 | 180 | $ hg up ref1 |
|
181 | 181 | 1 files updated, 0 files merged, 1 files removed, 0 files unresolved |
|
182 | 182 | $ hg branch stable |
|
183 | 183 | marked working directory as branch stable |
|
184 | 184 | (branches are permanent and global, did you want a bookmark?) |
|
185 | 185 | $ echo some text >a |
|
186 | 186 | $ hg ci -m "starting branch stable" |
|
187 | 187 | $ hg tag ref2 |
|
188 | 188 | $ echo some more text >a |
|
189 | 189 | $ hg ci -m "another change for branch stable" |
|
190 | 190 | $ hg up ref2 |
|
191 | 191 | 1 files updated, 0 files merged, 1 files removed, 0 files unresolved |
|
192 | 192 | $ hg parents |
|
193 | 193 | changeset: 13:e8ece76546a6 |
|
194 | 194 | branch: stable |
|
195 | 195 | tag: ref2 |
|
196 | 196 | parent: 10:a7949464abda |
|
197 | 197 | user: test |
|
198 | 198 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
199 | 199 | summary: starting branch stable |
|
200 | 200 | |
|
201 | 201 | |
|
202 | 202 | Repo a has two heads: |
|
203 | 203 | |
|
204 | 204 | $ hg heads |
|
205 | 205 | changeset: 15:0aae7cf88f0d |
|
206 | 206 | branch: stable |
|
207 | 207 | tag: tip |
|
208 | 208 | user: test |
|
209 | 209 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
210 | 210 | summary: another change for branch stable |
|
211 | 211 | |
|
212 | 212 | changeset: 12:f21241060d6a |
|
213 | 213 | user: test |
|
214 | 214 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
215 | 215 | summary: hacked default |
|
216 | 216 | |
|
217 | 217 | |
|
218 | 218 | $ cd .. |
|
219 | 219 | |
|
220 | 220 | |
|
221 | 221 | Testing --noupdate with --updaterev (must abort): |
|
222 | 222 | |
|
223 | 223 | $ hg clone --noupdate --updaterev 1 a ua |
|
224 | 224 | abort: cannot specify both --noupdate and --updaterev |
|
225 | 225 | [255] |
|
226 | 226 | |
|
227 | 227 | |
|
228 | 228 | Testing clone -u: |
|
229 | 229 | |
|
230 | 230 | $ hg clone -u . a ua |
|
231 | 231 | updating to branch stable |
|
232 | 232 | 2 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
233 | 233 | |
|
234 | 234 | Repo ua has both heads: |
|
235 | 235 | |
|
236 | 236 | $ hg -R ua heads |
|
237 | 237 | changeset: 15:0aae7cf88f0d |
|
238 | 238 | branch: stable |
|
239 | 239 | tag: tip |
|
240 | 240 | user: test |
|
241 | 241 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
242 | 242 | summary: another change for branch stable |
|
243 | 243 | |
|
244 | 244 | changeset: 12:f21241060d6a |
|
245 | 245 | user: test |
|
246 | 246 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
247 | 247 | summary: hacked default |
|
248 | 248 | |
|
249 | 249 | |
|
250 | 250 | Same revision checked out in repo a and ua: |
|
251 | 251 | |
|
252 | 252 | $ hg -R a parents --template "{node|short}\n" |
|
253 | 253 | e8ece76546a6 |
|
254 | 254 | $ hg -R ua parents --template "{node|short}\n" |
|
255 | 255 | e8ece76546a6 |
|
256 | 256 | |
|
257 | 257 | $ rm -r ua |
|
258 | 258 | |
|
259 | 259 | |
|
260 | 260 | Testing clone --pull -u: |
|
261 | 261 | |
|
262 | 262 | $ hg clone --pull -u . a ua |
|
263 | 263 | requesting all changes |
|
264 | 264 | adding changesets |
|
265 | 265 | adding manifests |
|
266 | 266 | adding file changes |
|
267 | 267 | added 16 changesets with 16 changes to 3 files (+1 heads) |
|
268 | 268 | updating to branch stable |
|
269 | 269 | 2 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
270 | 270 | |
|
271 | 271 | Repo ua has both heads: |
|
272 | 272 | |
|
273 | 273 | $ hg -R ua heads |
|
274 | 274 | changeset: 15:0aae7cf88f0d |
|
275 | 275 | branch: stable |
|
276 | 276 | tag: tip |
|
277 | 277 | user: test |
|
278 | 278 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
279 | 279 | summary: another change for branch stable |
|
280 | 280 | |
|
281 | 281 | changeset: 12:f21241060d6a |
|
282 | 282 | user: test |
|
283 | 283 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
284 | 284 | summary: hacked default |
|
285 | 285 | |
|
286 | 286 | |
|
287 | 287 | Same revision checked out in repo a and ua: |
|
288 | 288 | |
|
289 | 289 | $ hg -R a parents --template "{node|short}\n" |
|
290 | 290 | e8ece76546a6 |
|
291 | 291 | $ hg -R ua parents --template "{node|short}\n" |
|
292 | 292 | e8ece76546a6 |
|
293 | 293 | |
|
294 | 294 | $ rm -r ua |
|
295 | 295 | |
|
296 | 296 | |
|
297 | 297 | Testing clone -u <branch>: |
|
298 | 298 | |
|
299 | 299 | $ hg clone -u stable a ua |
|
300 | 300 | updating to branch stable |
|
301 | 301 | 3 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
302 | 302 | |
|
303 | 303 | Repo ua has both heads: |
|
304 | 304 | |
|
305 | 305 | $ hg -R ua heads |
|
306 | 306 | changeset: 15:0aae7cf88f0d |
|
307 | 307 | branch: stable |
|
308 | 308 | tag: tip |
|
309 | 309 | user: test |
|
310 | 310 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
311 | 311 | summary: another change for branch stable |
|
312 | 312 | |
|
313 | 313 | changeset: 12:f21241060d6a |
|
314 | 314 | user: test |
|
315 | 315 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
316 | 316 | summary: hacked default |
|
317 | 317 | |
|
318 | 318 | |
|
319 | 319 | Branch 'stable' is checked out: |
|
320 | 320 | |
|
321 | 321 | $ hg -R ua parents |
|
322 | 322 | changeset: 15:0aae7cf88f0d |
|
323 | 323 | branch: stable |
|
324 | 324 | tag: tip |
|
325 | 325 | user: test |
|
326 | 326 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
327 | 327 | summary: another change for branch stable |
|
328 | 328 | |
|
329 | 329 | |
|
330 | 330 | $ rm -r ua |
|
331 | 331 | |
|
332 | 332 | |
|
333 | 333 | Testing default checkout: |
|
334 | 334 | |
|
335 | 335 | $ hg clone a ua |
|
336 | 336 | updating to branch default |
|
337 | 337 | 3 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
338 | 338 | |
|
339 | 339 | Repo ua has both heads: |
|
340 | 340 | |
|
341 | 341 | $ hg -R ua heads |
|
342 | 342 | changeset: 15:0aae7cf88f0d |
|
343 | 343 | branch: stable |
|
344 | 344 | tag: tip |
|
345 | 345 | user: test |
|
346 | 346 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
347 | 347 | summary: another change for branch stable |
|
348 | 348 | |
|
349 | 349 | changeset: 12:f21241060d6a |
|
350 | 350 | user: test |
|
351 | 351 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
352 | 352 | summary: hacked default |
|
353 | 353 | |
|
354 | 354 | |
|
355 | 355 | Branch 'default' is checked out: |
|
356 | 356 | |
|
357 | 357 | $ hg -R ua parents |
|
358 | 358 | changeset: 12:f21241060d6a |
|
359 | 359 | user: test |
|
360 | 360 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
361 | 361 | summary: hacked default |
|
362 | 362 | |
|
363 | 363 | Test clone with a branch named "@" (issue3677) |
|
364 | 364 | |
|
365 | 365 | $ hg -R ua branch @ |
|
366 | 366 | marked working directory as branch @ |
|
367 | 367 | $ hg -R ua commit -m 'created branch @' |
|
368 | 368 | $ hg clone ua atbranch |
|
369 | 369 | updating to branch default |
|
370 | 370 | 3 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
371 | 371 | $ hg -R atbranch heads |
|
372 | 372 | changeset: 16:798b6d97153e |
|
373 | 373 | branch: @ |
|
374 | 374 | tag: tip |
|
375 | 375 | parent: 12:f21241060d6a |
|
376 | 376 | user: test |
|
377 | 377 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
378 | 378 | summary: created branch @ |
|
379 | 379 | |
|
380 | 380 | changeset: 15:0aae7cf88f0d |
|
381 | 381 | branch: stable |
|
382 | 382 | user: test |
|
383 | 383 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
384 | 384 | summary: another change for branch stable |
|
385 | 385 | |
|
386 | 386 | changeset: 12:f21241060d6a |
|
387 | 387 | user: test |
|
388 | 388 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
389 | 389 | summary: hacked default |
|
390 | 390 | |
|
391 | 391 | $ hg -R atbranch parents |
|
392 | 392 | changeset: 12:f21241060d6a |
|
393 | 393 | user: test |
|
394 | 394 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
395 | 395 | summary: hacked default |
|
396 | 396 | |
|
397 | 397 | |
|
398 | 398 | $ rm -r ua atbranch |
|
399 | 399 | |
|
400 | 400 | |
|
401 | 401 | Testing #<branch>: |
|
402 | 402 | |
|
403 | 403 | $ hg clone -u . a#stable ua |
|
404 | 404 | adding changesets |
|
405 | 405 | adding manifests |
|
406 | 406 | adding file changes |
|
407 | 407 | added 14 changesets with 14 changes to 3 files |
|
408 | 408 | updating to branch stable |
|
409 | 409 | 2 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
410 | 410 | |
|
411 | 411 | Repo ua has branch 'stable' and 'default' (was changed in fd511e9eeea6): |
|
412 | 412 | |
|
413 | 413 | $ hg -R ua heads |
|
414 | 414 | changeset: 13:0aae7cf88f0d |
|
415 | 415 | branch: stable |
|
416 | 416 | tag: tip |
|
417 | 417 | user: test |
|
418 | 418 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
419 | 419 | summary: another change for branch stable |
|
420 | 420 | |
|
421 | 421 | changeset: 10:a7949464abda |
|
422 | 422 | user: test |
|
423 | 423 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
424 | 424 | summary: test |
|
425 | 425 | |
|
426 | 426 | |
|
427 | 427 | Same revision checked out in repo a and ua: |
|
428 | 428 | |
|
429 | 429 | $ hg -R a parents --template "{node|short}\n" |
|
430 | 430 | e8ece76546a6 |
|
431 | 431 | $ hg -R ua parents --template "{node|short}\n" |
|
432 | 432 | e8ece76546a6 |
|
433 | 433 | |
|
434 | 434 | $ rm -r ua |
|
435 | 435 | |
|
436 | 436 | |
|
437 | 437 | Testing -u -r <branch>: |
|
438 | 438 | |
|
439 | 439 | $ hg clone -u . -r stable a ua |
|
440 | 440 | adding changesets |
|
441 | 441 | adding manifests |
|
442 | 442 | adding file changes |
|
443 | 443 | added 14 changesets with 14 changes to 3 files |
|
444 | 444 | updating to branch stable |
|
445 | 445 | 2 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
446 | 446 | |
|
447 | 447 | Repo ua has branch 'stable' and 'default' (was changed in fd511e9eeea6): |
|
448 | 448 | |
|
449 | 449 | $ hg -R ua heads |
|
450 | 450 | changeset: 13:0aae7cf88f0d |
|
451 | 451 | branch: stable |
|
452 | 452 | tag: tip |
|
453 | 453 | user: test |
|
454 | 454 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
455 | 455 | summary: another change for branch stable |
|
456 | 456 | |
|
457 | 457 | changeset: 10:a7949464abda |
|
458 | 458 | user: test |
|
459 | 459 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
460 | 460 | summary: test |
|
461 | 461 | |
|
462 | 462 | |
|
463 | 463 | Same revision checked out in repo a and ua: |
|
464 | 464 | |
|
465 | 465 | $ hg -R a parents --template "{node|short}\n" |
|
466 | 466 | e8ece76546a6 |
|
467 | 467 | $ hg -R ua parents --template "{node|short}\n" |
|
468 | 468 | e8ece76546a6 |
|
469 | 469 | |
|
470 | 470 | $ rm -r ua |
|
471 | 471 | |
|
472 | 472 | |
|
473 | 473 | Testing -r <branch>: |
|
474 | 474 | |
|
475 | 475 | $ hg clone -r stable a ua |
|
476 | 476 | adding changesets |
|
477 | 477 | adding manifests |
|
478 | 478 | adding file changes |
|
479 | 479 | added 14 changesets with 14 changes to 3 files |
|
480 | 480 | updating to branch stable |
|
481 | 481 | 3 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
482 | 482 | |
|
483 | 483 | Repo ua has branch 'stable' and 'default' (was changed in fd511e9eeea6): |
|
484 | 484 | |
|
485 | 485 | $ hg -R ua heads |
|
486 | 486 | changeset: 13:0aae7cf88f0d |
|
487 | 487 | branch: stable |
|
488 | 488 | tag: tip |
|
489 | 489 | user: test |
|
490 | 490 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
491 | 491 | summary: another change for branch stable |
|
492 | 492 | |
|
493 | 493 | changeset: 10:a7949464abda |
|
494 | 494 | user: test |
|
495 | 495 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
496 | 496 | summary: test |
|
497 | 497 | |
|
498 | 498 | |
|
499 | 499 | Branch 'stable' is checked out: |
|
500 | 500 | |
|
501 | 501 | $ hg -R ua parents |
|
502 | 502 | changeset: 13:0aae7cf88f0d |
|
503 | 503 | branch: stable |
|
504 | 504 | tag: tip |
|
505 | 505 | user: test |
|
506 | 506 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
507 | 507 | summary: another change for branch stable |
|
508 | 508 | |
|
509 | 509 | |
|
510 | 510 | $ rm -r ua |
|
511 | 511 | |
|
512 | 512 | |
|
513 | 513 | Issue2267: Error in 1.6 hg.py: TypeError: 'NoneType' object is not |
|
514 | 514 | iterable in addbranchrevs() |
|
515 | 515 | |
|
516 | 516 | $ cat <<EOF > simpleclone.py |
|
517 | 517 | > from mercurial import ui, hg |
|
518 | > myui = ui.ui() | |
|
518 | > myui = ui.ui.load() | |
|
519 | 519 | > repo = hg.repository(myui, 'a') |
|
520 | 520 | > hg.clone(myui, {}, repo, dest="ua") |
|
521 | 521 | > EOF |
|
522 | 522 | |
|
523 | 523 | $ python simpleclone.py |
|
524 | 524 | updating to branch default |
|
525 | 525 | 3 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
526 | 526 | |
|
527 | 527 | $ rm -r ua |
|
528 | 528 | |
|
529 | 529 | $ cat <<EOF > branchclone.py |
|
530 | 530 | > from mercurial import ui, hg, extensions |
|
531 | > myui = ui.ui() | |
|
531 | > myui = ui.ui.load() | |
|
532 | 532 | > extensions.loadall(myui) |
|
533 | 533 | > repo = hg.repository(myui, 'a') |
|
534 | 534 | > hg.clone(myui, {}, repo, dest="ua", branch=["stable",]) |
|
535 | 535 | > EOF |
|
536 | 536 | |
|
537 | 537 | $ python branchclone.py |
|
538 | 538 | adding changesets |
|
539 | 539 | adding manifests |
|
540 | 540 | adding file changes |
|
541 | 541 | added 14 changesets with 14 changes to 3 files |
|
542 | 542 | updating to branch stable |
|
543 | 543 | 3 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
544 | 544 | $ rm -r ua |
|
545 | 545 | |
|
546 | 546 | |
|
547 | 547 | Test clone with special '@' bookmark: |
|
548 | 548 | $ cd a |
|
549 | 549 | $ hg bookmark -r a7949464abda @ # branch point of stable from default |
|
550 | 550 | $ hg clone . ../i |
|
551 | 551 | updating to bookmark @ |
|
552 | 552 | 2 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
553 | 553 | $ hg id -i ../i |
|
554 | 554 | a7949464abda |
|
555 | 555 | $ rm -r ../i |
|
556 | 556 | |
|
557 | 557 | $ hg bookmark -f -r stable @ |
|
558 | 558 | $ hg bookmarks |
|
559 | 559 | @ 15:0aae7cf88f0d |
|
560 | 560 | $ hg clone . ../i |
|
561 | 561 | updating to bookmark @ on branch stable |
|
562 | 562 | 3 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
563 | 563 | $ hg id -i ../i |
|
564 | 564 | 0aae7cf88f0d |
|
565 | 565 | $ cd "$TESTTMP" |
|
566 | 566 | |
|
567 | 567 | |
|
568 | 568 | Testing failures: |
|
569 | 569 | |
|
570 | 570 | $ mkdir fail |
|
571 | 571 | $ cd fail |
|
572 | 572 | |
|
573 | 573 | No local source |
|
574 | 574 | |
|
575 | 575 | $ hg clone a b |
|
576 | 576 | abort: repository a not found! |
|
577 | 577 | [255] |
|
578 | 578 | |
|
579 | 579 | No remote source |
|
580 | 580 | |
|
581 | 581 | #if windows |
|
582 | 582 | $ hg clone http://127.0.0.1:3121/a b |
|
583 | 583 | abort: error: * (glob) |
|
584 | 584 | [255] |
|
585 | 585 | #else |
|
586 | 586 | $ hg clone http://127.0.0.1:3121/a b |
|
587 | 587 | abort: error: *refused* (glob) |
|
588 | 588 | [255] |
|
589 | 589 | #endif |
|
590 | 590 | $ rm -rf b # work around bug with http clone |
|
591 | 591 | |
|
592 | 592 | |
|
593 | 593 | #if unix-permissions no-root |
|
594 | 594 | |
|
595 | 595 | Inaccessible source |
|
596 | 596 | |
|
597 | 597 | $ mkdir a |
|
598 | 598 | $ chmod 000 a |
|
599 | 599 | $ hg clone a b |
|
600 | 600 | abort: repository a not found! |
|
601 | 601 | [255] |
|
602 | 602 | |
|
603 | 603 | Inaccessible destination |
|
604 | 604 | |
|
605 | 605 | $ hg init b |
|
606 | 606 | $ cd b |
|
607 | 607 | $ hg clone . ../a |
|
608 | 608 | abort: Permission denied: '../a' |
|
609 | 609 | [255] |
|
610 | 610 | $ cd .. |
|
611 | 611 | $ chmod 700 a |
|
612 | 612 | $ rm -r a b |
|
613 | 613 | |
|
614 | 614 | #endif |
|
615 | 615 | |
|
616 | 616 | |
|
617 | 617 | #if fifo |
|
618 | 618 | |
|
619 | 619 | Source of wrong type |
|
620 | 620 | |
|
621 | 621 | $ mkfifo a |
|
622 | 622 | $ hg clone a b |
|
623 | 623 | abort: repository a not found! |
|
624 | 624 | [255] |
|
625 | 625 | $ rm a |
|
626 | 626 | |
|
627 | 627 | #endif |
|
628 | 628 | |
|
629 | 629 | Default destination, same directory |
|
630 | 630 | |
|
631 | 631 | $ hg init q |
|
632 | 632 | $ hg clone q |
|
633 | 633 | destination directory: q |
|
634 | 634 | abort: destination 'q' is not empty |
|
635 | 635 | [255] |
|
636 | 636 | |
|
637 | 637 | destination directory not empty |
|
638 | 638 | |
|
639 | 639 | $ mkdir a |
|
640 | 640 | $ echo stuff > a/a |
|
641 | 641 | $ hg clone q a |
|
642 | 642 | abort: destination 'a' is not empty |
|
643 | 643 | [255] |
|
644 | 644 | |
|
645 | 645 | |
|
646 | 646 | #if unix-permissions no-root |
|
647 | 647 | |
|
648 | 648 | leave existing directory in place after clone failure |
|
649 | 649 | |
|
650 | 650 | $ hg init c |
|
651 | 651 | $ cd c |
|
652 | 652 | $ echo c > c |
|
653 | 653 | $ hg commit -A -m test |
|
654 | 654 | adding c |
|
655 | 655 | $ chmod -rx .hg/store/data |
|
656 | 656 | $ cd .. |
|
657 | 657 | $ mkdir d |
|
658 | 658 | $ hg clone c d 2> err |
|
659 | 659 | [255] |
|
660 | 660 | $ test -d d |
|
661 | 661 | $ test -d d/.hg |
|
662 | 662 | [1] |
|
663 | 663 | |
|
664 | 664 | re-enable perm to allow deletion |
|
665 | 665 | |
|
666 | 666 | $ chmod +rx c/.hg/store/data |
|
667 | 667 | |
|
668 | 668 | #endif |
|
669 | 669 | |
|
670 | 670 | $ cd .. |
|
671 | 671 | |
|
672 | 672 | Test clone from the repository in (emulated) revlog format 0 (issue4203): |
|
673 | 673 | |
|
674 | 674 | $ mkdir issue4203 |
|
675 | 675 | $ mkdir -p src/.hg |
|
676 | 676 | $ echo foo > src/foo |
|
677 | 677 | $ hg -R src add src/foo |
|
678 | 678 | $ hg -R src commit -m '#0' |
|
679 | 679 | $ hg -R src log -q |
|
680 | 680 | 0:e1bab28bca43 |
|
681 | 681 | $ hg clone -U -q src dst |
|
682 | 682 | $ hg -R dst log -q |
|
683 | 683 | 0:e1bab28bca43 |
|
684 | 684 | |
|
685 | 685 | Create repositories to test auto sharing functionality |
|
686 | 686 | |
|
687 | 687 | $ cat >> $HGRCPATH << EOF |
|
688 | 688 | > [extensions] |
|
689 | 689 | > share= |
|
690 | 690 | > EOF |
|
691 | 691 | |
|
692 | 692 | $ hg init empty |
|
693 | 693 | $ hg init source1a |
|
694 | 694 | $ cd source1a |
|
695 | 695 | $ echo initial1 > foo |
|
696 | 696 | $ hg -q commit -A -m initial |
|
697 | 697 | $ echo second > foo |
|
698 | 698 | $ hg commit -m second |
|
699 | 699 | $ cd .. |
|
700 | 700 | |
|
701 | 701 | $ hg init filteredrev0 |
|
702 | 702 | $ cd filteredrev0 |
|
703 | 703 | $ cat >> .hg/hgrc << EOF |
|
704 | 704 | > [experimental] |
|
705 | 705 | > evolution=createmarkers |
|
706 | 706 | > EOF |
|
707 | 707 | $ echo initial1 > foo |
|
708 | 708 | $ hg -q commit -A -m initial0 |
|
709 | 709 | $ hg -q up -r null |
|
710 | 710 | $ echo initial2 > foo |
|
711 | 711 | $ hg -q commit -A -m initial1 |
|
712 | 712 | $ hg debugobsolete c05d5c47a5cf81401869999f3d05f7d699d2b29a e082c1832e09a7d1e78b7fd49a592d372de854c8 |
|
713 | 713 | $ cd .. |
|
714 | 714 | |
|
715 | 715 | $ hg -q clone --pull source1a source1b |
|
716 | 716 | $ cd source1a |
|
717 | 717 | $ hg bookmark bookA |
|
718 | 718 | $ echo 1a > foo |
|
719 | 719 | $ hg commit -m 1a |
|
720 | 720 | $ cd ../source1b |
|
721 | 721 | $ hg -q up -r 0 |
|
722 | 722 | $ echo head1 > foo |
|
723 | 723 | $ hg commit -m head1 |
|
724 | 724 | created new head |
|
725 | 725 | $ hg bookmark head1 |
|
726 | 726 | $ hg -q up -r 0 |
|
727 | 727 | $ echo head2 > foo |
|
728 | 728 | $ hg commit -m head2 |
|
729 | 729 | created new head |
|
730 | 730 | $ hg bookmark head2 |
|
731 | 731 | $ hg -q up -r 0 |
|
732 | 732 | $ hg branch branch1 |
|
733 | 733 | marked working directory as branch branch1 |
|
734 | 734 | (branches are permanent and global, did you want a bookmark?) |
|
735 | 735 | $ echo branch1 > foo |
|
736 | 736 | $ hg commit -m branch1 |
|
737 | 737 | $ hg -q up -r 0 |
|
738 | 738 | $ hg branch branch2 |
|
739 | 739 | marked working directory as branch branch2 |
|
740 | 740 | $ echo branch2 > foo |
|
741 | 741 | $ hg commit -m branch2 |
|
742 | 742 | $ cd .. |
|
743 | 743 | $ hg init source2 |
|
744 | 744 | $ cd source2 |
|
745 | 745 | $ echo initial2 > foo |
|
746 | 746 | $ hg -q commit -A -m initial2 |
|
747 | 747 | $ echo second > foo |
|
748 | 748 | $ hg commit -m second |
|
749 | 749 | $ cd .. |
|
750 | 750 | |
|
751 | 751 | Clone with auto share from an empty repo should not result in share |
|
752 | 752 | |
|
753 | 753 | $ mkdir share |
|
754 | 754 | $ hg --config share.pool=share clone empty share-empty |
|
755 | 755 | (not using pooled storage: remote appears to be empty) |
|
756 | 756 | updating to branch default |
|
757 | 757 | 0 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
758 | 758 | $ ls share |
|
759 | 759 | $ test -d share-empty/.hg/store |
|
760 | 760 | $ test -f share-empty/.hg/sharedpath |
|
761 | 761 | [1] |
|
762 | 762 | |
|
763 | 763 | Clone with auto share from a repo with filtered revision 0 should not result in share |
|
764 | 764 | |
|
765 | 765 | $ hg --config share.pool=share clone filteredrev0 share-filtered |
|
766 | 766 | (not using pooled storage: unable to resolve identity of remote) |
|
767 | 767 | requesting all changes |
|
768 | 768 | adding changesets |
|
769 | 769 | adding manifests |
|
770 | 770 | adding file changes |
|
771 | 771 | added 1 changesets with 1 changes to 1 files |
|
772 | 772 | updating to branch default |
|
773 | 773 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
774 | 774 | |
|
775 | 775 | Clone from repo with content should result in shared store being created |
|
776 | 776 | |
|
777 | 777 | $ hg --config share.pool=share clone source1a share-dest1a |
|
778 | 778 | (sharing from new pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1) |
|
779 | 779 | requesting all changes |
|
780 | 780 | adding changesets |
|
781 | 781 | adding manifests |
|
782 | 782 | adding file changes |
|
783 | 783 | added 3 changesets with 3 changes to 1 files |
|
784 | 784 | searching for changes |
|
785 | 785 | no changes found |
|
786 | 786 | adding remote bookmark bookA |
|
787 | 787 | updating working directory |
|
788 | 788 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
789 | 789 | |
|
790 | 790 | The shared repo should have been created |
|
791 | 791 | |
|
792 | 792 | $ ls share |
|
793 | 793 | b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1 |
|
794 | 794 | |
|
795 | 795 | The destination should point to it |
|
796 | 796 | |
|
797 | 797 | $ cat share-dest1a/.hg/sharedpath; echo |
|
798 | 798 | $TESTTMP/share/b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1/.hg (glob) |
|
799 | 799 | |
|
800 | 800 | The destination should have bookmarks |
|
801 | 801 | |
|
802 | 802 | $ hg -R share-dest1a bookmarks |
|
803 | 803 | bookA 2:e5bfe23c0b47 |
|
804 | 804 | |
|
805 | 805 | The default path should be the remote, not the share |
|
806 | 806 | |
|
807 | 807 | $ hg -R share-dest1a config paths.default |
|
808 | 808 | $TESTTMP/source1a (glob) |
|
809 | 809 | |
|
810 | 810 | Clone with existing share dir should result in pull + share |
|
811 | 811 | |
|
812 | 812 | $ hg --config share.pool=share clone source1b share-dest1b |
|
813 | 813 | (sharing from existing pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1) |
|
814 | 814 | searching for changes |
|
815 | 815 | adding changesets |
|
816 | 816 | adding manifests |
|
817 | 817 | adding file changes |
|
818 | 818 | added 4 changesets with 4 changes to 1 files (+4 heads) |
|
819 | 819 | adding remote bookmark head1 |
|
820 | 820 | adding remote bookmark head2 |
|
821 | 821 | updating working directory |
|
822 | 822 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
823 | 823 | |
|
824 | 824 | $ ls share |
|
825 | 825 | b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1 |
|
826 | 826 | |
|
827 | 827 | $ cat share-dest1b/.hg/sharedpath; echo |
|
828 | 828 | $TESTTMP/share/b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1/.hg (glob) |
|
829 | 829 | |
|
830 | 830 | We only get bookmarks from the remote, not everything in the share |
|
831 | 831 | |
|
832 | 832 | $ hg -R share-dest1b bookmarks |
|
833 | 833 | head1 3:4a8dc1ab4c13 |
|
834 | 834 | head2 4:99f71071f117 |
|
835 | 835 | |
|
836 | 836 | Default path should be source, not share. |
|
837 | 837 | |
|
838 | 838 | $ hg -R share-dest1b config paths.default |
|
839 | 839 | $TESTTMP/source1b (glob) |
|
840 | 840 | |
|
841 | 841 | Checked out revision should be head of default branch |
|
842 | 842 | |
|
843 | 843 | $ hg -R share-dest1b log -r . |
|
844 | 844 | changeset: 4:99f71071f117 |
|
845 | 845 | bookmark: head2 |
|
846 | 846 | parent: 0:b5f04eac9d8f |
|
847 | 847 | user: test |
|
848 | 848 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
849 | 849 | summary: head2 |
|
850 | 850 | |
|
851 | 851 | |
|
852 | 852 | Clone from unrelated repo should result in new share |
|
853 | 853 | |
|
854 | 854 | $ hg --config share.pool=share clone source2 share-dest2 |
|
855 | 855 | (sharing from new pooled repository 22aeff664783fd44c6d9b435618173c118c3448e) |
|
856 | 856 | requesting all changes |
|
857 | 857 | adding changesets |
|
858 | 858 | adding manifests |
|
859 | 859 | adding file changes |
|
860 | 860 | added 2 changesets with 2 changes to 1 files |
|
861 | 861 | searching for changes |
|
862 | 862 | no changes found |
|
863 | 863 | updating working directory |
|
864 | 864 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
865 | 865 | |
|
866 | 866 | $ ls share |
|
867 | 867 | 22aeff664783fd44c6d9b435618173c118c3448e |
|
868 | 868 | b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1 |
|
869 | 869 | |
|
870 | 870 | remote naming mode works as advertised |
|
871 | 871 | |
|
872 | 872 | $ hg --config share.pool=shareremote --config share.poolnaming=remote clone source1a share-remote1a |
|
873 | 873 | (sharing from new pooled repository 195bb1fcdb595c14a6c13e0269129ed78f6debde) |
|
874 | 874 | requesting all changes |
|
875 | 875 | adding changesets |
|
876 | 876 | adding manifests |
|
877 | 877 | adding file changes |
|
878 | 878 | added 3 changesets with 3 changes to 1 files |
|
879 | 879 | searching for changes |
|
880 | 880 | no changes found |
|
881 | 881 | adding remote bookmark bookA |
|
882 | 882 | updating working directory |
|
883 | 883 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
884 | 884 | |
|
885 | 885 | $ ls shareremote |
|
886 | 886 | 195bb1fcdb595c14a6c13e0269129ed78f6debde |
|
887 | 887 | |
|
888 | 888 | $ hg --config share.pool=shareremote --config share.poolnaming=remote clone source1b share-remote1b |
|
889 | 889 | (sharing from new pooled repository c0d4f83847ca2a873741feb7048a45085fd47c46) |
|
890 | 890 | requesting all changes |
|
891 | 891 | adding changesets |
|
892 | 892 | adding manifests |
|
893 | 893 | adding file changes |
|
894 | 894 | added 6 changesets with 6 changes to 1 files (+4 heads) |
|
895 | 895 | searching for changes |
|
896 | 896 | no changes found |
|
897 | 897 | adding remote bookmark head1 |
|
898 | 898 | adding remote bookmark head2 |
|
899 | 899 | updating working directory |
|
900 | 900 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
901 | 901 | |
|
902 | 902 | $ ls shareremote |
|
903 | 903 | 195bb1fcdb595c14a6c13e0269129ed78f6debde |
|
904 | 904 | c0d4f83847ca2a873741feb7048a45085fd47c46 |
|
905 | 905 | |
|
906 | 906 | request to clone a single revision is respected in sharing mode |
|
907 | 907 | |
|
908 | 908 | $ hg --config share.pool=sharerevs clone -r 4a8dc1ab4c13 source1b share-1arev |
|
909 | 909 | (sharing from new pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1) |
|
910 | 910 | adding changesets |
|
911 | 911 | adding manifests |
|
912 | 912 | adding file changes |
|
913 | 913 | added 2 changesets with 2 changes to 1 files |
|
914 | 914 | no changes found |
|
915 | 915 | adding remote bookmark head1 |
|
916 | 916 | updating working directory |
|
917 | 917 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
918 | 918 | |
|
919 | 919 | $ hg -R share-1arev log -G |
|
920 | 920 | @ changeset: 1:4a8dc1ab4c13 |
|
921 | 921 | | bookmark: head1 |
|
922 | 922 | | tag: tip |
|
923 | 923 | | user: test |
|
924 | 924 | | date: Thu Jan 01 00:00:00 1970 +0000 |
|
925 | 925 | | summary: head1 |
|
926 | 926 | | |
|
927 | 927 | o changeset: 0:b5f04eac9d8f |
|
928 | 928 | user: test |
|
929 | 929 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
930 | 930 | summary: initial |
|
931 | 931 | |
|
932 | 932 | |
|
933 | 933 | making another clone should only pull down requested rev |
|
934 | 934 | |
|
935 | 935 | $ hg --config share.pool=sharerevs clone -r 99f71071f117 source1b share-1brev |
|
936 | 936 | (sharing from existing pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1) |
|
937 | 937 | searching for changes |
|
938 | 938 | adding changesets |
|
939 | 939 | adding manifests |
|
940 | 940 | adding file changes |
|
941 | 941 | added 1 changesets with 1 changes to 1 files (+1 heads) |
|
942 | 942 | adding remote bookmark head1 |
|
943 | 943 | adding remote bookmark head2 |
|
944 | 944 | updating working directory |
|
945 | 945 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
946 | 946 | |
|
947 | 947 | $ hg -R share-1brev log -G |
|
948 | 948 | @ changeset: 2:99f71071f117 |
|
949 | 949 | | bookmark: head2 |
|
950 | 950 | | tag: tip |
|
951 | 951 | | parent: 0:b5f04eac9d8f |
|
952 | 952 | | user: test |
|
953 | 953 | | date: Thu Jan 01 00:00:00 1970 +0000 |
|
954 | 954 | | summary: head2 |
|
955 | 955 | | |
|
956 | 956 | | o changeset: 1:4a8dc1ab4c13 |
|
957 | 957 | |/ bookmark: head1 |
|
958 | 958 | | user: test |
|
959 | 959 | | date: Thu Jan 01 00:00:00 1970 +0000 |
|
960 | 960 | | summary: head1 |
|
961 | 961 | | |
|
962 | 962 | o changeset: 0:b5f04eac9d8f |
|
963 | 963 | user: test |
|
964 | 964 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
965 | 965 | summary: initial |
|
966 | 966 | |
|
967 | 967 | |
|
968 | 968 | Request to clone a single branch is respected in sharing mode |
|
969 | 969 | |
|
970 | 970 | $ hg --config share.pool=sharebranch clone -b branch1 source1b share-1bbranch1 |
|
971 | 971 | (sharing from new pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1) |
|
972 | 972 | adding changesets |
|
973 | 973 | adding manifests |
|
974 | 974 | adding file changes |
|
975 | 975 | added 2 changesets with 2 changes to 1 files |
|
976 | 976 | no changes found |
|
977 | 977 | updating working directory |
|
978 | 978 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
979 | 979 | |
|
980 | 980 | $ hg -R share-1bbranch1 log -G |
|
981 | 981 | o changeset: 1:5f92a6c1a1b1 |
|
982 | 982 | | branch: branch1 |
|
983 | 983 | | tag: tip |
|
984 | 984 | | user: test |
|
985 | 985 | | date: Thu Jan 01 00:00:00 1970 +0000 |
|
986 | 986 | | summary: branch1 |
|
987 | 987 | | |
|
988 | 988 | @ changeset: 0:b5f04eac9d8f |
|
989 | 989 | user: test |
|
990 | 990 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
991 | 991 | summary: initial |
|
992 | 992 | |
|
993 | 993 | |
|
994 | 994 | $ hg --config share.pool=sharebranch clone -b branch2 source1b share-1bbranch2 |
|
995 | 995 | (sharing from existing pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1) |
|
996 | 996 | searching for changes |
|
997 | 997 | adding changesets |
|
998 | 998 | adding manifests |
|
999 | 999 | adding file changes |
|
1000 | 1000 | added 1 changesets with 1 changes to 1 files (+1 heads) |
|
1001 | 1001 | updating working directory |
|
1002 | 1002 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
1003 | 1003 | |
|
1004 | 1004 | $ hg -R share-1bbranch2 log -G |
|
1005 | 1005 | o changeset: 2:6bacf4683960 |
|
1006 | 1006 | | branch: branch2 |
|
1007 | 1007 | | tag: tip |
|
1008 | 1008 | | parent: 0:b5f04eac9d8f |
|
1009 | 1009 | | user: test |
|
1010 | 1010 | | date: Thu Jan 01 00:00:00 1970 +0000 |
|
1011 | 1011 | | summary: branch2 |
|
1012 | 1012 | | |
|
1013 | 1013 | | o changeset: 1:5f92a6c1a1b1 |
|
1014 | 1014 | |/ branch: branch1 |
|
1015 | 1015 | | user: test |
|
1016 | 1016 | | date: Thu Jan 01 00:00:00 1970 +0000 |
|
1017 | 1017 | | summary: branch1 |
|
1018 | 1018 | | |
|
1019 | 1019 | @ changeset: 0:b5f04eac9d8f |
|
1020 | 1020 | user: test |
|
1021 | 1021 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
1022 | 1022 | summary: initial |
|
1023 | 1023 | |
|
1024 | 1024 | |
|
1025 | 1025 | -U is respected in share clone mode |
|
1026 | 1026 | |
|
1027 | 1027 | $ hg --config share.pool=share clone -U source1a share-1anowc |
|
1028 | 1028 | (sharing from existing pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1) |
|
1029 | 1029 | searching for changes |
|
1030 | 1030 | no changes found |
|
1031 | 1031 | adding remote bookmark bookA |
|
1032 | 1032 | |
|
1033 | 1033 | $ ls share-1anowc |
|
1034 | 1034 | |
|
1035 | 1035 | Test that auto sharing doesn't cause failure of "hg clone local remote" |
|
1036 | 1036 | |
|
1037 | 1037 | $ cd $TESTTMP |
|
1038 | 1038 | $ hg -R a id -r 0 |
|
1039 | 1039 | acb14030fe0a |
|
1040 | 1040 | $ hg id -R remote -r 0 |
|
1041 | 1041 | abort: repository remote not found! |
|
1042 | 1042 | [255] |
|
1043 | 1043 | $ hg --config share.pool=share -q clone -e "python \"$TESTDIR/dummyssh\"" a ssh://user@dummy/remote |
|
1044 | 1044 | $ hg -R remote id -r 0 |
|
1045 | 1045 | acb14030fe0a |
|
1046 | 1046 | |
|
1047 | 1047 | Cloning into pooled storage doesn't race (issue5104) |
|
1048 | 1048 | |
|
1049 | 1049 | $ HGPOSTLOCKDELAY=2.0 hg --config share.pool=racepool --config extensions.lockdelay=$TESTDIR/lockdelay.py clone source1a share-destrace1 > race1.log 2>&1 & |
|
1050 | 1050 | $ HGPRELOCKDELAY=1.0 hg --config share.pool=racepool --config extensions.lockdelay=$TESTDIR/lockdelay.py clone source1a share-destrace2 > race2.log 2>&1 |
|
1051 | 1051 | $ wait |
|
1052 | 1052 | |
|
1053 | 1053 | $ hg -R share-destrace1 log -r tip |
|
1054 | 1054 | changeset: 2:e5bfe23c0b47 |
|
1055 | 1055 | bookmark: bookA |
|
1056 | 1056 | tag: tip |
|
1057 | 1057 | user: test |
|
1058 | 1058 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
1059 | 1059 | summary: 1a |
|
1060 | 1060 | |
|
1061 | 1061 | |
|
1062 | 1062 | $ hg -R share-destrace2 log -r tip |
|
1063 | 1063 | changeset: 2:e5bfe23c0b47 |
|
1064 | 1064 | bookmark: bookA |
|
1065 | 1065 | tag: tip |
|
1066 | 1066 | user: test |
|
1067 | 1067 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
1068 | 1068 | summary: 1a |
|
1069 | 1069 | |
|
1070 | 1070 | One repo should be new, the other should be shared from the pool. We |
|
1071 | 1071 | don't care which is which, so we just make sure we always print the |
|
1072 | 1072 | one containing "new pooled" first, then one one containing "existing |
|
1073 | 1073 | pooled". |
|
1074 | 1074 | |
|
1075 | 1075 | $ (grep 'new pooled' race1.log > /dev/null && cat race1.log || cat race2.log) | grep -v lock |
|
1076 | 1076 | (sharing from new pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1) |
|
1077 | 1077 | requesting all changes |
|
1078 | 1078 | adding changesets |
|
1079 | 1079 | adding manifests |
|
1080 | 1080 | adding file changes |
|
1081 | 1081 | added 3 changesets with 3 changes to 1 files |
|
1082 | 1082 | searching for changes |
|
1083 | 1083 | no changes found |
|
1084 | 1084 | adding remote bookmark bookA |
|
1085 | 1085 | updating working directory |
|
1086 | 1086 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
1087 | 1087 | |
|
1088 | 1088 | $ (grep 'existing pooled' race1.log > /dev/null && cat race1.log || cat race2.log) | grep -v lock |
|
1089 | 1089 | (sharing from existing pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1) |
|
1090 | 1090 | searching for changes |
|
1091 | 1091 | no changes found |
|
1092 | 1092 | adding remote bookmark bookA |
|
1093 | 1093 | updating working directory |
|
1094 | 1094 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
@@ -1,407 +1,407 | |||
|
1 | 1 | #require tic |
|
2 | 2 | |
|
3 | 3 | Set up a repo |
|
4 | 4 | |
|
5 | 5 | $ cp $HGRCPATH $HGRCPATH.pretest |
|
6 | 6 | $ cat <<EOF >> $HGRCPATH |
|
7 | 7 | > [ui] |
|
8 | 8 | > interactive = true |
|
9 | 9 | > interface = curses |
|
10 | 10 | > [experimental] |
|
11 | 11 | > crecordtest = testModeCommands |
|
12 | 12 | > EOF |
|
13 | 13 | |
|
14 | 14 | Record with noeol at eof (issue5268) |
|
15 | 15 | $ hg init noeol |
|
16 | 16 | $ cd noeol |
|
17 | 17 | $ printf '0' > a |
|
18 | 18 | $ printf '0\n' > b |
|
19 | 19 | $ hg ci -Aqm initial |
|
20 | 20 | $ printf '1\n0' > a |
|
21 | 21 | $ printf '1\n0\n' > b |
|
22 | 22 | $ cat <<EOF >testModeCommands |
|
23 | 23 | > c |
|
24 | 24 | > EOF |
|
25 | 25 | $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -m "add hunks" -d "0 0" |
|
26 | 26 | $ cd .. |
|
27 | 27 | |
|
28 | 28 | Normal repo |
|
29 | 29 | $ hg init a |
|
30 | 30 | $ cd a |
|
31 | 31 | |
|
32 | 32 | Committing some changes but stopping on the way |
|
33 | 33 | |
|
34 | 34 | $ echo "a" > a |
|
35 | 35 | $ hg add a |
|
36 | 36 | $ cat <<EOF >testModeCommands |
|
37 | 37 | > TOGGLE |
|
38 | 38 | > X |
|
39 | 39 | > EOF |
|
40 | 40 | $ hg commit -i -m "a" -d "0 0" |
|
41 | 41 | no changes to record |
|
42 | 42 | [1] |
|
43 | 43 | $ hg tip |
|
44 | 44 | changeset: -1:000000000000 |
|
45 | 45 | tag: tip |
|
46 | 46 | user: |
|
47 | 47 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
48 | 48 | |
|
49 | 49 | |
|
50 | 50 | Committing some changes |
|
51 | 51 | |
|
52 | 52 | $ cat <<EOF >testModeCommands |
|
53 | 53 | > X |
|
54 | 54 | > EOF |
|
55 | 55 | $ hg commit -i -m "a" -d "0 0" |
|
56 | 56 | $ hg tip |
|
57 | 57 | changeset: 0:cb9a9f314b8b |
|
58 | 58 | tag: tip |
|
59 | 59 | user: test |
|
60 | 60 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
61 | 61 | summary: a |
|
62 | 62 | |
|
63 | 63 | Check that commit -i works with no changes |
|
64 | 64 | $ hg commit -i |
|
65 | 65 | no changes to record |
|
66 | 66 | [1] |
|
67 | 67 | |
|
68 | 68 | Committing only one file |
|
69 | 69 | |
|
70 | 70 | $ echo "a" >> a |
|
71 | 71 | >>> open('b', 'wb').write("1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n") |
|
72 | 72 | $ hg add b |
|
73 | 73 | $ cat <<EOF >testModeCommands |
|
74 | 74 | > TOGGLE |
|
75 | 75 | > KEY_DOWN |
|
76 | 76 | > X |
|
77 | 77 | > EOF |
|
78 | 78 | $ hg commit -i -m "one file" -d "0 0" |
|
79 | 79 | $ hg tip |
|
80 | 80 | changeset: 1:fb2705a663ea |
|
81 | 81 | tag: tip |
|
82 | 82 | user: test |
|
83 | 83 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
84 | 84 | summary: one file |
|
85 | 85 | |
|
86 | 86 | $ hg cat -r tip a |
|
87 | 87 | a |
|
88 | 88 | $ cat a |
|
89 | 89 | a |
|
90 | 90 | a |
|
91 | 91 | |
|
92 | 92 | Committing only one hunk while aborting edition of hunk |
|
93 | 93 | |
|
94 | 94 | - Untoggle all the hunks, go down to the second file |
|
95 | 95 | - unfold it |
|
96 | 96 | - go down to second hunk (1 for the first hunk, 1 for the first hunkline, 1 for the second hunk, 1 for the second hunklike) |
|
97 | 97 | - toggle the second hunk |
|
98 | 98 | - toggle on and off the amend mode (to check that it toggles off) |
|
99 | 99 | - edit the hunk and quit the editor immediately with non-zero status |
|
100 | 100 | - commit |
|
101 | 101 | |
|
102 | 102 | $ printf "printf 'editor ran\n'; exit 1" > editor.sh |
|
103 | 103 | $ echo "x" > c |
|
104 | 104 | $ cat b >> c |
|
105 | 105 | $ echo "y" >> c |
|
106 | 106 | $ mv c b |
|
107 | 107 | $ cat <<EOF >testModeCommands |
|
108 | 108 | > A |
|
109 | 109 | > KEY_DOWN |
|
110 | 110 | > f |
|
111 | 111 | > KEY_DOWN |
|
112 | 112 | > KEY_DOWN |
|
113 | 113 | > KEY_DOWN |
|
114 | 114 | > KEY_DOWN |
|
115 | 115 | > TOGGLE |
|
116 | 116 | > a |
|
117 | 117 | > a |
|
118 | 118 | > e |
|
119 | 119 | > X |
|
120 | 120 | > EOF |
|
121 | 121 | $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -m "one hunk" -d "0 0" |
|
122 | 122 | editor ran |
|
123 | 123 | $ rm editor.sh |
|
124 | 124 | $ hg tip |
|
125 | 125 | changeset: 2:7d10dfe755a8 |
|
126 | 126 | tag: tip |
|
127 | 127 | user: test |
|
128 | 128 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
129 | 129 | summary: one hunk |
|
130 | 130 | |
|
131 | 131 | $ hg cat -r tip b |
|
132 | 132 | 1 |
|
133 | 133 | 2 |
|
134 | 134 | 3 |
|
135 | 135 | 4 |
|
136 | 136 | 5 |
|
137 | 137 | 6 |
|
138 | 138 | 7 |
|
139 | 139 | 8 |
|
140 | 140 | 9 |
|
141 | 141 | 10 |
|
142 | 142 | y |
|
143 | 143 | $ cat b |
|
144 | 144 | x |
|
145 | 145 | 1 |
|
146 | 146 | 2 |
|
147 | 147 | 3 |
|
148 | 148 | 4 |
|
149 | 149 | 5 |
|
150 | 150 | 6 |
|
151 | 151 | 7 |
|
152 | 152 | 8 |
|
153 | 153 | 9 |
|
154 | 154 | 10 |
|
155 | 155 | y |
|
156 | 156 | $ hg commit -m "other hunks" |
|
157 | 157 | $ hg tip |
|
158 | 158 | changeset: 3:a6735021574d |
|
159 | 159 | tag: tip |
|
160 | 160 | user: test |
|
161 | 161 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
162 | 162 | summary: other hunks |
|
163 | 163 | |
|
164 | 164 | $ hg cat -r tip b |
|
165 | 165 | x |
|
166 | 166 | 1 |
|
167 | 167 | 2 |
|
168 | 168 | 3 |
|
169 | 169 | 4 |
|
170 | 170 | 5 |
|
171 | 171 | 6 |
|
172 | 172 | 7 |
|
173 | 173 | 8 |
|
174 | 174 | 9 |
|
175 | 175 | 10 |
|
176 | 176 | y |
|
177 | 177 | |
|
178 | 178 | Newly added files can be selected with the curses interface |
|
179 | 179 | |
|
180 | 180 | $ hg update -C . |
|
181 | 181 | 0 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
182 | 182 | $ echo "hello" > x |
|
183 | 183 | $ hg add x |
|
184 | 184 | $ cat <<EOF >testModeCommands |
|
185 | 185 | > TOGGLE |
|
186 | 186 | > TOGGLE |
|
187 | 187 | > X |
|
188 | 188 | > EOF |
|
189 | 189 | $ hg st |
|
190 | 190 | A x |
|
191 | 191 | ? testModeCommands |
|
192 | 192 | $ hg commit -i -m "newly added file" -d "0 0" |
|
193 | 193 | $ hg st |
|
194 | 194 | ? testModeCommands |
|
195 | 195 | |
|
196 | 196 | Amend option works |
|
197 | 197 | $ echo "hello world" > x |
|
198 | 198 | $ hg diff -c . |
|
199 | 199 | diff -r a6735021574d -r 2b0e9be4d336 x |
|
200 | 200 | --- /dev/null Thu Jan 01 00:00:00 1970 +0000 |
|
201 | 201 | +++ b/x Thu Jan 01 00:00:00 1970 +0000 |
|
202 | 202 | @@ -0,0 +1,1 @@ |
|
203 | 203 | +hello |
|
204 | 204 | $ cat <<EOF >testModeCommands |
|
205 | 205 | > a |
|
206 | 206 | > X |
|
207 | 207 | > EOF |
|
208 | 208 | $ hg commit -i -m "newly added file" -d "0 0" |
|
209 | 209 | saved backup bundle to $TESTTMP/a/.hg/strip-backup/2b0e9be4d336-28bbe4e2-amend-backup.hg (glob) |
|
210 | 210 | $ hg diff -c . |
|
211 | 211 | diff -r a6735021574d -r c1d239d165ae x |
|
212 | 212 | --- /dev/null Thu Jan 01 00:00:00 1970 +0000 |
|
213 | 213 | +++ b/x Thu Jan 01 00:00:00 1970 +0000 |
|
214 | 214 | @@ -0,0 +1,1 @@ |
|
215 | 215 | +hello world |
|
216 | 216 | |
|
217 | 217 | Editing a hunk puts you back on that hunk when done editing (issue5041) |
|
218 | 218 | To do that, we change two lines in a file, pretend to edit the second line, |
|
219 | 219 | exit, toggle the line selected at the end of the edit and commit. |
|
220 | 220 | The first line should be recorded if we were put on the second line at the end |
|
221 | 221 | of the edit. |
|
222 | 222 | |
|
223 | 223 | $ hg update -C . |
|
224 | 224 | 0 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
225 | 225 | $ echo "foo" > x |
|
226 | 226 | $ echo "hello world" >> x |
|
227 | 227 | $ echo "bar" >> x |
|
228 | 228 | $ cat <<EOF >testModeCommands |
|
229 | 229 | > f |
|
230 | 230 | > KEY_DOWN |
|
231 | 231 | > KEY_DOWN |
|
232 | 232 | > KEY_DOWN |
|
233 | 233 | > KEY_DOWN |
|
234 | 234 | > e |
|
235 | 235 | > TOGGLE |
|
236 | 236 | > X |
|
237 | 237 | > EOF |
|
238 | 238 | $ printf "printf 'editor ran\n'; exit 0" > editor.sh |
|
239 | 239 | $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -m "edit hunk" -d "0 0" |
|
240 | 240 | editor ran |
|
241 | 241 | $ hg cat -r . x |
|
242 | 242 | foo |
|
243 | 243 | hello world |
|
244 | 244 | |
|
245 | 245 | Testing the review option. The entire final filtered patch should show |
|
246 | 246 | up in the editor and be editable. We will unselect the second file and |
|
247 | 247 | the first hunk of the third file. During review, we will decide that |
|
248 | 248 | "lower" sounds better than "bottom", and the final commit should |
|
249 | 249 | reflect this edition. |
|
250 | 250 | |
|
251 | 251 | $ hg update -C . |
|
252 | 252 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
253 | 253 | $ echo "top" > c |
|
254 | 254 | $ cat x >> c |
|
255 | 255 | $ echo "bottom" >> c |
|
256 | 256 | $ mv c x |
|
257 | 257 | $ echo "third a" >> a |
|
258 | 258 | $ echo "we will unselect this" >> b |
|
259 | 259 | |
|
260 | 260 | $ cat > editor.sh <<EOF |
|
261 | 261 | > cat "\$1" |
|
262 | 262 | > cat "\$1" | sed s/bottom/lower/ > tmp |
|
263 | 263 | > mv tmp "\$1" |
|
264 | 264 | > EOF |
|
265 | 265 | $ cat > testModeCommands <<EOF |
|
266 | 266 | > KEY_DOWN |
|
267 | 267 | > TOGGLE |
|
268 | 268 | > KEY_DOWN |
|
269 | 269 | > f |
|
270 | 270 | > KEY_DOWN |
|
271 | 271 | > TOGGLE |
|
272 | 272 | > R |
|
273 | 273 | > EOF |
|
274 | 274 | |
|
275 | 275 | $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -m "review hunks" -d "0 0" |
|
276 | 276 | # To remove '-' lines, make them ' ' lines (context). |
|
277 | 277 | # To remove '+' lines, delete them. |
|
278 | 278 | # Lines starting with # will be removed from the patch. |
|
279 | 279 | # |
|
280 | 280 | # If the patch applies cleanly, the edited patch will immediately |
|
281 | 281 | # be finalised. If it does not apply cleanly, rejects files will be |
|
282 | 282 | # generated. You can use those when you try again. |
|
283 | 283 | diff --git a/a b/a |
|
284 | 284 | --- a/a |
|
285 | 285 | +++ b/a |
|
286 | 286 | @@ -1,2 +1,3 @@ |
|
287 | 287 | a |
|
288 | 288 | a |
|
289 | 289 | +third a |
|
290 | 290 | diff --git a/x b/x |
|
291 | 291 | --- a/x |
|
292 | 292 | +++ b/x |
|
293 | 293 | @@ -1,2 +1,3 @@ |
|
294 | 294 | foo |
|
295 | 295 | hello world |
|
296 | 296 | +bottom |
|
297 | 297 | |
|
298 | 298 | $ hg cat -r . a |
|
299 | 299 | a |
|
300 | 300 | a |
|
301 | 301 | third a |
|
302 | 302 | |
|
303 | 303 | $ hg cat -r . b |
|
304 | 304 | x |
|
305 | 305 | 1 |
|
306 | 306 | 2 |
|
307 | 307 | 3 |
|
308 | 308 | 4 |
|
309 | 309 | 5 |
|
310 | 310 | 6 |
|
311 | 311 | 7 |
|
312 | 312 | 8 |
|
313 | 313 | 9 |
|
314 | 314 | 10 |
|
315 | 315 | y |
|
316 | 316 | |
|
317 | 317 | $ hg cat -r . x |
|
318 | 318 | foo |
|
319 | 319 | hello world |
|
320 | 320 | lower |
|
321 | 321 | Check ui.interface logic for the chunkselector |
|
322 | 322 | |
|
323 | 323 | The default interface is text |
|
324 | 324 | $ cp $HGRCPATH.pretest $HGRCPATH |
|
325 | 325 | $ chunkselectorinterface() { |
|
326 | 326 | > python <<EOF |
|
327 | 327 | > from mercurial import hg, ui, parsers;\ |
|
328 | > repo = hg.repository(ui.ui(), ".");\ | |
|
328 | > repo = hg.repository(ui.ui.load(), ".");\ | |
|
329 | 329 | > print repo.ui.interface("chunkselector") |
|
330 | 330 | > EOF |
|
331 | 331 | > } |
|
332 | 332 | $ chunkselectorinterface |
|
333 | 333 | text |
|
334 | 334 | |
|
335 | 335 | If only the default is set, we'll use that for the feature, too |
|
336 | 336 | $ cp $HGRCPATH.pretest $HGRCPATH |
|
337 | 337 | $ cat <<EOF >> $HGRCPATH |
|
338 | 338 | > [ui] |
|
339 | 339 | > interface = curses |
|
340 | 340 | > EOF |
|
341 | 341 | $ chunkselectorinterface |
|
342 | 342 | curses |
|
343 | 343 | |
|
344 | 344 | It is possible to override the default interface with a feature specific |
|
345 | 345 | interface |
|
346 | 346 | $ cp $HGRCPATH.pretest $HGRCPATH |
|
347 | 347 | $ cat <<EOF >> $HGRCPATH |
|
348 | 348 | > [ui] |
|
349 | 349 | > interface = text |
|
350 | 350 | > interface.chunkselector = curses |
|
351 | 351 | > EOF |
|
352 | 352 | |
|
353 | 353 | $ chunkselectorinterface |
|
354 | 354 | curses |
|
355 | 355 | |
|
356 | 356 | $ cp $HGRCPATH.pretest $HGRCPATH |
|
357 | 357 | $ cat <<EOF >> $HGRCPATH |
|
358 | 358 | > [ui] |
|
359 | 359 | > interface = curses |
|
360 | 360 | > interface.chunkselector = text |
|
361 | 361 | > EOF |
|
362 | 362 | |
|
363 | 363 | $ chunkselectorinterface |
|
364 | 364 | text |
|
365 | 365 | |
|
366 | 366 | If a bad interface name is given, we use the default value (with a nice |
|
367 | 367 | error message to suggest that the configuration needs to be fixed) |
|
368 | 368 | |
|
369 | 369 | $ cp $HGRCPATH.pretest $HGRCPATH |
|
370 | 370 | $ cat <<EOF >> $HGRCPATH |
|
371 | 371 | > [ui] |
|
372 | 372 | > interface = blah |
|
373 | 373 | > EOF |
|
374 | 374 | $ chunkselectorinterface |
|
375 | 375 | invalid value for ui.interface: blah (using text) |
|
376 | 376 | text |
|
377 | 377 | |
|
378 | 378 | $ cp $HGRCPATH.pretest $HGRCPATH |
|
379 | 379 | $ cat <<EOF >> $HGRCPATH |
|
380 | 380 | > [ui] |
|
381 | 381 | > interface = curses |
|
382 | 382 | > interface.chunkselector = blah |
|
383 | 383 | > EOF |
|
384 | 384 | $ chunkselectorinterface |
|
385 | 385 | invalid value for ui.interface.chunkselector: blah (using curses) |
|
386 | 386 | curses |
|
387 | 387 | |
|
388 | 388 | $ cp $HGRCPATH.pretest $HGRCPATH |
|
389 | 389 | $ cat <<EOF >> $HGRCPATH |
|
390 | 390 | > [ui] |
|
391 | 391 | > interface = blah |
|
392 | 392 | > interface.chunkselector = curses |
|
393 | 393 | > EOF |
|
394 | 394 | $ chunkselectorinterface |
|
395 | 395 | invalid value for ui.interface: blah |
|
396 | 396 | curses |
|
397 | 397 | |
|
398 | 398 | $ cp $HGRCPATH.pretest $HGRCPATH |
|
399 | 399 | $ cat <<EOF >> $HGRCPATH |
|
400 | 400 | > [ui] |
|
401 | 401 | > interface = blah |
|
402 | 402 | > interface.chunkselector = blah |
|
403 | 403 | > EOF |
|
404 | 404 | $ chunkselectorinterface |
|
405 | 405 | invalid value for ui.interface: blah |
|
406 | 406 | invalid value for ui.interface.chunkselector: blah (using text) |
|
407 | 407 | text |
@@ -1,131 +1,131 | |||
|
1 | 1 | # reproduce issue2264, issue2516 |
|
2 | 2 | |
|
3 | 3 | create test repo |
|
4 | 4 | $ cat <<EOF >> $HGRCPATH |
|
5 | 5 | > [extensions] |
|
6 | 6 | > transplant = |
|
7 | 7 | > EOF |
|
8 | 8 | $ hg init repo |
|
9 | 9 | $ cd repo |
|
10 | 10 | $ template="{rev} {desc|firstline} [{branch}]\n" |
|
11 | 11 | |
|
12 | 12 | # we need to start out with two changesets on the default branch |
|
13 | 13 | # in order to avoid the cute little optimization where transplant |
|
14 | 14 | # pulls rather than transplants |
|
15 | 15 | add initial changesets |
|
16 | 16 | $ echo feature1 > file1 |
|
17 | 17 | $ hg ci -Am"feature 1" |
|
18 | 18 | adding file1 |
|
19 | 19 | $ echo feature2 >> file2 |
|
20 | 20 | $ hg ci -Am"feature 2" |
|
21 | 21 | adding file2 |
|
22 | 22 | |
|
23 | 23 | # The changes to 'bugfix' are enough to show the bug: in fact, with only |
|
24 | 24 | # those changes, it's a very noisy crash ("RuntimeError: nothing |
|
25 | 25 | # committed after transplant"). But if we modify a second file in the |
|
26 | 26 | # transplanted changesets, the bug is much more subtle: transplant |
|
27 | 27 | # silently drops the second change to 'bugfix' on the floor, and we only |
|
28 | 28 | # see it when we run 'hg status' after transplanting. Subtle data loss |
|
29 | 29 | # bugs are worse than crashes, so reproduce the subtle case here. |
|
30 | 30 | commit bug fixes on bug fix branch |
|
31 | 31 | $ hg branch fixes |
|
32 | 32 | marked working directory as branch fixes |
|
33 | 33 | (branches are permanent and global, did you want a bookmark?) |
|
34 | 34 | $ echo fix1 > bugfix |
|
35 | 35 | $ echo fix1 >> file1 |
|
36 | 36 | $ hg ci -Am"fix 1" |
|
37 | 37 | adding bugfix |
|
38 | 38 | $ echo fix2 > bugfix |
|
39 | 39 | $ echo fix2 >> file1 |
|
40 | 40 | $ hg ci -Am"fix 2" |
|
41 | 41 | $ hg log -G --template="$template" |
|
42 | 42 | @ 3 fix 2 [fixes] |
|
43 | 43 | | |
|
44 | 44 | o 2 fix 1 [fixes] |
|
45 | 45 | | |
|
46 | 46 | o 1 feature 2 [default] |
|
47 | 47 | | |
|
48 | 48 | o 0 feature 1 [default] |
|
49 | 49 | |
|
50 | 50 | transplant bug fixes onto release branch |
|
51 | 51 | $ hg update 0 |
|
52 | 52 | 1 files updated, 0 files merged, 2 files removed, 0 files unresolved |
|
53 | 53 | $ hg branch release |
|
54 | 54 | marked working directory as branch release |
|
55 | 55 | $ hg transplant 2 3 |
|
56 | 56 | applying [0-9a-f]{12} (re) |
|
57 | 57 | [0-9a-f]{12} transplanted to [0-9a-f]{12} (re) |
|
58 | 58 | applying [0-9a-f]{12} (re) |
|
59 | 59 | [0-9a-f]{12} transplanted to [0-9a-f]{12} (re) |
|
60 | 60 | $ hg log -G --template="$template" |
|
61 | 61 | @ 5 fix 2 [release] |
|
62 | 62 | | |
|
63 | 63 | o 4 fix 1 [release] |
|
64 | 64 | | |
|
65 | 65 | | o 3 fix 2 [fixes] |
|
66 | 66 | | | |
|
67 | 67 | | o 2 fix 1 [fixes] |
|
68 | 68 | | | |
|
69 | 69 | | o 1 feature 2 [default] |
|
70 | 70 | |/ |
|
71 | 71 | o 0 feature 1 [default] |
|
72 | 72 | |
|
73 | 73 | $ hg status |
|
74 | 74 | $ hg status --rev 0:4 |
|
75 | 75 | M file1 |
|
76 | 76 | A bugfix |
|
77 | 77 | $ hg status --rev 4:5 |
|
78 | 78 | M bugfix |
|
79 | 79 | M file1 |
|
80 | 80 | |
|
81 | 81 | now test that we fixed the bug for all scripts/extensions |
|
82 | 82 | $ cat > $TESTTMP/committwice.py <<__EOF__ |
|
83 | 83 | > from mercurial import ui, hg, match, node |
|
84 | 84 | > from time import sleep |
|
85 | 85 | > |
|
86 | 86 | > def replacebyte(fn, b): |
|
87 | 87 | > f = open(fn, "rb+") |
|
88 | 88 | > f.seek(0, 0) |
|
89 | 89 | > f.write(b) |
|
90 | 90 | > f.close() |
|
91 | 91 | > |
|
92 | 92 | > def printfiles(repo, rev): |
|
93 | 93 | > print "revision %s files: %s" % (rev, repo[rev].files()) |
|
94 | 94 | > |
|
95 | > repo = hg.repository(ui.ui(), '.') | |
|
95 | > repo = hg.repository(ui.ui.load(), '.') | |
|
96 | 96 | > assert len(repo) == 6, \ |
|
97 | 97 | > "initial: len(repo): %d, expected: 6" % len(repo) |
|
98 | 98 | > |
|
99 | 99 | > replacebyte("bugfix", "u") |
|
100 | 100 | > sleep(2) |
|
101 | 101 | > try: |
|
102 | 102 | > print "PRE: len(repo): %d" % len(repo) |
|
103 | 103 | > wlock = repo.wlock() |
|
104 | 104 | > lock = repo.lock() |
|
105 | 105 | > replacebyte("file1", "x") |
|
106 | 106 | > repo.commit(text="x", user="test", date=(0, 0)) |
|
107 | 107 | > replacebyte("file1", "y") |
|
108 | 108 | > repo.commit(text="y", user="test", date=(0, 0)) |
|
109 | 109 | > print "POST: len(repo): %d" % len(repo) |
|
110 | 110 | > finally: |
|
111 | 111 | > lock.release() |
|
112 | 112 | > wlock.release() |
|
113 | 113 | > printfiles(repo, 6) |
|
114 | 114 | > printfiles(repo, 7) |
|
115 | 115 | > __EOF__ |
|
116 | 116 | $ $PYTHON $TESTTMP/committwice.py |
|
117 | 117 | PRE: len(repo): 6 |
|
118 | 118 | POST: len(repo): 8 |
|
119 | 119 | revision 6 files: ['bugfix', 'file1'] |
|
120 | 120 | revision 7 files: ['file1'] |
|
121 | 121 | |
|
122 | 122 | Do a size-preserving modification outside of that process |
|
123 | 123 | $ echo abcd > bugfix |
|
124 | 124 | $ hg status |
|
125 | 125 | M bugfix |
|
126 | 126 | $ hg log --template "{rev} {desc} {files}\n" -r5: |
|
127 | 127 | 5 fix 2 bugfix file1 |
|
128 | 128 | 6 x bugfix file1 |
|
129 | 129 | 7 y file1 |
|
130 | 130 | |
|
131 | 131 | $ cd .. |
@@ -1,692 +1,692 | |||
|
1 | 1 | commit date test |
|
2 | 2 | |
|
3 | 3 | $ hg init test |
|
4 | 4 | $ cd test |
|
5 | 5 | $ echo foo > foo |
|
6 | 6 | $ hg add foo |
|
7 | 7 | $ cat > $TESTTMP/checkeditform.sh <<EOF |
|
8 | 8 | > env | grep HGEDITFORM |
|
9 | 9 | > true |
|
10 | 10 | > EOF |
|
11 | 11 | $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg commit -m "" |
|
12 | 12 | HGEDITFORM=commit.normal.normal |
|
13 | 13 | abort: empty commit message |
|
14 | 14 | [255] |
|
15 | 15 | $ hg commit -d '0 0' -m commit-1 |
|
16 | 16 | $ echo foo >> foo |
|
17 | 17 | $ hg commit -d '1 4444444' -m commit-3 |
|
18 | 18 | abort: impossible time zone offset: 4444444 |
|
19 | 19 | [255] |
|
20 | 20 | $ hg commit -d '1 15.1' -m commit-4 |
|
21 | 21 | abort: invalid date: '1\t15.1' |
|
22 | 22 | [255] |
|
23 | 23 | $ hg commit -d 'foo bar' -m commit-5 |
|
24 | 24 | abort: invalid date: 'foo bar' |
|
25 | 25 | [255] |
|
26 | 26 | $ hg commit -d ' 1 4444' -m commit-6 |
|
27 | 27 | $ hg commit -d '111111111111 0' -m commit-7 |
|
28 | 28 | abort: date exceeds 32 bits: 111111111111 |
|
29 | 29 | [255] |
|
30 | 30 | $ hg commit -d '-111111111111 0' -m commit-7 |
|
31 | 31 | abort: date exceeds 32 bits: -111111111111 |
|
32 | 32 | [255] |
|
33 | 33 | $ echo foo >> foo |
|
34 | 34 | $ hg commit -d '1901-12-13 20:45:52 +0000' -m commit-7-2 |
|
35 | 35 | $ echo foo >> foo |
|
36 | 36 | $ hg commit -d '-2147483648 0' -m commit-7-3 |
|
37 | 37 | $ hg log -T '{rev} {date|isodatesec}\n' -l2 |
|
38 | 38 | 3 1901-12-13 20:45:52 +0000 |
|
39 | 39 | 2 1901-12-13 20:45:52 +0000 |
|
40 | 40 | $ hg commit -d '1901-12-13 20:45:51 +0000' -m commit-7 |
|
41 | 41 | abort: date exceeds 32 bits: -2147483649 |
|
42 | 42 | [255] |
|
43 | 43 | $ hg commit -d '-2147483649 0' -m commit-7 |
|
44 | 44 | abort: date exceeds 32 bits: -2147483649 |
|
45 | 45 | [255] |
|
46 | 46 | |
|
47 | 47 | commit added file that has been deleted |
|
48 | 48 | |
|
49 | 49 | $ echo bar > bar |
|
50 | 50 | $ hg add bar |
|
51 | 51 | $ rm bar |
|
52 | 52 | $ hg commit -m commit-8 |
|
53 | 53 | nothing changed (1 missing files, see 'hg status') |
|
54 | 54 | [1] |
|
55 | 55 | $ hg commit -m commit-8-2 bar |
|
56 | 56 | abort: bar: file not found! |
|
57 | 57 | [255] |
|
58 | 58 | |
|
59 | 59 | $ hg -q revert -a --no-backup |
|
60 | 60 | |
|
61 | 61 | $ mkdir dir |
|
62 | 62 | $ echo boo > dir/file |
|
63 | 63 | $ hg add |
|
64 | 64 | adding dir/file (glob) |
|
65 | 65 | $ hg -v commit -m commit-9 dir |
|
66 | 66 | committing files: |
|
67 | 67 | dir/file |
|
68 | 68 | committing manifest |
|
69 | 69 | committing changelog |
|
70 | 70 | committed changeset 4:1957363f1ced |
|
71 | 71 | |
|
72 | 72 | $ echo > dir.file |
|
73 | 73 | $ hg add |
|
74 | 74 | adding dir.file |
|
75 | 75 | $ hg commit -m commit-10 dir dir.file |
|
76 | 76 | abort: dir: no match under directory! |
|
77 | 77 | [255] |
|
78 | 78 | |
|
79 | 79 | $ echo >> dir/file |
|
80 | 80 | $ mkdir bleh |
|
81 | 81 | $ mkdir dir2 |
|
82 | 82 | $ cd bleh |
|
83 | 83 | $ hg commit -m commit-11 . |
|
84 | 84 | abort: bleh: no match under directory! |
|
85 | 85 | [255] |
|
86 | 86 | $ hg commit -m commit-12 ../dir ../dir2 |
|
87 | 87 | abort: dir2: no match under directory! |
|
88 | 88 | [255] |
|
89 | 89 | $ hg -v commit -m commit-13 ../dir |
|
90 | 90 | committing files: |
|
91 | 91 | dir/file |
|
92 | 92 | committing manifest |
|
93 | 93 | committing changelog |
|
94 | 94 | committed changeset 5:a31d8f87544a |
|
95 | 95 | $ cd .. |
|
96 | 96 | |
|
97 | 97 | $ hg commit -m commit-14 does-not-exist |
|
98 | 98 | abort: does-not-exist: * (glob) |
|
99 | 99 | [255] |
|
100 | 100 | |
|
101 | 101 | #if symlink |
|
102 | 102 | $ ln -s foo baz |
|
103 | 103 | $ hg commit -m commit-15 baz |
|
104 | 104 | abort: baz: file not tracked! |
|
105 | 105 | [255] |
|
106 | 106 | #endif |
|
107 | 107 | |
|
108 | 108 | $ touch quux |
|
109 | 109 | $ hg commit -m commit-16 quux |
|
110 | 110 | abort: quux: file not tracked! |
|
111 | 111 | [255] |
|
112 | 112 | $ echo >> dir/file |
|
113 | 113 | $ hg -v commit -m commit-17 dir/file |
|
114 | 114 | committing files: |
|
115 | 115 | dir/file |
|
116 | 116 | committing manifest |
|
117 | 117 | committing changelog |
|
118 | 118 | committed changeset 6:32d054c9d085 |
|
119 | 119 | |
|
120 | 120 | An empty date was interpreted as epoch origin |
|
121 | 121 | |
|
122 | 122 | $ echo foo >> foo |
|
123 | 123 | $ hg commit -d '' -m commit-no-date |
|
124 | 124 | $ hg tip --template '{date|isodate}\n' | grep '1970' |
|
125 | 125 | [1] |
|
126 | 126 | |
|
127 | 127 | Make sure we do not obscure unknown requires file entries (issue2649) |
|
128 | 128 | |
|
129 | 129 | $ echo foo >> foo |
|
130 | 130 | $ echo fake >> .hg/requires |
|
131 | 131 | $ hg commit -m bla |
|
132 | 132 | abort: repository requires features unknown to this Mercurial: fake! |
|
133 | 133 | (see https://mercurial-scm.org/wiki/MissingRequirement for more information) |
|
134 | 134 | [255] |
|
135 | 135 | |
|
136 | 136 | $ cd .. |
|
137 | 137 | |
|
138 | 138 | |
|
139 | 139 | partial subdir commit test |
|
140 | 140 | |
|
141 | 141 | $ hg init test2 |
|
142 | 142 | $ cd test2 |
|
143 | 143 | $ mkdir foo |
|
144 | 144 | $ echo foo > foo/foo |
|
145 | 145 | $ mkdir bar |
|
146 | 146 | $ echo bar > bar/bar |
|
147 | 147 | $ hg add |
|
148 | 148 | adding bar/bar (glob) |
|
149 | 149 | adding foo/foo (glob) |
|
150 | 150 | $ HGEDITOR=cat hg ci -e -m commit-subdir-1 foo |
|
151 | 151 | commit-subdir-1 |
|
152 | 152 | |
|
153 | 153 | |
|
154 | 154 | HG: Enter commit message. Lines beginning with 'HG:' are removed. |
|
155 | 155 | HG: Leave message empty to abort commit. |
|
156 | 156 | HG: -- |
|
157 | 157 | HG: user: test |
|
158 | 158 | HG: branch 'default' |
|
159 | 159 | HG: added foo/foo |
|
160 | 160 | |
|
161 | 161 | |
|
162 | 162 | $ hg ci -m commit-subdir-2 bar |
|
163 | 163 | |
|
164 | 164 | subdir log 1 |
|
165 | 165 | |
|
166 | 166 | $ hg log -v foo |
|
167 | 167 | changeset: 0:f97e73a25882 |
|
168 | 168 | user: test |
|
169 | 169 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
170 | 170 | files: foo/foo |
|
171 | 171 | description: |
|
172 | 172 | commit-subdir-1 |
|
173 | 173 | |
|
174 | 174 | |
|
175 | 175 | |
|
176 | 176 | subdir log 2 |
|
177 | 177 | |
|
178 | 178 | $ hg log -v bar |
|
179 | 179 | changeset: 1:aa809156d50d |
|
180 | 180 | tag: tip |
|
181 | 181 | user: test |
|
182 | 182 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
183 | 183 | files: bar/bar |
|
184 | 184 | description: |
|
185 | 185 | commit-subdir-2 |
|
186 | 186 | |
|
187 | 187 | |
|
188 | 188 | |
|
189 | 189 | full log |
|
190 | 190 | |
|
191 | 191 | $ hg log -v |
|
192 | 192 | changeset: 1:aa809156d50d |
|
193 | 193 | tag: tip |
|
194 | 194 | user: test |
|
195 | 195 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
196 | 196 | files: bar/bar |
|
197 | 197 | description: |
|
198 | 198 | commit-subdir-2 |
|
199 | 199 | |
|
200 | 200 | |
|
201 | 201 | changeset: 0:f97e73a25882 |
|
202 | 202 | user: test |
|
203 | 203 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
204 | 204 | files: foo/foo |
|
205 | 205 | description: |
|
206 | 206 | commit-subdir-1 |
|
207 | 207 | |
|
208 | 208 | |
|
209 | 209 | $ cd .. |
|
210 | 210 | |
|
211 | 211 | |
|
212 | 212 | dot and subdir commit test |
|
213 | 213 | |
|
214 | 214 | $ hg init test3 |
|
215 | 215 | $ echo commit-foo-subdir > commit-log-test |
|
216 | 216 | $ cd test3 |
|
217 | 217 | $ mkdir foo |
|
218 | 218 | $ echo foo content > foo/plain-file |
|
219 | 219 | $ hg add foo/plain-file |
|
220 | 220 | $ HGEDITOR=cat hg ci --edit -l ../commit-log-test foo |
|
221 | 221 | commit-foo-subdir |
|
222 | 222 | |
|
223 | 223 | |
|
224 | 224 | HG: Enter commit message. Lines beginning with 'HG:' are removed. |
|
225 | 225 | HG: Leave message empty to abort commit. |
|
226 | 226 | HG: -- |
|
227 | 227 | HG: user: test |
|
228 | 228 | HG: branch 'default' |
|
229 | 229 | HG: added foo/plain-file |
|
230 | 230 | |
|
231 | 231 | |
|
232 | 232 | $ echo modified foo content > foo/plain-file |
|
233 | 233 | $ hg ci -m commit-foo-dot . |
|
234 | 234 | |
|
235 | 235 | full log |
|
236 | 236 | |
|
237 | 237 | $ hg log -v |
|
238 | 238 | changeset: 1:95b38e3a5b2e |
|
239 | 239 | tag: tip |
|
240 | 240 | user: test |
|
241 | 241 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
242 | 242 | files: foo/plain-file |
|
243 | 243 | description: |
|
244 | 244 | commit-foo-dot |
|
245 | 245 | |
|
246 | 246 | |
|
247 | 247 | changeset: 0:65d4e9386227 |
|
248 | 248 | user: test |
|
249 | 249 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
250 | 250 | files: foo/plain-file |
|
251 | 251 | description: |
|
252 | 252 | commit-foo-subdir |
|
253 | 253 | |
|
254 | 254 | |
|
255 | 255 | |
|
256 | 256 | subdir log |
|
257 | 257 | |
|
258 | 258 | $ cd foo |
|
259 | 259 | $ hg log . |
|
260 | 260 | changeset: 1:95b38e3a5b2e |
|
261 | 261 | tag: tip |
|
262 | 262 | user: test |
|
263 | 263 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
264 | 264 | summary: commit-foo-dot |
|
265 | 265 | |
|
266 | 266 | changeset: 0:65d4e9386227 |
|
267 | 267 | user: test |
|
268 | 268 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
269 | 269 | summary: commit-foo-subdir |
|
270 | 270 | |
|
271 | 271 | $ cd .. |
|
272 | 272 | $ cd .. |
|
273 | 273 | |
|
274 | 274 | Issue1049: Hg permits partial commit of merge without warning |
|
275 | 275 | |
|
276 | 276 | $ hg init issue1049 |
|
277 | 277 | $ cd issue1049 |
|
278 | 278 | $ echo a > a |
|
279 | 279 | $ hg ci -Ama |
|
280 | 280 | adding a |
|
281 | 281 | $ echo a >> a |
|
282 | 282 | $ hg ci -mb |
|
283 | 283 | $ hg up 0 |
|
284 | 284 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
285 | 285 | $ echo b >> a |
|
286 | 286 | $ hg ci -mc |
|
287 | 287 | created new head |
|
288 | 288 | $ HGMERGE=true hg merge |
|
289 | 289 | merging a |
|
290 | 290 | 0 files updated, 1 files merged, 0 files removed, 0 files unresolved |
|
291 | 291 | (branch merge, don't forget to commit) |
|
292 | 292 | |
|
293 | 293 | should fail because we are specifying a file name |
|
294 | 294 | |
|
295 | 295 | $ hg ci -mmerge a |
|
296 | 296 | abort: cannot partially commit a merge (do not specify files or patterns) |
|
297 | 297 | [255] |
|
298 | 298 | |
|
299 | 299 | should fail because we are specifying a pattern |
|
300 | 300 | |
|
301 | 301 | $ hg ci -mmerge -I a |
|
302 | 302 | abort: cannot partially commit a merge (do not specify files or patterns) |
|
303 | 303 | [255] |
|
304 | 304 | |
|
305 | 305 | should succeed |
|
306 | 306 | |
|
307 | 307 | $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg ci -mmerge --edit |
|
308 | 308 | HGEDITFORM=commit.normal.merge |
|
309 | 309 | $ cd .. |
|
310 | 310 | |
|
311 | 311 | |
|
312 | 312 | test commit message content |
|
313 | 313 | |
|
314 | 314 | $ hg init commitmsg |
|
315 | 315 | $ cd commitmsg |
|
316 | 316 | $ echo changed > changed |
|
317 | 317 | $ echo removed > removed |
|
318 | 318 | $ hg book activebookmark |
|
319 | 319 | $ hg ci -qAm init |
|
320 | 320 | |
|
321 | 321 | $ hg rm removed |
|
322 | 322 | $ echo changed >> changed |
|
323 | 323 | $ echo added > added |
|
324 | 324 | $ hg add added |
|
325 | 325 | $ HGEDITOR=cat hg ci -A |
|
326 | 326 | |
|
327 | 327 | |
|
328 | 328 | HG: Enter commit message. Lines beginning with 'HG:' are removed. |
|
329 | 329 | HG: Leave message empty to abort commit. |
|
330 | 330 | HG: -- |
|
331 | 331 | HG: user: test |
|
332 | 332 | HG: branch 'default' |
|
333 | 333 | HG: bookmark 'activebookmark' |
|
334 | 334 | HG: added added |
|
335 | 335 | HG: changed changed |
|
336 | 336 | HG: removed removed |
|
337 | 337 | abort: empty commit message |
|
338 | 338 | [255] |
|
339 | 339 | |
|
340 | 340 | test saving last-message.txt |
|
341 | 341 | |
|
342 | 342 | $ hg init sub |
|
343 | 343 | $ echo a > sub/a |
|
344 | 344 | $ hg -R sub add sub/a |
|
345 | 345 | $ cat > sub/.hg/hgrc <<EOF |
|
346 | 346 | > [hooks] |
|
347 | 347 | > precommit.test-saving-last-message = false |
|
348 | 348 | > EOF |
|
349 | 349 | |
|
350 | 350 | $ echo 'sub = sub' > .hgsub |
|
351 | 351 | $ hg add .hgsub |
|
352 | 352 | |
|
353 | 353 | $ cat > $TESTTMP/editor.sh <<EOF |
|
354 | 354 | > echo "==== before editing:" |
|
355 | 355 | > cat \$1 |
|
356 | 356 | > echo "====" |
|
357 | 357 | > echo "test saving last-message.txt" >> \$1 |
|
358 | 358 | > EOF |
|
359 | 359 | |
|
360 | 360 | $ rm -f .hg/last-message.txt |
|
361 | 361 | $ HGEDITOR="sh $TESTTMP/editor.sh" hg commit -S -q |
|
362 | 362 | ==== before editing: |
|
363 | 363 | |
|
364 | 364 | |
|
365 | 365 | HG: Enter commit message. Lines beginning with 'HG:' are removed. |
|
366 | 366 | HG: Leave message empty to abort commit. |
|
367 | 367 | HG: -- |
|
368 | 368 | HG: user: test |
|
369 | 369 | HG: branch 'default' |
|
370 | 370 | HG: bookmark 'activebookmark' |
|
371 | 371 | HG: subrepo sub |
|
372 | 372 | HG: added .hgsub |
|
373 | 373 | HG: added added |
|
374 | 374 | HG: changed .hgsubstate |
|
375 | 375 | HG: changed changed |
|
376 | 376 | HG: removed removed |
|
377 | 377 | ==== |
|
378 | 378 | abort: precommit.test-saving-last-message hook exited with status 1 (in subrepo sub) |
|
379 | 379 | [255] |
|
380 | 380 | $ cat .hg/last-message.txt |
|
381 | 381 | |
|
382 | 382 | |
|
383 | 383 | test saving last-message.txt |
|
384 | 384 | |
|
385 | 385 | test that '[committemplate] changeset' definition and commit log |
|
386 | 386 | specific template keywords work well |
|
387 | 387 | |
|
388 | 388 | $ cat >> .hg/hgrc <<EOF |
|
389 | 389 | > [committemplate] |
|
390 | 390 | > changeset.commit.normal = HG: this is "commit.normal" template |
|
391 | 391 | > HG: {extramsg} |
|
392 | 392 | > {if(activebookmark, |
|
393 | 393 | > "HG: bookmark '{activebookmark}' is activated\n", |
|
394 | 394 | > "HG: no bookmark is activated\n")}{subrepos % |
|
395 | 395 | > "HG: subrepo '{subrepo}' is changed\n"} |
|
396 | 396 | > |
|
397 | 397 | > changeset.commit = HG: this is "commit" template |
|
398 | 398 | > HG: {extramsg} |
|
399 | 399 | > {if(activebookmark, |
|
400 | 400 | > "HG: bookmark '{activebookmark}' is activated\n", |
|
401 | 401 | > "HG: no bookmark is activated\n")}{subrepos % |
|
402 | 402 | > "HG: subrepo '{subrepo}' is changed\n"} |
|
403 | 403 | > |
|
404 | 404 | > changeset = HG: this is customized commit template |
|
405 | 405 | > HG: {extramsg} |
|
406 | 406 | > {if(activebookmark, |
|
407 | 407 | > "HG: bookmark '{activebookmark}' is activated\n", |
|
408 | 408 | > "HG: no bookmark is activated\n")}{subrepos % |
|
409 | 409 | > "HG: subrepo '{subrepo}' is changed\n"} |
|
410 | 410 | > EOF |
|
411 | 411 | |
|
412 | 412 | $ hg init sub2 |
|
413 | 413 | $ echo a > sub2/a |
|
414 | 414 | $ hg -R sub2 add sub2/a |
|
415 | 415 | $ echo 'sub2 = sub2' >> .hgsub |
|
416 | 416 | |
|
417 | 417 | $ HGEDITOR=cat hg commit -S -q |
|
418 | 418 | HG: this is "commit.normal" template |
|
419 | 419 | HG: Leave message empty to abort commit. |
|
420 | 420 | HG: bookmark 'activebookmark' is activated |
|
421 | 421 | HG: subrepo 'sub' is changed |
|
422 | 422 | HG: subrepo 'sub2' is changed |
|
423 | 423 | abort: empty commit message |
|
424 | 424 | [255] |
|
425 | 425 | |
|
426 | 426 | $ cat >> .hg/hgrc <<EOF |
|
427 | 427 | > [committemplate] |
|
428 | 428 | > changeset.commit.normal = |
|
429 | 429 | > # now, "changeset.commit" should be chosen for "hg commit" |
|
430 | 430 | > EOF |
|
431 | 431 | |
|
432 | 432 | $ hg bookmark --inactive activebookmark |
|
433 | 433 | $ hg forget .hgsub |
|
434 | 434 | $ HGEDITOR=cat hg commit -q |
|
435 | 435 | HG: this is "commit" template |
|
436 | 436 | HG: Leave message empty to abort commit. |
|
437 | 437 | HG: no bookmark is activated |
|
438 | 438 | abort: empty commit message |
|
439 | 439 | [255] |
|
440 | 440 | |
|
441 | 441 | $ cat >> .hg/hgrc <<EOF |
|
442 | 442 | > [committemplate] |
|
443 | 443 | > changeset.commit = |
|
444 | 444 | > # now, "changeset" should be chosen for "hg commit" |
|
445 | 445 | > EOF |
|
446 | 446 | |
|
447 | 447 | $ HGEDITOR=cat hg commit -q |
|
448 | 448 | HG: this is customized commit template |
|
449 | 449 | HG: Leave message empty to abort commit. |
|
450 | 450 | HG: no bookmark is activated |
|
451 | 451 | abort: empty commit message |
|
452 | 452 | [255] |
|
453 | 453 | |
|
454 | 454 | $ cat >> .hg/hgrc <<EOF |
|
455 | 455 | > [committemplate] |
|
456 | 456 | > changeset = {desc} |
|
457 | 457 | > HG: mods={file_mods} |
|
458 | 458 | > HG: adds={file_adds} |
|
459 | 459 | > HG: dels={file_dels} |
|
460 | 460 | > HG: files={files} |
|
461 | 461 | > HG: |
|
462 | 462 | > {splitlines(diff()) % 'HG: {line}\n' |
|
463 | 463 | > }HG: |
|
464 | 464 | > HG: mods={file_mods} |
|
465 | 465 | > HG: adds={file_adds} |
|
466 | 466 | > HG: dels={file_dels} |
|
467 | 467 | > HG: files={files}\n |
|
468 | 468 | > EOF |
|
469 | 469 | $ hg status -amr |
|
470 | 470 | M changed |
|
471 | 471 | A added |
|
472 | 472 | R removed |
|
473 | 473 | $ HGEDITOR=cat hg commit -q -e -m "foo bar" changed |
|
474 | 474 | foo bar |
|
475 | 475 | HG: mods=changed |
|
476 | 476 | HG: adds= |
|
477 | 477 | HG: dels= |
|
478 | 478 | HG: files=changed |
|
479 | 479 | HG: |
|
480 | 480 | HG: --- a/changed Thu Jan 01 00:00:00 1970 +0000 |
|
481 | 481 | HG: +++ b/changed Thu Jan 01 00:00:00 1970 +0000 |
|
482 | 482 | HG: @@ -1,1 +1,2 @@ |
|
483 | 483 | HG: changed |
|
484 | 484 | HG: +changed |
|
485 | 485 | HG: |
|
486 | 486 | HG: mods=changed |
|
487 | 487 | HG: adds= |
|
488 | 488 | HG: dels= |
|
489 | 489 | HG: files=changed |
|
490 | 490 | $ hg status -amr |
|
491 | 491 | A added |
|
492 | 492 | R removed |
|
493 | 493 | $ hg parents --template "M {file_mods}\nA {file_adds}\nR {file_dels}\n" |
|
494 | 494 | M changed |
|
495 | 495 | A |
|
496 | 496 | R |
|
497 | 497 | $ hg rollback -q |
|
498 | 498 | |
|
499 | 499 | $ cat >> .hg/hgrc <<EOF |
|
500 | 500 | > [committemplate] |
|
501 | 501 | > changeset = {desc} |
|
502 | 502 | > HG: mods={file_mods} |
|
503 | 503 | > HG: adds={file_adds} |
|
504 | 504 | > HG: dels={file_dels} |
|
505 | 505 | > HG: files={files} |
|
506 | 506 | > HG: |
|
507 | 507 | > {splitlines(diff("changed")) % 'HG: {line}\n' |
|
508 | 508 | > }HG: |
|
509 | 509 | > HG: mods={file_mods} |
|
510 | 510 | > HG: adds={file_adds} |
|
511 | 511 | > HG: dels={file_dels} |
|
512 | 512 | > HG: files={files} |
|
513 | 513 | > HG: |
|
514 | 514 | > {splitlines(diff("added")) % 'HG: {line}\n' |
|
515 | 515 | > }HG: |
|
516 | 516 | > HG: mods={file_mods} |
|
517 | 517 | > HG: adds={file_adds} |
|
518 | 518 | > HG: dels={file_dels} |
|
519 | 519 | > HG: files={files} |
|
520 | 520 | > HG: |
|
521 | 521 | > {splitlines(diff("removed")) % 'HG: {line}\n' |
|
522 | 522 | > }HG: |
|
523 | 523 | > HG: mods={file_mods} |
|
524 | 524 | > HG: adds={file_adds} |
|
525 | 525 | > HG: dels={file_dels} |
|
526 | 526 | > HG: files={files}\n |
|
527 | 527 | > EOF |
|
528 | 528 | $ HGEDITOR=cat hg commit -q -e -m "foo bar" added removed |
|
529 | 529 | foo bar |
|
530 | 530 | HG: mods= |
|
531 | 531 | HG: adds=added |
|
532 | 532 | HG: dels=removed |
|
533 | 533 | HG: files=added removed |
|
534 | 534 | HG: |
|
535 | 535 | HG: |
|
536 | 536 | HG: mods= |
|
537 | 537 | HG: adds=added |
|
538 | 538 | HG: dels=removed |
|
539 | 539 | HG: files=added removed |
|
540 | 540 | HG: |
|
541 | 541 | HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000 |
|
542 | 542 | HG: +++ b/added Thu Jan 01 00:00:00 1970 +0000 |
|
543 | 543 | HG: @@ -0,0 +1,1 @@ |
|
544 | 544 | HG: +added |
|
545 | 545 | HG: |
|
546 | 546 | HG: mods= |
|
547 | 547 | HG: adds=added |
|
548 | 548 | HG: dels=removed |
|
549 | 549 | HG: files=added removed |
|
550 | 550 | HG: |
|
551 | 551 | HG: --- a/removed Thu Jan 01 00:00:00 1970 +0000 |
|
552 | 552 | HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 |
|
553 | 553 | HG: @@ -1,1 +0,0 @@ |
|
554 | 554 | HG: -removed |
|
555 | 555 | HG: |
|
556 | 556 | HG: mods= |
|
557 | 557 | HG: adds=added |
|
558 | 558 | HG: dels=removed |
|
559 | 559 | HG: files=added removed |
|
560 | 560 | $ hg status -amr |
|
561 | 561 | M changed |
|
562 | 562 | $ hg parents --template "M {file_mods}\nA {file_adds}\nR {file_dels}\n" |
|
563 | 563 | M |
|
564 | 564 | A added |
|
565 | 565 | R removed |
|
566 | 566 | $ hg rollback -q |
|
567 | 567 | |
|
568 | 568 | $ cat >> .hg/hgrc <<EOF |
|
569 | 569 | > # disable customizing for subsequent tests |
|
570 | 570 | > [committemplate] |
|
571 | 571 | > changeset = |
|
572 | 572 | > EOF |
|
573 | 573 | |
|
574 | 574 | $ cd .. |
|
575 | 575 | |
|
576 | 576 | |
|
577 | 577 | commit copy |
|
578 | 578 | |
|
579 | 579 | $ hg init dir2 |
|
580 | 580 | $ cd dir2 |
|
581 | 581 | $ echo bleh > bar |
|
582 | 582 | $ hg add bar |
|
583 | 583 | $ hg ci -m 'add bar' |
|
584 | 584 | |
|
585 | 585 | $ hg cp bar foo |
|
586 | 586 | $ echo >> bar |
|
587 | 587 | $ hg ci -m 'cp bar foo; change bar' |
|
588 | 588 | |
|
589 | 589 | $ hg debugrename foo |
|
590 | 590 | foo renamed from bar:26d3ca0dfd18e44d796b564e38dd173c9668d3a9 |
|
591 | 591 | $ hg debugindex bar |
|
592 | 592 | rev offset length ..... linkrev nodeid p1 p2 (re) |
|
593 | 593 | 0 0 6 ..... 0 26d3ca0dfd18 000000000000 000000000000 (re) |
|
594 | 594 | 1 6 7 ..... 1 d267bddd54f7 26d3ca0dfd18 000000000000 (re) |
|
595 | 595 | |
|
596 | 596 | Test making empty commits |
|
597 | 597 | $ hg commit --config ui.allowemptycommit=True -m "empty commit" |
|
598 | 598 | $ hg log -r . -v --stat |
|
599 | 599 | changeset: 2:d809f3644287 |
|
600 | 600 | tag: tip |
|
601 | 601 | user: test |
|
602 | 602 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
603 | 603 | description: |
|
604 | 604 | empty commit |
|
605 | 605 | |
|
606 | 606 | |
|
607 | 607 | |
|
608 | 608 | verify pathauditor blocks evil filepaths |
|
609 | 609 | $ cat > evil-commit.py <<EOF |
|
610 | 610 | > from mercurial import ui, hg, context, node |
|
611 | 611 | > notrc = u".h\u200cg".encode('utf-8') + '/hgrc' |
|
612 | > u = ui.ui() | |
|
612 | > u = ui.ui.load() | |
|
613 | 613 | > r = hg.repository(u, '.') |
|
614 | 614 | > def filectxfn(repo, memctx, path): |
|
615 | 615 | > return context.memfilectx(repo, path, '[hooks]\nupdate = echo owned') |
|
616 | 616 | > c = context.memctx(r, [r['tip'].node(), node.nullid], |
|
617 | 617 | > 'evil', [notrc], filectxfn, 0) |
|
618 | 618 | > r.commitctx(c) |
|
619 | 619 | > EOF |
|
620 | 620 | $ $PYTHON evil-commit.py |
|
621 | 621 | #if windows |
|
622 | 622 | $ hg co --clean tip |
|
623 | 623 | abort: path contains illegal component: .h\xe2\x80\x8cg\\hgrc (esc) |
|
624 | 624 | [255] |
|
625 | 625 | #else |
|
626 | 626 | $ hg co --clean tip |
|
627 | 627 | abort: path contains illegal component: .h\xe2\x80\x8cg/hgrc (esc) |
|
628 | 628 | [255] |
|
629 | 629 | #endif |
|
630 | 630 | |
|
631 | 631 | $ hg rollback -f |
|
632 | 632 | repository tip rolled back to revision 2 (undo commit) |
|
633 | 633 | $ cat > evil-commit.py <<EOF |
|
634 | 634 | > from mercurial import ui, hg, context, node |
|
635 | 635 | > notrc = "HG~1/hgrc" |
|
636 | > u = ui.ui() | |
|
636 | > u = ui.ui.load() | |
|
637 | 637 | > r = hg.repository(u, '.') |
|
638 | 638 | > def filectxfn(repo, memctx, path): |
|
639 | 639 | > return context.memfilectx(repo, path, '[hooks]\nupdate = echo owned') |
|
640 | 640 | > c = context.memctx(r, [r['tip'].node(), node.nullid], |
|
641 | 641 | > 'evil', [notrc], filectxfn, 0) |
|
642 | 642 | > r.commitctx(c) |
|
643 | 643 | > EOF |
|
644 | 644 | $ $PYTHON evil-commit.py |
|
645 | 645 | $ hg co --clean tip |
|
646 | 646 | abort: path contains illegal component: HG~1/hgrc (glob) |
|
647 | 647 | [255] |
|
648 | 648 | |
|
649 | 649 | $ hg rollback -f |
|
650 | 650 | repository tip rolled back to revision 2 (undo commit) |
|
651 | 651 | $ cat > evil-commit.py <<EOF |
|
652 | 652 | > from mercurial import ui, hg, context, node |
|
653 | 653 | > notrc = "HG8B6C~2/hgrc" |
|
654 | > u = ui.ui() | |
|
654 | > u = ui.ui.load() | |
|
655 | 655 | > r = hg.repository(u, '.') |
|
656 | 656 | > def filectxfn(repo, memctx, path): |
|
657 | 657 | > return context.memfilectx(repo, path, '[hooks]\nupdate = echo owned') |
|
658 | 658 | > c = context.memctx(r, [r['tip'].node(), node.nullid], |
|
659 | 659 | > 'evil', [notrc], filectxfn, 0) |
|
660 | 660 | > r.commitctx(c) |
|
661 | 661 | > EOF |
|
662 | 662 | $ $PYTHON evil-commit.py |
|
663 | 663 | $ hg co --clean tip |
|
664 | 664 | abort: path contains illegal component: HG8B6C~2/hgrc (glob) |
|
665 | 665 | [255] |
|
666 | 666 | |
|
667 | 667 | # test that an unmodified commit template message aborts |
|
668 | 668 | |
|
669 | 669 | $ hg init unmodified_commit_template |
|
670 | 670 | $ cd unmodified_commit_template |
|
671 | 671 | $ echo foo > foo |
|
672 | 672 | $ hg add foo |
|
673 | 673 | $ hg commit -m "foo" |
|
674 | 674 | $ cat >> .hg/hgrc <<EOF |
|
675 | 675 | > [committemplate] |
|
676 | 676 | > changeset.commit = HI THIS IS NOT STRIPPED |
|
677 | 677 | > HG: this is customized commit template |
|
678 | 678 | > HG: {extramsg} |
|
679 | 679 | > {if(activebookmark, |
|
680 | 680 | > "HG: bookmark '{activebookmark}' is activated\n", |
|
681 | 681 | > "HG: no bookmark is activated\n")}{subrepos % |
|
682 | 682 | > "HG: subrepo '{subrepo}' is changed\n"} |
|
683 | 683 | > EOF |
|
684 | 684 | $ cat > $TESTTMP/notouching.sh <<EOF |
|
685 | 685 | > true |
|
686 | 686 | > EOF |
|
687 | 687 | $ echo foo2 > foo2 |
|
688 | 688 | $ hg add foo2 |
|
689 | 689 | $ HGEDITOR="sh $TESTTMP/notouching.sh" hg commit |
|
690 | 690 | abort: commit message unchanged |
|
691 | 691 | [255] |
|
692 | 692 | $ cd .. |
@@ -1,148 +1,148 | |||
|
1 | 1 | from __future__ import absolute_import, print_function |
|
2 | 2 | import os |
|
3 | 3 | from mercurial import ( |
|
4 | 4 | context, |
|
5 | 5 | encoding, |
|
6 | 6 | hg, |
|
7 | 7 | ui as uimod, |
|
8 | 8 | ) |
|
9 | 9 | |
|
10 | u = uimod.ui() | |
|
10 | u = uimod.ui.load() | |
|
11 | 11 | |
|
12 | 12 | repo = hg.repository(u, 'test1', create=1) |
|
13 | 13 | os.chdir('test1') |
|
14 | 14 | |
|
15 | 15 | # create 'foo' with fixed time stamp |
|
16 | 16 | f = open('foo', 'wb') |
|
17 | 17 | f.write(b'foo\n') |
|
18 | 18 | f.close() |
|
19 | 19 | os.utime('foo', (1000, 1000)) |
|
20 | 20 | |
|
21 | 21 | # add+commit 'foo' |
|
22 | 22 | repo[None].add(['foo']) |
|
23 | 23 | repo.commit(text='commit1', date="0 0") |
|
24 | 24 | |
|
25 | 25 | if os.name == 'nt': |
|
26 | 26 | d = repo[None]['foo'].date() |
|
27 | 27 | print("workingfilectx.date = (%d, %d)" % (d[0], d[1])) |
|
28 | 28 | else: |
|
29 | 29 | print("workingfilectx.date =", repo[None]['foo'].date()) |
|
30 | 30 | |
|
31 | 31 | # test memctx with non-ASCII commit message |
|
32 | 32 | |
|
33 | 33 | def filectxfn(repo, memctx, path): |
|
34 | 34 | return context.memfilectx(repo, "foo", "") |
|
35 | 35 | |
|
36 | 36 | ctx = context.memctx(repo, ['tip', None], |
|
37 | 37 | encoding.tolocal("Gr\xc3\xbcezi!"), |
|
38 | 38 | ["foo"], filectxfn) |
|
39 | 39 | ctx.commit() |
|
40 | 40 | for enc in "ASCII", "Latin-1", "UTF-8": |
|
41 | 41 | encoding.encoding = enc |
|
42 | 42 | print("%-8s: %s" % (enc, repo["tip"].description())) |
|
43 | 43 | |
|
44 | 44 | # test performing a status |
|
45 | 45 | |
|
46 | 46 | def getfilectx(repo, memctx, f): |
|
47 | 47 | fctx = memctx.parents()[0][f] |
|
48 | 48 | data, flags = fctx.data(), fctx.flags() |
|
49 | 49 | if f == 'foo': |
|
50 | 50 | data += 'bar\n' |
|
51 | 51 | return context.memfilectx(repo, f, data, 'l' in flags, 'x' in flags) |
|
52 | 52 | |
|
53 | 53 | ctxa = repo.changectx(0) |
|
54 | 54 | ctxb = context.memctx(repo, [ctxa.node(), None], "test diff", ["foo"], |
|
55 | 55 | getfilectx, ctxa.user(), ctxa.date()) |
|
56 | 56 | |
|
57 | 57 | print(ctxb.status(ctxa)) |
|
58 | 58 | |
|
59 | 59 | # test performing a diff on a memctx |
|
60 | 60 | |
|
61 | 61 | for d in ctxb.diff(ctxa, git=True): |
|
62 | 62 | print(d) |
|
63 | 63 | |
|
64 | 64 | # test safeness and correctness of "ctx.status()" |
|
65 | 65 | print('= checking context.status():') |
|
66 | 66 | |
|
67 | 67 | # ancestor "wcctx ~ 2" |
|
68 | 68 | actx2 = repo['.'] |
|
69 | 69 | |
|
70 | 70 | repo.wwrite('bar-m', 'bar-m\n', '') |
|
71 | 71 | repo.wwrite('bar-r', 'bar-r\n', '') |
|
72 | 72 | repo[None].add(['bar-m', 'bar-r']) |
|
73 | 73 | repo.commit(text='add bar-m, bar-r', date="0 0") |
|
74 | 74 | |
|
75 | 75 | # ancestor "wcctx ~ 1" |
|
76 | 76 | actx1 = repo['.'] |
|
77 | 77 | |
|
78 | 78 | repo.wwrite('bar-m', 'bar-m bar-m\n', '') |
|
79 | 79 | repo.wwrite('bar-a', 'bar-a\n', '') |
|
80 | 80 | repo[None].add(['bar-a']) |
|
81 | 81 | repo[None].forget(['bar-r']) |
|
82 | 82 | |
|
83 | 83 | # status at this point: |
|
84 | 84 | # M bar-m |
|
85 | 85 | # A bar-a |
|
86 | 86 | # R bar-r |
|
87 | 87 | # C foo |
|
88 | 88 | |
|
89 | 89 | from mercurial import scmutil |
|
90 | 90 | |
|
91 | 91 | print('== checking workingctx.status:') |
|
92 | 92 | |
|
93 | 93 | wctx = repo[None] |
|
94 | 94 | print('wctx._status=%s' % (str(wctx._status))) |
|
95 | 95 | |
|
96 | 96 | print('=== with "pattern match":') |
|
97 | 97 | print(actx1.status(other=wctx, |
|
98 | 98 | match=scmutil.matchfiles(repo, ['bar-m', 'foo']))) |
|
99 | 99 | print('wctx._status=%s' % (str(wctx._status))) |
|
100 | 100 | print(actx2.status(other=wctx, |
|
101 | 101 | match=scmutil.matchfiles(repo, ['bar-m', 'foo']))) |
|
102 | 102 | print('wctx._status=%s' % (str(wctx._status))) |
|
103 | 103 | |
|
104 | 104 | print('=== with "always match" and "listclean=True":') |
|
105 | 105 | print(actx1.status(other=wctx, listclean=True)) |
|
106 | 106 | print('wctx._status=%s' % (str(wctx._status))) |
|
107 | 107 | print(actx2.status(other=wctx, listclean=True)) |
|
108 | 108 | print('wctx._status=%s' % (str(wctx._status))) |
|
109 | 109 | |
|
110 | 110 | print("== checking workingcommitctx.status:") |
|
111 | 111 | |
|
112 | 112 | wcctx = context.workingcommitctx(repo, |
|
113 | 113 | scmutil.status(['bar-m'], |
|
114 | 114 | ['bar-a'], |
|
115 | 115 | [], |
|
116 | 116 | [], [], [], []), |
|
117 | 117 | text='', date='0 0') |
|
118 | 118 | print('wcctx._status=%s' % (str(wcctx._status))) |
|
119 | 119 | |
|
120 | 120 | print('=== with "always match":') |
|
121 | 121 | print(actx1.status(other=wcctx)) |
|
122 | 122 | print('wcctx._status=%s' % (str(wcctx._status))) |
|
123 | 123 | print(actx2.status(other=wcctx)) |
|
124 | 124 | print('wcctx._status=%s' % (str(wcctx._status))) |
|
125 | 125 | |
|
126 | 126 | print('=== with "always match" and "listclean=True":') |
|
127 | 127 | print(actx1.status(other=wcctx, listclean=True)) |
|
128 | 128 | print('wcctx._status=%s' % (str(wcctx._status))) |
|
129 | 129 | print(actx2.status(other=wcctx, listclean=True)) |
|
130 | 130 | print('wcctx._status=%s' % (str(wcctx._status))) |
|
131 | 131 | |
|
132 | 132 | print('=== with "pattern match":') |
|
133 | 133 | print(actx1.status(other=wcctx, |
|
134 | 134 | match=scmutil.matchfiles(repo, ['bar-m', 'foo']))) |
|
135 | 135 | print('wcctx._status=%s' % (str(wcctx._status))) |
|
136 | 136 | print(actx2.status(other=wcctx, |
|
137 | 137 | match=scmutil.matchfiles(repo, ['bar-m', 'foo']))) |
|
138 | 138 | print('wcctx._status=%s' % (str(wcctx._status))) |
|
139 | 139 | |
|
140 | 140 | print('=== with "pattern match" and "listclean=True":') |
|
141 | 141 | print(actx1.status(other=wcctx, |
|
142 | 142 | match=scmutil.matchfiles(repo, ['bar-r', 'foo']), |
|
143 | 143 | listclean=True)) |
|
144 | 144 | print('wcctx._status=%s' % (str(wcctx._status))) |
|
145 | 145 | print(actx2.status(other=wcctx, |
|
146 | 146 | match=scmutil.matchfiles(repo, ['bar-r', 'foo']), |
|
147 | 147 | listclean=True)) |
|
148 | 148 | print('wcctx._status=%s' % (str(wcctx._status))) |
@@ -1,41 +1,41 | |||
|
1 | 1 | from __future__ import absolute_import, print_function |
|
2 | 2 | import os |
|
3 | 3 | from mercurial import ( |
|
4 | 4 | commands, |
|
5 | 5 | extensions, |
|
6 | 6 | ui as uimod, |
|
7 | 7 | ) |
|
8 | 8 | |
|
9 | 9 | ignore = set(['highlight', 'win32text', 'factotum']) |
|
10 | 10 | |
|
11 | 11 | if os.name != 'nt': |
|
12 | 12 | ignore.add('win32mbcs') |
|
13 | 13 | |
|
14 | 14 | disabled = [ext for ext in extensions.disabled().keys() if ext not in ignore] |
|
15 | 15 | |
|
16 | 16 | hgrc = open(os.environ["HGRCPATH"], 'w') |
|
17 | 17 | hgrc.write('[extensions]\n') |
|
18 | 18 | |
|
19 | 19 | for ext in disabled: |
|
20 | 20 | hgrc.write(ext + '=\n') |
|
21 | 21 | |
|
22 | 22 | hgrc.close() |
|
23 | 23 | |
|
24 | u = uimod.ui() | |
|
24 | u = uimod.ui.load() | |
|
25 | 25 | extensions.loadall(u) |
|
26 | 26 | |
|
27 | 27 | globalshort = set() |
|
28 | 28 | globallong = set() |
|
29 | 29 | for option in commands.globalopts: |
|
30 | 30 | option[0] and globalshort.add(option[0]) |
|
31 | 31 | option[1] and globallong.add(option[1]) |
|
32 | 32 | |
|
33 | 33 | for cmd, entry in commands.table.iteritems(): |
|
34 | 34 | seenshort = globalshort.copy() |
|
35 | 35 | seenlong = globallong.copy() |
|
36 | 36 | for option in entry[1]: |
|
37 | 37 | if (option[0] and option[0] in seenshort) or \ |
|
38 | 38 | (option[1] and option[1] in seenlong): |
|
39 | 39 | print("command '" + cmd + "' has duplicate option " + str(option)) |
|
40 | 40 | seenshort.add(option[0]) |
|
41 | 41 | seenlong.add(option[1]) |
@@ -1,247 +1,247 | |||
|
1 | 1 | from __future__ import absolute_import, print_function |
|
2 | 2 | import os |
|
3 | 3 | import subprocess |
|
4 | 4 | import sys |
|
5 | 5 | |
|
6 | 6 | if subprocess.call(['python', '%s/hghave' % os.environ['TESTDIR'], |
|
7 | 7 | 'cacheable']): |
|
8 | 8 | sys.exit(80) |
|
9 | 9 | |
|
10 | 10 | from mercurial import ( |
|
11 | 11 | extensions, |
|
12 | 12 | hg, |
|
13 | 13 | scmutil, |
|
14 | 14 | ui as uimod, |
|
15 | 15 | util, |
|
16 | 16 | ) |
|
17 | 17 | |
|
18 | 18 | filecache = scmutil.filecache |
|
19 | 19 | |
|
20 | 20 | class fakerepo(object): |
|
21 | 21 | def __init__(self): |
|
22 | 22 | self._filecache = {} |
|
23 | 23 | |
|
24 | 24 | def join(self, p): |
|
25 | 25 | return p |
|
26 | 26 | |
|
27 | 27 | def sjoin(self, p): |
|
28 | 28 | return p |
|
29 | 29 | |
|
30 | 30 | @filecache('x', 'y') |
|
31 | 31 | def cached(self): |
|
32 | 32 | print('creating') |
|
33 | 33 | return 'string from function' |
|
34 | 34 | |
|
35 | 35 | def invalidate(self): |
|
36 | 36 | for k in self._filecache: |
|
37 | 37 | try: |
|
38 | 38 | delattr(self, k) |
|
39 | 39 | except AttributeError: |
|
40 | 40 | pass |
|
41 | 41 | |
|
42 | 42 | def basic(repo): |
|
43 | 43 | print("* neither file exists") |
|
44 | 44 | # calls function |
|
45 | 45 | repo.cached |
|
46 | 46 | |
|
47 | 47 | repo.invalidate() |
|
48 | 48 | print("* neither file still exists") |
|
49 | 49 | # uses cache |
|
50 | 50 | repo.cached |
|
51 | 51 | |
|
52 | 52 | # create empty file |
|
53 | 53 | f = open('x', 'w') |
|
54 | 54 | f.close() |
|
55 | 55 | repo.invalidate() |
|
56 | 56 | print("* empty file x created") |
|
57 | 57 | # should recreate the object |
|
58 | 58 | repo.cached |
|
59 | 59 | |
|
60 | 60 | f = open('x', 'w') |
|
61 | 61 | f.write('a') |
|
62 | 62 | f.close() |
|
63 | 63 | repo.invalidate() |
|
64 | 64 | print("* file x changed size") |
|
65 | 65 | # should recreate the object |
|
66 | 66 | repo.cached |
|
67 | 67 | |
|
68 | 68 | repo.invalidate() |
|
69 | 69 | print("* nothing changed with either file") |
|
70 | 70 | # stats file again, reuses object |
|
71 | 71 | repo.cached |
|
72 | 72 | |
|
73 | 73 | # atomic replace file, size doesn't change |
|
74 | 74 | # hopefully st_mtime doesn't change as well so this doesn't use the cache |
|
75 | 75 | # because of inode change |
|
76 | 76 | f = scmutil.opener('.')('x', 'w', atomictemp=True) |
|
77 | 77 | f.write('b') |
|
78 | 78 | f.close() |
|
79 | 79 | |
|
80 | 80 | repo.invalidate() |
|
81 | 81 | print("* file x changed inode") |
|
82 | 82 | repo.cached |
|
83 | 83 | |
|
84 | 84 | # create empty file y |
|
85 | 85 | f = open('y', 'w') |
|
86 | 86 | f.close() |
|
87 | 87 | repo.invalidate() |
|
88 | 88 | print("* empty file y created") |
|
89 | 89 | # should recreate the object |
|
90 | 90 | repo.cached |
|
91 | 91 | |
|
92 | 92 | f = open('y', 'w') |
|
93 | 93 | f.write('A') |
|
94 | 94 | f.close() |
|
95 | 95 | repo.invalidate() |
|
96 | 96 | print("* file y changed size") |
|
97 | 97 | # should recreate the object |
|
98 | 98 | repo.cached |
|
99 | 99 | |
|
100 | 100 | f = scmutil.opener('.')('y', 'w', atomictemp=True) |
|
101 | 101 | f.write('B') |
|
102 | 102 | f.close() |
|
103 | 103 | |
|
104 | 104 | repo.invalidate() |
|
105 | 105 | print("* file y changed inode") |
|
106 | 106 | repo.cached |
|
107 | 107 | |
|
108 | 108 | f = scmutil.opener('.')('x', 'w', atomictemp=True) |
|
109 | 109 | f.write('c') |
|
110 | 110 | f.close() |
|
111 | 111 | f = scmutil.opener('.')('y', 'w', atomictemp=True) |
|
112 | 112 | f.write('C') |
|
113 | 113 | f.close() |
|
114 | 114 | |
|
115 | 115 | repo.invalidate() |
|
116 | 116 | print("* both files changed inode") |
|
117 | 117 | repo.cached |
|
118 | 118 | |
|
119 | 119 | def fakeuncacheable(): |
|
120 | 120 | def wrapcacheable(orig, *args, **kwargs): |
|
121 | 121 | return False |
|
122 | 122 | |
|
123 | 123 | def wrapinit(orig, *args, **kwargs): |
|
124 | 124 | pass |
|
125 | 125 | |
|
126 | 126 | originit = extensions.wrapfunction(util.cachestat, '__init__', wrapinit) |
|
127 | 127 | origcacheable = extensions.wrapfunction(util.cachestat, 'cacheable', |
|
128 | 128 | wrapcacheable) |
|
129 | 129 | |
|
130 | 130 | for fn in ['x', 'y']: |
|
131 | 131 | try: |
|
132 | 132 | os.remove(fn) |
|
133 | 133 | except OSError: |
|
134 | 134 | pass |
|
135 | 135 | |
|
136 | 136 | basic(fakerepo()) |
|
137 | 137 | |
|
138 | 138 | util.cachestat.cacheable = origcacheable |
|
139 | 139 | util.cachestat.__init__ = originit |
|
140 | 140 | |
|
141 | 141 | def test_filecache_synced(): |
|
142 | 142 | # test old behavior that caused filecached properties to go out of sync |
|
143 | 143 | os.system('hg init && echo a >> a && hg ci -qAm.') |
|
144 | repo = hg.repository(uimod.ui()) | |
|
144 | repo = hg.repository(uimod.ui.load()) | |
|
145 | 145 | # first rollback clears the filecache, but changelog to stays in __dict__ |
|
146 | 146 | repo.rollback() |
|
147 | 147 | repo.commit('.') |
|
148 | 148 | # second rollback comes along and touches the changelog externally |
|
149 | 149 | # (file is moved) |
|
150 | 150 | repo.rollback() |
|
151 | 151 | # but since changelog isn't under the filecache control anymore, we don't |
|
152 | 152 | # see that it changed, and return the old changelog without reconstructing |
|
153 | 153 | # it |
|
154 | 154 | repo.commit('.') |
|
155 | 155 | |
|
156 | 156 | def setbeforeget(repo): |
|
157 | 157 | os.remove('x') |
|
158 | 158 | os.remove('y') |
|
159 | 159 | repo.cached = 'string set externally' |
|
160 | 160 | repo.invalidate() |
|
161 | 161 | print("* neither file exists") |
|
162 | 162 | print(repo.cached) |
|
163 | 163 | repo.invalidate() |
|
164 | 164 | f = open('x', 'w') |
|
165 | 165 | f.write('a') |
|
166 | 166 | f.close() |
|
167 | 167 | print("* file x created") |
|
168 | 168 | print(repo.cached) |
|
169 | 169 | |
|
170 | 170 | repo.cached = 'string 2 set externally' |
|
171 | 171 | repo.invalidate() |
|
172 | 172 | print("* string set externally again") |
|
173 | 173 | print(repo.cached) |
|
174 | 174 | |
|
175 | 175 | repo.invalidate() |
|
176 | 176 | f = open('y', 'w') |
|
177 | 177 | f.write('b') |
|
178 | 178 | f.close() |
|
179 | 179 | print("* file y created") |
|
180 | 180 | print(repo.cached) |
|
181 | 181 | |
|
182 | 182 | def antiambiguity(): |
|
183 | 183 | filename = 'ambigcheck' |
|
184 | 184 | |
|
185 | 185 | # try some times, because reproduction of ambiguity depends on |
|
186 | 186 | # "filesystem time" |
|
187 | 187 | for i in xrange(5): |
|
188 | 188 | fp = open(filename, 'w') |
|
189 | 189 | fp.write('FOO') |
|
190 | 190 | fp.close() |
|
191 | 191 | |
|
192 | 192 | oldstat = os.stat(filename) |
|
193 | 193 | if oldstat.st_ctime != oldstat.st_mtime: |
|
194 | 194 | # subsequent changing never causes ambiguity |
|
195 | 195 | continue |
|
196 | 196 | |
|
197 | 197 | repetition = 3 |
|
198 | 198 | |
|
199 | 199 | # repeat changing via checkambigatclosing, to examine whether |
|
200 | 200 | # st_mtime is advanced multiple times as expected |
|
201 | 201 | for i in xrange(repetition): |
|
202 | 202 | # explicit closing |
|
203 | 203 | fp = scmutil.checkambigatclosing(open(filename, 'a')) |
|
204 | 204 | fp.write('FOO') |
|
205 | 205 | fp.close() |
|
206 | 206 | |
|
207 | 207 | # implicit closing by "with" statement |
|
208 | 208 | with scmutil.checkambigatclosing(open(filename, 'a')) as fp: |
|
209 | 209 | fp.write('BAR') |
|
210 | 210 | |
|
211 | 211 | newstat = os.stat(filename) |
|
212 | 212 | if oldstat.st_ctime != newstat.st_ctime: |
|
213 | 213 | # timestamp ambiguity was naturally avoided while repetition |
|
214 | 214 | continue |
|
215 | 215 | |
|
216 | 216 | # st_mtime should be advanced "repetition * 2" times, because |
|
217 | 217 | # all changes occurred at same time (in sec) |
|
218 | 218 | expected = (oldstat.st_mtime + repetition * 2) & 0x7fffffff |
|
219 | 219 | if newstat.st_mtime != expected: |
|
220 | 220 | print("'newstat.st_mtime %s is not %s (as %s + %s * 2)" % |
|
221 | 221 | (newstat.st_mtime, expected, oldstat.st_mtime, repetition)) |
|
222 | 222 | |
|
223 | 223 | # no more examination is needed regardless of result |
|
224 | 224 | break |
|
225 | 225 | else: |
|
226 | 226 | # This platform seems too slow to examine anti-ambiguity |
|
227 | 227 | # of file timestamp (or test happened to be executed at |
|
228 | 228 | # bad timing). Exit silently in this case, because running |
|
229 | 229 | # on other faster platforms can detect problems |
|
230 | 230 | pass |
|
231 | 231 | |
|
232 | 232 | print('basic:') |
|
233 | 233 | print() |
|
234 | 234 | basic(fakerepo()) |
|
235 | 235 | print() |
|
236 | 236 | print('fakeuncacheable:') |
|
237 | 237 | print() |
|
238 | 238 | fakeuncacheable() |
|
239 | 239 | test_filecache_synced() |
|
240 | 240 | print() |
|
241 | 241 | print('setbeforeget:') |
|
242 | 242 | print() |
|
243 | 243 | setbeforeget(fakerepo()) |
|
244 | 244 | print() |
|
245 | 245 | print('antiambiguity:') |
|
246 | 246 | print() |
|
247 | 247 | antiambiguity() |
@@ -1,63 +1,63 | |||
|
1 | 1 | #!/usr/bin/env python |
|
2 | 2 | """ |
|
3 | 3 | Tests the behavior of filelog w.r.t. data starting with '\1\n' |
|
4 | 4 | """ |
|
5 | 5 | from __future__ import absolute_import, print_function |
|
6 | 6 | |
|
7 | 7 | from mercurial.node import ( |
|
8 | 8 | hex, |
|
9 | 9 | nullid, |
|
10 | 10 | ) |
|
11 | 11 | from mercurial import ( |
|
12 | 12 | hg, |
|
13 | 13 | ui as uimod, |
|
14 | 14 | ) |
|
15 | 15 | |
|
16 | myui = uimod.ui() | |
|
16 | myui = uimod.ui.load() | |
|
17 | 17 | repo = hg.repository(myui, path='.', create=True) |
|
18 | 18 | |
|
19 | 19 | fl = repo.file('foobar') |
|
20 | 20 | |
|
21 | 21 | def addrev(text, renamed=False): |
|
22 | 22 | if renamed: |
|
23 | 23 | # data doesn't matter. Just make sure filelog.renamed() returns True |
|
24 | 24 | meta = {'copyrev': hex(nullid), 'copy': 'bar'} |
|
25 | 25 | else: |
|
26 | 26 | meta = {} |
|
27 | 27 | |
|
28 | 28 | lock = t = None |
|
29 | 29 | try: |
|
30 | 30 | lock = repo.lock() |
|
31 | 31 | t = repo.transaction('commit') |
|
32 | 32 | node = fl.add(text, meta, t, 0, nullid, nullid) |
|
33 | 33 | return node |
|
34 | 34 | finally: |
|
35 | 35 | if t: |
|
36 | 36 | t.close() |
|
37 | 37 | if lock: |
|
38 | 38 | lock.release() |
|
39 | 39 | |
|
40 | 40 | def error(text): |
|
41 | 41 | print('ERROR: ' + text) |
|
42 | 42 | |
|
43 | 43 | textwith = '\1\nfoo' |
|
44 | 44 | without = 'foo' |
|
45 | 45 | |
|
46 | 46 | node = addrev(textwith) |
|
47 | 47 | if not textwith == fl.read(node): |
|
48 | 48 | error('filelog.read for data starting with \\1\\n') |
|
49 | 49 | if fl.cmp(node, textwith) or not fl.cmp(node, without): |
|
50 | 50 | error('filelog.cmp for data starting with \\1\\n') |
|
51 | 51 | if fl.size(0) != len(textwith): |
|
52 | 52 | error('FIXME: This is a known failure of filelog.size for data starting ' |
|
53 | 53 | 'with \\1\\n') |
|
54 | 54 | |
|
55 | 55 | node = addrev(textwith, renamed=True) |
|
56 | 56 | if not textwith == fl.read(node): |
|
57 | 57 | error('filelog.read for a renaming + data starting with \\1\\n') |
|
58 | 58 | if fl.cmp(node, textwith) or not fl.cmp(node, without): |
|
59 | 59 | error('filelog.cmp for a renaming + data starting with \\1\\n') |
|
60 | 60 | if fl.size(1) != len(textwith): |
|
61 | 61 | error('filelog.size for a renaming + data starting with \\1\\n') |
|
62 | 62 | |
|
63 | 63 | print('OK.') |
@@ -1,114 +1,114 | |||
|
1 | 1 | from __future__ import absolute_import, print_function |
|
2 | 2 | |
|
3 | 3 | from mercurial import demandimport; demandimport.enable() |
|
4 | 4 | from mercurial import ( |
|
5 | 5 | error, |
|
6 | 6 | ui as uimod, |
|
7 | 7 | url, |
|
8 | 8 | util, |
|
9 | 9 | ) |
|
10 | 10 | |
|
11 | 11 | urlerr = util.urlerr |
|
12 | 12 | urlreq = util.urlreq |
|
13 | 13 | |
|
14 | 14 | class myui(uimod.ui): |
|
15 | 15 | def interactive(self): |
|
16 | 16 | return False |
|
17 | 17 | |
|
18 | origui = myui() | |
|
18 | origui = myui.load() | |
|
19 | 19 | |
|
20 | 20 | def writeauth(items): |
|
21 | 21 | ui = origui.copy() |
|
22 | 22 | for name, value in items.iteritems(): |
|
23 | 23 | ui.setconfig('auth', name, value) |
|
24 | 24 | return ui |
|
25 | 25 | |
|
26 | 26 | def dumpdict(dict): |
|
27 | 27 | return '{' + ', '.join(['%s: %s' % (k, dict[k]) |
|
28 | 28 | for k in sorted(dict.iterkeys())]) + '}' |
|
29 | 29 | |
|
30 | 30 | def test(auth, urls=None): |
|
31 | 31 | print('CFG:', dumpdict(auth)) |
|
32 | 32 | prefixes = set() |
|
33 | 33 | for k in auth: |
|
34 | 34 | prefixes.add(k.split('.', 1)[0]) |
|
35 | 35 | for p in prefixes: |
|
36 | 36 | for name in ('.username', '.password'): |
|
37 | 37 | if (p + name) not in auth: |
|
38 | 38 | auth[p + name] = p |
|
39 | 39 | auth = dict((k, v) for k, v in auth.iteritems() if v is not None) |
|
40 | 40 | |
|
41 | 41 | ui = writeauth(auth) |
|
42 | 42 | |
|
43 | 43 | def _test(uri): |
|
44 | 44 | print('URI:', uri) |
|
45 | 45 | try: |
|
46 | 46 | pm = url.passwordmgr(ui, urlreq.httppasswordmgrwithdefaultrealm()) |
|
47 | 47 | u, authinfo = util.url(uri).authinfo() |
|
48 | 48 | if authinfo is not None: |
|
49 | 49 | pm.add_password(*authinfo) |
|
50 | 50 | print(' ', pm.find_user_password('test', u)) |
|
51 | 51 | except error.Abort: |
|
52 | 52 | print(' ','abort') |
|
53 | 53 | |
|
54 | 54 | if not urls: |
|
55 | 55 | urls = [ |
|
56 | 56 | 'http://example.org/foo', |
|
57 | 57 | 'http://example.org/foo/bar', |
|
58 | 58 | 'http://example.org/bar', |
|
59 | 59 | 'https://example.org/foo', |
|
60 | 60 | 'https://example.org/foo/bar', |
|
61 | 61 | 'https://example.org/bar', |
|
62 | 62 | 'https://x@example.org/bar', |
|
63 | 63 | 'https://y@example.org/bar', |
|
64 | 64 | ] |
|
65 | 65 | for u in urls: |
|
66 | 66 | _test(u) |
|
67 | 67 | |
|
68 | 68 | |
|
69 | 69 | print('\n*** Test in-uri schemes\n') |
|
70 | 70 | test({'x.prefix': 'http://example.org'}) |
|
71 | 71 | test({'x.prefix': 'https://example.org'}) |
|
72 | 72 | test({'x.prefix': 'http://example.org', 'x.schemes': 'https'}) |
|
73 | 73 | test({'x.prefix': 'https://example.org', 'x.schemes': 'http'}) |
|
74 | 74 | |
|
75 | 75 | print('\n*** Test separately configured schemes\n') |
|
76 | 76 | test({'x.prefix': 'example.org', 'x.schemes': 'http'}) |
|
77 | 77 | test({'x.prefix': 'example.org', 'x.schemes': 'https'}) |
|
78 | 78 | test({'x.prefix': 'example.org', 'x.schemes': 'http https'}) |
|
79 | 79 | |
|
80 | 80 | print('\n*** Test prefix matching\n') |
|
81 | 81 | test({'x.prefix': 'http://example.org/foo', |
|
82 | 82 | 'y.prefix': 'http://example.org/bar'}) |
|
83 | 83 | test({'x.prefix': 'http://example.org/foo', |
|
84 | 84 | 'y.prefix': 'http://example.org/foo/bar'}) |
|
85 | 85 | test({'x.prefix': '*', 'y.prefix': 'https://example.org/bar'}) |
|
86 | 86 | |
|
87 | 87 | print('\n*** Test user matching\n') |
|
88 | 88 | test({'x.prefix': 'http://example.org/foo', |
|
89 | 89 | 'x.username': None, |
|
90 | 90 | 'x.password': 'xpassword'}, |
|
91 | 91 | urls=['http://y@example.org/foo']) |
|
92 | 92 | test({'x.prefix': 'http://example.org/foo', |
|
93 | 93 | 'x.username': None, |
|
94 | 94 | 'x.password': 'xpassword', |
|
95 | 95 | 'y.prefix': 'http://example.org/foo', |
|
96 | 96 | 'y.username': 'y', |
|
97 | 97 | 'y.password': 'ypassword'}, |
|
98 | 98 | urls=['http://y@example.org/foo']) |
|
99 | 99 | test({'x.prefix': 'http://example.org/foo/bar', |
|
100 | 100 | 'x.username': None, |
|
101 | 101 | 'x.password': 'xpassword', |
|
102 | 102 | 'y.prefix': 'http://example.org/foo', |
|
103 | 103 | 'y.username': 'y', |
|
104 | 104 | 'y.password': 'ypassword'}, |
|
105 | 105 | urls=['http://y@example.org/foo/bar']) |
|
106 | 106 | |
|
107 | 107 | def testauthinfo(fullurl, authurl): |
|
108 | 108 | print('URIs:', fullurl, authurl) |
|
109 | 109 | pm = urlreq.httppasswordmgrwithdefaultrealm() |
|
110 | 110 | pm.add_password(*util.url(fullurl).authinfo()[1]) |
|
111 | 111 | print(pm.find_user_password('test', authurl)) |
|
112 | 112 | |
|
113 | 113 | print('\n*** Test urllib2 and util.url\n') |
|
114 | 114 | testauthinfo('http://user@example.com:8080/foo', 'http://example.com:8080/foo') |
@@ -1,48 +1,48 | |||
|
1 | 1 | from __future__ import absolute_import |
|
2 | 2 | |
|
3 | 3 | import os |
|
4 | 4 | from mercurial import ( |
|
5 | 5 | hg, |
|
6 | 6 | ui as uimod, |
|
7 | 7 | ) |
|
8 | 8 | from mercurial.hgweb import ( |
|
9 | 9 | hgwebdir_mod, |
|
10 | 10 | ) |
|
11 | 11 | hgwebdir = hgwebdir_mod.hgwebdir |
|
12 | 12 | |
|
13 | 13 | os.mkdir('webdir') |
|
14 | 14 | os.chdir('webdir') |
|
15 | 15 | |
|
16 | 16 | webdir = os.path.realpath('.') |
|
17 | 17 | |
|
18 | u = uimod.ui() | |
|
18 | u = uimod.ui.load() | |
|
19 | 19 | hg.repository(u, 'a', create=1) |
|
20 | 20 | hg.repository(u, 'b', create=1) |
|
21 | 21 | os.chdir('b') |
|
22 | 22 | hg.repository(u, 'd', create=1) |
|
23 | 23 | os.chdir('..') |
|
24 | 24 | hg.repository(u, 'c', create=1) |
|
25 | 25 | os.chdir('..') |
|
26 | 26 | |
|
27 | 27 | paths = {'t/a/': '%s/a' % webdir, |
|
28 | 28 | 'b': '%s/b' % webdir, |
|
29 | 29 | 'coll': '%s/*' % webdir, |
|
30 | 30 | 'rcoll': '%s/**' % webdir} |
|
31 | 31 | |
|
32 | 32 | config = os.path.join(webdir, 'hgwebdir.conf') |
|
33 | 33 | configfile = open(config, 'w') |
|
34 | 34 | configfile.write('[paths]\n') |
|
35 | 35 | for k, v in paths.items(): |
|
36 | 36 | configfile.write('%s = %s\n' % (k, v)) |
|
37 | 37 | configfile.close() |
|
38 | 38 | |
|
39 | 39 | confwd = hgwebdir(config) |
|
40 | 40 | dictwd = hgwebdir(paths) |
|
41 | 41 | |
|
42 | 42 | assert len(confwd.repos) == len(dictwd.repos), 'different numbers' |
|
43 | 43 | assert len(confwd.repos) == 9, 'expected 9 repos, found %d' % len(confwd.repos) |
|
44 | 44 | |
|
45 | 45 | found = dict(confwd.repos) |
|
46 | 46 | for key, path in dictwd.repos: |
|
47 | 47 | assert key in found, 'repository %s was not found' % key |
|
48 | 48 | assert found[key] == path, 'different paths for repo %s' % key |
@@ -1,96 +1,96 | |||
|
1 | 1 | #require killdaemons |
|
2 | 2 | |
|
3 | 3 | $ hgserve() { |
|
4 | 4 | > hg serve -a localhost -p $HGPORT1 -d --pid-file=hg.pid \ |
|
5 | 5 | > -E errors.log -v $@ > startup.log |
|
6 | 6 | > # Grepping hg serve stdout would hang on Windows |
|
7 | 7 | > grep -v 'listening at' startup.log |
|
8 | 8 | > cat hg.pid >> "$DAEMON_PIDS" |
|
9 | 9 | > } |
|
10 | 10 | $ hg init a |
|
11 | 11 | $ hg --encoding utf-8 -R a branch æ |
|
12 | 12 | marked working directory as branch \xc3\xa6 (esc) |
|
13 | 13 | (branches are permanent and global, did you want a bookmark?) |
|
14 | 14 | $ echo foo > a/foo |
|
15 | 15 | $ hg -R a ci -Am foo |
|
16 | 16 | adding foo |
|
17 | 17 | $ hgserve -R a --config web.push_ssl=False --config web.allow_push=* --encoding latin1 |
|
18 | 18 | $ hg --encoding utf-8 clone http://localhost:$HGPORT1 b |
|
19 | 19 | requesting all changes |
|
20 | 20 | adding changesets |
|
21 | 21 | adding manifests |
|
22 | 22 | adding file changes |
|
23 | 23 | added 1 changesets with 1 changes to 1 files |
|
24 | 24 | updating to branch \xc3\xa6 (esc) |
|
25 | 25 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
26 | 26 | $ hg --encoding utf-8 -R b log |
|
27 | 27 | changeset: 0:867c11ce77b8 |
|
28 | 28 | branch: \xc3\xa6 (esc) |
|
29 | 29 | tag: tip |
|
30 | 30 | user: test |
|
31 | 31 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
32 | 32 | summary: foo |
|
33 | 33 | |
|
34 | 34 | $ echo bar >> b/foo |
|
35 | 35 | $ hg -R b ci -m bar |
|
36 | 36 | $ hg --encoding utf-8 -R b push |
|
37 | 37 | pushing to http://localhost:$HGPORT1/ |
|
38 | 38 | searching for changes |
|
39 | 39 | remote: adding changesets |
|
40 | 40 | remote: adding manifests |
|
41 | 41 | remote: adding file changes |
|
42 | 42 | remote: added 1 changesets with 1 changes to 1 files |
|
43 | 43 | $ hg -R a --encoding utf-8 log |
|
44 | 44 | changeset: 1:58e7c90d67cb |
|
45 | 45 | branch: \xc3\xa6 (esc) |
|
46 | 46 | tag: tip |
|
47 | 47 | user: test |
|
48 | 48 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
49 | 49 | summary: bar |
|
50 | 50 | |
|
51 | 51 | changeset: 0:867c11ce77b8 |
|
52 | 52 | branch: \xc3\xa6 (esc) |
|
53 | 53 | user: test |
|
54 | 54 | date: Thu Jan 01 00:00:00 1970 +0000 |
|
55 | 55 | summary: foo |
|
56 | 56 | |
|
57 | 57 | $ killdaemons.py hg.pid |
|
58 | 58 | |
|
59 | 59 | verify 7e7d56fe4833 (encoding fallback in branchmap to maintain compatibility with 1.3.x) |
|
60 | 60 | |
|
61 | 61 | $ cat <<EOF > oldhg |
|
62 | 62 | > import sys |
|
63 | 63 | > from mercurial import ui, hg, commands |
|
64 | 64 | > |
|
65 | 65 | > class StdoutWrapper(object): |
|
66 | 66 | > def __init__(self, stdout): |
|
67 | 67 | > self._file = stdout |
|
68 | 68 | > |
|
69 | 69 | > def write(self, data): |
|
70 | 70 | > if data == '47\n': |
|
71 | 71 | > # latin1 encoding is one %xx (3 bytes) shorter |
|
72 | 72 | > data = '44\n' |
|
73 | 73 | > elif data.startswith('%C3%A6 '): |
|
74 | 74 | > # translate to latin1 encoding |
|
75 | 75 | > data = '%%E6 %s' % data[7:] |
|
76 | 76 | > self._file.write(data) |
|
77 | 77 | > |
|
78 | 78 | > def __getattr__(self, name): |
|
79 | 79 | > return getattr(self._file, name) |
|
80 | 80 | > |
|
81 | 81 | > sys.stdout = StdoutWrapper(sys.stdout) |
|
82 | 82 | > sys.stderr = StdoutWrapper(sys.stderr) |
|
83 | 83 | > |
|
84 | > myui = ui.ui() | |
|
84 | > myui = ui.ui.load() | |
|
85 | 85 | > repo = hg.repository(myui, 'a') |
|
86 | 86 | > commands.serve(myui, repo, stdio=True, cmdserver=False) |
|
87 | 87 | > EOF |
|
88 | 88 | $ echo baz >> b/foo |
|
89 | 89 | $ hg -R b ci -m baz |
|
90 | 90 | $ hg push -R b -e 'python oldhg' ssh://dummy/ --encoding latin1 |
|
91 | 91 | pushing to ssh://dummy/ |
|
92 | 92 | searching for changes |
|
93 | 93 | remote: adding changesets |
|
94 | 94 | remote: adding manifests |
|
95 | 95 | remote: adding file changes |
|
96 | 96 | remote: added 1 changesets with 1 changes to 1 files |
@@ -1,182 +1,182 | |||
|
1 | 1 | """test behavior of propertycache and unfiltered propertycache |
|
2 | 2 | |
|
3 | 3 | The repoview overlay is quite complex. We test the behavior of |
|
4 | 4 | property cache of both localrepo and repoview to prevent |
|
5 | 5 | regression.""" |
|
6 | 6 | |
|
7 | 7 | from __future__ import absolute_import, print_function |
|
8 | 8 | import os |
|
9 | 9 | import subprocess |
|
10 | 10 | |
|
11 | 11 | from mercurial import ( |
|
12 | 12 | hg, |
|
13 | 13 | localrepo, |
|
14 | 14 | ui as uimod, |
|
15 | 15 | util, |
|
16 | 16 | ) |
|
17 | 17 | |
|
18 | 18 | # create some special property cache that trace they call |
|
19 | 19 | |
|
20 | 20 | calllog = [] |
|
21 | 21 | @util.propertycache |
|
22 | 22 | def testcachedfoobar(repo): |
|
23 | 23 | name = repo.filtername |
|
24 | 24 | if name is None: |
|
25 | 25 | name = '' |
|
26 | 26 | val = len(name) |
|
27 | 27 | calllog.append(val) |
|
28 | 28 | return val |
|
29 | 29 | |
|
30 | 30 | unficalllog = [] |
|
31 | 31 | @localrepo.unfilteredpropertycache |
|
32 | 32 | def testcachedunfifoobar(repo): |
|
33 | 33 | name = repo.filtername |
|
34 | 34 | if name is None: |
|
35 | 35 | name = '' |
|
36 | 36 | val = 100 + len(name) |
|
37 | 37 | unficalllog.append(val) |
|
38 | 38 | return val |
|
39 | 39 | |
|
40 | 40 | #plug them on repo |
|
41 | 41 | localrepo.localrepository.testcachedfoobar = testcachedfoobar |
|
42 | 42 | localrepo.localrepository.testcachedunfifoobar = testcachedunfifoobar |
|
43 | 43 | |
|
44 | 44 | |
|
45 | 45 | # Create an empty repo and instantiate it. It is important to run |
|
46 | 46 | # these tests on the real object to detect regression. |
|
47 | 47 | repopath = os.path.join(os.environ['TESTTMP'], 'repo') |
|
48 | 48 | assert subprocess.call(['hg', 'init', repopath]) == 0 |
|
49 | ui = uimod.ui() | |
|
49 | ui = uimod.ui.load() | |
|
50 | 50 | repo = hg.repository(ui, path=repopath).unfiltered() |
|
51 | 51 | |
|
52 | 52 | |
|
53 | 53 | print('') |
|
54 | 54 | print('=== property cache ===') |
|
55 | 55 | print('') |
|
56 | 56 | print('calllog:', calllog) |
|
57 | 57 | print('cached value (unfiltered):', |
|
58 | 58 | vars(repo).get('testcachedfoobar', 'NOCACHE')) |
|
59 | 59 | |
|
60 | 60 | print('') |
|
61 | 61 | print('= first access on unfiltered, should do a call') |
|
62 | 62 | print('access:', repo.testcachedfoobar) |
|
63 | 63 | print('calllog:', calllog) |
|
64 | 64 | print('cached value (unfiltered):', |
|
65 | 65 | vars(repo).get('testcachedfoobar', 'NOCACHE')) |
|
66 | 66 | |
|
67 | 67 | print('') |
|
68 | 68 | print('= second access on unfiltered, should not do call') |
|
69 | 69 | print('access', repo.testcachedfoobar) |
|
70 | 70 | print('calllog:', calllog) |
|
71 | 71 | print('cached value (unfiltered):', |
|
72 | 72 | vars(repo).get('testcachedfoobar', 'NOCACHE')) |
|
73 | 73 | |
|
74 | 74 | print('') |
|
75 | 75 | print('= first access on "visible" view, should do a call') |
|
76 | 76 | visibleview = repo.filtered('visible') |
|
77 | 77 | print('cached value ("visible" view):', |
|
78 | 78 | vars(visibleview).get('testcachedfoobar', 'NOCACHE')) |
|
79 | 79 | print('access:', visibleview.testcachedfoobar) |
|
80 | 80 | print('calllog:', calllog) |
|
81 | 81 | print('cached value (unfiltered):', |
|
82 | 82 | vars(repo).get('testcachedfoobar', 'NOCACHE')) |
|
83 | 83 | print('cached value ("visible" view):', |
|
84 | 84 | vars(visibleview).get('testcachedfoobar', 'NOCACHE')) |
|
85 | 85 | |
|
86 | 86 | print('') |
|
87 | 87 | print('= second access on "visible view", should not do call') |
|
88 | 88 | print('access:', visibleview.testcachedfoobar) |
|
89 | 89 | print('calllog:', calllog) |
|
90 | 90 | print('cached value (unfiltered):', |
|
91 | 91 | vars(repo).get('testcachedfoobar', 'NOCACHE')) |
|
92 | 92 | print('cached value ("visible" view):', |
|
93 | 93 | vars(visibleview).get('testcachedfoobar', 'NOCACHE')) |
|
94 | 94 | |
|
95 | 95 | print('') |
|
96 | 96 | print('= no effect on other view') |
|
97 | 97 | immutableview = repo.filtered('immutable') |
|
98 | 98 | print('cached value ("immutable" view):', |
|
99 | 99 | vars(immutableview).get('testcachedfoobar', 'NOCACHE')) |
|
100 | 100 | print('access:', immutableview.testcachedfoobar) |
|
101 | 101 | print('calllog:', calllog) |
|
102 | 102 | print('cached value (unfiltered):', |
|
103 | 103 | vars(repo).get('testcachedfoobar', 'NOCACHE')) |
|
104 | 104 | print('cached value ("visible" view):', |
|
105 | 105 | vars(visibleview).get('testcachedfoobar', 'NOCACHE')) |
|
106 | 106 | print('cached value ("immutable" view):', |
|
107 | 107 | vars(immutableview).get('testcachedfoobar', 'NOCACHE')) |
|
108 | 108 | |
|
109 | 109 | # unfiltered property cache test |
|
110 | 110 | print('') |
|
111 | 111 | print('') |
|
112 | 112 | print('=== unfiltered property cache ===') |
|
113 | 113 | print('') |
|
114 | 114 | print('unficalllog:', unficalllog) |
|
115 | 115 | print('cached value (unfiltered): ', |
|
116 | 116 | vars(repo).get('testcachedunfifoobar', 'NOCACHE')) |
|
117 | 117 | print('cached value ("visible" view): ', |
|
118 | 118 | vars(visibleview).get('testcachedunfifoobar', 'NOCACHE')) |
|
119 | 119 | print('cached value ("immutable" view):', |
|
120 | 120 | vars(immutableview).get('testcachedunfifoobar', 'NOCACHE')) |
|
121 | 121 | |
|
122 | 122 | print('') |
|
123 | 123 | print('= first access on unfiltered, should do a call') |
|
124 | 124 | print('access (unfiltered):', repo.testcachedunfifoobar) |
|
125 | 125 | print('unficalllog:', unficalllog) |
|
126 | 126 | print('cached value (unfiltered): ', |
|
127 | 127 | vars(repo).get('testcachedunfifoobar', 'NOCACHE')) |
|
128 | 128 | |
|
129 | 129 | print('') |
|
130 | 130 | print('= second access on unfiltered, should not do call') |
|
131 | 131 | print('access (unfiltered):', repo.testcachedunfifoobar) |
|
132 | 132 | print('unficalllog:', unficalllog) |
|
133 | 133 | print('cached value (unfiltered): ', |
|
134 | 134 | vars(repo).get('testcachedunfifoobar', 'NOCACHE')) |
|
135 | 135 | |
|
136 | 136 | print('') |
|
137 | 137 | print('= access on view should use the unfiltered cache') |
|
138 | 138 | print('access (unfiltered): ', repo.testcachedunfifoobar) |
|
139 | 139 | print('access ("visible" view): ', visibleview.testcachedunfifoobar) |
|
140 | 140 | print('access ("immutable" view):', immutableview.testcachedunfifoobar) |
|
141 | 141 | print('unficalllog:', unficalllog) |
|
142 | 142 | print('cached value (unfiltered): ', |
|
143 | 143 | vars(repo).get('testcachedunfifoobar', 'NOCACHE')) |
|
144 | 144 | print('cached value ("visible" view): ', |
|
145 | 145 | vars(visibleview).get('testcachedunfifoobar', 'NOCACHE')) |
|
146 | 146 | print('cached value ("immutable" view):', |
|
147 | 147 | vars(immutableview).get('testcachedunfifoobar', 'NOCACHE')) |
|
148 | 148 | |
|
149 | 149 | print('') |
|
150 | 150 | print('= even if we clear the unfiltered cache') |
|
151 | 151 | del repo.__dict__['testcachedunfifoobar'] |
|
152 | 152 | print('cached value (unfiltered): ', |
|
153 | 153 | vars(repo).get('testcachedunfifoobar', 'NOCACHE')) |
|
154 | 154 | print('cached value ("visible" view): ', |
|
155 | 155 | vars(visibleview).get('testcachedunfifoobar', 'NOCACHE')) |
|
156 | 156 | print('cached value ("immutable" view):', |
|
157 | 157 | vars(immutableview).get('testcachedunfifoobar', 'NOCACHE')) |
|
158 | 158 | print('unficalllog:', unficalllog) |
|
159 | 159 | print('access ("visible" view): ', visibleview.testcachedunfifoobar) |
|
160 | 160 | print('unficalllog:', unficalllog) |
|
161 | 161 | print('cached value (unfiltered): ', |
|
162 | 162 | vars(repo).get('testcachedunfifoobar', 'NOCACHE')) |
|
163 | 163 | print('cached value ("visible" view): ', |
|
164 | 164 | vars(visibleview).get('testcachedunfifoobar', 'NOCACHE')) |
|
165 | 165 | print('cached value ("immutable" view):', |
|
166 | 166 | vars(immutableview).get('testcachedunfifoobar', 'NOCACHE')) |
|
167 | 167 | print('access ("immutable" view):', immutableview.testcachedunfifoobar) |
|
168 | 168 | print('unficalllog:', unficalllog) |
|
169 | 169 | print('cached value (unfiltered): ', |
|
170 | 170 | vars(repo).get('testcachedunfifoobar', 'NOCACHE')) |
|
171 | 171 | print('cached value ("visible" view): ', |
|
172 | 172 | vars(visibleview).get('testcachedunfifoobar', 'NOCACHE')) |
|
173 | 173 | print('cached value ("immutable" view):', |
|
174 | 174 | vars(immutableview).get('testcachedunfifoobar', 'NOCACHE')) |
|
175 | 175 | print('access (unfiltered): ', repo.testcachedunfifoobar) |
|
176 | 176 | print('unficalllog:', unficalllog) |
|
177 | 177 | print('cached value (unfiltered): ', |
|
178 | 178 | vars(repo).get('testcachedunfifoobar', 'NOCACHE')) |
|
179 | 179 | print('cached value ("visible" view): ', |
|
180 | 180 | vars(visibleview).get('testcachedunfifoobar', 'NOCACHE')) |
|
181 | 181 | print('cached value ("immutable" view):', |
|
182 | 182 | vars(immutableview).get('testcachedunfifoobar', 'NOCACHE')) |
@@ -1,88 +1,88 | |||
|
1 | 1 | from __future__ import absolute_import, print_function |
|
2 | 2 | import os |
|
3 | 3 | from mercurial import ( |
|
4 | 4 | hg, |
|
5 | 5 | merge, |
|
6 | 6 | ui as uimod, |
|
7 | 7 | ) |
|
8 | 8 | |
|
9 | u = uimod.ui() | |
|
9 | u = uimod.ui.load() | |
|
10 | 10 | |
|
11 | 11 | repo = hg.repository(u, 'test1', create=1) |
|
12 | 12 | os.chdir('test1') |
|
13 | 13 | |
|
14 | 14 | def commit(text, time): |
|
15 | 15 | repo.commit(text=text, date="%d 0" % time) |
|
16 | 16 | |
|
17 | 17 | def addcommit(name, time): |
|
18 | 18 | f = open(name, 'w') |
|
19 | 19 | f.write('%s\n' % name) |
|
20 | 20 | f.close() |
|
21 | 21 | repo[None].add([name]) |
|
22 | 22 | commit(name, time) |
|
23 | 23 | |
|
24 | 24 | def update(rev): |
|
25 | 25 | merge.update(repo, rev, False, True) |
|
26 | 26 | |
|
27 | 27 | def merge_(rev): |
|
28 | 28 | merge.update(repo, rev, True, False) |
|
29 | 29 | |
|
30 | 30 | if __name__ == '__main__': |
|
31 | 31 | addcommit("A", 0) |
|
32 | 32 | addcommit("B", 1) |
|
33 | 33 | |
|
34 | 34 | update(0) |
|
35 | 35 | addcommit("C", 2) |
|
36 | 36 | |
|
37 | 37 | merge_(1) |
|
38 | 38 | commit("D", 3) |
|
39 | 39 | |
|
40 | 40 | update(2) |
|
41 | 41 | addcommit("E", 4) |
|
42 | 42 | addcommit("F", 5) |
|
43 | 43 | |
|
44 | 44 | update(3) |
|
45 | 45 | addcommit("G", 6) |
|
46 | 46 | |
|
47 | 47 | merge_(5) |
|
48 | 48 | commit("H", 7) |
|
49 | 49 | |
|
50 | 50 | update(5) |
|
51 | 51 | addcommit("I", 8) |
|
52 | 52 | |
|
53 | 53 | # Ancestors |
|
54 | 54 | print('Ancestors of 5') |
|
55 | 55 | for r in repo.changelog.ancestors([5]): |
|
56 | 56 | print(r, end=' ') |
|
57 | 57 | |
|
58 | 58 | print('\nAncestors of 6 and 5') |
|
59 | 59 | for r in repo.changelog.ancestors([6, 5]): |
|
60 | 60 | print(r, end=' ') |
|
61 | 61 | |
|
62 | 62 | print('\nAncestors of 5 and 4') |
|
63 | 63 | for r in repo.changelog.ancestors([5, 4]): |
|
64 | 64 | print(r, end=' ') |
|
65 | 65 | |
|
66 | 66 | print('\nAncestors of 7, stop at 6') |
|
67 | 67 | for r in repo.changelog.ancestors([7], 6): |
|
68 | 68 | print(r, end=' ') |
|
69 | 69 | |
|
70 | 70 | print('\nAncestors of 7, including revs') |
|
71 | 71 | for r in repo.changelog.ancestors([7], inclusive=True): |
|
72 | 72 | print(r, end=' ') |
|
73 | 73 | |
|
74 | 74 | print('\nAncestors of 7, 5 and 3, including revs') |
|
75 | 75 | for r in repo.changelog.ancestors([7, 5, 3], inclusive=True): |
|
76 | 76 | print(r, end=' ') |
|
77 | 77 | |
|
78 | 78 | # Descendants |
|
79 | 79 | print('\n\nDescendants of 5') |
|
80 | 80 | for r in repo.changelog.descendants([5]): |
|
81 | 81 | print(r, end=' ') |
|
82 | 82 | |
|
83 | 83 | print('\nDescendants of 5 and 3') |
|
84 | 84 | for r in repo.changelog.descendants([5, 3]): |
|
85 | 85 | print(r, end=' ') |
|
86 | 86 | |
|
87 | 87 | print('\nDescendants of 5 and 4') |
|
88 | 88 | print(*repo.changelog.descendants([5, 4]), sep=' ') |
@@ -1,35 +1,35 | |||
|
1 | 1 | #!/usr/bin/env python |
|
2 | 2 | from __future__ import absolute_import, print_function |
|
3 | 3 | |
|
4 | 4 | from mercurial import ( |
|
5 | 5 | commands, |
|
6 | 6 | localrepo, |
|
7 | 7 | ui as uimod, |
|
8 | 8 | ) |
|
9 | 9 | |
|
10 | u = uimod.ui() | |
|
10 | u = uimod.ui.load() | |
|
11 | 11 | |
|
12 | 12 | print('% creating repo') |
|
13 | 13 | repo = localrepo.localrepository(u, '.', create=True) |
|
14 | 14 | |
|
15 | 15 | f = open('test.py', 'w') |
|
16 | 16 | try: |
|
17 | 17 | f.write('foo\n') |
|
18 | 18 | finally: |
|
19 | 19 | f.close |
|
20 | 20 | |
|
21 | 21 | print('% add and commit') |
|
22 | 22 | commands.add(u, repo, 'test.py') |
|
23 | 23 | commands.commit(u, repo, message='*') |
|
24 | 24 | commands.status(u, repo, clean=True) |
|
25 | 25 | |
|
26 | 26 | |
|
27 | 27 | print('% change') |
|
28 | 28 | f = open('test.py', 'w') |
|
29 | 29 | try: |
|
30 | 30 | f.write('bar\n') |
|
31 | 31 | finally: |
|
32 | 32 | f.close() |
|
33 | 33 | |
|
34 | 34 | # this would return clean instead of changed before the fix |
|
35 | 35 | commands.status(u, repo, clean=True, modified=True) |
@@ -1,57 +1,57 | |||
|
1 | 1 | from __future__ import absolute_import |
|
2 | 2 | |
|
3 | 3 | import os |
|
4 | 4 | import sys |
|
5 | 5 | import time |
|
6 | 6 | from mercurial import ( |
|
7 | 7 | commands, |
|
8 | 8 | hg, |
|
9 | 9 | ui as uimod, |
|
10 | 10 | util, |
|
11 | 11 | ) |
|
12 | 12 | |
|
13 | 13 | TESTDIR = os.environ["TESTDIR"] |
|
14 | 14 | BUNDLEPATH = os.path.join(TESTDIR, 'bundles', 'test-no-symlinks.hg') |
|
15 | 15 | |
|
16 | 16 | # only makes sense to test on os which supports symlinks |
|
17 | 17 | if not getattr(os, "symlink", False): |
|
18 | 18 | sys.exit(80) # SKIPPED_STATUS defined in run-tests.py |
|
19 | 19 | |
|
20 | u = uimod.ui() | |
|
20 | u = uimod.ui.load() | |
|
21 | 21 | # hide outer repo |
|
22 | 22 | hg.peer(u, {}, '.', create=True) |
|
23 | 23 | |
|
24 | 24 | # clone with symlink support |
|
25 | 25 | hg.clone(u, {}, BUNDLEPATH, 'test0') |
|
26 | 26 | |
|
27 | 27 | repo = hg.repository(u, 'test0') |
|
28 | 28 | |
|
29 | 29 | # wait a bit, or the status call wont update the dirstate |
|
30 | 30 | time.sleep(1) |
|
31 | 31 | commands.status(u, repo) |
|
32 | 32 | |
|
33 | 33 | # now disable symlink support -- this is what os.symlink would do on a |
|
34 | 34 | # non-symlink file system |
|
35 | 35 | def symlink_failure(src, dst): |
|
36 | 36 | raise OSError(1, "Operation not permitted") |
|
37 | 37 | os.symlink = symlink_failure |
|
38 | 38 | def islink_failure(path): |
|
39 | 39 | return False |
|
40 | 40 | os.path.islink = islink_failure |
|
41 | 41 | |
|
42 | 42 | # dereference links as if a Samba server has exported this to a |
|
43 | 43 | # Windows client |
|
44 | 44 | for f in 'test0/a.lnk', 'test0/d/b.lnk': |
|
45 | 45 | os.unlink(f) |
|
46 | 46 | fp = open(f, 'wb') |
|
47 | 47 | fp.write(util.readfile(f[:-4])) |
|
48 | 48 | fp.close() |
|
49 | 49 | |
|
50 | 50 | # reload repository |
|
51 | u = uimod.ui() | |
|
51 | u = uimod.ui.load() | |
|
52 | 52 | repo = hg.repository(u, 'test0') |
|
53 | 53 | commands.status(u, repo) |
|
54 | 54 | |
|
55 | 55 | # try cloning a repo which contains symlinks |
|
56 | u = uimod.ui() | |
|
56 | u = uimod.ui.load() | |
|
57 | 57 | hg.clone(u, {}, BUNDLEPATH, 'test1') |
@@ -1,203 +1,203 | |||
|
1 | 1 | # Since it's not easy to write a test that portably deals |
|
2 | 2 | # with files from different users/groups, we cheat a bit by |
|
3 | 3 | # monkey-patching some functions in the util module |
|
4 | 4 | |
|
5 | 5 | from __future__ import absolute_import, print_function |
|
6 | 6 | |
|
7 | 7 | import os |
|
8 | 8 | from mercurial import ( |
|
9 | 9 | error, |
|
10 | 10 | ui as uimod, |
|
11 | 11 | util, |
|
12 | 12 | ) |
|
13 | 13 | |
|
14 | 14 | hgrc = os.environ['HGRCPATH'] |
|
15 | 15 | f = open(hgrc) |
|
16 | 16 | basehgrc = f.read() |
|
17 | 17 | f.close() |
|
18 | 18 | |
|
19 | 19 | def testui(user='foo', group='bar', tusers=(), tgroups=(), |
|
20 | 20 | cuser='foo', cgroup='bar', debug=False, silent=False, |
|
21 | 21 | report=True): |
|
22 | 22 | # user, group => owners of the file |
|
23 | 23 | # tusers, tgroups => trusted users/groups |
|
24 | 24 | # cuser, cgroup => user/group of the current process |
|
25 | 25 | |
|
26 | 26 | # write a global hgrc with the list of trusted users/groups and |
|
27 | 27 | # some setting so that we can be sure it was read |
|
28 | 28 | f = open(hgrc, 'w') |
|
29 | 29 | f.write(basehgrc) |
|
30 | 30 | f.write('\n[paths]\n') |
|
31 | 31 | f.write('global = /some/path\n\n') |
|
32 | 32 | |
|
33 | 33 | if tusers or tgroups: |
|
34 | 34 | f.write('[trusted]\n') |
|
35 | 35 | if tusers: |
|
36 | 36 | f.write('users = %s\n' % ', '.join(tusers)) |
|
37 | 37 | if tgroups: |
|
38 | 38 | f.write('groups = %s\n' % ', '.join(tgroups)) |
|
39 | 39 | f.close() |
|
40 | 40 | |
|
41 | 41 | # override the functions that give names to uids and gids |
|
42 | 42 | def username(uid=None): |
|
43 | 43 | if uid is None: |
|
44 | 44 | return cuser |
|
45 | 45 | return user |
|
46 | 46 | util.username = username |
|
47 | 47 | |
|
48 | 48 | def groupname(gid=None): |
|
49 | 49 | if gid is None: |
|
50 | 50 | return 'bar' |
|
51 | 51 | return group |
|
52 | 52 | util.groupname = groupname |
|
53 | 53 | |
|
54 | 54 | def isowner(st): |
|
55 | 55 | return user == cuser |
|
56 | 56 | util.isowner = isowner |
|
57 | 57 | |
|
58 | 58 | # try to read everything |
|
59 | 59 | #print '# File belongs to user %s, group %s' % (user, group) |
|
60 | 60 | #print '# trusted users = %s; trusted groups = %s' % (tusers, tgroups) |
|
61 | 61 | kind = ('different', 'same') |
|
62 | 62 | who = ('', 'user', 'group', 'user and the group') |
|
63 | 63 | trusted = who[(user in tusers) + 2*(group in tgroups)] |
|
64 | 64 | if trusted: |
|
65 | 65 | trusted = ', but we trust the ' + trusted |
|
66 | 66 | print('# %s user, %s group%s' % (kind[user == cuser], kind[group == cgroup], |
|
67 | 67 | trusted)) |
|
68 | 68 | |
|
69 | u = uimod.ui() | |
|
69 | u = uimod.ui.load() | |
|
70 | 70 | u.setconfig('ui', 'debug', str(bool(debug))) |
|
71 | 71 | u.setconfig('ui', 'report_untrusted', str(bool(report))) |
|
72 | 72 | u.readconfig('.hg/hgrc') |
|
73 | 73 | if silent: |
|
74 | 74 | return u |
|
75 | 75 | print('trusted') |
|
76 | 76 | for name, path in u.configitems('paths'): |
|
77 | 77 | print(' ', name, '=', path) |
|
78 | 78 | print('untrusted') |
|
79 | 79 | for name, path in u.configitems('paths', untrusted=True): |
|
80 | 80 | print('.', end=' ') |
|
81 | 81 | u.config('paths', name) # warning with debug=True |
|
82 | 82 | print('.', end=' ') |
|
83 | 83 | u.config('paths', name, untrusted=True) # no warnings |
|
84 | 84 | print(name, '=', path) |
|
85 | 85 | print() |
|
86 | 86 | |
|
87 | 87 | return u |
|
88 | 88 | |
|
89 | 89 | os.mkdir('repo') |
|
90 | 90 | os.chdir('repo') |
|
91 | 91 | os.mkdir('.hg') |
|
92 | 92 | f = open('.hg/hgrc', 'w') |
|
93 | 93 | f.write('[paths]\n') |
|
94 | 94 | f.write('local = /another/path\n\n') |
|
95 | 95 | f.close() |
|
96 | 96 | |
|
97 | 97 | #print '# Everything is run by user foo, group bar\n' |
|
98 | 98 | |
|
99 | 99 | # same user, same group |
|
100 | 100 | testui() |
|
101 | 101 | # same user, different group |
|
102 | 102 | testui(group='def') |
|
103 | 103 | # different user, same group |
|
104 | 104 | testui(user='abc') |
|
105 | 105 | # ... but we trust the group |
|
106 | 106 | testui(user='abc', tgroups=['bar']) |
|
107 | 107 | # different user, different group |
|
108 | 108 | testui(user='abc', group='def') |
|
109 | 109 | # ... but we trust the user |
|
110 | 110 | testui(user='abc', group='def', tusers=['abc']) |
|
111 | 111 | # ... but we trust the group |
|
112 | 112 | testui(user='abc', group='def', tgroups=['def']) |
|
113 | 113 | # ... but we trust the user and the group |
|
114 | 114 | testui(user='abc', group='def', tusers=['abc'], tgroups=['def']) |
|
115 | 115 | # ... but we trust all users |
|
116 | 116 | print('# we trust all users') |
|
117 | 117 | testui(user='abc', group='def', tusers=['*']) |
|
118 | 118 | # ... but we trust all groups |
|
119 | 119 | print('# we trust all groups') |
|
120 | 120 | testui(user='abc', group='def', tgroups=['*']) |
|
121 | 121 | # ... but we trust the whole universe |
|
122 | 122 | print('# we trust all users and groups') |
|
123 | 123 | testui(user='abc', group='def', tusers=['*'], tgroups=['*']) |
|
124 | 124 | # ... check that users and groups are in different namespaces |
|
125 | 125 | print("# we don't get confused by users and groups with the same name") |
|
126 | 126 | testui(user='abc', group='def', tusers=['def'], tgroups=['abc']) |
|
127 | 127 | # ... lists of user names work |
|
128 | 128 | print("# list of user names") |
|
129 | 129 | testui(user='abc', group='def', tusers=['foo', 'xyz', 'abc', 'bleh'], |
|
130 | 130 | tgroups=['bar', 'baz', 'qux']) |
|
131 | 131 | # ... lists of group names work |
|
132 | 132 | print("# list of group names") |
|
133 | 133 | testui(user='abc', group='def', tusers=['foo', 'xyz', 'bleh'], |
|
134 | 134 | tgroups=['bar', 'def', 'baz', 'qux']) |
|
135 | 135 | |
|
136 | 136 | print("# Can't figure out the name of the user running this process") |
|
137 | 137 | testui(user='abc', group='def', cuser=None) |
|
138 | 138 | |
|
139 | 139 | print("# prints debug warnings") |
|
140 | 140 | u = testui(user='abc', group='def', cuser='foo', debug=True) |
|
141 | 141 | |
|
142 | 142 | print("# report_untrusted enabled without debug hides warnings") |
|
143 | 143 | u = testui(user='abc', group='def', cuser='foo', report=False) |
|
144 | 144 | |
|
145 | 145 | print("# report_untrusted enabled with debug shows warnings") |
|
146 | 146 | u = testui(user='abc', group='def', cuser='foo', debug=True, report=False) |
|
147 | 147 | |
|
148 | 148 | print("# ui.readconfig sections") |
|
149 | 149 | filename = 'foobar' |
|
150 | 150 | f = open(filename, 'w') |
|
151 | 151 | f.write('[foobar]\n') |
|
152 | 152 | f.write('baz = quux\n') |
|
153 | 153 | f.close() |
|
154 | 154 | u.readconfig(filename, sections=['foobar']) |
|
155 | 155 | print(u.config('foobar', 'baz')) |
|
156 | 156 | |
|
157 | 157 | print() |
|
158 | 158 | print("# read trusted, untrusted, new ui, trusted") |
|
159 | u = uimod.ui() | |
|
159 | u = uimod.ui.load() | |
|
160 | 160 | u.setconfig('ui', 'debug', 'on') |
|
161 | 161 | u.readconfig(filename) |
|
162 | 162 | u2 = u.copy() |
|
163 | 163 | def username(uid=None): |
|
164 | 164 | return 'foo' |
|
165 | 165 | util.username = username |
|
166 | 166 | u2.readconfig('.hg/hgrc') |
|
167 | 167 | print('trusted:') |
|
168 | 168 | print(u2.config('foobar', 'baz')) |
|
169 | 169 | print('untrusted:') |
|
170 | 170 | print(u2.config('foobar', 'baz', untrusted=True)) |
|
171 | 171 | |
|
172 | 172 | print() |
|
173 | 173 | print("# error handling") |
|
174 | 174 | |
|
175 | 175 | def assertraises(f, exc=error.Abort): |
|
176 | 176 | try: |
|
177 | 177 | f() |
|
178 | 178 | except exc as inst: |
|
179 | 179 | print('raised', inst.__class__.__name__) |
|
180 | 180 | else: |
|
181 | 181 | print('no exception?!') |
|
182 | 182 | |
|
183 | 183 | print("# file doesn't exist") |
|
184 | 184 | os.unlink('.hg/hgrc') |
|
185 | 185 | assert not os.path.exists('.hg/hgrc') |
|
186 | 186 | testui(debug=True, silent=True) |
|
187 | 187 | testui(user='abc', group='def', debug=True, silent=True) |
|
188 | 188 | |
|
189 | 189 | print() |
|
190 | 190 | print("# parse error") |
|
191 | 191 | f = open('.hg/hgrc', 'w') |
|
192 | 192 | f.write('foo') |
|
193 | 193 | f.close() |
|
194 | 194 | |
|
195 | 195 | try: |
|
196 | 196 | testui(user='abc', group='def', silent=True) |
|
197 | 197 | except error.ParseError as inst: |
|
198 | 198 | print(inst) |
|
199 | 199 | |
|
200 | 200 | try: |
|
201 | 201 | testui(debug=True, silent=True) |
|
202 | 202 | except error.ParseError as inst: |
|
203 | 203 | print(inst) |
@@ -1,40 +1,40 | |||
|
1 | 1 | from __future__ import absolute_import, print_function |
|
2 | 2 | |
|
3 | 3 | import os |
|
4 | 4 | from hgext import ( |
|
5 | 5 | color, |
|
6 | 6 | ) |
|
7 | 7 | from mercurial import ( |
|
8 | 8 | dispatch, |
|
9 | 9 | ui as uimod, |
|
10 | 10 | ) |
|
11 | 11 | |
|
12 | 12 | # ensure errors aren't buffered |
|
13 | 13 | testui = color.colorui() |
|
14 | 14 | testui.pushbuffer() |
|
15 | 15 | testui.write(('buffered\n')) |
|
16 | 16 | testui.warn(('warning\n')) |
|
17 | 17 | testui.write_err('error\n') |
|
18 | 18 | print(repr(testui.popbuffer())) |
|
19 | 19 | |
|
20 | 20 | # test dispatch.dispatch with the same ui object |
|
21 | 21 | hgrc = open(os.environ["HGRCPATH"], 'w') |
|
22 | 22 | hgrc.write('[extensions]\n') |
|
23 | 23 | hgrc.write('color=\n') |
|
24 | 24 | hgrc.close() |
|
25 | 25 | |
|
26 | ui_ = uimod.ui() | |
|
26 | ui_ = uimod.ui.load() | |
|
27 | 27 | ui_.setconfig('ui', 'formatted', 'True') |
|
28 | 28 | |
|
29 | 29 | # we're not interested in the output, so write that to devnull |
|
30 | 30 | ui_.fout = open(os.devnull, 'w') |
|
31 | 31 | |
|
32 | 32 | # call some arbitrary command just so we go through |
|
33 | 33 | # color's wrapped _runcommand twice. |
|
34 | 34 | def runcmd(): |
|
35 | 35 | dispatch.dispatch(dispatch.request(['version', '-q'], ui_)) |
|
36 | 36 | |
|
37 | 37 | runcmd() |
|
38 | 38 | print("colored? " + str(issubclass(ui_.__class__, color.colorui))) |
|
39 | 39 | runcmd() |
|
40 | 40 | print("colored? " + str(issubclass(ui_.__class__, color.colorui))) |
@@ -1,103 +1,103 | |||
|
1 | 1 | from __future__ import absolute_import, print_function |
|
2 | 2 | from mercurial import ( |
|
3 | 3 | dispatch, |
|
4 | 4 | error, |
|
5 | 5 | ui as uimod, |
|
6 | 6 | ) |
|
7 | 7 | |
|
8 | testui = uimod.ui() | |
|
8 | testui = uimod.ui.load() | |
|
9 | 9 | parsed = dispatch._parseconfig(testui, [ |
|
10 | 10 | 'values.string=string value', |
|
11 | 11 | 'values.bool1=true', |
|
12 | 12 | 'values.bool2=false', |
|
13 | 13 | 'values.boolinvalid=foo', |
|
14 | 14 | 'values.int1=42', |
|
15 | 15 | 'values.int2=-42', |
|
16 | 16 | 'values.intinvalid=foo', |
|
17 | 17 | 'lists.list1=foo', |
|
18 | 18 | 'lists.list2=foo bar baz', |
|
19 | 19 | 'lists.list3=alice, bob', |
|
20 | 20 | 'lists.list4=foo bar baz alice, bob', |
|
21 | 21 | 'lists.list5=abc d"ef"g "hij def"', |
|
22 | 22 | 'lists.list6="hello world", "how are you?"', |
|
23 | 23 | 'lists.list7=Do"Not"Separate', |
|
24 | 24 | 'lists.list8="Do"Separate', |
|
25 | 25 | 'lists.list9="Do\\"NotSeparate"', |
|
26 | 26 | 'lists.list10=string "with extraneous" quotation mark"', |
|
27 | 27 | 'lists.list11=x, y', |
|
28 | 28 | 'lists.list12="x", "y"', |
|
29 | 29 | 'lists.list13=""" key = "x", "y" """', |
|
30 | 30 | 'lists.list14=,,,, ', |
|
31 | 31 | 'lists.list15=" just with starting quotation', |
|
32 | 32 | 'lists.list16="longer quotation" with "no ending quotation', |
|
33 | 33 | 'lists.list17=this is \\" "not a quotation mark"', |
|
34 | 34 | 'lists.list18=\n \n\nding\ndong', |
|
35 | 35 | ]) |
|
36 | 36 | |
|
37 | 37 | print(repr(testui.configitems('values'))) |
|
38 | 38 | print(repr(testui.configitems('lists'))) |
|
39 | 39 | print("---") |
|
40 | 40 | print(repr(testui.config('values', 'string'))) |
|
41 | 41 | print(repr(testui.config('values', 'bool1'))) |
|
42 | 42 | print(repr(testui.config('values', 'bool2'))) |
|
43 | 43 | print(repr(testui.config('values', 'unknown'))) |
|
44 | 44 | print("---") |
|
45 | 45 | try: |
|
46 | 46 | print(repr(testui.configbool('values', 'string'))) |
|
47 | 47 | except error.ConfigError as inst: |
|
48 | 48 | print(inst) |
|
49 | 49 | print(repr(testui.configbool('values', 'bool1'))) |
|
50 | 50 | print(repr(testui.configbool('values', 'bool2'))) |
|
51 | 51 | print(repr(testui.configbool('values', 'bool2', True))) |
|
52 | 52 | print(repr(testui.configbool('values', 'unknown'))) |
|
53 | 53 | print(repr(testui.configbool('values', 'unknown', True))) |
|
54 | 54 | print("---") |
|
55 | 55 | print(repr(testui.configint('values', 'int1'))) |
|
56 | 56 | print(repr(testui.configint('values', 'int2'))) |
|
57 | 57 | print("---") |
|
58 | 58 | print(repr(testui.configlist('lists', 'list1'))) |
|
59 | 59 | print(repr(testui.configlist('lists', 'list2'))) |
|
60 | 60 | print(repr(testui.configlist('lists', 'list3'))) |
|
61 | 61 | print(repr(testui.configlist('lists', 'list4'))) |
|
62 | 62 | print(repr(testui.configlist('lists', 'list4', ['foo']))) |
|
63 | 63 | print(repr(testui.configlist('lists', 'list5'))) |
|
64 | 64 | print(repr(testui.configlist('lists', 'list6'))) |
|
65 | 65 | print(repr(testui.configlist('lists', 'list7'))) |
|
66 | 66 | print(repr(testui.configlist('lists', 'list8'))) |
|
67 | 67 | print(repr(testui.configlist('lists', 'list9'))) |
|
68 | 68 | print(repr(testui.configlist('lists', 'list10'))) |
|
69 | 69 | print(repr(testui.configlist('lists', 'list11'))) |
|
70 | 70 | print(repr(testui.configlist('lists', 'list12'))) |
|
71 | 71 | print(repr(testui.configlist('lists', 'list13'))) |
|
72 | 72 | print(repr(testui.configlist('lists', 'list14'))) |
|
73 | 73 | print(repr(testui.configlist('lists', 'list15'))) |
|
74 | 74 | print(repr(testui.configlist('lists', 'list16'))) |
|
75 | 75 | print(repr(testui.configlist('lists', 'list17'))) |
|
76 | 76 | print(repr(testui.configlist('lists', 'list18'))) |
|
77 | 77 | print(repr(testui.configlist('lists', 'unknown'))) |
|
78 | 78 | print(repr(testui.configlist('lists', 'unknown', ''))) |
|
79 | 79 | print(repr(testui.configlist('lists', 'unknown', 'foo'))) |
|
80 | 80 | print(repr(testui.configlist('lists', 'unknown', ['foo']))) |
|
81 | 81 | print(repr(testui.configlist('lists', 'unknown', 'foo bar'))) |
|
82 | 82 | print(repr(testui.configlist('lists', 'unknown', 'foo, bar'))) |
|
83 | 83 | print(repr(testui.configlist('lists', 'unknown', ['foo bar']))) |
|
84 | 84 | print(repr(testui.configlist('lists', 'unknown', ['foo', 'bar']))) |
|
85 | 85 | |
|
86 | 86 | print(repr(testui.config('values', 'String'))) |
|
87 | 87 | |
|
88 | 88 | def function(): |
|
89 | 89 | pass |
|
90 | 90 | |
|
91 | 91 | # values that aren't strings should work |
|
92 | 92 | testui.setconfig('hook', 'commit', function) |
|
93 | 93 | print(function == testui.config('hook', 'commit')) |
|
94 | 94 | |
|
95 | 95 | # invalid values |
|
96 | 96 | try: |
|
97 | 97 | testui.configbool('values', 'boolinvalid') |
|
98 | 98 | except error.ConfigError: |
|
99 | 99 | print('boolinvalid') |
|
100 | 100 | try: |
|
101 | 101 | testui.configint('values', 'intinvalid') |
|
102 | 102 | except error.ConfigError: |
|
103 | 103 | print('intinvalid') |
@@ -1,51 +1,51 | |||
|
1 | 1 | from __future__ import absolute_import, print_function |
|
2 | 2 | |
|
3 | 3 | import os |
|
4 | 4 | from mercurial import ( |
|
5 | 5 | ui as uimod, |
|
6 | 6 | ) |
|
7 | 7 | |
|
8 | 8 | hgrc = os.environ['HGRCPATH'] |
|
9 | 9 | f = open(hgrc) |
|
10 | 10 | basehgrc = f.read() |
|
11 | 11 | f.close() |
|
12 | 12 | |
|
13 | 13 | print(' hgrc settings command line options final result ') |
|
14 | 14 | print(' quiet verbo debug quiet verbo debug quiet verbo debug') |
|
15 | 15 | |
|
16 | 16 | for i in xrange(64): |
|
17 | 17 | hgrc_quiet = bool(i & 1<<0) |
|
18 | 18 | hgrc_verbose = bool(i & 1<<1) |
|
19 | 19 | hgrc_debug = bool(i & 1<<2) |
|
20 | 20 | cmd_quiet = bool(i & 1<<3) |
|
21 | 21 | cmd_verbose = bool(i & 1<<4) |
|
22 | 22 | cmd_debug = bool(i & 1<<5) |
|
23 | 23 | |
|
24 | 24 | f = open(hgrc, 'w') |
|
25 | 25 | f.write(basehgrc) |
|
26 | 26 | f.write('\n[ui]\n') |
|
27 | 27 | if hgrc_quiet: |
|
28 | 28 | f.write('quiet = True\n') |
|
29 | 29 | if hgrc_verbose: |
|
30 | 30 | f.write('verbose = True\n') |
|
31 | 31 | if hgrc_debug: |
|
32 | 32 | f.write('debug = True\n') |
|
33 | 33 | f.close() |
|
34 | 34 | |
|
35 | u = uimod.ui() | |
|
35 | u = uimod.ui.load() | |
|
36 | 36 | if cmd_quiet or cmd_debug or cmd_verbose: |
|
37 | 37 | u.setconfig('ui', 'quiet', str(bool(cmd_quiet))) |
|
38 | 38 | u.setconfig('ui', 'verbose', str(bool(cmd_verbose))) |
|
39 | 39 | u.setconfig('ui', 'debug', str(bool(cmd_debug))) |
|
40 | 40 | |
|
41 | 41 | check = '' |
|
42 | 42 | if u.debugflag: |
|
43 | 43 | if not u.verbose or u.quiet: |
|
44 | 44 | check = ' *' |
|
45 | 45 | elif u.verbose and u.quiet: |
|
46 | 46 | check = ' +' |
|
47 | 47 | |
|
48 | 48 | print(('%2d %5s %5s %5s %5s %5s %5s -> %5s %5s %5s%s' |
|
49 | 49 | % (i, hgrc_quiet, hgrc_verbose, hgrc_debug, |
|
50 | 50 | cmd_quiet, cmd_verbose, cmd_debug, |
|
51 | 51 | u.quiet, u.verbose, u.debugflag, check))) |
@@ -1,66 +1,66 | |||
|
1 | 1 | from __future__ import absolute_import, print_function |
|
2 | 2 | |
|
3 | 3 | import os |
|
4 | 4 | |
|
5 | 5 | from mercurial import ( |
|
6 | 6 | hg, |
|
7 | 7 | scmutil, |
|
8 | 8 | ui as uimod, |
|
9 | 9 | util, |
|
10 | 10 | ) |
|
11 | 11 | |
|
12 | 12 | chdir = os.chdir |
|
13 | 13 | mkdir = os.mkdir |
|
14 | 14 | pjoin = os.path.join |
|
15 | 15 | |
|
16 | 16 | walkrepos = scmutil.walkrepos |
|
17 | 17 | checklink = util.checklink |
|
18 | 18 | |
|
19 | u = uimod.ui() | |
|
19 | u = uimod.ui.load() | |
|
20 | 20 | sym = checklink('.') |
|
21 | 21 | |
|
22 | 22 | hg.repository(u, 'top1', create=1) |
|
23 | 23 | mkdir('subdir') |
|
24 | 24 | chdir('subdir') |
|
25 | 25 | hg.repository(u, 'sub1', create=1) |
|
26 | 26 | mkdir('subsubdir') |
|
27 | 27 | chdir('subsubdir') |
|
28 | 28 | hg.repository(u, 'subsub1', create=1) |
|
29 | 29 | chdir(os.path.pardir) |
|
30 | 30 | if sym: |
|
31 | 31 | os.symlink(os.path.pardir, 'circle') |
|
32 | 32 | os.symlink(pjoin('subsubdir', 'subsub1'), 'subsub1') |
|
33 | 33 | |
|
34 | 34 | def runtest(): |
|
35 | 35 | reposet = frozenset(walkrepos('.', followsym=True)) |
|
36 | 36 | if sym and (len(reposet) != 3): |
|
37 | 37 | print("reposet = %r" % (reposet,)) |
|
38 | 38 | print(("Found %d repositories when I should have found 3" |
|
39 | 39 | % (len(reposet),))) |
|
40 | 40 | if (not sym) and (len(reposet) != 2): |
|
41 | 41 | print("reposet = %r" % (reposet,)) |
|
42 | 42 | print(("Found %d repositories when I should have found 2" |
|
43 | 43 | % (len(reposet),))) |
|
44 | 44 | sub1set = frozenset((pjoin('.', 'sub1'), |
|
45 | 45 | pjoin('.', 'circle', 'subdir', 'sub1'))) |
|
46 | 46 | if len(sub1set & reposet) != 1: |
|
47 | 47 | print("sub1set = %r" % (sub1set,)) |
|
48 | 48 | print("reposet = %r" % (reposet,)) |
|
49 | 49 | print("sub1set and reposet should have exactly one path in common.") |
|
50 | 50 | sub2set = frozenset((pjoin('.', 'subsub1'), |
|
51 | 51 | pjoin('.', 'subsubdir', 'subsub1'))) |
|
52 | 52 | if len(sub2set & reposet) != 1: |
|
53 | 53 | print("sub2set = %r" % (sub2set,)) |
|
54 | 54 | print("reposet = %r" % (reposet,)) |
|
55 | 55 | print("sub2set and reposet should have exactly one path in common.") |
|
56 | 56 | sub3 = pjoin('.', 'circle', 'top1') |
|
57 | 57 | if sym and sub3 not in reposet: |
|
58 | 58 | print("reposet = %r" % (reposet,)) |
|
59 | 59 | print("Symbolic links are supported and %s is not in reposet" % (sub3,)) |
|
60 | 60 | |
|
61 | 61 | runtest() |
|
62 | 62 | if sym: |
|
63 | 63 | # Simulate not having symlinks. |
|
64 | 64 | del os.path.samestat |
|
65 | 65 | sym = False |
|
66 | 66 | runtest() |
General Comments 0
You need to be logged in to leave comments.
Login now