##// END OF EJS Templates
extdiff: reintroduce backward compatibility with manual quoting of parameters...
Mads Kiilerich -
r23969:01e5b732 stable
parent child Browse files
Show More
@@ -1,337 +1,339
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, cmdline, pats, opts):
112 def dodiff(ui, repo, cmdline, 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 do3way = '$parent2' in cmdline
123 do3way = '$parent2' in cmdline
124
124
125 if revs and change:
125 if revs and change:
126 msg = _('cannot specify --rev and --change at the same time')
126 msg = _('cannot specify --rev and --change at the same time')
127 raise util.Abort(msg)
127 raise util.Abort(msg)
128 elif change:
128 elif change:
129 node2 = scmutil.revsingle(repo, change, None).node()
129 node2 = scmutil.revsingle(repo, change, None).node()
130 node1a, node1b = repo.changelog.parents(node2)
130 node1a, node1b = repo.changelog.parents(node2)
131 else:
131 else:
132 node1a, node2 = scmutil.revpair(repo, revs)
132 node1a, node2 = scmutil.revpair(repo, revs)
133 if not revs:
133 if not revs:
134 node1b = repo.dirstate.p2()
134 node1b = repo.dirstate.p2()
135 else:
135 else:
136 node1b = nullid
136 node1b = nullid
137
137
138 # Disable 3-way merge if there is only one parent
138 # Disable 3-way merge if there is only one parent
139 if do3way:
139 if do3way:
140 if node1b == nullid:
140 if node1b == nullid:
141 do3way = False
141 do3way = False
142
142
143 matcher = scmutil.match(repo[node2], pats, opts)
143 matcher = scmutil.match(repo[node2], pats, opts)
144 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])
145 if do3way:
145 if do3way:
146 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])
147 else:
147 else:
148 mod_b, add_b, rem_b = set(), set(), set()
148 mod_b, add_b, rem_b = set(), set(), set()
149 modadd = mod_a | add_a | mod_b | add_b
149 modadd = mod_a | add_a | mod_b | add_b
150 common = modadd | rem_a | rem_b
150 common = modadd | rem_a | rem_b
151 if not common:
151 if not common:
152 return 0
152 return 0
153
153
154 tmproot = tempfile.mkdtemp(prefix='extdiff.')
154 tmproot = tempfile.mkdtemp(prefix='extdiff.')
155 try:
155 try:
156 # Always make a copy of node1a (and node1b, if applicable)
156 # Always make a copy of node1a (and node1b, if applicable)
157 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)
158 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot)[0]
158 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot)[0]
159 rev1a = '@%d' % repo[node1a].rev()
159 rev1a = '@%d' % repo[node1a].rev()
160 if do3way:
160 if do3way:
161 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)
162 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot)[0]
162 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot)[0]
163 rev1b = '@%d' % repo[node1b].rev()
163 rev1b = '@%d' % repo[node1b].rev()
164 else:
164 else:
165 dir1b = None
165 dir1b = None
166 rev1b = ''
166 rev1b = ''
167
167
168 fns_and_mtime = []
168 fns_and_mtime = []
169
169
170 # 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
171 dir2root = ''
171 dir2root = ''
172 rev2 = ''
172 rev2 = ''
173 if node2:
173 if node2:
174 dir2 = snapshot(ui, repo, modadd, node2, tmproot)[0]
174 dir2 = snapshot(ui, repo, modadd, node2, tmproot)[0]
175 rev2 = '@%d' % repo[node2].rev()
175 rev2 = '@%d' % repo[node2].rev()
176 elif len(common) > 1:
176 elif len(common) > 1:
177 #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
178 #the working dir in this case (because the other cases
178 #the working dir in this case (because the other cases
179 #are: diffing 2 revisions or single file -- in which case
179 #are: diffing 2 revisions or single file -- in which case
180 #the file is already directly passed to the diff tool).
180 #the file is already directly passed to the diff tool).
181 dir2, fns_and_mtime = snapshot(ui, repo, modadd, None, tmproot)
181 dir2, fns_and_mtime = snapshot(ui, repo, modadd, None, tmproot)
182 else:
182 else:
183 # This lets the diff tool open the changed file directly
183 # This lets the diff tool open the changed file directly
184 dir2 = ''
184 dir2 = ''
185 dir2root = repo.root
185 dir2root = repo.root
186
186
187 label1a = rev1a
187 label1a = rev1a
188 label1b = rev1b
188 label1b = rev1b
189 label2 = rev2
189 label2 = rev2
190
190
191 # If only one change, diff the files instead of the directories
191 # If only one change, diff the files instead of the directories
192 # Handle bogus modifies correctly by checking if the files exist
192 # Handle bogus modifies correctly by checking if the files exist
193 if len(common) == 1:
193 if len(common) == 1:
194 common_file = util.localpath(common.pop())
194 common_file = util.localpath(common.pop())
195 dir1a = os.path.join(tmproot, dir1a, common_file)
195 dir1a = os.path.join(tmproot, dir1a, common_file)
196 label1a = common_file + rev1a
196 label1a = common_file + rev1a
197 if not os.path.isfile(dir1a):
197 if not os.path.isfile(dir1a):
198 dir1a = os.devnull
198 dir1a = os.devnull
199 if do3way:
199 if do3way:
200 dir1b = os.path.join(tmproot, dir1b, common_file)
200 dir1b = os.path.join(tmproot, dir1b, common_file)
201 label1b = common_file + rev1b
201 label1b = common_file + rev1b
202 if not os.path.isfile(dir1b):
202 if not os.path.isfile(dir1b):
203 dir1b = os.devnull
203 dir1b = os.devnull
204 dir2 = os.path.join(dir2root, dir2, common_file)
204 dir2 = os.path.join(dir2root, dir2, common_file)
205 label2 = common_file + rev2
205 label2 = common_file + rev2
206
206
207 # Function to quote file/dir names in the argument string.
207 # Function to quote file/dir names in the argument string.
208 # When not operating in 3-way mode, an empty string is
208 # When not operating in 3-way mode, an empty string is
209 # returned for parent2
209 # returned for parent2
210 replace = {'parent': dir1a, 'parent1': dir1a, 'parent2': dir1b,
210 replace = {'parent': dir1a, 'parent1': dir1a, 'parent2': dir1b,
211 'plabel1': label1a, 'plabel2': label1b,
211 'plabel1': label1a, 'plabel2': label1b,
212 'clabel': label2, 'child': dir2,
212 'clabel': label2, 'child': dir2,
213 'root': repo.root}
213 'root': repo.root}
214 def quote(match):
214 def quote(match):
215 key = match.group()[1:]
215 pre = match.group(2)
216 key = match.group(3)
216 if not do3way and key == 'parent2':
217 if not do3way and key == 'parent2':
217 return ''
218 return pre
218 return util.shellquote(replace[key])
219 return pre + util.shellquote(replace[key])
219
220
220 # Match parent2 first, so 'parent1?' will match both parent1 and parent
221 # Match parent2 first, so 'parent1?' will match both parent1 and parent
221 regex = '\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)'
222 regex = (r'''(['"]?)([^\s'"$]*)'''
223 r'\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)\1')
222 if not do3way and not re.search(regex, cmdline):
224 if not do3way and not re.search(regex, cmdline):
223 cmdline += ' $parent1 $child'
225 cmdline += ' $parent1 $child'
224 cmdline = re.sub(regex, quote, cmdline)
226 cmdline = re.sub(regex, quote, cmdline)
225
227
226 ui.debug('running %r in %s\n' % (cmdline, tmproot))
228 ui.debug('running %r in %s\n' % (cmdline, tmproot))
227 ui.system(cmdline, cwd=tmproot)
229 ui.system(cmdline, cwd=tmproot)
228
230
229 for copy_fn, working_fn, mtime in fns_and_mtime:
231 for copy_fn, working_fn, mtime in fns_and_mtime:
230 if os.lstat(copy_fn).st_mtime != mtime:
232 if os.lstat(copy_fn).st_mtime != mtime:
231 ui.debug('file changed while diffing. '
233 ui.debug('file changed while diffing. '
232 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn))
234 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn))
233 util.copyfile(copy_fn, working_fn)
235 util.copyfile(copy_fn, working_fn)
234
236
235 return 1
237 return 1
236 finally:
238 finally:
237 ui.note(_('cleaning up temp directory\n'))
239 ui.note(_('cleaning up temp directory\n'))
238 shutil.rmtree(tmproot)
240 shutil.rmtree(tmproot)
239
241
240 @command('extdiff',
242 @command('extdiff',
241 [('p', 'program', '',
243 [('p', 'program', '',
242 _('comparison program to run'), _('CMD')),
244 _('comparison program to run'), _('CMD')),
243 ('o', 'option', [],
245 ('o', 'option', [],
244 _('pass option to comparison program'), _('OPT')),
246 _('pass option to comparison program'), _('OPT')),
245 ('r', 'rev', [], _('revision'), _('REV')),
247 ('r', 'rev', [], _('revision'), _('REV')),
246 ('c', 'change', '', _('change made by revision'), _('REV')),
248 ('c', 'change', '', _('change made by revision'), _('REV')),
247 ] + commands.walkopts,
249 ] + commands.walkopts,
248 _('hg extdiff [OPT]... [FILE]...'),
250 _('hg extdiff [OPT]... [FILE]...'),
249 inferrepo=True)
251 inferrepo=True)
250 def extdiff(ui, repo, *pats, **opts):
252 def extdiff(ui, repo, *pats, **opts):
251 '''use external program to diff repository (or selected files)
253 '''use external program to diff repository (or selected files)
252
254
253 Show differences between revisions for the specified files, using
255 Show differences between revisions for the specified files, using
254 an external program. The default program used is diff, with
256 an external program. The default program used is diff, with
255 default options "-Npru".
257 default options "-Npru".
256
258
257 To select a different program, use the -p/--program option. The
259 To select a different program, use the -p/--program option. The
258 program will be passed the names of two directories to compare. To
260 program will be passed the names of two directories to compare. To
259 pass additional options to the program, use -o/--option. These
261 pass additional options to the program, use -o/--option. These
260 will be passed before the names of the directories to compare.
262 will be passed before the names of the directories to compare.
261
263
262 When two revision arguments are given, then changes are shown
264 When two revision arguments are given, then changes are shown
263 between those revisions. If only one revision is specified then
265 between those revisions. If only one revision is specified then
264 that revision is compared to the working directory, and, when no
266 that revision is compared to the working directory, and, when no
265 revisions are specified, the working directory files are compared
267 revisions are specified, the working directory files are compared
266 to its parent.'''
268 to its parent.'''
267 program = opts.get('program')
269 program = opts.get('program')
268 option = opts.get('option')
270 option = opts.get('option')
269 if not program:
271 if not program:
270 program = 'diff'
272 program = 'diff'
271 option = option or ['-Npru']
273 option = option or ['-Npru']
272 cmdline = ' '.join(map(util.shellquote, [program] + option))
274 cmdline = ' '.join(map(util.shellquote, [program] + option))
273 return dodiff(ui, repo, cmdline, pats, opts)
275 return dodiff(ui, repo, cmdline, pats, opts)
274
276
275 def uisetup(ui):
277 def uisetup(ui):
276 for cmd, path in ui.configitems('extdiff'):
278 for cmd, path in ui.configitems('extdiff'):
277 if cmd.startswith('cmd.'):
279 if cmd.startswith('cmd.'):
278 cmd = cmd[4:]
280 cmd = cmd[4:]
279 if not path:
281 if not path:
280 path = util.findexe(cmd)
282 path = util.findexe(cmd)
281 if path is None:
283 if path is None:
282 path = filemerge.findexternaltool(ui, cmd) or cmd
284 path = filemerge.findexternaltool(ui, cmd) or cmd
283 diffopts = ui.config('extdiff', 'opts.' + cmd, '')
285 diffopts = ui.config('extdiff', 'opts.' + cmd, '')
284 cmdline = util.shellquote(path)
286 cmdline = util.shellquote(path)
285 if diffopts:
287 if diffopts:
286 cmdline += ' ' + diffopts
288 cmdline += ' ' + diffopts
287 elif cmd.startswith('opts.'):
289 elif cmd.startswith('opts.'):
288 continue
290 continue
289 else:
291 else:
290 if path:
292 if path:
291 # case "cmd = path opts"
293 # case "cmd = path opts"
292 cmdline = path
294 cmdline = path
293 diffopts = len(shlex.split(cmdline)) > 1
295 diffopts = len(shlex.split(cmdline)) > 1
294 else:
296 else:
295 # case "cmd ="
297 # case "cmd ="
296 path = util.findexe(cmd)
298 path = util.findexe(cmd)
297 if path is None:
299 if path is None:
298 path = filemerge.findexternaltool(ui, cmd) or cmd
300 path = filemerge.findexternaltool(ui, cmd) or cmd
299 cmdline = util.shellquote(path)
301 cmdline = util.shellquote(path)
300 diffopts = False
302 diffopts = False
301 # look for diff arguments in [diff-tools] then [merge-tools]
303 # look for diff arguments in [diff-tools] then [merge-tools]
302 if not diffopts:
304 if not diffopts:
303 args = ui.config('diff-tools', cmd+'.diffargs') or \
305 args = ui.config('diff-tools', cmd+'.diffargs') or \
304 ui.config('merge-tools', cmd+'.diffargs')
306 ui.config('merge-tools', cmd+'.diffargs')
305 if args:
307 if args:
306 cmdline += ' ' + args
308 cmdline += ' ' + args
307 def save(cmdline):
309 def save(cmdline):
308 '''use closure to save diff command to use'''
310 '''use closure to save diff command to use'''
309 def mydiff(ui, repo, *pats, **opts):
311 def mydiff(ui, repo, *pats, **opts):
310 options = ' '.join(map(util.shellquote, opts['option']))
312 options = ' '.join(map(util.shellquote, opts['option']))
311 if options:
313 if options:
312 options = ' ' + options
314 options = ' ' + options
313 return dodiff(ui, repo, cmdline + options, pats, opts)
315 return dodiff(ui, repo, cmdline + options, pats, opts)
314 doc = _('''\
316 doc = _('''\
315 use %(path)s to diff repository (or selected files)
317 use %(path)s to diff repository (or selected files)
316
318
317 Show differences between revisions for the specified files, using
319 Show differences between revisions for the specified files, using
318 the %(path)s program.
320 the %(path)s program.
319
321
320 When two revision arguments are given, then changes are shown
322 When two revision arguments are given, then changes are shown
321 between those revisions. If only one revision is specified then
323 between those revisions. If only one revision is specified then
322 that revision is compared to the working directory, and, when no
324 that revision is compared to the working directory, and, when no
323 revisions are specified, the working directory files are compared
325 revisions are specified, the working directory files are compared
324 to its parent.\
326 to its parent.\
325 ''') % {'path': util.uirepr(path)}
327 ''') % {'path': util.uirepr(path)}
326
328
327 # We must translate the docstring right away since it is
329 # We must translate the docstring right away since it is
328 # used as a format string. The string will unfortunately
330 # used as a format string. The string will unfortunately
329 # be translated again in commands.helpcmd and this will
331 # be translated again in commands.helpcmd and this will
330 # fail when the docstring contains non-ASCII characters.
332 # fail when the docstring contains non-ASCII characters.
331 # Decoding the string to a Unicode string here (using the
333 # Decoding the string to a Unicode string here (using the
332 # right encoding) prevents that.
334 # right encoding) prevents that.
333 mydiff.__doc__ = doc.decode(encoding.encoding)
335 mydiff.__doc__ = doc.decode(encoding.encoding)
334 return mydiff
336 return mydiff
335 cmdtable[cmd] = (save(cmdline),
337 cmdtable[cmd] = (save(cmdline),
336 cmdtable['extdiff'][1][1:],
338 cmdtable['extdiff'][1][1:],
337 _('hg %s [OPTION]... [FILE]...') % cmd)
339 _('hg %s [OPTION]... [FILE]...') % cmd)
@@ -1,302 +1,324
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
97 issue4463: usage of command line configuration without additional quoting
98
98
99 $ cat <<EOF >> $HGRCPATH
99 $ cat <<EOF >> $HGRCPATH
100 > [extdiff]
100 > [extdiff]
101 > cmd.4463a = echo
101 > cmd.4463a = echo
102 > opts.4463a = a-naked 'single quoted' "double quoted"
102 > opts.4463a = a-naked 'single quoted' "double quoted"
103 > 4463b = echo b-naked 'single quoted' "double quoted"
103 > 4463b = echo b-naked 'single quoted' "double quoted"
104 > echo =
104 > echo =
105 > EOF
105 > EOF
106 $ hg update -q -C 0
106 $ hg update -q -C 0
107 $ echo a >> a
107 $ echo a >> a
108 #if windows
108 #if windows
109 $ hg --debug 4463a | grep '^running'
109 $ hg --debug 4463a | grep '^running'
110 running 'echo a-naked \'single quoted\' "double quoted" *\\a *\\a' in */extdiff.* (glob)
110 running 'echo a-naked \'single quoted\' "double quoted" *\\a *\\a' in */extdiff.* (glob)
111 $ hg --debug 4463b | grep '^running'
111 $ hg --debug 4463b | grep '^running'
112 running 'echo b-naked \'single quoted\' "double quoted" *\\a *\\a' in */extdiff.* (glob)
112 running 'echo b-naked \'single quoted\' "double quoted" *\\a *\\a' in */extdiff.* (glob)
113 $ hg --debug echo | grep '^running'
113 $ hg --debug echo | grep '^running'
114 running '*echo* *\\a *\\a' in */extdiff.* (glob)
114 running '*echo* *\\a *\\a' in */extdiff.* (glob)
115 #else
115 #else
116 $ hg --debug 4463a | grep '^running'
116 $ hg --debug 4463a | grep '^running'
117 running 'echo a-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob)
117 running 'echo a-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob)
118 $ hg --debug 4463b | grep '^running'
118 $ hg --debug 4463b | grep '^running'
119 running 'echo b-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob)
119 running 'echo b-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob)
120 $ hg --debug echo | grep '^running'
120 $ hg --debug echo | grep '^running'
121 running '*echo */a $TESTTMP/a/a' in */extdiff.* (glob)
121 running '*echo */a $TESTTMP/a/a' in */extdiff.* (glob)
122 #endif
122 #endif
123
123
124 (getting options from other than extdiff section)
124 (getting options from other than extdiff section)
125
125
126 $ cat <<EOF >> $HGRCPATH
126 $ cat <<EOF >> $HGRCPATH
127 > [extdiff]
127 > [extdiff]
128 > # using diff-tools diffargs
128 > # using diff-tools diffargs
129 > 4463b2 = echo
129 > 4463b2 = echo
130 > # using merge-tools diffargs
130 > # using merge-tools diffargs
131 > 4463b3 = echo
131 > 4463b3 = echo
132 > # no diffargs
132 > # no diffargs
133 > 4463b4 = echo
133 > 4463b4 = echo
134 > [diff-tools]
134 > [diff-tools]
135 > 4463b2.diffargs = b2-naked 'single quoted' "double quoted"
135 > 4463b2.diffargs = b2-naked 'single quoted' "double quoted"
136 > [merge-tools]
136 > [merge-tools]
137 > 4463b3.diffargs = b3-naked 'single quoted' "double quoted"
137 > 4463b3.diffargs = b3-naked 'single quoted' "double quoted"
138 > EOF
138 > EOF
139 #if windows
139 #if windows
140 $ hg --debug 4463b2 | grep '^running'
140 $ hg --debug 4463b2 | grep '^running'
141 running 'echo b2-naked \'single quoted\' "double quoted" *\\a *\\a' in */extdiff.* (glob)
141 running 'echo b2-naked \'single quoted\' "double quoted" *\\a *\\a' in */extdiff.* (glob)
142 $ hg --debug 4463b3 | grep '^running'
142 $ hg --debug 4463b3 | grep '^running'
143 running 'echo b3-naked \'single quoted\' "double quoted" *\\a *\\a' in */extdiff.* (glob)
143 running 'echo b3-naked \'single quoted\' "double quoted" *\\a *\\a' in */extdiff.* (glob)
144 $ hg --debug 4463b4 | grep '^running'
144 $ hg --debug 4463b4 | grep '^running'
145 running 'echo *\\a *\\a' in */extdiff.* (glob)
145 running 'echo *\\a *\\a' in */extdiff.* (glob)
146 $ hg --debug 4463b4 --option b4-naked --option 'being quoted' | grep '^running'
146 $ hg --debug 4463b4 --option b4-naked --option 'being quoted' | grep '^running'
147 running 'echo b4-naked "being quoted" *\\a *\\a' in */extdiff.* (glob)
147 running 'echo b4-naked "being quoted" *\\a *\\a' in */extdiff.* (glob)
148 $ hg --debug extdiff -p echo --option echo-naked --option 'being quoted' | grep '^running'
148 $ hg --debug extdiff -p echo --option echo-naked --option 'being quoted' | grep '^running'
149 running 'echo echo-naked "being quoted" *\\a *\\a' in */extdiff.* (glob)
149 running 'echo echo-naked "being quoted" *\\a *\\a' in */extdiff.* (glob)
150 #else
150 #else
151 $ hg --debug 4463b2 | grep '^running'
151 $ hg --debug 4463b2 | grep '^running'
152 running 'echo b2-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob)
152 running 'echo b2-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob)
153 $ hg --debug 4463b3 | grep '^running'
153 $ hg --debug 4463b3 | grep '^running'
154 running 'echo b3-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob)
154 running 'echo b3-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob)
155 $ hg --debug 4463b4 | grep '^running'
155 $ hg --debug 4463b4 | grep '^running'
156 running 'echo */a $TESTTMP/a/a' in */extdiff.* (glob)
156 running 'echo */a $TESTTMP/a/a' in */extdiff.* (glob)
157 $ hg --debug 4463b4 --option b4-naked --option 'being quoted' | grep '^running'
157 $ hg --debug 4463b4 --option b4-naked --option 'being quoted' | grep '^running'
158 running "echo b4-naked 'being quoted' */a $TESTTMP/a/a" in */extdiff.* (glob)
158 running "echo b4-naked 'being quoted' */a $TESTTMP/a/a" in */extdiff.* (glob)
159 $ hg --debug extdiff -p echo --option echo-naked --option 'being quoted' | grep '^running'
159 $ hg --debug extdiff -p echo --option echo-naked --option 'being quoted' | grep '^running'
160 running "echo echo-naked 'being quoted' */a $TESTTMP/a/a" in */extdiff.* (glob)
160 running "echo echo-naked 'being quoted' */a $TESTTMP/a/a" in */extdiff.* (glob)
161 #endif
161 #endif
162
162
163 $ touch 'sp ace'
164 $ hg add 'sp ace'
165 $ hg ci -m 'sp ace'
166 created new head
167 $ echo > 'sp ace'
168
169 Test pre-72a89cf86fcd backward compatibility with half-baked manual quoting
170
171 $ cat <<EOF >> $HGRCPATH
172 > [extdiff]
173 > odd =
174 > [merge-tools]
175 > odd.diffargs = --foo='\$clabel' '\$clabel' "--bar=\$clabel" "\$clabel"
176 > odd.executable = echo
177 > EOF
178 #if windows
179 TODO
180 #else
181 $ hg --debug odd | grep '^running'
182 running "/bin/echo --foo='sp ace' 'sp ace' --bar='sp ace' 'sp ace'" in * (glob)
183 #endif
184
163 #if execbit
185 #if execbit
164
186
165 Test extdiff of multiple files in tmp dir:
187 Test extdiff of multiple files in tmp dir:
166
188
167 $ hg update -C 0 > /dev/null
189 $ hg update -C 0 > /dev/null
168 $ echo changed > a
190 $ echo changed > a
169 $ echo changed > b
191 $ echo changed > b
170 $ chmod +x b
192 $ chmod +x b
171
193
172 Diff in working directory, before:
194 Diff in working directory, before:
173
195
174 $ hg diff --git
196 $ hg diff --git
175 diff --git a/a b/a
197 diff --git a/a b/a
176 --- a/a
198 --- a/a
177 +++ b/a
199 +++ b/a
178 @@ -1,1 +1,1 @@
200 @@ -1,1 +1,1 @@
179 -a
201 -a
180 +changed
202 +changed
181 diff --git a/b b/b
203 diff --git a/b b/b
182 old mode 100644
204 old mode 100644
183 new mode 100755
205 new mode 100755
184 --- a/b
206 --- a/b
185 +++ b/b
207 +++ b/b
186 @@ -1,1 +1,1 @@
208 @@ -1,1 +1,1 @@
187 -b
209 -b
188 +changed
210 +changed
189
211
190
212
191 Edit with extdiff -p:
213 Edit with extdiff -p:
192
214
193 Prepare custom diff/edit tool:
215 Prepare custom diff/edit tool:
194
216
195 $ cat > 'diff tool.py' << EOT
217 $ cat > 'diff tool.py' << EOT
196 > #!/usr/bin/env python
218 > #!/usr/bin/env python
197 > import time
219 > import time
198 > time.sleep(1) # avoid unchanged-timestamp problems
220 > time.sleep(1) # avoid unchanged-timestamp problems
199 > file('a/a', 'ab').write('edited\n')
221 > file('a/a', 'ab').write('edited\n')
200 > file('a/b', 'ab').write('edited\n')
222 > file('a/b', 'ab').write('edited\n')
201 > EOT
223 > EOT
202
224
203 $ chmod +x 'diff tool.py'
225 $ chmod +x 'diff tool.py'
204
226
205 will change to /tmp/extdiff.TMP and populate directories a.TMP and a
227 will change to /tmp/extdiff.TMP and populate directories a.TMP and a
206 and start tool
228 and start tool
207
229
208 $ hg extdiff -p "`pwd`/diff tool.py"
230 $ hg extdiff -p "`pwd`/diff tool.py"
209 [1]
231 [1]
210
232
211 Diff in working directory, after:
233 Diff in working directory, after:
212
234
213 $ hg diff --git
235 $ hg diff --git
214 diff --git a/a b/a
236 diff --git a/a b/a
215 --- a/a
237 --- a/a
216 +++ b/a
238 +++ b/a
217 @@ -1,1 +1,2 @@
239 @@ -1,1 +1,2 @@
218 -a
240 -a
219 +changed
241 +changed
220 +edited
242 +edited
221 diff --git a/b b/b
243 diff --git a/b b/b
222 old mode 100644
244 old mode 100644
223 new mode 100755
245 new mode 100755
224 --- a/b
246 --- a/b
225 +++ b/b
247 +++ b/b
226 @@ -1,1 +1,2 @@
248 @@ -1,1 +1,2 @@
227 -b
249 -b
228 +changed
250 +changed
229 +edited
251 +edited
230
252
231 Test extdiff with --option:
253 Test extdiff with --option:
232
254
233 $ hg extdiff -p echo -o this -c 1
255 $ hg extdiff -p echo -o this -c 1
234 this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
256 this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
235 [1]
257 [1]
236
258
237 $ hg falabala -o this -c 1
259 $ hg falabala -o this -c 1
238 diffing this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
260 diffing this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
239 [1]
261 [1]
240
262
241 Test extdiff's handling of options with spaces in them:
263 Test extdiff's handling of options with spaces in them:
242
264
243 $ hg edspace -c 1
265 $ hg edspace -c 1
244 name <user@example.com> */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
266 name <user@example.com> */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
245 [1]
267 [1]
246
268
247 $ hg extdiff -p echo -o "name <user@example.com>" -c 1
269 $ hg extdiff -p echo -o "name <user@example.com>" -c 1
248 name <user@example.com> */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
270 name <user@example.com> */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
249 [1]
271 [1]
250
272
251 Test with revsets:
273 Test with revsets:
252
274
253 $ hg extdif -p echo -c "rev(1)"
275 $ hg extdif -p echo -c "rev(1)"
254 */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
276 */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
255 [1]
277 [1]
256
278
257 $ hg extdif -p echo -r "0::1"
279 $ hg extdif -p echo -r "0::1"
258 */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
280 */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
259 [1]
281 [1]
260
282
261 Fallback to merge-tools.tool.executable|regkey
283 Fallback to merge-tools.tool.executable|regkey
262 $ mkdir dir
284 $ mkdir dir
263 $ cat > 'dir/tool.sh' << EOF
285 $ cat > 'dir/tool.sh' << EOF
264 > #!/bin/sh
286 > #!/bin/sh
265 > echo "** custom diff **"
287 > echo "** custom diff **"
266 > EOF
288 > EOF
267 $ chmod +x dir/tool.sh
289 $ chmod +x dir/tool.sh
268 $ tool=`pwd`/dir/tool.sh
290 $ tool=`pwd`/dir/tool.sh
269 $ hg --debug tl --config extdiff.tl= --config merge-tools.tl.executable=$tool
291 $ hg --debug tl --config extdiff.tl= --config merge-tools.tl.executable=$tool
270 making snapshot of 2 files from rev * (glob)
292 making snapshot of 2 files from rev * (glob)
271 a
293 a
272 b
294 b
273 making snapshot of 2 files from working directory
295 making snapshot of 2 files from working directory
274 a
296 a
275 b
297 b
276 running '$TESTTMP/a/dir/tool.sh a.* a' in */extdiff.* (glob)
298 running '$TESTTMP/a/dir/tool.sh a.* a' in */extdiff.* (glob)
277 ** custom diff **
299 ** custom diff **
278 cleaning up temp directory
300 cleaning up temp directory
279 [1]
301 [1]
280
302
281 $ cd ..
303 $ cd ..
282
304
283 #endif
305 #endif
284
306
285 #if symlink
307 #if symlink
286
308
287 Test symlinks handling (issue1909)
309 Test symlinks handling (issue1909)
288
310
289 $ hg init testsymlinks
311 $ hg init testsymlinks
290 $ cd testsymlinks
312 $ cd testsymlinks
291 $ echo a > a
313 $ echo a > a
292 $ hg ci -Am adda
314 $ hg ci -Am adda
293 adding a
315 adding a
294 $ echo a >> a
316 $ echo a >> a
295 $ ln -s missing linka
317 $ ln -s missing linka
296 $ hg add linka
318 $ hg add linka
297 $ hg falabala -r 0 --traceback
319 $ hg falabala -r 0 --traceback
298 diffing testsymlinks.07f494440405 testsymlinks
320 diffing testsymlinks.07f494440405 testsymlinks
299 [1]
321 [1]
300 $ cd ..
322 $ cd ..
301
323
302 #endif
324 #endif
General Comments 0
You need to be logged in to leave comments. Login now