~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge_core.py

[merge] from aaron

Show diffs side-by-side

added added

removed removed

Lines of Context:
5
5
from bzrlib.osutils import backup_file, rename
6
6
from bzrlib.merge3 import Merge3
7
7
import bzrlib
 
8
from changeset import get_contents
8
9
 
9
10
class ApplyMerge3:
10
11
    """Contents-change wrapper around merge3.Merge3"""
12
13
        self.file_id = file_id
13
14
        self.base = base
14
15
        self.other = other
15
 
 
 
16
 
 
17
    def is_creation(self):
 
18
        return False
 
19
 
 
20
    def is_deletion(self):
 
21
        return False
 
22
 
16
23
    def __eq__(self, other):
17
24
        if not isinstance(other, ApplyMerge3):
18
25
            return False
35
42
                raise Exception("%s not in tree" % self.file_id)
36
43
                return ()
37
44
            return tree.get_file(self.file_id).readlines()
38
 
        ### garh. 
39
 
        other_entry = other.tree.inventory[self.file_id]
40
 
        if other_entry.kind == 'symlink':
41
 
            self.apply_symlink(other_entry, base, other, filename)
42
 
            return
43
45
        base_lines = get_lines(base)
44
46
        other_lines = get_lines(other)
45
47
        m3 = Merge3(base_lines, file(filename, "rb").readlines(), other_lines)
63
65
            conflict_handler.merge_conflict(new_file, filename, base_lines,
64
66
                                            other_lines)
65
67
 
66
 
    def apply_symlink(self, other_entry, base, other, filename):
67
 
        if self.file_id in base:
68
 
            base_entry = base.tree.inventory[self.file_id]
69
 
            base_entry._read_tree_state(base.tree)
70
 
        else:
71
 
            base_entry = None
72
 
        other_entry._read_tree_state(other.tree)
73
 
        if not base_entry or other_entry.detect_changes(base_entry):
74
 
            other_change = True
75
 
        else:
76
 
            other_change = False
77
 
        this_link = os.readlink(filename)
78
 
        if not base_entry or base_entry.symlink_target != this_link:
79
 
            this_change = True
80
 
        else:
81
 
            this_change = False
82
 
        if this_change and not other_change:
83
 
            pass
84
 
        elif not this_change and other_change:
85
 
            os.unlink(filename)
86
 
            os.symlink(other_entry.symlink_target, filename)
87
 
        elif this_change and other_change:
88
 
            # conflict
89
 
            os.unlink(filename)
90
 
            os.symlink(other_entry.symlink_target, filename + '.OTHER')
91
 
            os.symlink(this_link, filename + '.THIS')
92
 
            if base_entry is not None:
93
 
                os.symlink(other_entry.symlink_target, filename + '.BASE')
94
 
            note("merge3 conflict in '%s'.\n", filename)
95
 
 
96
68
 
97
69
class BackupBeforeChange:
98
70
    """Contents-change wrapper to back up file first"""
99
71
    def __init__(self, contents_change):
100
72
        self.contents_change = contents_change
101
 
 
 
73
 
 
74
    def is_creation(self):
 
75
        return self.contents_change.is_creation()
 
76
 
 
77
    def is_deletion(self):
 
78
        return self.contents_change.is_deletion()
 
79
 
102
80
    def __eq__(self, other):
103
81
        if not isinstance(other, BackupBeforeChange):
104
82
            return False
129
107
    cset = changeset_function(base, other, interesting_ids)
130
108
    new_cset = make_merge_changeset(cset, this, base, other, 
131
109
                                    conflict_handler, merge_factory)
132
 
    result = apply_changeset(new_cset, invert_invent(this.tree.inventory),
133
 
                             this.root, conflict_handler, False)
 
110
    result = apply_changeset(new_cset, invert_invent(this.inventory),
 
111
                             this.basedir, conflict_handler, False)
134
112
    conflict_handler.finalize()
135
113
    return result
136
114
 
139
117
def make_merge_changeset(cset, this, base, other, 
140
118
                         conflict_handler, merge_factory):
141
119
    new_cset = changeset.Changeset()
142
 
    def get_this_contents(id):
143
 
        path = this.readonly_path(id)
144
 
        if os.path.isdir(path):
145
 
            return changeset.dir_create
146
 
        else:
147
 
            return changeset.FileCreate(file(path, "rb").read())
148
120
 
149
121
    for entry in cset.entries.itervalues():
150
122
        if entry.is_boring():
188
160
        assert hasattr(tree, "__contains__"), "%s" % tree
189
161
        if not tree.has_or_had_id(file_id):
190
162
            return (None, None, "")
191
 
        entry = tree.tree.inventory[file_id]
 
163
        entry = tree.inventory[file_id]
192
164
        my_dir = tree.id2path(entry.parent_id)
193
165
        if my_dir is None:
194
166
            my_dir = ""
233
205
    return new_entry
234
206
 
235
207
 
236
 
def get_contents(entry, tree):
237
 
    return get_id_contents(entry.id, tree)
238
 
 
239
 
def get_id_contents(file_id, tree):
240
 
    """Get a contents change element suitable for use with ReplaceContents
241
 
    """
242
 
    tree_entry = tree.tree.inventory[file_id]
243
 
    if tree_entry.kind == "file":
