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