~bzr-pqm/bzr/bzr.dev

1092.2.24 by Robert Collins
merge from martins newformat branch - brings in transport abstraction
1
import os.path
2
493 by Martin Pool
- Merge aaron's merge command
3
import changeset
4
from changeset import Inventory, apply_changeset, invert_dict
1092.2.24 by Robert Collins
merge from martins newformat branch - brings in transport abstraction
5
from bzrlib.osutils import backup_file, rename
6
from bzrlib.merge3 import Merge3
7
import bzrlib
1185.12.30 by Aaron Bentley
Moved all fooCreate into get_contents
8
from changeset import get_contents
974.1.4 by Aaron Bentley
Implemented merge3 as the default text merge
9
10
class ApplyMerge3:
11
    """Contents-change wrapper around merge3.Merge3"""
1185.18.1 by Aaron Bentley
Added --show-base to merge
12
    def __init__(self, file_id, base, other, show_base=False):
974.1.23 by Aaron Bentley
Avoided unnecessary temp files
13
        self.file_id = file_id
14
        self.base = base
15
        self.other = other
1185.18.1 by Aaron Bentley
Added --show-base to merge
16
        self.show_base = show_base
1185.12.31 by Aaron Bentley
Replaced FileCreate with TreeFileCreate, fixed revert with empty parents
17
18
    def is_creation(self):
19
        return False
20
21
    def is_deletion(self):
22
        return False
23
974.1.4 by Aaron Bentley
Implemented merge3 as the default text merge
24
    def __eq__(self, other):
25
        if not isinstance(other, ApplyMerge3):
26
            return False
974.1.23 by Aaron Bentley
Avoided unnecessary temp files
27
        return (self.base == other.base and 
28
                self.other == other.other and self.file_id == other.file_id)
974.1.4 by Aaron Bentley
Implemented merge3 as the default text merge
29
30
    def __ne__(self, other):
31
        return not (self == other)
32
33
    def apply(self, filename, conflict_handler, reverse=False):
34
        new_file = filename+".new" 
35
        if not reverse:
974.1.23 by Aaron Bentley
Avoided unnecessary temp files
36
            base = self.base
37
            other = self.other
974.1.4 by Aaron Bentley
Implemented merge3 as the default text merge
38
        else:
974.1.23 by Aaron Bentley
Avoided unnecessary temp files
39
            base = self.other
40
            other = self.base
41
        def get_lines(tree):
42
            if self.file_id not in tree:
43
                raise Exception("%s not in tree" % self.file_id)
44
                return ()
45
            return tree.get_file(self.file_id).readlines()
46
        base_lines = get_lines(base)
47
        other_lines = get_lines(other)
48
        m3 = Merge3(base_lines, file(filename, "rb").readlines(), other_lines)
974.1.4 by Aaron Bentley
Implemented merge3 as the default text merge
49
50
        new_conflicts = False
51
        output_file = file(new_file, "wb")
52
        start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
1185.18.1 by Aaron Bentley
Added --show-base to merge
53
        if self.show_base is True:
54
            base_marker = '|' * 7
55
        else:
56
            base_marker = None
974.1.4 by Aaron Bentley
Implemented merge3 as the default text merge
57
        for line in m3.merge_lines(name_a = "TREE", name_b = "MERGE-SOURCE", 
1185.18.1 by Aaron Bentley
Added --show-base to merge
58
                       name_base = "BASE-REVISION",
59
                       start_marker=start_marker, base_marker=base_marker):
974.1.4 by Aaron Bentley
Implemented merge3 as the default text merge
60
            if line.startswith(start_marker):
61
                new_conflicts = True
974.1.45 by aaron.bentley at utoronto
Shortened conflict markers to 7 characters, to please smerge
62
                output_file.write(line.replace(start_marker, '<' * 7))
974.1.4 by Aaron Bentley
Implemented merge3 as the default text merge
63
            else:
64
                output_file.write(line)
65
        output_file.close()
