~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Robert Collins
  • Date: 2005-10-16 23:53:02 UTC
  • mto: This revision was merged to the branch mainline in revision 1459.
  • Revision ID: robertc@lifelesslap.robertcollins.net-20051016235302-818de607403e1c6e
test that the presence of a signature does not make a missing base file magically appear present

Show diffs side-by-side

added added

removed removed

Lines of Context:
124
124
 
125
125
    def new_contents_conflict(self, filename, other_contents):
126
126
        """Conflicting contents for newly added file."""
127
 
        other.contents.apply(filename + ".OTHER")
 
127
        self.copy(other_contents, filename + ".OTHER")
128
128
        self.conflict("Conflict in newly added file %s" % filename)
129
129
    
130
130
 
153
153
 
154
154
    def abs_this_path(self, file_id):
155
155
        """Return the absolute path for a file_id in the this tree."""
156
 
        return self.this_tree.id2abspath(file_id)
 
156
        relpath = self.this_tree.id2path(file_id)
 
157
        return self.this_tree.tree.abspath(relpath)
157
158
 
158
159
    def add_missing_parents(self, file_id, tree):
159
160
        """If some of the parents for file_id are missing, add them."""
160
 
        entry = tree.inventory[file_id]
 
161
        entry = tree.tree.inventory[file_id]
161
162
        if entry.parent_id not in self.this_tree:
162
163
            return self.create_all_missing(entry.parent_id, tree)
163
164
        else:
165
166
 
166
167
    def create_all_missing(self, file_id, tree):
167
168
        """Add contents for a file_id and all its parents to a tree."""
168
 
        entry = tree.inventory[file_id]
 
169
        entry = tree.tree.inventory[file_id]
169
170
        if entry.parent_id is not None and entry.parent_id not in self.this_tree:
170
171
            abspath = self.create_all_missing(entry.parent_id, tree)
171
172
        else:
177
178
 
178
179
    def create(self, file_id, path, tree, reverse=False):
179
180
        """Uses tree data to create a filesystem object for the file_id"""
180
 
        from changeset import get_contents
181
 
        get_contents(tree, file_id)(path, self, reverse)
 
181
        from merge_core import get_id_contents
 
182
        get_id_contents(file_id, tree)(path, self, reverse)
182
183
 
183
184
    def missing_for_merge(self, file_id, other_path):
184
185
        """The file_id doesn't exist in THIS, but does in OTHER and BASE"""
189
190
        self.create(file_id, stem+".OTHER", self.other_tree)
190
191
        self.create(file_id, stem+".BASE", self.base_tree)
191
192
 
192
 
    def threeway_contents_conflict(filename, this_contents, base_contents,
193
 
                                   other_contents):
194
 
        self.conflict("Three-way conflict merging %s" % filename)
195
 
 
196
193
    def finalize(self):
197
194
        if not self.ignore_zero:
198
195
            note("%d conflicts encountered.\n" % self.conflicts)
199
196
            
200
 
def get_tree(treespec, local_branch=None):
 
197
def get_tree(treespec, temp_root, label, local_branch=None):
201
198
    location, revno = treespec
202
 
    branch = Branch.open_containing(location)[0]
 
199
    branch = Branch.open_containing(location)
203
200
    if revno is None:
204
201
        revision = None
205
202
    elif revno == -1:
206
203
        revision = branch.last_revision()
207
204
    else:
208
205
        revision = branch.get_rev_id(revno)
209
 
    return branch, get_revid_tree(branch, revision, local_branch)
 
206
    return branch, get_revid_tree(branch, revision, temp_root, label,
 
207
                                  local_branch)
210
208
 
211
 
def get_revid_tree(branch, revision, local_branch):
 
209
def get_revid_tree(branch, revision, temp_root, label, local_branch):
212
210
    if revision is None:
213
211
        base_tree = branch.working_tree()
214
212
    else:
217
215
            base_tree = local_branch.revision_tree(revision)
218
216
        else:
219
217
            base_tree = branch.revision_tree(revision)
220
 
    return base_tree
 
218
    temp_path = os.path.join(temp_root, label)
 
219
    os.mkdir(temp_path)
 
220
    return MergeAdapterTree(base_tree, temp_path)
221
221
 
222
222
 
223
223
def file_exists(tree, file_id):
224
224
    return tree.has_filename(tree.id2path(file_id))
225
225
    
226
226
 
 
227
class MergeAdapterTree(object):
 
