~ubuntu-branches/ubuntu/hardy/bzr/hardy-updates

« back to all changes in this revision

Viewing changes to bzrlib/merge_core.py

  • Committer: Bazaar Package Importer
  • Author(s): Jeff Bailey
  • Date: 2005-11-07 13:17:53 UTC
  • Revision ID: james.westby@ubuntu.com-20051107131753-qsy145z1rfug5i27
Tags: upstream-0.6.2
ImportĀ upstreamĀ versionĀ 0.6.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import os.path
 
2
 
 
3
import changeset
 
4
from changeset import Inventory, apply_changeset, invert_dict
 
5
from bzrlib.osutils import backup_file, rename
 
6
from bzrlib.merge3 import Merge3
 
7
import bzrlib
 
8
from bzrlib.atomicfile import AtomicFile
 
9
from changeset import get_contents
 
10
 
 
11
class ApplyMerge3:
 
12
    history_based = False
 
13
    """Contents-change wrapper around merge3.Merge3"""
 
14
    def __init__(self, file_id, base, other, show_base=False, reprocess=False):
 
15
        self.file_id = file_id
 
16
        self.base = base
 
17
        self.other = other
 
18
        self.show_base = show_base
 
19
        self.reprocess = reprocess
 
20
 
 
21
    def is_creation(self):
 
22
        return False
 
23
 
 
24
    def is_deletion(self):
 
25
        return False
 
26
 
 
27
    def __eq__(self, other):
 
28
        if not isinstance(other, ApplyMerge3):
 
29
            return False
 
30
        return (self.base == other.base and 
 
31
                self.other == other.other and self.file_id == other.file_id)
 
32
 
 
33
    def __ne__(self, other):
 
34
        return not (self == other)
 
35
 
 
36
    def apply(self, filename, conflict_handler, reverse=False):
 
37
        new_file = filename+".new" 
 
38
        if not reverse:
 
39
            base = self.base
 
40
            other = self.other
 
41
        else:
 
42
            base = self.other
 
43
            other = self.base
 
44
        def get_lines(tree):
 
45
            if self.file_id not in tree:
 
46
                raise Exception("%s not in tree" % self.file_id)
 
47
                return ()
 
48
            return tree.get_file(self.file_id).readlines()
 
49
        base_lines = get_lines(base)
 
50
        other_lines = get_lines(other)
 
51
        m3 = Merge3(base_lines, file(filename, "rb").readlines(), other_lines)
 
52
 
 
53
        new_conflicts = False
 
54
        output_file = file(new_file, "wb")
 
55
        start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
 
56
        if self.show_base is True:
 
57
            base_marker = '|' * 7
 
58
        else:
 
59
            base_marker = None
 
60
        for line in m3.merge_lines(name_a = "TREE", name_b = "MERGE-SOURCE", 
 
61
                       name_base = "BASE-REVISION",
 
62
                       start_marker=start_marker, base_marker=base_marker,
 
63
                       reprocess = self.reprocess):
 
64
            if line.startswith(start_marker):
 
65
                new_conflicts = True
 
66
                output_file.write(line.replace(start_marker, '<' * 7))
 
67
            else:
 
68
                output_file.write(line)
 
69
        output_file.close()
 
70
        if not new_conflicts:
 
71
            os.chmod(new_file, os.stat(filename).st_mode)
 
72
            rename(new_file, filename)
 
73
            return
 
74
        else:
 
75
            conflict_handler.merge_conflict(new_file, filename, base_lines,
 
76
                                            other_lines)
 
77
 
 
78
class WeaveMerge:
 
79
    """Contents-change wrapper around weave merge"""
 
80
    history_based = True
 
81
    def __init__(self, weave, this_revision_id, other_revision_id):
 
82
        self.weave = weave
 
83
        self.this_revision_id = this_revision_id
 
84
        self.other_revision_id = other_revision_id
 
