##// END OF EJS Templates
extdiff: fix defaulting to "diff" if no --program is given
Peter Arrenbrecht -
r9519:0d3c1aa9 default
parent child Browse files
Show More
@@ -1,229 +1,229
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, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
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 diff
11 to compare revisions, or revision with working directory. The external diff
12 programs are called with a configurable set of options and two
12 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 to configure new diff commands, so
16 The `extdiff' extension also allows 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 vdiff, runs kdiff3
26 # add new command called vdiff, runs kdiff3
27 vdiff = kdiff3
27 vdiff = kdiff3
28
28
29 # add new command called meld, runs meld (no need to name twice)
29 # add new command called meld, runs meld (no need to name twice)
30 meld =
30 meld =
31
31
32 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
32 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
33 # (see http://www.vim.org/scripts/script.php?script_id=102)
33 # (see http://www.vim.org/scripts/script.php?script_id=102)
34 # Non English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
34 # Non English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
35 # your .vimrc
35 # your .vimrc
36 vimdiff = gvim -f '+next' '+execute "DirDiff" argv(0) argv(1)'
36 vimdiff = gvim -f '+next' '+execute "DirDiff" argv(0) argv(1)'
37
37
38 You can use -I/-X and list of file or directory names like normal "hg
38 You can use -I/-X and list of file or directory names like normal "hg
39 diff" command. The `extdiff' extension makes snapshots of only needed
39 diff" command. The `extdiff' extension makes snapshots of only needed
40 files, so running the external diff program will actually be pretty
40 files, so running the external diff program will actually be pretty
41 fast (at least faster than having to compare the entire tree).
41 fast (at least faster than having to compare the entire tree).
42 '''
42 '''
43
43
44 from mercurial.i18n import _
44 from mercurial.i18n import _
45 from mercurial.node import short
45 from mercurial.node import short
46 from mercurial import cmdutil, util, commands
46 from mercurial import cmdutil, util, commands
47 import os, shlex, shutil, tempfile
47 import os, shlex, shutil, tempfile
48
48
49 def snapshot(ui, repo, files, node, tmproot):
49 def snapshot(ui, repo, files, node, tmproot):
50 '''snapshot files as of some revision
50 '''snapshot files as of some revision
51 if not using snapshot, -I/-X does not work and recursive diff
51 if not using snapshot, -I/-X does not work and recursive diff
52 in tools like kdiff3 and meld displays too many files.'''
52 in tools like kdiff3 and meld displays too many files.'''
53 dirname = os.path.basename(repo.root)
53 dirname = os.path.basename(repo.root)
54 if dirname == "":
54 if dirname == "":
55 dirname = "root"
55 dirname = "root"
56 if node is not None:
56 if node is not None:
57 dirname = '%s.%s' % (dirname, short(node))
57 dirname = '%s.%s' % (dirname, short(node))
58 base = os.path.join(tmproot, dirname)
58 base = os.path.join(tmproot, dirname)
59 os.mkdir(base)
59 os.mkdir(base)
60 if node is not None:
60 if node is not None:
61 ui.note(_('making snapshot of %d files from rev %s\n') %
61 ui.note(_('making snapshot of %d files from rev %s\n') %
62 (len(files), short(node)))
62 (len(files), short(node)))
63 else:
63 else:
64 ui.note(_('making snapshot of %d files from working directory\n') %
64 ui.note(_('making snapshot of %d files from working directory\n') %
65 (len(files)))
65 (len(files)))
66 wopener = util.opener(base)
66 wopener = util.opener(base)
67 fns_and_mtime = []
67 fns_and_mtime = []
68 ctx = repo[node]
68 ctx = repo[node]
69 for fn in files:
69 for fn in files:
70 wfn = util.pconvert(fn)
70 wfn = util.pconvert(fn)
71 if not wfn in ctx:
71 if not wfn in ctx:
72 # skipping new file after a merge ?
72 # skipping new file after a merge ?
73 continue
73 continue
74 ui.note(' %s\n' % wfn)
74 ui.note(' %s\n' % wfn)
75 dest = os.path.join(base, wfn)
75 dest = os.path.join(base, wfn)
76 fctx = ctx[wfn]
76 fctx = ctx[wfn]
77 data = repo.wwritedata(wfn, fctx.data())
77 data = repo.wwritedata(wfn, fctx.data())
78 if 'l' in fctx.flags():
78 if 'l' in fctx.flags():
79 wopener.symlink(data, wfn)
79 wopener.symlink(data, wfn)
80 else:
80 else:
81 wopener(wfn, 'w').write(data)
81 wopener(wfn, 'w').write(data)
82 if 'x' in fctx.flags():
82 if 'x' in fctx.flags():
83 util.set_flags(dest, False, True)
83 util.set_flags(dest, False, True)
84 if node is None:
84 if node is None:
85 fns_and_mtime.append((dest, repo.wjoin(fn), os.path.getmtime(dest)))
85 fns_and_mtime.append((dest, repo.wjoin(fn), os.path.getmtime(dest)))
86 return dirname, fns_and_mtime
86 return dirname, fns_and_mtime
87
87
88 def dodiff(ui, repo, diffcmd, diffopts, pats, opts):
88 def dodiff(ui, repo, diffcmd, diffopts, pats, opts):
89 '''Do the actuall diff:
89 '''Do the actuall diff:
90
90
91 - copy to a temp structure if diffing 2 internal revisions
91 - copy to a temp structure if diffing 2 internal revisions
92 - copy to a temp structure if diffing working revision with
92 - copy to a temp structure if diffing working revision with
93 another one and more than 1 file is changed
93 another one and more than 1 file is changed
94 - just invoke the diff for a single file in the working dir
94 - just invoke the diff for a single file in the working dir
95 '''
95 '''
96
96
97 revs = opts.get('rev')
97 revs = opts.get('rev')
98 change = opts.get('change')
98 change = opts.get('change')
99
99
100 if revs and change:
100 if revs and change:
101 msg = _('cannot specify --rev and --change at the same time')
101 msg = _('cannot specify --rev and --change at the same time')
102 raise util.Abort(msg)
102 raise util.Abort(msg)
103 elif change:
103 elif change:
104 node2 = repo.lookup(change)
104 node2 = repo.lookup(change)
105 node1 = repo[node2].parents()[0].node()
105 node1 = repo[node2].parents()[0].node()
106 else:
106 else:
107 node1, node2 = cmdutil.revpair(repo, revs)
107 node1, node2 = cmdutil.revpair(repo, revs)
108
108
109 matcher = cmdutil.match(repo, pats, opts)
109 matcher = cmdutil.match(repo, pats, opts)
110 modified, added, removed = repo.status(node1, node2, matcher)[:3]
110 modified, added, removed = repo.status(node1, node2, matcher)[:3]
111 if not (modified or added or removed):
111 if not (modified or added or removed):
112 return 0
112 return 0
113
113
114 tmproot = tempfile.mkdtemp(prefix='extdiff.')
114 tmproot = tempfile.mkdtemp(prefix='extdiff.')
115 dir2root = ''
115 dir2root = ''
116 try:
116 try:
117 # Always make a copy of node1
117 # Always make a copy of node1
118 dir1 = snapshot(ui, repo, modified + removed, node1, tmproot)[0]
118 dir1 = snapshot(ui, repo, modified + removed, node1, tmproot)[0]
119 changes = len(modified) + len(removed) + len(added)
119 changes = len(modified) + len(removed) + len(added)
120
120
121 # If node2 in not the wc or there is >1 change, copy it
121 # If node2 in not the wc or there is >1 change, copy it
122 if node2 or changes > 1:
122 if node2 or changes > 1:
123 dir2, fns_and_mtime = snapshot(ui, repo, modified + added, node2, tmproot)
123 dir2, fns_and_mtime = snapshot(ui, repo, modified + added, node2, tmproot)
124 else:
124 else:
125 # This lets the diff tool open the changed file directly
125 # This lets the diff tool open the changed file directly
126 dir2 = ''
126 dir2 = ''
127 dir2root = repo.root
127 dir2root = repo.root
128 fns_and_mtime = []
128 fns_and_mtime = []
129
129
130 # If only one change, diff the files instead of the directories
130 # If only one change, diff the files instead of the directories
131 if changes == 1 :
131 if changes == 1 :
132 if len(modified):
132 if len(modified):
133 dir1 = os.path.join(dir1, util.localpath(modified[0]))
133 dir1 = os.path.join(dir1, util.localpath(modified[0]))
134 dir2 = os.path.join(dir2root, dir2, util.localpath(modified[0]))
134 dir2 = os.path.join(dir2root, dir2, util.localpath(modified[0]))
135 elif len(removed) :
135 elif len(removed) :
136 dir1 = os.path.join(dir1, util.localpath(removed[0]))
136 dir1 = os.path.join(dir1, util.localpath(removed[0]))
137 dir2 = os.devnull
137 dir2 = os.devnull
138 else:
138 else:
139 dir1 = os.devnull
139 dir1 = os.devnull
140 dir2 = os.path.join(dir2root, dir2, util.localpath(added[0]))
140 dir2 = os.path.join(dir2root, dir2, util.localpath(added[0]))
141
141
142 cmdline = ('%s %s %s %s' %
142 cmdline = ('%s %s %s %s' %
143 (util.shellquote(diffcmd), ' '.join(diffopts),
143 (util.shellquote(diffcmd), ' '.join(diffopts),
144 util.shellquote(dir1), util.shellquote(dir2)))
144 util.shellquote(dir1), util.shellquote(dir2)))
145 ui.debug(_('running %r in %s\n') % (cmdline, tmproot))
145 ui.debug(_('running %r in %s\n') % (cmdline, tmproot))
146 util.system(cmdline, cwd=tmproot)
146 util.system(cmdline, cwd=tmproot)
147
147
148 for copy_fn, working_fn, mtime in fns_and_mtime:
148 for copy_fn, working_fn, mtime in fns_and_mtime:
149 if os.path.getmtime(copy_fn) != mtime:
149 if os.path.getmtime(copy_fn) != mtime:
150 ui.debug(_('file changed while diffing. '
150 ui.debug(_('file changed while diffing. '
151 'Overwriting: %s (src: %s)\n') % (working_fn, copy_fn))
151 'Overwriting: %s (src: %s)\n') % (working_fn, copy_fn))
152 util.copyfile(copy_fn, working_fn)
152 util.copyfile(copy_fn, working_fn)
153
153
154 return 1
154 return 1
155 finally:
155 finally:
156 ui.note(_('cleaning up temp directory\n'))
156 ui.note(_('cleaning up temp directory\n'))
157 shutil.rmtree(tmproot)
157 shutil.rmtree(tmproot)
158
158
159 def extdiff(ui, repo, *pats, **opts):
159 def extdiff(ui, repo, *pats, **opts):
160 '''use external program to diff repository (or selected files)
160 '''use external program to diff repository (or selected files)
161
161
162 Show differences between revisions for the specified files, using
162 Show differences between revisions for the specified files, using
163 an external program. The default program used is diff, with
163 an external program. The default program used is diff, with
164 default options "-Npru".
164 default options "-Npru".
165
165
166 To select a different program, use the -p/--program option. The
166 To select a different program, use the -p/--program option. The
167 program will be passed the names of two directories to compare. To
167 program will be passed the names of two directories to compare. To
168 pass additional options to the program, use -o/--option. These
168 pass additional options to the program, use -o/--option. These
169 will be passed before the names of the directories to compare.
169 will be passed before the names of the directories to compare.
170
170
171 When two revision arguments are given, then changes are shown
171 When two revision arguments are given, then changes are shown
172 between those revisions. If only one revision is specified then
172 between those revisions. If only one revision is specified then
173 that revision is compared to the working directory, and, when no
173 that revision is compared to the working directory, and, when no
174 revisions are specified, the working directory files are compared
174 revisions are specified, the working directory files are compared
175 to its parent.'''
175 to its parent.'''
176 program = opts['program'] or 'diff'
176 program = opts.get('program')
177 if opts['program']:
177 option = opts.get('option')
178 option = opts['option']
178 if not program:
179 else:
179 program = 'diff'
180 option = opts['option'] or ['-Npru']
180 option = option or ['-Npru']
181 return dodiff(ui, repo, program, option, pats, opts)
181 return dodiff(ui, repo, program, option, pats, opts)
182
182
183 cmdtable = {
183 cmdtable = {
184 "extdiff":
184 "extdiff":
185 (extdiff,
185 (extdiff,
186 [('p', 'program', '', _('comparison program to run')),
186 [('p', 'program', '', _('comparison program to run')),
187 ('o', 'option', [], _('pass option to comparison program')),
187 ('o', 'option', [], _('pass option to comparison program')),
188 ('r', 'rev', [], _('revision')),
188 ('r', 'rev', [], _('revision')),
189 ('c', 'change', '', _('change made by revision')),
189 ('c', 'change', '', _('change made by revision')),
190 ] + commands.walkopts,
190 ] + commands.walkopts,
191 _('hg extdiff [OPT]... [FILE]...')),
191 _('hg extdiff [OPT]... [FILE]...')),
192 }
192 }
193
193
194 def uisetup(ui):
194 def uisetup(ui):
195 for cmd, path in ui.configitems('extdiff'):
195 for cmd, path in ui.configitems('extdiff'):
196 if cmd.startswith('cmd.'):
196 if cmd.startswith('cmd.'):
197 cmd = cmd[4:]
197 cmd = cmd[4:]
198 if not path: path = cmd
198 if not path: path = cmd
199 diffopts = ui.config('extdiff', 'opts.' + cmd, '')
199 diffopts = ui.config('extdiff', 'opts.' + cmd, '')
200 diffopts = diffopts and [diffopts] or []
200 diffopts = diffopts and [diffopts] or []
201 elif cmd.startswith('opts.'):
201 elif cmd.startswith('opts.'):
202 continue
202 continue
203 else:
203 else:
204 # command = path opts
204 # command = path opts
205 if path:
205 if path:
206 diffopts = shlex.split(path)
206 diffopts = shlex.split(path)
207 path = diffopts.pop(0)
207 path = diffopts.pop(0)
208 else:
208 else:
209 path, diffopts = cmd, []
209 path, diffopts = cmd, []
210 def save(cmd, path, diffopts):
210 def save(cmd, path, diffopts):
211 '''use closure to save diff command to use'''
211 '''use closure to save diff command to use'''
212 def mydiff(ui, repo, *pats, **opts):
212 def mydiff(ui, repo, *pats, **opts):
213 return dodiff(ui, repo, path, diffopts, pats, opts)
213 return dodiff(ui, repo, path, diffopts, pats, opts)
214 mydiff.__doc__ = '''use %(path)s to diff repository (or selected files)
214 mydiff.__doc__ = '''use %(path)s to diff repository (or selected files)
215
215
216 Show differences between revisions for the specified
216 Show differences between revisions for the specified
217 files, using the %(path)s program.
217 files, using the %(path)s program.
218
218
219 When two revision arguments are given, then changes are
219 When two revision arguments are given, then changes are
220 shown between those revisions. If only one revision is
220 shown between those revisions. If only one revision is
221 specified then that revision is compared to the working
221 specified then that revision is compared to the working
222 directory, and, when no revisions are specified, the
222 directory, and, when no revisions are specified, the
223 working directory files are compared to its parent.''' % {
223 working directory files are compared to its parent.''' % {
224 'path': util.uirepr(path),
224 'path': util.uirepr(path),
225 }
225 }
226 return mydiff
226 return mydiff
227 cmdtable[cmd] = (save(cmd, path, diffopts),
227 cmdtable[cmd] = (save(cmd, path, diffopts),
228 cmdtable['extdiff'][1][1:],
228 cmdtable['extdiff'][1][1:],
229 _('hg %s [OPTION]... [FILE]...') % cmd)
229 _('hg %s [OPTION]... [FILE]...') % cmd)
General Comments 0
You need to be logged in to leave comments. Login now