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