85
 
 
86
    def is_creation(self):
 
87
        return False
 
88
 
 
89
    def is_deletion(self):
 
90
        return False
 
91
 
 
92
    def __eq__(self, other):
 
93
        if not isinstance(other, WeaveMerge):
 
94
            return False
 
95
        return self.weave == other.weave and\
 
96
            self.this_revision_id == other.this_revision_id and\
 
97
            self.other_revision_id == other.other_revision_id
 
98
 
 
99
    def __ne__(self, other):
 
100
        return not (self == other)
 
101
 
 
102
    def apply(self, filename, conflict_handler, reverse=False):
 
103
        this_i = self.weave.lookup(self.this_revision_id)
 
104
        other_i = self.weave.lookup(self.other_revision_id)
 
105
        plan = self.weave.plan_merge(this_i, other_i)
 
106
        lines = self.weave.weave_merge(plan)
 
107
        conflicts = False
 
108
        out_file = AtomicFile(filename, mode='wb')
 
109
        for line in lines:
 
110
            if line == '<<<<<<<\n':
 
111
                conflicts = True
 
112
            out_file.write(line)
 
113
        if conflicts:
 
114
            conflict_handler.weave_merge_conflict(filename, self.weave,
 
115
                                                  other_i, out_file)
 
116
        else:
 
117
            out_file.commit()
 
118
 
 
119
class BackupBeforeChange:
 
120
    """Contents-change wrapper to back up file first"""
 
121
    def __init__(self, contents_change):
 
122
        self.contents_change = contents_change
 
123
 
 
124
    def is_creation(self):
 
125
        return self.contents_change.is_creation()
 
126
 
 
127
    def is_deletion(self):
 
128
        return self.contents_change.is_deletion()
 
129
 
 
130
    def __eq__(self, other):
 
131
        if not isinstance(other, BackupBeforeChange):
 
132
            return False
 
133
        return (self.contents_change == other.contents_change)
 
134
 
 
135
    def __ne__(self, other):
 
136
        return not (self == other)
 
137
 
 
138
    def apply(self, filename, conflict_handler, reverse=False):
 
139
        backup_file(filename)
 
140
        self.contents_change.apply(filename, conflict_handler, reverse)
 
141
 
 
142
 
 
143
def invert_invent(inventory):
 
144
    invert_invent = {}
 
145
    for file_id in inventory:
 
146
        path = inventory.id2path(file_id)
 
147
        if path == '':
 
148
            path = './.'
 
149
        else:
 
150
            path = './' + path
 
151
        invert_invent[file_id] = path
 
152
    return invert_invent
 
153
 
 
154
 
 
155
def merge_flex(this, base, other, changeset_function, inventory_function,
 
156
               conflict_handler, merge_factory, interesting_ids):
 
157
    cset = changeset_function(base, other, interesting_ids)
 
158
    new_cset = make_merge_changeset(cset, this, base, other, 
 
159
                                    conflict_handler, merge_factory)
 
160
    result = apply_changeset(new_cset, invert_invent(this.inventory),
 
161
                             this.basedir, conflict_handler, False)
 
162
    return result
 
163
    
 
164
 
 
165
def make_merge_changeset(cset, this, base, other, 
 
166
                         conflict_handler, merge_factory):
 
167
    new_cset = changeset.Changeset()
 
168
 
 
169
    for entry in cset.entries.itervalues():
 
170
        if entry.is_boring():
 
171
            new_cset.add_entry(entry)
 
172
        else:
 
173
            new_entry = make_merged_entry(entry, this, base, other, 
 
174
                                          conflict_handler)
 
175
            new_contents = make_merged_contents(entry, this, base, other, 
 
176
                                                conflict_handler,
 
177
                                                merge_factory)
 
178
            new_entry.contents_change = new_contents
 
179
            new_entry.metadata_change = make_merged_metadata(entry, base, other)
 
