diff options
Diffstat (limited to 'eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/hgext/extdiff.py')
-rw-r--r-- | eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/hgext/extdiff.py | 325 |
1 files changed, 325 insertions, 0 deletions
diff --git a/eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/hgext/extdiff.py b/eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/hgext/extdiff.py new file mode 100644 index 0000000..5cf9f03 --- /dev/null +++ b/eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/hgext/extdiff.py @@ -0,0 +1,325 @@ +# extdiff.py - external diff program support for mercurial +# +# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +'''command to allow external programs to compare revisions + +The extdiff Mercurial extension allows you to use external programs +to compare revisions, or revision with working directory. The external +diff programs are called with a configurable set of options and two +non-option arguments: paths to directories containing snapshots of +files to compare. + +The extdiff extension also allows to configure new diff commands, so +you do not need to type :hg:`extdiff -p kdiff3` always. :: + + [extdiff] + # add new command that runs GNU diff(1) in 'context diff' mode + cdiff = gdiff -Nprc5 + ## or the old way: + #cmd.cdiff = gdiff + #opts.cdiff = -Nprc5 + + # add new command called vdiff, runs kdiff3 + vdiff = kdiff3 + + # add new command called meld, runs meld (no need to name twice) + meld = + + # add new command called vimdiff, runs gvimdiff with DirDiff plugin + # (see http://www.vim.org/scripts/script.php?script_id=102) Non + # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in + # your .vimrc + vimdiff = gvim -f '+next' '+execute "DirDiff" argv(0) argv(1)' + +Tool arguments can include variables that are expanded at runtime:: + + $parent1, $plabel1 - filename, descriptive label of first parent + $child, $clabel - filename, descriptive label of child revision + $parent2, $plabel2 - filename, descriptive label of second parent + $parent is an alias for $parent1. + +The extdiff extension will look in your [diff-tools] and [merge-tools] +sections for diff tool arguments, when none are specified in [extdiff]. + +:: + + [extdiff] + kdiff3 = + + [diff-tools] + kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child + +You can use -I/-X and list of file or directory names like normal +:hg:`diff` command. The extdiff extension makes snapshots of only +needed files, so running the external diff program will actually be +pretty fast (at least faster than having to compare the entire tree). +''' + +from mercurial.i18n import _ +from mercurial.node import short, nullid +from mercurial import cmdutil, util, commands, encoding +import os, shlex, shutil, tempfile, re + +def snapshot(ui, repo, files, node, tmproot): + '''snapshot files as of some revision + if not using snapshot, -I/-X does not work and recursive diff + in tools like kdiff3 and meld displays too many files.''' + dirname = os.path.basename(repo.root) + if dirname == "": + dirname = "root" + if node is not None: + dirname = '%s.%s' % (dirname, short(node)) + base = os.path.join(tmproot, dirname) + os.mkdir(base) + if node is not None: + ui.note(_('making snapshot of %d files from rev %s\n') % + (len(files), short(node))) + else: + ui.note(_('making snapshot of %d files from working directory\n') % + (len(files))) + wopener = util.opener(base) + fns_and_mtime = [] + ctx = repo[node] + for fn in files: + wfn = util.pconvert(fn) + if not wfn in ctx: + # File doesn't exist; could be a bogus modify + continue + ui.note(' %s\n' % wfn) + dest = os.path.join(base, wfn) + fctx = ctx[wfn] + data = repo.wwritedata(wfn, fctx.data()) + if 'l' in fctx.flags(): + wopener.symlink(data, wfn) + else: + wopener(wfn, 'w').write(data) + if 'x' in fctx.flags(): + util.set_flags(dest, False, True) + if node is None: + fns_and_mtime.append((dest, repo.wjoin(fn), os.path.getmtime(dest))) + return dirname, fns_and_mtime + +def dodiff(ui, repo, diffcmd, diffopts, pats, opts): + '''Do the actuall diff: + + - copy to a temp structure if diffing 2 internal revisions + - copy to a temp structure if diffing working revision with + another one and more than 1 file is changed + - just invoke the diff for a single file in the working dir + ''' + + revs = opts.get('rev') + change = opts.get('change') + args = ' '.join(diffopts) + do3way = '$parent2' in args + + if revs and change: + msg = _('cannot specify --rev and --change at the same time') + raise util.Abort(msg) + elif change: + node2 = repo.lookup(change) + node1a, node1b = repo.changelog.parents(node2) + else: + node1a, node2 = cmdutil.revpair(repo, revs) + if not revs: + node1b = repo.dirstate.parents()[1] + else: + node1b = nullid + + # Disable 3-way merge if there is only one parent + if do3way: + if node1b == nullid: + do3way = False + + matcher = cmdutil.match(repo, pats, opts) + mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher)[:3]) + if do3way: + mod_b, add_b, rem_b = map(set, repo.status(node1b, node2, matcher)[:3]) + else: + mod_b, add_b, rem_b = set(), set(), set() + modadd = mod_a | add_a | mod_b | add_b + common = modadd | rem_a | rem_b + if not common: + return 0 + + tmproot = tempfile.mkdtemp(prefix='extdiff.') + try: + # Always make a copy of node1a (and node1b, if applicable) + dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a) + dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot)[0] + rev1a = '@%d' % repo[node1a].rev() + if do3way: + dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b) + dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot)[0] + rev1b = '@%d' % repo[node1b].rev() + else: + dir1b = None + rev1b = '' + + fns_and_mtime = [] + + # If node2 in not the wc or there is >1 change, copy it + dir2root = '' + rev2 = '' + if node2: + dir2 = snapshot(ui, repo, modadd, node2, tmproot)[0] + rev2 = '@%d' % repo[node2].rev() + elif len(common) > 1: + #we only actually need to get the files to copy back to + #the working dir in this case (because the other cases + #are: diffing 2 revisions or single file -- in which case + #the file is already directly passed to the diff tool). + dir2, fns_and_mtime = snapshot(ui, repo, modadd, None, tmproot) + else: + # This lets the diff tool open the changed file directly + dir2 = '' + dir2root = repo.root + + label1a = rev1a + label1b = rev1b + label2 = rev2 + + # If only one change, diff the files instead of the directories + # Handle bogus modifies correctly by checking if the files exist + if len(common) == 1: + common_file = util.localpath(common.pop()) + dir1a = os.path.join(dir1a, common_file) + label1a = common_file + rev1a + if not os.path.isfile(os.path.join(tmproot, dir1a)): + dir1a = os.devnull + if do3way: + dir1b = os.path.join(dir1b, common_file) + label1b = common_file + rev1b + if not os.path.isfile(os.path.join(tmproot, dir1b)): + dir1b = os.devnull + dir2 = os.path.join(dir2root, dir2, common_file) + label2 = common_file + rev2 + + # Function to quote file/dir names in the argument string. + # When not operating in 3-way mode, an empty string is + # returned for parent2 + replace = dict(parent=dir1a, parent1=dir1a, parent2=dir1b, + plabel1=label1a, plabel2=label1b, + clabel=label2, child=dir2) + def quote(match): + key = match.group()[1:] + if not do3way and key == 'parent2': + return '' + return util.shellquote(replace[key]) + + # Match parent2 first, so 'parent1?' will match both parent1 and parent + regex = '\$(parent2|parent1?|child|plabel1|plabel2|clabel)' + if not do3way and not re.search(regex, args): + args += ' $parent1 $child' + args = re.sub(regex, quote, args) + cmdline = util.shellquote(diffcmd) + ' ' + args + + ui.debug('running %r in %s\n' % (cmdline, tmproot)) + util.system(cmdline, cwd=tmproot) + + for copy_fn, working_fn, mtime in fns_and_mtime: + if os.path.getmtime(copy_fn) != mtime: + ui.debug('file changed while diffing. ' + 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn)) + util.copyfile(copy_fn, working_fn) + + return 1 + finally: + ui.note(_('cleaning up temp directory\n')) + shutil.rmtree(tmproot) + +def extdiff(ui, repo, *pats, **opts): + '''use external program to diff repository (or selected files) + + Show differences between revisions for the specified files, using + an external program. The default program used is diff, with + default options "-Npru". + + To select a different program, use the -p/--program option. The + program will be passed the names of two directories to compare. To + pass additional options to the program, use -o/--option. These + will be passed before the names of the directories to compare. + + When two revision arguments are given, then changes are shown + between those revisions. If only one revision is specified then + that revision is compared to the working directory, and, when no + revisions are specified, the working directory files are compared + to its parent.''' + program = opts.get('program') + option = opts.get('option') + if not program: + program = 'diff' + option = option or ['-Npru'] + return dodiff(ui, repo, program, option, pats, opts) + +cmdtable = { + "extdiff": + (extdiff, + [('p', 'program', '', + _('comparison program to run'), _('CMD')), + ('o', 'option', [], + _('pass option to comparison program'), _('OPT')), + ('r', 'rev', [], + _('revision'), _('REV')), + ('c', 'change', '', + _('change made by revision'), _('REV')), + ] + commands.walkopts, + _('hg extdiff [OPT]... [FILE]...')), + } + +def uisetup(ui): + for cmd, path in ui.configitems('extdiff'): + if cmd.startswith('cmd.'): + cmd = cmd[4:] + if not path: + path = cmd + diffopts = ui.config('extdiff', 'opts.' + cmd, '') + diffopts = diffopts and [diffopts] or [] + elif cmd.startswith('opts.'): + continue + else: + # command = path opts + if path: + diffopts = shlex.split(path) + path = diffopts.pop(0) + else: + path, diffopts = cmd, [] + # look for diff arguments in [diff-tools] then [merge-tools] + if diffopts == []: + args = ui.config('diff-tools', cmd+'.diffargs') or \ + ui.config('merge-tools', cmd+'.diffargs') + if args: + diffopts = shlex.split(args) + def save(cmd, path, diffopts): + '''use closure to save diff command to use''' + def mydiff(ui, repo, *pats, **opts): + return dodiff(ui, repo, path, diffopts + opts['option'], + pats, opts) + doc = _('''\ +use %(path)s to diff repository (or selected files) + + Show differences between revisions for the specified files, using + the %(path)s program. + + When two revision arguments are given, then changes are shown + between those revisions. If only one revision is specified then + that revision is compared to the working directory, and, when no + revisions are specified, the working directory files are compared + to its parent.\ +''') % dict(path=util.uirepr(path)) + + # We must translate the docstring right away since it is + # used as a format string. The string will unfortunately + # be translated again in commands.helpcmd and this will + # fail when the docstring contains non-ASCII characters. + # Decoding the string to a Unicode string here (using the + # right encoding) prevents that. + mydiff.__doc__ = doc.decode(encoding.encoding) + return mydiff + cmdtable[cmd] = (save(cmd, path, diffopts), + cmdtable['extdiff'][1][1:], + _('hg %s [OPTION]... [FILE]...') % cmd) |