~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Robert Collins
  • Date: 2005-10-08 00:39:04 UTC
  • mfrom: (1185.1.52)
  • Revision ID: robertc@robertcollins.net-20051008003904-aaffaea2778efe3e
merge in martins reweave, integrated to fetch, and a bugfix for commit and upgrade with executable files

Show diffs side-by-side

added added

removed removed

Lines of Context:
29
29
from bzrlib.errors import BzrCommandError, UnrelatedBranches, NoCommonAncestor
30
30
from bzrlib.errors import NoCommits
31
31
from bzrlib.delta import compare_trees
32
 
from bzrlib.trace import mutter, warning, note
 
32
from bzrlib.trace import mutter, warning
33
33
from bzrlib.fetch import greedy_fetch, fetch
34
34
from bzrlib.revision import is_ancestor
35
35
from bzrlib.osutils import rename
39
39
# TODO: build_working_dir can be built on something simpler than merge()
40
40
 
41
41
# FIXME: merge() parameters seem oriented towards the command line
42
 
# NOTABUG: merge is a helper for commandline functions.  merge_inner is the
43
 
#          the core functionality.
44
42
 
45
43
# comments from abentley on irc: merge happens in two stages, each
46
44
# of which generates a changeset object
124
122
 
125
123
    def new_contents_conflict(self, filename, other_contents):
126
124
        """Conflicting contents for newly added file."""
127
 
        other.contents.apply(filename + ".OTHER")
 
125
        self.copy(other_contents, filename + ".OTHER")
128
126
        self.conflict("Conflict in newly added file %s" % filename)
129
127
    
130
128
 
153
151
 
154
152
    def abs_this_path(self, file_id):
155
153
        """Return the absolute path for a file_id in the this tree."""
156
 
        return self.this_tree.id2abspath(file_id)
 
154
        relpath = self.this_tree.id2path(file_id)
 
155
        return self.this_tree.tree.abspath(relpath)
157
156
 
158
157
    def add_missing_parents(self, file_id, tree):
159
158
        """If some of the parents for file_id are missing, add them."""
160
 
        entry = tree.inventory[file_id]
 
159
        entry = tree.tree.inventory[file_id]
161
160
        if entry.parent_id not in self.this_tree:
162
161
            return self.create_all_missing(entry.parent_id, tree)
163
162
        else:
165
164
 
166
165
    def create_all_missing(self, file_id, tree):
167
166
        """Add contents for a file_id and all its parents to a tree."""
168
 
        entry = tree.inventory[file_id]
 
167
        entry = tree.tree.inventory[file_id]
169
168
        if entry.parent_id is not None and entry.parent_id not in self.this_tree:
170
169
            abspath = self.create_all_missing(entry.parent_id, tree)
171
170
        else:
177
176
 
178
177
    def create(self, file_id, path, tree, reverse=False):
179
178
        """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)
 
179
        from merge_core import get_id_contents
 
180
        get_id_contents(file_id, tree)(path, self, reverse)
182
181
 
183
182
    def missing_for_merge(self, file_id, other_path):
184
183
        """The file_id doesn't exist in THIS, but does in OTHER and BASE"""
189
188
        self.create(file_id, stem+".OTHER", self.other_tree)
190
189
        self.create(file_id, stem+".BASE", self.base_tree)
191
190
 
192
 
    def threeway_contents_conflict(filename, this_contents, base_contents,
193
 
                                   other_contents):
194
 
        self.conflict("Three-way conflict merging %s" % filename)
195
 
 
196
191
    def finalize(self):
197
192
        if not self.ignore_zero:
198
 
            note("%d conflicts encountered.\n" % self.conflicts)
 
193
            print "%d conflicts encountered.\n" % self.conflicts
199
194
            
200
 
def get_tree(treespec, local_branch=None):
 
195
def get_tree(treespec, temp_root, label, local_branch=None):
201
196
    location, revno = treespec
202
 
    branch = Branch.open_containing(location)[0]
 
197
    branch = Branch.open_containing(location)
203
198
    if revno is None:
204
199
        revision = None
205
200
    elif revno == -1:
206
201
        revision = branch.last_revision()
207
202
    else:
208
203
        revision = branch.get_rev_id(revno)
209
 
    return branch, get_revid_tree(branch, revision, local_branch)
 
204
    return branch, get_revid_tree(branch, revision, temp_root, label,
 
205
                                  local_branch)
210
206
 
211
 
def get_revid_tree(branch, revision, local_branch):
 
207
def get_revid_tree(branch, revision, temp_root, label, local_branch):
212
208
    if revision is None:
213
209
        base_tree = branch.working_tree()
214
210
    else:
217
213
            base_tree = local_branch.revision_tree(revision)
218
214
        else:
219
215
            base_tree = branch.revision_tree(revision)
220
 
    return base_tree
 
