~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

Merge in format-5 work - release bzr 0.1rc1.

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
19
import tempfile
20
20
import shutil
21
21
import errno
22
 
from fetch import greedy_fetch
23
22
 
24
23
import bzrlib.osutils
25
24
import bzrlib.revision
26
25
from bzrlib.merge_core import merge_flex, ApplyMerge3, BackupBeforeChange
27
26
from bzrlib.changeset import generate_changeset, ExceptionConflictHandler
28
 
from bzrlib.changeset import Inventory, Diff3Merge
29
 
from bzrlib.branch import find_branch
30
 
from bzrlib.errors import BzrCommandError, UnrelatedBranches
 
27
from bzrlib.changeset import Inventory, Diff3Merge, ReplaceContents
 
28
from bzrlib.branch import Branch
 
29
from bzrlib.errors import BzrCommandError, UnrelatedBranches, NoCommonAncestor
 
30
from bzrlib.errors import NoCommits
31
31
from bzrlib.delta import compare_trees
32
32
from bzrlib.trace import mutter, warning
33
 
from bzrlib.fetch import greedy_fetch
 
33
from bzrlib.fetch import greedy_fetch, fetch
34
34
from bzrlib.revision import is_ancestor
 
35
from bzrlib.osutils import rename
 
36
from bzrlib.revision import common_ancestor, MultipleRevisionSources
 
37
from bzrlib.errors import NoSuchRevision
 
38
 
 
39
# TODO: build_working_dir can be built on something simpler than merge()
 
40
 
 
41
# FIXME: merge() parameters seem oriented towards the command line
35
42
 
36
43
# comments from abentley on irc: merge happens in two stages, each
37
44
# of which generates a changeset object
46
53
    conflict that are not explicitly handled cause an exception and
47
54
    terminate the merge.
48
55
    """
49
 
    def __init__(self, dir, ignore_zero=False):
50
 
        ExceptionConflictHandler.__init__(self, dir)
 
56
    def __init__(self, this_tree, base_tree, other_tree, ignore_zero=False):
 
57
        ExceptionConflictHandler.__init__(self)
51
58
        self.conflicts = 0
52
59
        self.ignore_zero = ignore_zero
 
60
        self.this_tree = this_tree
 
61
        self.base_tree = base_tree
 
62
        self.other_tree = other_tree
53
63
 
54
64
    def copy(self, source, dest):
55
65
        """Copy the text and mode of a file
83
93
            last_new_name = name
84
94
        new_name = last_new_name+suffix
85
95
        try:
86
 
            os.rename(name, new_name)
 
96
            rename(name, new_name)
87
97
            return new_name
88
98
        except OSError, e:
89
99
            if e.errno != errno.EEXIST and e.errno != errno.ENOTEMPTY:
107
117
        self.add_suffix(this_path, ".THIS")
108
118
        self.dump(base_lines, this_path+".BASE")
109
119
        self.dump(other_lines, this_path+".OTHER")
110
 
        os.rename(new_file, this_path)
 
120
        rename(new_file, this_path)
111
121
        self.conflict("Diff3 conflict encountered in %s" % this_path)
112
122
 
113
123
    def new_contents_conflict(self, filename, other_contents):
127
137
            % filename)
128
138
        return "skip"
129
139
 
 
140
    def rem_contents_conflict(self, filename, this_contents, base_contents):
 
141
        base_contents(filename+".BASE", self, False)
 
142
        this_contents(filename+".THIS", self, False)
 
143
        self.conflict("Other branch deleted locally modified file %s" %
 
144
                      filename)
 
145
        return ReplaceContents(this_contents, None)
 
146
 
 
147
    def abs_this_path(self, file_id):
 
148
        """Return the absolute path for a file_id in the this tree."""
 
149
        relpath = self.this_tree.id2path(file_id)
 
150
        return self.this_tree.tree.abspath(relpath)
 
151
 
 
152
    def add_missing_parents(self, file_id, tree):
 
153
        """If some of the parents for file_id are missing, add them."""
 
154
        entry = tree.tree.inventory[file_id]
 
155
        if entry.parent_id not in self.this_tree:
 
156
            return self.create_all_missing(entry.parent_id, tree)
 
157
        else:
 
158
            return self.abs_this_path(entry.parent_id)
 
159
 
 
160
    def create_all_missing(self, file_id, tree):
 
161
        """Add contents for a file_id and all its parents to a tree."""
 
162
        entry = tree.tree.inventory[file_id]
 
