##// END OF EJS Templates
extdiff: fix crash when showing diff from wdir()...
Martin von Zweigbergk -
r46748:dfe2760d 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 ctx1a,
398 ctx1a,
399 ctx1b,
399 ctx1b,
400 ctx2,
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 = ctx1a.status(ctx2, 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 = ctx1b.status(ctx2, 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 ctx1a (and ctx1b, 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 ctx1a to ctx2
430 # * modified or removed from ctx1a to ctx2
431 # * modified or added from ctx1b to ctx2
431 # * modified or added from ctx1b to ctx2
432 # (except file added from ctx1a to ctx2 as they were not present in
432 # (except file added from ctx1a to ctx2 as they were not present in
433 # ctx1a)
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, ctx1a.node(), tmproot, subrepos)[0]
435 dir1a = snapshot(ui, repo, dir1a_files, ctx1a.node(), tmproot, subrepos)[0]
436 rev1a = b'@%d' % ctx1a.rev()
436 rev1a = b'' if ctx1a.rev() is None else 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(
440 dir1b = snapshot(
441 ui, repo, dir1b_files, ctx1b.node(), tmproot, subrepos
441 ui, repo, dir1b_files, ctx1b.node(), tmproot, subrepos
442 )[0]
442 )[0]
443 rev1b = b'@%d' % ctx1b.rev()
443 rev1b = b'@%d' % ctx1b.rev()
444 else:
444 else:
445 dir1b = None
445 dir1b = None
446 rev1b = b''
446 rev1b = b''
447
447
448 fnsandstat = []
448 fnsandstat = []
449
449
450 # If ctx2 is not the wc or there is >1 change, copy it
450 # If ctx2 is not the wc or there is >1 change, copy it
451 dir2root = b''
451 dir2root = b''
452 rev2 = b''
452 rev2 = b''
453 if ctx2.node() is not None:
453 if ctx2.node() is not None:
454 dir2 = snapshot(ui, repo, modadd, ctx2.node(), tmproot, subrepos)[0]
454 dir2 = snapshot(ui, repo, modadd, ctx2.node(), tmproot, subrepos)[0]
455 rev2 = b'@%d' % ctx2.rev()
455 rev2 = b'@%d' % ctx2.rev()
456 elif len(common) > 1:
456 elif len(common) > 1:
457 # 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
458 # the working dir in this case (because the other cases
458 # the working dir in this case (because the other cases
459 # are: diffing 2 revisions or single file -- in which case
459 # are: diffing 2 revisions or single file -- in which case
460 # the file is already directly passed to the diff tool).
460 # the file is already directly passed to the diff tool).
461 dir2, fnsandstat = snapshot(ui, repo, modadd, None, tmproot, subrepos)
461 dir2, fnsandstat = snapshot(ui, repo, modadd, None, tmproot, subrepos)
462 else:
462 else:
463 # This lets the diff tool open the changed file directly
463 # This lets the diff tool open the changed file directly
464 dir2 = b''
464 dir2 = b''
465 dir2root = repo.root
465 dir2root = repo.root
466
466
467 label1a = rev1a
467 label1a = rev1a
468 label1b = rev1b
468 label1b = rev1b
469 label2 = rev2
469 label2 = rev2
470
470
471 if not opts.get(b'per_file'):
471 if not opts.get(b'per_file'):
472 # If only one change, diff the files instead of the directories
472 # If only one change, diff the files instead of the directories
473 # Handle bogus modifies correctly by checking if the files exist
473 # Handle bogus modifies correctly by checking if the files exist
474 if len(common) == 1:
474 if len(common) == 1:
475 common_file = util.localpath(common.pop())
475 common_file = util.localpath(common.pop())
476 dir1a = os.path.join(tmproot, dir1a, common_file)
476 dir1a = os.path.join(tmproot, dir1a, common_file)
477 label1a = common_file + rev1a
477 label1a = common_file + rev1a
478 if not os.path.isfile(dir1a):
478 if not os.path.isfile(dir1a):
479 dir1a = pycompat.osdevnull
479 dir1a = pycompat.osdevnull
480 if do3way:
480 if do3way:
481 dir1b = os.path.join(tmproot, dir1b, common_file)
481 dir1b = os.path.join(tmproot, dir1b, common_file)
482 label1b = common_file + rev1b
482 label1b = common_file + rev1b
483 if not os.path.isfile(dir1b):
483 if not os.path.isfile(dir1b):
484 dir1b = pycompat.osdevnull
484 dir1b = pycompat.osdevnull
485 dir2 = os.path.join(dir2root, dir2, common_file)
485 dir2 = os.path.join(dir2root, dir2, common_file)
486 label2 = common_file + rev2
486 label2 = common_file + rev2
487
487
488 # 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
489 cmdline = formatcmdline(
489 cmdline = formatcmdline(
490 cmdline,
490 cmdline,
491 repo.root,
491 repo.root,
492 do3way=do3way,
492 do3way=do3way,
493 parent1=dir1a,
493 parent1=dir1a,
494 plabel1=label1a,
494 plabel1=label1a,
495 parent2=dir1b,
495 parent2=dir1b,
496 plabel2=label1b,
496 plabel2=label1b,
497 child=dir2,
497 child=dir2,
498 clabel=label2,
498 clabel=label2,
499 )
499 )
500 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))
501 ui.system(cmdline, cwd=tmproot, blockedtag=b'extdiff')
501 ui.system(cmdline, cwd=tmproot, blockedtag=b'extdiff')
502 else:
502 else:
503 # Run the external tool once for each pair of files
503 # Run the external tool once for each pair of files
504 _runperfilediff(
504 _runperfilediff(
505 cmdline,
505 cmdline,
506 repo.root,
506 repo.root,
507 ui,
507 ui,
508 guitool=guitool,
508 guitool=guitool,
509 do3way=do3way,
509 do3way=do3way,
510 confirm=opts.get(b'confirm'),
510 confirm=opts.get(b'confirm'),
511 commonfiles=common,
511 commonfiles=common,
512 tmproot=tmproot,
512 tmproot=tmproot,
513 dir1a=os.path.join(tmproot, dir1a),
513 dir1a=os.path.join(tmproot, dir1a),
514 dir1b=os.path.join(tmproot, dir1b) if do3way else None,
514 dir1b=os.path.join(tmproot, dir1b) if do3way else None,
515 dir2=os.path.join(dir2root, dir2),
515 dir2=os.path.join(dir2root, dir2),
516 rev1a=rev1a,
516 rev1a=rev1a,
517 rev1b=rev1b,
517 rev1b=rev1b,
518 rev2=rev2,
518 rev2=rev2,
519 )
519 )
520
520
521 for copy_fn, working_fn, st in fnsandstat:
521 for copy_fn, working_fn, st in fnsandstat:
522 cpstat = os.lstat(copy_fn)
522 cpstat = os.lstat(copy_fn)
523 # 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
524 # 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.
525 # 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,
526 # which could be expensive.
526 # which could be expensive.
527 # copyfile() carries over the permission, so the mode check could
527 # copyfile() carries over the permission, so the mode check could
528 # 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
529 # changed without affecting mtime or size.
529 # changed without affecting mtime or size.
530 if (
530 if (
531 cpstat[stat.ST_MTIME] != st[stat.ST_MTIME]
531 cpstat[stat.ST_MTIME] != st[stat.ST_MTIME]
532 or cpstat.st_size != st.st_size
532 or cpstat.st_size != st.st_size
533 or (cpstat.st_mode & 0o100) != (st.st_mode & 0o100)
533 or (cpstat.st_mode & 0o100) != (st.st_mode & 0o100)
534 ):
534 ):
535 ui.debug(
535 ui.debug(
536 b'file changed while diffing. '
536 b'file changed while diffing. '
537 b'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn)
537 b'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn)
538 )
538 )
539 util.copyfile(copy_fn, working_fn)
539 util.copyfile(copy_fn, working_fn)
540
540
541 return 1
541 return 1
542
542
543
543
544 def dodiff(ui, repo, cmdline, pats, opts, guitool=False):
544 def dodiff(ui, repo, cmdline, pats, opts, guitool=False):
545 """Do the actual diff:
545 """Do the actual diff:
546
546
547 - copy to a temp structure if diffing 2 internal revisions
547 - copy to a temp structure if diffing 2 internal revisions
548 - copy to a temp structure if diffing working revision with
548 - copy to a temp structure if diffing working revision with
549 another one and more than 1 file is changed
549 another one and more than 1 file is changed
550 - 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
551 """
551 """
552
552
553 cmdutil.check_at_most_one_arg(opts, b'rev', b'change')
553 cmdutil.check_at_most_one_arg(opts, b'rev', b'change')
554 revs = opts.get(b'rev')
554 revs = opts.get(b'rev')
555 change = opts.get(b'change')
555 change = opts.get(b'change')
556 do3way = b'$parent2' in cmdline
556 do3way = b'$parent2' in cmdline
557
557
558 if change:
558 if change:
559 ctx2 = scmutil.revsingle(repo, change, None)
559 ctx2 = scmutil.revsingle(repo, change, None)
560 ctx1a, ctx1b = ctx2.p1(), ctx2.p2()
560 ctx1a, ctx1b = ctx2.p1(), ctx2.p2()
561 else:
561 else:
562 ctx1a, ctx2 = scmutil.revpair(repo, revs)
562 ctx1a, ctx2 = scmutil.revpair(repo, revs)
563 if not revs:
563 if not revs:
564 ctx1b = repo[None].p2()
564 ctx1b = repo[None].p2()
565 else:
565 else:
566 ctx1b = repo[nullid]
566 ctx1b = repo[nullid]
567
567
568 # Disable 3-way merge if there is only one parent
568 # Disable 3-way merge if there is only one parent
569 if do3way:
569 if do3way:
570 if ctx1b.node() == nullid:
570 if ctx1b.node() == nullid:
571 do3way = False
571 do3way = False
572
572
573 matcher = scmutil.match(ctx2, pats, opts)
573 matcher = scmutil.match(ctx2, pats, opts)
574
574
575 if opts.get(b'patch'):
575 if opts.get(b'patch'):
576 if opts.get(b'subrepos'):
576 if opts.get(b'subrepos'):
577 raise error.Abort(_(b'--patch cannot be used with --subrepos'))
577 raise error.Abort(_(b'--patch cannot be used with --subrepos'))
578 if opts.get(b'per_file'):
578 if opts.get(b'per_file'):
579 raise error.Abort(_(b'--patch cannot be used with --per-file'))
579 raise error.Abort(_(b'--patch cannot be used with --per-file'))
580 if ctx2.node() is None:
580 if ctx2.node() is None:
581 raise error.Abort(_(b'--patch requires two revisions'))
581 raise error.Abort(_(b'--patch requires two revisions'))
582
582
583 tmproot = pycompat.mkdtemp(prefix=b'extdiff.')
583 tmproot = pycompat.mkdtemp(prefix=b'extdiff.')
584 try:
584 try:
585 if opts.get(b'patch'):
585 if opts.get(b'patch'):
586 return diffpatch(
586 return diffpatch(
587 ui, repo, ctx1a.node(), ctx2.node(), tmproot, matcher, cmdline
587 ui, repo, ctx1a.node(), ctx2.node(), tmproot, matcher, cmdline
588 )
588 )
589
589
590 return diffrevs(
590 return diffrevs(
591 ui,
591 ui,
592 repo,
592 repo,
593 ctx1a,
593 ctx1a,
594 ctx1b,
594 ctx1b,
595 ctx2,
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]
@@ -1,545 +1,554 b''
1 $ echo "[extensions]" >> $HGRCPATH
1 $ echo "[extensions]" >> $HGRCPATH
2 $ echo "extdiff=" >> $HGRCPATH
2 $ echo "extdiff=" >> $HGRCPATH
3
3
4 $ hg init a
4 $ hg init a
5 $ cd a
5 $ cd a
6 $ echo a > a
6 $ echo a > a
7 $ echo b > b
7 $ echo b > b
8 $ hg add
8 $ hg add
9 adding a
9 adding a
10 adding b
10 adding b
11
11
12 Should diff cloned directories:
12 Should diff cloned directories:
13
13
14 $ hg extdiff -o -r
14 $ hg extdiff -o -r
15 Only in a: a
15 Only in a: a
16 Only in a: b
16 Only in a: b
17 [1]
17 [1]
18
18
19 $ cat <<EOF >> $HGRCPATH
19 $ cat <<EOF >> $HGRCPATH
20 > [extdiff]
20 > [extdiff]
21 > cmd.falabala = echo
21 > cmd.falabala = echo
22 > opts.falabala = diffing
22 > opts.falabala = diffing
23 > cmd.edspace = echo
23 > cmd.edspace = echo
24 > opts.edspace = "name <user@example.com>"
24 > opts.edspace = "name <user@example.com>"
25 > alabalaf =
25 > alabalaf =
26 > [merge-tools]
26 > [merge-tools]
27 > alabalaf.executable = echo
27 > alabalaf.executable = echo
28 > alabalaf.diffargs = diffing
28 > alabalaf.diffargs = diffing
29 > EOF
29 > EOF
30
30
31 $ hg falabala
31 $ hg falabala
32 diffing a.000000000000 a
32 diffing a.000000000000 a
33 [1]
33 [1]
34
34
35 $ hg help falabala
35 $ hg help falabala
36 hg falabala [OPTION]... [FILE]...
36 hg falabala [OPTION]... [FILE]...
37
37
38 use external program to diff repository (or selected files)
38 use external program to diff repository (or selected files)
39
39
40 Show differences between revisions for the specified files, using the
40 Show differences between revisions for the specified files, using the
41 following program:
41 following program:
42
42
43 'echo'
43 'echo'
44
44
45 When two revision arguments are given, then changes are shown between
45 When two revision arguments are given, then changes are shown between
46 those revisions. If only one revision is specified then that revision is
46 those revisions. If only one revision is specified then that revision is
47 compared to the working directory, and, when no revisions are specified,
47 compared to the working directory, and, when no revisions are specified,
48 the working directory files are compared to its parent.
48 the working directory files are compared to its parent.
49
49
50 options ([+] can be repeated):
50 options ([+] can be repeated):
51
51
52 -o --option OPT [+] pass option to comparison program
52 -o --option OPT [+] pass option to comparison program
53 -r --rev REV [+] revision
53 -r --rev REV [+] revision
54 -c --change REV change made by revision
54 -c --change REV change made by revision
55 --per-file compare each file instead of revision snapshots
55 --per-file compare each file instead of revision snapshots
56 --confirm prompt user before each external program invocation
56 --confirm prompt user before each external program invocation
57 --patch compare patches for two revisions
57 --patch compare patches for two revisions
58 -I --include PATTERN [+] include names matching the given patterns
58 -I --include PATTERN [+] include names matching the given patterns
59 -X --exclude PATTERN [+] exclude names matching the given patterns
59 -X --exclude PATTERN [+] exclude names matching the given patterns
60 -S --subrepos recurse into subrepositories
60 -S --subrepos recurse into subrepositories
61
61
62 (some details hidden, use --verbose to show complete help)
62 (some details hidden, use --verbose to show complete help)
63
63
64 $ hg ci -d '0 0' -mtest1
64 $ hg ci -d '0 0' -mtest1
65
65
66 $ echo b >> a
66 $ echo b >> a
67 $ hg ci -d '1 0' -mtest2
67 $ hg ci -d '1 0' -mtest2
68
68
69 Should diff cloned files directly:
69 Should diff cloned files directly:
70
70
71 $ hg falabala -r 0:1
71 $ hg falabala -r 0:1
72 diffing "*\\extdiff.*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
72 diffing "*\\extdiff.*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
73 diffing */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
73 diffing */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
74 [1]
74 [1]
75
75
76 Can show diff from working copy:
77 $ echo c >> a
78 $ hg falabala -r 'wdir()' -r 1
79 diffing "*\\extdiff.*\\a" "a.34eed99112ab\\a" (glob) (windows !)
80 diffing */extdiff.*/a a.34eed99112ab/a (glob) (no-windows !)
81 [1]
82 $ hg revert a
83 $ rm a.orig
84
76 Specifying an empty revision should abort.
85 Specifying an empty revision should abort.
77
86
78 $ hg extdiff -p diff --patch --rev 'ancestor()' --rev 1
87 $ hg extdiff -p diff --patch --rev 'ancestor()' --rev 1
79 abort: empty revision on one side of range
88 abort: empty revision on one side of range
80 [255]
89 [255]
81
90
82 Test diff during merge:
91 Test diff during merge:
83
92
84 $ hg update -C 0
93 $ hg update -C 0
85 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
94 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
86 $ echo c >> c
95 $ echo c >> c
87 $ hg add c
96 $ hg add c
88 $ hg ci -m "new branch" -d '1 0'
97 $ hg ci -m "new branch" -d '1 0'
89 created new head
98 created new head
90 $ hg merge 1
99 $ hg merge 1
91 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
100 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
92 (branch merge, don't forget to commit)
101 (branch merge, don't forget to commit)
93
102
94 Should diff cloned file against wc file:
103 Should diff cloned file against wc file:
95
104
96 $ hg falabala
105 $ hg falabala
97 diffing "*\\extdiff.*\\a.2a13a4d2da36\\a" "*\\a\\a" (glob) (windows !)
106 diffing "*\\extdiff.*\\a.2a13a4d2da36\\a" "*\\a\\a" (glob) (windows !)
98 diffing */extdiff.*/a.2a13a4d2da36/a */a/a (glob) (no-windows !)
107 diffing */extdiff.*/a.2a13a4d2da36/a */a/a (glob) (no-windows !)
99 [1]
108 [1]
100
109
101
110
102 Test --change option:
111 Test --change option:
103
112
104 $ hg ci -d '2 0' -mtest3
113 $ hg ci -d '2 0' -mtest3
105
114
106 $ hg falabala -c 1
115 $ hg falabala -c 1
107 diffing "*\\extdiff.*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
116 diffing "*\\extdiff.*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
108 diffing */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
117 diffing */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
109 [1]
118 [1]
110
119
111 Check diff are made from the first parent:
120 Check diff are made from the first parent:
112
121
113 $ hg falabala -c 3 || echo "diff-like tools yield a non-zero exit code"
122 $ hg falabala -c 3 || echo "diff-like tools yield a non-zero exit code"
114 diffing "*\\extdiff.*\\a.2a13a4d2da36\\a" "a.46c0e4daeb72\\a" (glob) (windows !)
123 diffing "*\\extdiff.*\\a.2a13a4d2da36\\a" "a.46c0e4daeb72\\a" (glob) (windows !)
115 diffing */extdiff.*/a.2a13a4d2da36/a a.46c0e4daeb72/a (glob) (no-windows !)
124 diffing */extdiff.*/a.2a13a4d2da36/a a.46c0e4daeb72/a (glob) (no-windows !)
116 diff-like tools yield a non-zero exit code
125 diff-like tools yield a non-zero exit code
117
126
118 issue3153: ensure using extdiff with removed subrepos doesn't crash:
127 issue3153: ensure using extdiff with removed subrepos doesn't crash:
119
128
120 $ hg init suba
129 $ hg init suba
121 $ cd suba
130 $ cd suba
122 $ echo suba > suba
131 $ echo suba > suba
123 $ hg add
132 $ hg add
124 adding suba
133 adding suba
125 $ hg ci -m "adding suba file"
134 $ hg ci -m "adding suba file"
126 $ cd ..
135 $ cd ..
127 $ echo suba=suba > .hgsub
136 $ echo suba=suba > .hgsub
128 $ hg add
137 $ hg add
129 adding .hgsub
138 adding .hgsub
130 $ hg ci -Sm "adding subrepo"
139 $ hg ci -Sm "adding subrepo"
131 $ echo > .hgsub
140 $ echo > .hgsub
132 $ hg ci -m "removing subrepo"
141 $ hg ci -m "removing subrepo"
133 $ hg falabala -r 4 -r 5 -S
142 $ hg falabala -r 4 -r 5 -S
134 diffing a.398e36faf9c6 a.5ab95fb166c4
143 diffing a.398e36faf9c6 a.5ab95fb166c4
135 [1]
144 [1]
136
145
137 Test --per-file option:
146 Test --per-file option:
138
147
139 $ hg up -q -C 3
148 $ hg up -q -C 3
140 $ echo a2 > a
149 $ echo a2 > a
141 $ echo b2 > b
150 $ echo b2 > b
142 $ hg ci -d '3 0' -mtestmode1
151 $ hg ci -d '3 0' -mtestmode1
143 created new head
152 created new head
144 $ hg falabala -c 6 --per-file
153 $ hg falabala -c 6 --per-file
145 diffing "*\\extdiff.*\\a.46c0e4daeb72\\a" "a.81906f2b98ac\\a" (glob) (windows !)
154 diffing "*\\extdiff.*\\a.46c0e4daeb72\\a" "a.81906f2b98ac\\a" (glob) (windows !)
146 diffing */extdiff.*/a.46c0e4daeb72/a a.81906f2b98ac/a (glob) (no-windows !)
155 diffing */extdiff.*/a.46c0e4daeb72/a a.81906f2b98ac/a (glob) (no-windows !)
147 diffing "*\\extdiff.*\\a.46c0e4daeb72\\b" "a.81906f2b98ac\\b" (glob) (windows !)
156 diffing "*\\extdiff.*\\a.46c0e4daeb72\\b" "a.81906f2b98ac\\b" (glob) (windows !)
148 diffing */extdiff.*/a.46c0e4daeb72/b a.81906f2b98ac/b (glob) (no-windows !)
157 diffing */extdiff.*/a.46c0e4daeb72/b a.81906f2b98ac/b (glob) (no-windows !)
149 [1]
158 [1]
150
159
151 Test --per-file option for gui tool:
160 Test --per-file option for gui tool:
152
161
153 $ hg --config extdiff.gui.alabalaf=True alabalaf -c 6 --per-file --debug
162 $ hg --config extdiff.gui.alabalaf=True alabalaf -c 6 --per-file --debug
154 diffing */extdiff.*/a.46c0e4daeb72/* a.81906f2b98ac/* (glob)
163 diffing */extdiff.*/a.46c0e4daeb72/* a.81906f2b98ac/* (glob)
155 diffing */extdiff.*/a.46c0e4daeb72/* a.81906f2b98ac/* (glob)
164 diffing */extdiff.*/a.46c0e4daeb72/* a.81906f2b98ac/* (glob)
156 making snapshot of 2 files from rev 46c0e4daeb72
165 making snapshot of 2 files from rev 46c0e4daeb72
157 a
166 a
158 b
167 b
159 making snapshot of 2 files from rev 81906f2b98ac
168 making snapshot of 2 files from rev 81906f2b98ac
160 a
169 a
161 b
170 b
162 running '* diffing * *' in * (backgrounded) (glob)
171 running '* diffing * *' in * (backgrounded) (glob)
163 running '* diffing * *' in * (backgrounded) (glob)
172 running '* diffing * *' in * (backgrounded) (glob)
164 cleaning up temp directory
173 cleaning up temp directory
165 [1]
174 [1]
166
175
167 Test --per-file option for gui tool again:
176 Test --per-file option for gui tool again:
168
177
169 $ hg --config merge-tools.alabalaf.gui=True alabalaf -c 6 --per-file --debug
178 $ hg --config merge-tools.alabalaf.gui=True alabalaf -c 6 --per-file --debug
170 diffing */extdiff.*/a.46c0e4daeb72/* a.81906f2b98ac/* (glob)
179 diffing */extdiff.*/a.46c0e4daeb72/* a.81906f2b98ac/* (glob)
171 diffing */extdiff.*/a.46c0e4daeb72/* a.81906f2b98ac/* (glob)
180 diffing */extdiff.*/a.46c0e4daeb72/* a.81906f2b98ac/* (glob)
172 making snapshot of 2 files from rev 46c0e4daeb72
181 making snapshot of 2 files from rev 46c0e4daeb72
173 a
182 a
174 b
183 b
175 making snapshot of 2 files from rev 81906f2b98ac
184 making snapshot of 2 files from rev 81906f2b98ac
176 a
185 a
177 b
186 b
178 running '* diffing * *' in * (backgrounded) (glob)
187 running '* diffing * *' in * (backgrounded) (glob)
179 running '* diffing * *' in * (backgrounded) (glob)
188 running '* diffing * *' in * (backgrounded) (glob)
180 cleaning up temp directory
189 cleaning up temp directory
181 [1]
190 [1]
182
191
183 Test --per-file and --confirm options:
192 Test --per-file and --confirm options:
184
193
185 $ hg --config ui.interactive=True falabala -c 6 --per-file --confirm <<EOF
194 $ hg --config ui.interactive=True falabala -c 6 --per-file --confirm <<EOF
186 > n
195 > n
187 > y
196 > y
188 > EOF
197 > EOF
189 diff a (1 of 2) [Yns?] n
198 diff a (1 of 2) [Yns?] n
190 diff b (2 of 2) [Yns?] y
199 diff b (2 of 2) [Yns?] y
191 diffing "*\\extdiff.*\\a.46c0e4daeb72\\b" "a.81906f2b98ac\\b" (glob) (windows !)
200 diffing "*\\extdiff.*\\a.46c0e4daeb72\\b" "a.81906f2b98ac\\b" (glob) (windows !)
192 diffing */extdiff.*/a.46c0e4daeb72/b a.81906f2b98ac/b (glob) (no-windows !)
201 diffing */extdiff.*/a.46c0e4daeb72/b a.81906f2b98ac/b (glob) (no-windows !)
193 [1]
202 [1]
194
203
195 Test --per-file and --confirm options with skipping:
204 Test --per-file and --confirm options with skipping:
196
205
197 $ hg --config ui.interactive=True falabala -c 6 --per-file --confirm <<EOF
206 $ hg --config ui.interactive=True falabala -c 6 --per-file --confirm <<EOF
198 > s
207 > s
199 > EOF
208 > EOF
200 diff a (1 of 2) [Yns?] s
209 diff a (1 of 2) [Yns?] s
201 [1]
210 [1]
202
211
203 issue4463: usage of command line configuration without additional quoting
212 issue4463: usage of command line configuration without additional quoting
204
213
205 $ cat <<EOF >> $HGRCPATH
214 $ cat <<EOF >> $HGRCPATH
206 > [extdiff]
215 > [extdiff]
207 > cmd.4463a = echo
216 > cmd.4463a = echo
208 > opts.4463a = a-naked 'single quoted' "double quoted"
217 > opts.4463a = a-naked 'single quoted' "double quoted"
209 > 4463b = echo b-naked 'single quoted' "double quoted"
218 > 4463b = echo b-naked 'single quoted' "double quoted"
210 > echo =
219 > echo =
211 > EOF
220 > EOF
212 $ hg update -q -C 0
221 $ hg update -q -C 0
213 $ echo a >> a
222 $ echo a >> a
214
223
215 $ hg --debug 4463a | grep '^running'
224 $ hg --debug 4463a | grep '^running'
216 running 'echo a-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
225 running 'echo a-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
217 running 'echo a-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob) (no-windows !)
226 running 'echo a-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob) (no-windows !)
218 $ hg --debug 4463b | grep '^running'
227 $ hg --debug 4463b | grep '^running'
219 running 'echo b-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
228 running 'echo b-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
220 running 'echo b-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob) (no-windows !)
229 running 'echo b-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob) (no-windows !)
221 $ hg --debug echo | grep '^running'
230 $ hg --debug echo | grep '^running'
222 running '*echo* "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
231 running '*echo* "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
223 running '*echo */a $TESTTMP/a/a' in */extdiff.* (glob) (no-windows !)
232 running '*echo */a $TESTTMP/a/a' in */extdiff.* (glob) (no-windows !)
224
233
225 (getting options from other than extdiff section)
234 (getting options from other than extdiff section)
226
235
227 $ cat <<EOF >> $HGRCPATH
236 $ cat <<EOF >> $HGRCPATH
228 > [extdiff]
237 > [extdiff]
229 > # using diff-tools diffargs
238 > # using diff-tools diffargs
230 > 4463b2 = echo
239 > 4463b2 = echo
231 > # using merge-tools diffargs
240 > # using merge-tools diffargs
232 > 4463b3 = echo
241 > 4463b3 = echo
233 > # no diffargs
242 > # no diffargs
234 > 4463b4 = echo
243 > 4463b4 = echo
235 > [diff-tools]
244 > [diff-tools]
236 > 4463b2.diffargs = b2-naked 'single quoted' "double quoted"
245 > 4463b2.diffargs = b2-naked 'single quoted' "double quoted"
237 > [merge-tools]
246 > [merge-tools]
238 > 4463b3.diffargs = b3-naked 'single quoted' "double quoted"
247 > 4463b3.diffargs = b3-naked 'single quoted' "double quoted"
239 > EOF
248 > EOF
240
249
241 $ hg --debug 4463b2 | grep '^running'
250 $ hg --debug 4463b2 | grep '^running'
242 running 'echo b2-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
251 running 'echo b2-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
243 running 'echo b2-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob) (no-windows !)
252 running 'echo b2-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob) (no-windows !)
244 $ hg --debug 4463b3 | grep '^running'
253 $ hg --debug 4463b3 | grep '^running'
245 running 'echo b3-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
254 running 'echo b3-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
246 running 'echo b3-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob) (no-windows !)
255 running 'echo b3-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob) (no-windows !)
247 $ hg --debug 4463b4 | grep '^running'
256 $ hg --debug 4463b4 | grep '^running'
248 running 'echo "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
257 running 'echo "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
249 running 'echo */a $TESTTMP/a/a' in */extdiff.* (glob) (no-windows !)
258 running 'echo */a $TESTTMP/a/a' in */extdiff.* (glob) (no-windows !)
250 $ hg --debug 4463b4 --option b4-naked --option 'being quoted' | grep '^running'
259 $ hg --debug 4463b4 --option b4-naked --option 'being quoted' | grep '^running'
251 running 'echo b4-naked "being quoted" "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
260 running 'echo b4-naked "being quoted" "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
252 running "echo b4-naked 'being quoted' */a $TESTTMP/a/a" in */extdiff.* (glob) (no-windows !)
261 running "echo b4-naked 'being quoted' */a $TESTTMP/a/a" in */extdiff.* (glob) (no-windows !)
253 $ hg --debug extdiff -p echo --option echo-naked --option 'being quoted' | grep '^running'
262 $ hg --debug extdiff -p echo --option echo-naked --option 'being quoted' | grep '^running'
254 running 'echo echo-naked "being quoted" "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
263 running 'echo echo-naked "being quoted" "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
255 running "echo echo-naked 'being quoted' */a $TESTTMP/a/a" in */extdiff.* (glob) (no-windows !)
264 running "echo echo-naked 'being quoted' */a $TESTTMP/a/a" in */extdiff.* (glob) (no-windows !)
256
265
257 $ touch 'sp ace'
266 $ touch 'sp ace'
258 $ hg add 'sp ace'
267 $ hg add 'sp ace'
259 $ hg ci -m 'sp ace'
268 $ hg ci -m 'sp ace'
260 created new head
269 created new head
261 $ echo > 'sp ace'
270 $ echo > 'sp ace'
262
271
263 Test pre-72a89cf86fcd backward compatibility with half-baked manual quoting
272 Test pre-72a89cf86fcd backward compatibility with half-baked manual quoting
264
273
265 $ cat <<EOF >> $HGRCPATH
274 $ cat <<EOF >> $HGRCPATH
266 > [extdiff]
275 > [extdiff]
267 > odd =
276 > odd =
268 > [merge-tools]
277 > [merge-tools]
269 > odd.diffargs = --foo='\$clabel' '\$clabel' "--bar=\$clabel" "\$clabel"
278 > odd.diffargs = --foo='\$clabel' '\$clabel' "--bar=\$clabel" "\$clabel"
270 > odd.executable = echo
279 > odd.executable = echo
271 > EOF
280 > EOF
272
281
273 $ hg --debug odd | grep '^running'
282 $ hg --debug odd | grep '^running'
274 running '"*\\echo.exe" --foo="sp ace" "sp ace" --bar="sp ace" "sp ace"' in * (glob) (windows !)
283 running '"*\\echo.exe" --foo="sp ace" "sp ace" --bar="sp ace" "sp ace"' in * (glob) (windows !)
275 running "*/echo --foo='sp ace' 'sp ace' --bar='sp ace' 'sp ace'" in * (glob) (no-windows !)
284 running "*/echo --foo='sp ace' 'sp ace' --bar='sp ace' 'sp ace'" in * (glob) (no-windows !)
276
285
277 Empty argument must be quoted
286 Empty argument must be quoted
278
287
279 $ cat <<EOF >> $HGRCPATH
288 $ cat <<EOF >> $HGRCPATH
280 > [extdiff]
289 > [extdiff]
281 > kdiff3 = echo
290 > kdiff3 = echo
282 > [merge-tools]
291 > [merge-tools]
283 > kdiff3.diffargs=--L1 \$plabel1 --L2 \$clabel \$parent \$child
292 > kdiff3.diffargs=--L1 \$plabel1 --L2 \$clabel \$parent \$child
284 > EOF
293 > EOF
285
294
286 $ hg --debug kdiff3 -r0 | grep '^running'
295 $ hg --debug kdiff3 -r0 | grep '^running'
287 running 'echo --L1 "@0" --L2 "" a.8a5febb7f867 a' in * (glob) (windows !)
296 running 'echo --L1 "@0" --L2 "" a.8a5febb7f867 a' in * (glob) (windows !)
288 running "echo --L1 '@0' --L2 '' a.8a5febb7f867 a" in * (glob) (no-windows !)
297 running "echo --L1 '@0' --L2 '' a.8a5febb7f867 a" in * (glob) (no-windows !)
289
298
290
299
291 Test extdiff of multiple files in tmp dir:
300 Test extdiff of multiple files in tmp dir:
292
301
293 $ hg update -C 0 > /dev/null
302 $ hg update -C 0 > /dev/null
294 $ echo changed > a
303 $ echo changed > a
295 $ echo changed > b
304 $ echo changed > b
296 #if execbit
305 #if execbit
297 $ chmod +x b
306 $ chmod +x b
298 #endif
307 #endif
299
308
300 Diff in working directory, before:
309 Diff in working directory, before:
301
310
302 $ hg diff --git
311 $ hg diff --git
303 diff --git a/a b/a
312 diff --git a/a b/a
304 --- a/a
313 --- a/a
305 +++ b/a
314 +++ b/a
306 @@ -1,1 +1,1 @@
315 @@ -1,1 +1,1 @@
307 -a
316 -a
308 +changed
317 +changed
309 diff --git a/b b/b
318 diff --git a/b b/b
310 old mode 100644 (execbit !)
319 old mode 100644 (execbit !)
311 new mode 100755 (execbit !)
320 new mode 100755 (execbit !)
312 --- a/b
321 --- a/b
313 +++ b/b
322 +++ b/b
314 @@ -1,1 +1,1 @@
323 @@ -1,1 +1,1 @@
315 -b
324 -b
316 +changed
325 +changed
317
326
318
327
319 Edit with extdiff -p:
328 Edit with extdiff -p:
320
329
321 Prepare custom diff/edit tool:
330 Prepare custom diff/edit tool:
322
331
323 $ cat > 'diff tool.py' << EOT
332 $ cat > 'diff tool.py' << EOT
324 > #!$PYTHON
333 > #!$PYTHON
325 > import time
334 > import time
326 > time.sleep(1) # avoid unchanged-timestamp problems
335 > time.sleep(1) # avoid unchanged-timestamp problems
327 > open('a/a', 'ab').write(b'edited\n')
336 > open('a/a', 'ab').write(b'edited\n')
328 > open('a/b', 'ab').write(b'edited\n')
337 > open('a/b', 'ab').write(b'edited\n')
329 > EOT
338 > EOT
330
339
331 #if execbit
340 #if execbit
332 $ chmod +x 'diff tool.py'
341 $ chmod +x 'diff tool.py'
333 #endif
342 #endif
334
343
335 will change to /tmp/extdiff.TMP and populate directories a.TMP and a
344 will change to /tmp/extdiff.TMP and populate directories a.TMP and a
336 and start tool
345 and start tool
337
346
338 #if windows
347 #if windows
339 $ cat > 'diff tool.bat' << EOF
348 $ cat > 'diff tool.bat' << EOF
340 > @"$PYTHON" "`pwd`/diff tool.py"
349 > @"$PYTHON" "`pwd`/diff tool.py"
341 > EOF
350 > EOF
342 $ hg extdiff -p "`pwd`/diff tool.bat"
351 $ hg extdiff -p "`pwd`/diff tool.bat"
343 [1]
352 [1]
344 #else
353 #else
345 $ hg extdiff -p "`pwd`/diff tool.py"
354 $ hg extdiff -p "`pwd`/diff tool.py"
346 [1]
355 [1]
347 #endif
356 #endif
348
357
349 Diff in working directory, after:
358 Diff in working directory, after:
350
359
351 $ hg diff --git
360 $ hg diff --git
352 diff --git a/a b/a
361 diff --git a/a b/a
353 --- a/a
362 --- a/a
354 +++ b/a
363 +++ b/a
355 @@ -1,1 +1,2 @@
364 @@ -1,1 +1,2 @@
356 -a
365 -a
357 +changed
366 +changed
358 +edited
367 +edited
359 diff --git a/b b/b
368 diff --git a/b b/b
360 old mode 100644 (execbit !)
369 old mode 100644 (execbit !)
361 new mode 100755 (execbit !)
370 new mode 100755 (execbit !)
362 --- a/b
371 --- a/b
363 +++ b/b
372 +++ b/b
364 @@ -1,1 +1,2 @@
373 @@ -1,1 +1,2 @@
365 -b
374 -b
366 +changed
375 +changed
367 +edited
376 +edited
368
377
369 Test extdiff with --option:
378 Test extdiff with --option:
370
379
371 $ hg extdiff -p echo -o this -c 1
380 $ hg extdiff -p echo -o this -c 1
372 this "*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
381 this "*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
373 this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
382 this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
374 [1]
383 [1]
375
384
376 $ hg falabala -o this -c 1
385 $ hg falabala -o this -c 1
377 diffing this "*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
386 diffing this "*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
378 diffing this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
387 diffing this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
379 [1]
388 [1]
380
389
381 Test extdiff's handling of options with spaces in them:
390 Test extdiff's handling of options with spaces in them:
382
391
383 $ hg edspace -c 1
392 $ hg edspace -c 1
384 "name <user@example.com>" "*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
393 "name <user@example.com>" "*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
385 name <user@example.com> */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
394 name <user@example.com> */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
386 [1]
395 [1]
387
396
388 $ hg extdiff -p echo -o "name <user@example.com>" -c 1
397 $ hg extdiff -p echo -o "name <user@example.com>" -c 1
389 "name <user@example.com>" "*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
398 "name <user@example.com>" "*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
390 name <user@example.com> */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
399 name <user@example.com> */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
391 [1]
400 [1]
392
401
393 Test with revsets:
402 Test with revsets:
394
403
395 $ hg extdif -p echo -c "rev(1)"
404 $ hg extdif -p echo -c "rev(1)"
396 "*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
405 "*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
397 */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
406 */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
398 [1]
407 [1]
399
408
400 $ hg extdif -p echo -r "0::1"
409 $ hg extdif -p echo -r "0::1"
401 "*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
410 "*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
402 */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
411 */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
403 [1]
412 [1]
404
413
405 Fallback to merge-tools.tool.executable|regkey
414 Fallback to merge-tools.tool.executable|regkey
406 $ mkdir dir
415 $ mkdir dir
407 $ cat > 'dir/tool.sh' << 'EOF'
416 $ cat > 'dir/tool.sh' << 'EOF'
408 > #!/bin/sh
417 > #!/bin/sh
409 > # Mimic a tool that syncs all attrs, including mtime
418 > # Mimic a tool that syncs all attrs, including mtime
410 > cp $1/a $2/a
419 > cp $1/a $2/a
411 > touch -r $1/a $2/a
420 > touch -r $1/a $2/a
412 > chmod +x $2/a
421 > chmod +x $2/a
413 > echo "** custom diff **"
422 > echo "** custom diff **"
414 > EOF
423 > EOF
415 #if execbit
424 #if execbit
416 $ chmod +x dir/tool.sh
425 $ chmod +x dir/tool.sh
417 #endif
426 #endif
418
427
419 Windows can't run *.sh directly, so create a shim executable that can be.
428 Windows can't run *.sh directly, so create a shim executable that can be.
420 Without something executable, the next hg command will try to run `tl` instead
429 Without something executable, the next hg command will try to run `tl` instead
421 of $tool (and fail).
430 of $tool (and fail).
422 #if windows
431 #if windows
423 $ cat > dir/tool.bat <<EOF
432 $ cat > dir/tool.bat <<EOF
424 > @sh -c "`pwd`/dir/tool.sh %1 %2"
433 > @sh -c "`pwd`/dir/tool.sh %1 %2"
425 > EOF
434 > EOF
426 $ tool=`pwd`/dir/tool.bat
435 $ tool=`pwd`/dir/tool.bat
427 #else
436 #else
428 $ tool=`pwd`/dir/tool.sh
437 $ tool=`pwd`/dir/tool.sh
429 #endif
438 #endif
430
439
431 $ cat a
440 $ cat a
432 changed
441 changed
433 edited
442 edited
434 $ hg --debug tl --config extdiff.tl= --config merge-tools.tl.executable=$tool
443 $ hg --debug tl --config extdiff.tl= --config merge-tools.tl.executable=$tool
435 making snapshot of 2 files from rev * (glob)
444 making snapshot of 2 files from rev * (glob)
436 a
445 a
437 b
446 b
438 making snapshot of 2 files from working directory
447 making snapshot of 2 files from working directory
439 a
448 a
440 b
449 b
441 running '$TESTTMP/a/dir/tool.bat a.* a' in */extdiff.* (glob) (windows !)
450 running '$TESTTMP/a/dir/tool.bat a.* a' in */extdiff.* (glob) (windows !)
442 running '$TESTTMP/a/dir/tool.sh a.* a' in */extdiff.* (glob) (no-windows !)
451 running '$TESTTMP/a/dir/tool.sh a.* a' in */extdiff.* (glob) (no-windows !)
443 ** custom diff **
452 ** custom diff **
444 file changed while diffing. Overwriting: $TESTTMP/a/a (src: */extdiff.*/a/a) (glob)
453 file changed while diffing. Overwriting: $TESTTMP/a/a (src: */extdiff.*/a/a) (glob)
445 cleaning up temp directory
454 cleaning up temp directory
446 [1]
455 [1]
447 $ cat a
456 $ cat a
448 a
457 a
449
458
450 #if execbit
459 #if execbit
451 $ [ -x a ]
460 $ [ -x a ]
452
461
453 $ cat > 'dir/tool.sh' << 'EOF'
462 $ cat > 'dir/tool.sh' << 'EOF'
454 > #!/bin/sh
463 > #!/bin/sh
455 > chmod -x $2/a
464 > chmod -x $2/a
456 > echo "** custom diff **"
465 > echo "** custom diff **"
457 > EOF
466 > EOF
458
467
459 $ hg --debug tl --config extdiff.tl= --config merge-tools.tl.executable=$tool
468 $ hg --debug tl --config extdiff.tl= --config merge-tools.tl.executable=$tool
460 making snapshot of 2 files from rev * (glob)
469 making snapshot of 2 files from rev * (glob)
461 a
470 a
462 b
471 b
463 making snapshot of 2 files from working directory
472 making snapshot of 2 files from working directory
464 a
473 a
465 b
474 b
466 running '$TESTTMP/a/dir/tool.sh a.* a' in */extdiff.* (glob)
475 running '$TESTTMP/a/dir/tool.sh a.* a' in */extdiff.* (glob)
467 ** custom diff **
476 ** custom diff **
468 file changed while diffing. Overwriting: $TESTTMP/a/a (src: */extdiff.*/a/a) (glob)
477 file changed while diffing. Overwriting: $TESTTMP/a/a (src: */extdiff.*/a/a) (glob)
469 cleaning up temp directory
478 cleaning up temp directory
470 [1]
479 [1]
471
480
472 $ [ -x a ]
481 $ [ -x a ]
473 [1]
482 [1]
474 #endif
483 #endif
475
484
476 $ cd ..
485 $ cd ..
477
486
478 #if symlink
487 #if symlink
479
488
480 Test symlinks handling (issue1909)
489 Test symlinks handling (issue1909)
481
490
482 $ hg init testsymlinks
491 $ hg init testsymlinks
483 $ cd testsymlinks
492 $ cd testsymlinks
484 $ echo a > a
493 $ echo a > a
485 $ hg ci -Am adda
494 $ hg ci -Am adda
486 adding a
495 adding a
487 $ echo a >> a
496 $ echo a >> a
488 $ ln -s missing linka
497 $ ln -s missing linka
489 $ hg add linka
498 $ hg add linka
490 $ hg falabala -r 0 --traceback
499 $ hg falabala -r 0 --traceback
491 diffing testsymlinks.07f494440405 testsymlinks
500 diffing testsymlinks.07f494440405 testsymlinks
492 [1]
501 [1]
493 $ cd ..
502 $ cd ..
494
503
495 #endif
504 #endif
496
505
497 Test handling of non-ASCII paths in generated docstrings (issue5301)
506 Test handling of non-ASCII paths in generated docstrings (issue5301)
498
507
499 >>> with open("u", "wb") as f:
508 >>> with open("u", "wb") as f:
500 ... n = f.write(b"\xa5\xa5")
509 ... n = f.write(b"\xa5\xa5")
501 $ U=`cat u`
510 $ U=`cat u`
502
511
503 $ HGPLAIN=1 hg --config hgext.extdiff= --config extdiff.cmd.td=hi help -k xyzzy
512 $ HGPLAIN=1 hg --config hgext.extdiff= --config extdiff.cmd.td=hi help -k xyzzy
504 abort: no matches
513 abort: no matches
505 (try 'hg help' for a list of topics)
514 (try 'hg help' for a list of topics)
506 [255]
515 [255]
507
516
508 $ HGPLAIN=1 hg --config hgext.extdiff= --config extdiff.cmd.td=hi help td > /dev/null
517 $ HGPLAIN=1 hg --config hgext.extdiff= --config extdiff.cmd.td=hi help td > /dev/null
509
518
510 $ LC_MESSAGES=ja_JP.UTF-8 hg --config hgext.extdiff= --config extdiff.cmd.td=$U help -k xyzzy
519 $ LC_MESSAGES=ja_JP.UTF-8 hg --config hgext.extdiff= --config extdiff.cmd.td=$U help -k xyzzy
511 abort: no matches
520 abort: no matches
512 (try 'hg help' for a list of topics)
521 (try 'hg help' for a list of topics)
513 [255]
522 [255]
514
523
515 $ LC_MESSAGES=ja_JP.UTF-8 hg --config hgext.extdiff= --config extdiff.cmd.td=$U help td \
524 $ LC_MESSAGES=ja_JP.UTF-8 hg --config hgext.extdiff= --config extdiff.cmd.td=$U help td \
516 > | grep "^ '"
525 > | grep "^ '"
517 '\xa5\xa5'
526 '\xa5\xa5'
518
527
519 $ cd $TESTTMP
528 $ cd $TESTTMP
520
529
521 Test that diffing a single file works, even if that file is new
530 Test that diffing a single file works, even if that file is new
522
531
523 $ hg init testsinglefile
532 $ hg init testsinglefile
524 $ cd testsinglefile
533 $ cd testsinglefile
525 $ echo a > a
534 $ echo a > a
526 $ hg add a
535 $ hg add a
527 $ hg falabala
536 $ hg falabala
528 diffing nul "*\\a" (glob) (windows !)
537 diffing nul "*\\a" (glob) (windows !)
529 diffing /dev/null */a (glob) (no-windows !)
538 diffing /dev/null */a (glob) (no-windows !)
530 [1]
539 [1]
531 $ hg ci -qm a
540 $ hg ci -qm a
532 $ hg falabala -c .
541 $ hg falabala -c .
533 diffing nul "*\\a" (glob) (windows !)
542 diffing nul "*\\a" (glob) (windows !)
534 diffing /dev/null */a (glob) (no-windows !)
543 diffing /dev/null */a (glob) (no-windows !)
535 [1]
544 [1]
536 $ echo a >> a
545 $ echo a >> a
537 $ hg falabala
546 $ hg falabala
538 diffing "*\\a" "*\\a" (glob) (windows !)
547 diffing "*\\a" "*\\a" (glob) (windows !)
539 diffing */a */a (glob) (no-windows !)
548 diffing */a */a (glob) (no-windows !)
540 [1]
549 [1]
541 $ hg ci -qm 2a
550 $ hg ci -qm 2a
542 $ hg falabala -c .
551 $ hg falabala -c .
543 diffing "*\\a" "*\\a" (glob) (windows !)
552 diffing "*\\a" "*\\a" (glob) (windows !)
544 diffing */a */a (glob) (no-windows !)
553 diffing */a */a (glob) (no-windows !)
545 [1]
554 [1]
General Comments 0
You need to be logged in to leave comments. Login now