244
 
        return changeset.FileCreate(tree.get_file(file_id).read())
245
 
    elif tree_entry.kind == "symlink":
246
 
        return changeset.SymlinkCreate(tree.get_symlink_target(file_id))
247
 
    else:
248
 
        assert tree_entry.kind in ("root_directory", "directory")
249
 
        return changeset.dir_create
250
 
 
251
208
def make_merged_contents(entry, this, base, other, conflict_handler,
252
209
                         merge_factory):
253
210
    contents = entry.contents_change
254
211
    if contents is None:
255
212
        return None
256
 
    this_path = this.readonly_path(entry.id)
 
213
    if entry.id in this:
 
214
        this_path = this.id2abspath(entry.id)
 
215
    else:
 
216
        this_path = None
257
217
    def make_merge():
258
218
        if this_path is None:
259
219
            return conflict_handler.missing_for_merge(entry.id, 
264
224
        if contents.old_contents is None and contents.new_contents is None:
265
225
            return None
266
226
        if contents.new_contents is None:
267
 
            this_contents = get_contents(entry, this)
 
227
            this_contents = get_contents(this, entry.id)
268
228
            if this_path is not None and bzrlib.osutils.lexists(this_path):
269
229
                if this_contents != contents.old_contents:
270
230
                    return conflict_handler.rem_contents_conflict(this_path, 
276
236
            if this_path is None or not bzrlib.osutils.lexists(this_path):
277
237
                return contents
278
238
            else:
279
 
                this_contents = get_contents(entry, this)
 
239
                this_contents = get_contents(this, entry.id)
280
240
                if this_contents == contents.new_contents:
281
241
                    return None
282
242
                else:
283
 
                    other_path = other.readonly_path(entry.id)    
284
243
                    conflict_handler.new_contents_conflict(this_path, 
285
 
                                                           other_path)
286
 
        elif (isinstance(contents.old_contents, changeset.FileCreate)
287
 
              and isinstance(contents.new_contents, changeset.FileCreate)):
288
 
            return make_merge()
289
 
        elif (isinstance(contents.old_contents, changeset.SymlinkCreate)
290
 
              and isinstance(contents.new_contents, changeset.SymlinkCreate)):
 
244
                                                           other_contents)
 
245
        elif isinstance(contents.old_contents, changeset.TreeFileCreate) and \
 
246
            isinstance(contents.new_contents, changeset.TreeFileCreate):
291
247
            return make_merge()
292
248
        else:
293
 
            raise Exception("Unhandled merge scenario")
 
249
            this_contents = get_contents(this, entry.id)
 
250
            if this_contents == contents.old_contents:
 
251
                return contents
 
252
            elif this_contents == contents.new_contents:
 
253
                return None
 
254
            elif contents.old_contents == contents.new_contents:
 
255
                return None
 
256
            else:
 
257
                conflict_handler.threeway_contents_conflict(this_path, 
 
258
                    this_contents, contents.old_contents,
 
259
                    contents.new_contents)
 
260
                
294
261
 
295
262
def make_merged_metadata(entry, base, other):
296
263
    metadata = entry.metadata_change
297
264
    if metadata is None:
298
265
        return None
299
 
    if isinstance(metadata, changeset.ChangeExecFlag):
300
 
        if metadata.new_exec_flag is None:
301
 
            return None
302
 
        elif metadata.old_exec_flag is None:
303
 
            return metadata
304
 
        else:
305
 
            base_path = base.readonly_path(entry.id)
306
 
            other_path = other.readonly_path(entry.id)    
307
 
            return ExecFlagMerge(base_path, other_path)
 
266
    assert isinstance(metadata, changeset.ChangeExecFlag)
 
267
    if metadata.new_exec_flag is None:
 
268
        return None
 
269
    elif metadata.old_exec_flag is None:
 
270
        return metadata
 
271
    else:
 
272
        return ExecFlagMerge(base, other, entry.id)
308
273
    
309
274
 
310
275
class ExecFlagMerge(object):
311
 
    def __init__(self, base_path, other_path):
312
 
        self.base_path = base_path
313
 
        self.other_path = other_path
 
276
    def __init__(self, base_tree, other_tree, file_id):
 
277
        self.base_tree = base_tree
 
278
        self.other_tree = other_tree
 
279
        self.file_id = file_id
314
280
 
315
281
    def apply(self, filename, conflict_handler, reverse=False):
316
282
        if not reverse:
317
 
            base = self.base_path
318
 
            other = self.other_path
 
283
            base = self.base_tree
 
284
            other = self.other_tree
319
285
        else:
320
 
            base = self.other_path
321
 
            other = self.base_path
322
 
        base_mode = os.stat(base).st_mode
323
 
        base_exec_flag = bool(base_mode & 0111)
324
 
        other_mode = os.stat(other).st_mode
325
 
        other_exec_flag = bool(other_mode & 0111)
 
286
            base = self.other_tree
 
287
            other = self.base_tree
 
288
        base_exec_flag = base.is_executable(self.file_id)
 
289
        other_exec_flag = other.is_executable(self.file_id)
326
290
        this_mode = os.stat(filename).st_mode
327
291
        this_exec_flag = bool(this_mode & 0111)
328
292
        if (base_exec_flag != other_exec_flag and