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