180
            new_cset.add_entry(new_entry)
 
181
 
 
182
    return new_cset
 
183
 
 
184
class ThreeWayConflict(Exception):
 
185
    def __init__(self, this, base, other):
 
186
        self.this = this
 
187
        self.base = base
 
188
        self.other = other
 
189
        msg = "Conflict merging %s %s and %s" % (this, base, other)
 
190
        Exception.__init__(self, msg)
 
191
 
 
192
def threeway_select(this, base, other):
 
193
    """Returns a value selected by the three-way algorithm.
 
194
    Raises ThreewayConflict if the algorithm yields a conflict"""
 
195
    if base == other:
 
196
        return this
 
197
    elif base == this:
 
198
        return other
 
199
    elif other == this:
 
200
        return this
 
201
    else:
 
202
        raise ThreeWayConflict(this, base, other)
 
203
 
 
204
 
 
205
def make_merged_entry(entry, this, base, other, conflict_handler):
 
206
    from bzrlib.trace import mutter
 
207
    def entry_data(file_id, tree):
 
208
        assert hasattr(tree, "__contains__"), "%s" % tree
 
209
        if not tree.has_or_had_id(file_id):
 
210
            return (None, None, "")
 
211
        entry = tree.inventory[file_id]
 
212
        my_dir = tree.id2path(entry.parent_id)
 
213
        if my_dir is None:
 
214
            my_dir = ""
 
215
        return entry.name, entry.parent_id, my_dir 
 
216
    this_name, this_parent, this_dir = entry_data(entry.id, this)
 
217
    base_name, base_parent, base_dir = entry_data(entry.id, base)
 
218
    other_name, other_parent, other_dir = entry_data(entry.id, other)
 
219
    mutter("Dirs: this, base, other %r %r %r" % (this_dir, base_dir, other_dir))
 
220
    mutter("Names: this, base, other %r %r %r" % (this_name, base_name, other_name))
 
221
    old_name = this_name
 
222
    try:
 
223
        new_name = threeway_select(this_name, base_name, other_name)
 
224
    except ThreeWayConflict:
 
225
        new_name = conflict_handler.rename_conflict(entry.id, this_name, 
 
226
                                                    base_name, other_name)
 
227
 
 
228
    old_parent = this_parent
 
229
    try:
 
230
        new_parent = threeway_select(this_parent, base_parent, other_parent)
 
231
    except ThreeWayConflict:
 
232
        new_parent = conflict_handler.move_conflict(entry.id, this_dir,
 
233
                                                    base_dir, other_dir)
 
234
    def get_path(name, parent):
 
235
        if name is not None:
 
236
            if name == "":
 
237
                assert parent is None
 
238
                return './.'
 
239
            parent_dir = {this_parent: this_dir, other_parent: other_dir, 
 
240
                          base_parent: base_dir}
 
241
            directory = parent_dir[parent]
 
242
            return os.path.join(directory, name)
 
243
        else:
 
244
            assert parent is None
 
245
            return None
 
246
 
 
247
    old_path = get_path(old_name, old_parent)
 
248
        
 
249
    new_entry = changeset.ChangesetEntry(entry.id, old_parent, old_path)
 
250
    new_entry.new_path = get_path(new_name, new_parent)
 
251
    new_entry.new_parent = new_parent
 
252
    mutter(repr(new_entry))
 
253
    return new_entry
 
254
 
 
255
 
 
256
def make_merged_contents(entry, this, base, other, conflict_handler,
 
257
                         merge_factory):
 
258
    contents = entry.contents_change
 
259
    if contents is None:
 
260
        return None
 
261
    if entry.id in this:
 
262
        this_path = this.id2abspath(entry.id)
 
263
    else:
 
264
        this_path = None
 
265
    def make_merge():
 
266
        if this_path is None:
 
267
            return conflict_handler.missing_for_merge(entry.id, 
 
268
                                                      other.id2path(entry.id))
 