228
    """MergeAdapterTree adapts a normal tree for merge_inner to use.
 
229
 
 
230
    The interface the merge_inner needs is nearly but not quite
 
231
    the same as that of bzrlib.tree with the exception of readonly_path.
 
232
    """
 
233
    
 
234
    def __init__(self, tree, tempdir):
 
235
        object.__init__(self)
 
236
        if hasattr(tree, "basedir"):
 
237
            self.root = tree.basedir
 
238
        else:
 
239
            self.root = None
 
240
        self.tree = tree
 
241
        self.tempdir = tempdir
 
242
        os.mkdir(os.path.join(self.tempdir, "texts"))
 
243
        os.mkdir(os.path.join(self.tempdir, "symlinks"))
 
244
        self.cached = {}
 
245
 
 
246
    def __iter__(self):
 
247
        return self.tree.__iter__()
 
248
 
 
249
    def __contains__(self, file_id):
 
250
        return file_id in self.tree
 
251
 
 
252
    def get_file(self, file_id):
 
253
        return self.tree.get_file(file_id)
 
254
 
 
255
    def get_file_sha1(self, id):
 
256
        return self.tree.get_file_sha1(id)
 
257
 
 
258
    def is_executable(self, id):
 
259
        return self.tree.is_executable(id)
 
260
 
 
261
    def id2path(self, file_id):
 
262
        return self.tree.id2path(file_id)
 
263
 
 
264
    def has_id(self, file_id):
 
265
        return self.tree.has_id(file_id)
 
266
 
 
267
    def has_or_had_id(self, file_id):
 
268
        if file_id == self.tree.inventory.root.file_id:
 
269
            return True
 
270
        return self.tree.inventory.has_id(file_id)
 
271
 
 
272
    def has_or_had_id(self, file_id):
 
273
        if file_id == self.tree.inventory.root.file_id:
 
274
            return True
 
275
        return self.tree.inventory.has_id(file_id)
 
276
 
 
277
    def readonly_path(self, id):
 
278
        if id not in self.tree:
 
279
            return None
 
280
        if self.root is not None:
 
281
            return self.tree.abspath(self.tree.id2path(id))
 
282
        else:
 
283
            kind = self.tree.inventory[id].kind
 
284
            if kind in ("directory", "root_directory"):
 
285
                return self.tempdir
 
286
            if not self.cached.has_key(id):
 
287
                if kind == "file":
 
288
                    path = os.path.join(self.tempdir, "texts", id)
 
289
                    outfile = file(path, "wb")
 
290
                    outfile.write(self.tree.get_file(id).read())
 
291
                    assert(bzrlib.osutils.lexists(path))
 
292
                    if self.tree.is_executable(id):
 
293
                        os.chmod(path, 0755)
 
294
                else:
 
295
                    assert kind == "symlink"
 
296
                    path = os.path.join(self.tempdir, "symlinks", id)
 
297
                    target = self.tree.get_symlink_target(id)
 
298
                    os.symlink(target, path)
 
299
                self.cached[id] = path
 
300
            return self.cached[id]
 
301
 
 
302
 
227
303
def build_working_dir(to_dir):
228
304
    """Build a working directory in an empty directory.
229
305
 
234
310
    eventually be done by just building the tree directly calling into 
235
311
    lower-level code (e.g. constructing a changeset).
236
312
    """
237
 
    # RBC 20051019 is this not just 'export' ?
238
313
    merge((to_dir, -1), (to_dir, 0), this_dir=to_dir,
239
314
          check_clean=False, ignore_zero=True)
240
315
 
261
336
    All available ancestors of other_revision and base_revision are
262
337
    automatically pulled into the branch.
