##// END OF EJS Templates
extdiff: add --change option to display single changeset diff...
Gilles Moris -
r7758:e81e6c99 default
parent child Browse files
Show More
@@ -1,247 +1,260 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
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 '''
9 9 The `extdiff' Mercurial extension allows you to use external programs
10 10 to compare revisions, or revision with working dir. The external diff
11 11 programs are called with a configurable set of options and two
12 12 non-option arguments: paths to directories containing snapshots of
13 13 files to compare.
14 14
15 15 To enable this extension:
16 16
17 17 [extensions]
18 18 hgext.extdiff =
19 19
20 20 The `extdiff' extension also allows to configure new diff commands, so
21 21 you do not need to type "hg extdiff -p kdiff3" always.
22 22
23 23 [extdiff]
24 24 # add new command that runs GNU diff(1) in 'context diff' mode
25 25 cdiff = gdiff -Nprc5
26 26 ## or the old way:
27 27 #cmd.cdiff = gdiff
28 28 #opts.cdiff = -Nprc5
29 29
30 30 # add new command called vdiff, runs kdiff3
31 31 vdiff = kdiff3
32 32
33 33 # add new command called meld, runs meld (no need to name twice)
34 34 meld =
35 35
36 36 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
37 37 #(see http://www.vim.org/scripts/script.php?script_id=102)
38 38 # Non english user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
39 39 # your .vimrc
40 40 vimdiff = gvim -f '+next' '+execute "DirDiff" argv(0) argv(1)'
41 41
42 42 You can use -I/-X and list of file or directory names like normal
43 43 "hg diff" command. The `extdiff' extension makes snapshots of only
44 44 needed files, so running the external diff program will actually be
45 45 pretty fast (at least faster than having to compare the entire tree).
46 46 '''
47 47
48 48 from mercurial.i18n import _
49 49 from mercurial.node import short
50 50 from mercurial import cmdutil, util, commands
51 51 import os, shlex, shutil, tempfile
52 52
53 53 def snapshot_node(ui, repo, files, node, tmproot):
54 54 '''snapshot files as of some revision'''
55 55 dirname = os.path.basename(repo.root)
56 56 if dirname == "":
57 57 dirname = "root"
58 58 dirname = '%s.%s' % (dirname, short(node))
59 59 base = os.path.join(tmproot, dirname)
60 60 os.mkdir(base)
61 61 ui.note(_('making snapshot of %d files from rev %s\n') %
62 62 (len(files), short(node)))
63 63 ctx = repo[node]
64 64 for fn in files:
65 65 wfn = util.pconvert(fn)
66 66 if not wfn in ctx:
67 67 # skipping new file after a merge ?
68 68 continue
69 69 ui.note(' %s\n' % wfn)
70 70 dest = os.path.join(base, wfn)
71 71 destdir = os.path.dirname(dest)
72 72 if not os.path.isdir(destdir):
73 73 os.makedirs(destdir)
74 74 data = repo.wwritedata(wfn, ctx[wfn].data())
75 75 open(dest, 'wb').write(data)
76 76 return dirname
77 77
78 78
79 79 def snapshot_wdir(ui, repo, files, tmproot):
80 80 '''snapshot files from working directory.
81 81 if not using snapshot, -I/-X does not work and recursive diff
82 82 in tools like kdiff3 and meld displays too many files.'''
83 83 dirname = os.path.basename(repo.root)
84 84 if dirname == "":
85 85 dirname = "root"
86 86 base = os.path.join(tmproot, dirname)
87 87 os.mkdir(base)
88 88 ui.note(_('making snapshot of %d files from working dir\n') %
89 89 (len(files)))
90 90
91 91 fns_and_mtime = []
92 92
93 93 for fn in files:
94 94 wfn = util.pconvert(fn)
95 95 ui.note(' %s\n' % wfn)
96 96 dest = os.path.join(base, wfn)
97 97 destdir = os.path.dirname(dest)
98 98 if not os.path.isdir(destdir):
99 99 os.makedirs(destdir)
100 100
101 101 fp = open(dest, 'wb')
102 102 for chunk in util.filechunkiter(repo.wopener(wfn)):
103 103 fp.write(chunk)
104 104 fp.close()
105 105
106 106 fns_and_mtime.append((dest, repo.wjoin(fn), os.path.getmtime(dest)))
107 107
108 108
109 109 return dirname, fns_and_mtime
110 110
111 111
112 112 def dodiff(ui, repo, diffcmd, diffopts, pats, opts):
113 113 '''Do the actuall diff:
114 114
115 115 - copy to a temp structure if diffing 2 internal revisions
116 116 - copy to a temp structure if diffing working revision with
117 117 another one and more than 1 file is changed
118 118 - just invoke the diff for a single file in the working dir
119 119 '''
120 node1, node2 = cmdutil.revpair(repo, opts['rev'])
120
121 revs = opts.get('rev')
122 change = opts.get('change')
123
124 if revs and change:
125 msg = _('cannot specify --rev and --change at the same time')
126 raise util.Abort(msg)
127 elif change:
128 node2 = repo.lookup(change)
129 node1 = repo[node2].parents()[0].node()
130 else:
131 node1, node2 = cmdutil.revpair(repo, revs)
132
121 133 matcher = cmdutil.match(repo, pats, opts)
122 134 modified, added, removed = repo.status(node1, node2, matcher)[:3]
123 135 if not (modified or added or removed):
124 136 return 0
125 137
126 138 tmproot = tempfile.mkdtemp(prefix='extdiff.')
127 139 dir2root = ''
128 140 try:
129 141 # Always make a copy of node1
130 142 dir1 = snapshot_node(ui, repo, modified + removed, node1, tmproot)
131 143 changes = len(modified) + len(removed) + len(added)
132 144
133 145 fns_and_mtime = []
134 146
135 147 # If node2 in not the wc or there is >1 change, copy it
136 148 if node2:
137 149 dir2 = snapshot_node(ui, repo, modified + added, node2, tmproot)
138 150 elif changes > 1:
139 151 #we only actually need to get the files to copy back to the working
140 152 #dir in this case (because the other cases are: diffing 2 revisions
141 153 #or single file -- in which case the file is already directly passed
142 154 #to the diff tool).
143 155 dir2, fns_and_mtime = snapshot_wdir(ui, repo, modified + added, tmproot)
144 156 else:
145 157 # This lets the diff tool open the changed file directly
146 158 dir2 = ''
147 159 dir2root = repo.root
148 160
149 161 # If only one change, diff the files instead of the directories
150 162 if changes == 1 :
151 163 if len(modified):
152 164 dir1 = os.path.join(dir1, util.localpath(modified[0]))
153 165 dir2 = os.path.join(dir2root, dir2, util.localpath(modified[0]))
154 166 elif len(removed) :
155 167 dir1 = os.path.join(dir1, util.localpath(removed[0]))
156 168 dir2 = os.devnull
157 169 else:
158 170 dir1 = os.devnull
159 171 dir2 = os.path.join(dir2root, dir2, util.localpath(added[0]))
160 172
161 173 cmdline = ('%s %s %s %s' %
162 174 (util.shellquote(diffcmd), ' '.join(diffopts),
163 175 util.shellquote(dir1), util.shellquote(dir2)))
164 176 ui.debug(_('running %r in %s\n') % (cmdline, tmproot))
165 177 util.system(cmdline, cwd=tmproot)
166 178
167 179 for copy_fn, working_fn, mtime in fns_and_mtime:
168 180 if os.path.getmtime(copy_fn) != mtime:
169 181 ui.debug(_('file changed while diffing. '
170 182 'Overwriting: %s (src: %s)\n') % (working_fn, copy_fn))
171 183 util.copyfile(copy_fn, working_fn)
172 184
173 185 return 1
174 186 finally:
175 187 ui.note(_('cleaning up temp directory\n'))
176 188 shutil.rmtree(tmproot)
177 189
178 190 def extdiff(ui, repo, *pats, **opts):
179 191 '''use external program to diff repository (or selected files)
180 192
181 193 Show differences between revisions for the specified files, using
182 194 an external program. The default program used is diff, with
183 195 default options "-Npru".
184 196
185 197 To select a different program, use the -p option. The program
186 198 will be passed the names of two directories to compare. To pass
187 199 additional options to the program, use the -o option. These will
188 200 be passed before the names of the directories to compare.
189 201
190 202 When two revision arguments are given, then changes are
191 203 shown between those revisions. If only one revision is
192 204 specified then that revision is compared to the working
193 205 directory, and, when no revisions are specified, the
194 206 working directory files are compared to its parent.'''
195 207 program = opts['program'] or 'diff'
196 208 if opts['program']:
197 209 option = opts['option']
198 210 else:
199 211 option = opts['option'] or ['-Npru']
200 212 return dodiff(ui, repo, program, option, pats, opts)
201 213
202 214 cmdtable = {
203 215 "extdiff":
204 216 (extdiff,
205 217 [('p', 'program', '', _('comparison program to run')),
206 218 ('o', 'option', [], _('pass option to comparison program')),
207 219 ('r', 'rev', [], _('revision')),
220 ('c', 'change', '', _('change made by revision')),
208 221 ] + commands.walkopts,
209 222 _('hg extdiff [OPT]... [FILE]...')),
210 223 }
211 224
212 225 def uisetup(ui):
213 226 for cmd, path in ui.configitems('extdiff'):
214 227 if cmd.startswith('cmd.'):
215 228 cmd = cmd[4:]
216 229 if not path: path = cmd
217 230 diffopts = ui.config('extdiff', 'opts.' + cmd, '')
218 231 diffopts = diffopts and [diffopts] or []
219 232 elif cmd.startswith('opts.'):
220 233 continue
221 234 else:
222 235 # command = path opts
223 236 if path:
224 237 diffopts = shlex.split(path)
225 238 path = diffopts.pop(0)
226 239 else:
227 240 path, diffopts = cmd, []
228 241 def save(cmd, path, diffopts):
229 242 '''use closure to save diff command to use'''
230 243 def mydiff(ui, repo, *pats, **opts):
231 244 return dodiff(ui, repo, path, diffopts, pats, opts)
232 245 mydiff.__doc__ = '''use %(path)s to diff repository (or selected files)
233 246
234 247 Show differences between revisions for the specified
235 248 files, using the %(path)s program.
236 249
237 250 When two revision arguments are given, then changes are
238 251 shown between those revisions. If only one revision is
239 252 specified then that revision is compared to the working
240 253 directory, and, when no revisions are specified, the
241 254 working directory files are compared to its parent.''' % {
242 255 'path': util.uirepr(path),
243 256 }
244 257 return mydiff
245 258 cmdtable[cmd] = (save(cmd, path, diffopts),
246 259 cmdtable['extdiff'][1][1:],
247 260 _('hg %s [OPTION]... [FILE]...') % cmd)
@@ -1,40 +1,45 b''
1 1 #!/bin/sh
2 2
3 3 echo "[extensions]" >> $HGRCPATH
4 4 echo "extdiff=" >> $HGRCPATH
5 5
6 6 hg init a
7 7 cd a
8 8 echo a > a
9 9 echo b > b
10 10 hg add
11 11 # should diff cloned directories
12 12 hg extdiff -o -r $opt
13 13
14 14 echo "[extdiff]" >> $HGRCPATH
15 15 echo "cmd.falabala=echo" >> $HGRCPATH
16 16 echo "opts.falabala=diffing" >> $HGRCPATH
17 17
18 18 hg falabala
19 19
20 20 hg help falabala
21 21
22 22 hg ci -d '0 0' -mtest1
23 23
24 24 echo b >> a
25 25 hg ci -d '1 0' -mtest2
26 26
27 27 # should diff cloned files directly
28 28 hg falabala -r 0:1
29 29
30 30 # test diff during merge
31 31 hg update 0
32 32 echo c >> c
33 33 hg add c
34 34 hg ci -m "new branch" -d '1 0'
35 hg update -C 1
36 hg merge tip
35 hg merge 1
37 36 # should diff cloned file against wc file
38 hg falabala > out || echo "diff-like tools yield a non-zero exit code"
37 hg falabala > out
39 38 # cleanup the output since the wc is a tmp directory
40 39 sed 's:\(.* \).*\(\/test-extdiff\):\1[tmp]\2:' out
40 # test --change option
41 hg ci -d '2 0' -mtest3
42 hg falabala -c 1
43 # check diff are made from the first parent
44 hg falabala -c 3 || echo "diff-like tools yield a non-zero exit code"
45 #hg log
@@ -1,34 +1,36 b''
1 1 adding a
2 2 adding b
3 3 Only in a: a
4 4 Only in a: b
5 5 diffing a.000000000000 a
6 6 hg falabala [OPTION]... [FILE]...
7 7
8 8 use 'echo' to diff repository (or selected files)
9 9
10 10 Show differences between revisions for the specified
11 11 files, using the 'echo' program.
12 12
13 13 When two revision arguments are given, then changes are
14 14 shown between those revisions. If only one revision is
15 15 specified then that revision is compared to the working
16 16 directory, and, when no revisions are specified, the
17 17 working directory files are compared to its parent.
18 18
19 19 options:
20 20
21 21 -o --option pass option to comparison program
22 22 -r --rev revision
23 -c --change change made by revision
23 24 -I --include include names matching the given patterns
24 25 -X --exclude exclude names matching the given patterns
25 26
26 27 use "hg -v help falabala" to show global options
27 28 diffing a.8a5febb7f867/a a.34eed99112ab/a
28 29 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
29 30 created new head
30 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
31 31 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
32 32 (branch merge, don't forget to commit)
33 diffing a.2a13a4d2da36/a [tmp]/test-extdiff/a/a
34 diffing a.8a5febb7f867/a a.34eed99112ab/a
35 diffing a.2a13a4d2da36/a a.46c0e4daeb72/a
33 36 diff-like tools yield a non-zero exit code
34 diffing a.34eed99112ab/c [tmp]/test-extdiff/a/c
General Comments 0
You need to be logged in to leave comments. Login now