##// END OF EJS Templates
help.merge-tools: do not double document merge tools...
Gregory Szorc -
r24099:be83fd9d default
parent child Browse files
Show More
@@ -1,473 +1,476 b''
1 # filemerge.py - file-level merge handling for Mercurial
1 # filemerge.py - file-level merge handling for Mercurial
2 #
2 #
3 # Copyright 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import short
8 from node import short
9 from i18n import _
9 from i18n import _
10 import util, simplemerge, match, error, templater, templatekw
10 import util, simplemerge, match, error, templater, templatekw
11 import os, tempfile, re, filecmp
11 import os, tempfile, re, filecmp
12 import tagmerge
12 import tagmerge
13
13
14 def _toolstr(ui, tool, part, default=""):
14 def _toolstr(ui, tool, part, default=""):
15 return ui.config("merge-tools", tool + "." + part, default)
15 return ui.config("merge-tools", tool + "." + part, default)
16
16
17 def _toolbool(ui, tool, part, default=False):
17 def _toolbool(ui, tool, part, default=False):
18 return ui.configbool("merge-tools", tool + "." + part, default)
18 return ui.configbool("merge-tools", tool + "." + part, default)
19
19
20 def _toollist(ui, tool, part, default=[]):
20 def _toollist(ui, tool, part, default=[]):
21 return ui.configlist("merge-tools", tool + "." + part, default)
21 return ui.configlist("merge-tools", tool + "." + part, default)
22
22
23 internals = {}
23 internals = {}
24 # Merge tools to document.
25 internalsdoc = {}
24
26
25 def internaltool(name, trymerge, onfailure=None):
27 def internaltool(name, trymerge, onfailure=None):
26 '''return a decorator for populating internal merge tool table'''
28 '''return a decorator for populating internal merge tool table'''
27 def decorator(func):
29 def decorator(func):
28 fullname = ':' + name
30 fullname = ':' + name
29 func.__doc__ = "``%s``\n" % fullname + func.__doc__.strip()
31 func.__doc__ = "``%s``\n" % fullname + func.__doc__.strip()
30 internals[fullname] = func
32 internals[fullname] = func
31 internals['internal:' + name] = func
33 internals['internal:' + name] = func
34 internalsdoc[fullname] = func
32 func.trymerge = trymerge
35 func.trymerge = trymerge
33 func.onfailure = onfailure
36 func.onfailure = onfailure
34 return func
37 return func
35 return decorator
38 return decorator
36
39
37 def _findtool(ui, tool):
40 def _findtool(ui, tool):
38 if tool in internals:
41 if tool in internals:
39 return tool
42 return tool
40 return findexternaltool(ui, tool)
43 return findexternaltool(ui, tool)
41
44
42 def findexternaltool(ui, tool):
45 def findexternaltool(ui, tool):
43 for kn in ("regkey", "regkeyalt"):
46 for kn in ("regkey", "regkeyalt"):
44 k = _toolstr(ui, tool, kn)
47 k = _toolstr(ui, tool, kn)
45 if not k:
48 if not k:
46 continue
49 continue
47 p = util.lookupreg(k, _toolstr(ui, tool, "regname"))
50 p = util.lookupreg(k, _toolstr(ui, tool, "regname"))
48 if p:
51 if p:
49 p = util.findexe(p + _toolstr(ui, tool, "regappend"))
52 p = util.findexe(p + _toolstr(ui, tool, "regappend"))
50 if p:
53 if p:
51 return p
54 return p
52 exe = _toolstr(ui, tool, "executable", tool)
55 exe = _toolstr(ui, tool, "executable", tool)
53 return util.findexe(util.expandpath(exe))
56 return util.findexe(util.expandpath(exe))
54
57
55 def _picktool(repo, ui, path, binary, symlink):
58 def _picktool(repo, ui, path, binary, symlink):
56 def check(tool, pat, symlink, binary):
59 def check(tool, pat, symlink, binary):
57 tmsg = tool
60 tmsg = tool
58 if pat:
61 if pat:
59 tmsg += " specified for " + pat
62 tmsg += " specified for " + pat
60 if not _findtool(ui, tool):
63 if not _findtool(ui, tool):
61 if pat: # explicitly requested tool deserves a warning
64 if pat: # explicitly requested tool deserves a warning
62 ui.warn(_("couldn't find merge tool %s\n") % tmsg)
65 ui.warn(_("couldn't find merge tool %s\n") % tmsg)
63 else: # configured but non-existing tools are more silent
66 else: # configured but non-existing tools are more silent
64 ui.note(_("couldn't find merge tool %s\n") % tmsg)
67 ui.note(_("couldn't find merge tool %s\n") % tmsg)
65 elif symlink and not _toolbool(ui, tool, "symlink"):
68 elif symlink and not _toolbool(ui, tool, "symlink"):
66 ui.warn(_("tool %s can't handle symlinks\n") % tmsg)
69 ui.warn(_("tool %s can't handle symlinks\n") % tmsg)
67 elif binary and not _toolbool(ui, tool, "binary"):
70 elif binary and not _toolbool(ui, tool, "binary"):
68 ui.warn(_("tool %s can't handle binary\n") % tmsg)
71 ui.warn(_("tool %s can't handle binary\n") % tmsg)
69 elif not util.gui() and _toolbool(ui, tool, "gui"):
72 elif not util.gui() and _toolbool(ui, tool, "gui"):
70 ui.warn(_("tool %s requires a GUI\n") % tmsg)
73 ui.warn(_("tool %s requires a GUI\n") % tmsg)
71 else:
74 else:
72 return True
75 return True
73 return False
76 return False
74
77
75 # forcemerge comes from command line arguments, highest priority
78 # forcemerge comes from command line arguments, highest priority
76 force = ui.config('ui', 'forcemerge')
79 force = ui.config('ui', 'forcemerge')
77 if force:
80 if force:
78 toolpath = _findtool(ui, force)
81 toolpath = _findtool(ui, force)
79 if toolpath:
82 if toolpath:
80 return (force, util.shellquote(toolpath))
83 return (force, util.shellquote(toolpath))
81 else:
84 else:
82 # mimic HGMERGE if given tool not found
85 # mimic HGMERGE if given tool not found
83 return (force, force)
86 return (force, force)
84
87
85 # HGMERGE takes next precedence
88 # HGMERGE takes next precedence
86 hgmerge = os.environ.get("HGMERGE")
89 hgmerge = os.environ.get("HGMERGE")
87 if hgmerge:
90 if hgmerge:
88 return (hgmerge, hgmerge)
91 return (hgmerge, hgmerge)
89
92
90 # then patterns
93 # then patterns
91 for pat, tool in ui.configitems("merge-patterns"):
94 for pat, tool in ui.configitems("merge-patterns"):
92 mf = match.match(repo.root, '', [pat])
95 mf = match.match(repo.root, '', [pat])
93 if mf(path) and check(tool, pat, symlink, False):
96 if mf(path) and check(tool, pat, symlink, False):
94 toolpath = _findtool(ui, tool)
97 toolpath = _findtool(ui, tool)
95 return (tool, util.shellquote(toolpath))
98 return (tool, util.shellquote(toolpath))
96
99
97 # then merge tools
100 # then merge tools
98 tools = {}
101 tools = {}
99 for k, v in ui.configitems("merge-tools"):
102 for k, v in ui.configitems("merge-tools"):
100 t = k.split('.')[0]
103 t = k.split('.')[0]
101 if t not in tools:
104 if t not in tools:
102 tools[t] = int(_toolstr(ui, t, "priority", "0"))
105 tools[t] = int(_toolstr(ui, t, "priority", "0"))
103 names = tools.keys()
106 names = tools.keys()
104 tools = sorted([(-p, t) for t, p in tools.items()])
107 tools = sorted([(-p, t) for t, p in tools.items()])
105 uimerge = ui.config("ui", "merge")
108 uimerge = ui.config("ui", "merge")
106 if uimerge:
109 if uimerge:
107 if uimerge not in names:
110 if uimerge not in names:
108 return (uimerge, uimerge)
111 return (uimerge, uimerge)
109 tools.insert(0, (None, uimerge)) # highest priority
112 tools.insert(0, (None, uimerge)) # highest priority
110 tools.append((None, "hgmerge")) # the old default, if found
113 tools.append((None, "hgmerge")) # the old default, if found
111 for p, t in tools:
114 for p, t in tools:
112 if check(t, None, symlink, binary):
115 if check(t, None, symlink, binary):
113 toolpath = _findtool(ui, t)
116 toolpath = _findtool(ui, t)
114 return (t, util.shellquote(toolpath))
117 return (t, util.shellquote(toolpath))
115
118
116 # internal merge or prompt as last resort
119 # internal merge or prompt as last resort
117 if symlink or binary:
120 if symlink or binary:
118 return ":prompt", None
121 return ":prompt", None
119 return ":merge", None
122 return ":merge", None
120
123
121 def _eoltype(data):
124 def _eoltype(data):
122 "Guess the EOL type of a file"
125 "Guess the EOL type of a file"
123 if '\0' in data: # binary
126 if '\0' in data: # binary
124 return None
127 return None
125 if '\r\n' in data: # Windows
128 if '\r\n' in data: # Windows
126 return '\r\n'
129 return '\r\n'
127 if '\r' in data: # Old Mac
130 if '\r' in data: # Old Mac
128 return '\r'
131 return '\r'
129 if '\n' in data: # UNIX
132 if '\n' in data: # UNIX
130 return '\n'
133 return '\n'
131 return None # unknown
134 return None # unknown
132
135
133 def _matcheol(file, origfile):
136 def _matcheol(file, origfile):
134 "Convert EOL markers in a file to match origfile"
137 "Convert EOL markers in a file to match origfile"
135 tostyle = _eoltype(util.readfile(origfile))
138 tostyle = _eoltype(util.readfile(origfile))
136 if tostyle:
139 if tostyle:
137 data = util.readfile(file)
140 data = util.readfile(file)
138 style = _eoltype(data)
141 style = _eoltype(data)
139 if style:
142 if style:
140 newdata = data.replace(style, tostyle)
143 newdata = data.replace(style, tostyle)
141 if newdata != data:
144 if newdata != data:
142 util.writefile(file, newdata)
145 util.writefile(file, newdata)
143
146
144 @internaltool('prompt', False)
147 @internaltool('prompt', False)
145 def _iprompt(repo, mynode, orig, fcd, fco, fca, toolconf):
148 def _iprompt(repo, mynode, orig, fcd, fco, fca, toolconf):
146 """Asks the user which of the local or the other version to keep as
149 """Asks the user which of the local or the other version to keep as
147 the merged version."""
150 the merged version."""
148 ui = repo.ui
151 ui = repo.ui
149 fd = fcd.path()
152 fd = fcd.path()
150
153
151 if ui.promptchoice(_(" no tool found to merge %s\n"
154 if ui.promptchoice(_(" no tool found to merge %s\n"
152 "keep (l)ocal or take (o)ther?"
155 "keep (l)ocal or take (o)ther?"
153 "$$ &Local $$ &Other") % fd, 0):
156 "$$ &Local $$ &Other") % fd, 0):
154 return _iother(repo, mynode, orig, fcd, fco, fca, toolconf)
157 return _iother(repo, mynode, orig, fcd, fco, fca, toolconf)
155 else:
158 else:
156 return _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf)
159 return _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf)
157
160
158 @internaltool('local', False)
161 @internaltool('local', False)
159 def _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf):
162 def _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf):
160 """Uses the local version of files as the merged version."""
163 """Uses the local version of files as the merged version."""
161 return 0
164 return 0
162
165
163 @internaltool('other', False)
166 @internaltool('other', False)
164 def _iother(repo, mynode, orig, fcd, fco, fca, toolconf):
167 def _iother(repo, mynode, orig, fcd, fco, fca, toolconf):
165 """Uses the other version of files as the merged version."""
168 """Uses the other version of files as the merged version."""
166 repo.wwrite(fcd.path(), fco.data(), fco.flags())
169 repo.wwrite(fcd.path(), fco.data(), fco.flags())
167 return 0
170 return 0
168
171
169 @internaltool('fail', False)
172 @internaltool('fail', False)
170 def _ifail(repo, mynode, orig, fcd, fco, fca, toolconf):
173 def _ifail(repo, mynode, orig, fcd, fco, fca, toolconf):
171 """
174 """
172 Rather than attempting to merge files that were modified on both
175 Rather than attempting to merge files that were modified on both
173 branches, it marks them as unresolved. The resolve command must be
176 branches, it marks them as unresolved. The resolve command must be
174 used to resolve these conflicts."""
177 used to resolve these conflicts."""
175 return 1
178 return 1
176
179
177 def _premerge(repo, toolconf, files, labels=None):
180 def _premerge(repo, toolconf, files, labels=None):
178 tool, toolpath, binary, symlink = toolconf
181 tool, toolpath, binary, symlink = toolconf
179 if symlink:
182 if symlink:
180 return 1
183 return 1
181 a, b, c, back = files
184 a, b, c, back = files
182
185
183 ui = repo.ui
186 ui = repo.ui
184
187
185 validkeep = ['keep', 'keep-merge3']
188 validkeep = ['keep', 'keep-merge3']
186
189
187 # do we attempt to simplemerge first?
190 # do we attempt to simplemerge first?
188 try:
191 try:
189 premerge = _toolbool(ui, tool, "premerge", not binary)
192 premerge = _toolbool(ui, tool, "premerge", not binary)
190 except error.ConfigError:
193 except error.ConfigError:
191 premerge = _toolstr(ui, tool, "premerge").lower()
194 premerge = _toolstr(ui, tool, "premerge").lower()
192 if premerge not in validkeep:
195 if premerge not in validkeep:
193 _valid = ', '.join(["'" + v + "'" for v in validkeep])
196 _valid = ', '.join(["'" + v + "'" for v in validkeep])
194 raise error.ConfigError(_("%s.premerge not valid "
197 raise error.ConfigError(_("%s.premerge not valid "
195 "('%s' is neither boolean nor %s)") %
198 "('%s' is neither boolean nor %s)") %
196 (tool, premerge, _valid))
199 (tool, premerge, _valid))
197
200
198 if premerge:
201 if premerge:
199 if premerge == 'keep-merge3':
202 if premerge == 'keep-merge3':
200 if not labels:
203 if not labels:
201 labels = _defaultconflictlabels
204 labels = _defaultconflictlabels
202 if len(labels) < 3:
205 if len(labels) < 3:
203 labels.append('base')
206 labels.append('base')
204 r = simplemerge.simplemerge(ui, a, b, c, quiet=True, label=labels)
207 r = simplemerge.simplemerge(ui, a, b, c, quiet=True, label=labels)
205 if not r:
208 if not r:
206 ui.debug(" premerge successful\n")
209 ui.debug(" premerge successful\n")
207 return 0
210 return 0
208 if premerge not in validkeep:
211 if premerge not in validkeep:
209 util.copyfile(back, a) # restore from backup and try again
212 util.copyfile(back, a) # restore from backup and try again
210 return 1 # continue merging
213 return 1 # continue merging
211
214
212 @internaltool('merge', True,
215 @internaltool('merge', True,
213 _("merging %s incomplete! "
216 _("merging %s incomplete! "
214 "(edit conflicts, then use 'hg resolve --mark')\n"))
217 "(edit conflicts, then use 'hg resolve --mark')\n"))
215 def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
218 def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
216 """
219 """
217 Uses the internal non-interactive simple merge algorithm for merging
220 Uses the internal non-interactive simple merge algorithm for merging
218 files. It will fail if there are any conflicts and leave markers in
221 files. It will fail if there are any conflicts and leave markers in
219 the partially merged file. Markers will have two sections, one for each side
222 the partially merged file. Markers will have two sections, one for each side
220 of merge."""
223 of merge."""
221 tool, toolpath, binary, symlink = toolconf
224 tool, toolpath, binary, symlink = toolconf
222 if symlink:
225 if symlink:
223 repo.ui.warn(_('warning: internal :merge cannot merge symlinks '
226 repo.ui.warn(_('warning: internal :merge cannot merge symlinks '
224 'for %s\n') % fcd.path())
227 'for %s\n') % fcd.path())
225 return False, 1
228 return False, 1
226 r = _premerge(repo, toolconf, files, labels=labels)
229 r = _premerge(repo, toolconf, files, labels=labels)
227 if r:
230 if r:
228 a, b, c, back = files
231 a, b, c, back = files
229
232
230 ui = repo.ui
233 ui = repo.ui
231
234
232 r = simplemerge.simplemerge(ui, a, b, c, label=labels)
235 r = simplemerge.simplemerge(ui, a, b, c, label=labels)
233 return True, r
236 return True, r
234 return False, 0
237 return False, 0
235
238
236 @internaltool('merge3', True,
239 @internaltool('merge3', True,
237 _("merging %s incomplete! "
240 _("merging %s incomplete! "
238 "(edit conflicts, then use 'hg resolve --mark')\n"))
241 "(edit conflicts, then use 'hg resolve --mark')\n"))
239 def _imerge3(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
242 def _imerge3(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
240 """
243 """
241 Uses the internal non-interactive simple merge algorithm for merging
244 Uses the internal non-interactive simple merge algorithm for merging
242 files. It will fail if there are any conflicts and leave markers in
245 files. It will fail if there are any conflicts and leave markers in
243 the partially merged file. Marker will have three sections, one from each
246 the partially merged file. Marker will have three sections, one from each
244 side of the merge and one for the base content."""
247 side of the merge and one for the base content."""
245 if not labels:
248 if not labels:
246 labels = _defaultconflictlabels
249 labels = _defaultconflictlabels
247 if len(labels) < 3:
250 if len(labels) < 3:
248 labels.append('base')
251 labels.append('base')
249 return _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels)
252 return _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels)
250
253
251 @internaltool('tagmerge', True,
254 @internaltool('tagmerge', True,
252 _("automatic tag merging of %s failed! "
255 _("automatic tag merging of %s failed! "
253 "(use 'hg resolve --tool :merge' or another merge "
256 "(use 'hg resolve --tool :merge' or another merge "
254 "tool of your choice)\n"))
257 "tool of your choice)\n"))
255 def _itagmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
258 def _itagmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
256 """
259 """
257 Uses the internal tag merge algorithm (experimental).
260 Uses the internal tag merge algorithm (experimental).
258 """
261 """
259 return tagmerge.merge(repo, fcd, fco, fca)
262 return tagmerge.merge(repo, fcd, fco, fca)
260
263
261 @internaltool('dump', True)
264 @internaltool('dump', True)
262 def _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
265 def _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
263 """
266 """
264 Creates three versions of the files to merge, containing the
267 Creates three versions of the files to merge, containing the
265 contents of local, other and base. These files can then be used to
268 contents of local, other and base. These files can then be used to
266 perform a merge manually. If the file to be merged is named
269 perform a merge manually. If the file to be merged is named
267 ``a.txt``, these files will accordingly be named ``a.txt.local``,
270 ``a.txt``, these files will accordingly be named ``a.txt.local``,
268 ``a.txt.other`` and ``a.txt.base`` and they will be placed in the
271 ``a.txt.other`` and ``a.txt.base`` and they will be placed in the
269 same directory as ``a.txt``."""
272 same directory as ``a.txt``."""
270 r = _premerge(repo, toolconf, files, labels=labels)
273 r = _premerge(repo, toolconf, files, labels=labels)
271 if r:
274 if r:
272 a, b, c, back = files
275 a, b, c, back = files
273
276
274 fd = fcd.path()
277 fd = fcd.path()
275
278
276 util.copyfile(a, a + ".local")
279 util.copyfile(a, a + ".local")
277 repo.wwrite(fd + ".other", fco.data(), fco.flags())
280 repo.wwrite(fd + ".other", fco.data(), fco.flags())
278 repo.wwrite(fd + ".base", fca.data(), fca.flags())
281 repo.wwrite(fd + ".base", fca.data(), fca.flags())
279 return False, r
282 return False, r
280
283
281 def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
284 def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
282 r = _premerge(repo, toolconf, files, labels=labels)
285 r = _premerge(repo, toolconf, files, labels=labels)
283 if r:
286 if r:
284 tool, toolpath, binary, symlink = toolconf
287 tool, toolpath, binary, symlink = toolconf
285 a, b, c, back = files
288 a, b, c, back = files
286 out = ""
289 out = ""
287 env = {'HG_FILE': fcd.path(),
290 env = {'HG_FILE': fcd.path(),
288 'HG_MY_NODE': short(mynode),
291 'HG_MY_NODE': short(mynode),
289 'HG_OTHER_NODE': str(fco.changectx()),
292 'HG_OTHER_NODE': str(fco.changectx()),
290 'HG_BASE_NODE': str(fca.changectx()),
293 'HG_BASE_NODE': str(fca.changectx()),
291 'HG_MY_ISLINK': 'l' in fcd.flags(),
294 'HG_MY_ISLINK': 'l' in fcd.flags(),
292 'HG_OTHER_ISLINK': 'l' in fco.flags(),
295 'HG_OTHER_ISLINK': 'l' in fco.flags(),
293 'HG_BASE_ISLINK': 'l' in fca.flags(),
296 'HG_BASE_ISLINK': 'l' in fca.flags(),
294 }
297 }
295
298
296 ui = repo.ui
299 ui = repo.ui
297
300
298 args = _toolstr(ui, tool, "args", '$local $base $other')
301 args = _toolstr(ui, tool, "args", '$local $base $other')
299 if "$output" in args:
302 if "$output" in args:
300 out, a = a, back # read input from backup, write to original
303 out, a = a, back # read input from backup, write to original
301 replace = {'local': a, 'base': b, 'other': c, 'output': out}
304 replace = {'local': a, 'base': b, 'other': c, 'output': out}
302 args = util.interpolate(r'\$', replace, args,
305 args = util.interpolate(r'\$', replace, args,
303 lambda s: util.shellquote(util.localpath(s)))
306 lambda s: util.shellquote(util.localpath(s)))
304 r = ui.system(toolpath + ' ' + args, cwd=repo.root, environ=env)
307 r = ui.system(toolpath + ' ' + args, cwd=repo.root, environ=env)
305 return True, r
308 return True, r
306 return False, 0
309 return False, 0
307
310
308 def _formatconflictmarker(repo, ctx, template, label, pad):
311 def _formatconflictmarker(repo, ctx, template, label, pad):
309 """Applies the given template to the ctx, prefixed by the label.
312 """Applies the given template to the ctx, prefixed by the label.
310
313
311 Pad is the minimum width of the label prefix, so that multiple markers
314 Pad is the minimum width of the label prefix, so that multiple markers
312 can have aligned templated parts.
315 can have aligned templated parts.
313 """
316 """
314 if ctx.node() is None:
317 if ctx.node() is None:
315 ctx = ctx.p1()
318 ctx = ctx.p1()
316
319
317 props = templatekw.keywords.copy()
320 props = templatekw.keywords.copy()
318 props['templ'] = template
321 props['templ'] = template
319 props['ctx'] = ctx
322 props['ctx'] = ctx
320 props['repo'] = repo
323 props['repo'] = repo
321 templateresult = template('conflictmarker', **props)
324 templateresult = template('conflictmarker', **props)
322
325
323 label = ('%s:' % label).ljust(pad + 1)
326 label = ('%s:' % label).ljust(pad + 1)
324 mark = '%s %s' % (label, templater.stringify(templateresult))
327 mark = '%s %s' % (label, templater.stringify(templateresult))
325
328
326 if mark:
329 if mark:
327 mark = mark.splitlines()[0] # split for safety
330 mark = mark.splitlines()[0] # split for safety
328
331
329 # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ')
332 # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ')
330 return util.ellipsis(mark, 80 - 8)
333 return util.ellipsis(mark, 80 - 8)
331
334
332 _defaultconflictmarker = ('{node|short} ' +
335 _defaultconflictmarker = ('{node|short} ' +
333 '{ifeq(tags, "tip", "", "{tags} ")}' +
336 '{ifeq(tags, "tip", "", "{tags} ")}' +
334 '{if(bookmarks, "{bookmarks} ")}' +
337 '{if(bookmarks, "{bookmarks} ")}' +
335 '{ifeq(branch, "default", "", "{branch} ")}' +
338 '{ifeq(branch, "default", "", "{branch} ")}' +
336 '- {author|user}: {desc|firstline}')
339 '- {author|user}: {desc|firstline}')
337
340
338 _defaultconflictlabels = ['local', 'other']
341 _defaultconflictlabels = ['local', 'other']
339
342
340 def _formatlabels(repo, fcd, fco, fca, labels):
343 def _formatlabels(repo, fcd, fco, fca, labels):
341 """Formats the given labels using the conflict marker template.
344 """Formats the given labels using the conflict marker template.
342
345
343 Returns a list of formatted labels.
346 Returns a list of formatted labels.
344 """
347 """
345 cd = fcd.changectx()
348 cd = fcd.changectx()
346 co = fco.changectx()
349 co = fco.changectx()
347 ca = fca.changectx()
350 ca = fca.changectx()
348
351
349 ui = repo.ui
352 ui = repo.ui
350 template = ui.config('ui', 'mergemarkertemplate', _defaultconflictmarker)
353 template = ui.config('ui', 'mergemarkertemplate', _defaultconflictmarker)
351 template = templater.parsestring(template, quoted=False)
354 template = templater.parsestring(template, quoted=False)
352 tmpl = templater.templater(None, cache={'conflictmarker': template})
355 tmpl = templater.templater(None, cache={'conflictmarker': template})
353
356
354 pad = max(len(l) for l in labels)
357 pad = max(len(l) for l in labels)
355
358
356 newlabels = [_formatconflictmarker(repo, cd, tmpl, labels[0], pad),
359 newlabels = [_formatconflictmarker(repo, cd, tmpl, labels[0], pad),
357 _formatconflictmarker(repo, co, tmpl, labels[1], pad)]
360 _formatconflictmarker(repo, co, tmpl, labels[1], pad)]
358 if len(labels) > 2:
361 if len(labels) > 2:
359 newlabels.append(_formatconflictmarker(repo, ca, tmpl, labels[2], pad))
362 newlabels.append(_formatconflictmarker(repo, ca, tmpl, labels[2], pad))
360 return newlabels
363 return newlabels
361
364
362 def filemerge(repo, mynode, orig, fcd, fco, fca, labels=None):
365 def filemerge(repo, mynode, orig, fcd, fco, fca, labels=None):
363 """perform a 3-way merge in the working directory
366 """perform a 3-way merge in the working directory
364
367
365 mynode = parent node before merge
368 mynode = parent node before merge
366 orig = original local filename before merge
369 orig = original local filename before merge
367 fco = other file context
370 fco = other file context
368 fca = ancestor file context
371 fca = ancestor file context
369 fcd = local file context for current/destination file
372 fcd = local file context for current/destination file
370 """
373 """
371
374
372 def temp(prefix, ctx):
375 def temp(prefix, ctx):
373 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
376 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
374 (fd, name) = tempfile.mkstemp(prefix=pre)
377 (fd, name) = tempfile.mkstemp(prefix=pre)
375 data = repo.wwritedata(ctx.path(), ctx.data())
378 data = repo.wwritedata(ctx.path(), ctx.data())
376 f = os.fdopen(fd, "wb")
379 f = os.fdopen(fd, "wb")
377 f.write(data)
380 f.write(data)
378 f.close()
381 f.close()
379 return name
382 return name
380
383
381 if not fco.cmp(fcd): # files identical?
384 if not fco.cmp(fcd): # files identical?
382 return None
385 return None
383
386
384 ui = repo.ui
387 ui = repo.ui
385 fd = fcd.path()
388 fd = fcd.path()
386 binary = fcd.isbinary() or fco.isbinary() or fca.isbinary()
389 binary = fcd.isbinary() or fco.isbinary() or fca.isbinary()
387 symlink = 'l' in fcd.flags() + fco.flags()
390 symlink = 'l' in fcd.flags() + fco.flags()
388 tool, toolpath = _picktool(repo, ui, fd, binary, symlink)
391 tool, toolpath = _picktool(repo, ui, fd, binary, symlink)
389 ui.debug("picked tool '%s' for %s (binary %s symlink %s)\n" %
392 ui.debug("picked tool '%s' for %s (binary %s symlink %s)\n" %
390 (tool, fd, binary, symlink))
393 (tool, fd, binary, symlink))
391
394
392 if tool in internals:
395 if tool in internals:
393 func = internals[tool]
396 func = internals[tool]
394 trymerge = func.trymerge
397 trymerge = func.trymerge
395 onfailure = func.onfailure
398 onfailure = func.onfailure
396 else:
399 else:
397 func = _xmerge
400 func = _xmerge
398 trymerge = True
401 trymerge = True
399 onfailure = _("merging %s failed!\n")
402 onfailure = _("merging %s failed!\n")
400
403
401 toolconf = tool, toolpath, binary, symlink
404 toolconf = tool, toolpath, binary, symlink
402
405
403 if not trymerge:
406 if not trymerge:
404 return func(repo, mynode, orig, fcd, fco, fca, toolconf)
407 return func(repo, mynode, orig, fcd, fco, fca, toolconf)
405
408
406 a = repo.wjoin(fd)
409 a = repo.wjoin(fd)
407 b = temp("base", fca)
410 b = temp("base", fca)
408 c = temp("other", fco)
411 c = temp("other", fco)
409 back = a + ".orig"
412 back = a + ".orig"
410 util.copyfile(a, back)
413 util.copyfile(a, back)
411
414
412 if orig != fco.path():
415 if orig != fco.path():
413 ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
416 ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
414 else:
417 else:
415 ui.status(_("merging %s\n") % fd)
418 ui.status(_("merging %s\n") % fd)
416
419
417 ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca))
420 ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca))
418
421
419 markerstyle = ui.config('ui', 'mergemarkers', 'basic')
422 markerstyle = ui.config('ui', 'mergemarkers', 'basic')
420 if not labels:
423 if not labels:
421 labels = _defaultconflictlabels
424 labels = _defaultconflictlabels
422 if markerstyle != 'basic':
425 if markerstyle != 'basic':
423 labels = _formatlabels(repo, fcd, fco, fca, labels)
426 labels = _formatlabels(repo, fcd, fco, fca, labels)
424
427
425 needcheck, r = func(repo, mynode, orig, fcd, fco, fca, toolconf,
428 needcheck, r = func(repo, mynode, orig, fcd, fco, fca, toolconf,
426 (a, b, c, back), labels=labels)
429 (a, b, c, back), labels=labels)
427 if not needcheck:
430 if not needcheck:
428 if r:
431 if r:
429 if onfailure:
432 if onfailure:
430 ui.warn(onfailure % fd)
433 ui.warn(onfailure % fd)
431 else:
434 else:
432 util.unlink(back)
435 util.unlink(back)
433
436
434 util.unlink(b)
437 util.unlink(b)
435 util.unlink(c)
438 util.unlink(c)
436 return r
439 return r
437
440
438 if not r and (_toolbool(ui, tool, "checkconflicts") or
441 if not r and (_toolbool(ui, tool, "checkconflicts") or
439 'conflicts' in _toollist(ui, tool, "check")):
442 'conflicts' in _toollist(ui, tool, "check")):
440 if re.search("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data(),
443 if re.search("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data(),
441 re.MULTILINE):
444 re.MULTILINE):
442 r = 1
445 r = 1
443
446
444 checked = False
447 checked = False
445 if 'prompt' in _toollist(ui, tool, "check"):
448 if 'prompt' in _toollist(ui, tool, "check"):
446 checked = True
449 checked = True
447 if ui.promptchoice(_("was merge of '%s' successful (yn)?"
450 if ui.promptchoice(_("was merge of '%s' successful (yn)?"
448 "$$ &Yes $$ &No") % fd, 1):
451 "$$ &Yes $$ &No") % fd, 1):
449 r = 1
452 r = 1
450
453
451 if not r and not checked and (_toolbool(ui, tool, "checkchanged") or
454 if not r and not checked and (_toolbool(ui, tool, "checkchanged") or
452 'changed' in _toollist(ui, tool, "check")):
455 'changed' in _toollist(ui, tool, "check")):
453 if filecmp.cmp(a, back):
456 if filecmp.cmp(a, back):
454 if ui.promptchoice(_(" output file %s appears unchanged\n"
457 if ui.promptchoice(_(" output file %s appears unchanged\n"
455 "was merge successful (yn)?"
458 "was merge successful (yn)?"
456 "$$ &Yes $$ &No") % fd, 1):
459 "$$ &Yes $$ &No") % fd, 1):
457 r = 1
460 r = 1
458
461
459 if _toolbool(ui, tool, "fixeol"):
462 if _toolbool(ui, tool, "fixeol"):
460 _matcheol(a, back)
463 _matcheol(a, back)
461
464
462 if r:
465 if r:
463 if onfailure:
466 if onfailure:
464 ui.warn(onfailure % fd)
467 ui.warn(onfailure % fd)
465 else:
468 else:
466 util.unlink(back)
469 util.unlink(back)
467
470
468 util.unlink(b)
471 util.unlink(b)
469 util.unlink(c)
472 util.unlink(c)
470 return r
473 return r
471
474
472 # tell hggettext to extract docstrings from these functions:
475 # tell hggettext to extract docstrings from these functions:
473 i18nfunctions = internals.values()
476 i18nfunctions = internals.values()
@@ -1,512 +1,513 b''
1 # help.py - help data for mercurial
1 # help.py - help data for mercurial
2 #
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import gettext, _
8 from i18n import gettext, _
9 import itertools, os, textwrap
9 import itertools, os, textwrap
10 import error
10 import error
11 import extensions, revset, fileset, templatekw, templatefilters, filemerge
11 import extensions, revset, fileset, templatekw, templatefilters, filemerge
12 import encoding, util, minirst
12 import encoding, util, minirst
13 import cmdutil
13 import cmdutil
14 import hgweb.webcommands as webcommands
14 import hgweb.webcommands as webcommands
15
15
16 def listexts(header, exts, indent=1, showdeprecated=False):
16 def listexts(header, exts, indent=1, showdeprecated=False):
17 '''return a text listing of the given extensions'''
17 '''return a text listing of the given extensions'''
18 rst = []
18 rst = []
19 if exts:
19 if exts:
20 rst.append('\n%s\n\n' % header)
20 rst.append('\n%s\n\n' % header)
21 for name, desc in sorted(exts.iteritems()):
21 for name, desc in sorted(exts.iteritems()):
22 if '(DEPRECATED)' in desc and not showdeprecated:
22 if '(DEPRECATED)' in desc and not showdeprecated:
23 continue
23 continue
24 rst.append('%s:%s: %s\n' % (' ' * indent, name, desc))
24 rst.append('%s:%s: %s\n' % (' ' * indent, name, desc))
25 return rst
25 return rst
26
26
27 def extshelp():
27 def extshelp():
28 rst = loaddoc('extensions')().splitlines(True)
28 rst = loaddoc('extensions')().splitlines(True)
29 rst.extend(listexts(
29 rst.extend(listexts(
30 _('enabled extensions:'), extensions.enabled(), showdeprecated=True))
30 _('enabled extensions:'), extensions.enabled(), showdeprecated=True))
31 rst.extend(listexts(_('disabled extensions:'), extensions.disabled()))
31 rst.extend(listexts(_('disabled extensions:'), extensions.disabled()))
32 doc = ''.join(rst)
32 doc = ''.join(rst)
33 return doc
33 return doc
34
34
35 def optrst(header, options, verbose):
35 def optrst(header, options, verbose):
36 data = []
36 data = []
37 multioccur = False
37 multioccur = False
38 for option in options:
38 for option in options:
39 if len(option) == 5:
39 if len(option) == 5:
40 shortopt, longopt, default, desc, optlabel = option
40 shortopt, longopt, default, desc, optlabel = option
41 else:
41 else:
42 shortopt, longopt, default, desc = option
42 shortopt, longopt, default, desc = option
43 optlabel = _("VALUE") # default label
43 optlabel = _("VALUE") # default label
44
44
45 if not verbose and ("DEPRECATED" in desc or _("DEPRECATED") in desc):
45 if not verbose and ("DEPRECATED" in desc or _("DEPRECATED") in desc):
46 continue
46 continue
47
47
48 so = ''
48 so = ''
49 if shortopt:
49 if shortopt:
50 so = '-' + shortopt
50 so = '-' + shortopt
51 lo = '--' + longopt
51 lo = '--' + longopt
52 if default:
52 if default:
53 desc += _(" (default: %s)") % default
53 desc += _(" (default: %s)") % default
54
54
55 if isinstance(default, list):
55 if isinstance(default, list):
56 lo += " %s [+]" % optlabel
56 lo += " %s [+]" % optlabel
57 multioccur = True
57 multioccur = True
58 elif (default is not None) and not isinstance(default, bool):
58 elif (default is not None) and not isinstance(default, bool):
59 lo += " %s" % optlabel
59 lo += " %s" % optlabel
60
60
61 data.append((so, lo, desc))
61 data.append((so, lo, desc))
62
62
63 if multioccur:
63 if multioccur:
64 header += (_(" ([+] can be repeated)"))
64 header += (_(" ([+] can be repeated)"))
65
65
66 rst = ['\n%s:\n\n' % header]
66 rst = ['\n%s:\n\n' % header]
67 rst.extend(minirst.maketable(data, 1))
67 rst.extend(minirst.maketable(data, 1))
68
68
69 return ''.join(rst)
69 return ''.join(rst)
70
70
71 def indicateomitted(rst, omitted, notomitted=None):
71 def indicateomitted(rst, omitted, notomitted=None):
72 rst.append('\n\n.. container:: omitted\n\n %s\n\n' % omitted)
72 rst.append('\n\n.. container:: omitted\n\n %s\n\n' % omitted)
73 if notomitted:
73 if notomitted:
74 rst.append('\n\n.. container:: notomitted\n\n %s\n\n' % notomitted)
74 rst.append('\n\n.. container:: notomitted\n\n %s\n\n' % notomitted)
75
75
76 def topicmatch(kw):
76 def topicmatch(kw):
77 """Return help topics matching kw.
77 """Return help topics matching kw.
78
78
79 Returns {'section': [(name, summary), ...], ...} where section is
79 Returns {'section': [(name, summary), ...], ...} where section is
80 one of topics, commands, extensions, or extensioncommands.
80 one of topics, commands, extensions, or extensioncommands.
81 """
81 """
82 kw = encoding.lower(kw)
82 kw = encoding.lower(kw)
83 def lowercontains(container):
83 def lowercontains(container):
84 return kw in encoding.lower(container) # translated in helptable
84 return kw in encoding.lower(container) # translated in helptable
85 results = {'topics': [],
85 results = {'topics': [],
86 'commands': [],
86 'commands': [],
87 'extensions': [],
87 'extensions': [],
88 'extensioncommands': [],
88 'extensioncommands': [],
89 }
89 }
90 for names, header, doc in helptable:
90 for names, header, doc in helptable:
91 # Old extensions may use a str as doc.
91 # Old extensions may use a str as doc.
92 if (sum(map(lowercontains, names))
92 if (sum(map(lowercontains, names))
93 or lowercontains(header)
93 or lowercontains(header)
94 or (callable(doc) and lowercontains(doc()))):
94 or (callable(doc) and lowercontains(doc()))):
95 results['topics'].append((names[0], header))
95 results['topics'].append((names[0], header))
96 import commands # avoid cycle
96 import commands # avoid cycle
97 for cmd, entry in commands.table.iteritems():
97 for cmd, entry in commands.table.iteritems():
98 if len(entry) == 3:
98 if len(entry) == 3:
99 summary = entry[2]
99 summary = entry[2]
100 else:
100 else:
101 summary = ''
101 summary = ''
102 # translate docs *before* searching there
102 # translate docs *before* searching there
103 docs = _(getattr(entry[0], '__doc__', None)) or ''
103 docs = _(getattr(entry[0], '__doc__', None)) or ''
104 if kw in cmd or lowercontains(summary) or lowercontains(docs):
104 if kw in cmd or lowercontains(summary) or lowercontains(docs):
105 doclines = docs.splitlines()
105 doclines = docs.splitlines()
106 if doclines:
106 if doclines:
107 summary = doclines[0]
107 summary = doclines[0]
108 cmdname = cmd.split('|')[0].lstrip('^')
108 cmdname = cmd.split('|')[0].lstrip('^')
109 results['commands'].append((cmdname, summary))
109 results['commands'].append((cmdname, summary))
110 for name, docs in itertools.chain(
110 for name, docs in itertools.chain(
111 extensions.enabled(False).iteritems(),
111 extensions.enabled(False).iteritems(),
112 extensions.disabled().iteritems()):
112 extensions.disabled().iteritems()):
113 # extensions.load ignores the UI argument
113 # extensions.load ignores the UI argument
114 mod = extensions.load(None, name, '')
114 mod = extensions.load(None, name, '')
115 name = name.split('.')[-1]
115 name = name.split('.')[-1]
116 if lowercontains(name) or lowercontains(docs):
116 if lowercontains(name) or lowercontains(docs):
117 # extension docs are already translated
117 # extension docs are already translated
118 results['extensions'].append((name, docs.splitlines()[0]))
118 results['extensions'].append((name, docs.splitlines()[0]))
119 for cmd, entry in getattr(mod, 'cmdtable', {}).iteritems():
119 for cmd, entry in getattr(mod, 'cmdtable', {}).iteritems():
120 if kw in cmd or (len(entry) > 2 and lowercontains(entry[2])):
120 if kw in cmd or (len(entry) > 2 and lowercontains(entry[2])):
121 cmdname = cmd.split('|')[0].lstrip('^')
121 cmdname = cmd.split('|')[0].lstrip('^')
122 if entry[0].__doc__:
122 if entry[0].__doc__:
123 cmddoc = gettext(entry[0].__doc__).splitlines()[0]
123 cmddoc = gettext(entry[0].__doc__).splitlines()[0]
124 else:
124 else:
125 cmddoc = _('(no help text available)')
125 cmddoc = _('(no help text available)')
126 results['extensioncommands'].append((cmdname, cmddoc))
126 results['extensioncommands'].append((cmdname, cmddoc))
127 return results
127 return results
128
128
129 def loaddoc(topic):
129 def loaddoc(topic):
130 """Return a delayed loader for help/topic.txt."""
130 """Return a delayed loader for help/topic.txt."""
131
131
132 def loader():
132 def loader():
133 docdir = os.path.join(util.datapath, 'help')
133 docdir = os.path.join(util.datapath, 'help')
134 path = os.path.join(docdir, topic + ".txt")
134 path = os.path.join(docdir, topic + ".txt")
135 doc = gettext(util.readfile(path))
135 doc = gettext(util.readfile(path))
136 for rewriter in helphooks.get(topic, []):
136 for rewriter in helphooks.get(topic, []):
137 doc = rewriter(topic, doc)
137 doc = rewriter(topic, doc)
138 return doc
138 return doc
139
139
140 return loader
140 return loader
141
141
142 helptable = sorted([
142 helptable = sorted([
143 (["config", "hgrc"], _("Configuration Files"), loaddoc('config')),
143 (["config", "hgrc"], _("Configuration Files"), loaddoc('config')),
144 (["dates"], _("Date Formats"), loaddoc('dates')),
144 (["dates"], _("Date Formats"), loaddoc('dates')),
145 (["patterns"], _("File Name Patterns"), loaddoc('patterns')),
145 (["patterns"], _("File Name Patterns"), loaddoc('patterns')),
146 (['environment', 'env'], _('Environment Variables'),
146 (['environment', 'env'], _('Environment Variables'),
147 loaddoc('environment')),
147 loaddoc('environment')),
148 (['revisions', 'revs'], _('Specifying Single Revisions'),
148 (['revisions', 'revs'], _('Specifying Single Revisions'),
149 loaddoc('revisions')),
149 loaddoc('revisions')),
150 (['multirevs', 'mrevs'], _('Specifying Multiple Revisions'),
150 (['multirevs', 'mrevs'], _('Specifying Multiple Revisions'),
151 loaddoc('multirevs')),
151 loaddoc('multirevs')),
152 (['revsets', 'revset'], _("Specifying Revision Sets"), loaddoc('revsets')),
152 (['revsets', 'revset'], _("Specifying Revision Sets"), loaddoc('revsets')),
153 (['filesets', 'fileset'], _("Specifying File Sets"), loaddoc('filesets')),
153 (['filesets', 'fileset'], _("Specifying File Sets"), loaddoc('filesets')),
154 (['diffs'], _('Diff Formats'), loaddoc('diffs')),
154 (['diffs'], _('Diff Formats'), loaddoc('diffs')),
155 (['merge-tools', 'mergetools'], _('Merge Tools'), loaddoc('merge-tools')),
155 (['merge-tools', 'mergetools'], _('Merge Tools'), loaddoc('merge-tools')),
156 (['templating', 'templates', 'template', 'style'], _('Template Usage'),
156 (['templating', 'templates', 'template', 'style'], _('Template Usage'),
157 loaddoc('templates')),
157 loaddoc('templates')),
158 (['urls'], _('URL Paths'), loaddoc('urls')),
158 (['urls'], _('URL Paths'), loaddoc('urls')),
159 (["extensions"], _("Using Additional Features"), extshelp),
159 (["extensions"], _("Using Additional Features"), extshelp),
160 (["subrepos", "subrepo"], _("Subrepositories"), loaddoc('subrepos')),
160 (["subrepos", "subrepo"], _("Subrepositories"), loaddoc('subrepos')),
161 (["hgweb"], _("Configuring hgweb"), loaddoc('hgweb')),
161 (["hgweb"], _("Configuring hgweb"), loaddoc('hgweb')),
162 (["glossary"], _("Glossary"), loaddoc('glossary')),
162 (["glossary"], _("Glossary"), loaddoc('glossary')),
163 (["hgignore", "ignore"], _("Syntax for Mercurial Ignore Files"),
163 (["hgignore", "ignore"], _("Syntax for Mercurial Ignore Files"),
164 loaddoc('hgignore')),
164 loaddoc('hgignore')),
165 (["phases"], _("Working with Phases"), loaddoc('phases')),
165 (["phases"], _("Working with Phases"), loaddoc('phases')),
166 ])
166 ])
167
167
168 # Map topics to lists of callable taking the current topic help and
168 # Map topics to lists of callable taking the current topic help and
169 # returning the updated version
169 # returning the updated version
170 helphooks = {}
170 helphooks = {}
171
171
172 def addtopichook(topic, rewriter):
172 def addtopichook(topic, rewriter):
173 helphooks.setdefault(topic, []).append(rewriter)
173 helphooks.setdefault(topic, []).append(rewriter)
174
174
175 def makeitemsdoc(topic, doc, marker, items, dedent=False):
175 def makeitemsdoc(topic, doc, marker, items, dedent=False):
176 """Extract docstring from the items key to function mapping, build a
176 """Extract docstring from the items key to function mapping, build a
177 .single documentation block and use it to overwrite the marker in doc
177 .single documentation block and use it to overwrite the marker in doc
178 """
178 """
179 entries = []
179 entries = []
180 for name in sorted(items):
180 for name in sorted(items):
181 text = (items[name].__doc__ or '').rstrip()
181 text = (items[name].__doc__ or '').rstrip()
182 if not text:
182 if not text:
183 continue
183 continue
184 text = gettext(text)
184 text = gettext(text)
185 if dedent:
185 if dedent:
186 text = textwrap.dedent(text)
186 text = textwrap.dedent(text)
187 lines = text.splitlines()
187 lines = text.splitlines()
188 doclines = [(lines[0])]
188 doclines = [(lines[0])]
189 for l in lines[1:]:
189 for l in lines[1:]:
190 # Stop once we find some Python doctest
190 # Stop once we find some Python doctest
191 if l.strip().startswith('>>>'):
191 if l.strip().startswith('>>>'):
192 break
192 break
193 if dedent:
193 if dedent:
194 doclines.append(l.rstrip())
194 doclines.append(l.rstrip())
195 else:
195 else:
196 doclines.append(' ' + l.strip())
196 doclines.append(' ' + l.strip())
197 entries.append('\n'.join(doclines))
197 entries.append('\n'.join(doclines))
198 entries = '\n\n'.join(entries)
198 entries = '\n\n'.join(entries)
199 return doc.replace(marker, entries)
199 return doc.replace(marker, entries)
200
200
201 def addtopicsymbols(topic, marker, symbols, dedent=False):
201 def addtopicsymbols(topic, marker, symbols, dedent=False):
202 def add(topic, doc):
202 def add(topic, doc):
203 return makeitemsdoc(topic, doc, marker, symbols, dedent=dedent)
203 return makeitemsdoc(topic, doc, marker, symbols, dedent=dedent)
204 addtopichook(topic, add)
204 addtopichook(topic, add)
205
205
206 addtopicsymbols('filesets', '.. predicatesmarker', fileset.symbols)
206 addtopicsymbols('filesets', '.. predicatesmarker', fileset.symbols)
207 addtopicsymbols('merge-tools', '.. internaltoolsmarker', filemerge.internals)
207 addtopicsymbols('merge-tools', '.. internaltoolsmarker',
208 filemerge.internalsdoc)
208 addtopicsymbols('revsets', '.. predicatesmarker', revset.symbols)
209 addtopicsymbols('revsets', '.. predicatesmarker', revset.symbols)
209 addtopicsymbols('templates', '.. keywordsmarker', templatekw.dockeywords)
210 addtopicsymbols('templates', '.. keywordsmarker', templatekw.dockeywords)
210 addtopicsymbols('templates', '.. filtersmarker', templatefilters.filters)
211 addtopicsymbols('templates', '.. filtersmarker', templatefilters.filters)
211 addtopicsymbols('hgweb', '.. webcommandsmarker', webcommands.commands,
212 addtopicsymbols('hgweb', '.. webcommandsmarker', webcommands.commands,
212 dedent=True)
213 dedent=True)
213
214
214 def help_(ui, name, unknowncmd=False, full=True, **opts):
215 def help_(ui, name, unknowncmd=False, full=True, **opts):
215 '''
216 '''
216 Generate the help for 'name' as unformatted restructured text. If
217 Generate the help for 'name' as unformatted restructured text. If
217 'name' is None, describe the commands available.
218 'name' is None, describe the commands available.
218 '''
219 '''
219
220
220 import commands # avoid cycle
221 import commands # avoid cycle
221
222
222 def helpcmd(name):
223 def helpcmd(name):
223 try:
224 try:
224 aliases, entry = cmdutil.findcmd(name, commands.table,
225 aliases, entry = cmdutil.findcmd(name, commands.table,
225 strict=unknowncmd)
226 strict=unknowncmd)
226 except error.AmbiguousCommand, inst:
227 except error.AmbiguousCommand, inst:
227 # py3k fix: except vars can't be used outside the scope of the
228 # py3k fix: except vars can't be used outside the scope of the
228 # except block, nor can be used inside a lambda. python issue4617
229 # except block, nor can be used inside a lambda. python issue4617
229 prefix = inst.args[0]
230 prefix = inst.args[0]
230 select = lambda c: c.lstrip('^').startswith(prefix)
231 select = lambda c: c.lstrip('^').startswith(prefix)
231 rst = helplist(select)
232 rst = helplist(select)
232 return rst
233 return rst
233
234
234 rst = []
235 rst = []
235
236
236 # check if it's an invalid alias and display its error if it is
237 # check if it's an invalid alias and display its error if it is
237 if getattr(entry[0], 'badalias', None):
238 if getattr(entry[0], 'badalias', None):
238 rst.append(entry[0].badalias + '\n')
239 rst.append(entry[0].badalias + '\n')
239 if entry[0].unknowncmd:
240 if entry[0].unknowncmd:
240 try:
241 try:
241 rst.extend(helpextcmd(entry[0].cmdname))
242 rst.extend(helpextcmd(entry[0].cmdname))
242 except error.UnknownCommand:
243 except error.UnknownCommand:
243 pass
244 pass
244 return rst
245 return rst
245
246
246 # synopsis
247 # synopsis
247 if len(entry) > 2:
248 if len(entry) > 2:
248 if entry[2].startswith('hg'):
249 if entry[2].startswith('hg'):
249 rst.append("%s\n" % entry[2])
250 rst.append("%s\n" % entry[2])
250 else:
251 else:
251 rst.append('hg %s %s\n' % (aliases[0], entry[2]))
252 rst.append('hg %s %s\n' % (aliases[0], entry[2]))
252 else:
253 else:
253 rst.append('hg %s\n' % aliases[0])
254 rst.append('hg %s\n' % aliases[0])
254 # aliases
255 # aliases
255 if full and not ui.quiet and len(aliases) > 1:
256 if full and not ui.quiet and len(aliases) > 1:
256 rst.append(_("\naliases: %s\n") % ', '.join(aliases[1:]))
257 rst.append(_("\naliases: %s\n") % ', '.join(aliases[1:]))
257 rst.append('\n')
258 rst.append('\n')
258
259
259 # description
260 # description
260 doc = gettext(entry[0].__doc__)
261 doc = gettext(entry[0].__doc__)
261 if not doc:
262 if not doc:
262 doc = _("(no help text available)")
263 doc = _("(no help text available)")
263 if util.safehasattr(entry[0], 'definition'): # aliased command
264 if util.safehasattr(entry[0], 'definition'): # aliased command
264 if entry[0].definition.startswith('!'): # shell alias
265 if entry[0].definition.startswith('!'): # shell alias
265 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
266 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
266 else:
267 else:
267 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
268 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
268 doc = doc.splitlines(True)
269 doc = doc.splitlines(True)
269 if ui.quiet or not full:
270 if ui.quiet or not full:
270 rst.append(doc[0])
271 rst.append(doc[0])
271 else:
272 else:
272 rst.extend(doc)
273 rst.extend(doc)
273 rst.append('\n')
274 rst.append('\n')
274
275
275 # check if this command shadows a non-trivial (multi-line)
276 # check if this command shadows a non-trivial (multi-line)
276 # extension help text
277 # extension help text
277 try:
278 try:
278 mod = extensions.find(name)
279 mod = extensions.find(name)
279 doc = gettext(mod.__doc__) or ''
280 doc = gettext(mod.__doc__) or ''
280 if '\n' in doc.strip():
281 if '\n' in doc.strip():
281 msg = _('(use "hg help -e %s" to show help for '
282 msg = _('(use "hg help -e %s" to show help for '
282 'the %s extension)') % (name, name)
283 'the %s extension)') % (name, name)
283 rst.append('\n%s\n' % msg)
284 rst.append('\n%s\n' % msg)
284 except KeyError:
285 except KeyError:
285 pass
286 pass
286
287
287 # options
288 # options
288 if not ui.quiet and entry[1]:
289 if not ui.quiet and entry[1]:
289 rst.append(optrst(_("options"), entry[1], ui.verbose))
290 rst.append(optrst(_("options"), entry[1], ui.verbose))
290
291
291 if ui.verbose:
292 if ui.verbose:
292 rst.append(optrst(_("global options"),
293 rst.append(optrst(_("global options"),
293 commands.globalopts, ui.verbose))
294 commands.globalopts, ui.verbose))
294
295
295 if not ui.verbose:
296 if not ui.verbose:
296 if not full:
297 if not full:
297 rst.append(_('\n(use "hg %s -h" to show more help)\n')
298 rst.append(_('\n(use "hg %s -h" to show more help)\n')
298 % name)
299 % name)
299 elif not ui.quiet:
300 elif not ui.quiet:
300 rst.append(_('\n(some details hidden, use --verbose '
301 rst.append(_('\n(some details hidden, use --verbose '
301 'to show complete help)'))
302 'to show complete help)'))
302
303
303 return rst
304 return rst
304
305
305
306
306 def helplist(select=None):
307 def helplist(select=None):
307 # list of commands
308 # list of commands
308 if name == "shortlist":
309 if name == "shortlist":
309 header = _('basic commands:\n\n')
310 header = _('basic commands:\n\n')
310 elif name == "debug":
311 elif name == "debug":
311 header = _('debug commands (internal and unsupported):\n\n')
312 header = _('debug commands (internal and unsupported):\n\n')
312 else:
313 else:
313 header = _('list of commands:\n\n')
314 header = _('list of commands:\n\n')
314
315
315 h = {}
316 h = {}
316 cmds = {}
317 cmds = {}
317 for c, e in commands.table.iteritems():
318 for c, e in commands.table.iteritems():
318 f = c.split("|", 1)[0]
319 f = c.split("|", 1)[0]
319 if select and not select(f):
320 if select and not select(f):
320 continue
321 continue
321 if (not select and name != 'shortlist' and
322 if (not select and name != 'shortlist' and
322 e[0].__module__ != commands.__name__):
323 e[0].__module__ != commands.__name__):
323 continue
324 continue
324 if name == "shortlist" and not f.startswith("^"):
325 if name == "shortlist" and not f.startswith("^"):
325 continue
326 continue
326 f = f.lstrip("^")
327 f = f.lstrip("^")
327 if not ui.debugflag and f.startswith("debug") and name != "debug":
328 if not ui.debugflag and f.startswith("debug") and name != "debug":
328 continue
329 continue
329 doc = e[0].__doc__
330 doc = e[0].__doc__
330 if doc and 'DEPRECATED' in doc and not ui.verbose:
331 if doc and 'DEPRECATED' in doc and not ui.verbose:
331 continue
332 continue
332 doc = gettext(doc)
333 doc = gettext(doc)
333 if not doc:
334 if not doc:
334 doc = _("(no help text available)")
335 doc = _("(no help text available)")
335 h[f] = doc.splitlines()[0].rstrip()
336 h[f] = doc.splitlines()[0].rstrip()
336 cmds[f] = c.lstrip("^")
337 cmds[f] = c.lstrip("^")
337
338
338 rst = []
339 rst = []
339 if not h:
340 if not h:
340 if not ui.quiet:
341 if not ui.quiet:
341 rst.append(_('no commands defined\n'))
342 rst.append(_('no commands defined\n'))
342 return rst
343 return rst
343
344
344 if not ui.quiet:
345 if not ui.quiet:
345 rst.append(header)
346 rst.append(header)
346 fns = sorted(h)
347 fns = sorted(h)
347 for f in fns:
348 for f in fns:
348 if ui.verbose:
349 if ui.verbose:
349 commacmds = cmds[f].replace("|",", ")
350 commacmds = cmds[f].replace("|",", ")
350 rst.append(" :%s: %s\n" % (commacmds, h[f]))
351 rst.append(" :%s: %s\n" % (commacmds, h[f]))
351 else:
352 else:
352 rst.append(' :%s: %s\n' % (f, h[f]))
353 rst.append(' :%s: %s\n' % (f, h[f]))
353
354
354 if not name:
355 if not name:
355 exts = listexts(_('enabled extensions:'), extensions.enabled())
356 exts = listexts(_('enabled extensions:'), extensions.enabled())
356 if exts:
357 if exts:
357 rst.append('\n')
358 rst.append('\n')
358 rst.extend(exts)
359 rst.extend(exts)
359
360
360 rst.append(_("\nadditional help topics:\n\n"))
361 rst.append(_("\nadditional help topics:\n\n"))
361 topics = []
362 topics = []
362 for names, header, doc in helptable:
363 for names, header, doc in helptable:
363 topics.append((names[0], header))
364 topics.append((names[0], header))
364 for t, desc in topics:
365 for t, desc in topics:
365 rst.append(" :%s: %s\n" % (t, desc))
366 rst.append(" :%s: %s\n" % (t, desc))
366
367
367 if ui.quiet:
368 if ui.quiet:
368 pass
369 pass
369 elif ui.verbose:
370 elif ui.verbose:
370 rst.append('\n%s\n' % optrst(_("global options"),
371 rst.append('\n%s\n' % optrst(_("global options"),
371 commands.globalopts, ui.verbose))
372 commands.globalopts, ui.verbose))
372 if name == 'shortlist':
373 if name == 'shortlist':
373 rst.append(_('\n(use "hg help" for the full list '
374 rst.append(_('\n(use "hg help" for the full list '
374 'of commands)\n'))
375 'of commands)\n'))
375 else:
376 else:
376 if name == 'shortlist':
377 if name == 'shortlist':
377 rst.append(_('\n(use "hg help" for the full list of commands '
378 rst.append(_('\n(use "hg help" for the full list of commands '
378 'or "hg -v" for details)\n'))
379 'or "hg -v" for details)\n'))
379 elif name and not full:
380 elif name and not full:
380 rst.append(_('\n(use "hg help %s" to show the full help '
381 rst.append(_('\n(use "hg help %s" to show the full help '
381 'text)\n') % name)
382 'text)\n') % name)
382 elif name and cmds and name in cmds.keys():
383 elif name and cmds and name in cmds.keys():
383 rst.append(_('\n(use "hg help -v -e %s" to show built-in '
384 rst.append(_('\n(use "hg help -v -e %s" to show built-in '
384 'aliases and global options)\n') % name)
385 'aliases and global options)\n') % name)
385 else:
386 else:
386 rst.append(_('\n(use "hg help -v%s" to show built-in aliases '
387 rst.append(_('\n(use "hg help -v%s" to show built-in aliases '
387 'and global options)\n')
388 'and global options)\n')
388 % (name and " " + name or ""))
389 % (name and " " + name or ""))
389 return rst
390 return rst
390
391
391 def helptopic(name):
392 def helptopic(name):
392 for names, header, doc in helptable:
393 for names, header, doc in helptable:
393 if name in names:
394 if name in names:
394 break
395 break
395 else:
396 else:
396 raise error.UnknownCommand(name)
397 raise error.UnknownCommand(name)
397
398
398 rst = [minirst.section(header)]
399 rst = [minirst.section(header)]
399
400
400 # description
401 # description
401 if not doc:
402 if not doc:
402 rst.append(" %s\n" % _("(no help text available)"))
403 rst.append(" %s\n" % _("(no help text available)"))
403 if callable(doc):
404 if callable(doc):
404 rst += [" %s\n" % l for l in doc().splitlines()]
405 rst += [" %s\n" % l for l in doc().splitlines()]
405
406
406 if not ui.verbose:
407 if not ui.verbose:
407 omitted = _('(some details hidden, use --verbose'
408 omitted = _('(some details hidden, use --verbose'
408 ' to show complete help)')
409 ' to show complete help)')
409 indicateomitted(rst, omitted)
410 indicateomitted(rst, omitted)
410
411
411 try:
412 try:
412 cmdutil.findcmd(name, commands.table)
413 cmdutil.findcmd(name, commands.table)
413 rst.append(_('\nuse "hg help -c %s" to see help for '
414 rst.append(_('\nuse "hg help -c %s" to see help for '
414 'the %s command\n') % (name, name))
415 'the %s command\n') % (name, name))
415 except error.UnknownCommand:
416 except error.UnknownCommand:
416 pass
417 pass
417 return rst
418 return rst
418
419
419 def helpext(name):
420 def helpext(name):
420 try:
421 try:
421 mod = extensions.find(name)
422 mod = extensions.find(name)
422 doc = gettext(mod.__doc__) or _('no help text available')
423 doc = gettext(mod.__doc__) or _('no help text available')
423 except KeyError:
424 except KeyError:
424 mod = None
425 mod = None
425 doc = extensions.disabledext(name)
426 doc = extensions.disabledext(name)
426 if not doc:
427 if not doc:
427 raise error.UnknownCommand(name)
428 raise error.UnknownCommand(name)
428
429
429 if '\n' not in doc:
430 if '\n' not in doc:
430 head, tail = doc, ""
431 head, tail = doc, ""
431 else:
432 else:
432 head, tail = doc.split('\n', 1)
433 head, tail = doc.split('\n', 1)
433 rst = [_('%s extension - %s\n\n') % (name.split('.')[-1], head)]
434 rst = [_('%s extension - %s\n\n') % (name.split('.')[-1], head)]
434 if tail:
435 if tail:
435 rst.extend(tail.splitlines(True))
436 rst.extend(tail.splitlines(True))
436 rst.append('\n')
437 rst.append('\n')
437
438
438 if not ui.verbose:
439 if not ui.verbose:
439 omitted = _('(some details hidden, use --verbose'
440 omitted = _('(some details hidden, use --verbose'
440 ' to show complete help)')
441 ' to show complete help)')
441 indicateomitted(rst, omitted)
442 indicateomitted(rst, omitted)
442
443
443 if mod:
444 if mod:
444 try:
445 try:
445 ct = mod.cmdtable
446 ct = mod.cmdtable
446 except AttributeError:
447 except AttributeError:
447 ct = {}
448 ct = {}
448 modcmds = set([c.split('|', 1)[0] for c in ct])
449 modcmds = set([c.split('|', 1)[0] for c in ct])
449 rst.extend(helplist(modcmds.__contains__))
450 rst.extend(helplist(modcmds.__contains__))
450 else:
451 else:
451 rst.append(_('(use "hg help extensions" for information on enabling'
452 rst.append(_('(use "hg help extensions" for information on enabling'
452 ' extensions)\n'))
453 ' extensions)\n'))
453 return rst
454 return rst
454
455
455 def helpextcmd(name):
456 def helpextcmd(name):
456 cmd, ext, mod = extensions.disabledcmd(ui, name,
457 cmd, ext, mod = extensions.disabledcmd(ui, name,
457 ui.configbool('ui', 'strict'))
458 ui.configbool('ui', 'strict'))
458 doc = gettext(mod.__doc__).splitlines()[0]
459 doc = gettext(mod.__doc__).splitlines()[0]
459
460
460 rst = listexts(_("'%s' is provided by the following "
461 rst = listexts(_("'%s' is provided by the following "
461 "extension:") % cmd, {ext: doc}, indent=4)
462 "extension:") % cmd, {ext: doc}, indent=4)
462 rst.append('\n')
463 rst.append('\n')
463 rst.append(_('(use "hg help extensions" for information on enabling '
464 rst.append(_('(use "hg help extensions" for information on enabling '
464 'extensions)\n'))
465 'extensions)\n'))
465 return rst
466 return rst
466
467
467
468
468 rst = []
469 rst = []
469 kw = opts.get('keyword')
470 kw = opts.get('keyword')
470 if kw:
471 if kw:
471 matches = topicmatch(kw)
472 matches = topicmatch(kw)
472 for t, title in (('topics', _('Topics')),
473 for t, title in (('topics', _('Topics')),
473 ('commands', _('Commands')),
474 ('commands', _('Commands')),
474 ('extensions', _('Extensions')),
475 ('extensions', _('Extensions')),
475 ('extensioncommands', _('Extension Commands'))):
476 ('extensioncommands', _('Extension Commands'))):
476 if matches[t]:
477 if matches[t]:
477 rst.append('%s:\n\n' % title)
478 rst.append('%s:\n\n' % title)
478 rst.extend(minirst.maketable(sorted(matches[t]), 1))
479 rst.extend(minirst.maketable(sorted(matches[t]), 1))
479 rst.append('\n')
480 rst.append('\n')
480 if not rst:
481 if not rst:
481 msg = _('no matches')
482 msg = _('no matches')
482 hint = _('try "hg help" for a list of topics')
483 hint = _('try "hg help" for a list of topics')
483 raise util.Abort(msg, hint=hint)
484 raise util.Abort(msg, hint=hint)
484 elif name and name != 'shortlist':
485 elif name and name != 'shortlist':
485 if unknowncmd:
486 if unknowncmd:
486 queries = (helpextcmd,)
487 queries = (helpextcmd,)
487 elif opts.get('extension'):
488 elif opts.get('extension'):
488 queries = (helpext,)
489 queries = (helpext,)
489 elif opts.get('command'):
490 elif opts.get('command'):
490 queries = (helpcmd,)
491 queries = (helpcmd,)
491 else:
492 else:
492 queries = (helptopic, helpcmd, helpext, helpextcmd)
493 queries = (helptopic, helpcmd, helpext, helpextcmd)
493 for f in queries:
494 for f in queries:
494 try:
495 try:
495 rst = f(name)
496 rst = f(name)
496 break
497 break
497 except error.UnknownCommand:
498 except error.UnknownCommand:
498 pass
499 pass
499 else:
500 else:
500 if unknowncmd:
501 if unknowncmd:
501 raise error.UnknownCommand(name)
502 raise error.UnknownCommand(name)
502 else:
503 else:
503 msg = _('no such help topic: %s') % name
504 msg = _('no such help topic: %s') % name
504 hint = _('try "hg help --keyword %s"') % name
505 hint = _('try "hg help --keyword %s"') % name
505 raise util.Abort(msg, hint=hint)
506 raise util.Abort(msg, hint=hint)
506 else:
507 else:
507 # program name
508 # program name
508 if not ui.quiet:
509 if not ui.quiet:
509 rst = [_("Mercurial Distributed SCM\n"), '\n']
510 rst = [_("Mercurial Distributed SCM\n"), '\n']
510 rst.extend(helplist())
511 rst.extend(helplist())
511
512
512 return ''.join(rst)
513 return ''.join(rst)
General Comments 0
You need to be logged in to leave comments. Login now