##// END OF EJS Templates
extdiff: refactor cmdline and gui calculation login in a separate fn...
Pulkit Goyal -
r45970:2d08dcf8 default
parent child Browse files
Show More
@@ -1,761 +1,783 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 dir2,
258 dir2,
259 rev1a,
259 rev1a,
260 rev1b,
260 rev1b,
261 rev2,
261 rev2,
262 ):
262 ):
263 # Note that we need to sort the list of files because it was
263 # Note that we need to sort the list of files because it was
264 # built in an "unstable" way and it's annoying to get files in a
264 # built in an "unstable" way and it's annoying to get files in a
265 # random order, especially when "confirm" mode is enabled.
265 # random order, especially when "confirm" mode is enabled.
266 waitprocs = []
266 waitprocs = []
267 totalfiles = len(commonfiles)
267 totalfiles = len(commonfiles)
268 for idx, commonfile in enumerate(sorted(commonfiles)):
268 for idx, commonfile in enumerate(sorted(commonfiles)):
269 path1a = os.path.join(dir1a, commonfile)
269 path1a = os.path.join(dir1a, commonfile)
270 label1a = commonfile + rev1a
270 label1a = commonfile + rev1a
271 if not os.path.isfile(path1a):
271 if not os.path.isfile(path1a):
272 path1a = pycompat.osdevnull
272 path1a = pycompat.osdevnull
273
273
274 path1b = b''
274 path1b = b''
275 label1b = b''
275 label1b = b''
276 if do3way:
276 if do3way:
277 path1b = os.path.join(dir1b, commonfile)
277 path1b = os.path.join(dir1b, commonfile)
278 label1b = commonfile + rev1b
278 label1b = commonfile + rev1b
279 if not os.path.isfile(path1b):
279 if not os.path.isfile(path1b):
280 path1b = pycompat.osdevnull
280 path1b = pycompat.osdevnull
281
281
282 path2 = os.path.join(dir2, commonfile)
282 path2 = os.path.join(dir2, commonfile)
283 label2 = commonfile + rev2
283 label2 = commonfile + rev2
284
284
285 if confirm:
285 if confirm:
286 # Prompt before showing this diff
286 # Prompt before showing this diff
287 difffiles = _(b'diff %s (%d of %d)') % (
287 difffiles = _(b'diff %s (%d of %d)') % (
288 commonfile,
288 commonfile,
289 idx + 1,
289 idx + 1,
290 totalfiles,
290 totalfiles,
291 )
291 )
292 responses = _(
292 responses = _(
293 b'[Yns?]'
293 b'[Yns?]'
294 b'$$ &Yes, show diff'
294 b'$$ &Yes, show diff'
295 b'$$ &No, skip this diff'
295 b'$$ &No, skip this diff'
296 b'$$ &Skip remaining diffs'
296 b'$$ &Skip remaining diffs'
297 b'$$ &? (display help)'
297 b'$$ &? (display help)'
298 )
298 )
299 r = ui.promptchoice(b'%s %s' % (difffiles, responses))
299 r = ui.promptchoice(b'%s %s' % (difffiles, responses))
300 if r == 3: # ?
300 if r == 3: # ?
301 while r == 3:
301 while r == 3:
302 for c, t in ui.extractchoices(responses)[1]:
302 for c, t in ui.extractchoices(responses)[1]:
303 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
303 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
304 r = ui.promptchoice(b'%s %s' % (difffiles, responses))
304 r = ui.promptchoice(b'%s %s' % (difffiles, responses))
305 if r == 0: # yes
305 if r == 0: # yes
306 pass
306 pass
307 elif r == 1: # no
307 elif r == 1: # no
308 continue
308 continue
309 elif r == 2: # skip
309 elif r == 2: # skip
310 break
310 break
311
311
312 curcmdline = formatcmdline(
312 curcmdline = formatcmdline(
313 cmdline,
313 cmdline,
314 repo_root,
314 repo_root,
315 do3way=do3way,
315 do3way=do3way,
316 parent1=path1a,
316 parent1=path1a,
317 plabel1=label1a,
317 plabel1=label1a,
318 parent2=path1b,
318 parent2=path1b,
319 plabel2=label1b,
319 plabel2=label1b,
320 child=path2,
320 child=path2,
321 clabel=label2,
321 clabel=label2,
322 )
322 )
323
323
324 if confirm or not guitool:
324 if confirm or not guitool:
325 # Run the comparison program and wait for it to exit
325 # Run the comparison program and wait for it to exit
326 # before we show the next file.
326 # before we show the next file.
327 # This is because either we need to wait for confirmation
327 # This is because either we need to wait for confirmation
328 # from the user between each invocation, or because, as far
328 # from the user between each invocation, or because, as far
329 # as we know, the tool doesn't have a GUI, in which case
329 # as we know, the tool doesn't have a GUI, in which case
330 # we can't run multiple CLI programs at the same time.
330 # we can't run multiple CLI programs at the same time.
331 ui.debug(
331 ui.debug(
332 b'running %r in %s\n' % (pycompat.bytestr(curcmdline), tmproot)
332 b'running %r in %s\n' % (pycompat.bytestr(curcmdline), tmproot)
333 )
333 )
334 ui.system(curcmdline, cwd=tmproot, blockedtag=b'extdiff')
334 ui.system(curcmdline, cwd=tmproot, blockedtag=b'extdiff')
335 else:
335 else:
336 # Run the comparison program but don't wait, as we're
336 # Run the comparison program but don't wait, as we're
337 # going to rapid-fire each file diff and then wait on
337 # going to rapid-fire each file diff and then wait on
338 # the whole group.
338 # the whole group.
339 ui.debug(
339 ui.debug(
340 b'running %r in %s (backgrounded)\n'
340 b'running %r in %s (backgrounded)\n'
341 % (pycompat.bytestr(curcmdline), tmproot)
341 % (pycompat.bytestr(curcmdline), tmproot)
342 )
342 )
343 proc = _systembackground(curcmdline, cwd=tmproot)
343 proc = _systembackground(curcmdline, cwd=tmproot)
344 waitprocs.append(proc)
344 waitprocs.append(proc)
345
345
346 if waitprocs:
346 if waitprocs:
347 with ui.timeblockedsection(b'extdiff'):
347 with ui.timeblockedsection(b'extdiff'):
348 for proc in waitprocs:
348 for proc in waitprocs:
349 proc.wait()
349 proc.wait()
350
350
351
351
352 def diffpatch(ui, repo, node1, node2, tmproot, matcher, cmdline):
352 def diffpatch(ui, repo, node1, node2, tmproot, matcher, cmdline):
353 template = b'hg-%h.patch'
353 template = b'hg-%h.patch'
354 # write patches to temporary files
354 # write patches to temporary files
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[node1].rev(), repo[node2].rev()],
358 [repo[node1].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 label1 = cmdutil.makefilename(repo[node1], template)
363 label1 = cmdutil.makefilename(repo[node1], template)
364 label2 = cmdutil.makefilename(repo[node2], template)
364 label2 = cmdutil.makefilename(repo[node2], template)
365 file1 = repo.vfs.reljoin(tmproot, label1)
365 file1 = repo.vfs.reljoin(tmproot, label1)
366 file2 = repo.vfs.reljoin(tmproot, label2)
366 file2 = repo.vfs.reljoin(tmproot, label2)
367 cmdline = formatcmdline(
367 cmdline = formatcmdline(
368 cmdline,
368 cmdline,
369 repo.root,
369 repo.root,
370 # no 3way while comparing patches
370 # no 3way while comparing patches
371 do3way=False,
371 do3way=False,
372 parent1=file1,
372 parent1=file1,
373 plabel1=label1,
373 plabel1=label1,
374 # while comparing patches, there is no second parent
374 # while comparing patches, there is no second parent
375 parent2=None,
375 parent2=None,
376 plabel2=None,
376 plabel2=None,
377 child=file2,
377 child=file2,
378 clabel=label2,
378 clabel=label2,
379 )
379 )
380 ui.debug(b'running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot))
380 ui.debug(b'running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot))
381 ui.system(cmdline, cwd=tmproot, blockedtag=b'extdiff')
381 ui.system(cmdline, cwd=tmproot, blockedtag=b'extdiff')
382 return 1
382 return 1
383
383
384
384
385 def diffrevs(
385 def diffrevs(
386 ui,
386 ui,
387 repo,
387 repo,
388 node1a,
388 node1a,
389 node1b,
389 node1b,
390 node2,
390 node2,
391 matcher,
391 matcher,
392 tmproot,
392 tmproot,
393 cmdline,
393 cmdline,
394 do3way,
394 do3way,
395 guitool,
395 guitool,
396 opts,
396 opts,
397 ):
397 ):
398
398
399 subrepos = opts.get(b'subrepos')
399 subrepos = opts.get(b'subrepos')
400
400
401 # calculate list of files changed between both revs
401 # calculate list of files changed between both revs
402 st = repo.status(node1a, node2, matcher, listsubrepos=subrepos)
402 st = repo.status(node1a, node2, matcher, listsubrepos=subrepos)
403 mod_a, add_a, rem_a = set(st.modified), set(st.added), set(st.removed)
403 mod_a, add_a, rem_a = set(st.modified), set(st.added), set(st.removed)
404 if do3way:
404 if do3way:
405 stb = repo.status(node1b, node2, matcher, listsubrepos=subrepos)
405 stb = repo.status(node1b, node2, matcher, listsubrepos=subrepos)
406 mod_b, add_b, rem_b = (
406 mod_b, add_b, rem_b = (
407 set(stb.modified),
407 set(stb.modified),
408 set(stb.added),
408 set(stb.added),
409 set(stb.removed),
409 set(stb.removed),
410 )
410 )
411 else:
411 else:
412 mod_b, add_b, rem_b = set(), set(), set()
412 mod_b, add_b, rem_b = set(), set(), set()
413 modadd = mod_a | add_a | mod_b | add_b
413 modadd = mod_a | add_a | mod_b | add_b
414 common = modadd | rem_a | rem_b
414 common = modadd | rem_a | rem_b
415 if not common:
415 if not common:
416 return 0
416 return 0
417
417
418 # Always make a copy of node1a (and node1b, if applicable)
418 # Always make a copy of node1a (and node1b, if applicable)
419 # dir1a should contain files which are:
419 # dir1a should contain files which are:
420 # * modified or removed from node1a to node2
420 # * modified or removed from node1a to node2
421 # * modified or added from node1b to node2
421 # * modified or added from node1b to node2
422 # (except file added from node1a to node2 as they were not present in
422 # (except file added from node1a to node2 as they were not present in
423 # node1a)
423 # node1a)
424 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
424 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
425 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot, subrepos)[0]
425 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot, subrepos)[0]
426 rev1a = b'@%d' % repo[node1a].rev()
426 rev1a = b'@%d' % repo[node1a].rev()
427 if do3way:
427 if do3way:
428 # file calculation criteria same as dir1a
428 # file calculation criteria same as dir1a
429 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
429 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
430 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot, subrepos)[0]
430 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot, subrepos)[0]
431 rev1b = b'@%d' % repo[node1b].rev()
431 rev1b = b'@%d' % repo[node1b].rev()
432 else:
432 else:
433 dir1b = None
433 dir1b = None
434 rev1b = b''
434 rev1b = b''
435
435
436 fnsandstat = []
436 fnsandstat = []
437
437
438 # If node2 in not the wc or there is >1 change, copy it
438 # If node2 in not the wc or there is >1 change, copy it
439 dir2root = b''
439 dir2root = b''
440 rev2 = b''
440 rev2 = b''
441 if node2:
441 if node2:
442 dir2 = snapshot(ui, repo, modadd, node2, tmproot, subrepos)[0]
442 dir2 = snapshot(ui, repo, modadd, node2, tmproot, subrepos)[0]
443 rev2 = b'@%d' % repo[node2].rev()
443 rev2 = b'@%d' % repo[node2].rev()
444 elif len(common) > 1:
444 elif len(common) > 1:
445 # we only actually need to get the files to copy back to
445 # we only actually need to get the files to copy back to
446 # the working dir in this case (because the other cases
446 # the working dir in this case (because the other cases
447 # are: diffing 2 revisions or single file -- in which case
447 # are: diffing 2 revisions or single file -- in which case
448 # the file is already directly passed to the diff tool).
448 # the file is already directly passed to the diff tool).
449 dir2, fnsandstat = snapshot(ui, repo, modadd, None, tmproot, subrepos)
449 dir2, fnsandstat = snapshot(ui, repo, modadd, None, tmproot, subrepos)
450 else:
450 else:
451 # This lets the diff tool open the changed file directly
451 # This lets the diff tool open the changed file directly
452 dir2 = b''
452 dir2 = b''
453 dir2root = repo.root
453 dir2root = repo.root
454
454
455 label1a = rev1a
455 label1a = rev1a
456 label1b = rev1b
456 label1b = rev1b
457 label2 = rev2
457 label2 = rev2
458
458
459 if not opts.get(b'per_file'):
459 if not opts.get(b'per_file'):
460 # If only one change, diff the files instead of the directories
460 # If only one change, diff the files instead of the directories
461 # Handle bogus modifies correctly by checking if the files exist
461 # Handle bogus modifies correctly by checking if the files exist
462 if len(common) == 1:
462 if len(common) == 1:
463 common_file = util.localpath(common.pop())
463 common_file = util.localpath(common.pop())
464 dir1a = os.path.join(tmproot, dir1a, common_file)
464 dir1a = os.path.join(tmproot, dir1a, common_file)
465 label1a = common_file + rev1a
465 label1a = common_file + rev1a
466 if not os.path.isfile(dir1a):
466 if not os.path.isfile(dir1a):
467 dir1a = pycompat.osdevnull
467 dir1a = pycompat.osdevnull
468 if do3way:
468 if do3way:
469 dir1b = os.path.join(tmproot, dir1b, common_file)
469 dir1b = os.path.join(tmproot, dir1b, common_file)
470 label1b = common_file + rev1b
470 label1b = common_file + rev1b
471 if not os.path.isfile(dir1b):
471 if not os.path.isfile(dir1b):
472 dir1b = pycompat.osdevnull
472 dir1b = pycompat.osdevnull
473 dir2 = os.path.join(dir2root, dir2, common_file)
473 dir2 = os.path.join(dir2root, dir2, common_file)
474 label2 = common_file + rev2
474 label2 = common_file + rev2
475
475
476 # Run the external tool on the 2 temp directories or the patches
476 # Run the external tool on the 2 temp directories or the patches
477 cmdline = formatcmdline(
477 cmdline = formatcmdline(
478 cmdline,
478 cmdline,
479 repo.root,
479 repo.root,
480 do3way=do3way,
480 do3way=do3way,
481 parent1=dir1a,
481 parent1=dir1a,
482 plabel1=label1a,
482 plabel1=label1a,
483 parent2=dir1b,
483 parent2=dir1b,
484 plabel2=label1b,
484 plabel2=label1b,
485 child=dir2,
485 child=dir2,
486 clabel=label2,
486 clabel=label2,
487 )
487 )
488 ui.debug(b'running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot))
488 ui.debug(b'running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot))
489 ui.system(cmdline, cwd=tmproot, blockedtag=b'extdiff')
489 ui.system(cmdline, cwd=tmproot, blockedtag=b'extdiff')
490 else:
490 else:
491 # Run the external tool once for each pair of files
491 # Run the external tool once for each pair of files
492 _runperfilediff(
492 _runperfilediff(
493 cmdline,
493 cmdline,
494 repo.root,
494 repo.root,
495 ui,
495 ui,
496 guitool=guitool,
496 guitool=guitool,
497 do3way=do3way,
497 do3way=do3way,
498 confirm=opts.get(b'confirm'),
498 confirm=opts.get(b'confirm'),
499 commonfiles=common,
499 commonfiles=common,
500 tmproot=tmproot,
500 tmproot=tmproot,
501 dir1a=os.path.join(tmproot, dir1a),
501 dir1a=os.path.join(tmproot, dir1a),
502 dir1b=os.path.join(tmproot, dir1b) if do3way else None,
502 dir1b=os.path.join(tmproot, dir1b) if do3way else None,
503 dir2=os.path.join(dir2root, dir2),
503 dir2=os.path.join(dir2root, dir2),
504 rev1a=rev1a,
504 rev1a=rev1a,
505 rev1b=rev1b,
505 rev1b=rev1b,
506 rev2=rev2,
506 rev2=rev2,
507 )
507 )
508
508
509 for copy_fn, working_fn, st in fnsandstat:
509 for copy_fn, working_fn, st in fnsandstat:
510 cpstat = os.lstat(copy_fn)
510 cpstat = os.lstat(copy_fn)
511 # Some tools copy the file and attributes, so mtime may not detect
511 # Some tools copy the file and attributes, so mtime may not detect
512 # all changes. A size check will detect more cases, but not all.
512 # all changes. A size check will detect more cases, but not all.
513 # The only certain way to detect every case is to diff all files,
513 # The only certain way to detect every case is to diff all files,
514 # which could be expensive.
514 # which could be expensive.
515 # copyfile() carries over the permission, so the mode check could
515 # copyfile() carries over the permission, so the mode check could
516 # be in an 'elif' branch, but for the case where the file has
516 # be in an 'elif' branch, but for the case where the file has
517 # changed without affecting mtime or size.
517 # changed without affecting mtime or size.
518 if (
518 if (
519 cpstat[stat.ST_MTIME] != st[stat.ST_MTIME]
519 cpstat[stat.ST_MTIME] != st[stat.ST_MTIME]
520 or cpstat.st_size != st.st_size
520 or cpstat.st_size != st.st_size
521 or (cpstat.st_mode & 0o100) != (st.st_mode & 0o100)
521 or (cpstat.st_mode & 0o100) != (st.st_mode & 0o100)
522 ):
522 ):
523 ui.debug(
523 ui.debug(
524 b'file changed while diffing. '
524 b'file changed while diffing. '
525 b'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn)
525 b'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn)
526 )
526 )
527 util.copyfile(copy_fn, working_fn)
527 util.copyfile(copy_fn, working_fn)
528
528
529 return 1
529 return 1
530
530
531
531
532 def dodiff(ui, repo, cmdline, pats, opts, guitool=False):
532 def dodiff(ui, repo, cmdline, pats, opts, guitool=False):
533 '''Do the actual diff:
533 '''Do the actual diff:
534
534
535 - copy to a temp structure if diffing 2 internal revisions
535 - copy to a temp structure if diffing 2 internal revisions
536 - copy to a temp structure if diffing working revision with
536 - copy to a temp structure if diffing working revision with
537 another one and more than 1 file is changed
537 another one and more than 1 file is changed
538 - just invoke the diff for a single file in the working dir
538 - just invoke the diff for a single file in the working dir
539 '''
539 '''
540
540
541 cmdutil.check_at_most_one_arg(opts, b'rev', b'change')
541 cmdutil.check_at_most_one_arg(opts, b'rev', b'change')
542 revs = opts.get(b'rev')
542 revs = opts.get(b'rev')
543 change = opts.get(b'change')
543 change = opts.get(b'change')
544 do3way = b'$parent2' in cmdline
544 do3way = b'$parent2' in cmdline
545
545
546 if change:
546 if change:
547 ctx2 = scmutil.revsingle(repo, change, None)
547 ctx2 = scmutil.revsingle(repo, change, None)
548 ctx1a, ctx1b = ctx2.p1(), ctx2.p2()
548 ctx1a, ctx1b = ctx2.p1(), ctx2.p2()
549 else:
549 else:
550 ctx1a, ctx2 = scmutil.revpair(repo, revs)
550 ctx1a, ctx2 = scmutil.revpair(repo, revs)
551 if not revs:
551 if not revs:
552 ctx1b = repo[None].p2()
552 ctx1b = repo[None].p2()
553 else:
553 else:
554 ctx1b = repo[nullid]
554 ctx1b = repo[nullid]
555
555
556 node1a = ctx1a.node()
556 node1a = ctx1a.node()
557 node1b = ctx1b.node()
557 node1b = ctx1b.node()
558 node2 = ctx2.node()
558 node2 = ctx2.node()
559
559
560 # Disable 3-way merge if there is only one parent
560 # Disable 3-way merge if there is only one parent
561 if do3way:
561 if do3way:
562 if node1b == nullid:
562 if node1b == nullid:
563 do3way = False
563 do3way = False
564
564
565 matcher = scmutil.match(repo[node2], pats, opts)
565 matcher = scmutil.match(repo[node2], pats, opts)
566
566
567 if opts.get(b'patch'):
567 if opts.get(b'patch'):
568 if opts.get(b'subrepos'):
568 if opts.get(b'subrepos'):
569 raise error.Abort(_(b'--patch cannot be used with --subrepos'))
569 raise error.Abort(_(b'--patch cannot be used with --subrepos'))
570 if opts.get(b'per_file'):
570 if opts.get(b'per_file'):
571 raise error.Abort(_(b'--patch cannot be used with --per-file'))
571 raise error.Abort(_(b'--patch cannot be used with --per-file'))
572 if node2 is None:
572 if node2 is None:
573 raise error.Abort(_(b'--patch requires two revisions'))
573 raise error.Abort(_(b'--patch requires two revisions'))
574
574
575 tmproot = pycompat.mkdtemp(prefix=b'extdiff.')
575 tmproot = pycompat.mkdtemp(prefix=b'extdiff.')
576 try:
576 try:
577 if opts.get(b'patch'):
577 if opts.get(b'patch'):
578 return diffpatch(ui, repo, node1a, node2, tmproot, matcher, cmdline)
578 return diffpatch(ui, repo, node1a, node2, tmproot, matcher, cmdline)
579
579
580 return diffrevs(
580 return diffrevs(
581 ui,
581 ui,
582 repo,
582 repo,
583 node1a,
583 node1a,
584 node1b,
584 node1b,
585 node2,
585 node2,
586 matcher,
586 matcher,
587 tmproot,
587 tmproot,
588 cmdline,
588 cmdline,
589 do3way,
589 do3way,
590 guitool,
590 guitool,
591 opts,
591 opts,
592 )
592 )
593
593
594 finally:
594 finally:
595 ui.note(_(b'cleaning up temp directory\n'))
595 ui.note(_(b'cleaning up temp directory\n'))
596 shutil.rmtree(tmproot)
596 shutil.rmtree(tmproot)
597
597
598
598
599 extdiffopts = (
599 extdiffopts = (
600 [
600 [
601 (
601 (
602 b'o',
602 b'o',
603 b'option',
603 b'option',
604 [],
604 [],
605 _(b'pass option to comparison program'),
605 _(b'pass option to comparison program'),
606 _(b'OPT'),
606 _(b'OPT'),
607 ),
607 ),
608 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
608 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
609 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
609 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
610 (
610 (
611 b'',
611 b'',
612 b'per-file',
612 b'per-file',
613 False,
613 False,
614 _(b'compare each file instead of revision snapshots'),
614 _(b'compare each file instead of revision snapshots'),
615 ),
615 ),
616 (
616 (
617 b'',
617 b'',
618 b'confirm',
618 b'confirm',
619 False,
619 False,
620 _(b'prompt user before each external program invocation'),
620 _(b'prompt user before each external program invocation'),
621 ),
621 ),
622 (b'', b'patch', None, _(b'compare patches for two revisions')),
622 (b'', b'patch', None, _(b'compare patches for two revisions')),
623 ]
623 ]
624 + cmdutil.walkopts
624 + cmdutil.walkopts
625 + cmdutil.subrepoopts
625 + cmdutil.subrepoopts
626 )
626 )
627
627
628
628
629 @command(
629 @command(
630 b'extdiff',
630 b'extdiff',
631 [(b'p', b'program', b'', _(b'comparison program to run'), _(b'CMD')),]
631 [(b'p', b'program', b'', _(b'comparison program to run'), _(b'CMD')),]
632 + extdiffopts,
632 + extdiffopts,
633 _(b'hg extdiff [OPT]... [FILE]...'),
633 _(b'hg extdiff [OPT]... [FILE]...'),
634 helpcategory=command.CATEGORY_FILE_CONTENTS,
634 helpcategory=command.CATEGORY_FILE_CONTENTS,
635 inferrepo=True,
635 inferrepo=True,
636 )
636 )
637 def extdiff(ui, repo, *pats, **opts):
637 def extdiff(ui, repo, *pats, **opts):
638 '''use external program to diff repository (or selected files)
638 '''use external program to diff repository (or selected files)
639
639
640 Show differences between revisions for the specified files, using
640 Show differences between revisions for the specified files, using
641 an external program. The default program used is diff, with
641 an external program. The default program used is diff, with
642 default options "-Npru".
642 default options "-Npru".
643
643
644 To select a different program, use the -p/--program option. The
644 To select a different program, use the -p/--program option. The
645 program will be passed the names of two directories to compare,
645 program will be passed the names of two directories to compare,
646 unless the --per-file option is specified (see below). To pass
646 unless the --per-file option is specified (see below). To pass
647 additional options to the program, use -o/--option. These will be
647 additional options to the program, use -o/--option. These will be
648 passed before the names of the directories or files to compare.
648 passed before the names of the directories or files to compare.
649
649
650 When two revision arguments are given, then changes are shown
650 When two revision arguments are given, then changes are shown
651 between those revisions. If only one revision is specified then
651 between those revisions. If only one revision is specified then
652 that revision is compared to the working directory, and, when no
652 that revision is compared to the working directory, and, when no
653 revisions are specified, the working directory files are compared
653 revisions are specified, the working directory files are compared
654 to its parent.
654 to its parent.
655
655
656 The --per-file option runs the external program repeatedly on each
656 The --per-file option runs the external program repeatedly on each
657 file to diff, instead of once on two directories. By default,
657 file to diff, instead of once on two directories. By default,
658 this happens one by one, where the next file diff is open in the
658 this happens one by one, where the next file diff is open in the
659 external program only once the previous external program (for the
659 external program only once the previous external program (for the
660 previous file diff) has exited. If the external program has a
660 previous file diff) has exited. If the external program has a
661 graphical interface, it can open all the file diffs at once instead
661 graphical interface, it can open all the file diffs at once instead
662 of one by one. See :hg:`help -e extdiff` for information about how
662 of one by one. See :hg:`help -e extdiff` for information about how
663 to tell Mercurial that a given program has a graphical interface.
663 to tell Mercurial that a given program has a graphical interface.
664
664
665 The --confirm option will prompt the user before each invocation of
665 The --confirm option will prompt the user before each invocation of
666 the external program. It is ignored if --per-file isn't specified.
666 the external program. It is ignored if --per-file isn't specified.
667 '''
667 '''
668 opts = pycompat.byteskwargs(opts)
668 opts = pycompat.byteskwargs(opts)
669 program = opts.get(b'program')
669 program = opts.get(b'program')
670 option = opts.get(b'option')
670 option = opts.get(b'option')
671 if not program:
671 if not program:
672 program = b'diff'
672 program = b'diff'
673 option = option or [b'-Npru']
673 option = option or [b'-Npru']
674 cmdline = b' '.join(map(procutil.shellquote, [program] + option))
674 cmdline = b' '.join(map(procutil.shellquote, [program] + option))
675 return dodiff(ui, repo, cmdline, pats, opts)
675 return dodiff(ui, repo, cmdline, pats, opts)
676
676
677
677
678 class savedcmd(object):
678 class savedcmd(object):
679 """use external program to diff repository (or selected files)
679 """use external program to diff repository (or selected files)
680
680
681 Show differences between revisions for the specified files, using
681 Show differences between revisions for the specified files, using
682 the following program::
682 the following program::
683
683
684 %(path)s
684 %(path)s
685
685
686 When two revision arguments are given, then changes are shown
686 When two revision arguments are given, then changes are shown
687 between those revisions. If only one revision is specified then
687 between those revisions. If only one revision is specified then
688 that revision is compared to the working directory, and, when no
688 that revision is compared to the working directory, and, when no
689 revisions are specified, the working directory files are compared
689 revisions are specified, the working directory files are compared
690 to its parent.
690 to its parent.
691 """
691 """
692
692
693 def __init__(self, path, cmdline, isgui):
693 def __init__(self, path, cmdline, isgui):
694 # We can't pass non-ASCII through docstrings (and path is
694 # We can't pass non-ASCII through docstrings (and path is
695 # in an unknown encoding anyway), but avoid double separators on
695 # in an unknown encoding anyway), but avoid double separators on
696 # Windows
696 # Windows
697 docpath = stringutil.escapestr(path).replace(b'\\\\', b'\\')
697 docpath = stringutil.escapestr(path).replace(b'\\\\', b'\\')
698 self.__doc__ %= {'path': pycompat.sysstr(stringutil.uirepr(docpath))}
698 self.__doc__ %= {'path': pycompat.sysstr(stringutil.uirepr(docpath))}
699 self._cmdline = cmdline
699 self._cmdline = cmdline
700 self._isgui = isgui
700 self._isgui = isgui
701
701
702 def __call__(self, ui, repo, *pats, **opts):
702 def __call__(self, ui, repo, *pats, **opts):
703 opts = pycompat.byteskwargs(opts)
703 opts = pycompat.byteskwargs(opts)
704 options = b' '.join(map(procutil.shellquote, opts[b'option']))
704 options = b' '.join(map(procutil.shellquote, opts[b'option']))
705 if options:
705 if options:
706 options = b' ' + options
706 options = b' ' + options
707 return dodiff(
707 return dodiff(
708 ui, repo, self._cmdline + options, pats, opts, guitool=self._isgui
708 ui, repo, self._cmdline + options, pats, opts, guitool=self._isgui
709 )
709 )
710
710
711
711
712 def _gettooldetails(ui, cmd, path):
713 """
714 returns following things for a
715 ```
716 [extdiff]
717 <cmd> = <path>
718 ```
719 entry:
720
721 cmd: command/tool name
722 path: path to the tool
723 cmdline: the command which should be run
724 isgui: whether the tool uses GUI or not
725
726 Reads all external tools related configs, whether it be extdiff section,
727 diff-tools or merge-tools section, or its specified in an old format or
728 the latest format.
729 """
730 path = util.expandpath(path)
731 if cmd.startswith(b'cmd.'):
732 cmd = cmd[4:]
733 if not path:
734 path = procutil.findexe(cmd)
735 if path is None:
736 path = filemerge.findexternaltool(ui, cmd) or cmd
737 diffopts = ui.config(b'extdiff', b'opts.' + cmd)
738 cmdline = procutil.shellquote(path)
739 if diffopts:
740 cmdline += b' ' + diffopts
741 isgui = ui.configbool(b'extdiff', b'gui.' + cmd)
742 else:
743 if path:
744 # case "cmd = path opts"
745 cmdline = path
746 diffopts = len(pycompat.shlexsplit(cmdline)) > 1
747 else:
748 # case "cmd ="
749 path = procutil.findexe(cmd)
750 if path is None:
751 path = filemerge.findexternaltool(ui, cmd) or cmd
752 cmdline = procutil.shellquote(path)
753 diffopts = False
754 isgui = ui.configbool(b'extdiff', b'gui.' + cmd)
755 # look for diff arguments in [diff-tools] then [merge-tools]
756 if not diffopts:
757 key = cmd + b'.diffargs'
758 for section in (b'diff-tools', b'merge-tools'):
759 args = ui.config(section, key)
760 if args:
761 cmdline += b' ' + args
762 if isgui is None:
763 isgui = ui.configbool(section, cmd + b'.gui') or False
764 break
765 return cmd, path, cmdline, isgui
766
767
712 def uisetup(ui):
768 def uisetup(ui):
713 for cmd, path in ui.configitems(b'extdiff'):
769 for cmd, path in ui.configitems(b'extdiff'):
714 if cmd.startswith(b'opts.') or cmd.startswith(b'gui.'):
770 if cmd.startswith(b'opts.') or cmd.startswith(b'gui.'):
715 continue
771 continue
716 path = util.expandpath(path)
772 cmd, path, cmdline, isgui = _gettooldetails(ui, cmd, path)
717 if cmd.startswith(b'cmd.'):
718 cmd = cmd[4:]
719 if not path:
720 path = procutil.findexe(cmd)
721 if path is None:
722 path = filemerge.findexternaltool(ui, cmd) or cmd
723 diffopts = ui.config(b'extdiff', b'opts.' + cmd)
724 cmdline = procutil.shellquote(path)
725 if diffopts:
726 cmdline += b' ' + diffopts
727 isgui = ui.configbool(b'extdiff', b'gui.' + cmd)
728 else:
729 if path:
730 # case "cmd = path opts"
731 cmdline = path
732 diffopts = len(pycompat.shlexsplit(cmdline)) > 1
733 else:
734 # case "cmd ="
735 path = procutil.findexe(cmd)
736 if path is None:
737 path = filemerge.findexternaltool(ui, cmd) or cmd
738 cmdline = procutil.shellquote(path)
739 diffopts = False
740 isgui = ui.configbool(b'extdiff', b'gui.' + cmd)
741 # look for diff arguments in [diff-tools] then [merge-tools]
742 if not diffopts:
743 key = cmd + b'.diffargs'
744 for section in (b'diff-tools', b'merge-tools'):
745 args = ui.config(section, key)
746 if args:
747 cmdline += b' ' + args
748 if isgui is None:
749 isgui = ui.configbool(section, cmd + b'.gui') or False
750 break
751 command(
773 command(
752 cmd,
774 cmd,
753 extdiffopts[:],
775 extdiffopts[:],
754 _(b'hg %s [OPTION]... [FILE]...') % cmd,
776 _(b'hg %s [OPTION]... [FILE]...') % cmd,
755 helpcategory=command.CATEGORY_FILE_CONTENTS,
777 helpcategory=command.CATEGORY_FILE_CONTENTS,
756 inferrepo=True,
778 inferrepo=True,
757 )(savedcmd(path, cmdline, isgui))
779 )(savedcmd(path, cmdline, isgui))
758
780
759
781
760 # tell hggettext to extract docstrings from these functions:
782 # tell hggettext to extract docstrings from these functions:
761 i18nfunctions = [savedcmd]
783 i18nfunctions = [savedcmd]
General Comments 0
You need to be logged in to leave comments. Login now