##// END OF EJS Templates
extdiff: merge node and working dir snapshot modes
Patrick Mezard -
r8064:5c7bc1ae default
parent child Browse files
Show More
@@ -1,260 +1,230
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 def snapshot_node(ui, repo, files, node, tmproot):
54 '''snapshot files as of some revision'''
53 def snapshot(ui, repo, files, node, tmproot):
54 '''snapshot files as of some revision
55 if not using snapshot, -I/-X does not work and recursive diff
56 in tools like kdiff3 and meld displays too many files.'''
55 57 dirname = os.path.basename(repo.root)
56 58 if dirname == "":
57 59 dirname = "root"
58 dirname = '%s.%s' % (dirname, short(node))
60 if node is not None:
61 dirname = '%s.%s' % (dirname, short(node))
59 62 base = os.path.join(tmproot, dirname)
60 63 os.mkdir(base)
61 ui.note(_('making snapshot of %d files from rev %s\n') %
62 (len(files), short(node)))
64 if node is not None:
65 ui.note(_('making snapshot of %d files from rev %s\n') %
66 (len(files), short(node)))
67 else:
68 ui.note(_('making snapshot of %d files from working dir\n') %
69 (len(files)))
70
71 fns_and_mtime = []
63 72 ctx = repo[node]
64 73 for fn in files:
65 74 wfn = util.pconvert(fn)
66 75 if not wfn in ctx:
67 76 # skipping new file after a merge ?
68 77 continue
69 78 ui.note(' %s\n' % wfn)
70 79 dest = os.path.join(base, wfn)
71 80 destdir = os.path.dirname(dest)
72 81 if not os.path.isdir(destdir):
73 82 os.makedirs(destdir)
74 83 data = repo.wwritedata(wfn, ctx[wfn].data())
75 84 open(dest, 'wb').write(data)
76 return dirname
77
78
79 def snapshot_wdir(ui, repo, files, tmproot):
80 '''snapshot files from working directory.
81 if not using snapshot, -I/-X does not work and recursive diff
82 in tools like kdiff3 and meld displays too many files.'''
83 dirname = os.path.basename(repo.root)
84 if dirname == "":
85 dirname = "root"
86 base = os.path.join(tmproot, dirname)
87 os.mkdir(base)
88 ui.note(_('making snapshot of %d files from working dir\n') %
89 (len(files)))
90
91 fns_and_mtime = []
92
93 for fn in files:
94 wfn = util.pconvert(fn)
95 ui.note(' %s\n' % wfn)
96 dest = os.path.join(base, wfn)
97 destdir = os.path.dirname(dest)
98 if not os.path.isdir(destdir):
99 os.makedirs(destdir)
100
101 fp = open(dest, 'wb')
102 for chunk in util.filechunkiter(repo.wopener(wfn)):
103 fp.write(chunk)
104 fp.close()
105
106 fns_and_mtime.append((dest, repo.wjoin(fn), os.path.getmtime(dest)))
107
108
85 if node is None:
86 fns_and_mtime.append((dest, repo.wjoin(fn), os.path.getmtime(dest)))
109 87 return dirname, fns_and_mtime
110 88
111
112 89 def dodiff(ui, repo, diffcmd, diffopts, pats, opts):
113 90 '''Do the actuall diff:
114 91
115 92 - copy to a temp structure if diffing 2 internal revisions
116 93 - copy to a temp structure if diffing working revision with
117 94 another one and more than 1 file is changed
118 95 - just invoke the diff for a single file in the working dir
119 96 '''
120 97
121 98 revs = opts.get('rev')
122 99 change = opts.get('change')
123 100
124 101 if revs and change:
125 102 msg = _('cannot specify --rev and --change at the same time')
126 103 raise util.Abort(msg)
127 104 elif change:
128 105 node2 = repo.lookup(change)
129 106 node1 = repo[node2].parents()[0].node()
130 107 else:
131 108 node1, node2 = cmdutil.revpair(repo, revs)
132 109
133 110 matcher = cmdutil.match(repo, pats, opts)
134 111 modified, added, removed = repo.status(node1, node2, matcher)[:3]
135 112 if not (modified or added or removed):
136 113 return 0
137 114
138 115 tmproot = tempfile.mkdtemp(prefix='extdiff.')
139 116 dir2root = ''
140 117 try:
141 118 # Always make a copy of node1
142 dir1 = snapshot_node(ui, repo, modified + removed, node1, tmproot)
119 dir1 = snapshot(ui, repo, modified + removed, node1, tmproot)[0]
143 120 changes = len(modified) + len(removed) + len(added)
144 121
145 fns_and_mtime = []
146
147 122 # If node2 in not the wc or there is >1 change, copy it
148 if node2:
149 dir2 = snapshot_node(ui, repo, modified + added, node2, tmproot)
150 elif changes > 1:
151 #we only actually need to get the files to copy back to the working
152 #dir in this case (because the other cases are: diffing 2 revisions
153 #or single file -- in which case the file is already directly passed
154 #to the diff tool).
155 dir2, fns_and_mtime = snapshot_wdir(ui, repo, modified + added, tmproot)
123 if node2 or changes > 1:
124 dir2, fns_and_mtime = snapshot(ui, repo, modified + added, node2, tmproot)
156 125 else:
157 126 # This lets the diff tool open the changed file directly
158 127 dir2 = ''
159 128 dir2root = repo.root
129 fns_and_mtime = []
160 130
161 131 # If only one change, diff the files instead of the directories
162 132 if changes == 1 :
163 133 if len(modified):
164 134 dir1 = os.path.join(dir1, util.localpath(modified[0]))
165 135 dir2 = os.path.join(dir2root, dir2, util.localpath(modified[0]))
166 136 elif len(removed) :
167 137 dir1 = os.path.join(dir1, util.localpath(removed[0]))
168 138 dir2 = os.devnull
169 139 else:
170 140 dir1 = os.devnull
171 141 dir2 = os.path.join(dir2root, dir2, util.localpath(added[0]))
172 142
173 143 cmdline = ('%s %s %s %s' %
174 144 (util.shellquote(diffcmd), ' '.join(diffopts),
175 145 util.shellquote(dir1), util.shellquote(dir2)))
176 146 ui.debug(_('running %r in %s\n') % (cmdline, tmproot))
177 147 util.system(cmdline, cwd=tmproot)
178 148
179 149 for copy_fn, working_fn, mtime in fns_and_mtime:
180 150 if os.path.getmtime(copy_fn) != mtime:
181 151 ui.debug(_('file changed while diffing. '
182 152 'Overwriting: %s (src: %s)\n') % (working_fn, copy_fn))
183 153 util.copyfile(copy_fn, working_fn)
184 154
185 155 return 1
186 156 finally:
187 157 ui.note(_('cleaning up temp directory\n'))
188 158 shutil.rmtree(tmproot)
189 159
190 160 def extdiff(ui, repo, *pats, **opts):
191 161 '''use external program to diff repository (or selected files)
192 162
193 163 Show differences between revisions for the specified files, using
194 164 an external program. The default program used is diff, with
195 165 default options "-Npru".
196 166
197 167 To select a different program, use the -p option. The program
198 168 will be passed the names of two directories to compare. To pass
199 169 additional options to the program, use the -o option. These will
200 170 be passed before the names of the directories to compare.
201 171
202 172 When two revision arguments are given, then changes are
203 173 shown between those revisions. If only one revision is
204 174 specified then that revision is compared to the working
205 175 directory, and, when no revisions are specified, the
206 176 working directory files are compared to its parent.'''
207 177 program = opts['program'] or 'diff'
208 178 if opts['program']:
209 179 option = opts['option']
210 180 else:
211 181 option = opts['option'] or ['-Npru']
212 182 return dodiff(ui, repo, program, option, pats, opts)
213 183
214 184 cmdtable = {
215 185 "extdiff":
216 186 (extdiff,
217 187 [('p', 'program', '', _('comparison program to run')),
218 188 ('o', 'option', [], _('pass option to comparison program')),
219 189 ('r', 'rev', [], _('revision')),
220 190 ('c', 'change', '', _('change made by revision')),
221 191 ] + commands.walkopts,
222 192 _('hg extdiff [OPT]... [FILE]...')),
223 193 }
224 194
225 195 def uisetup(ui):
226 196 for cmd, path in ui.configitems('extdiff'):
227 197 if cmd.startswith('cmd.'):
228 198 cmd = cmd[4:]
229 199 if not path: path = cmd
230 200 diffopts = ui.config('extdiff', 'opts.' + cmd, '')
231 201 diffopts = diffopts and [diffopts] or []
232 202 elif cmd.startswith('opts.'):
233 203 continue
234 204 else:
235 205 # command = path opts
236 206 if path:
237 207 diffopts = shlex.split(path)
238 208 path = diffopts.pop(0)
239 209 else:
240 210 path, diffopts = cmd, []
241 211 def save(cmd, path, diffopts):
242 212 '''use closure to save diff command to use'''
243 213 def mydiff(ui, repo, *pats, **opts):
244 214 return dodiff(ui, repo, path, diffopts, pats, opts)
245 215 mydiff.__doc__ = '''use %(path)s to diff repository (or selected files)
246 216
247 217 Show differences between revisions for the specified
248 218 files, using the %(path)s program.
249 219
250 220 When two revision arguments are given, then changes are
251 221 shown between those revisions. If only one revision is
252 222 specified then that revision is compared to the working
253 223 directory, and, when no revisions are specified, the
254 224 working directory files are compared to its parent.''' % {
255 225 'path': util.uirepr(path),
256 226 }
257 227 return mydiff
258 228 cmdtable[cmd] = (save(cmd, path, diffopts),
259 229 cmdtable['extdiff'][1][1:],
260 230 _('hg %s [OPTION]... [FILE]...') % cmd)
General Comments 0
You need to be logged in to leave comments. Login now