66
        if not new_conflicts:
67
            os.chmod(new_file, os.stat(filename).st_mode)
1185.1.40 by Robert Collins
Merge what applied of Alexander Belchenko's win32 patch.
68
            rename(new_file, filename)
974.1.4 by Aaron Bentley
Implemented merge3 as the default text merge
69
            return
70
        else:
974.1.23 by Aaron Bentley
Avoided unnecessary temp files
71
            conflict_handler.merge_conflict(new_file, filename, base_lines,
72
                                            other_lines)
493 by Martin Pool
- Merge aaron's merge command
73
974.1.8 by Aaron Bentley
Added default backups for merge-revert
74
75
class BackupBeforeChange:
76
    """Contents-change wrapper to back up file first"""
77
    def __init__(self, contents_change):
78
        self.contents_change = contents_change
1185.12.31 by Aaron Bentley
Replaced FileCreate with TreeFileCreate, fixed revert with empty parents
79
80
    def is_creation(self):
81
        return self.contents_change.is_creation()
82
83
    def is_deletion(self):
84
        return self.contents_change.is_deletion()
85
974.1.8 by Aaron Bentley
Added default backups for merge-revert
86
    def __eq__(self, other):
87
        if not isinstance(other, BackupBeforeChange):
88
            return False
89
        return (self.contents_change == other.contents_change)
90
91
    def __ne__(self, other):
92
        return not (self == other)
93
94
    def apply(self, filename, conflict_handler, reverse=False):
95
        backup_file(filename)
96
        self.contents_change.apply(filename, conflict_handler, reverse)
97
98
493 by Martin Pool
- Merge aaron's merge command
99
def invert_invent(inventory):
100
    invert_invent = {}
974.1.19 by Aaron Bentley
Removed MergeTree.inventory
101
    for file_id in inventory:
102
        path = inventory.id2path(file_id)
103
        if path == '':
104
            path = './.'
105
        else:
106
            path = './' + path
107
        invert_invent[file_id] = path
493 by Martin Pool
- Merge aaron's merge command
108
    return invert_invent
109
110
111
def merge_flex(this, base, other, changeset_function, inventory_function,
974.1.17 by Aaron Bentley
Switched to using a set of interesting file_ids instead of SourceFile attribute
112
               conflict_handler, merge_factory, interesting_ids):
974.1.19 by Aaron Bentley
Removed MergeTree.inventory
113
    cset = changeset_function(base, other, interesting_ids)
974.1.20 by Aaron Bentley
Eliminated ThreeWayInventory
114
    new_cset = make_merge_changeset(cset, this, base, other, 
974.1.3 by Aaron Bentley
Added merge_factory parameter to merge_flex
115
                                    conflict_handler, merge_factory)
1185.12.40 by abentley
Got even closer to standard Tree interface
116
    result = apply_changeset(new_cset, invert_invent(this.inventory),
1185.12.38 by abentley
semi-broke merge
117
                             this.basedir, conflict_handler, False)
622 by Martin Pool
Updated merge patch from Aaron
118
    return result
493 by Martin Pool
- Merge aaron's merge command
119
    
120
974.1.20 by Aaron Bentley
Eliminated ThreeWayInventory
121
def make_merge_changeset(cset, this, base, other, 
974.1.3 by Aaron Bentley
Added merge_factory parameter to merge_flex
122
                         conflict_handler, merge_factory):
493 by Martin Pool
- Merge aaron's merge command
123
    new_cset = changeset.Changeset()
124
125
    for entry in cset.entries.itervalues():
126
        if entry.is_boring():
127
            new_cset.add_entry(entry)
128
        else:
974.1.20 by Aaron Bentley
Eliminated ThreeWayInventory
129
            new_entry = make_merged_entry(entry, this, base, other, 
130
                                          conflict_handler)
