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