##// END OF EJS Templates
extdiff: pass full paths of `dir1a` and `dir1b` to `_runperfilediff()`...
Pulkit Goyal -
r45957:e7c57354 default
parent child Browse files
Show More
@@ -1,763 +1,763 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(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(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, node1, node2, tmproot, matcher, cmdline):
353 def diffpatch(ui, repo, node1, node2, tmproot, matcher, cmdline):
354 template = b'hg-%h.patch'
354 template = b'hg-%h.patch'
355 # write patches to temporary files
355 # write patches to temporary files
356 with formatter.nullformatter(ui, b'extdiff', {}) as fm:
356 with formatter.nullformatter(ui, b'extdiff', {}) as fm:
357 cmdutil.export(
357 cmdutil.export(
358 repo,
358 repo,
359 [repo[node1].rev(), repo[node2].rev()],
359 [repo[node1].rev(), repo[node2].rev()],
360 fm,
360 fm,
361 fntemplate=repo.vfs.reljoin(tmproot, template),
361 fntemplate=repo.vfs.reljoin(tmproot, template),
362 match=matcher,
362 match=matcher,
363 )
363 )
364 label1 = cmdutil.makefilename(repo[node1], template)
364 label1 = cmdutil.makefilename(repo[node1], template)
365 label2 = cmdutil.makefilename(repo[node2], template)
365 label2 = cmdutil.makefilename(repo[node2], template)
366 file1 = repo.vfs.reljoin(tmproot, label1)
366 file1 = repo.vfs.reljoin(tmproot, label1)
367 file2 = repo.vfs.reljoin(tmproot, label2)
367 file2 = repo.vfs.reljoin(tmproot, label2)
368 cmdline = formatcmdline(
368 cmdline = formatcmdline(
369 cmdline,
369 cmdline,
370 repo.root,
370 repo.root,
371 # no 3way while comparing patches
371 # no 3way while comparing patches
372 do3way=False,
372 do3way=False,
373 parent1=file1,
373 parent1=file1,
374 plabel1=label1,
374 plabel1=label1,
375 # while comparing patches, there is no second parent
375 # while comparing patches, there is no second parent
376 parent2=None,
376 parent2=None,
377 plabel2=None,
377 plabel2=None,
378 child=file2,
378 child=file2,
379 clabel=label2,
379 clabel=label2,
380 )
380 )
381 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))
382 ui.system(cmdline, cwd=tmproot, blockedtag=b'extdiff')
382 ui.system(cmdline, cwd=tmproot, blockedtag=b'extdiff')
383 return 1
383 return 1
384
384
385
385
386 def diffrevs(
386 def diffrevs(
387 ui,
387 ui,
388 repo,
388 repo,
389 node1a,
389 node1a,
390 node1b,
390 node1b,
391 node2,
391 node2,
392 matcher,
392 matcher,
393 tmproot,
393 tmproot,
394 cmdline,
394 cmdline,
395 do3way,
395 do3way,
396 guitool,
396 guitool,
397 opts,
397 opts,
398 ):
398 ):
399
399
400 subrepos = opts.get(b'subrepos')
400 subrepos = opts.get(b'subrepos')
401
401
402 # calculate list of files changed between both revs
402 # calculate list of files changed between both revs
403 st = repo.status(node1a, node2, matcher, listsubrepos=subrepos)
403 st = repo.status(node1a, node2, matcher, listsubrepos=subrepos)
404 mod_a, add_a, rem_a = set(st.modified), set(st.added), set(st.removed)
404 mod_a, add_a, rem_a = set(st.modified), set(st.added), set(st.removed)
405 if do3way:
405 if do3way:
406 stb = repo.status(node1b, node2, matcher, listsubrepos=subrepos)
406 stb = repo.status(node1b, node2, matcher, listsubrepos=subrepos)
407 mod_b, add_b, rem_b = (
407 mod_b, add_b, rem_b = (
408 set(stb.modified),
408 set(stb.modified),
409 set(stb.added),
409 set(stb.added),
410 set(stb.removed),
410 set(stb.removed),
411 )
411 )
412 else:
412 else:
413 mod_b, add_b, rem_b = set(), set(), set()
413 mod_b, add_b, rem_b = set(), set(), set()
414 modadd = mod_a | add_a | mod_b | add_b
414 modadd = mod_a | add_a | mod_b | add_b
415 common = modadd | rem_a | rem_b
415 common = modadd | rem_a | rem_b
416 if not common:
416 if not common:
417 return 0
417 return 0
418
418
419 # Always make a copy of node1a (and node1b, if applicable)
419 # Always make a copy of node1a (and node1b, if applicable)
420 # dir1a should contain files which are:
420 # dir1a should contain files which are:
421 # * modified or removed from node1a to node2
421 # * modified or removed from node1a to node2
422 # * modified or added from node1b to node2
422 # * modified or added from node1b to node2
423 # (except file added from node1a to node2 as they were not present in
423 # (except file added from node1a to node2 as they were not present in
424 # node1a)
424 # node1a)
425 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
425 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
426 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot, subrepos)[0]
426 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot, subrepos)[0]
427 rev1a = b'@%d' % repo[node1a].rev()
427 rev1a = b'@%d' % repo[node1a].rev()
428 if do3way:
428 if do3way:
429 # file calculation criteria same as dir1a
429 # file calculation criteria same as dir1a
430 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
430 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
431 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot, subrepos)[0]
431 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot, subrepos)[0]
432 rev1b = b'@%d' % repo[node1b].rev()
432 rev1b = b'@%d' % repo[node1b].rev()
433 else:
433 else:
434 dir1b = None
434 dir1b = None
435 rev1b = b''
435 rev1b = b''
436
436
437 fnsandstat = []
437 fnsandstat = []
438
438
439 # If node2 in not the wc or there is >1 change, copy it
439 # If node2 in not the wc or there is >1 change, copy it
440 dir2root = b''
440 dir2root = b''
441 rev2 = b''
441 rev2 = b''
442 if node2:
442 if node2:
443 dir2 = snapshot(ui, repo, modadd, node2, tmproot, subrepos)[0]
443 dir2 = snapshot(ui, repo, modadd, node2, tmproot, subrepos)[0]
444 rev2 = b'@%d' % repo[node2].rev()
444 rev2 = b'@%d' % repo[node2].rev()
445 elif len(common) > 1:
445 elif len(common) > 1:
446 # we only actually need to get the files to copy back to
446 # we only actually need to get the files to copy back to
447 # the working dir in this case (because the other cases
447 # the working dir in this case (because the other cases
448 # are: diffing 2 revisions or single file -- in which case
448 # are: diffing 2 revisions or single file -- in which case
449 # the file is already directly passed to the diff tool).
449 # the file is already directly passed to the diff tool).
450 dir2, fnsandstat = snapshot(ui, repo, modadd, None, tmproot, subrepos)
450 dir2, fnsandstat = snapshot(ui, repo, modadd, None, tmproot, subrepos)
451 else:
451 else:
452 # This lets the diff tool open the changed file directly
452 # This lets the diff tool open the changed file directly
453 dir2 = b''
453 dir2 = b''
454 dir2root = repo.root
454 dir2root = repo.root
455
455
456 label1a = rev1a
456 label1a = rev1a
457 label1b = rev1b
457 label1b = rev1b
458 label2 = rev2
458 label2 = rev2
459
459
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 if not opts.get(b'per_file'):
476 if not opts.get(b'per_file'):
477 # Run the external tool on the 2 temp directories or the patches
477 # Run the external tool on the 2 temp directories or the patches
478 cmdline = formatcmdline(
478 cmdline = formatcmdline(
479 cmdline,
479 cmdline,
480 repo.root,
480 repo.root,
481 do3way=do3way,
481 do3way=do3way,
482 parent1=dir1a,
482 parent1=dir1a,
483 plabel1=label1a,
483 plabel1=label1a,
484 parent2=dir1b,
484 parent2=dir1b,
485 plabel2=label1b,
485 plabel2=label1b,
486 child=dir2,
486 child=dir2,
487 clabel=label2,
487 clabel=label2,
488 )
488 )
489 ui.debug(b'running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot))
489 ui.debug(b'running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot))
490 ui.system(cmdline, cwd=tmproot, blockedtag=b'extdiff')
490 ui.system(cmdline, cwd=tmproot, blockedtag=b'extdiff')
491 else:
491 else:
492 # Run the external tool once for each pair of files
492 # Run the external tool once for each pair of files
493 _runperfilediff(
493 _runperfilediff(
494 cmdline,
494 cmdline,
495 repo.root,
495 repo.root,
496 ui,
496 ui,
497 guitool=guitool,
497 guitool=guitool,
498 do3way=do3way,
498 do3way=do3way,
499 confirm=opts.get(b'confirm'),
499 confirm=opts.get(b'confirm'),
500 commonfiles=common,
500 commonfiles=common,
501 tmproot=tmproot,
501 tmproot=tmproot,
502 dir1a=dir1a,
502 dir1a=os.path.join(tmproot, dir1a),
503 dir1b=dir1b,
503 dir1b=os.path.join(tmproot, dir1b) if do3way else None,
504 dir2root=dir2root,
504 dir2root=dir2root,
505 dir2=dir2,
505 dir2=dir2,
506 rev1a=rev1a,
506 rev1a=rev1a,
507 rev1b=rev1b,
507 rev1b=rev1b,
508 rev2=rev2,
508 rev2=rev2,
509 )
509 )
510
510
511 for copy_fn, working_fn, st in fnsandstat:
511 for copy_fn, working_fn, st in fnsandstat:
512 cpstat = os.lstat(copy_fn)
512 cpstat = os.lstat(copy_fn)
513 # Some tools copy the file and attributes, so mtime may not detect
513 # Some tools copy the file and attributes, so mtime may not detect
514 # all changes. A size check will detect more cases, but not all.
514 # all changes. A size check will detect more cases, but not all.
515 # The only certain way to detect every case is to diff all files,
515 # The only certain way to detect every case is to diff all files,
516 # which could be expensive.
516 # which could be expensive.
517 # copyfile() carries over the permission, so the mode check could
517 # copyfile() carries over the permission, so the mode check could
518 # be in an 'elif' branch, but for the case where the file has
518 # be in an 'elif' branch, but for the case where the file has
519 # changed without affecting mtime or size.
519 # changed without affecting mtime or size.
520 if (
520 if (
521 cpstat[stat.ST_MTIME] != st[stat.ST_MTIME]
521 cpstat[stat.ST_MTIME] != st[stat.ST_MTIME]
522 or cpstat.st_size != st.st_size
522 or cpstat.st_size != st.st_size
523 or (cpstat.st_mode & 0o100) != (st.st_mode & 0o100)
523 or (cpstat.st_mode & 0o100) != (st.st_mode & 0o100)
524 ):
524 ):
525 ui.debug(
525 ui.debug(
526 b'file changed while diffing. '
526 b'file changed while diffing. '
527 b'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn)
527 b'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn)
528 )
528 )
529 util.copyfile(copy_fn, working_fn)
529 util.copyfile(copy_fn, working_fn)
530
530
531 return 1
531 return 1
532
532
533
533
534 def dodiff(ui, repo, cmdline, pats, opts, guitool=False):
534 def dodiff(ui, repo, cmdline, pats, opts, guitool=False):
535 '''Do the actual diff:
535 '''Do the actual diff:
536
536
537 - copy to a temp structure if diffing 2 internal revisions
537 - copy to a temp structure if diffing 2 internal revisions
538 - copy to a temp structure if diffing working revision with
538 - copy to a temp structure if diffing working revision with
539 another one and more than 1 file is changed
539 another one and more than 1 file is changed
540 - just invoke the diff for a single file in the working dir
540 - just invoke the diff for a single file in the working dir
541 '''
541 '''
542
542
543 cmdutil.check_at_most_one_arg(opts, b'rev', b'change')
543 cmdutil.check_at_most_one_arg(opts, b'rev', b'change')
544 revs = opts.get(b'rev')
544 revs = opts.get(b'rev')
545 change = opts.get(b'change')
545 change = opts.get(b'change')
546 do3way = b'$parent2' in cmdline
546 do3way = b'$parent2' in cmdline
547
547
548 if change:
548 if change:
549 ctx2 = scmutil.revsingle(repo, change, None)
549 ctx2 = scmutil.revsingle(repo, change, None)
550 ctx1a, ctx1b = ctx2.p1(), ctx2.p2()
550 ctx1a, ctx1b = ctx2.p1(), ctx2.p2()
551 else:
551 else:
552 ctx1a, ctx2 = scmutil.revpair(repo, revs)
552 ctx1a, ctx2 = scmutil.revpair(repo, revs)
553 if not revs:
553 if not revs:
554 ctx1b = repo[None].p2()
554 ctx1b = repo[None].p2()
555 else:
555 else:
556 ctx1b = repo[nullid]
556 ctx1b = repo[nullid]
557
557
558 node1a = ctx1a.node()
558 node1a = ctx1a.node()
559 node1b = ctx1b.node()
559 node1b = ctx1b.node()
560 node2 = ctx2.node()
560 node2 = ctx2.node()
561
561
562 # Disable 3-way merge if there is only one parent
562 # Disable 3-way merge if there is only one parent
563 if do3way:
563 if do3way:
564 if node1b == nullid:
564 if node1b == nullid:
565 do3way = False
565 do3way = False
566
566
567 matcher = scmutil.match(repo[node2], pats, opts)
567 matcher = scmutil.match(repo[node2], pats, opts)
568
568
569 if opts.get(b'patch'):
569 if opts.get(b'patch'):
570 if opts.get(b'subrepos'):
570 if opts.get(b'subrepos'):
571 raise error.Abort(_(b'--patch cannot be used with --subrepos'))
571 raise error.Abort(_(b'--patch cannot be used with --subrepos'))
572 if opts.get(b'per_file'):
572 if opts.get(b'per_file'):
573 raise error.Abort(_(b'--patch cannot be used with --per-file'))
573 raise error.Abort(_(b'--patch cannot be used with --per-file'))
574 if node2 is None:
574 if node2 is None:
575 raise error.Abort(_(b'--patch requires two revisions'))
575 raise error.Abort(_(b'--patch requires two revisions'))
576
576
577 tmproot = pycompat.mkdtemp(prefix=b'extdiff.')
577 tmproot = pycompat.mkdtemp(prefix=b'extdiff.')
578 try:
578 try:
579 if opts.get(b'patch'):
579 if opts.get(b'patch'):
580 return diffpatch(ui, repo, node1a, node2, tmproot, matcher, cmdline)
580 return diffpatch(ui, repo, node1a, node2, tmproot, matcher, cmdline)
581
581
582 return diffrevs(
582 return diffrevs(
583 ui,
583 ui,
584 repo,
584 repo,
585 node1a,
585 node1a,
586 node1b,
586 node1b,
587 node2,
587 node2,
588 matcher,
588 matcher,
589 tmproot,
589 tmproot,
590 cmdline,
590 cmdline,
591 do3way,
591 do3way,
592 guitool,
592 guitool,
593 opts,
593 opts,
594 )
594 )
595
595
596 finally:
596 finally:
597 ui.note(_(b'cleaning up temp directory\n'))
597 ui.note(_(b'cleaning up temp directory\n'))
598 shutil.rmtree(tmproot)
598 shutil.rmtree(tmproot)
599
599
600
600
601 extdiffopts = (
601 extdiffopts = (
602 [
602 [
603 (
603 (
604 b'o',
604 b'o',
605 b'option',
605 b'option',
606 [],
606 [],
607 _(b'pass option to comparison program'),
607 _(b'pass option to comparison program'),
608 _(b'OPT'),
608 _(b'OPT'),
609 ),
609 ),
610 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
610 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
611 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
611 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
612 (
612 (
613 b'',
613 b'',
614 b'per-file',
614 b'per-file',
615 False,
615 False,
616 _(b'compare each file instead of revision snapshots'),
616 _(b'compare each file instead of revision snapshots'),
617 ),
617 ),
618 (
618 (
619 b'',
619 b'',
620 b'confirm',
620 b'confirm',
621 False,
621 False,
622 _(b'prompt user before each external program invocation'),
622 _(b'prompt user before each external program invocation'),
623 ),
623 ),
624 (b'', b'patch', None, _(b'compare patches for two revisions')),
624 (b'', b'patch', None, _(b'compare patches for two revisions')),
625 ]
625 ]
626 + cmdutil.walkopts
626 + cmdutil.walkopts
627 + cmdutil.subrepoopts
627 + cmdutil.subrepoopts
628 )
628 )
629
629
630
630
631 @command(
631 @command(
632 b'extdiff',
632 b'extdiff',
633 [(b'p', b'program', b'', _(b'comparison program to run'), _(b'CMD')),]
633 [(b'p', b'program', b'', _(b'comparison program to run'), _(b'CMD')),]
634 + extdiffopts,
634 + extdiffopts,
635 _(b'hg extdiff [OPT]... [FILE]...'),
635 _(b'hg extdiff [OPT]... [FILE]...'),
636 helpcategory=command.CATEGORY_FILE_CONTENTS,
636 helpcategory=command.CATEGORY_FILE_CONTENTS,
637 inferrepo=True,
637 inferrepo=True,
638 )
638 )
639 def extdiff(ui, repo, *pats, **opts):
639 def extdiff(ui, repo, *pats, **opts):
640 '''use external program to diff repository (or selected files)
640 '''use external program to diff repository (or selected files)
641
641
642 Show differences between revisions for the specified files, using
642 Show differences between revisions for the specified files, using
643 an external program. The default program used is diff, with
643 an external program. The default program used is diff, with
644 default options "-Npru".
644 default options "-Npru".
645
645
646 To select a different program, use the -p/--program option. The
646 To select a different program, use the -p/--program option. The
647 program will be passed the names of two directories to compare,
647 program will be passed the names of two directories to compare,
648 unless the --per-file option is specified (see below). To pass
648 unless the --per-file option is specified (see below). To pass
649 additional options to the program, use -o/--option. These will be
649 additional options to the program, use -o/--option. These will be
650 passed before the names of the directories or files to compare.
650 passed before the names of the directories or files to compare.
651
651
652 When two revision arguments are given, then changes are shown
652 When two revision arguments are given, then changes are shown
653 between those revisions. If only one revision is specified then
653 between those revisions. If only one revision is specified then
654 that revision is compared to the working directory, and, when no
654 that revision is compared to the working directory, and, when no
655 revisions are specified, the working directory files are compared
655 revisions are specified, the working directory files are compared
656 to its parent.
656 to its parent.
657
657
658 The --per-file option runs the external program repeatedly on each
658 The --per-file option runs the external program repeatedly on each
659 file to diff, instead of once on two directories. By default,
659 file to diff, instead of once on two directories. By default,
660 this happens one by one, where the next file diff is open in the
660 this happens one by one, where the next file diff is open in the
661 external program only once the previous external program (for the
661 external program only once the previous external program (for the
662 previous file diff) has exited. If the external program has a
662 previous file diff) has exited. If the external program has a
663 graphical interface, it can open all the file diffs at once instead
663 graphical interface, it can open all the file diffs at once instead
664 of one by one. See :hg:`help -e extdiff` for information about how
664 of one by one. See :hg:`help -e extdiff` for information about how
665 to tell Mercurial that a given program has a graphical interface.
665 to tell Mercurial that a given program has a graphical interface.
666
666
667 The --confirm option will prompt the user before each invocation of
667 The --confirm option will prompt the user before each invocation of
668 the external program. It is ignored if --per-file isn't specified.
668 the external program. It is ignored if --per-file isn't specified.
669 '''
669 '''
670 opts = pycompat.byteskwargs(opts)
670 opts = pycompat.byteskwargs(opts)
671 program = opts.get(b'program')
671 program = opts.get(b'program')
672 option = opts.get(b'option')
672 option = opts.get(b'option')
673 if not program:
673 if not program:
674 program = b'diff'
674 program = b'diff'
675 option = option or [b'-Npru']
675 option = option or [b'-Npru']
676 cmdline = b' '.join(map(procutil.shellquote, [program] + option))
676 cmdline = b' '.join(map(procutil.shellquote, [program] + option))
677 return dodiff(ui, repo, cmdline, pats, opts)
677 return dodiff(ui, repo, cmdline, pats, opts)
678
678
679
679
680 class savedcmd(object):
680 class savedcmd(object):
681 """use external program to diff repository (or selected files)
681 """use external program to diff repository (or selected files)
682
682
683 Show differences between revisions for the specified files, using
683 Show differences between revisions for the specified files, using
684 the following program::
684 the following program::
685
685
686 %(path)s
686 %(path)s
687
687
688 When two revision arguments are given, then changes are shown
688 When two revision arguments are given, then changes are shown
689 between those revisions. If only one revision is specified then
689 between those revisions. If only one revision is specified then
690 that revision is compared to the working directory, and, when no
690 that revision is compared to the working directory, and, when no
691 revisions are specified, the working directory files are compared
691 revisions are specified, the working directory files are compared
692 to its parent.
692 to its parent.
693 """
693 """
694
694
695 def __init__(self, path, cmdline, isgui):
695 def __init__(self, path, cmdline, isgui):
696 # We can't pass non-ASCII through docstrings (and path is
696 # We can't pass non-ASCII through docstrings (and path is
697 # in an unknown encoding anyway), but avoid double separators on
697 # in an unknown encoding anyway), but avoid double separators on
698 # Windows
698 # Windows
699 docpath = stringutil.escapestr(path).replace(b'\\\\', b'\\')
699 docpath = stringutil.escapestr(path).replace(b'\\\\', b'\\')
700 self.__doc__ %= {'path': pycompat.sysstr(stringutil.uirepr(docpath))}
700 self.__doc__ %= {'path': pycompat.sysstr(stringutil.uirepr(docpath))}
701 self._cmdline = cmdline
701 self._cmdline = cmdline
702 self._isgui = isgui
702 self._isgui = isgui
703
703
704 def __call__(self, ui, repo, *pats, **opts):
704 def __call__(self, ui, repo, *pats, **opts):
705 opts = pycompat.byteskwargs(opts)
705 opts = pycompat.byteskwargs(opts)
706 options = b' '.join(map(procutil.shellquote, opts[b'option']))
706 options = b' '.join(map(procutil.shellquote, opts[b'option']))
707 if options:
707 if options:
708 options = b' ' + options
708 options = b' ' + options
709 return dodiff(
709 return dodiff(
710 ui, repo, self._cmdline + options, pats, opts, guitool=self._isgui
710 ui, repo, self._cmdline + options, pats, opts, guitool=self._isgui
711 )
711 )
712
712
713
713
714 def uisetup(ui):
714 def uisetup(ui):
715 for cmd, path in ui.configitems(b'extdiff'):
715 for cmd, path in ui.configitems(b'extdiff'):
716 path = util.expandpath(path)
716 path = util.expandpath(path)
717 if cmd.startswith(b'cmd.'):
717 if cmd.startswith(b'cmd.'):
718 cmd = cmd[4:]
718 cmd = cmd[4:]
719 if not path:
719 if not path:
720 path = procutil.findexe(cmd)
720 path = procutil.findexe(cmd)
721 if path is None:
721 if path is None:
722 path = filemerge.findexternaltool(ui, cmd) or cmd
722 path = filemerge.findexternaltool(ui, cmd) or cmd
723 diffopts = ui.config(b'extdiff', b'opts.' + cmd)
723 diffopts = ui.config(b'extdiff', b'opts.' + cmd)
724 cmdline = procutil.shellquote(path)
724 cmdline = procutil.shellquote(path)
725 if diffopts:
725 if diffopts:
726 cmdline += b' ' + diffopts
726 cmdline += b' ' + diffopts
727 isgui = ui.configbool(b'extdiff', b'gui.' + cmd)
727 isgui = ui.configbool(b'extdiff', b'gui.' + cmd)
728 elif cmd.startswith(b'opts.') or cmd.startswith(b'gui.'):
728 elif cmd.startswith(b'opts.') or cmd.startswith(b'gui.'):
729 continue
729 continue
730 else:
730 else:
731 if path:
731 if path:
732 # case "cmd = path opts"
732 # case "cmd = path opts"
733 cmdline = path
733 cmdline = path
734 diffopts = len(pycompat.shlexsplit(cmdline)) > 1
734 diffopts = len(pycompat.shlexsplit(cmdline)) > 1
735 else:
735 else:
736 # case "cmd ="
736 # case "cmd ="
737 path = procutil.findexe(cmd)
737 path = procutil.findexe(cmd)
738 if path is None:
738 if path is None:
739 path = filemerge.findexternaltool(ui, cmd) or cmd
739 path = filemerge.findexternaltool(ui, cmd) or cmd
740 cmdline = procutil.shellquote(path)
740 cmdline = procutil.shellquote(path)
741 diffopts = False
741 diffopts = False
742 isgui = ui.configbool(b'extdiff', b'gui.' + cmd)
742 isgui = ui.configbool(b'extdiff', b'gui.' + cmd)
743 # look for diff arguments in [diff-tools] then [merge-tools]
743 # look for diff arguments in [diff-tools] then [merge-tools]
744 if not diffopts:
744 if not diffopts:
745 key = cmd + b'.diffargs'
745 key = cmd + b'.diffargs'
746 for section in (b'diff-tools', b'merge-tools'):
746 for section in (b'diff-tools', b'merge-tools'):
747 args = ui.config(section, key)
747 args = ui.config(section, key)
748 if args:
748 if args:
749 cmdline += b' ' + args
749 cmdline += b' ' + args
750 if isgui is None:
750 if isgui is None:
751 isgui = ui.configbool(section, cmd + b'.gui') or False
751 isgui = ui.configbool(section, cmd + b'.gui') or False
752 break
752 break
753 command(
753 command(
754 cmd,
754 cmd,
755 extdiffopts[:],
755 extdiffopts[:],
756 _(b'hg %s [OPTION]... [FILE]...') % cmd,
756 _(b'hg %s [OPTION]... [FILE]...') % cmd,
757 helpcategory=command.CATEGORY_FILE_CONTENTS,
757 helpcategory=command.CATEGORY_FILE_CONTENTS,
758 inferrepo=True,
758 inferrepo=True,
759 )(savedcmd(path, cmdline, isgui))
759 )(savedcmd(path, cmdline, isgui))
760
760
761
761
762 # tell hggettext to extract docstrings from these functions:
762 # tell hggettext to extract docstrings from these functions:
763 i18nfunctions = [savedcmd]
763 i18nfunctions = [savedcmd]
General Comments 0
You need to be logged in to leave comments. Login now