263
338
    """
264
 
    if this_dir is None:
265
 
        this_dir = '.'
266
 
    this_branch = Branch.open_containing(this_dir)[0]
267
 
    this_rev_id = this_branch.last_revision()
268
 
    if this_rev_id is None:
269
 
        raise BzrCommandError("This branch has no commits")
270
 
    if check_clean:
271
 
        changes = compare_trees(this_branch.working_tree(), 
272
 
                                this_branch.basis_tree(), False)
273
 
        if changes.has_changed():
274
 
            raise BzrCommandError("Working tree has uncommitted changes.")
275
 
    other_branch, other_tree = get_tree(other_revision, this_branch)
276
 
    if other_revision[1] == -1:
277
 
        other_rev_id = other_branch.last_revision()
278
 
        if other_rev_id is None:
279
 
            raise NoCommits(other_branch)
280
 
        other_basis = other_rev_id
281
 
    elif other_revision[1] is not None:
282
 
        other_rev_id = other_branch.get_rev_id(other_revision[1])
283
 
        other_basis = other_rev_id
284
 
    else:
285
 
        other_rev_id = None
286
 
        other_basis = other_branch.last_revision()
287
 
        if other_basis is None:
288
 
            raise NoCommits(other_branch)
289
 
    if base_revision == [None, None]:
290
 
        try:
291
 
            base_rev_id = common_ancestor(this_rev_id, other_basis, 
292
 
                                          this_branch)
293
 
        except NoCommonAncestor:
294
 
            raise UnrelatedBranches()
295
 
        base_tree = get_revid_tree(this_branch, base_rev_id, None)
296
 
        base_is_ancestor = True
297
 
    else:
298
 
        base_branch, base_tree = get_tree(base_revision)
299
 
        if base_revision[1] == -1:
300
 
            base_rev_id = base_branch.last_revision()
301
 
        elif base_revision[1] is None:
302
 
            base_rev_id = None
303
 
        else:
304
 
            base_rev_id = base_branch.get_rev_id(base_revision[1])
305
 
        fetch(from_branch=base_branch, to_branch=this_branch)
306
 
        base_is_ancestor = is_ancestor(this_rev_id, base_rev_id,
307
 
                                       this_branch)
308
 
    if file_list is None:
309
 
        interesting_ids = None
310
 
    else:
311
 
        interesting_ids = set()
312
 
        this_tree = this_branch.working_tree()
313
 
        for fname in file_list:
314
 
            path = this_tree.relpath(fname)
315
 
            found_id = False
316
 
            for tree in (this_tree, base_tree, other_tree):
317
 
                file_id = tree.inventory.path2id(path)
318
 
                if file_id is not None:
319
 
                    interesting_ids.add(file_id)
320
 
                    found_id = True
321
 
            if not found_id:
322
 
                raise BzrCommandError("%s is not a source file in any"
323
 
                                      " tree." % fname)
324
 
    merge_inner(this_branch, other_tree, base_tree, tempdir=None, 
325
 
                ignore_zero=ignore_zero, backup_files=backup_files, 
326
 
                merge_type=merge_type, interesting_ids=interesting_ids)
327
 
    if base_is_ancestor and other_rev_id is not None\
328
 
        and other_rev_id not in this_branch.revision_history():
329
 
        this_branch.add_pending_merge(other_rev_id)
 
339
    tempdir = tempfile.mkdtemp(prefix="bzr-")
 
340
    try:
 
341
        if this_dir is None:
 
342
            this_dir = '.'
 
343
        this_branch = Branch.open_containing(this_dir)
 
344
        this_rev_id = this_branch.last_revision()
 
345
        if this_rev_id is None:
 
346
            raise BzrCommandError("This branch has no commits")
 
347
        if check_clean:
 
348
            changes = compare_trees(this_branch.working_tree(), 
 
349
                                    this_branch.basis_tree(), False)
 
350
            if changes.has_changed():
 
351
                raise BzrCommandError("Working tree has uncommitted changes.")
 
352
        other_branch, other_tree = get_tree(other_revision, tempdir, "other",
 
353
                                            this_branch)
 
354
        if other_revision[1] == -1:
 
355
            other_rev_id = other_branch.last_revision()
 
356
            if other_rev_id is None:
 
357
                raise NoCommits(other_branch)
 
358
            other_basis = other_rev_id
 
359
        elif other_revision[1] is not None:
 
360
            other_rev_id = other_branch.get_rev_id(other_revision[1])
 
361
            other_basis = other_rev_id
 
362
        else:
 
363
            other_rev_id = None
 
364
            other_basis = other_branch.last_revision()
 
365
            if other_basis is None:
 
366
                raise NoCommits(other_branch)
 
367
        if base_revision == [None, None]:
 
368
            try:
 
369
                base_rev_id = common_ancestor(this_rev_id, other_basis, 
 
370
                                              this_branch)
 
371
            except NoCommonAncestor:
 
372
                raise UnrelatedBranches()
 
373
            base_tree = get_revid_tree(this_branch, base_rev_id, tempdir, 
 
374
                                       "base", None)
 
375
            base_is_ancestor = True
 
376
        else:
 
377
            base_branch, base_tree = get_tree(base_revision, tempdir, "base")
 
378
            if base_revision[1] == -1:
 
379
                base_rev_id = base_branch.last_revision()
 
380
            elif base_revision[1] is None:
 
381
                base_rev_id = None
 
382
            else:
 
383
                base_rev_id = base_branch.get_rev_id(base_revision[1])
 
384
            fetch(from_branch=base_branch, to_branch=this_branch)
 
385
            base_is_ancestor = is_ancestor(this_rev_id, base_rev_id,
 
386
                                           this_branch)
 
387
        if file_list is None:
 
388
            interesting_ids = None
 
389
        else:
 
390
            interesting_ids = set()
 
391
            this_tree = this_branch.working_tree()
 
392
            for fname in file_list:
 
393
                path = this_branch.relpath(fname)
 
394
                found_id = False
 
395
                for tree in (this_tree, base_tree.tree, other_tree.tree):
 
396
                    file_id = tree.inventory.path2id(path)
 
397
                    if file_id is not None:
 
398
                        interesting_ids.add(file_id)
 
399
                        found_id = True
 
400
                if not found_id:
 
401
                    raise BzrCommandError("%s is not a source file in any"
 
402
                                          " tree." % fname)
 
403
        merge_inner(this_branch, other_tree, base_tree, tempdir, 
 
404
                    ignore_zero=ignore_zero, backup_files=backup_files, 
 
405
                    merge_type=merge_type, interesting_ids=interesting_ids)
 
406
        if base_is_ancestor and other_rev_id is not None\
 
407
            and other_rev_id not in this_branch.revision_history():
 
408
            this_branch.add_pending_merge(other_rev_id)
 
409
    finally:
 
410
        shutil.rmtree(tempdir)
330
411
 
331
412
 
332
413
def set_interesting(inventory_a, inventory_b, interesting_ids):
337
418
             source_file.interesting = source_file.id in interesting_ids
338
419
 
339
420
 
340
 
def merge_inner(this_branch, other_tree, base_tree, tempdir=None, 
341
 
                ignore_zero=False, merge_type=ApplyMerge3, backup_files=False,
342
 
                interesting_ids=None):
343
 
    """Primary interface for merging. 
