~ubuntu-branches/ubuntu/lucid/bzr/lucid-proposed

« back to all changes in this revision

Viewing changes to bzrlib/merge_core.py

  • Committer: Bazaar Package Importer
  • Author(s): Jeff Bailey
  • Date: 2006-03-20 08:31:00 UTC
  • mfrom: (1.1.2 upstream)
  • mto: This revision was merged to the branch mainline in revision 4.
  • Revision ID: james.westby@ubuntu.com-20060320083100-ovdi2ssuw0epcx8s
Tags: 0.8~200603200831-0ubuntu1
* Snapshot uploaded to Dapper at Martin Pool's request.

* Disable testsuite for upload.  Fakeroot and the testsuite don't
  play along.

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