163
        if entry.parent_id is not None and entry.parent_id not in self.this_tree:
 
164
            abspath = self.create_all_missing(entry.parent_id, tree)
 
165
        else:
 
166
            abspath = self.abs_this_path(entry.parent_id)
 
167
        entry_path = os.path.join(abspath, entry.name)
 
168
        if not os.path.isdir(entry_path):
 
169
            self.create(file_id, entry_path, tree)
 
170
        return entry_path
 
171
 
 
172
    def create(self, file_id, path, tree, reverse=False):
 
173
        """Uses tree data to create a filesystem object for the file_id"""
 
174
        from merge_core import get_id_contents
 
175
        get_id_contents(file_id, tree)(path, self, reverse)
 
176
 
 
177
    def missing_for_merge(self, file_id, other_path):
 
178
        """The file_id doesn't exist in THIS, but does in OTHER and BASE"""
 
179
        self.conflict("Other branch modified locally deleted file %s" %
 
180
                      other_path)
 
181
        parent_dir = self.add_missing_parents(file_id, self.other_tree)
 
182
        stem = os.path.join(parent_dir, os.path.basename(other_path))
 
183
        self.create(file_id, stem+".OTHER", self.other_tree)
 
184
        self.create(file_id, stem+".BASE", self.base_tree)
 
185
 
130
186
    def finalize(self):
131
187
        if not self.ignore_zero:
132
188
            print "%d conflicts encountered.\n" % self.conflicts
133
189
            
134
190
def get_tree(treespec, temp_root, label, local_branch=None):
135
191
    location, revno = treespec
136
 
    branch = find_branch(location)
 
192
    branch = Branch.open_containing(location)
137
193
    if revno is None:
138
194
        revision = None
139
195
    elif revno == -1:
140
 
        revision = branch.last_patch()
 
196
        revision = branch.last_revision()
141
197
    else:
142
 
        revision = branch.lookup_revision(revno)
 
198
        revision = branch.get_rev_id(revno)
143
199
    return branch, get_revid_tree(branch, revision, temp_root, label,
144
200
                                  local_branch)
145
201
 
171
227
        self.tree = tree
172
228
        self.tempdir = tempdir
173
229
        os.mkdir(os.path.join(self.tempdir, "texts"))
 
230
        os.mkdir(os.path.join(self.tempdir, "symlinks"))
174
231
        self.cached = {}
175
232
 
176
233
    def __iter__(self):
185
242
    def get_file_sha1(self, id):
186
243
        return self.tree.get_file_sha1(id)
187
244
 
 
245
    def is_executable(self, id):
 
246
        return self.tree.is_executable(id)
 
247
 
188
248
    def id2path(self, file_id):
189
249
        return self.tree.id2path(file_id)
190
250
 
207
267
        if self.root is not None:
208
268
            return self.tree.abspath(self.tree.id2path(id))
209
269
        else:
210
 
            if self.tree.inventory[id].kind in ("directory", "root_directory"):
 
270
            kind = self.tree.inventory[id].kind
 
271
            if kind in ("directory", "root_directory"):
211
272
                return self.tempdir
212
273
            if not self.cached.has_key(id):
213
 
                path = os.path.join(self.tempdir, "texts", id)
214
 
                outfile = file(path, "wb")
215
 
                outfile.write(self.tree.get_file(id).read())
216
 
                assert(os.path.exists(path))
 
274
                if kind == "file":
 
275
                    path = os.path.join(self.tempdir, "texts", id)
 
276
                    outfile = file(path, "wb")
 
277
                    outfile.write(self.tree.get_file(id).read())
 
278
                    assert(bzrlib.osutils.lexists(path))
 
279
                    if self.tree.is_executable(id):
 
280
                        os.chmod(path, 0755)
 
281
                else:
 
282
                    assert kind == "symlink"
 
283
                    path = os.path.join(self.tempdir, "symlinks", id)
 
284
                    target = self.tree.get_symlink_target(id)
 
285
                    os.symlink(target, path)
217
286
                self.cached[id] = path
218
287
            return self.cached[id]
219
288
 
220
289
 
 
290
def build_working_dir(to_dir):
 
291
    """Build a working directory in an empty directory.
 
292
 
 
293
    to_dir is a directory containing branch metadata but no working files,
 
294
    typically constructed by cloning an existing branch. 
 
295
 
 
296
    This is split out as a special idiomatic case of merge.  It could
 
297
    eventually be done by just building the tree directly calling into 
 
298
    lower-level code (e.g. constructing a changeset).
 
299
    """
 