344
 
 
345
 
    typical use is probably 
346
 
    'merge_inner(branch, branch.get_revision_tree(other_revision),
347
 
                 branch.get_revision_tree(base_revision))'
348
 
    """
349
 
    if tempdir is None:
350
 
        _tempdir = tempfile.mkdtemp(prefix="bzr-")
351
 
    else:
352
 
        _tempdir = tempdir
353
 
    try:
354
 
        _merge_inner(this_branch, other_tree, base_tree, _tempdir,
355
 
                     ignore_zero, merge_type, backup_files, interesting_ids)
356
 
    finally:
357
 
        if tempdir is None:
358
 
            shutil.rmtree(_tempdir)
359
 
 
360
 
 
361
 
def _merge_inner(this_branch, other_tree, base_tree, user_tempdir, 
362
 
                ignore_zero=False, merge_type=ApplyMerge3, backup_files=False,
363
 
                interesting_ids=None):
 
421
def merge_inner(this_branch, other_tree, base_tree, tempdir, 
 
422
                ignore_zero=False, merge_type=ApplyMerge3, backup_files=False,
 
423
                interesting_ids=None):
 
424
 
364
425
    def merge_factory(file_id, base, other):
365
426
        contents_change = merge_type(file_id, base, other)
366
427
        if backup_files:
367
428
            contents_change = BackupBeforeChange(contents_change)
368
429
        return contents_change
369
430
 
370
 
    this_tree = get_tree((this_branch.base, None))[1]
 
431
    this_tree = get_tree((this_branch.base, None), tempdir, "this")[1]
371
432
 
372
433
    def get_inventory(tree):
373
 
        return tree.inventory
 
434
        return tree.tree.inventory
374
435
 
375
436
    inv_changes = merge_flex(this_tree, base_tree, other_tree,
376
437
                             generate_changeset, get_inventory,
389
450
            path = path[2:]
390
451
        adjust_ids.append((path, id))
391
452
    if len(adjust_ids) > 0:
392
 
        this_branch.set_inventory(regen_inventory(this_branch, 
393
 
                                                  this_tree.basedir,
 
453
        this_branch.set_inventory(regen_inventory(this_branch, this_tree.root,
394
454
                                                  adjust_ids))
395
455
 
396
456