##// END OF EJS Templates
extdiff: add repository root as a variable...
Steven Stallion -
r14045:1c38777f default
parent child Browse files
Show More
@@ -1,326 +1,328 b''
1 1 # extdiff.py - external diff program support for mercurial
2 2 #
3 3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''command to allow external programs to compare revisions
9 9
10 10 The extdiff Mercurial extension allows you to use external programs
11 11 to compare revisions, or revision with working directory. The external
12 12 diff programs are called with a configurable set of options and two
13 13 non-option arguments: paths to directories containing snapshots of
14 14 files to compare.
15 15
16 16 The extdiff extension also allows to configure new diff commands, so
17 17 you do not need to type :hg:`extdiff -p kdiff3` always. ::
18 18
19 19 [extdiff]
20 20 # add new command that runs GNU diff(1) in 'context diff' mode
21 21 cdiff = gdiff -Nprc5
22 22 ## or the old way:
23 23 #cmd.cdiff = gdiff
24 24 #opts.cdiff = -Nprc5
25 25
26 26 # add new command called vdiff, runs kdiff3
27 27 vdiff = kdiff3
28 28
29 29 # add new command called meld, runs meld (no need to name twice)
30 30 meld =
31 31
32 32 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
33 33 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
34 34 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
35 35 # your .vimrc
36 36 vimdiff = gvim -f '+next' '+execute "DirDiff" argv(0) argv(1)'
37 37
38 38 Tool arguments can include variables that are expanded at runtime::
39 39
40 40 $parent1, $plabel1 - filename, descriptive label of first parent
41 41 $child, $clabel - filename, descriptive label of child revision
42 42 $parent2, $plabel2 - filename, descriptive label of second parent
43 $root - repository root
43 44 $parent is an alias for $parent1.
44 45
45 46 The extdiff extension will look in your [diff-tools] and [merge-tools]
46 47 sections for diff tool arguments, when none are specified in [extdiff].
47 48
48 49 ::
49 50
50 51 [extdiff]
51 52 kdiff3 =
52 53
53 54 [diff-tools]
54 55 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
55 56
56 57 You can use -I/-X and list of file or directory names like normal
57 58 :hg:`diff` command. The extdiff extension makes snapshots of only
58 59 needed files, so running the external diff program will actually be
59 60 pretty fast (at least faster than having to compare the entire tree).
60 61 '''
61 62
62 63 from mercurial.i18n import _
63 64 from mercurial.node import short, nullid
64 65 from mercurial import cmdutil, scmutil, util, commands, encoding
65 66 import os, shlex, shutil, tempfile, re
66 67
67 68 def snapshot(ui, repo, files, node, tmproot):
68 69 '''snapshot files as of some revision
69 70 if not using snapshot, -I/-X does not work and recursive diff
70 71 in tools like kdiff3 and meld displays too many files.'''
71 72 dirname = os.path.basename(repo.root)
72 73 if dirname == "":
73 74 dirname = "root"
74 75 if node is not None:
75 76 dirname = '%s.%s' % (dirname, short(node))
76 77 base = os.path.join(tmproot, dirname)
77 78 os.mkdir(base)
78 79 if node is not None:
79 80 ui.note(_('making snapshot of %d files from rev %s\n') %
80 81 (len(files), short(node)))
81 82 else:
82 83 ui.note(_('making snapshot of %d files from working directory\n') %
83 84 (len(files)))
84 85 wopener = scmutil.opener(base)
85 86 fns_and_mtime = []
86 87 ctx = repo[node]
87 88 for fn in files:
88 89 wfn = util.pconvert(fn)
89 90 if not wfn in ctx:
90 91 # File doesn't exist; could be a bogus modify
91 92 continue
92 93 ui.note(' %s\n' % wfn)
93 94 dest = os.path.join(base, wfn)
94 95 fctx = ctx[wfn]
95 96 data = repo.wwritedata(wfn, fctx.data())
96 97 if 'l' in fctx.flags():
97 98 wopener.symlink(data, wfn)
98 99 else:
99 100 wopener(wfn, 'w').write(data)
100 101 if 'x' in fctx.flags():
101 102 util.set_flags(dest, False, True)
102 103 if node is None:
103 104 fns_and_mtime.append((dest, repo.wjoin(fn),
104 105 os.lstat(dest).st_mtime))
105 106 return dirname, fns_and_mtime
106 107
107 108 def dodiff(ui, repo, diffcmd, diffopts, pats, opts):
108 109 '''Do the actuall diff:
109 110
110 111 - copy to a temp structure if diffing 2 internal revisions
111 112 - copy to a temp structure if diffing working revision with
112 113 another one and more than 1 file is changed
113 114 - just invoke the diff for a single file in the working dir
114 115 '''
115 116
116 117 revs = opts.get('rev')
117 118 change = opts.get('change')
118 119 args = ' '.join(diffopts)
119 120 do3way = '$parent2' in args
120 121
121 122 if revs and change:
122 123 msg = _('cannot specify --rev and --change at the same time')
123 124 raise util.Abort(msg)
124 125 elif change:
125 126 node2 = cmdutil.revsingle(repo, change, None).node()
126 127 node1a, node1b = repo.changelog.parents(node2)
127 128 else:
128 129 node1a, node2 = cmdutil.revpair(repo, revs)
129 130 if not revs:
130 131 node1b = repo.dirstate.p2()
131 132 else:
132 133 node1b = nullid
133 134
134 135 # Disable 3-way merge if there is only one parent
135 136 if do3way:
136 137 if node1b == nullid:
137 138 do3way = False
138 139
139 140 matcher = cmdutil.match(repo, pats, opts)
140 141 mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher)[:3])
141 142 if do3way:
142 143 mod_b, add_b, rem_b = map(set, repo.status(node1b, node2, matcher)[:3])
143 144 else:
144 145 mod_b, add_b, rem_b = set(), set(), set()
145 146 modadd = mod_a | add_a | mod_b | add_b
146 147 common = modadd | rem_a | rem_b
147 148 if not common:
148 149 return 0
149 150
150 151 tmproot = tempfile.mkdtemp(prefix='extdiff.')
151 152 try:
152 153 # Always make a copy of node1a (and node1b, if applicable)
153 154 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
154 155 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot)[0]
155 156 rev1a = '@%d' % repo[node1a].rev()
156 157 if do3way:
157 158 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
158 159 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot)[0]
159 160 rev1b = '@%d' % repo[node1b].rev()
160 161 else:
161 162 dir1b = None
162 163 rev1b = ''
163 164
164 165 fns_and_mtime = []
165 166
166 167 # If node2 in not the wc or there is >1 change, copy it
167 168 dir2root = ''
168 169 rev2 = ''
169 170 if node2:
170 171 dir2 = snapshot(ui, repo, modadd, node2, tmproot)[0]
171 172 rev2 = '@%d' % repo[node2].rev()
172 173 elif len(common) > 1:
173 174 #we only actually need to get the files to copy back to
174 175 #the working dir in this case (because the other cases
175 176 #are: diffing 2 revisions or single file -- in which case
176 177 #the file is already directly passed to the diff tool).
177 178 dir2, fns_and_mtime = snapshot(ui, repo, modadd, None, tmproot)
178 179 else:
179 180 # This lets the diff tool open the changed file directly
180 181 dir2 = ''
181 182 dir2root = repo.root
182 183
183 184 label1a = rev1a
184 185 label1b = rev1b
185 186 label2 = rev2
186 187
187 188 # If only one change, diff the files instead of the directories
188 189 # Handle bogus modifies correctly by checking if the files exist
189 190 if len(common) == 1:
190 191 common_file = util.localpath(common.pop())
191 192 dir1a = os.path.join(tmproot, dir1a, common_file)
192 193 label1a = common_file + rev1a
193 194 if not os.path.isfile(dir1a):
194 195 dir1a = os.devnull
195 196 if do3way:
196 197 dir1b = os.path.join(tmproot, dir1b, common_file)
197 198 label1b = common_file + rev1b
198 199 if not os.path.isfile(dir1b):
199 200 dir1b = os.devnull
200 201 dir2 = os.path.join(dir2root, dir2, common_file)
201 202 label2 = common_file + rev2
202 203
203 204 # Function to quote file/dir names in the argument string.
204 205 # When not operating in 3-way mode, an empty string is
205 206 # returned for parent2
206 207 replace = dict(parent=dir1a, parent1=dir1a, parent2=dir1b,
207 208 plabel1=label1a, plabel2=label1b,
208 clabel=label2, child=dir2)
209 clabel=label2, child=dir2,
210 root=repo.root)
209 211 def quote(match):
210 212 key = match.group()[1:]
211 213 if not do3way and key == 'parent2':
212 214 return ''
213 215 return util.shellquote(replace[key])
214 216
215 217 # Match parent2 first, so 'parent1?' will match both parent1 and parent
216 regex = '\$(parent2|parent1?|child|plabel1|plabel2|clabel)'
218 regex = '\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)'
217 219 if not do3way and not re.search(regex, args):
218 220 args += ' $parent1 $child'
219 221 args = re.sub(regex, quote, args)
220 222 cmdline = util.shellquote(diffcmd) + ' ' + args
221 223
222 224 ui.debug('running %r in %s\n' % (cmdline, tmproot))
223 225 util.system(cmdline, cwd=tmproot)
224 226
225 227 for copy_fn, working_fn, mtime in fns_and_mtime:
226 228 if os.lstat(copy_fn).st_mtime != mtime:
227 229 ui.debug('file changed while diffing. '
228 230 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn))
229 231 util.copyfile(copy_fn, working_fn)
230 232
231 233 return 1
232 234 finally:
233 235 ui.note(_('cleaning up temp directory\n'))
234 236 shutil.rmtree(tmproot)
235 237
236 238 def extdiff(ui, repo, *pats, **opts):
237 239 '''use external program to diff repository (or selected files)
238 240
239 241 Show differences between revisions for the specified files, using
240 242 an external program. The default program used is diff, with
241 243 default options "-Npru".
242 244
243 245 To select a different program, use the -p/--program option. The
244 246 program will be passed the names of two directories to compare. To
245 247 pass additional options to the program, use -o/--option. These
246 248 will be passed before the names of the directories to compare.
247 249
248 250 When two revision arguments are given, then changes are shown
249 251 between those revisions. If only one revision is specified then
250 252 that revision is compared to the working directory, and, when no
251 253 revisions are specified, the working directory files are compared
252 254 to its parent.'''
253 255 program = opts.get('program')
254 256 option = opts.get('option')
255 257 if not program:
256 258 program = 'diff'
257 259 option = option or ['-Npru']
258 260 return dodiff(ui, repo, program, option, pats, opts)
259 261
260 262 cmdtable = {
261 263 "extdiff":
262 264 (extdiff,
263 265 [('p', 'program', '',
264 266 _('comparison program to run'), _('CMD')),
265 267 ('o', 'option', [],
266 268 _('pass option to comparison program'), _('OPT')),
267 269 ('r', 'rev', [],
268 270 _('revision'), _('REV')),
269 271 ('c', 'change', '',
270 272 _('change made by revision'), _('REV')),
271 273 ] + commands.walkopts,
272 274 _('hg extdiff [OPT]... [FILE]...')),
273 275 }
274 276
275 277 def uisetup(ui):
276 278 for cmd, path in ui.configitems('extdiff'):
277 279 if cmd.startswith('cmd.'):
278 280 cmd = cmd[4:]
279 281 if not path:
280 282 path = cmd
281 283 diffopts = ui.config('extdiff', 'opts.' + cmd, '')
282 284 diffopts = diffopts and [diffopts] or []
283 285 elif cmd.startswith('opts.'):
284 286 continue
285 287 else:
286 288 # command = path opts
287 289 if path:
288 290 diffopts = shlex.split(path)
289 291 path = diffopts.pop(0)
290 292 else:
291 293 path, diffopts = cmd, []
292 294 # look for diff arguments in [diff-tools] then [merge-tools]
293 295 if diffopts == []:
294 296 args = ui.config('diff-tools', cmd+'.diffargs') or \
295 297 ui.config('merge-tools', cmd+'.diffargs')
296 298 if args:
297 299 diffopts = shlex.split(args)
298 300 def save(cmd, path, diffopts):
299 301 '''use closure to save diff command to use'''
300 302 def mydiff(ui, repo, *pats, **opts):
301 303 return dodiff(ui, repo, path, diffopts + opts['option'],
302 304 pats, opts)
303 305 doc = _('''\
304 306 use %(path)s to diff repository (or selected files)
305 307
306 308 Show differences between revisions for the specified files, using
307 309 the %(path)s program.
308 310
309 311 When two revision arguments are given, then changes are shown
310 312 between those revisions. If only one revision is specified then
311 313 that revision is compared to the working directory, and, when no
312 314 revisions are specified, the working directory files are compared
313 315 to its parent.\
314 316 ''') % dict(path=util.uirepr(path))
315 317
316 318 # We must translate the docstring right away since it is
317 319 # used as a format string. The string will unfortunately
318 320 # be translated again in commands.helpcmd and this will
319 321 # fail when the docstring contains non-ASCII characters.
320 322 # Decoding the string to a Unicode string here (using the
321 323 # right encoding) prevents that.
322 324 mydiff.__doc__ = doc.decode(encoding.encoding)
323 325 return mydiff
324 326 cmdtable[cmd] = (save(cmd, path, diffopts),
325 327 cmdtable['extdiff'][1][1:],
326 328 _('hg %s [OPTION]... [FILE]...') % cmd)
General Comments 0
You need to be logged in to leave comments. Login now