~zindar/bzr-extmerge/trunk

1 by Erik Bågfors
initial checkin
1
# -*- coding: UTF-8 -*-
2
# Copyright (C) 2006 Erik Bågfors <erik@bagfors.nu>
2 by Erik Bågfors
you can now set user defined merge tool in configuration
3
#
1 by Erik Bågfors
initial checkin
4
# GNU GPL v2.
2 by Erik Bågfors
you can now set user defined merge tool in configuration
5
#
1 by Erik Bågfors
initial checkin
6
7
"""external merge plugin for bzr"""
8
9
import errno
10
import exceptions
11
import os
8.1.1 by Philippe Neumann
- Added more flexible 'tool'-parameter handling
12
import shutil
1 by Erik Bågfors
initial checkin
13
from subprocess import call
10 by Erik Bagfors
merge in support for other external mergers, such as meld
14
import tempfile
1 by Erik Bågfors
initial checkin
15
16
from bzrlib.commands import Command, register_command
2 by Erik Bågfors
you can now set user defined merge tool in configuration
17
from bzrlib.config import GlobalConfig
5.1.1 by Wouter van Heyst
small bzrlib api fix
18
from bzrlib.conflicts import CONFLICT_SUFFIXES
8.1.1 by Philippe Neumann
- Added more flexible 'tool'-parameter handling
19
from bzrlib.errors import BzrCommandError
7 by Ian Clatworthy
Make --all a local option instead of using the global one that no longer exists.
20
from bzrlib.option import Option
5.1.1 by Wouter van Heyst
small bzrlib api fix
21
from bzrlib.workingtree import WorkingTree
1 by Erik Bågfors
initial checkin
22
23
2 by Erik Bågfors
you can now set user defined merge tool in configuration
24
# The same syntax as in mergetools below, can be used in external_merge
25
# in bazaar.conf
26
# %b = base  (foo.BASE)
27
# %t = this  (foo.THIS)
28
# %o = other (foo.OTHER)
29
# %r = resolved file (aka output file) (foo)
8.1.1 by Philippe Neumann
- Added more flexible 'tool'-parameter handling
30
# %T = this  (foo.THIS), a temporary copy of foo.THIS; will be used to
31
#                        overwrite 'foo' if the merge succeeds
32
33
valid_parameter_sets = (
34
    # normal 3 way merge + output file
35
    ('%r', '%b', '%t', '%o'),
36
    # meld-style 3 way merge, one file (%T) being used for both in- and output
37
    ('%b', '%T', '%o'),
38
    # meld-style 2 way merge, one file (%T) being used for both in- and output
39
    ('%T', '%o')
40
)
1 by Erik Bågfors
initial checkin
41
42
mergetools = [
8.1.1 by Philippe Neumann
- Added more flexible 'tool'-parameter handling
43
    'kdiff3 --output %r %b %t %o',
44
    'xxdiff -m -O -M %r %t %b %o',
8.1.2 by Erik Bagfors
added info about meld, and made kdiff and xxdiff defaults
45
    'meld %b %T %o',
8.1.1 by Philippe Neumann
- Added more flexible 'tool'-parameter handling
46
    'opendiff %t %o -ancestor %b -merge %r'
47
]
1 by Erik Bågfors
initial checkin
48
9 by Erik Bagfors
pep8-ify
49
1 by Erik Bågfors
initial checkin
50
class cmd_extmerge(Command):
8.1.2 by Erik Bagfors
added info about meld, and made kdiff and xxdiff defaults
51
    """Calls an external merge program such as meld, xxdiff, kdiff3, opendiff, etc
1 by Erik Bågfors
initial checkin
52
    to help you resolve any conflicts you have.
2 by Erik Bågfors
you can now set user defined merge tool in configuration
53
54
    Will call a user defined merge tool if it exists, otherwise, will try
8.1.2 by Erik Bagfors
added info about meld, and made kdiff and xxdiff defaults
55
    kdiff3, xxdiff, meld and opendiff, in that order.
2 by Erik Bågfors
you can now set user defined merge tool in configuration
56
57
    To define your own merge tool, set external_merge in bazaar.conf.
7 by Ian Clatworthy
Make --all a local option instead of using the global one that no longer exists.
58
    See extmerge/__init__.py for the syntax.
1 by Erik Bågfors
initial checkin
59
    """
60
    aliases = ['emerge']
61
    takes_args = ['file*']
7 by Ian Clatworthy
Make --all a local option instead of using the global one that no longer exists.
62
    takes_options = [Option('all', help='Use all files with conflicts.')]
9 by Erik Bagfors
pep8-ify
63
1 by Erik Bågfors
initial checkin
64
    def run(self, file_list, all=False):
65
        if file_list is None:
66
            if not all:
67
                raise BzrCommandError(
68
                    "command 'extmerge' needs one or more FILE, or --all")
5 by Erik Bågfors
updates for bzr.dev from LarstiQ
69
            tree = WorkingTree.open_containing(u'.')[0]
