1# Copyright (C) 2010 Canonical Ltd. 2# 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. 7# 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. 12# 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 16 17"""Utility functions for managing external merge tools such as kdiff3.""" 18 19import os 20import shutil 21import subprocess 22import sys 23import tempfile 24 25from .lazy_import import lazy_import 26lazy_import(globals(), """ 27from breezy import ( 28 cmdline, 29 osutils, 30 trace, 31) 32""") 33 34 35known_merge_tools = { 36 'bcompare': 'bcompare {this} {other} {base} {result}', 37 'kdiff3': 'kdiff3 {base} {this} {other} -o {result}', 38 'xdiff': 'xxdiff -m -O -M {result} {this} {base} {other}', 39 'meld': 'meld {base} {this_temp} {other}', 40 'opendiff': 'opendiff {this} {other} -ancestor {base} -merge {result}', 41 'winmergeu': 'winmergeu {result}', 42} 43 44 45def check_availability(command_line): 46 cmd_list = cmdline.split(command_line) 47 exe = cmd_list[0] 48 if sys.platform == 'win32': 49 exe = _get_executable_path(exe) 50 if exe is None: 51 return False 52 base, ext = os.path.splitext(exe) 53 path_ext = [s.lower() 54 for s in os.getenv('PATHEXT', '').split(os.pathsep)] 55 return os.path.exists(exe) and ext in path_ext 56 else: 57 return (os.access(exe, os.X_OK) or 58 osutils.find_executable_on_path(exe) is not None) 59 60 61def invoke(command_line, filename, invoker=None): 62 """Invokes the given merge tool command line, substituting the given 63 filename according to the embedded substitution markers. Optionally, it 64 will use the given invoker function instead of the default 65 subprocess_invoker. 66 """ 67 if invoker is None: 68 invoker = subprocess_invoker 69 cmd_list = cmdline.split(command_line) 70 exe = _get_executable_path(cmd_list[0]) 71 if exe is not None: 72 cmd_list[0] = exe 73 args, tmp_file = _subst_filename(cmd_list, filename) 74 75 def cleanup(retcode): 76 if tmp_file is not None: 77 if retcode == 0: # on success, replace file with temp file 78 shutil.move(tmp_file, filename) 79 else: # otherwise, delete temp file 80 os.remove(tmp_file) 81 return invoker(args[0], args[1:], cleanup) 82 83 84def _get_executable_path(exe): 85 if os.path.isabs(exe): 86 return exe 87 return osutils.find_executable_on_path(exe) 88 89 90def _subst_filename(args, filename): 91 subst_names = { 92 'base': filename + u'.BASE', 93 'this': filename + u'.THIS', 94 'other': filename + u'.OTHER', 95 'result': filename, 96 } 97 tmp_file = None 98 subst_args = [] 99 for arg in args: 100 if '{this_temp}' in arg and 'this_temp' not in subst_names: 101 fh, tmp_file = tempfile.mkstemp(u"_bzr_mergetools_%s.THIS" % 102 os.path.basename(filename)) 103 trace.mutter('fh=%r, tmp_file=%r', fh, tmp_file) 104 os.close(fh) 105 shutil.copy(filename + u".THIS", tmp_file) 106 subst_names['this_temp'] = tmp_file 107 arg = _format_arg(arg, subst_names) 108 subst_args.append(arg) 109 return subst_args, tmp_file 110 111 112# This would be better implemented using format() from python 2.6 113def _format_arg(arg, subst_names): 114 arg = arg.replace('{base}', subst_names['base']) 115 arg = arg.replace('{this}', subst_names['this']) 116 arg = arg.replace('{other}', subst_names['other']) 117 arg = arg.replace('{result}', subst_names['result']) 118 if 'this_temp' in subst_names: 119 arg = arg.replace('{this_temp}', subst_names['this_temp']) 120 return arg 121 122 123def subprocess_invoker(executable, args, cleanup): 124 retcode = subprocess.call([executable] + args) 125 cleanup(retcode) 126 return retcode 127