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