##// END OF EJS Templates
configitems: register the 'extdata.*.diffargs' config
Boris Feld -
r34779:bf138446 default
parent child Browse files
Show More
@@ -1,409 +1,414
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 __future__ import absolute_import
63 from __future__ import absolute_import
64
64
65 import os
65 import os
66 import re
66 import re
67 import shutil
67 import shutil
68 import tempfile
68 import tempfile
69 from mercurial.i18n import _
69 from mercurial.i18n import _
70 from mercurial.node import (
70 from mercurial.node import (
71 nullid,
71 nullid,
72 short,
72 short,
73 )
73 )
74 from mercurial import (
74 from mercurial import (
75 archival,
75 archival,
76 cmdutil,
76 cmdutil,
77 error,
77 error,
78 filemerge,
78 filemerge,
79 pycompat,
79 pycompat,
80 registrar,
80 registrar,
81 scmutil,
81 scmutil,
82 util,
82 util,
83 )
83 )
84
84
85 cmdtable = {}
85 cmdtable = {}
86 command = registrar.command(cmdtable)
86 command = registrar.command(cmdtable)
87
87
88 configtable = {}
88 configtable = {}
89 configitem = registrar.configitem(configtable)
89 configitem = registrar.configitem(configtable)
90
90
91 configitem('extdiff', r'opts\..*',
91 configitem('extdiff', r'opts\..*',
92 default='',
92 default='',
93 generic=True,
93 generic=True,
94 )
94 )
95
95
96 configitem('diff-tools', r'.*\.diffargs$',
97 default=None,
98 generic=True,
99 )
100
96 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
101 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
97 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
102 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
98 # be specifying the version(s) of Mercurial they are tested with, or
103 # be specifying the version(s) of Mercurial they are tested with, or
99 # leave the attribute unspecified.
104 # leave the attribute unspecified.
100 testedwith = 'ships-with-hg-core'
105 testedwith = 'ships-with-hg-core'
101
106
102 def snapshot(ui, repo, files, node, tmproot, listsubrepos):
107 def snapshot(ui, repo, files, node, tmproot, listsubrepos):
103 '''snapshot files as of some revision
108 '''snapshot files as of some revision
104 if not using snapshot, -I/-X does not work and recursive diff
109 if not using snapshot, -I/-X does not work and recursive diff
105 in tools like kdiff3 and meld displays too many files.'''
110 in tools like kdiff3 and meld displays too many files.'''
106 dirname = os.path.basename(repo.root)
111 dirname = os.path.basename(repo.root)
107 if dirname == "":
112 if dirname == "":
108 dirname = "root"
113 dirname = "root"
109 if node is not None:
114 if node is not None:
110 dirname = '%s.%s' % (dirname, short(node))
115 dirname = '%s.%s' % (dirname, short(node))
111 base = os.path.join(tmproot, dirname)
116 base = os.path.join(tmproot, dirname)
112 os.mkdir(base)
117 os.mkdir(base)
113 fnsandstat = []
118 fnsandstat = []
114
119
115 if node is not None:
120 if node is not None:
116 ui.note(_('making snapshot of %d files from rev %s\n') %
121 ui.note(_('making snapshot of %d files from rev %s\n') %
117 (len(files), short(node)))
122 (len(files), short(node)))
118 else:
123 else:
119 ui.note(_('making snapshot of %d files from working directory\n') %
124 ui.note(_('making snapshot of %d files from working directory\n') %
120 (len(files)))
125 (len(files)))
121
126
122 if files:
127 if files:
123 repo.ui.setconfig("ui", "archivemeta", False)
128 repo.ui.setconfig("ui", "archivemeta", False)
124
129
125 archival.archive(repo, base, node, 'files',
130 archival.archive(repo, base, node, 'files',
126 matchfn=scmutil.matchfiles(repo, files),
131 matchfn=scmutil.matchfiles(repo, files),
127 subrepos=listsubrepos)
132 subrepos=listsubrepos)
128
133
129 for fn in sorted(files):
134 for fn in sorted(files):
130 wfn = util.pconvert(fn)
135 wfn = util.pconvert(fn)
131 ui.note(' %s\n' % wfn)
136 ui.note(' %s\n' % wfn)
132
137
133 if node is None:
138 if node is None:
134 dest = os.path.join(base, wfn)
139 dest = os.path.join(base, wfn)
135
140
136 fnsandstat.append((dest, repo.wjoin(fn), os.lstat(dest)))
141 fnsandstat.append((dest, repo.wjoin(fn), os.lstat(dest)))
137 return dirname, fnsandstat
142 return dirname, fnsandstat
138
143
139 def dodiff(ui, repo, cmdline, pats, opts):
144 def dodiff(ui, repo, cmdline, pats, opts):
140 '''Do the actual diff:
145 '''Do the actual diff:
141
146
142 - copy to a temp structure if diffing 2 internal revisions
147 - copy to a temp structure if diffing 2 internal revisions
143 - copy to a temp structure if diffing working revision with
148 - copy to a temp structure if diffing working revision with
144 another one and more than 1 file is changed
149 another one and more than 1 file is changed
145 - just invoke the diff for a single file in the working dir
150 - just invoke the diff for a single file in the working dir
146 '''
151 '''
147
152
148 revs = opts.get('rev')
153 revs = opts.get('rev')
149 change = opts.get('change')
154 change = opts.get('change')
150 do3way = '$parent2' in cmdline
155 do3way = '$parent2' in cmdline
151
156
152 if revs and change:
157 if revs and change:
153 msg = _('cannot specify --rev and --change at the same time')
158 msg = _('cannot specify --rev and --change at the same time')
154 raise error.Abort(msg)
159 raise error.Abort(msg)
155 elif change:
160 elif change:
156 node2 = scmutil.revsingle(repo, change, None).node()
161 node2 = scmutil.revsingle(repo, change, None).node()
157 node1a, node1b = repo.changelog.parents(node2)
162 node1a, node1b = repo.changelog.parents(node2)
158 else:
163 else:
159 node1a, node2 = scmutil.revpair(repo, revs)
164 node1a, node2 = scmutil.revpair(repo, revs)
160 if not revs:
165 if not revs:
161 node1b = repo.dirstate.p2()
166 node1b = repo.dirstate.p2()
162 else:
167 else:
163 node1b = nullid
168 node1b = nullid
164
169
165 # Disable 3-way merge if there is only one parent
170 # Disable 3-way merge if there is only one parent
166 if do3way:
171 if do3way:
167 if node1b == nullid:
172 if node1b == nullid:
168 do3way = False
173 do3way = False
169
174
170 subrepos=opts.get('subrepos')
175 subrepos=opts.get('subrepos')
171
176
172 matcher = scmutil.match(repo[node2], pats, opts)
177 matcher = scmutil.match(repo[node2], pats, opts)
173
178
174 if opts.get('patch'):
179 if opts.get('patch'):
175 if subrepos:
180 if subrepos:
176 raise error.Abort(_('--patch cannot be used with --subrepos'))
181 raise error.Abort(_('--patch cannot be used with --subrepos'))
177 if node2 is None:
182 if node2 is None:
178 raise error.Abort(_('--patch requires two revisions'))
183 raise error.Abort(_('--patch requires two revisions'))
179 else:
184 else:
180 mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher,
185 mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher,
181 listsubrepos=subrepos)[:3])
186 listsubrepos=subrepos)[:3])
182 if do3way:
187 if do3way:
183 mod_b, add_b, rem_b = map(set,
188 mod_b, add_b, rem_b = map(set,
184 repo.status(node1b, node2, matcher,
189 repo.status(node1b, node2, matcher,
185 listsubrepos=subrepos)[:3])
190 listsubrepos=subrepos)[:3])
186 else:
191 else:
187 mod_b, add_b, rem_b = set(), set(), set()
192 mod_b, add_b, rem_b = set(), set(), set()
188 modadd = mod_a | add_a | mod_b | add_b
193 modadd = mod_a | add_a | mod_b | add_b
189 common = modadd | rem_a | rem_b
194 common = modadd | rem_a | rem_b
190 if not common:
195 if not common:
191 return 0
196 return 0
192
197
193 tmproot = tempfile.mkdtemp(prefix='extdiff.')
198 tmproot = tempfile.mkdtemp(prefix='extdiff.')
194 try:
199 try:
195 if not opts.get('patch'):
200 if not opts.get('patch'):
196 # Always make a copy of node1a (and node1b, if applicable)
201 # Always make a copy of node1a (and node1b, if applicable)
197 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
202 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
198 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot,
203 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot,
199 subrepos)[0]
204 subrepos)[0]
200 rev1a = '@%d' % repo[node1a].rev()
205 rev1a = '@%d' % repo[node1a].rev()
201 if do3way:
206 if do3way:
202 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
207 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
203 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot,
208 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot,
204 subrepos)[0]
209 subrepos)[0]
205 rev1b = '@%d' % repo[node1b].rev()
210 rev1b = '@%d' % repo[node1b].rev()
206 else:
211 else:
207 dir1b = None
212 dir1b = None
208 rev1b = ''
213 rev1b = ''
209
214
210 fnsandstat = []
215 fnsandstat = []
211
216
212 # If node2 in not the wc or there is >1 change, copy it
217 # If node2 in not the wc or there is >1 change, copy it
213 dir2root = ''
218 dir2root = ''
214 rev2 = ''
219 rev2 = ''
215 if node2:
220 if node2:
216 dir2 = snapshot(ui, repo, modadd, node2, tmproot, subrepos)[0]
221 dir2 = snapshot(ui, repo, modadd, node2, tmproot, subrepos)[0]
217 rev2 = '@%d' % repo[node2].rev()
222 rev2 = '@%d' % repo[node2].rev()
218 elif len(common) > 1:
223 elif len(common) > 1:
219 #we only actually need to get the files to copy back to
224 #we only actually need to get the files to copy back to
220 #the working dir in this case (because the other cases
225 #the working dir in this case (because the other cases
221 #are: diffing 2 revisions or single file -- in which case
226 #are: diffing 2 revisions or single file -- in which case
222 #the file is already directly passed to the diff tool).
227 #the file is already directly passed to the diff tool).
223 dir2, fnsandstat = snapshot(ui, repo, modadd, None, tmproot,
228 dir2, fnsandstat = snapshot(ui, repo, modadd, None, tmproot,
224 subrepos)
229 subrepos)
225 else:
230 else:
226 # This lets the diff tool open the changed file directly
231 # This lets the diff tool open the changed file directly
227 dir2 = ''
232 dir2 = ''
228 dir2root = repo.root
233 dir2root = repo.root
229
234
230 label1a = rev1a
235 label1a = rev1a
231 label1b = rev1b
236 label1b = rev1b
232 label2 = rev2
237 label2 = rev2
233
238
234 # If only one change, diff the files instead of the directories
239 # If only one change, diff the files instead of the directories
235 # Handle bogus modifies correctly by checking if the files exist
240 # Handle bogus modifies correctly by checking if the files exist
236 if len(common) == 1:
241 if len(common) == 1:
237 common_file = util.localpath(common.pop())
242 common_file = util.localpath(common.pop())
238 dir1a = os.path.join(tmproot, dir1a, common_file)
243 dir1a = os.path.join(tmproot, dir1a, common_file)
239 label1a = common_file + rev1a
244 label1a = common_file + rev1a
240 if not os.path.isfile(dir1a):
245 if not os.path.isfile(dir1a):
241 dir1a = os.devnull
246 dir1a = os.devnull
242 if do3way:
247 if do3way:
243 dir1b = os.path.join(tmproot, dir1b, common_file)
248 dir1b = os.path.join(tmproot, dir1b, common_file)
244 label1b = common_file + rev1b
249 label1b = common_file + rev1b
245 if not os.path.isfile(dir1b):
250 if not os.path.isfile(dir1b):
246 dir1b = os.devnull
251 dir1b = os.devnull
247 dir2 = os.path.join(dir2root, dir2, common_file)
252 dir2 = os.path.join(dir2root, dir2, common_file)
248 label2 = common_file + rev2
253 label2 = common_file + rev2
249 else:
254 else:
250 template = 'hg-%h.patch'
255 template = 'hg-%h.patch'
251 cmdutil.export(repo, [repo[node1a].rev(), repo[node2].rev()],
256 cmdutil.export(repo, [repo[node1a].rev(), repo[node2].rev()],
252 fntemplate=repo.vfs.reljoin(tmproot, template),
257 fntemplate=repo.vfs.reljoin(tmproot, template),
253 match=matcher)
258 match=matcher)
254 label1a = cmdutil.makefilename(repo, template, node1a)
259 label1a = cmdutil.makefilename(repo, template, node1a)
255 label2 = cmdutil.makefilename(repo, template, node2)
260 label2 = cmdutil.makefilename(repo, template, node2)
256 dir1a = repo.vfs.reljoin(tmproot, label1a)
261 dir1a = repo.vfs.reljoin(tmproot, label1a)
257 dir2 = repo.vfs.reljoin(tmproot, label2)
262 dir2 = repo.vfs.reljoin(tmproot, label2)
258 dir1b = None
263 dir1b = None
259 label1b = None
264 label1b = None
260 fnsandstat = []
265 fnsandstat = []
261
266
262 # Function to quote file/dir names in the argument string.
267 # Function to quote file/dir names in the argument string.
263 # When not operating in 3-way mode, an empty string is
268 # When not operating in 3-way mode, an empty string is
264 # returned for parent2
269 # returned for parent2
265 replace = {'parent': dir1a, 'parent1': dir1a, 'parent2': dir1b,
270 replace = {'parent': dir1a, 'parent1': dir1a, 'parent2': dir1b,
266 'plabel1': label1a, 'plabel2': label1b,
271 'plabel1': label1a, 'plabel2': label1b,
267 'clabel': label2, 'child': dir2,
272 'clabel': label2, 'child': dir2,
268 'root': repo.root}
273 'root': repo.root}
269 def quote(match):
274 def quote(match):
270 pre = match.group(2)
275 pre = match.group(2)
271 key = match.group(3)
276 key = match.group(3)
272 if not do3way and key == 'parent2':
277 if not do3way and key == 'parent2':
273 return pre
278 return pre
274 return pre + util.shellquote(replace[key])
279 return pre + util.shellquote(replace[key])
275
280
276 # Match parent2 first, so 'parent1?' will match both parent1 and parent
281 # Match parent2 first, so 'parent1?' will match both parent1 and parent
277 regex = (r'''(['"]?)([^\s'"$]*)'''
282 regex = (r'''(['"]?)([^\s'"$]*)'''
278 r'\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)\1')
283 r'\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)\1')
279 if not do3way and not re.search(regex, cmdline):
284 if not do3way and not re.search(regex, cmdline):
280 cmdline += ' $parent1 $child'
285 cmdline += ' $parent1 $child'
281 cmdline = re.sub(regex, quote, cmdline)
286 cmdline = re.sub(regex, quote, cmdline)
282
287
283 ui.debug('running %r in %s\n' % (cmdline, tmproot))
288 ui.debug('running %r in %s\n' % (cmdline, tmproot))
284 ui.system(cmdline, cwd=tmproot, blockedtag='extdiff')
289 ui.system(cmdline, cwd=tmproot, blockedtag='extdiff')
285
290
286 for copy_fn, working_fn, st in fnsandstat:
291 for copy_fn, working_fn, st in fnsandstat:
287 cpstat = os.lstat(copy_fn)
292 cpstat = os.lstat(copy_fn)
288 # Some tools copy the file and attributes, so mtime may not detect
293 # Some tools copy the file and attributes, so mtime may not detect
289 # all changes. A size check will detect more cases, but not all.
294 # all changes. A size check will detect more cases, but not all.
290 # The only certain way to detect every case is to diff all files,
295 # The only certain way to detect every case is to diff all files,
291 # which could be expensive.
296 # which could be expensive.
292 # copyfile() carries over the permission, so the mode check could
297 # copyfile() carries over the permission, so the mode check could
293 # be in an 'elif' branch, but for the case where the file has
298 # be in an 'elif' branch, but for the case where the file has
294 # changed without affecting mtime or size.
299 # changed without affecting mtime or size.
295 if (cpstat.st_mtime != st.st_mtime or cpstat.st_size != st.st_size
300 if (cpstat.st_mtime != st.st_mtime or cpstat.st_size != st.st_size
296 or (cpstat.st_mode & 0o100) != (st.st_mode & 0o100)):
301 or (cpstat.st_mode & 0o100) != (st.st_mode & 0o100)):
297 ui.debug('file changed while diffing. '
302 ui.debug('file changed while diffing. '
298 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn))
303 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn))
299 util.copyfile(copy_fn, working_fn)
304 util.copyfile(copy_fn, working_fn)
300
305
301 return 1
306 return 1
302 finally:
307 finally:
303 ui.note(_('cleaning up temp directory\n'))
308 ui.note(_('cleaning up temp directory\n'))
304 shutil.rmtree(tmproot)
309 shutil.rmtree(tmproot)
305
310
306 extdiffopts = [
311 extdiffopts = [
307 ('o', 'option', [],
312 ('o', 'option', [],
308 _('pass option to comparison program'), _('OPT')),
313 _('pass option to comparison program'), _('OPT')),
309 ('r', 'rev', [], _('revision'), _('REV')),
314 ('r', 'rev', [], _('revision'), _('REV')),
310 ('c', 'change', '', _('change made by revision'), _('REV')),
315 ('c', 'change', '', _('change made by revision'), _('REV')),
311 ('', 'patch', None, _('compare patches for two revisions'))
316 ('', 'patch', None, _('compare patches for two revisions'))
312 ] + cmdutil.walkopts + cmdutil.subrepoopts
317 ] + cmdutil.walkopts + cmdutil.subrepoopts
313
318
314 @command('extdiff',
319 @command('extdiff',
315 [('p', 'program', '', _('comparison program to run'), _('CMD')),
320 [('p', 'program', '', _('comparison program to run'), _('CMD')),
316 ] + extdiffopts,
321 ] + extdiffopts,
317 _('hg extdiff [OPT]... [FILE]...'),
322 _('hg extdiff [OPT]... [FILE]...'),
318 inferrepo=True)
323 inferrepo=True)
319 def extdiff(ui, repo, *pats, **opts):
324 def extdiff(ui, repo, *pats, **opts):
320 '''use external program to diff repository (or selected files)
325 '''use external program to diff repository (or selected files)
321
326
322 Show differences between revisions for the specified files, using
327 Show differences between revisions for the specified files, using
323 an external program. The default program used is diff, with
328 an external program. The default program used is diff, with
324 default options "-Npru".
329 default options "-Npru".
325
330
326 To select a different program, use the -p/--program option. The
331 To select a different program, use the -p/--program option. The
327 program will be passed the names of two directories to compare. To
332 program will be passed the names of two directories to compare. To
328 pass additional options to the program, use -o/--option. These
333 pass additional options to the program, use -o/--option. These
329 will be passed before the names of the directories to compare.
334 will be passed before the names of the directories to compare.
330
335
331 When two revision arguments are given, then changes are shown
336 When two revision arguments are given, then changes are shown
332 between those revisions. If only one revision is specified then
337 between those revisions. If only one revision is specified then
333 that revision is compared to the working directory, and, when no
338 that revision is compared to the working directory, and, when no
334 revisions are specified, the working directory files are compared
339 revisions are specified, the working directory files are compared
335 to its parent.'''
340 to its parent.'''
336 program = opts.get('program')
341 program = opts.get('program')
337 option = opts.get('option')
342 option = opts.get('option')
338 if not program:
343 if not program:
339 program = 'diff'
344 program = 'diff'
340 option = option or ['-Npru']
345 option = option or ['-Npru']
341 cmdline = ' '.join(map(util.shellquote, [program] + option))
346 cmdline = ' '.join(map(util.shellquote, [program] + option))
342 return dodiff(ui, repo, cmdline, pats, opts)
347 return dodiff(ui, repo, cmdline, pats, opts)
343
348
344 class savedcmd(object):
349 class savedcmd(object):
345 """use external program to diff repository (or selected files)
350 """use external program to diff repository (or selected files)
346
351
347 Show differences between revisions for the specified files, using
352 Show differences between revisions for the specified files, using
348 the following program::
353 the following program::
349
354
350 %(path)s
355 %(path)s
351
356
352 When two revision arguments are given, then changes are shown
357 When two revision arguments are given, then changes are shown
353 between those revisions. If only one revision is specified then
358 between those revisions. If only one revision is specified then
354 that revision is compared to the working directory, and, when no
359 that revision is compared to the working directory, and, when no
355 revisions are specified, the working directory files are compared
360 revisions are specified, the working directory files are compared
356 to its parent.
361 to its parent.
357 """
362 """
358
363
359 def __init__(self, path, cmdline):
364 def __init__(self, path, cmdline):
360 # We can't pass non-ASCII through docstrings (and path is
365 # We can't pass non-ASCII through docstrings (and path is
361 # in an unknown encoding anyway)
366 # in an unknown encoding anyway)
362 docpath = util.escapestr(path)
367 docpath = util.escapestr(path)
363 self.__doc__ = self.__doc__ % {'path': util.uirepr(docpath)}
368 self.__doc__ = self.__doc__ % {'path': util.uirepr(docpath)}
364 self._cmdline = cmdline
369 self._cmdline = cmdline
365
370
366 def __call__(self, ui, repo, *pats, **opts):
371 def __call__(self, ui, repo, *pats, **opts):
367 options = ' '.join(map(util.shellquote, opts['option']))
372 options = ' '.join(map(util.shellquote, opts['option']))
368 if options:
373 if options:
369 options = ' ' + options
374 options = ' ' + options
370 return dodiff(ui, repo, self._cmdline + options, pats, opts)
375 return dodiff(ui, repo, self._cmdline + options, pats, opts)
371
376
372 def uisetup(ui):
377 def uisetup(ui):
373 for cmd, path in ui.configitems('extdiff'):
378 for cmd, path in ui.configitems('extdiff'):
374 path = util.expandpath(path)
379 path = util.expandpath(path)
375 if cmd.startswith('cmd.'):
380 if cmd.startswith('cmd.'):
376 cmd = cmd[4:]
381 cmd = cmd[4:]
377 if not path:
382 if not path:
378 path = util.findexe(cmd)
383 path = util.findexe(cmd)
379 if path is None:
384 if path is None:
380 path = filemerge.findexternaltool(ui, cmd) or cmd
385 path = filemerge.findexternaltool(ui, cmd) or cmd
381 diffopts = ui.config('extdiff', 'opts.' + cmd)
386 diffopts = ui.config('extdiff', 'opts.' + cmd)
382 cmdline = util.shellquote(path)
387 cmdline = util.shellquote(path)
383 if diffopts:
388 if diffopts:
384 cmdline += ' ' + diffopts
389 cmdline += ' ' + diffopts
385 elif cmd.startswith('opts.'):
390 elif cmd.startswith('opts.'):
386 continue
391 continue
387 else:
392 else:
388 if path:
393 if path:
389 # case "cmd = path opts"
394 # case "cmd = path opts"
390 cmdline = path
395 cmdline = path
391 diffopts = len(pycompat.shlexsplit(cmdline)) > 1
396 diffopts = len(pycompat.shlexsplit(cmdline)) > 1
392 else:
397 else:
393 # case "cmd ="
398 # case "cmd ="
394 path = util.findexe(cmd)
399 path = util.findexe(cmd)
395 if path is None:
400 if path is None:
396 path = filemerge.findexternaltool(ui, cmd) or cmd
401 path = filemerge.findexternaltool(ui, cmd) or cmd
397 cmdline = util.shellquote(path)
402 cmdline = util.shellquote(path)
398 diffopts = False
403 diffopts = False
399 # look for diff arguments in [diff-tools] then [merge-tools]
404 # look for diff arguments in [diff-tools] then [merge-tools]
400 if not diffopts:
405 if not diffopts:
401 args = ui.config('diff-tools', cmd+'.diffargs') or \
406 args = ui.config('diff-tools', cmd+'.diffargs') or \
402 ui.config('merge-tools', cmd+'.diffargs')
407 ui.config('merge-tools', cmd+'.diffargs')
403 if args:
408 if args:
404 cmdline += ' ' + args
409 cmdline += ' ' + args
405 command(cmd, extdiffopts[:], _('hg %s [OPTION]... [FILE]...') % cmd,
410 command(cmd, extdiffopts[:], _('hg %s [OPTION]... [FILE]...') % cmd,
406 inferrepo=True)(savedcmd(path, cmdline))
411 inferrepo=True)(savedcmd(path, cmdline))
407
412
408 # tell hggettext to extract docstrings from these functions:
413 # tell hggettext to extract docstrings from these functions:
409 i18nfunctions = [savedcmd]
414 i18nfunctions = [savedcmd]
General Comments 0
You need to be logged in to leave comments. Login now