909.1.4 by Aaron Bentley
Fixed conflict handling for missing merge targets
131
            new_contents = make_merged_contents(entry, this, base, other, 
974.1.20 by Aaron Bentley
Eliminated ThreeWayInventory
132
                                                conflict_handler,
974.1.3 by Aaron Bentley
Added merge_factory parameter to merge_flex
133
                                                merge_factory)
850 by Martin Pool
- Merge merge updates from aaron
134
            new_entry.contents_change = new_contents
135
            new_entry.metadata_change = make_merged_metadata(entry, base, other)
136
            new_cset.add_entry(new_entry)
137
493 by Martin Pool
- Merge aaron's merge command
138
    return new_cset
139
974.1.22 by Aaron Bentley
Refactored code
140
class ThreeWayConflict(Exception):
141
    def __init__(self, this, base, other):
142
        self.this = this
143
        self.base = base
144
        self.other = other
145
        msg = "Conflict merging %s %s and %s" % (this, base, other)
146
        Exception.__init__(self, msg)
147
148
def threeway_select(this, base, other):
149
    """Returns a value selected by the three-way algorithm.
150
    Raises ThreewayConflict if the algorithm yields a conflict"""
151
    if base == other:
152
        return this
153
    elif base == this:
154
        return other
155
    elif other == this:
156
        return this
157
    else:
158
        raise ThreeWayConflict(this, base, other)
159
160
974.1.20 by Aaron Bentley
Eliminated ThreeWayInventory
161
def make_merged_entry(entry, this, base, other, conflict_handler):
909.1.2 by aaron.bentley at utoronto
Fixed rename handling in merge
162
    from bzrlib.trace import mutter
974.1.20 by Aaron Bentley
Eliminated ThreeWayInventory
163
    def entry_data(file_id, tree):
164
        assert hasattr(tree, "__contains__"), "%s" % tree
1185.12.39 by abentley
Propogated has_or_had_id to Tree
165
        if not tree.has_or_had_id(file_id):
974.1.20 by Aaron Bentley
Eliminated ThreeWayInventory
166
            return (None, None, "")
1185.12.40 by abentley
Got even closer to standard Tree interface
167
        entry = tree.inventory[file_id]
974.1.20 by Aaron Bentley
Eliminated ThreeWayInventory
168
        my_dir = tree.id2path(entry.parent_id)
169
        if my_dir is None:
170
            my_dir = ""
171
        return entry.name, entry.parent_id, my_dir 
172
    this_name, this_parent, this_dir = entry_data(entry.id, this)
173
    base_name, base_parent, base_dir = entry_data(entry.id, base)
174
    other_name, other_parent, other_dir = entry_data(entry.id, other)
909.1.2 by aaron.bentley at utoronto
Fixed rename handling in merge
175
    mutter("Dirs: this, base, other %r %r %r" % (this_dir, base_dir, other_dir))
176
    mutter("Names: this, base, other %r %r %r" % (this_name, base_name, other_name))
974.1.22 by Aaron Bentley
Refactored code
177
    old_name = this_name
178
    try:
179
        new_name = threeway_select(this_name, base_name, other_name)
180
    except ThreeWayConflict:
181
        new_name = conflict_handler.rename_conflict(entry.id, this_name, 
182
                                                    base_name, other_name)
183
184
    old_parent = this_parent
185
    try:
186
        new_parent = threeway_select(this_parent, base_parent, other_parent)
187
    except ThreeWayConflict:
188
        new_parent = conflict_handler.move_conflict(entry.id, this_dir,
189
                                                    base_dir, other_dir)
190
    def get_path(name, parent):
974.1.47 by Aaron Bentley
Merged changes from the merge4 branch
191
        if name is not None:
192
            if name == "":
193
                assert parent is None
194
                return './.'
974.1.22 by Aaron Bentley
Refactored code
195
            parent_dir = {this_parent: this_dir, other_parent: other_dir, 
196
                          base_parent: base_dir}
197
            directory = parent_dir[parent]
198
            return os.path.join(directory, name)
199
        else:
974.1.47 by Aaron Bentley
Merged changes from the merge4 branch
200
            assert parent is None