216
    temp_path = os.path.join(temp_root, label)
 
217
    os.mkdir(temp_path)
 
218
    return MergeTree(base_tree, temp_path)
221
219
 
222
220
 
223
221
def file_exists(tree, file_id):
224
222
    return tree.has_filename(tree.id2path(file_id))
225
223
    
226
224
 
 
225
class MergeTree(object):
 
226
    def __init__(self, tree, tempdir):
 
227
        object.__init__(self)
 
228
        if hasattr(tree, "basedir"):
 
229
            self.root = tree.basedir
 
230
        else:
 
231
            self.root = None
 
232
        self.tree = tree
 
233
        self.tempdir = tempdir
 
234
        os.mkdir(os.path.join(self.tempdir, "texts"))
 
235
        os.mkdir(os.path.join(self.tempdir, "symlinks"))
 
236
        self.cached = {}
 
237
 
 
238
    def __iter__(self):
 
239
        return self.tree.__iter__()
 
240
 
 
241
    def __contains__(self, file_id):
 
242
        return file_id in self.tree
 
243
 
 
244
    def get_file(self, file_id):
 
245
        return self.tree.get_file(file_id)
 
246
 
 
247
    def get_file_sha1(self, id):
 
248
        return self.tree.get_file_sha1(id)
 
249
 
 
250
    def is_executable(self, id):
 
251
        return self.tree.is_executable(id)
 
252
 
 
253
    def id2path(self, file_id):
 
254
        return self.tree.id2path(file_id)
 
255
 
 
256
    def has_id(self, file_id):
 
257
        return self.tree.has_id(file_id)
 
258
 
 
259
    def has_or_had_id(self, file_id):
 
260
        if file_id == self.tree.inventory.root.file_id:
 
261
            return True
 
262
        return self.tree.inventory.has_id(file_id)
 
263
 
 
264
    def has_or_had_id(self, file_id):
 
265
        if file_id == self.tree.inventory.root.file_id:
 
266
            return True
 
267
        return self.tree.inventory.has_id(file_id)
 
268
 
 
269
    def readonly_path(self, id):
 
270
        if id not in self.tree:
 
271
            return None
 
272
        if self.root is not None:
 
273
            return self.tree.abspath(self.tree.id2path(id))
 
274
        else:
 
275
            kind = self.tree.inventory[id].kind
 
276
            if kind in ("directory", "root_directory"):
 
277
                return self.tempdir
 
278
            if not self.cached.has_key(id):
 
279
                if kind == "file":
 
280
                    path = os.path.join(self.tempdir, "texts", id)
 
281
                    outfile = file(path, "wb")
 
282
                    outfile.write(self.tree.get_file(id).read())
 
283
                    assert(bzrlib.osutils.lexists(path))
 
284
                    if self.tree.is_executable(id):
 
285
                        os.chmod(path, 0755)
 
286
                else:
 
287
                    assert kind == "symlink"
 
288
                    path = os.path.join(self.tempdir, "symlinks", id)
 
289
                    target = self.tree.get_symlink_target(id)
 
290
                    os.symlink(target, path)
 
291
                self.cached[id] = path
 
292
            return self.cached[id]
 
293
 
 
294
 
227
295
def build_working_dir(to_dir):
228
296
    """Build a working directory in an empty directory.
229
297
 
234
302
    eventually be done by just building the tree directly calling into 
235
303
    lower-level code (e.g. constructing a changeset).
236
304
    """
237
 
    # RBC 20051019 is this not just 'export' ?
238
305
    merge((to_dir, -1), (to_dir, 0), this_dir=to_dir,
239
306
          check_clean=False, ignore_zero=True)
240
307
 
261
328
    All available ancestors of other_revision and base_revision are
262
329
    automatically pulled into the branch.