269
        return merge_factory(entry.id, base, other)
 
270
 
 
271
    if isinstance(contents, changeset.ReplaceContents):
 
272
        base_contents = contents.old_contents
 
273
        other_contents = contents.new_contents
 
274
        if base_contents is None and other_contents is None:
 
275
            return None
 
276
        if other_contents is None:
 
277
            this_contents = get_contents(this, entry.id)
 
278
            if this_path is not None and bzrlib.osutils.lexists(this_path):
 
279
                if this_contents != base_contents:
 
280
                    return conflict_handler.rem_contents_conflict(this_path, 
 
281
                        this_contents, base_contents)
 
282
                return contents
 
283
            else:
 
284
                return None
 
285
        elif base_contents is None:
 
286
            if this_path is None or not bzrlib.osutils.lexists(this_path):
 
287
                return contents
 
288
            else:
 
289
                this_contents = get_contents(this, entry.id)
 
290
                if this_contents == other_contents:
 
291
                    return None
 
292
                else:
 
293
                    conflict_handler.new_contents_conflict(this_path, 
 
294
                        other_contents)
 
295
        elif isinstance(base_contents, changeset.TreeFileCreate) and \
 
296
            isinstance(other_contents, changeset.TreeFileCreate):
 
297
            return make_merge()
 
298
        else:
 
299
            this_contents = get_contents(this, entry.id)
 
300
            if this_contents == base_contents:
 
301
                return contents
 
302
            elif this_contents == other_contents:
 
303
                return None
 
304
            elif base_contents == other_contents:
 
305
                return None
 
306
            else:
 
307
                conflict_handler.threeway_contents_conflict(this_path,
 
308
                                                            this_contents,
 
309
                                                            base_contents,
 
310
                                                            other_contents)
 
311
                
 
312
 
 
313
def make_merged_metadata(entry, base, other):
 
314
    metadata = entry.metadata_change
 
315
    if metadata is None:
 
316
        return None
 
317
    assert isinstance(metadata, changeset.ChangeExecFlag)
 
318
    if metadata.new_exec_flag is None:
 
319
        return None
 
320
    elif metadata.old_exec_flag is None:
 
321
        return metadata
 
322
    else:
 
323
        return ExecFlagMerge(base, other, entry.id)
 
324
    
 
325
 
 
326
class ExecFlagMerge(object):
 
327
    def __init__(self, base_tree, other_tree, file_id):
 
328
        self.base_tree = base_tree
 
329
        self.other_tree = other_tree
 
330
        self.file_id = file_id
 
331
 
 
332
    def apply(self, filename, conflict_handler, reverse=False):
 
333
        if not reverse:
 
334
            base = self.base_tree
 
335
            other = self.other_tree
 
336
        else:
 
337
            base = self.other_tree
 
338
            other = self.base_tree
 
339
        base_exec_flag = base.is_executable(self.file_id)
 
340
        other_exec_flag = other.is_executable(self.file_id)
 
341
        this_mode = os.stat(filename).st_mode
 
342
        this_exec_flag = bool(this_mode & 0111)
 
343
        if (base_exec_flag != other_exec_flag and
 
344
            this_exec_flag != other_exec_flag):
 
345
            assert this_exec_flag == base_exec_flag
 
346
            current_mode = os.stat(filename).st_mode
 
347
            if other_exec_flag:
 
348
                umask = os.umask(0)
 
349
                os.umask(umask)
 
350
                to_mode = current_mode | (0100 & ~umask)
 
351
                # Enable x-bit for others only if they can read it.
 
352
                if current_mode & 0004:
 
353
                    to_mode |= 0001 & ~umask
 
354
                if current_mode & 0040:
 
355
                    to_mode |= 0010 & ~umask
 
356
            else:
 
357
                to_mode = current_mode & ~0111
 
358
            os.chmod(filename, to_mode)
 
359