70
            file_list = list(tree.abspath(f.path) for f in tree.conflicts())
1 by Erik Bågfors
initial checkin
71
        else:
72
            if all:
73
                raise BzrCommandError(
74
                        "If --all is specified, no FILE may be provided")
75
        for filename in file_list:
76
            if not os.path.exists(filename):
77
                print "%s does not exists" % filename
78
            else:
79
                failures = 0
80
                for suffix in CONFLICT_SUFFIXES:
81
                    if not os.path.exists(filename + suffix) and not failures:
82
                        print "%s is not conflicted" % filename
83
                        failures = 1
84
                    
85
                if not failures:
86
                    run_extmerge(filename)
3 by Erik Bågfors
better error message
87
        if len(file_list) == 0:
88
            print "no conflicting files"
89
        else:
90
            print "remember to bzr resolve your files"
1 by Erik Bågfors
initial checkin
91
9 by Erik Bagfors
pep8-ify
92
1 by Erik Bågfors
initial checkin
93
def get_user_merge_tool():
2 by Erik Bågfors
you can now set user defined merge tool in configuration
94
    return GlobalConfig().get_user_option('external_merge')
95
1 by Erik Bågfors
initial checkin
96
8.1.1 by Philippe Neumann
- Added more flexible 'tool'-parameter handling
97
def validate_command(tool):
98
    """Validates that 'tool' contains at least one set of 'valid_parameter_sets'"""
99
100
    def check_substrings(string, substrings):
101
        """Checks if all 'substrings' occur in 'string'"""
102
        for e in substrings:
103
            if not e in string:
104
                return False
105
106
        return True
107
108
    sets = map(lambda x: check_substrings(tool, x), valid_parameter_sets)
109
110
    # not a single valid-parameter-set present -> BzrCommandError
111
    if not True in sets:
4 by Erik Bågfors
use bzr exceptions for errors
112
        raise BzrCommandError(
8.1.1 by Philippe Neumann
- Added more flexible 'tool'-parameter handling
113
            "Error in external merge tool definition.\n" +
114
            "Definition needs to contain one of the following parameter sets:\n" +
115
            "".join(map(
116
                lambda x:
117
                    "\t- %s\n" % x,
118
                    map(", ".join, valid_parameter_sets)
119
            )) +
120
            "Definition is %s" % tool
121
        )
122
10 by Erik Bagfors
merge in support for other external mergers, such as meld
123
8.1.1 by Philippe Neumann
- Added more flexible 'tool'-parameter handling
124
def execute_command(tool, filename):
125
    tool = tool.replace('%r', filename)
126
    tool = tool.replace('%t', filename + '.THIS')
127
    tool = tool.replace('%o', filename + '.OTHER')
128
    tool = tool.replace('%b', filename + '.BASE')
129
130
    # create tmpfile for %T (if needed)
131
    this_tmp = False
132
    if '%T' in tool:
133
        this_tmp = tempfile.mktemp("_bzr_extmerge_%s.THIS" % os.path.basename(filename))
134
        shutil.copy(filename + ".THIS", this_tmp)
135
        tool = tool.replace('%T', this_tmp)
136
137
    ret = call(tool.split(' '))
138
139
    # cleanup tmpfile of %T
140
    if this_tmp:
141
        # merge success -> keep changes
142
        if ret == 0:
143
            shutil.move(this_tmp, filename)
144
        # merge failed -> delete file
145
        else:
146
            os.remove(this_tmp)
147
148
    return ret
1 by Erik Bågfors
initial checkin
149
150
151
def run_extmerge(filename):
152
    global mergetools
153
    usertool = get_user_merge_tool()
154
    if usertool:
155
        mergetools = [usertool]
156
157
    for tool in mergetools:
8.1.1 by Philippe Neumann
- Added more flexible 'tool'-parameter handling
158
        validate_command(tool)
1 by Erik Bågfors
initial checkin
159
        try:
8.1.1 by Philippe Neumann
- Added more flexible 'tool'-parameter handling
160
            ret = execute_command(tool, filename)
1 by Erik Bågfors
initial checkin
161
        except OSError, e:
162
            # ENOENT means no such editor
163
            if e.errno == errno.ENOENT:
164
                continue
165
            raise
8.1.1 by Philippe Neumann
- Added more flexible 'tool'-parameter handling
166
        if ret == 0:
1 by Erik Bågfors
initial checkin
167
            return True
8.1.1 by Philippe Neumann
- Added more flexible 'tool'-parameter handling
168
        elif ret == 127:
1 by Erik Bågfors
initial checkin
169
            continue
170
        else:
171
            mergetools = [tool]
172
            break
173
174
    if len(mergetools) != 1:
4 by Erik Bågfors
use bzr exceptions for errors
175
        raise BzrCommandError("Found no valid merge tool")
1 by Erik Bågfors
initial checkin
176
9 by Erik Bagfors
pep8-ify
177
1 by Erik Bågfors
initial checkin
178
register_command(cmd_extmerge)