##// END OF EJS Templates
ui: factor out ui.load() to create a ui without loading configs (API)...
Yuya Nishihara -
r30559:d83ca854 default
parent child Browse files
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 # we always trust global config files
142 155 for f in scmutil.rcpath():
143 self.readconfig(f, trust=True)
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,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