300
    merge((to_dir, -1), (to_dir, 0), this_dir=to_dir,
 
301
          check_clean=False, ignore_zero=True)
 
302
 
221
303
 
222
304
def merge(other_revision, base_revision,
223
305
          check_clean=True, ignore_zero=False,
234
316
    check_clean
235
317
        If true, this_dir must have no uncommitted changes before the
236
318
        merge begins.
237
 
    all available ancestors of other_revision and base_revision are
 
319
    ignore_zero - If true, suppress the "zero conflicts" message when 
 
320
        there are no conflicts; should be set when doing something we expect
 
321
        to complete perfectly.
 
322
 
 
323
    All available ancestors of other_revision and base_revision are
238
324
    automatically pulled into the branch.
239
325
    """
240
 
    from bzrlib.revision import common_ancestor, MultipleRevisionSources
241
 
    from bzrlib.errors import NoSuchRevision
242
326
    tempdir = tempfile.mkdtemp(prefix="bzr-")
243
327
    try:
244
328
        if this_dir is None:
245
329
            this_dir = '.'
246
 
        this_branch = find_branch(this_dir)
247
 
        this_rev_id = this_branch.last_patch()
 
330
        this_branch = Branch.open_containing(this_dir)
 
331
        this_rev_id = this_branch.last_revision()
248
332
        if this_rev_id is None:
249
333
            raise BzrCommandError("This branch has no commits")
250
334
        if check_clean:
255
339
        other_branch, other_tree = get_tree(other_revision, tempdir, "other",
256
340
                                            this_branch)
257
341
        if other_revision[1] == -1:
258
 
            other_rev_id = other_branch.last_patch()
 
342
            other_rev_id = other_branch.last_revision()
 
343
            if other_rev_id is None:
 
344
                raise NoCommits(other_branch)
259
345
            other_basis = other_rev_id
260
346
        elif other_revision[1] is not None:
261
 
            other_rev_id = other_branch.lookup_revision(other_revision[1])
 
347
            other_rev_id = other_branch.get_rev_id(other_revision[1])
262
348
            other_basis = other_rev_id
263
349
        else:
264
350
            other_rev_id = None
265
 
            other_basis = other_branch.last_patch()
 
351
            other_basis = other_branch.last_revision()
 
352
            if other_basis is None:
 
353
                raise NoCommits(other_branch)
266
354
        if base_revision == [None, None]:
267
 
            base_rev_id = common_ancestor(this_rev_id, other_basis, 
268
 
                                          this_branch)
269
 
            if base_rev_id is None:
 
355
            try:
 
356
                base_rev_id = common_ancestor(this_rev_id, other_basis, 
 
357
                                              this_branch)
 
358
            except NoCommonAncestor:
270
359
                raise UnrelatedBranches()
271
360
            base_tree = get_revid_tree(this_branch, base_rev_id, tempdir, 
272
361
                                       "base", None)
274
363
        else:
275
364
            base_branch, base_tree = get_tree(base_revision, tempdir, "base")
276
365
            if base_revision[1] == -1:
277
 
                base_rev_id = base_branch.last_patch()
 
366
                base_rev_id = base_branch.last_revision()
278
367
            elif base_revision[1] is None:
279
368
                base_rev_id = None
280
369
            else:
281
 
                base_rev_id = base_branch.lookup_revision(base_revision[1])
282
 
            if base_rev_id is not None:
283
 
                base_is_ancestor = is_ancestor(this_rev_id, base_rev_id, 
284
 
                                               MultipleRevisionSources(this_branch, 
285
 
                                                                       base_branch))
286
 
            else:
287
 
                base_is_ancestor = False
 
370
                base_rev_id = base_branch.get_rev_id(base_revision[1])
 
371
            fetch(from_branch=base_branch, to_branch=this_branch)
 
372
            base_is_ancestor = is_ancestor(this_rev_id, base_rev_id,
 
373
                                           this_branch)
288
374
        if file_list is None:
289
375
            interesting_ids = None
290
376
        else:
346
432
 
347
433
    inv_changes = merge_flex(this_tree, base_tree, other_tree,
348
434
                             generate_cset_optimized, get_inventory,
349
 
                             MergeConflictHandler(base_tree.root,
350
 
                                                  ignore_zero=ignore_zero),
 
435
                             MergeConflictHandler(this_tree, base_tree,
 
436
                             other_tree, ignore_zero=ignore_zero),
351
437
                             merge_factory=merge_factory, 
352
438
                             interesting_ids=interesting_ids)
353
439