263
330
    """
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)
 
331
    tempdir = tempfile.mkdtemp(prefix="bzr-")
 
332
    try:
 
333
        if this_dir is None:
 
334
            this_dir = '.'
 
335
        this_branch = Branch.open_containing(this_dir)
 
336
        this_rev_id = this_branch.last_revision()
 
337
        if this_rev_id is None:
 
338
            raise BzrCommandError("This branch has no commits")
 
339
        if check_clean:
 
340
            changes = compare_trees(this_branch.working_tree(), 
 
341
                                    this_branch.basis_tree(), False)
 
342
            if changes.has_changed():
 
343
                raise BzrCommandError("Working tree has uncommitted changes.")
 
344
        other_branch, other_tree = get_tree(other_revision, tempdir, "other",
 
345
                                            this_branch)
 
346
        if other_revision[1] == -1:
 
347
            other_rev_id = other_branch.last_revision()
 
348
            if other_rev_id is None:
 
349
                raise NoCommits(other_branch)
 
350
            other_basis = other_rev_id
 
351
        elif other_revision[1] is not None:
 
352
            other_rev_id = other_branch.get_rev_id(other_revision[1])
 
353
            other_basis = other_rev_id
 
354
        else:
 
355
            other_rev_id = None
 
356
            other_basis = other_branch.last_revision()
 
357
            if other_basis is None:
 
358
                raise NoCommits(other_branch)
 
359
        if base_revision == [None, None]:
 
360
            try:
 
361
                base_rev_id = common_ancestor(this_rev_id, other_basis, 
 
362
                                              this_branch)
 
363
            except NoCommonAncestor:
 
364
                raise UnrelatedBranches()
 
365
            base_tree = get_revid_tree(this_branch, base_rev_id, tempdir, 
 
366
                                       "base", None)
 
367
            base_is_ancestor = True
 
368
        else:
 
369
            base_branch, base_tree = get_tree(base_revision, tempdir, "base")
 
370
            if base_revision[1] == -1:
 
371
                base_rev_id = base_branch.last_revision()
 
372
            elif base_revision[1] is None:
 
373
                base_rev_id = None
 
374
            else:
 
375
                base_rev_id = base_branch.get_rev_id(base_revision[1])
 
376
            fetch(from_branch=base_branch, to_branch=this_branch)
 
377
            base_is_ancestor = is_ancestor(this_rev_id, base_rev_id,
 
378
                                           this_branch)
 
379
        if file_list is None:
 
380
            interesting_ids = None
 
381
        else:
 
382
            interesting_ids = set()
 
383
            this_tree = this_branch.working_tree()
 
384
            for fname in file_list:
 
385
                path = this_branch.relpath(fname)
 
386
                found_id = False
 
387
                for tree in (this_tree, base_tree.tree, other_tree.tree):
 
388
                    file_id = tree.inventory.path2id(path)
 
389
                    if file_id is not None:
 
390
                        interesting_ids.add(file_id)
 
391
                        found_id = True
 
392
                if not found_id:
 
393
                    raise BzrCommandError("%s is not a source file in any"
 
394
                                          " tree." % fname)
 
395
        merge_inner(this_branch, other_tree, base_tree, tempdir, 
 
396
                    ignore_zero=ignore_zero, backup_files=backup_files, 
 
397
                    merge_type=merge_type, interesting_ids=interesting_ids)
 
398
        if base_is_ancestor and other_rev_id is not None\
 
399
            and other_rev_id not in this_branch.revision_history():
 
400
            this_branch.add_pending_merge(other_rev_id)
 
401
    finally:
 
402
        shutil.rmtree(tempdir)
330
403
 
331
404
 
332
405
def set_interesting(inventory_a, inventory_b, interesting_ids):
337
410
             source_file.interesting = source_file.id in interesting_ids
338
411
 
339
412
 
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):
 
413
def generate_cset_optimized(tree_a, tree_b, interesting_ids=None):
 
414
    """Generate a changeset.  If interesting_ids is supplied, only changes
 
415
    to those files will be shown.  Metadata changes are stripped.
 
416
    """ 
 
417
    cset =  generate_changeset(tree_a, tree_b, interesting_ids)
 
418
    for entry in cset.entries.itervalues():
 
419
        entry.metadata_change = None
 
420
    return cset
 
421
 
 
422
 
 
423
def merge_inner(this_branch, other_tree, base_tree, tempdir, 
 
424
                ignore_zero=False, merge_type=ApplyMerge3, backup_files=False,
 
425
                interesting_ids=None):
 
426
 
364
427
    def merge_factory(file_id, base, other):
365
428
        contents_change = merge_type(file_id, base, other)
366
429
        if backup_files:
367
430
            contents_change = BackupBeforeChange(contents_change)
368
431
        return contents_change
369
432
 
370
 
    this_tree = get_tree((this_branch.base, None))[1]
 
433
    this_tree = get_tree((this_branch.base, None), tempdir, "this")[1]
371
434
 
372
435
    def get_inventory(tree):
373
 
        return tree.inventory
 
436
        return tree.tree.inventory
374
437
 
375
438
    inv_changes = merge_flex(this_tree, base_tree, other_tree,
376
 
                             generate_changeset, get_inventory,
 
439
                             generate_cset_optimized, get_inventory,
377
440
                             MergeConflictHandler(this_tree, base_tree,
378
441
                             other_tree, ignore_zero=ignore_zero),
379
442
                             merge_factory=merge_factory, 
389
452
            path = path[2:]
390
453
        adjust_ids.append((path, id))
391
454
    if len(adjust_ids) > 0:
392
 
        this_branch.set_inventory(regen_inventory(this_branch, 
393
 
                                                  this_tree.basedir,
 
455
        this_branch.set_inventory(regen_inventory(this_branch, this_tree.root,
394
456
                                                  adjust_ids))
395
457
 
396
458