~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:02:33 UTC
  • mfrom: (1421)
  • mto: This revision was merged to the branch mainline in revision 1422.
  • Revision ID: robertc@robertcollins.net-20051008000233-c7c5d0d2b7000da0
merge from newformat stuff and upgrade

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
31
30
from bzrlib.errors import NoCommits
32
31
from bzrlib.delta import compare_trees
33
32
from bzrlib.trace import mutter, warning
34
 
from bzrlib.fetch import greedy_fetch
 
33
from bzrlib.fetch import greedy_fetch, fetch
35
34
from bzrlib.revision import is_ancestor
36
35
from bzrlib.osutils import rename
37
 
 
 
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
38
42
 
39
43
# comments from abentley on irc: merge happens in two stages, each
40
44
# of which generates a changeset object
49
53
    conflict that are not explicitly handled cause an exception and
50
54
    terminate the merge.
51
55
    """
52
 
    def __init__(self, ignore_zero=False):
 
56
    def __init__(self, this_tree, base_tree, other_tree, ignore_zero=False):
53
57
        ExceptionConflictHandler.__init__(self)
54
58
        self.conflicts = 0
55
59
        self.ignore_zero = ignore_zero
 
60
        self.this_tree = this_tree
 
61
        self.base_tree = base_tree
 
62
        self.other_tree = other_tree
56
63
 
57
64
    def copy(self, source, dest):
58
65
        """Copy the text and mode of a file
135
142
        this_contents(filename+".THIS", self, False)
136
143
        return ReplaceContents(this_contents, None)
137
144
 
 
145
    def rem_contents_conflict(self, filename, this_contents, base_contents):
 
146
        base_contents(filename+".BASE", self, False)
 
147
        this_contents(filename+".THIS", self, False)
 
148
        self.conflict("Other branch deleted locally modified file %s" %
 
149
                      filename)
 
150
        return ReplaceContents(this_contents, None)
 
151
 
 
152
    def abs_this_path(self, file_id):
 
153
        """Return the absolute path for a file_id in the this tree."""
 
154
        relpath = self.this_tree.id2path(file_id)
 
155
        return self.this_tree.tree.abspath(relpath)
 
156
 
 
157
    def add_missing_parents(self, file_id, tree):
 
158
        """If some of the parents for file_id are missing, add them."""
 
159
        entry = tree.tree.inventory[file_id]
 
160
        if entry.parent_id not in self.this_tree:
 
161
            return self.create_all_missing(entry.parent_id, tree)
 
162
        else:
 
163
            return self.abs_this_path(entry.parent_id)
 
164
 
 
165
    def create_all_missing(self, file_id, tree):
 
166
        """Add contents for a file_id and all its parents to a tree."""
 
167
        entry = tree.tree.inventory[file_id]
 
168
        if entry.parent_id is not None and entry.parent_id not in self.this_tree:
 
169
            abspath = self.create_all_missing(entry.parent_id, tree)
 
170
        else:
 
171
            abspath = self.abs_this_path(entry.parent_id)
 
172
        entry_path = os.path.join(abspath, entry.name)
 
173
        if not os.path.isdir(entry_path):
 
174
            self.create(file_id, entry_path, tree)
 
175
        return entry_path
 
176
 
 
177
    def create(self, file_id, path, tree, reverse=False):
 
178
        """Uses tree data to create a filesystem object for the file_id"""
 
179
        from merge_core import get_id_contents
 
180
        get_id_contents(file_id, tree)(path, self, reverse)
 
181
 
 
182
    def missing_for_merge(self, file_id, other_path):
 
183
        """The file_id doesn't exist in THIS, but does in OTHER and BASE"""
 
184
        self.conflict("Other branch modified locally deleted file %s" %
 
185
                      other_path)
 
186
        parent_dir = self.add_missing_parents(file_id, self.other_tree)
 
187
        stem = os.path.join(parent_dir, os.path.basename(other_path))
 
188
        self.create(file_id, stem+".OTHER", self.other_tree)
 
189
        self.create(file_id, stem+".BASE", self.base_tree)
 
190
 
138
191
    def finalize(self):
139
192
        if not self.ignore_zero:
140
193
            print "%d conflicts encountered.\n" % self.conflicts
145
198
    if revno is None:
146
199
        revision = None
147
200
    elif revno == -1:
148
 
        revision = branch.last_patch()
 
201
        revision = branch.last_revision()
149
202
    else:
150
203
        revision = branch.get_rev_id(revno)
