~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Robert Collins
  • Date: 2005-10-19 10:11:57 UTC
  • mfrom: (1185.16.78)
  • mto: This revision was merged to the branch mainline in revision 1470.
  • Revision ID: robertc@robertcollins.net-20051019101157-17438d311e746b4f
mergeĀ fromĀ upstream

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
 
32
from bzrlib.trace import mutter, warning, note
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.
42
44
 
43
45
# comments from abentley on irc: merge happens in two stages, each
44
46
# of which generates a changeset object
122
124
 
123
125
    def new_contents_conflict(self, filename, other_contents):
124
126
        """Conflicting contents for newly added file."""
125
 
        self.copy(other_contents, filename + ".OTHER")
 
127
        other.contents.apply(filename + ".OTHER")
126
128
        self.conflict("Conflict in newly added file %s" % filename)
127
129
    
128
130
 
140
142
    def rem_contents_conflict(self, filename, this_contents, base_contents):
141
143
        base_contents(filename+".BASE", self, False)
142
144
        this_contents(filename+".THIS", self, False)
 
145
        return ReplaceContents(this_contents, None)
 
146
 
 
147
    def rem_contents_conflict(self, filename, this_contents, base_contents):
 
148
        base_contents(filename+".BASE", self, False)
 
149
        this_contents(filename+".THIS", self, False)
143
150
        self.conflict("Other branch deleted locally modified file %s" %
144
151
                      filename)
145
152
        return ReplaceContents(this_contents, None)
146
153
 
147
154
    def abs_this_path(self, file_id):
148
155
        """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)
 
156
        return self.this_tree.id2abspath(file_id)
151
157
 
152
158
    def add_missing_parents(self, file_id, tree):
153
159
        """If some of the parents for file_id are missing, add them."""
154
 
        entry = tree.tree.inventory[file_id]
 
160
        entry = tree.inventory[file_id]
155
161
        if entry.parent_id not in self.this_tree:
156
162
            return self.create_all_missing(entry.parent_id, tree)
157
163
        else:
159
165
 
160
166
    def create_all_missing(self, file_id, tree):
161
167
        """Add contents for a file_id and all its parents to a tree."""
162
 
        entry = tree.tree.inventory[file_id]
 
168
        entry = tree.inventory[file_id]
163
169
        if entry.parent_id is not None and entry.parent_id not in self.this_tree:
164
170
            abspath = self.create_all_missing(entry.parent_id, tree)
165
171
        else:
171
177
 
172
178
    def create(self, file_id, path, tree, reverse=False):
173
179
        """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)
 
180
        from changeset import get_contents
 
181
        get_contents(tree, file_id)(path, self, reverse)
176
182
 
177
183
    def missing_for_merge(self, file_id, other_path):
178
184
        """The file_id doesn't exist in THIS, but does in OTHER and BASE"""
183
189
        self.create(file_id, stem+".OTHER", self.other_tree)
184
190
        self.create(file_id, stem+".BASE", self.base_tree)
185
191
 
 
192
    def threeway_contents_conflict(filename, this_contents, base_contents,
 
193
                                   other_contents):
 
194
        self.conflict("Three-way conflict merging %s" % filename)
 
195
 
186
196
    def finalize(self):
187
197
        if not self.ignore_zero:
188
 
            print "%d conflicts encountered.\n" % self.conflicts
 
198
            note("%d conflicts encountered.\n" % self.conflicts)
189
199
            
190
 
def get_tree(treespec, temp_root, label, local_branch=None):
 
200
def get_tree(treespec, local_branch=None):
191
201
    location, revno = treespec
192
 
    branch = Branch.open_containing(location)
 
202
    branch = Branch.open_containing(location)[0]
193
203
    if revno is None:
194
204
        revision = None
195
205
    elif revno == -1:
196
206
        revision = branch.last_revision()
197
207
    else:
198
208
        revision = branch.get_rev_id(revno)
199
 
    return branch, get_revid_tree(branch, revision, temp_root, label,
200
 
                                  local_branch)
 
209
    return branch, get_revid_tree(branch, revision, local_branch)
201
210
 
202
 
def get_revid_tree(branch, revision, temp_root, label, local_branch):
 
211
def get_revid_tree(branch, revision, local_branch):
203
212
    if revision is None:
204
213
        base_tree = branch.working_tree()
205
214
    else:
208
217
            base_tree = local_branch.revision_tree(revision)
209
218
        else:
210
219
            base_tree = branch.revision_tree(revision)
211
 
    temp_path = os.path.join(temp_root, label)
212
 
    os.mkdir(temp_path)
213
 
    return MergeTree(base_tree, temp_path)
 
220
    return base_tree
214
221
 
215
222
 
216
223
def file_exists(tree, file_id):
217
224
    return tree.has_filename(tree.id2path(file_id))
218
225
    
219
226
 
220
 
class MergeTree(object):
221
 
    def __init__(self, tree, tempdir):
222
 
        object.__init__(self)
223
 
        if hasattr(tree, "basedir"):
224
 
            self.root = tree.basedir
225
 
        else:
226
 
            self.root = None
227
 
        self.tree = tree
228
 
        self.tempdir = tempdir
229
 
        os.mkdir(os.path.join(self.tempdir, "texts"))
230
 
        os.mkdir(os.path.join(self.tempdir, "symlinks"))
231
 
        self.cached = {}
232
 
 
233
 
    def __iter__(self):
234
 
        return self.tree.__iter__()
235
 
 
236
 
    def __contains__(self, file_id):
