##// END OF EJS Templates
extdiff: remove unrequired do3way argument to diffpatch()...
Pulkit Goyal -
r45688:d23881b1 default
parent child Browse files
Show More
@@ -1,755 +1,754 b''
1 # extdiff.py - external diff program support for mercurial
1 # extdiff.py - external diff program support for mercurial
2 #
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.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 '''command to allow external programs to compare revisions
8 '''command to allow external programs to compare revisions
9
9
10 The extdiff Mercurial extension allows you to use external programs
10 The extdiff Mercurial extension allows you to use external programs
11 to compare revisions, or revision with working directory. The external
11 to compare revisions, or revision with working directory. The external
12 diff programs are called with a configurable set of options and two
12 diff programs are called with a configurable set of options and two
13 non-option arguments: paths to directories containing snapshots of
13 non-option arguments: paths to directories containing snapshots of
14 files to compare.
14 files to compare.
15
15
16 If there is more than one file being compared and the "child" revision
16 If there is more than one file being compared and the "child" revision
17 is the working directory, any modifications made in the external diff
17 is the working directory, any modifications made in the external diff
18 program will be copied back to the working directory from the temporary
18 program will be copied back to the working directory from the temporary
19 directory.
19 directory.
20
20
21 The extdiff extension also allows you to configure new diff commands, so
21 The extdiff extension also allows you to configure new diff commands, so
22 you do not need to type :hg:`extdiff -p kdiff3` always. ::
22 you do not need to type :hg:`extdiff -p kdiff3` always. ::
23
23
24 [extdiff]
24 [extdiff]
25 # add new command that runs GNU diff(1) in 'context diff' mode
25 # add new command that runs GNU diff(1) in 'context diff' mode
26 cdiff = gdiff -Nprc5
26 cdiff = gdiff -Nprc5
27 ## or the old way:
27 ## or the old way:
28 #cmd.cdiff = gdiff
28 #cmd.cdiff = gdiff
29 #opts.cdiff = -Nprc5
29 #opts.cdiff = -Nprc5
30
30
31 # add new command called meld, runs meld (no need to name twice). If
31 # add new command called meld, runs meld (no need to name twice). If
32 # the meld executable is not available, the meld tool in [merge-tools]
32 # the meld executable is not available, the meld tool in [merge-tools]
33 # will be used, if available
33 # will be used, if available
34 meld =
34 meld =
35
35
36 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
36 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
37 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
37 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
38 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
38 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
39 # your .vimrc
39 # your .vimrc
40 vimdiff = gvim -f "+next" \\
40 vimdiff = gvim -f "+next" \\
41 "+execute 'DirDiff' fnameescape(argv(0)) fnameescape(argv(1))"
41 "+execute 'DirDiff' fnameescape(argv(0)) fnameescape(argv(1))"
42
42
43 Tool arguments can include variables that are expanded at runtime::
43 Tool arguments can include variables that are expanded at runtime::
44
44
45 $parent1, $plabel1 - filename, descriptive label of first parent
45 $parent1, $plabel1 - filename, descriptive label of first parent
46 $child, $clabel - filename, descriptive label of child revision
46 $child, $clabel - filename, descriptive label of child revision
47 $parent2, $plabel2 - filename, descriptive label of second parent
47 $parent2, $plabel2 - filename, descriptive label of second parent
48 $root - repository root
48 $root - repository root
49 $parent is an alias for $parent1.
49 $parent is an alias for $parent1.
50
50
51 The extdiff extension will look in your [diff-tools] and [merge-tools]
51 The extdiff extension will look in your [diff-tools] and [merge-tools]
52 sections for diff tool arguments, when none are specified in [extdiff].
52 sections for diff tool arguments, when none are specified in [extdiff].
53
53
54 ::
54 ::
55
55
56 [extdiff]
56 [extdiff]
57 kdiff3 =
57 kdiff3 =
58
58
59 [diff-tools]
59 [diff-tools]
60 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
60 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
61
61
62 If a program has a graphical interface, it might be interesting to tell
62 If a program has a graphical interface, it might be interesting to tell
63 Mercurial about it. It will prevent the program from being mistakenly
63 Mercurial about it. It will prevent the program from being mistakenly
64 used in a terminal-only environment (such as an SSH terminal session),
64 used in a terminal-only environment (such as an SSH terminal session),
65 and will make :hg:`extdiff --per-file` open multiple file diffs at once
65 and will make :hg:`extdiff --per-file` open multiple file diffs at once
66 instead of one by one (if you still want to open file diffs one by one,
66 instead of one by one (if you still want to open file diffs one by one,
67 you can use the --confirm option).
67 you can use the --confirm option).
68
68
69 Declaring that a tool has a graphical interface can be done with the
69 Declaring that a tool has a graphical interface can be done with the
70 ``gui`` flag next to where ``diffargs`` are specified:
70 ``gui`` flag next to where ``diffargs`` are specified:
71
71
72 ::
72 ::
73
73
74 [diff-tools]
74 [diff-tools]
75 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
75 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
76 kdiff3.gui = true
76 kdiff3.gui = true
77
77
78 You can use -I/-X and list of file or directory names like normal
78 You can use -I/-X and list of file or directory names like normal
79 :hg:`diff` command. The extdiff extension makes snapshots of only
79 :hg:`diff` command. The extdiff extension makes snapshots of only
80 needed files, so running the external diff program will actually be
80 needed files, so running the external diff program will actually be
81 pretty fast (at least faster than having to compare the entire tree).
81 pretty fast (at least faster than having to compare the entire tree).
82 '''
82 '''
83
83
84 from __future__ import absolute_import
84 from __future__ import absolute_import
85
85
86 import os
86 import os
87 import re
87 import re
88 import shutil
88 import shutil
89 import stat
89 import stat
90 import subprocess
90 import subprocess
91
91
92 from mercurial.i18n import _
92 from mercurial.i18n import _
93 from mercurial.node import (
93 from mercurial.node import (
94 nullid,
94 nullid,
95 short,
95 short,
96 )
96 )
97 from mercurial import (
97 from mercurial import (
98 archival,
98 archival,
99 cmdutil,
99 cmdutil,
100 encoding,
100 encoding,
101 error,
101 error,
102 filemerge,
102 filemerge,
103 formatter,
103 formatter,
104 pycompat,
104 pycompat,
105 registrar,
105 registrar,
106 scmutil,
106 scmutil,
107 util,
107 util,
108 )
108 )
109 from mercurial.utils import (
109 from mercurial.utils import (
110 procutil,
110 procutil,
111 stringutil,
111 stringutil,
112 )
112 )
113
113
114 cmdtable = {}
114 cmdtable = {}
115 command = registrar.command(cmdtable)
115 command = registrar.command(cmdtable)
116
116
117 configtable = {}
117 configtable = {}
118 configitem = registrar.configitem(configtable)
118 configitem = registrar.configitem(configtable)
119
119
120 configitem(
120 configitem(
121 b'extdiff', br'opts\..*', default=b'', generic=True,
121 b'extdiff', br'opts\..*', default=b'', generic=True,
122 )
122 )
123
123
124 configitem(
124 configitem(
125 b'extdiff', br'gui\..*', generic=True,
125 b'extdiff', br'gui\..*', generic=True,
126 )
126 )
127
127
128 configitem(
128 configitem(
129 b'diff-tools', br'.*\.diffargs$', default=None, generic=True,
129 b'diff-tools', br'.*\.diffargs$', default=None, generic=True,
130 )
130 )
131
131
132 configitem(
132 configitem(
133 b'diff-tools', br'.*\.gui$', generic=True,
133 b'diff-tools', br'.*\.gui$', generic=True,
134 )
134 )
135
135
136 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
136 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
137 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
137 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
138 # be specifying the version(s) of Mercurial they are tested with, or
138 # be specifying the version(s) of Mercurial they are tested with, or
139 # leave the attribute unspecified.
139 # leave the attribute unspecified.
140 testedwith = b'ships-with-hg-core'
140 testedwith = b'ships-with-hg-core'
141
141
142
142
143 def snapshot(ui, repo, files, node, tmproot, listsubrepos):
143 def snapshot(ui, repo, files, node, tmproot, listsubrepos):
144 '''snapshot files as of some revision
144 '''snapshot files as of some revision
145 if not using snapshot, -I/-X does not work and recursive diff
145 if not using snapshot, -I/-X does not work and recursive diff
146 in tools like kdiff3 and meld displays too many files.'''
146 in tools like kdiff3 and meld displays too many files.'''
147 dirname = os.path.basename(repo.root)
147 dirname = os.path.basename(repo.root)
148 if dirname == b"":
148 if dirname == b"":
149 dirname = b"root"
149 dirname = b"root"
150 if node is not None:
150 if node is not None:
151 dirname = b'%s.%s' % (dirname, short(node))
151 dirname = b'%s.%s' % (dirname, short(node))
152 base = os.path.join(tmproot, dirname)
152 base = os.path.join(tmproot, dirname)
153 os.mkdir(base)
153 os.mkdir(base)
154 fnsandstat = []
154 fnsandstat = []
155
155
156 if node is not None:
156 if node is not None:
157 ui.note(
157 ui.note(
158 _(b'making snapshot of %d files from rev %s\n')
158 _(b'making snapshot of %d files from rev %s\n')
159 % (len(files), short(node))
159 % (len(files), short(node))
160 )
160 )
161 else:
161 else:
162 ui.note(
162 ui.note(
163 _(b'making snapshot of %d files from working directory\n')
163 _(b'making snapshot of %d files from working directory\n')
164 % (len(files))
164 % (len(files))
165 )
165 )
166
166
167 if files:
167 if files:
168 repo.ui.setconfig(b"ui", b"archivemeta", False)
168 repo.ui.setconfig(b"ui", b"archivemeta", False)
169
169
170 archival.archive(
170 archival.archive(
171 repo,
171 repo,
172 base,
172 base,
173 node,
173 node,
174 b'files',
174 b'files',
175 match=scmutil.matchfiles(repo, files),
175 match=scmutil.matchfiles(repo, files),
176 subrepos=listsubrepos,
176 subrepos=listsubrepos,
177 )
177 )
178
178
179 for fn in sorted(files):
179 for fn in sorted(files):
180 wfn = util.pconvert(fn)
180 wfn = util.pconvert(fn)
181 ui.note(b' %s\n' % wfn)
181 ui.note(b' %s\n' % wfn)
182
182
183 if node is None:
183 if node is None:
184 dest = os.path.join(base, wfn)
184 dest = os.path.join(base, wfn)
185
185
186 fnsandstat.append((dest, repo.wjoin(fn), os.lstat(dest)))
186 fnsandstat.append((dest, repo.wjoin(fn), os.lstat(dest)))
187 return dirname, fnsandstat
187 return dirname, fnsandstat
188
188
189
189
190 def formatcmdline(
190 def formatcmdline(
191 cmdline,
191 cmdline,
192 repo_root,
192 repo_root,
193 do3way,
193 do3way,
194 parent1,
194 parent1,
195 plabel1,
195 plabel1,
196 parent2,
196 parent2,
197 plabel2,
197 plabel2,
198 child,
198 child,
199 clabel,
199 clabel,
200 ):
200 ):
201 # Function to quote file/dir names in the argument string.
201 # Function to quote file/dir names in the argument string.
202 # When not operating in 3-way mode, an empty string is
202 # When not operating in 3-way mode, an empty string is
203 # returned for parent2
203 # returned for parent2
204 replace = {
204 replace = {
205 b'parent': parent1,
205 b'parent': parent1,
206 b'parent1': parent1,
206 b'parent1': parent1,
207 b'parent2': parent2,
207 b'parent2': parent2,
208 b'plabel1': plabel1,
208 b'plabel1': plabel1,
209 b'plabel2': plabel2,
209 b'plabel2': plabel2,
210 b'child': child,
210 b'child': child,
211 b'clabel': clabel,
211 b'clabel': clabel,
212 b'root': repo_root,
212 b'root': repo_root,
213 }
213 }
214
214
215 def quote(match):
215 def quote(match):
216 pre = match.group(2)
216 pre = match.group(2)
217 key = match.group(3)
217 key = match.group(3)
218 if not do3way and key == b'parent2':
218 if not do3way and key == b'parent2':
219 return pre
219 return pre
220 return pre + procutil.shellquote(replace[key])
220 return pre + procutil.shellquote(replace[key])
221
221
222 # Match parent2 first, so 'parent1?' will match both parent1 and parent
222 # Match parent2 first, so 'parent1?' will match both parent1 and parent
223 regex = (
223 regex = (
224 br'''(['"]?)([^\s'"$]*)'''
224 br'''(['"]?)([^\s'"$]*)'''
225 br'\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)\1'
225 br'\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)\1'
226 )
226 )
227 if not do3way and not re.search(regex, cmdline):
227 if not do3way and not re.search(regex, cmdline):
228 cmdline += b' $parent1 $child'
228 cmdline += b' $parent1 $child'
229 return re.sub(regex, quote, cmdline)
229 return re.sub(regex, quote, cmdline)
230
230
231
231
232 def _systembackground(cmd, environ=None, cwd=None):
232 def _systembackground(cmd, environ=None, cwd=None):
233 ''' like 'procutil.system', but returns the Popen object directly
233 ''' like 'procutil.system', but returns the Popen object directly
234 so we don't have to wait on it.
234 so we don't have to wait on it.
235 '''
235 '''
236 env = procutil.shellenviron(environ)
236 env = procutil.shellenviron(environ)
237 proc = subprocess.Popen(
237 proc = subprocess.Popen(
238 procutil.tonativestr(cmd),
238 procutil.tonativestr(cmd),
239 shell=True,
239 shell=True,
240 close_fds=procutil.closefds,
240 close_fds=procutil.closefds,
241 env=procutil.tonativeenv(env),
241 env=procutil.tonativeenv(env),
242 cwd=pycompat.rapply(procutil.tonativestr, cwd),
242 cwd=pycompat.rapply(procutil.tonativestr, cwd),
243 )
243 )
244 return proc
244 return proc
245
245
246
246
247 def _runperfilediff(
247 def _runperfilediff(
248 cmdline,
248 cmdline,
249 repo_root,
249 repo_root,
250 ui,
250 ui,
251 guitool,
251 guitool,
252 do3way,
252 do3way,
253 confirm,
253 confirm,
254 commonfiles,
254 commonfiles,
255 tmproot,
255 tmproot,
256 dir1a,
256 dir1a,
257 dir1b,
257 dir1b,
258 dir2root,
258 dir2root,
259 dir2,
259 dir2,
260 rev1a,
260 rev1a,
261 rev1b,
261 rev1b,
262 rev2,
262 rev2,
263 ):
263 ):
264 # Note that we need to sort the list of files because it was
264 # Note that we need to sort the list of files because it was
265 # built in an "unstable" way and it's annoying to get files in a
265 # built in an "unstable" way and it's annoying to get files in a
266 # random order, especially when "confirm" mode is enabled.
266 # random order, especially when "confirm" mode is enabled.
267 waitprocs = []
267 waitprocs = []
268 totalfiles = len(commonfiles)
268 totalfiles = len(commonfiles)
269 for idx, commonfile in enumerate(sorted(commonfiles)):
269 for idx, commonfile in enumerate(sorted(commonfiles)):
270 path1a = os.path.join(tmproot, dir1a, commonfile)
270 path1a = os.path.join(tmproot, dir1a, commonfile)
271 label1a = commonfile + rev1a
271 label1a = commonfile + rev1a
272 if not os.path.isfile(path1a):
272 if not os.path.isfile(path1a):
273 path1a = pycompat.osdevnull
273 path1a = pycompat.osdevnull
274
274
275 path1b = b''
275 path1b = b''
276 label1b = b''
276 label1b = b''
277 if do3way:
277 if do3way:
278 path1b = os.path.join(tmproot, dir1b, commonfile)
278 path1b = os.path.join(tmproot, dir1b, commonfile)
279 label1b = commonfile + rev1b
279 label1b = commonfile + rev1b
280 if not os.path.isfile(path1b):
280 if not os.path.isfile(path1b):
281 path1b = pycompat.osdevnull
281 path1b = pycompat.osdevnull
282
282
283 path2 = os.path.join(dir2root, dir2, commonfile)
283 path2 = os.path.join(dir2root, dir2, commonfile)
284 label2 = commonfile + rev2
284 label2 = commonfile + rev2
285
285
286 if confirm:
286 if confirm:
287 # Prompt before showing this diff
287 # Prompt before showing this diff
288 difffiles = _(b'diff %s (%d of %d)') % (
288 difffiles = _(b'diff %s (%d of %d)') % (
289 commonfile,
289 commonfile,
290 idx + 1,
290 idx + 1,
291 totalfiles,
291 totalfiles,
292 )
292 )
293 responses = _(
293 responses = _(
294 b'[Yns?]'
294 b'[Yns?]'
295 b'$$ &Yes, show diff'
295 b'$$ &Yes, show diff'
296 b'$$ &No, skip this diff'
296 b'$$ &No, skip this diff'
297 b'$$ &Skip remaining diffs'
297 b'$$ &Skip remaining diffs'
298 b'$$ &? (display help)'
298 b'$$ &? (display help)'
299 )
299 )
300 r = ui.promptchoice(b'%s %s' % (difffiles, responses))
300 r = ui.promptchoice(b'%s %s' % (difffiles, responses))
301 if r == 3: # ?
301 if r == 3: # ?
302 while r == 3:
302 while r == 3:
303 for c, t in ui.extractchoices(responses)[1]:
303 for c, t in ui.extractchoices(responses)[1]:
304 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
304 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
305 r = ui.promptchoice(b'%s %s' % (difffiles, responses))
305 r = ui.promptchoice(b'%s %s' % (difffiles, responses))
306 if r == 0: # yes
306 if r == 0: # yes
307 pass
307 pass
308 elif r == 1: # no
308 elif r == 1: # no
309 continue
309 continue
310 elif r == 2: # skip
310 elif r == 2: # skip
311 break
311 break
312
312
313 curcmdline = formatcmdline(
313 curcmdline = formatcmdline(
314 cmdline,
314 cmdline,
315 repo_root,
315 repo_root,
316 do3way=do3way,
316 do3way=do3way,
317 parent1=path1a,
317 parent1=path1a,
318 plabel1=label1a,
318 plabel1=label1a,
319 parent2=path1b,
319 parent2=path1b,
320 plabel2=label1b,
320 plabel2=label1b,
321 child=path2,
321 child=path2,
322 clabel=label2,
322 clabel=label2,
323 )
323 )
324
324
325 if confirm or not guitool:
325 if confirm or not guitool:
326 # Run the comparison program and wait for it to exit
326 # Run the comparison program and wait for it to exit
327 # before we show the next file.
327 # before we show the next file.
328 # This is because either we need to wait for confirmation
328 # This is because either we need to wait for confirmation
329 # from the user between each invocation, or because, as far
329 # from the user between each invocation, or because, as far
330 # as we know, the tool doesn't have a GUI, in which case
330 # as we know, the tool doesn't have a GUI, in which case
331 # we can't run multiple CLI programs at the same time.
331 # we can't run multiple CLI programs at the same time.
332 ui.debug(
332 ui.debug(
333 b'running %r in %s\n' % (pycompat.bytestr(curcmdline), tmproot)
333 b'running %r in %s\n' % (pycompat.bytestr(curcmdline), tmproot)
334 )
334 )
335 ui.system(curcmdline, cwd=tmproot, blockedtag=b'extdiff')
335 ui.system(curcmdline, cwd=tmproot, blockedtag=b'extdiff')
336 else:
336 else:
337 # Run the comparison program but don't wait, as we're
337 # Run the comparison program but don't wait, as we're
338 # going to rapid-fire each file diff and then wait on
338 # going to rapid-fire each file diff and then wait on
339 # the whole group.
339 # the whole group.
340 ui.debug(
340 ui.debug(
341 b'running %r in %s (backgrounded)\n'
341 b'running %r in %s (backgrounded)\n'
342 % (pycompat.bytestr(curcmdline), tmproot)
342 % (pycompat.bytestr(curcmdline), tmproot)
343 )
343 )
344 proc = _systembackground(curcmdline, cwd=tmproot)
344 proc = _systembackground(curcmdline, cwd=tmproot)
345 waitprocs.append(proc)
345 waitprocs.append(proc)
346
346
347 if waitprocs:
347 if waitprocs:
348 with ui.timeblockedsection(b'extdiff'):
348 with ui.timeblockedsection(b'extdiff'):
349 for proc in waitprocs:
349 for proc in waitprocs:
350 proc.wait()
350 proc.wait()
351
351
352
352
353 def diffpatch(ui, repo, node1a, node2, tmproot, matcher, cmdline, do3way):
353 def diffpatch(ui, repo, node1a, node2, tmproot, matcher, cmdline):
354 template = b'hg-%h.patch'
354 template = b'hg-%h.patch'
355 with formatter.nullformatter(ui, b'extdiff', {}) as fm:
355 with formatter.nullformatter(ui, b'extdiff', {}) as fm:
356 cmdutil.export(
356 cmdutil.export(
357 repo,
357 repo,
358 [repo[node1a].rev(), repo[node2].rev()],
358 [repo[node1a].rev(), repo[node2].rev()],
359 fm,
359 fm,
360 fntemplate=repo.vfs.reljoin(tmproot, template),
360 fntemplate=repo.vfs.reljoin(tmproot, template),
361 match=matcher,
361 match=matcher,
362 )
362 )
363 label1a = cmdutil.makefilename(repo[node1a], template)
363 label1a = cmdutil.makefilename(repo[node1a], template)
364 label2 = cmdutil.makefilename(repo[node2], template)
364 label2 = cmdutil.makefilename(repo[node2], template)
365 dir1a = repo.vfs.reljoin(tmproot, label1a)
365 dir1a = repo.vfs.reljoin(tmproot, label1a)
366 dir2 = repo.vfs.reljoin(tmproot, label2)
366 dir2 = repo.vfs.reljoin(tmproot, label2)
367 dir1b = None
367 dir1b = None
368 label1b = None
368 label1b = None
369 cmdline = formatcmdline(
369 cmdline = formatcmdline(
370 cmdline,
370 cmdline,
371 repo.root,
371 repo.root,
372 do3way=do3way,
373 parent1=dir1a,
372 parent1=dir1a,
374 plabel1=label1a,
373 plabel1=label1a,
375 parent2=dir1b,
374 parent2=dir1b,
376 plabel2=label1b,
375 plabel2=label1b,
377 child=dir2,
376 child=dir2,
377 # no 3way while comparing patches
378 do3way=False,
378 clabel=label2,
379 clabel=label2,
379 )
380 )
380 ui.debug(b'running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot))
381 ui.debug(b'running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot))
381 ui.system(cmdline, cwd=tmproot, blockedtag=b'extdiff')
382 ui.system(cmdline, cwd=tmproot, blockedtag=b'extdiff')
382 return 1
383 return 1
383
384
384
385
385 def diffrevs(
386 def diffrevs(
386 ui,
387 ui,
387 repo,
388 repo,
388 node1a,
389 node1a,
389 node1b,
390 node1b,
390 node2,
391 node2,
391 matcher,
392 matcher,
392 tmproot,
393 tmproot,
393 cmdline,
394 cmdline,
394 do3way,
395 do3way,
395 guitool,
396 guitool,
396 opts,
397 opts,
397 ):
398 ):
398
399
399 subrepos = opts.get(b'subrepos')
400 subrepos = opts.get(b'subrepos')
400 st = repo.status(node1a, node2, matcher, listsubrepos=subrepos)
401 st = repo.status(node1a, node2, matcher, listsubrepos=subrepos)
401 mod_a, add_a, rem_a = set(st.modified), set(st.added), set(st.removed)
402 mod_a, add_a, rem_a = set(st.modified), set(st.added), set(st.removed)
402 if do3way:
403 if do3way:
403 stb = repo.status(node1b, node2, matcher, listsubrepos=subrepos)
404 stb = repo.status(node1b, node2, matcher, listsubrepos=subrepos)
404 mod_b, add_b, rem_b = (
405 mod_b, add_b, rem_b = (
405 set(stb.modified),
406 set(stb.modified),
406 set(stb.added),
407 set(stb.added),
407 set(stb.removed),
408 set(stb.removed),
408 )
409 )
409 else:
410 else:
410 mod_b, add_b, rem_b = set(), set(), set()
411 mod_b, add_b, rem_b = set(), set(), set()
411 modadd = mod_a | add_a | mod_b | add_b
412 modadd = mod_a | add_a | mod_b | add_b
412 common = modadd | rem_a | rem_b
413 common = modadd | rem_a | rem_b
413 if not common:
414 if not common:
414 return 0
415 return 0
415 # Always make a copy of node1a (and node1b, if applicable)
416 # Always make a copy of node1a (and node1b, if applicable)
416 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
417 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
417 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot, subrepos)[0]
418 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot, subrepos)[0]
418 rev1a = b'@%d' % repo[node1a].rev()
419 rev1a = b'@%d' % repo[node1a].rev()
419 if do3way:
420 if do3way:
420 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
421 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
421 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot, subrepos)[0]
422 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot, subrepos)[0]
422 rev1b = b'@%d' % repo[node1b].rev()
423 rev1b = b'@%d' % repo[node1b].rev()
423 else:
424 else:
424 dir1b = None
425 dir1b = None
425 rev1b = b''
426 rev1b = b''
426
427
427 fnsandstat = []
428 fnsandstat = []
428
429
429 # If node2 in not the wc or there is >1 change, copy it
430 # If node2 in not the wc or there is >1 change, copy it
430 dir2root = b''
431 dir2root = b''
431 rev2 = b''
432 rev2 = b''
432 if node2:
433 if node2:
433 dir2 = snapshot(ui, repo, modadd, node2, tmproot, subrepos)[0]
434 dir2 = snapshot(ui, repo, modadd, node2, tmproot, subrepos)[0]
434 rev2 = b'@%d' % repo[node2].rev()
435 rev2 = b'@%d' % repo[node2].rev()
435 elif len(common) > 1:
436 elif len(common) > 1:
436 # we only actually need to get the files to copy back to
437 # we only actually need to get the files to copy back to
437 # the working dir in this case (because the other cases
438 # the working dir in this case (because the other cases
438 # are: diffing 2 revisions or single file -- in which case
439 # are: diffing 2 revisions or single file -- in which case
439 # the file is already directly passed to the diff tool).
440 # the file is already directly passed to the diff tool).
440 dir2, fnsandstat = snapshot(ui, repo, modadd, None, tmproot, subrepos)
441 dir2, fnsandstat = snapshot(ui, repo, modadd, None, tmproot, subrepos)
441 else:
442 else:
442 # This lets the diff tool open the changed file directly
443 # This lets the diff tool open the changed file directly
443 dir2 = b''
444 dir2 = b''
444 dir2root = repo.root
445 dir2root = repo.root
445
446
446 label1a = rev1a
447 label1a = rev1a
447 label1b = rev1b
448 label1b = rev1b
448 label2 = rev2
449 label2 = rev2
449
450
450 # If only one change, diff the files instead of the directories
451 # If only one change, diff the files instead of the directories
451 # Handle bogus modifies correctly by checking if the files exist
452 # Handle bogus modifies correctly by checking if the files exist
452 if len(common) == 1:
453 if len(common) == 1:
453 common_file = util.localpath(common.pop())
454 common_file = util.localpath(common.pop())
454 dir1a = os.path.join(tmproot, dir1a, common_file)
455 dir1a = os.path.join(tmproot, dir1a, common_file)
455 label1a = common_file + rev1a
456 label1a = common_file + rev1a
456 if not os.path.isfile(dir1a):
457 if not os.path.isfile(dir1a):
457 dir1a = pycompat.osdevnull
458 dir1a = pycompat.osdevnull
458 if do3way:
459 if do3way:
459 dir1b = os.path.join(tmproot, dir1b, common_file)
460 dir1b = os.path.join(tmproot, dir1b, common_file)
460 label1b = common_file + rev1b
461 label1b = common_file + rev1b
461 if not os.path.isfile(dir1b):
462 if not os.path.isfile(dir1b):
462 dir1b = pycompat.osdevnull
463 dir1b = pycompat.osdevnull
463 dir2 = os.path.join(dir2root, dir2, common_file)
464 dir2 = os.path.join(dir2root, dir2, common_file)
464 label2 = common_file + rev2
465 label2 = common_file + rev2
465
466
466 if not opts.get(b'per_file'):
467 if not opts.get(b'per_file'):
467 # Run the external tool on the 2 temp directories or the patches
468 # Run the external tool on the 2 temp directories or the patches
468 cmdline = formatcmdline(
469 cmdline = formatcmdline(
469 cmdline,
470 cmdline,
470 repo.root,
471 repo.root,
471 do3way=do3way,
472 do3way=do3way,
472 parent1=dir1a,
473 parent1=dir1a,
473 plabel1=label1a,
474 plabel1=label1a,
474 parent2=dir1b,
475 parent2=dir1b,
475 plabel2=label1b,
476 plabel2=label1b,
476 child=dir2,
477 child=dir2,
477 clabel=label2,
478 clabel=label2,
478 )
479 )
479 ui.debug(b'running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot))
480 ui.debug(b'running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot))
480 ui.system(cmdline, cwd=tmproot, blockedtag=b'extdiff')
481 ui.system(cmdline, cwd=tmproot, blockedtag=b'extdiff')
481 else:
482 else:
482 # Run the external tool once for each pair of files
483 # Run the external tool once for each pair of files
483 _runperfilediff(
484 _runperfilediff(
484 cmdline,
485 cmdline,
485 repo.root,
486 repo.root,
486 ui,
487 ui,
487 guitool=guitool,
488 guitool=guitool,
488 do3way=do3way,
489 do3way=do3way,
489 confirm=opts.get(b'confirm'),
490 confirm=opts.get(b'confirm'),
490 commonfiles=common,
491 commonfiles=common,
491 tmproot=tmproot,
492 tmproot=tmproot,
492 dir1a=dir1a,
493 dir1a=dir1a,
493 dir1b=dir1b,
494 dir1b=dir1b,
494 dir2root=dir2root,
495 dir2root=dir2root,
495 dir2=dir2,
496 dir2=dir2,
496 rev1a=rev1a,
497 rev1a=rev1a,
497 rev1b=rev1b,
498 rev1b=rev1b,
498 rev2=rev2,
499 rev2=rev2,
499 )
500 )
500
501
501 for copy_fn, working_fn, st in fnsandstat:
502 for copy_fn, working_fn, st in fnsandstat:
502 cpstat = os.lstat(copy_fn)
503 cpstat = os.lstat(copy_fn)
503 # Some tools copy the file and attributes, so mtime may not detect
504 # Some tools copy the file and attributes, so mtime may not detect
504 # all changes. A size check will detect more cases, but not all.
505 # all changes. A size check will detect more cases, but not all.
505 # The only certain way to detect every case is to diff all files,
506 # The only certain way to detect every case is to diff all files,
506 # which could be expensive.
507 # which could be expensive.
507 # copyfile() carries over the permission, so the mode check could
508 # copyfile() carries over the permission, so the mode check could
508 # be in an 'elif' branch, but for the case where the file has
509 # be in an 'elif' branch, but for the case where the file has
509 # changed without affecting mtime or size.
510 # changed without affecting mtime or size.
510 if (
511 if (
511 cpstat[stat.ST_MTIME] != st[stat.ST_MTIME]
512 cpstat[stat.ST_MTIME] != st[stat.ST_MTIME]
512 or cpstat.st_size != st.st_size
513 or cpstat.st_size != st.st_size
513 or (cpstat.st_mode & 0o100) != (st.st_mode & 0o100)
514 or (cpstat.st_mode & 0o100) != (st.st_mode & 0o100)
514 ):
515 ):
515 ui.debug(
516 ui.debug(
516 b'file changed while diffing. '
517 b'file changed while diffing. '
517 b'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn)
518 b'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn)
518 )
519 )
519 util.copyfile(copy_fn, working_fn)
520 util.copyfile(copy_fn, working_fn)
520
521
521 return 1
522 return 1
522
523
523
524
524 def dodiff(ui, repo, cmdline, pats, opts, guitool=False):
525 def dodiff(ui, repo, cmdline, pats, opts, guitool=False):
525 '''Do the actual diff:
526 '''Do the actual diff:
526
527
527 - copy to a temp structure if diffing 2 internal revisions
528 - copy to a temp structure if diffing 2 internal revisions
528 - copy to a temp structure if diffing working revision with
529 - copy to a temp structure if diffing working revision with
529 another one and more than 1 file is changed
530 another one and more than 1 file is changed
530 - just invoke the diff for a single file in the working dir
531 - just invoke the diff for a single file in the working dir
531 '''
532 '''
532
533
533 cmdutil.check_at_most_one_arg(opts, b'rev', b'change')
534 cmdutil.check_at_most_one_arg(opts, b'rev', b'change')
534 revs = opts.get(b'rev')
535 revs = opts.get(b'rev')
535 change = opts.get(b'change')
536 change = opts.get(b'change')
536 do3way = b'$parent2' in cmdline
537 do3way = b'$parent2' in cmdline
537
538
538 if change:
539 if change:
539 ctx2 = scmutil.revsingle(repo, change, None)
540 ctx2 = scmutil.revsingle(repo, change, None)
540 ctx1a, ctx1b = ctx2.p1(), ctx2.p2()
541 ctx1a, ctx1b = ctx2.p1(), ctx2.p2()
541 else:
542 else:
542 ctx1a, ctx2 = scmutil.revpair(repo, revs)
543 ctx1a, ctx2 = scmutil.revpair(repo, revs)
543 if not revs:
544 if not revs:
544 ctx1b = repo[None].p2()
545 ctx1b = repo[None].p2()
545 else:
546 else:
546 ctx1b = repo[nullid]
547 ctx1b = repo[nullid]
547
548
548 node1a = ctx1a.node()
549 node1a = ctx1a.node()
549 node1b = ctx1b.node()
550 node1b = ctx1b.node()
550 node2 = ctx2.node()
551 node2 = ctx2.node()
551
552
552 # Disable 3-way merge if there is only one parent
553 # Disable 3-way merge if there is only one parent
553 if do3way:
554 if do3way:
554 if node1b == nullid:
555 if node1b == nullid:
555 do3way = False
556 do3way = False
556
557
557 matcher = scmutil.match(repo[node2], pats, opts)
558 matcher = scmutil.match(repo[node2], pats, opts)
558
559
559 if opts.get(b'patch'):
560 if opts.get(b'patch'):
560 if opts.get(b'subrepos'):
561 if opts.get(b'subrepos'):
561 raise error.Abort(_(b'--patch cannot be used with --subrepos'))
562 raise error.Abort(_(b'--patch cannot be used with --subrepos'))
562 if opts.get(b'per_file'):
563 if opts.get(b'per_file'):
563 raise error.Abort(_(b'--patch cannot be used with --per-file'))
564 raise error.Abort(_(b'--patch cannot be used with --per-file'))
564 if node2 is None:
565 if node2 is None:
565 raise error.Abort(_(b'--patch requires two revisions'))
566 raise error.Abort(_(b'--patch requires two revisions'))
566
567
567 tmproot = pycompat.mkdtemp(prefix=b'extdiff.')
568 tmproot = pycompat.mkdtemp(prefix=b'extdiff.')
568 try:
569 try:
569 if opts.get(b'patch'):
570 if opts.get(b'patch'):
570 return diffpatch(
571 return diffpatch(ui, repo, node1a, node2, tmproot, matcher, cmdline)
571 ui, repo, node1a, node2, tmproot, matcher, cmdline, do3way
572 )
573
572
574 return diffrevs(
573 return diffrevs(
575 ui,
574 ui,
576 repo,
575 repo,
577 node1a,
576 node1a,
578 node1b,
577 node1b,
579 node2,
578 node2,
580 matcher,
579 matcher,
581 tmproot,
580 tmproot,
582 cmdline,
581 cmdline,
583 do3way,
582 do3way,
584 guitool,
583 guitool,
585 opts,
584 opts,
586 )
585 )
587
586
588 finally:
587 finally:
589 ui.note(_(b'cleaning up temp directory\n'))
588 ui.note(_(b'cleaning up temp directory\n'))
590 shutil.rmtree(tmproot)
589 shutil.rmtree(tmproot)
591
590
592
591
593 extdiffopts = (
592 extdiffopts = (
594 [
593 [
595 (
594 (
596 b'o',
595 b'o',
597 b'option',
596 b'option',
598 [],
597 [],
599 _(b'pass option to comparison program'),
598 _(b'pass option to comparison program'),
600 _(b'OPT'),
599 _(b'OPT'),
601 ),
600 ),
602 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
601 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
603 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
602 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
604 (
603 (
605 b'',
604 b'',
606 b'per-file',
605 b'per-file',
607 False,
606 False,
608 _(b'compare each file instead of revision snapshots'),
607 _(b'compare each file instead of revision snapshots'),
609 ),
608 ),
610 (
609 (
611 b'',
610 b'',
612 b'confirm',
611 b'confirm',
613 False,
612 False,
614 _(b'prompt user before each external program invocation'),
613 _(b'prompt user before each external program invocation'),
615 ),
614 ),
616 (b'', b'patch', None, _(b'compare patches for two revisions')),
615 (b'', b'patch', None, _(b'compare patches for two revisions')),
617 ]
616 ]
618 + cmdutil.walkopts
617 + cmdutil.walkopts
619 + cmdutil.subrepoopts
618 + cmdutil.subrepoopts
620 )
619 )
621
620
622
621
623 @command(
622 @command(
624 b'extdiff',
623 b'extdiff',
625 [(b'p', b'program', b'', _(b'comparison program to run'), _(b'CMD')),]
624 [(b'p', b'program', b'', _(b'comparison program to run'), _(b'CMD')),]
626 + extdiffopts,
625 + extdiffopts,
627 _(b'hg extdiff [OPT]... [FILE]...'),
626 _(b'hg extdiff [OPT]... [FILE]...'),
628 helpcategory=command.CATEGORY_FILE_CONTENTS,
627 helpcategory=command.CATEGORY_FILE_CONTENTS,
629 inferrepo=True,
628 inferrepo=True,
630 )
629 )
631 def extdiff(ui, repo, *pats, **opts):
630 def extdiff(ui, repo, *pats, **opts):
632 '''use external program to diff repository (or selected files)
631 '''use external program to diff repository (or selected files)
633
632
634 Show differences between revisions for the specified files, using
633 Show differences between revisions for the specified files, using
635 an external program. The default program used is diff, with
634 an external program. The default program used is diff, with
636 default options "-Npru".
635 default options "-Npru".
637
636
638 To select a different program, use the -p/--program option. The
637 To select a different program, use the -p/--program option. The
639 program will be passed the names of two directories to compare,
638 program will be passed the names of two directories to compare,
640 unless the --per-file option is specified (see below). To pass
639 unless the --per-file option is specified (see below). To pass
641 additional options to the program, use -o/--option. These will be
640 additional options to the program, use -o/--option. These will be
642 passed before the names of the directories or files to compare.
641 passed before the names of the directories or files to compare.
643
642
644 When two revision arguments are given, then changes are shown
643 When two revision arguments are given, then changes are shown
645 between those revisions. If only one revision is specified then
644 between those revisions. If only one revision is specified then
646 that revision is compared to the working directory, and, when no
645 that revision is compared to the working directory, and, when no
647 revisions are specified, the working directory files are compared
646 revisions are specified, the working directory files are compared
648 to its parent.
647 to its parent.
649
648
650 The --per-file option runs the external program repeatedly on each
649 The --per-file option runs the external program repeatedly on each
651 file to diff, instead of once on two directories. By default,
650 file to diff, instead of once on two directories. By default,
652 this happens one by one, where the next file diff is open in the
651 this happens one by one, where the next file diff is open in the
653 external program only once the previous external program (for the
652 external program only once the previous external program (for the
654 previous file diff) has exited. If the external program has a
653 previous file diff) has exited. If the external program has a
655 graphical interface, it can open all the file diffs at once instead
654 graphical interface, it can open all the file diffs at once instead
656 of one by one. See :hg:`help -e extdiff` for information about how
655 of one by one. See :hg:`help -e extdiff` for information about how
657 to tell Mercurial that a given program has a graphical interface.
656 to tell Mercurial that a given program has a graphical interface.
658
657
659 The --confirm option will prompt the user before each invocation of
658 The --confirm option will prompt the user before each invocation of
660 the external program. It is ignored if --per-file isn't specified.
659 the external program. It is ignored if --per-file isn't specified.
661 '''
660 '''
662 opts = pycompat.byteskwargs(opts)
661 opts = pycompat.byteskwargs(opts)
663 program = opts.get(b'program')
662 program = opts.get(b'program')
664 option = opts.get(b'option')
663 option = opts.get(b'option')
665 if not program:
664 if not program:
666 program = b'diff'
665 program = b'diff'
667 option = option or [b'-Npru']
666 option = option or [b'-Npru']
668 cmdline = b' '.join(map(procutil.shellquote, [program] + option))
667 cmdline = b' '.join(map(procutil.shellquote, [program] + option))
669 return dodiff(ui, repo, cmdline, pats, opts)
668 return dodiff(ui, repo, cmdline, pats, opts)
670
669
671
670
672 class savedcmd(object):
671 class savedcmd(object):
673 """use external program to diff repository (or selected files)
672 """use external program to diff repository (or selected files)
674
673
675 Show differences between revisions for the specified files, using
674 Show differences between revisions for the specified files, using
676 the following program::
675 the following program::
677
676
678 %(path)s
677 %(path)s
679
678
680 When two revision arguments are given, then changes are shown
679 When two revision arguments are given, then changes are shown
681 between those revisions. If only one revision is specified then
680 between those revisions. If only one revision is specified then
682 that revision is compared to the working directory, and, when no
681 that revision is compared to the working directory, and, when no
683 revisions are specified, the working directory files are compared
682 revisions are specified, the working directory files are compared
684 to its parent.
683 to its parent.
685 """
684 """
686
685
687 def __init__(self, path, cmdline, isgui):
686 def __init__(self, path, cmdline, isgui):
688 # We can't pass non-ASCII through docstrings (and path is
687 # We can't pass non-ASCII through docstrings (and path is
689 # in an unknown encoding anyway), but avoid double separators on
688 # in an unknown encoding anyway), but avoid double separators on
690 # Windows
689 # Windows
691 docpath = stringutil.escapestr(path).replace(b'\\\\', b'\\')
690 docpath = stringutil.escapestr(path).replace(b'\\\\', b'\\')
692 self.__doc__ %= {'path': pycompat.sysstr(stringutil.uirepr(docpath))}
691 self.__doc__ %= {'path': pycompat.sysstr(stringutil.uirepr(docpath))}
693 self._cmdline = cmdline
692 self._cmdline = cmdline
694 self._isgui = isgui
693 self._isgui = isgui
695
694
696 def __call__(self, ui, repo, *pats, **opts):
695 def __call__(self, ui, repo, *pats, **opts):
697 opts = pycompat.byteskwargs(opts)
696 opts = pycompat.byteskwargs(opts)
698 options = b' '.join(map(procutil.shellquote, opts[b'option']))
697 options = b' '.join(map(procutil.shellquote, opts[b'option']))
699 if options:
698 if options:
700 options = b' ' + options
699 options = b' ' + options
701 return dodiff(
700 return dodiff(
702 ui, repo, self._cmdline + options, pats, opts, guitool=self._isgui
701 ui, repo, self._cmdline + options, pats, opts, guitool=self._isgui
703 )
702 )
704
703
705
704
706 def uisetup(ui):
705 def uisetup(ui):
707 for cmd, path in ui.configitems(b'extdiff'):
706 for cmd, path in ui.configitems(b'extdiff'):
708 path = util.expandpath(path)
707 path = util.expandpath(path)
709 if cmd.startswith(b'cmd.'):
708 if cmd.startswith(b'cmd.'):
710 cmd = cmd[4:]
709 cmd = cmd[4:]
711 if not path:
710 if not path:
712 path = procutil.findexe(cmd)
711 path = procutil.findexe(cmd)
713 if path is None:
712 if path is None:
714 path = filemerge.findexternaltool(ui, cmd) or cmd
713 path = filemerge.findexternaltool(ui, cmd) or cmd
715 diffopts = ui.config(b'extdiff', b'opts.' + cmd)
714 diffopts = ui.config(b'extdiff', b'opts.' + cmd)
716 cmdline = procutil.shellquote(path)
715 cmdline = procutil.shellquote(path)
717 if diffopts:
716 if diffopts:
718 cmdline += b' ' + diffopts
717 cmdline += b' ' + diffopts
719 isgui = ui.configbool(b'extdiff', b'gui.' + cmd)
718 isgui = ui.configbool(b'extdiff', b'gui.' + cmd)
720 elif cmd.startswith(b'opts.') or cmd.startswith(b'gui.'):
719 elif cmd.startswith(b'opts.') or cmd.startswith(b'gui.'):
721 continue
720 continue
722 else:
721 else:
723 if path:
722 if path:
724 # case "cmd = path opts"
723 # case "cmd = path opts"
725 cmdline = path
724 cmdline = path
726 diffopts = len(pycompat.shlexsplit(cmdline)) > 1
725 diffopts = len(pycompat.shlexsplit(cmdline)) > 1
727 else:
726 else:
728 # case "cmd ="
727 # case "cmd ="
729 path = procutil.findexe(cmd)
728 path = procutil.findexe(cmd)
730 if path is None:
729 if path is None:
731 path = filemerge.findexternaltool(ui, cmd) or cmd
730 path = filemerge.findexternaltool(ui, cmd) or cmd
732 cmdline = procutil.shellquote(path)
731 cmdline = procutil.shellquote(path)
733 diffopts = False
732 diffopts = False
734 isgui = ui.configbool(b'extdiff', b'gui.' + cmd)
733 isgui = ui.configbool(b'extdiff', b'gui.' + cmd)
735 # look for diff arguments in [diff-tools] then [merge-tools]
734 # look for diff arguments in [diff-tools] then [merge-tools]
736 if not diffopts:
735 if not diffopts:
737 key = cmd + b'.diffargs'
736 key = cmd + b'.diffargs'
738 for section in (b'diff-tools', b'merge-tools'):
737 for section in (b'diff-tools', b'merge-tools'):
739 args = ui.config(section, key)
738 args = ui.config(section, key)
740 if args:
739 if args:
741 cmdline += b' ' + args
740 cmdline += b' ' + args
742 if isgui is None:
741 if isgui is None:
743 isgui = ui.configbool(section, cmd + b'.gui') or False
742 isgui = ui.configbool(section, cmd + b'.gui') or False
744 break
743 break
745 command(
744 command(
746 cmd,
745 cmd,
747 extdiffopts[:],
746 extdiffopts[:],
748 _(b'hg %s [OPTION]... [FILE]...') % cmd,
747 _(b'hg %s [OPTION]... [FILE]...') % cmd,
749 helpcategory=command.CATEGORY_FILE_CONTENTS,
748 helpcategory=command.CATEGORY_FILE_CONTENTS,
750 inferrepo=True,
749 inferrepo=True,
751 )(savedcmd(path, cmdline, isgui))
750 )(savedcmd(path, cmdline, isgui))
752
751
753
752
754 # tell hggettext to extract docstrings from these functions:
753 # tell hggettext to extract docstrings from these functions:
755 i18nfunctions = [savedcmd]
754 i18nfunctions = [savedcmd]
General Comments 0
You need to be logged in to leave comments. Login now