1
# Copyright (C) 2010 Canonical Ltd.
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Registry for external merge tools, e.g. kdiff3, meld, etc."""
25
from bzrlib.lazy_import import lazy_import
26
lazy_import(globals(), """
38
from bzrlib.commands import Command
39
from bzrlib.option import Option
42
def subprocess_invoker(executable, args, cleanup):
43
retcode = subprocess.call([executable] + args)
48
_WIN32_PATH_EXT = [unicode(ext.lower())
49
for ext in os.getenv('PATHEXT', '').split(';')]
52
class MergeTool(object):
54
def __init__(self, name, command_line):
55
"""Initializes the merge tool with a name and a command-line."""
57
self.command_line = command_line
58
self._cmd_list = cmdline.split(self.command_line)
61
return '<%s(%s, %s)>' % (self.__class__, self.name, self.command_line)
63
def is_available(self):
64
exe = self._cmd_list[0]
65
return (os.path.exists(exe)
66
or osutils.find_executable_on_path(exe) is not None)
68
def invoke(self, filename, invoker=None):
70
invoker = subprocess_invoker
71
args, tmp_file = self._subst_filename(self._cmd_list, filename)
73
if tmp_file is not None:
74
if retcode == 0: # on success, replace file with temp file
75
shutil.move(tmp_file, filename)
76
else: # otherwise, delete temp file
78
return invoker(args[0], args[1:], cleanup)
80
def _subst_filename(self, args, filename):
82
u'base': filename + u'.BASE',
83
u'this': filename + u'.THIS',
84
u'other': filename + u'.OTHER',
90
if u'{this_temp}' in arg and not 'this_temp' in subst_names:
91
tmp_file = tempfile.mktemp(u"_bzr_mergetools_%s.THIS" %
92
os.path.basename(filename))
93
shutil.copy(filename + u".THIS", tmp_file)
94
subst_names['this_temp'] = tmp_file
95
arg = arg.format(**subst_names)
96
subst_args.append(arg)
97
return subst_args, tmp_file
100
_KNOWN_MERGE_TOOLS = (
101
u'bcompare {this} {other} {base} {result}',
102
u'kdiff3 {base} {this} {other} -o {result}',
103
u'xxdiff -m -O -M {result} {this} {base} {other}',
104
u'meld {base} {this_temp} {other}',
105
u'opendiff {this} {other} -ancestor {base} -merge {result}',
106
u'winmergeu {result}',
110
def detect_merge_tools():
111
tools = [MergeTool(None, commandline) for commandline in _KNOWN_MERGE_TOOLS]
112
return [tool for tool in tools if tool.is_available()]