~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

Converted test cases to Tree Transform

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
import errno
21
21
 
22
22
import bzrlib
23
 
from bzrlib._changeset import generate_changeset, ExceptionConflictHandler
24
 
from bzrlib._changeset import Inventory, Diff3Merge, ReplaceContents
25
 
from bzrlib._merge_core import merge_flex, BackupBeforeChange
26
23
from bzrlib.branch import Branch
27
24
from bzrlib.delta import compare_trees
28
25
from bzrlib.errors import (BzrCommandError,
52
49
# stage 1: generate OLD->OTHER,
53
50
# stage 2: use MINE and OLD->OTHER to generate MINE -> RESULT
54
51
 
55
 
class _MergeConflictHandler(ExceptionConflictHandler):
56
 
    """Handle conflicts encountered while merging.
57
 
 
58
 
    This subclasses ExceptionConflictHandler, so that any types of
59
 
    conflict that are not explicitly handled cause an exception and
60
 
    terminate the merge.
61
 
    """
62
 
    def __init__(self, this_tree, base_tree, other_tree, ignore_zero=False):
63
 
        ExceptionConflictHandler.__init__(self)
64
 
        self.conflicts = 0
65
 
        self.ignore_zero = ignore_zero
66
 
        self.this_tree = this_tree
67
 
        self.base_tree = base_tree
68
 
        self.other_tree = other_tree
69
 
 
70
 
    def copy(self, source, dest):
71
 
        """Copy the text and mode of a file
72
 
        :param source: The path of the file to copy
73
 
        :param dest: The distination file to create
74
 
        """
75
 
        s_file = file(source, "rb")
76
 
        d_file = file(dest, "wb")
77
 
        for line in s_file:
78
 
            d_file.write(line)
79
 
        os.chmod(dest, 0777 & os.stat(source).st_mode)
80
 
 
81
 
    def dump(self, lines, dest):
82
 
        """Copy the text and mode of a file
83
 
        :param source: The path of the file to copy
84
 
        :param dest: The distination file to create
85
 
        """
86
 
        d_file = file(dest, "wb")
87
 
        for line in lines:
88
 
            d_file.write(line)
89
 
 
90
 
    def add_suffix(self, name, suffix, last_new_name=None, fix_inventory=True):
91
 
        """Rename a file to append a suffix.  If the new name exists, the
92
 
        suffix is added repeatedly until a non-existant name is found
93
 
 
94
 
        :param name: The path of the file
95
 
        :param suffix: The suffix to append
96
 
        :param last_new_name: (used for recursive calls) the last name tried
97
 
        """
98
 
        if last_new_name is None:
99
 
            last_new_name = name
100
 
        new_name = last_new_name+suffix
101
 
        try:
102
 
            rename(name, new_name)
103
 
            if fix_inventory is True:
104
 
                try:
105
 
                    relpath = self.this_tree.relpath(name)
106
 
                except NotBranchError:
107
 
                    relpath = None
108
 
                if relpath is not None:
109
 
                    file_id = self.this_tree.path2id(relpath)
110
 
                    if file_id is not None:
111
 
                        new_path = self.this_tree.relpath(new_name)
112
 
                        rename(new_name, name)
113
 
                        self.this_tree.rename_one(relpath, new_path)
114
 
                        assert self.this_tree.id2path(file_id) == new_path
115
 
        except OSError, e:
116
 
            if e.errno != errno.EEXIST and e.errno != errno.ENOTEMPTY:
117
 
                raise
118
 
            return self.add_suffix(name, suffix, last_new_name=new_name, 
119
 
                                   fix_inventory=fix_inventory)
120
 
        return new_name
121
 
 
122
 
    def conflict(self, text):
123
 
        warning(text)
124
 
        self.conflicts += 1
125
 
        
126
 
 
127
 
    def merge_conflict(self, new_file, this_path, base_lines, other_lines):
128
 
        """
129
 
        Handle diff3 conflicts by producing a .THIS, .BASE and .OTHER.  The
130
 
        main file will be a version with diff3 conflicts.
131
 
        :param new_file: Path to the output file with diff3 markers
132
 
        :param this_path: Path to the file text for the THIS tree
133
 
        :param base_path: Path to the file text for the BASE tree
134
 
        :param other_path: Path to the file text for the OTHER tree
135
 
        """
136
 
        self.add_suffix(this_path, ".THIS", fix_inventory=False)
137
 
        self.dump(base_lines, this_path+".BASE")
138
 
        self.dump(other_lines, this_path+".OTHER")
139
 
        rename(new_file, this_path)
140
 
        self.conflict("Diff3 conflict encountered in %s" % this_path)
141
 
 
142
 
    def weave_merge_conflict(self, filename, weave, other_i, out_file):
143
 
        """
144
 
        Handle weave conflicts by producing a .THIS, and .OTHER.  The
145
 
        main file will be a version with diff3-style conflicts.
146
 
        """
147
 
        self.add_suffix(filename, ".THIS", fix_inventory=False)
148
 
        out_file.commit()
149
 
        self.dump(weave.get_iter(other_i), filename+".OTHER")
150
 
        self.conflict("Text conflict encountered in %s" % filename)
151
 
 
152
 
    def new_contents_conflict(self, filename, other_contents):
153
 
        """Conflicting contents for newly added file."""
154
 
        other_contents(filename + ".OTHER", self, False)
155
 
        self.conflict("Conflict in newly added file %s" % filename)
156
 
    
157
 
 
158
 
    def target_exists(self, entry, target, old_path):
159
 
        """Handle the case when the target file or dir exists"""
160
 
        moved_path = self.add_suffix(target, ".moved")
161
 
        self.conflict("Moved existing %s to %s" % (target, moved_path))
162
 
 
163
 
    def rmdir_non_empty(self, filename):
164
 
        """Handle the case where the dir to be removed still has contents"""
165
 
        self.conflict("Directory %s not removed because it is not empty"\
166
 
            % filename)
167
 
        return "skip"
168
 
 
169
 
    def rem_contents_conflict(self, filename, this_contents, base_contents):
170
 
        base_contents(filename+".BASE", self)
171
 
        this_contents(filename+".THIS", self)
172
 
        self.conflict("Other branch deleted locally modified file %s" %
173
 
                      filename)
174
 
        return ReplaceContents(this_contents, None)
175
 
 
176
 
    def abs_this_path(self, file_id):
177
 
        """Return the absolute path for a file_id in the this tree."""
178
 
        return self.this_tree.id2abspath(file_id)
179
 
 
180
 
    def add_missing_parents(self, file_id, tree):
181
 
        """If some of the parents for file_id are missing, add them."""
182
 
        entry = tree.inventory[file_id]
183
 
        if entry.parent_id not in self.this_tree:
184
 
            return self.create_all_missing(entry.parent_id, tree)
185
 
        else:
186
 
            return self.abs_this_path(entry.parent_id)
187
 
 
188
 
    def create_all_missing(self, file_id, tree):
189
 
        """Add contents for a file_id and all its parents to a tree."""
190
 
        entry = tree.inventory[file_id]
191
 
        if entry.parent_id is not None and entry.parent_id not in self.this_tree:
192
 
            abspath = self.create_all_missing(entry.parent_id, tree)
193
 
        else:
194
 
            abspath = self.abs_this_path(entry.parent_id)
195
 
        entry_path = pathjoin(abspath, entry.name)
196
 
        if not os.path.isdir(entry_path):
197
 
            self.create(file_id, entry_path, tree)
198
 
        return entry_path
199
 
 
200
 
    def create(self, file_id, path, tree):
201
 
        """Uses tree data to create a filesystem object for the file_id"""
202
 
        from bzrlib._changeset import get_contents
203
 
        get_contents(tree, file_id)(path, self)
204
 
 
205
 
    def missing_for_merge(self, file_id, other_path):
206
 
        """The file_id doesn't exist in THIS, but does in OTHER and BASE"""
207
 
        self.conflict("Other branch modified locally deleted file %s" %
208
 
                      other_path)
209
 
        parent_dir = self.add_missing_parents(file_id, self.other_tree)
210
 
        stem = pathjoin(parent_dir, os.path.basename(other_path))
211
 
        self.create(file_id, stem+".OTHER", self.other_tree)
212
 
        self.create(file_id, stem+".BASE", self.base_tree)
213
 
 
214
 
    def threeway_contents_conflict(filename, this_contents, base_contents,
215
 
                                   other_contents):
216
 
        self.conflict("Three-way conflict merging %s" % filename)
217
 
 
218
 
    def finalize(self):
219
 
        if self.conflicts == 0:
220
 
            if not self.ignore_zero:
221
 
                note("All changes applied successfully.")
222
 
        else:
223
 
            note("%d conflicts encountered." % self.conflicts)
224
 
 
225
52
def _get_tree(treespec, local_branch=None):
226
53
    location, revno = treespec
227
54
    branch = Branch.open_containing(location)[0]
298
125
        merger._set_interesting_files(interesting_files)
299
126
    merger.show_base = show_base 
300
127
    merger.reprocess = reprocess
301
 
    merger.conflict_handler = _MergeConflictHandler(merger.this_tree, 
302
 
                                                    base_tree, other_tree,
303
 
                                                    ignore_zero=ignore_zero)
304
128
    merger.other_rev_id = other_rev_id
305
129
    merger.other_basis = other_rev_id
306
130
    return merger.do_merge()
323
147
        self.interesting_ids = None
324
148
        self.show_base = False
325
149
        self.reprocess = False
326
 
        self.conflict_handler = _MergeConflictHandler(self.this_tree, 
327
 
                                                      base_tree, other_tree)
328
150
 
329
151
    def revision_tree(self, revision_id):
330
152
        return self.this_branch.repository.revision_tree(revision_id)