237
 
        return file_id in self.tree
238
 
 
239
 
    def get_file(self, file_id):
240
 
        return self.tree.get_file(file_id)
241
 
 
242
 
    def get_file_sha1(self, id):
243
 
        return self.tree.get_file_sha1(id)
244
 
 
245
 
    def is_executable(self, id):
246
 
        return self.tree.is_executable(id)
247
 
 
248
 
    def id2path(self, file_id):
249
 
        return self.tree.id2path(file_id)
250
 
 
251
 
    def has_id(self, file_id):
252
 
        return self.tree.has_id(file_id)
253
 
 
254
 
    def has_or_had_id(self, file_id):
255
 
        if file_id == self.tree.inventory.root.file_id:
256
 
            return True
257
 
        return self.tree.inventory.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 readonly_path(self, id):
265
 
        if id not in self.tree:
266
 
            return None
267
 
        if self.root is not None:
268
 
            return self.tree.abspath(self.tree.id2path(id))
269
 
        else:
270
 
            kind = self.tree.inventory[id].kind
271
 
            if kind in ("directory", "root_directory"):
272
 
                return self.tempdir
273
 
            if not self.cached.has_key(id):
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)
286
 
                self.cached[id] = path
287
 
            return self.cached[id]
288
 
 
289
 
 
290
227
def build_working_dir(to_dir):
291
228
    """Build a working directory in an empty directory.
292
229
 
327
264
    try:
328
265
        if this_dir is None:
329
266
            this_dir = '.'
330
 
        this_branch = Branch.open_containing(this_dir)
 
267
        this_branch = Branch.open_containing(this_dir)[0]
331
268
        this_rev_id = this_branch.last_revision()
332
269
        if this_rev_id is None:
333
270
            raise BzrCommandError("This branch has no commits")
336
273
                                    this_branch.basis_tree(), False)
337
274
            if changes.has_changed():
338
275
                raise BzrCommandError("Working tree has uncommitted changes.")
339
 
        other_branch, other_tree = get_tree(other_revision, tempdir, "other",
340
 
                                            this_branch)
 
276
        other_branch, other_tree = get_tree(other_revision, this_branch)
341
277
        if other_revision[1] == -1:
342
278
            other_rev_id = other_branch.last_revision()
343
279
            if other_rev_id is None:
357
293
                                              this_branch)
358
294
            except NoCommonAncestor:
359
295
                raise UnrelatedBranches()
360
 
            base_tree = get_revid_tree(this_branch, base_rev_id, tempdir, 
361
 
                                       "base", None)
 
296
            base_tree = get_revid_tree(this_branch, base_rev_id, None)
362
297
            base_is_ancestor = True
363
298
        else:
364
 
            base_branch, base_tree = get_tree(base_revision, tempdir, "base")
 
299
            base_branch, base_tree = get_tree(base_revision)
365
300
            if base_revision[1] == -1:
366
301
                base_rev_id = base_branch.last_revision()
367
302
            elif base_revision[1] is None:
377
312
            interesting_ids = set()
378
313
            this_tree = this_branch.working_tree()
379
314
            for fname in file_list:
380
 
                path = this_branch.relpath(fname)
 
315
                path = this_tree.relpath(fname)
381
316
                found_id = False
382
 
                for tree in (this_tree, base_tree.tree, other_tree.tree):
 
317
                for tree in (this_tree, base_tree, other_tree):
383
318
                    file_id = tree.inventory.path2id(path)
384
319
                    if file_id is not None:
385
320
                        interesting_ids.add(file_id)
405
340
             source_file.interesting = source_file.id in interesting_ids
406
341
 
407
342
 
408
 
def generate_cset_optimized(tree_a, tree_b, interesting_ids=None):
409
 
    """Generate a changeset.  If interesting_ids is supplied, only changes
410
 
    to those files will be shown.  Metadata changes are stripped.
411
 
    """ 
412
 
    cset =  generate_changeset(tree_a, tree_b, interesting_ids)
413
 
    for entry in cset.entries.itervalues():
414
 
        entry.metadata_change = None
415
 
    return cset
416
 
 
417
 
 
418
343
def merge_inner(this_branch, other_tree, base_tree, tempdir, 
419
344
                ignore_zero=False, merge_type=ApplyMerge3, backup_files=False,
420
345
                interesting_ids=None):
425
350
            contents_change = BackupBeforeChange(contents_change)
426
351
        return contents_change
427
352
 
428
 
    this_tree = get_tree((this_branch.base, None), tempdir, "this")[1]
 
353
    this_tree = get_tree((this_branch.base, None))[1]
429
354
 
430
355
    def get_inventory(tree):
431
 
        return tree.tree.inventory
 
356
        return tree.inventory
432
357
 
433
358
    inv_changes = merge_flex(this_tree, base_tree, other_tree,
434
 
                             generate_cset_optimized, get_inventory,
 
359
                             generate_changeset, get_inventory,
435
360
                             MergeConflictHandler(this_tree, base_tree,
436
361
                             other_tree, ignore_zero=ignore_zero),
437
362
                             merge_factory=merge_factory, 
447
372
            path = path[2:]
448
373
        adjust_ids.append((path, id))
449
374
    if len(adjust_ids) > 0:
450
 
        this_branch.set_inventory(regen_inventory(this_branch, this_tree.root,
 
375
        this_branch.set_inventory(regen_inventory(this_branch, 
 
376
                                                  this_tree.basedir,
451
377
                                                  adjust_ids))
452
378
 
453
379