##// END OF EJS Templates
extdiff: avoid unexpected quoting arguments for external tools (issue4463)...
FUJIWARA Katsunori -
r23680:4075f2f8 default
parent child Browse files
Show More
@@ -1,330 +1,337 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 The extdiff extension also allows you to configure new diff commands, so
16 The extdiff extension also allows you to configure new diff commands, so
17 you do not need to type :hg:`extdiff -p kdiff3` always. ::
17 you do not need to type :hg:`extdiff -p kdiff3` always. ::
18
18
19 [extdiff]
19 [extdiff]
20 # add new command that runs GNU diff(1) in 'context diff' mode
20 # add new command that runs GNU diff(1) in 'context diff' mode
21 cdiff = gdiff -Nprc5
21 cdiff = gdiff -Nprc5
22 ## or the old way:
22 ## or the old way:
23 #cmd.cdiff = gdiff
23 #cmd.cdiff = gdiff
24 #opts.cdiff = -Nprc5
24 #opts.cdiff = -Nprc5
25
25
26 # add new command called meld, runs meld (no need to name twice). If
26 # add new command called meld, runs meld (no need to name twice). If
27 # the meld executable is not available, the meld tool in [merge-tools]
27 # the meld executable is not available, the meld tool in [merge-tools]
28 # will be used, if available
28 # will be used, if available
29 meld =
29 meld =
30
30
31 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
31 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
32 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
32 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
33 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
33 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
34 # your .vimrc
34 # your .vimrc
35 vimdiff = gvim -f "+next" \\
35 vimdiff = gvim -f "+next" \\
36 "+execute 'DirDiff' fnameescape(argv(0)) fnameescape(argv(1))"
36 "+execute 'DirDiff' fnameescape(argv(0)) fnameescape(argv(1))"
37
37
38 Tool arguments can include variables that are expanded at runtime::
38 Tool arguments can include variables that are expanded at runtime::
39
39
40 $parent1, $plabel1 - filename, descriptive label of first parent
40 $parent1, $plabel1 - filename, descriptive label of first parent
41 $child, $clabel - filename, descriptive label of child revision
41 $child, $clabel - filename, descriptive label of child revision
42 $parent2, $plabel2 - filename, descriptive label of second parent
42 $parent2, $plabel2 - filename, descriptive label of second parent
43 $root - repository root
43 $root - repository root
44 $parent is an alias for $parent1.
44 $parent is an alias for $parent1.
45
45
46 The extdiff extension will look in your [diff-tools] and [merge-tools]
46 The extdiff extension will look in your [diff-tools] and [merge-tools]
47 sections for diff tool arguments, when none are specified in [extdiff].
47 sections for diff tool arguments, when none are specified in [extdiff].
48
48
49 ::
49 ::
50
50
51 [extdiff]
51 [extdiff]
52 kdiff3 =
52 kdiff3 =
53
53
54 [diff-tools]
54 [diff-tools]
55 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
55 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
56
56
57 You can use -I/-X and list of file or directory names like normal
57 You can use -I/-X and list of file or directory names like normal
58 :hg:`diff` command. The extdiff extension makes snapshots of only
58 :hg:`diff` command. The extdiff extension makes snapshots of only
59 needed files, so running the external diff program will actually be
59 needed files, so running the external diff program will actually be
60 pretty fast (at least faster than having to compare the entire tree).
60 pretty fast (at least faster than having to compare the entire tree).
61 '''
61 '''
62
62
63 from mercurial.i18n import _
63 from mercurial.i18n import _
64 from mercurial.node import short, nullid
64 from mercurial.node import short, nullid
65 from mercurial import cmdutil, scmutil, util, commands, encoding, filemerge
65 from mercurial import cmdutil, scmutil, util, commands, encoding, filemerge
66 import os, shlex, shutil, tempfile, re
66 import os, shlex, shutil, tempfile, re
67
67
68 cmdtable = {}
68 cmdtable = {}
69 command = cmdutil.command(cmdtable)
69 command = cmdutil.command(cmdtable)
70 testedwith = 'internal'
70 testedwith = 'internal'
71
71
72 def snapshot(ui, repo, files, node, tmproot):
72 def snapshot(ui, repo, files, node, tmproot):
73 '''snapshot files as of some revision
73 '''snapshot files as of some revision
74 if not using snapshot, -I/-X does not work and recursive diff
74 if not using snapshot, -I/-X does not work and recursive diff
75 in tools like kdiff3 and meld displays too many files.'''
75 in tools like kdiff3 and meld displays too many files.'''
76 dirname = os.path.basename(repo.root)
76 dirname = os.path.basename(repo.root)
77 if dirname == "":
77 if dirname == "":
78 dirname = "root"
78 dirname = "root"
79 if node is not None:
79 if node is not None:
80 dirname = '%s.%s' % (dirname, short(node))
80 dirname = '%s.%s' % (dirname, short(node))
81 base = os.path.join(tmproot, dirname)
81 base = os.path.join(tmproot, dirname)
82 os.mkdir(base)
82 os.mkdir(base)
83 if node is not None:
83 if node is not None:
84 ui.note(_('making snapshot of %d files from rev %s\n') %
84 ui.note(_('making snapshot of %d files from rev %s\n') %
85 (len(files), short(node)))
85 (len(files), short(node)))
86 else:
86 else:
87 ui.note(_('making snapshot of %d files from working directory\n') %
87 ui.note(_('making snapshot of %d files from working directory\n') %
88 (len(files)))
88 (len(files)))
89 wopener = scmutil.opener(base)
89 wopener = scmutil.opener(base)
90 fns_and_mtime = []
90 fns_and_mtime = []
91 ctx = repo[node]
91 ctx = repo[node]
92 for fn in sorted(files):
92 for fn in sorted(files):
93 wfn = util.pconvert(fn)
93 wfn = util.pconvert(fn)
94 if wfn not in ctx:
94 if wfn not in ctx:
95 # File doesn't exist; could be a bogus modify
95 # File doesn't exist; could be a bogus modify
96 continue
96 continue
97 ui.note(' %s\n' % wfn)
97 ui.note(' %s\n' % wfn)
98 dest = os.path.join(base, wfn)
98 dest = os.path.join(base, wfn)
99 fctx = ctx[wfn]
99 fctx = ctx[wfn]
100 data = repo.wwritedata(wfn, fctx.data())
100 data = repo.wwritedata(wfn, fctx.data())
101 if 'l' in fctx.flags():
101 if 'l' in fctx.flags():
102 wopener.symlink(data, wfn)
102 wopener.symlink(data, wfn)
103 else:
103 else:
104 wopener.write(wfn, data)
104 wopener.write(wfn, data)
105 if 'x' in fctx.flags():
105 if 'x' in fctx.flags():
106 util.setflags(dest, False, True)
106 util.setflags(dest, False, True)
107 if node is None:
107 if node is None:
108 fns_and_mtime.append((dest, repo.wjoin(fn),
108 fns_and_mtime.append((dest, repo.wjoin(fn),
109 os.lstat(dest).st_mtime))
109 os.lstat(dest).st_mtime))
110 return dirname, fns_and_mtime
110 return dirname, fns_and_mtime
111
111
112 def dodiff(ui, repo, diffcmd, diffopts, pats, opts):
112 def dodiff(ui, repo, args, pats, opts):
113 '''Do the actual diff:
113 '''Do the actual diff:
114
114
115 - copy to a temp structure if diffing 2 internal revisions
115 - copy to a temp structure if diffing 2 internal revisions
116 - copy to a temp structure if diffing working revision with
116 - copy to a temp structure if diffing working revision with
117 another one and more than 1 file is changed
117 another one and more than 1 file is changed
118 - just invoke the diff for a single file in the working dir
118 - just invoke the diff for a single file in the working dir
119 '''
119 '''
120
120
121 revs = opts.get('rev')
121 revs = opts.get('rev')
122 change = opts.get('change')
122 change = opts.get('change')
123 args = ' '.join(map(util.shellquote, diffopts))
124 do3way = '$parent2' in args
123 do3way = '$parent2' in args
125
124
126 if revs and change:
125 if revs and change:
127 msg = _('cannot specify --rev and --change at the same time')
126 msg = _('cannot specify --rev and --change at the same time')
128 raise util.Abort(msg)
127 raise util.Abort(msg)
129 elif change:
128 elif change:
130 node2 = scmutil.revsingle(repo, change, None).node()
129 node2 = scmutil.revsingle(repo, change, None).node()
131 node1a, node1b = repo.changelog.parents(node2)
130 node1a, node1b = repo.changelog.parents(node2)
132 else:
131 else:
133 node1a, node2 = scmutil.revpair(repo, revs)
132 node1a, node2 = scmutil.revpair(repo, revs)
134 if not revs:
133 if not revs:
135 node1b = repo.dirstate.p2()
134 node1b = repo.dirstate.p2()
136 else:
135 else:
137 node1b = nullid
136 node1b = nullid
138
137
139 # Disable 3-way merge if there is only one parent
138 # Disable 3-way merge if there is only one parent
140 if do3way:
139 if do3way:
141 if node1b == nullid:
140 if node1b == nullid:
142 do3way = False
141 do3way = False
143
142
144 matcher = scmutil.match(repo[node2], pats, opts)
143 matcher = scmutil.match(repo[node2], pats, opts)
145 mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher)[:3])
144 mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher)[:3])
146 if do3way:
145 if do3way:
147 mod_b, add_b, rem_b = map(set, repo.status(node1b, node2, matcher)[:3])
146 mod_b, add_b, rem_b = map(set, repo.status(node1b, node2, matcher)[:3])
148 else:
147 else:
149 mod_b, add_b, rem_b = set(), set(), set()
148 mod_b, add_b, rem_b = set(), set(), set()
150 modadd = mod_a | add_a | mod_b | add_b
149 modadd = mod_a | add_a | mod_b | add_b
151 common = modadd | rem_a | rem_b
150 common = modadd | rem_a | rem_b
152 if not common:
151 if not common:
153 return 0
152 return 0
154
153
155 tmproot = tempfile.mkdtemp(prefix='extdiff.')
154 tmproot = tempfile.mkdtemp(prefix='extdiff.')
156 try:
155 try:
157 # Always make a copy of node1a (and node1b, if applicable)
156 # Always make a copy of node1a (and node1b, if applicable)
158 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
157 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
159 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot)[0]
158 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot)[0]
160 rev1a = '@%d' % repo[node1a].rev()
159 rev1a = '@%d' % repo[node1a].rev()
161 if do3way:
160 if do3way:
162 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
161 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
163 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot)[0]
162 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot)[0]
164 rev1b = '@%d' % repo[node1b].rev()
163 rev1b = '@%d' % repo[node1b].rev()
165 else:
164 else:
166 dir1b = None
165 dir1b = None
167 rev1b = ''
166 rev1b = ''
168
167
169 fns_and_mtime = []
168 fns_and_mtime = []
170
169
171 # If node2 in not the wc or there is >1 change, copy it
170 # If node2 in not the wc or there is >1 change, copy it
172 dir2root = ''
171 dir2root = ''
173 rev2 = ''
172 rev2 = ''
174 if node2:
173 if node2:
175 dir2 = snapshot(ui, repo, modadd, node2, tmproot)[0]
174 dir2 = snapshot(ui, repo, modadd, node2, tmproot)[0]
176 rev2 = '@%d' % repo[node2].rev()
175 rev2 = '@%d' % repo[node2].rev()
177 elif len(common) > 1:
176 elif len(common) > 1:
178 #we only actually need to get the files to copy back to
177 #we only actually need to get the files to copy back to
179 #the working dir in this case (because the other cases
178 #the working dir in this case (because the other cases
180 #are: diffing 2 revisions or single file -- in which case
179 #are: diffing 2 revisions or single file -- in which case
181 #the file is already directly passed to the diff tool).
180 #the file is already directly passed to the diff tool).
182 dir2, fns_and_mtime = snapshot(ui, repo, modadd, None, tmproot)
181 dir2, fns_and_mtime = snapshot(ui, repo, modadd, None, tmproot)
183 else:
182 else:
184 # This lets the diff tool open the changed file directly
183 # This lets the diff tool open the changed file directly
185 dir2 = ''
184 dir2 = ''
186 dir2root = repo.root
185 dir2root = repo.root
187
186
188 label1a = rev1a
187 label1a = rev1a
189 label1b = rev1b
188 label1b = rev1b
190 label2 = rev2
189 label2 = rev2
191
190
192 # If only one change, diff the files instead of the directories
191 # If only one change, diff the files instead of the directories
193 # Handle bogus modifies correctly by checking if the files exist
192 # Handle bogus modifies correctly by checking if the files exist
194 if len(common) == 1:
193 if len(common) == 1:
195 common_file = util.localpath(common.pop())
194 common_file = util.localpath(common.pop())
196 dir1a = os.path.join(tmproot, dir1a, common_file)
195 dir1a = os.path.join(tmproot, dir1a, common_file)
197 label1a = common_file + rev1a
196 label1a = common_file + rev1a
198 if not os.path.isfile(dir1a):
197 if not os.path.isfile(dir1a):
199 dir1a = os.devnull
198 dir1a = os.devnull
200 if do3way:
199 if do3way:
201 dir1b = os.path.join(tmproot, dir1b, common_file)
200 dir1b = os.path.join(tmproot, dir1b, common_file)
202 label1b = common_file + rev1b
201 label1b = common_file + rev1b
203 if not os.path.isfile(dir1b):
202 if not os.path.isfile(dir1b):
204 dir1b = os.devnull
203 dir1b = os.devnull
205 dir2 = os.path.join(dir2root, dir2, common_file)
204 dir2 = os.path.join(dir2root, dir2, common_file)
206 label2 = common_file + rev2
205 label2 = common_file + rev2
207
206
208 # Function to quote file/dir names in the argument string.
207 # Function to quote file/dir names in the argument string.
209 # When not operating in 3-way mode, an empty string is
208 # When not operating in 3-way mode, an empty string is
210 # returned for parent2
209 # returned for parent2
211 replace = {'parent': dir1a, 'parent1': dir1a, 'parent2': dir1b,
210 replace = {'parent': dir1a, 'parent1': dir1a, 'parent2': dir1b,
212 'plabel1': label1a, 'plabel2': label1b,
211 'plabel1': label1a, 'plabel2': label1b,
213 'clabel': label2, 'child': dir2,
212 'clabel': label2, 'child': dir2,
214 'root': repo.root}
213 'root': repo.root}
215 def quote(match):
214 def quote(match):
216 key = match.group()[1:]
215 key = match.group()[1:]
217 if not do3way and key == 'parent2':
216 if not do3way and key == 'parent2':
218 return ''
217 return ''
219 return util.shellquote(replace[key])
218 return util.shellquote(replace[key])
220
219
221 # Match parent2 first, so 'parent1?' will match both parent1 and parent
220 # Match parent2 first, so 'parent1?' will match both parent1 and parent
222 regex = '\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)'
221 regex = '\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)'
223 if not do3way and not re.search(regex, args):
222 if not do3way and not re.search(regex, args):
224 args += ' $parent1 $child'
223 args += ' $parent1 $child'
225 args = re.sub(regex, quote, args)
224 cmdline = re.sub(regex, quote, args)
226 cmdline = util.shellquote(diffcmd) + ' ' + args
227
225
228 ui.debug('running %r in %s\n' % (cmdline, tmproot))
226 ui.debug('running %r in %s\n' % (cmdline, tmproot))
229 ui.system(cmdline, cwd=tmproot)
227 ui.system(cmdline, cwd=tmproot)
230
228
231 for copy_fn, working_fn, mtime in fns_and_mtime:
229 for copy_fn, working_fn, mtime in fns_and_mtime:
232 if os.lstat(copy_fn).st_mtime != mtime:
230 if os.lstat(copy_fn).st_mtime != mtime:
233 ui.debug('file changed while diffing. '
231 ui.debug('file changed while diffing. '
234 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn))
232 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn))
235 util.copyfile(copy_fn, working_fn)
233 util.copyfile(copy_fn, working_fn)
236
234
237 return 1
235 return 1
238 finally:
236 finally:
239 ui.note(_('cleaning up temp directory\n'))
237 ui.note(_('cleaning up temp directory\n'))
240 shutil.rmtree(tmproot)
238 shutil.rmtree(tmproot)
241
239
242 @command('extdiff',
240 @command('extdiff',
243 [('p', 'program', '',
241 [('p', 'program', '',
244 _('comparison program to run'), _('CMD')),
242 _('comparison program to run'), _('CMD')),
245 ('o', 'option', [],
243 ('o', 'option', [],
246 _('pass option to comparison program'), _('OPT')),
244 _('pass option to comparison program'), _('OPT')),
247 ('r', 'rev', [], _('revision'), _('REV')),
245 ('r', 'rev', [], _('revision'), _('REV')),
248 ('c', 'change', '', _('change made by revision'), _('REV')),
246 ('c', 'change', '', _('change made by revision'), _('REV')),
249 ] + commands.walkopts,
247 ] + commands.walkopts,
250 _('hg extdiff [OPT]... [FILE]...'),
248 _('hg extdiff [OPT]... [FILE]...'),
251 inferrepo=True)
249 inferrepo=True)
252 def extdiff(ui, repo, *pats, **opts):
250 def extdiff(ui, repo, *pats, **opts):
253 '''use external program to diff repository (or selected files)
251 '''use external program to diff repository (or selected files)
254
252
255 Show differences between revisions for the specified files, using
253 Show differences between revisions for the specified files, using
256 an external program. The default program used is diff, with
254 an external program. The default program used is diff, with
257 default options "-Npru".
255 default options "-Npru".
258
256
259 To select a different program, use the -p/--program option. The
257 To select a different program, use the -p/--program option. The
260 program will be passed the names of two directories to compare. To
258 program will be passed the names of two directories to compare. To
261 pass additional options to the program, use -o/--option. These
259 pass additional options to the program, use -o/--option. These
262 will be passed before the names of the directories to compare.
260 will be passed before the names of the directories to compare.
263
261
264 When two revision arguments are given, then changes are shown
262 When two revision arguments are given, then changes are shown
265 between those revisions. If only one revision is specified then
263 between those revisions. If only one revision is specified then
266 that revision is compared to the working directory, and, when no
264 that revision is compared to the working directory, and, when no
267 revisions are specified, the working directory files are compared
265 revisions are specified, the working directory files are compared
268 to its parent.'''
266 to its parent.'''
269 program = opts.get('program')
267 program = opts.get('program')
270 option = opts.get('option')
268 option = opts.get('option')
271 if not program:
269 if not program:
272 program = 'diff'
270 program = 'diff'
273 option = option or ['-Npru']
271 option = option or ['-Npru']
274 return dodiff(ui, repo, program, option, pats, opts)
272 cmdline = ' '.join(map(util.shellquote, [program] + option))
273 return dodiff(ui, repo, cmdline, pats, opts)
275
274
276 def uisetup(ui):
275 def uisetup(ui):
277 for cmd, path in ui.configitems('extdiff'):
276 for cmd, path in ui.configitems('extdiff'):
278 if cmd.startswith('cmd.'):
277 if cmd.startswith('cmd.'):
279 cmd = cmd[4:]
278 cmd = cmd[4:]
280 if not path:
279 if not path:
281 path = util.findexe(cmd)
280 path = util.findexe(cmd)
282 if path is None:
281 if path is None:
283 path = filemerge.findexternaltool(ui, cmd) or cmd
282 path = filemerge.findexternaltool(ui, cmd) or cmd
284 diffopts = shlex.split(ui.config('extdiff', 'opts.' + cmd, ''))
283 diffopts = ui.config('extdiff', 'opts.' + cmd, '')
284 cmdline = util.shellquote(path)
285 if diffopts:
286 cmdline += ' ' + diffopts
285 elif cmd.startswith('opts.'):
287 elif cmd.startswith('opts.'):
286 continue
288 continue
287 else:
289 else:
288 # command = path opts
289 if path:
290 if path:
290 diffopts = shlex.split(path)
291 # case "cmd = path opts"
291 path = diffopts.pop(0)
292 cmdline = path
293 diffopts = len(shlex.split(cmdline)) > 1
292 else:
294 else:
293 path, diffopts = util.findexe(cmd), []
295 # case "cmd ="
296 path = util.findexe(cmd)
294 if path is None:
297 if path is None:
295 path = filemerge.findexternaltool(ui, cmd) or cmd
298 path = filemerge.findexternaltool(ui, cmd) or cmd
299 cmdline = util.shellquote(path)
300 diffopts = False
296 # look for diff arguments in [diff-tools] then [merge-tools]
301 # look for diff arguments in [diff-tools] then [merge-tools]
297 if diffopts == []:
302 if not diffopts:
298 args = ui.config('diff-tools', cmd+'.diffargs') or \
303 args = ui.config('diff-tools', cmd+'.diffargs') or \
299 ui.config('merge-tools', cmd+'.diffargs')
304 ui.config('merge-tools', cmd+'.diffargs')
300 if args:
305 if args:
301 diffopts = shlex.split(args)
306 cmdline += ' ' + args
302 def save(cmd, path, diffopts):
307 def save(cmdline):
303 '''use closure to save diff command to use'''
308 '''use closure to save diff command to use'''
304 def mydiff(ui, repo, *pats, **opts):
309 def mydiff(ui, repo, *pats, **opts):
305 return dodiff(ui, repo, path, diffopts + opts['option'],
310 options = ' '.join(map(util.shellquote, opts['option']))
306 pats, opts)
311 if options:
312 options = ' ' + options
313 return dodiff(ui, repo, cmdline + options, pats, opts)
307 doc = _('''\
314 doc = _('''\
308 use %(path)s to diff repository (or selected files)
315 use %(path)s to diff repository (or selected files)
309
316
310 Show differences between revisions for the specified files, using
317 Show differences between revisions for the specified files, using
311 the %(path)s program.
318 the %(path)s program.
312
319
313 When two revision arguments are given, then changes are shown
320 When two revision arguments are given, then changes are shown
314 between those revisions. If only one revision is specified then
321 between those revisions. If only one revision is specified then
315 that revision is compared to the working directory, and, when no
322 that revision is compared to the working directory, and, when no
316 revisions are specified, the working directory files are compared
323 revisions are specified, the working directory files are compared
317 to its parent.\
324 to its parent.\
318 ''') % {'path': util.uirepr(path)}
325 ''') % {'path': util.uirepr(path)}
319
326
320 # We must translate the docstring right away since it is
327 # We must translate the docstring right away since it is
321 # used as a format string. The string will unfortunately
328 # used as a format string. The string will unfortunately
322 # be translated again in commands.helpcmd and this will
329 # be translated again in commands.helpcmd and this will
323 # fail when the docstring contains non-ASCII characters.
330 # fail when the docstring contains non-ASCII characters.
324 # Decoding the string to a Unicode string here (using the
331 # Decoding the string to a Unicode string here (using the
325 # right encoding) prevents that.
332 # right encoding) prevents that.
326 mydiff.__doc__ = doc.decode(encoding.encoding)
333 mydiff.__doc__ = doc.decode(encoding.encoding)
327 return mydiff
334 return mydiff
328 cmdtable[cmd] = (save(cmd, path, diffopts),
335 cmdtable[cmd] = (save(cmdline),
329 cmdtable['extdiff'][1][1:],
336 cmdtable['extdiff'][1][1:],
330 _('hg %s [OPTION]... [FILE]...') % cmd)
337 _('hg %s [OPTION]... [FILE]...') % cmd)
@@ -1,236 +1,302 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 $opt
14 $ hg extdiff -o -r $opt
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 > EOF
25 > EOF
26
26
27 $ hg falabala
27 $ hg falabala
28 diffing a.000000000000 a
28 diffing a.000000000000 a
29 [1]
29 [1]
30
30
31 $ hg help falabala
31 $ hg help falabala
32 hg falabala [OPTION]... [FILE]...
32 hg falabala [OPTION]... [FILE]...
33
33
34 use 'echo' to diff repository (or selected files)
34 use 'echo' to diff repository (or selected files)
35
35
36 Show differences between revisions for the specified files, using the
36 Show differences between revisions for the specified files, using the
37 'echo' program.
37 'echo' program.
38
38
39 When two revision arguments are given, then changes are shown between
39 When two revision arguments are given, then changes are shown between
40 those revisions. If only one revision is specified then that revision is
40 those revisions. If only one revision is specified then that revision is
41 compared to the working directory, and, when no revisions are specified,
41 compared to the working directory, and, when no revisions are specified,
42 the working directory files are compared to its parent.
42 the working directory files are compared to its parent.
43
43
44 options ([+] can be repeated):
44 options ([+] can be repeated):
45
45
46 -o --option OPT [+] pass option to comparison program
46 -o --option OPT [+] pass option to comparison program
47 -r --rev REV [+] revision
47 -r --rev REV [+] revision
48 -c --change REV change made by revision
48 -c --change REV change made by revision
49 -I --include PATTERN [+] include names matching the given patterns
49 -I --include PATTERN [+] include names matching the given patterns
50 -X --exclude PATTERN [+] exclude names matching the given patterns
50 -X --exclude PATTERN [+] exclude names matching the given patterns
51
51
52 (some details hidden, use --verbose to show complete help)
52 (some details hidden, use --verbose to show complete help)
53
53
54 $ hg ci -d '0 0' -mtest1
54 $ hg ci -d '0 0' -mtest1
55
55
56 $ echo b >> a
56 $ echo b >> a
57 $ hg ci -d '1 0' -mtest2
57 $ hg ci -d '1 0' -mtest2
58
58
59 Should diff cloned files directly:
59 Should diff cloned files directly:
60
60
61 $ hg falabala -r 0:1
61 $ hg falabala -r 0:1
62 diffing */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
62 diffing */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
63 [1]
63 [1]
64
64
65 Test diff during merge:
65 Test diff during merge:
66
66
67 $ hg update -C 0
67 $ hg update -C 0
68 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
68 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
69 $ echo c >> c
69 $ echo c >> c
70 $ hg add c
70 $ hg add c
71 $ hg ci -m "new branch" -d '1 0'
71 $ hg ci -m "new branch" -d '1 0'
72 created new head
72 created new head
73 $ hg merge 1
73 $ hg merge 1
74 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
74 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
75 (branch merge, don't forget to commit)
75 (branch merge, don't forget to commit)
76
76
77 Should diff cloned file against wc file:
77 Should diff cloned file against wc file:
78
78
79 $ hg falabala
79 $ hg falabala
80 diffing */extdiff.*/a.2a13a4d2da36/a */a/a (glob)
80 diffing */extdiff.*/a.2a13a4d2da36/a */a/a (glob)
81 [1]
81 [1]
82
82
83
83
84 Test --change option:
84 Test --change option:
85
85
86 $ hg ci -d '2 0' -mtest3
86 $ hg ci -d '2 0' -mtest3
87 $ hg falabala -c 1
87 $ hg falabala -c 1
88 diffing */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
88 diffing */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
89 [1]
89 [1]
90
90
91 Check diff are made from the first parent:
91 Check diff are made from the first parent:
92
92
93 $ hg falabala -c 3 || echo "diff-like tools yield a non-zero exit code"
93 $ hg falabala -c 3 || echo "diff-like tools yield a non-zero exit code"
94 diffing */extdiff.*/a.2a13a4d2da36/a a.46c0e4daeb72/a (glob)
94 diffing */extdiff.*/a.2a13a4d2da36/a a.46c0e4daeb72/a (glob)
95 diff-like tools yield a non-zero exit code
95 diff-like tools yield a non-zero exit code
96
96
97 issue4463: usage of command line configuration without additional quoting
98
99 $ cat <<EOF >> $HGRCPATH
100 > [extdiff]
101 > cmd.4463a = echo
102 > opts.4463a = a-naked 'single quoted' "double quoted"
103 > 4463b = echo b-naked 'single quoted' "double quoted"
104 > echo =
105 > EOF
106 $ hg update -q -C 0
107 $ echo a >> a
108 #if windows
109 $ hg --debug 4463a | grep '^running'
110 running '"echo" a-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob)
111 $ hg --debug 4463b | grep '^running'
112 running 'echo b-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob)
113 $ hg --debug echo | grep '^running'
114 running '"*echo*" "*\\a" "*\\a"' in */extdiff.* (glob)
115 #else
116 $ hg --debug 4463a | grep '^running'
117 running '\'echo\' a-naked \'single quoted\' "double quoted" \'*/a\' \'$TESTTMP/a/a\'' in */extdiff.* (glob)
118 $ hg --debug 4463b | grep '^running'
119 running 'echo b-naked \'single quoted\' "double quoted" \'*/a\' \'$TESTTMP/a/a\'' in */extdiff.* (glob)
120 $ hg --debug echo | grep '^running'
121 running "'*echo*' '*/a' '$TESTTMP/a/a'" in */extdiff.* (glob)
122 #endif
123
124 (getting options from other than extdiff section)
125
126 $ cat <<EOF >> $HGRCPATH
127 > [extdiff]
128 > # using diff-tools diffargs
129 > 4463b2 = echo
130 > # using merge-tools diffargs
131 > 4463b3 = echo
132 > # no diffargs
133 > 4463b4 = echo
134 > [diff-tools]
135 > 4463b2.diffargs = b2-naked 'single quoted' "double quoted"
136 > [merge-tools]
137 > 4463b3.diffargs = b3-naked 'single quoted' "double quoted"
138 > EOF
139 #if windows
140 $ hg --debug 4463b2 | grep '^running'
141 running 'echo b2-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob)
142 $ hg --debug 4463b3 | grep '^running'
143 running 'echo b3-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob)
144 $ hg --debug 4463b4 | grep '^running'
145 running 'echo "*\\a" "*\\a"' in */extdiff.* (glob)
146 $ hg --debug 4463b4 --option 'being quoted' | grep '^running'
147 running 'echo "being quoted" "*\\a" "*\\a"' in */extdiff.* (glob)
148 $ hg --debug extdiff -p echo --option 'being quoted' | grep '^running'
149 running '"echo" "being quoted" "*\\a" "*\\a"' in */extdiff.* (glob)
150 #else
151 $ hg --debug 4463b2 | grep '^running'
152 running 'echo b2-naked \'single quoted\' "double quoted" \'*/a\' \'$TESTTMP/a/a\'' in */extdiff.* (glob)
153 $ hg --debug 4463b3 | grep '^running'
154 running 'echo b3-naked \'single quoted\' "double quoted" \'*/a\' \'$TESTTMP/a/a\'' in */extdiff.* (glob)
155 $ hg --debug 4463b4 | grep '^running'
156 running "echo '*/a' '$TESTTMP/a/a'" in */extdiff.* (glob)
157 $ hg --debug 4463b4 --option 'being quoted' | grep '^running'
158 running "echo 'being quoted' '*/a' '$TESTTMP/a/a'" in */extdiff.* (glob)
159 $ hg --debug extdiff -p echo --option 'being quoted' | grep '^running'
160 running "'echo' 'being quoted' '*/a' '$TESTTMP/a/a'" in */extdiff.* (glob)
161 #endif
162
97 #if execbit
163 #if execbit
98
164
99 Test extdiff of multiple files in tmp dir:
165 Test extdiff of multiple files in tmp dir:
100
166
101 $ hg update -C 0 > /dev/null
167 $ hg update -C 0 > /dev/null
102 $ echo changed > a
168 $ echo changed > a
103 $ echo changed > b
169 $ echo changed > b
104 $ chmod +x b
170 $ chmod +x b
105
171
106 Diff in working directory, before:
172 Diff in working directory, before:
107
173
108 $ hg diff --git
174 $ hg diff --git
109 diff --git a/a b/a
175 diff --git a/a b/a
110 --- a/a
176 --- a/a
111 +++ b/a
177 +++ b/a
112 @@ -1,1 +1,1 @@
178 @@ -1,1 +1,1 @@
113 -a
179 -a
114 +changed
180 +changed
115 diff --git a/b b/b
181 diff --git a/b b/b
116 old mode 100644
182 old mode 100644
117 new mode 100755
183 new mode 100755
118 --- a/b
184 --- a/b
119 +++ b/b
185 +++ b/b
120 @@ -1,1 +1,1 @@
186 @@ -1,1 +1,1 @@
121 -b
187 -b
122 +changed
188 +changed
123
189
124
190
125 Edit with extdiff -p:
191 Edit with extdiff -p:
126
192
127 Prepare custom diff/edit tool:
193 Prepare custom diff/edit tool:
128
194
129 $ cat > 'diff tool.py' << EOT
195 $ cat > 'diff tool.py' << EOT
130 > #!/usr/bin/env python
196 > #!/usr/bin/env python
131 > import time
197 > import time
132 > time.sleep(1) # avoid unchanged-timestamp problems
198 > time.sleep(1) # avoid unchanged-timestamp problems
133 > file('a/a', 'ab').write('edited\n')
199 > file('a/a', 'ab').write('edited\n')
134 > file('a/b', 'ab').write('edited\n')
200 > file('a/b', 'ab').write('edited\n')
135 > EOT
201 > EOT
136
202
137 $ chmod +x 'diff tool.py'
203 $ chmod +x 'diff tool.py'
138
204
139 will change to /tmp/extdiff.TMP and populate directories a.TMP and a
205 will change to /tmp/extdiff.TMP and populate directories a.TMP and a
140 and start tool
206 and start tool
141
207
142 $ hg extdiff -p "`pwd`/diff tool.py"
208 $ hg extdiff -p "`pwd`/diff tool.py"
143 [1]
209 [1]
144
210
145 Diff in working directory, after:
211 Diff in working directory, after:
146
212
147 $ hg diff --git
213 $ hg diff --git
148 diff --git a/a b/a
214 diff --git a/a b/a
149 --- a/a
215 --- a/a
150 +++ b/a
216 +++ b/a
151 @@ -1,1 +1,2 @@
217 @@ -1,1 +1,2 @@
152 -a
218 -a
153 +changed
219 +changed
154 +edited
220 +edited
155 diff --git a/b b/b
221 diff --git a/b b/b
156 old mode 100644
222 old mode 100644
157 new mode 100755
223 new mode 100755
158 --- a/b
224 --- a/b
159 +++ b/b
225 +++ b/b
160 @@ -1,1 +1,2 @@
226 @@ -1,1 +1,2 @@
161 -b
227 -b
162 +changed
228 +changed
163 +edited
229 +edited
164
230
165 Test extdiff with --option:
231 Test extdiff with --option:
166
232
167 $ hg extdiff -p echo -o this -c 1
233 $ hg extdiff -p echo -o this -c 1
168 this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
234 this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
169 [1]
235 [1]
170
236
171 $ hg falabala -o this -c 1
237 $ hg falabala -o this -c 1
172 diffing this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
238 diffing this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
173 [1]
239 [1]
174
240
175 Test extdiff's handling of options with spaces in them:
241 Test extdiff's handling of options with spaces in them:
176
242
177 $ hg edspace -c 1
243 $ hg edspace -c 1
178 name <user@example.com> */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
244 name <user@example.com> */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
179 [1]
245 [1]
180
246
181 $ hg extdiff -p echo -o "name <user@example.com>" -c 1
247 $ hg extdiff -p echo -o "name <user@example.com>" -c 1
182 name <user@example.com> */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
248 name <user@example.com> */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
183 [1]
249 [1]
184
250
185 Test with revsets:
251 Test with revsets:
186
252
187 $ hg extdif -p echo -c "rev(1)"
253 $ hg extdif -p echo -c "rev(1)"
188 */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
254 */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
189 [1]
255 [1]
190
256
191 $ hg extdif -p echo -r "0::1"
257 $ hg extdif -p echo -r "0::1"
192 */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
258 */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
193 [1]
259 [1]
194
260
195 Fallback to merge-tools.tool.executable|regkey
261 Fallback to merge-tools.tool.executable|regkey
196 $ mkdir dir
262 $ mkdir dir
197 $ cat > 'dir/tool.sh' << EOF
263 $ cat > 'dir/tool.sh' << EOF
198 > #!/bin/sh
264 > #!/bin/sh
199 > echo "** custom diff **"
265 > echo "** custom diff **"
200 > EOF
266 > EOF
201 $ chmod +x dir/tool.sh
267 $ chmod +x dir/tool.sh
202 $ tool=`pwd`/dir/tool.sh
268 $ tool=`pwd`/dir/tool.sh
203 $ hg --debug tl --config extdiff.tl= --config merge-tools.tl.executable=$tool
269 $ hg --debug tl --config extdiff.tl= --config merge-tools.tl.executable=$tool
204 making snapshot of 2 files from rev * (glob)
270 making snapshot of 2 files from rev * (glob)
205 a
271 a
206 b
272 b
207 making snapshot of 2 files from working directory
273 making snapshot of 2 files from working directory
208 a
274 a
209 b
275 b
210 running "'$TESTTMP/a/dir/tool.sh' 'a.*' 'a'" in */extdiff.* (glob)
276 running "'$TESTTMP/a/dir/tool.sh' 'a.*' 'a'" in */extdiff.* (glob)
211 ** custom diff **
277 ** custom diff **
212 cleaning up temp directory
278 cleaning up temp directory
213 [1]
279 [1]
214
280
215 $ cd ..
281 $ cd ..
216
282
217 #endif
283 #endif
218
284
219 #if symlink
285 #if symlink
220
286
221 Test symlinks handling (issue1909)
287 Test symlinks handling (issue1909)
222
288
223 $ hg init testsymlinks
289 $ hg init testsymlinks
224 $ cd testsymlinks
290 $ cd testsymlinks
225 $ echo a > a
291 $ echo a > a
226 $ hg ci -Am adda
292 $ hg ci -Am adda
227 adding a
293 adding a
228 $ echo a >> a
294 $ echo a >> a
229 $ ln -s missing linka
295 $ ln -s missing linka
230 $ hg add linka
296 $ hg add linka
231 $ hg falabala -r 0 --traceback
297 $ hg falabala -r 0 --traceback
232 diffing testsymlinks.07f494440405 testsymlinks
298 diffing testsymlinks.07f494440405 testsymlinks
233 [1]
299 [1]
234 $ cd ..
300 $ cd ..
235
301
236 #endif
302 #endif
General Comments 0
You need to be logged in to leave comments. Login now