]> git.lizzy.rs Git - plan9front.git/blob - sys/lib/python/hgext/extdiff.py
hgwebfs: write headers individually, so they are not limited by webfs iounit (thanks...
[plan9front.git] / sys / lib / python / hgext / extdiff.py
1 # extdiff.py - external diff program support for mercurial
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 #
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
7
8 '''command to allow external programs to compare revisions
9
10 The extdiff Mercurial extension allows you to use external programs
11 to compare revisions, or revision with working directory. The external
12 diff programs are called with a configurable set of options and two
13 non-option arguments: paths to directories containing snapshots of
14 files to compare.
15
16 The extdiff extension also allows to configure new diff commands, so
17 you do not need to type "hg extdiff -p kdiff3" always. ::
18
19   [extdiff]
20   # add new command that runs GNU diff(1) in 'context diff' mode
21   cdiff = gdiff -Nprc5
22   ## or the old way:
23   #cmd.cdiff = gdiff
24   #opts.cdiff = -Nprc5
25
26   # add new command called vdiff, runs kdiff3
27   vdiff = kdiff3
28
29   # add new command called meld, runs meld (no need to name twice)
30   meld =
31
32   # add new command called vimdiff, runs gvimdiff with DirDiff plugin
33   # (see http://www.vim.org/scripts/script.php?script_id=102) Non
34   # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
35   # your .vimrc
36   vimdiff = gvim -f '+next' '+execute "DirDiff" argv(0) argv(1)'
37
38 You can use -I/-X and list of file or directory names like normal "hg
39 diff" command. The extdiff extension makes snapshots of only needed
40 files, so running the external diff program will actually be pretty
41 fast (at least faster than having to compare the entire tree).
42 '''
43
44 from mercurial.i18n import _
45 from mercurial.node import short
46 from mercurial import cmdutil, util, commands
47 import os, shlex, shutil, tempfile
48
49 def snapshot(ui, repo, files, node, tmproot):
50     '''snapshot files as of some revision
51     if not using snapshot, -I/-X does not work and recursive diff
52     in tools like kdiff3 and meld displays too many files.'''
53     dirname = os.path.basename(repo.root)
54     if dirname == "":
55         dirname = "root"
56     if node is not None:
57         dirname = '%s.%s' % (dirname, short(node))
58     base = os.path.join(tmproot, dirname)
59     os.mkdir(base)
60     if node is not None:
61         ui.note(_('making snapshot of %d files from rev %s\n') %
62                 (len(files), short(node)))
63     else:
64         ui.note(_('making snapshot of %d files from working directory\n') %
65             (len(files)))
66     wopener = util.opener(base)
67     fns_and_mtime = []
68     ctx = repo[node]
69     for fn in files:
70         wfn = util.pconvert(fn)
71         if not wfn in ctx:
72             # skipping new file after a merge ?
73             continue
74         ui.note('  %s\n' % wfn)
75         dest = os.path.join(base, wfn)
76         fctx = ctx[wfn]
77         data = repo.wwritedata(wfn, fctx.data())
78         if 'l' in fctx.flags():
79             wopener.symlink(data, wfn)
80         else:
81             wopener(wfn, 'w').write(data)
82             if 'x' in fctx.flags():
83                 util.set_flags(dest, False, True)
84         if node is None:
85             fns_and_mtime.append((dest, repo.wjoin(fn), os.path.getmtime(dest)))
86     return dirname, fns_and_mtime
87
88 def dodiff(ui, repo, diffcmd, diffopts, pats, opts):
89     '''Do the actuall diff:
90
91     - copy to a temp structure if diffing 2 internal revisions
92     - copy to a temp structure if diffing working revision with
93       another one and more than 1 file is changed
94     - just invoke the diff for a single file in the working dir
95     '''
96
97     revs = opts.get('rev')
98     change = opts.get('change')
99
100     if revs and change:
101         msg = _('cannot specify --rev and --change at the same time')
102         raise util.Abort(msg)
103     elif change:
104         node2 = repo.lookup(change)
105         node1 = repo[node2].parents()[0].node()
106     else:
107         node1, node2 = cmdutil.revpair(repo, revs)
108
109     matcher = cmdutil.match(repo, pats, opts)
110     modified, added, removed = repo.status(node1, node2, matcher)[:3]
111     if not (modified or added or removed):
112         return 0
113
114     tmproot = tempfile.mkdtemp(prefix='extdiff.')
115     dir2root = ''
116     try:
117         # Always make a copy of node1
118         dir1 = snapshot(ui, repo, modified + removed, node1, tmproot)[0]
119         changes = len(modified) + len(removed) + len(added)
120
121         # If node2 in not the wc or there is >1 change, copy it
122         if node2 or changes > 1:
123             dir2, fns_and_mtime = snapshot(ui, repo, modified + added, node2, tmproot)
124         else:
125             # This lets the diff tool open the changed file directly
126             dir2 = ''
127             dir2root = repo.root
128             fns_and_mtime = []
129
130         # If only one change, diff the files instead of the directories
131         if changes == 1 :
132             if len(modified):
133                 dir1 = os.path.join(dir1, util.localpath(modified[0]))
134                 dir2 = os.path.join(dir2root, dir2, util.localpath(modified[0]))
135             elif len(removed) :
136                 dir1 = os.path.join(dir1, util.localpath(removed[0]))
137                 dir2 = os.devnull
138             else:
139                 dir1 = os.devnull
140                 dir2 = os.path.join(dir2root, dir2, util.localpath(added[0]))
141
142         cmdline = ('%s %s %s %s' %
143                    (util.shellquote(diffcmd), ' '.join(diffopts),
144                     util.shellquote(dir1), util.shellquote(dir2)))
145         ui.debug(_('running %r in %s\n') % (cmdline, tmproot))
146         util.system(cmdline, cwd=tmproot)
147
148         for copy_fn, working_fn, mtime in fns_and_mtime:
149             if os.path.getmtime(copy_fn) != mtime:
150                 ui.debug(_('file changed while diffing. '
151                          'Overwriting: %s (src: %s)\n') % (working_fn, copy_fn))
152                 util.copyfile(copy_fn, working_fn)
153
154         return 1
155     finally:
156         ui.note(_('cleaning up temp directory\n'))
157         shutil.rmtree(tmproot)
158
159 def extdiff(ui, repo, *pats, **opts):
160     '''use external program to diff repository (or selected files)
161
162     Show differences between revisions for the specified files, using
163     an external program. The default program used is diff, with
164     default options "-Npru".
165
166     To select a different program, use the -p/--program option. The
167     program will be passed the names of two directories to compare. To
168     pass additional options to the program, use -o/--option. These
169     will be passed before the names of the directories to compare.
170
171     When two revision arguments are given, then changes are shown
172     between those revisions. If only one revision is specified then
173     that revision is compared to the working directory, and, when no
174     revisions are specified, the working directory files are compared
175     to its parent.'''
176     program = opts['program'] or 'diff'
177     if opts['program']:
178         option = opts['option']
179     else:
180         option = opts['option'] or ['-Npru']
181     return dodiff(ui, repo, program, option, pats, opts)
182
183 cmdtable = {
184     "extdiff":
185     (extdiff,
186      [('p', 'program', '', _('comparison program to run')),
187       ('o', 'option', [], _('pass option to comparison program')),
188       ('r', 'rev', [], _('revision')),
189       ('c', 'change', '', _('change made by revision')),
190      ] + commands.walkopts,
191      _('hg extdiff [OPT]... [FILE]...')),
192     }
193
194 def uisetup(ui):
195     for cmd, path in ui.configitems('extdiff'):
196         if cmd.startswith('cmd.'):
197             cmd = cmd[4:]
198             if not path: path = cmd
199             diffopts = ui.config('extdiff', 'opts.' + cmd, '')
200             diffopts = diffopts and [diffopts] or []
201         elif cmd.startswith('opts.'):
202             continue
203         else:
204             # command = path opts
205             if path:
206                 diffopts = shlex.split(path)
207                 path = diffopts.pop(0)
208             else:
209                 path, diffopts = cmd, []
210         def save(cmd, path, diffopts):
211             '''use closure to save diff command to use'''
212             def mydiff(ui, repo, *pats, **opts):
213                 return dodiff(ui, repo, path, diffopts, pats, opts)
214             mydiff.__doc__ = _('''\
215 use %(path)s to diff repository (or selected files)
216
217     Show differences between revisions for the specified files, using the
218     %(path)s program.
219
220     When two revision arguments are given, then changes are shown between
221     those revisions. If only one revision is specified then that revision is
222     compared to the working directory, and, when no revisions are specified,
223     the working directory files are compared to its parent.\
224 ''') % dict(path=util.uirepr(path))
225             return mydiff
226         cmdtable[cmd] = (save(cmd, path, diffopts),
227                          cmdtable['extdiff'][1][1:],
228                          _('hg %s [OPTION]... [FILE]...') % cmd)