974.1.22 by Aaron Bentley
Refactored code
201
            return None
202
203
    old_path = get_path(old_name, old_parent)
204
        
909.1.2 by aaron.bentley at utoronto
Fixed rename handling in merge
205
    new_entry = changeset.ChangesetEntry(entry.id, old_parent, old_path)
974.1.22 by Aaron Bentley
Refactored code
206
    new_entry.new_path = get_path(new_name, new_parent)
493 by Martin Pool
- Merge aaron's merge command
207
    new_entry.new_parent = new_parent
909.1.2 by aaron.bentley at utoronto
Fixed rename handling in merge
208
    mutter(repr(new_entry))
850 by Martin Pool
- Merge merge updates from aaron
209
    return new_entry
210
211
974.1.20 by Aaron Bentley
Eliminated ThreeWayInventory
212
def make_merged_contents(entry, this, base, other, conflict_handler,
974.1.3 by Aaron Bentley
Added merge_factory parameter to merge_flex
213
                         merge_factory):
850 by Martin Pool
- Merge merge updates from aaron
214
    contents = entry.contents_change
215
    if contents is None:
216
        return None
1185.12.36 by abentley
Removed all remaining use of readonly_path
217
    if entry.id in this:
218
        this_path = this.id2abspath(entry.id)
219
    else:
220
        this_path = None
974.1.3 by Aaron Bentley
Added merge_factory parameter to merge_flex
221
    def make_merge():
850 by Martin Pool
- Merge merge updates from aaron
222
        if this_path is None:
974.1.20 by Aaron Bentley
Eliminated ThreeWayInventory
223
            return conflict_handler.missing_for_merge(entry.id, 
224
                                                      other.id2path(entry.id))
974.1.23 by Aaron Bentley
Avoided unnecessary temp files
225
        return merge_factory(entry.id, base, other)
850 by Martin Pool
- Merge merge updates from aaron
226
227
    if isinstance(contents, changeset.ReplaceContents):
1185.12.67 by Aaron Bentley
attempted to clarify the three-way merge code
228
        base_contents = contents.old_contents
229
        other_contents = contents.new_contents
230
        if base_contents is None and other_contents is None:
850 by Martin Pool
- Merge merge updates from aaron
231
            return None
1185.12.67 by Aaron Bentley
attempted to clarify the three-way merge code
232
        if other_contents is None:
1185.12.30 by Aaron Bentley
Moved all fooCreate into get_contents
233
            this_contents = get_contents(this, entry.id)
1092.2.24 by Robert Collins
merge from martins newformat branch - brings in transport abstraction
234
            if this_path is not None and bzrlib.osutils.lexists(this_path):
1185.12.67 by Aaron Bentley
attempted to clarify the three-way merge code
235
                if this_contents != base_contents:
1185.10.8 by Aaron Bentley
Conflict handling where OTHER is deleted
236
                    return conflict_handler.rem_contents_conflict(this_path, 
1185.12.67 by Aaron Bentley
attempted to clarify the three-way merge code
237
                        this_contents, base_contents)
850 by Martin Pool
- Merge merge updates from aaron
238
                return contents
239
            else:
240
                return None
1185.12.67 by Aaron Bentley
attempted to clarify the three-way merge code
241
        elif base_contents is None:
1092.2.24 by Robert Collins
merge from martins newformat branch - brings in transport abstraction
242
            if this_path is None or not bzrlib.osutils.lexists(this_path):
850 by Martin Pool
- Merge merge updates from aaron
243
                return contents
244
            else:
1185.12.30 by Aaron Bentley
Moved all fooCreate into get_contents
245
                this_contents = get_contents(this, entry.id)
1185.12.67 by Aaron Bentley
attempted to clarify the three-way merge code
246
                if this_contents == other_contents:
850 by Martin Pool
- Merge merge updates from aaron
247
                    return None
248
                else:
249
                    conflict_handler.new_contents_conflict(this_path, 
1185.12.67 by Aaron Bentley
attempted to clarify the three-way merge code
250
                        other_contents)
251
        elif isinstance(base_contents, changeset.TreeFileCreate) and \
252
            isinstance(other_contents, changeset.TreeFileCreate):
1448 by Robert Collins
revert symlinks correctly
253
            return make_merge()
850 by Martin Pool
- Merge merge updates from aaron
254
        else:
1185.12.33 by Aaron Bentley
Fixed symlink reverting
255
            this_contents = get_contents(this, entry.id)
1185.12.67 by Aaron Bentley
attempted to clarify the three-way merge code
256
            if this_contents == base_contents:
1185.12.33 by Aaron Bentley
Fixed symlink reverting
257
                return contents
1185.12.67 by Aaron Bentley
attempted to clarify the three-way merge code
258
            elif this_contents == other_contents:
1185.12.33 by Aaron Bentley
Fixed symlink reverting
259
                return None
1185.12.67 by Aaron Bentley
attempted to clarify the three-way merge code
260
            elif base_contents == other_contents:
1185.12.33 by Aaron Bentley
Fixed symlink reverting
261
                return None
262
            else:
1185.12.68 by Aaron Bentley
tweaked spacing
263
                conflict_handler.threeway_contents_conflict(this_path,
264
                                                            this_contents,
265
                                                            base_contents,
266
                                                            other_contents)
1185.12.33 by Aaron Bentley
Fixed symlink reverting
267
                
850 by Martin Pool
- Merge merge updates from aaron
268
269
def make_merged_metadata(entry, base, other):
1398 by Robert Collins
integrate in Gustavos x-bit patch
270
    metadata = entry.metadata_change
271
    if metadata is None:
272
        return None
1185.12.36 by abentley
Removed all remaining use of readonly_path
273
    assert isinstance(metadata, changeset.ChangeExecFlag)
274
    if metadata.new_exec_flag is None:
275
        return None
276
    elif metadata.old_exec_flag is None:
277
        return metadata
278
    else:
279
        return ExecFlagMerge(base, other, entry.id)
493 by Martin Pool
- Merge aaron's merge command
280
    
281
1434 by Robert Collins
merge Gustavos executable2 patch
282
class ExecFlagMerge(object):
1185.12.36 by abentley
Removed all remaining use of readonly_path
283
    def __init__(self, base_tree, other_tree, file_id):
284
        self.base_tree = base_tree
285
        self.other_tree = other_tree
286
        self.file_id = file_id
493 by Martin Pool
- Merge aaron's merge command
287
288
    def apply(self, filename, conflict_handler, reverse=False):
289
        if not reverse:
1185.12.36 by abentley
Removed all remaining use of readonly_path
290
            base = self.base_tree
291
            other = self.other_tree
493 by Martin Pool
- Merge aaron's merge command
292
        else:
1185.12.36 by abentley
Removed all remaining use of readonly_path
293
            base = self.other_tree
294
            other = self.base_tree
295
        base_exec_flag = base.is_executable(self.file_id)
296
        other_exec_flag = other.is_executable(self.file_id)
1434 by Robert Collins
merge Gustavos executable2 patch
297
        this_mode = os.stat(filename).st_mode
298
        this_exec_flag = bool(this_mode & 0111)
299
        if (base_exec_flag != other_exec_flag and
300
            this_exec_flag != other_exec_flag):
301
            assert this_exec_flag == base_exec_flag
302
            current_mode = os.stat(filename).st_mode
303
            if other_exec_flag:
304
                umask = os.umask(0)
305
                os.umask(umask)
306
                to_mode = current_mode | (0100 & ~umask)
307
                # Enable x-bit for others only if they can read it.
308
                if current_mode & 0004:
309
                    to_mode |= 0001 & ~umask
310
                if current_mode & 0040:
311
                    to_mode |= 0010 & ~umask
312
            else:
313
                to_mode = current_mode & ~0111
314
            os.chmod(filename, to_mode)
315