151
204
    return branch, get_revid_tree(branch, revision, temp_root, label,
179
232
        self.tree = tree
180
233
        self.tempdir = tempdir
181
234
        os.mkdir(os.path.join(self.tempdir, "texts"))
 
235
        os.mkdir(os.path.join(self.tempdir, "symlinks"))
182
236
        self.cached = {}
183
237
 
184
238
    def __iter__(self):
193
247
    def get_file_sha1(self, id):
194
248
        return self.tree.get_file_sha1(id)
195
249
 
 
250
    def is_executable(self, id):
 
251
        return self.tree.is_executable(id)
 
252
 
196
253
    def id2path(self, file_id):
197
254
        return self.tree.id2path(file_id)
198
255
 
215
272
        if self.root is not None:
216
273
            return self.tree.abspath(self.tree.id2path(id))
217
274
        else:
218
 
            if self.tree.inventory[id].kind in ("directory", "root_directory"):
 
275
            kind = self.tree.inventory[id].kind
 
276
            if kind in ("directory", "root_directory"):
219
277
                return self.tempdir
220
278
            if not self.cached.has_key(id):
221
 
                path = os.path.join(self.tempdir, "texts", id)
222
 
                outfile = file(path, "wb")
223
 
                outfile.write(self.tree.get_file(id).read())
224
 
                assert(os.path.exists(path))
 
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)
225
291
                self.cached[id] = path
226
292
            return self.cached[id]
227
293
 
228
294
 
 
295
def build_working_dir(to_dir):
 
296
    """Build a working directory in an empty directory.
 
297
 
 
298
    to_dir is a directory containing branch metadata but no working files,
 
299
    typically constructed by cloning an existing branch. 
 
300
 
 
301
    This is split out as a special idiomatic case of merge.  It could
 
302
    eventually be done by just building the tree directly calling into 
 
303
    lower-level code (e.g. constructing a changeset).
 
304
    """
 
305
    merge((to_dir, -1), (to_dir, 0), this_dir=to_dir,
 
306
          check_clean=False, ignore_zero=True)
 
307
 
229
308
 
230
309
def merge(other_revision, base_revision,
231
310
          check_clean=True, ignore_zero=False,
242
321
    check_clean
243
322
        If true, this_dir must have no uncommitted changes before the
244
323
        merge begins.
245
 
    all available ancestors of other_revision and base_revision are
 
324
    ignore_zero - If true, suppress the "zero conflicts" message when 
 
325
        there are no conflicts; should be set when doing something we expect
 
326
        to complete perfectly.
 
327
 
 
328
    All available ancestors of other_revision and base_revision are
246
329
    automatically pulled into the branch.
247
330
    """
248
 
    from bzrlib.revision import common_ancestor, MultipleRevisionSources
249
 
    from bzrlib.errors import NoSuchRevision
250
331
    tempdir = tempfile.mkdtemp(prefix="bzr-")
251
332
    try:
252
333
        if this_dir is None:
253
334
            this_dir = '.'
254
335
        this_branch = Branch.open_containing(this_dir)
255
 
        this_rev_id = this_branch.last_patch()
 
336
        this_rev_id = this_branch.last_revision()
256
337
        if this_rev_id is None:
257
338
            raise BzrCommandError("This branch has no commits")
258
339
        if check_clean:
263
344
        other_branch, other_tree = get_tree(other_revision, tempdir, "other",
264
345
                                            this_branch)
265
346
        if other_revision[1] == -1:
266
 
            other_rev_id = other_branch.last_patch()
 
347
            other_rev_id = other_branch.last_revision()
267
348
            if other_rev_id is None:
268
349
                raise NoCommits(other_branch)
269
350
            other_basis = other_rev_id
272
353
            other_basis = other_rev_id
273
354
        else:
274
355
            other_rev_id = None
275
 
            other_basis = other_branch.last_patch()
 
356
            other_basis = other_branch.last_revision()
276
357
            if other_basis is None:
277
358
                raise NoCommits(other_branch)
278
359
        if base_revision == [None, None]:
287
368
        else:
288
369
            base_branch, base_tree = get_tree(base_revision, tempdir, "base")
289
370
            if base_revision[1] == -1:
290
 
                base_rev_id = base_branch.last_patch()
 
371
                base_rev_id = base_branch.last_revision()
291
372
            elif base_revision[1] is None:
292
373
                base_rev_id = None
293
374
            else:
294
375
                base_rev_id = base_branch.get_rev_id(base_revision[1])
295
 
            multi_source = MultipleRevisionSources(this_branch, base_branch)
 
376
            fetch(from_branch=base_branch, to_branch=this_branch)
296
377
            base_is_ancestor = is_ancestor(this_rev_id, base_rev_id,
297
 
                                           multi_source)
 
378
                                           this_branch)
298
379
        if file_list is None:
299
380
            interesting_ids = None
300
381
        else:
356
437
 
357
438
    inv_changes = merge_flex(this_tree, base_tree, other_tree,
358
439
                             generate_cset_optimized, get_inventory,
359
 
                             MergeConflictHandler(ignore_zero=ignore_zero),
 
440
                             MergeConflictHandler(this_tree, base_tree,
 
441
                             other_tree, ignore_zero=ignore_zero),
360
442
                             merge_factory=merge_factory, 
361
443
                             interesting_ids=interesting_ids)
362
444