~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: John Arbash Meinel
  • Date: 2005-09-15 21:35:53 UTC
  • mfrom: (907.1.57)
  • mto: (1393.2.1)
  • mto: This revision was merged to the branch mainline in revision 1396.
  • Revision ID: john@arbash-meinel.com-20050915213552-a6c83a5ef1e20897
(broken) Transport work is merged in. Tests do not pass yet.

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
22
23
 
23
24
import bzrlib.osutils
24
25
import bzrlib.revision
25
26
from bzrlib.merge_core import merge_flex, ApplyMerge3, BackupBeforeChange
26
27
from bzrlib.changeset import generate_changeset, ExceptionConflictHandler
27
 
from bzrlib.changeset import Inventory, Diff3Merge, ReplaceContents
28
 
from bzrlib.branch import Branch
 
28
from bzrlib.changeset import Inventory, Diff3Merge
 
29
from bzrlib.branch import find_branch
29
30
from bzrlib.errors import BzrCommandError, UnrelatedBranches, NoCommonAncestor
30
31
from bzrlib.errors import NoCommits
31
32
from bzrlib.delta import compare_trees
32
33
from bzrlib.trace import mutter, warning
33
 
from bzrlib.fetch import greedy_fetch, fetch
 
34
from bzrlib.fetch import greedy_fetch
34
35
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
42
36
 
43
37
# comments from abentley on irc: merge happens in two stages, each
44
38
# of which generates a changeset object
53
47
    conflict that are not explicitly handled cause an exception and
54
48
    terminate the merge.
55
49
    """
56
 
    def __init__(self, this_tree, base_tree, other_tree, ignore_zero=False):
57
 
        ExceptionConflictHandler.__init__(self)
 
50
    def __init__(self, dir, ignore_zero=False):
 
51
        ExceptionConflictHandler.__init__(self, dir)
58
52
        self.conflicts = 0
59
53
        self.ignore_zero = ignore_zero
60
 
        self.this_tree = this_tree
61
 
        self.base_tree = base_tree
62
 
        self.other_tree = other_tree
63
54
 
64
55
    def copy(self, source, dest):
65
56
        """Copy the text and mode of a file
93
84
            last_new_name = name
94
85
        new_name = last_new_name+suffix
95
86
        try:
96
 
            rename(name, new_name)
 
87
            os.rename(name, new_name)
97
88
            return new_name
98
89
        except OSError, e:
99
90
            if e.errno != errno.EEXIST and e.errno != errno.ENOTEMPTY:
117
108
        self.add_suffix(this_path, ".THIS")
118
109
        self.dump(base_lines, this_path+".BASE")
119
110
        self.dump(other_lines, this_path+".OTHER")
120
 
        rename(new_file, this_path)
 
111
        os.rename(new_file, this_path)
121
112
        self.conflict("Diff3 conflict encountered in %s" % this_path)
122
113
 
123
114
    def new_contents_conflict(self, filename, other_contents):
137
128
            % filename)
138
129
        return "skip"
139
130
 
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
 
 
186
131
    def finalize(self):
187
132
        if not self.ignore_zero:
188
133
            print "%d conflicts encountered.\n" % self.conflicts
189
134
            
190
135
def get_tree(treespec, temp_root, label, local_branch=None):
191
136
    location, revno = treespec
192
 
    branch = Branch.open_containing(location)
 
137
    branch = find_branch(location)
193
138
    if revno is None:
194
139
        revision = None
195
140
    elif revno == -1:
196
 
        revision = branch.last_revision()
 
141
        revision = branch.last_patch()
197
142
    else:
198
 
        revision = branch.get_rev_id(revno)
 
143
        revision = branch.lookup_revision(revno)
199
144
    return branch, get_revid_tree(branch, revision, temp_root, label,
200
145
                                  local_branch)
201
146
 
227
172
        self.tree = tree
228
173
        self.tempdir = tempdir
229
174
        os.mkdir(os.path.join(self.tempdir, "texts"))
230
 
        os.mkdir(os.path.join(self.tempdir, "symlinks"))
231
175
        self.cached = {}
232
176
 
233
177
    def __iter__(self):
242
186
    def get_file_sha1(self, id):
243
187
        return self.tree.get_file_sha1(id)
244
188
 
245
 
    def is_executable(self, id):
246
 
        return self.tree.is_executable(id)
247
 
 
248
189
    def id2path(self, file_id):
249
190
        return self.tree.id2path(file_id)
250
191
 
267
208
        if self.root is not None:
268
209
            return self.tree.abspath(self.tree.id2path(id))
269
210
        else:
270
 
            kind = self.tree.inventory[id].kind
271
 
            if kind in ("directory", "root_directory"):
 
211
            if self.tree.inventory[id].kind in ("directory", "root_directory"):
272
212
                return self.tempdir
273
213
            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)
 
214
                path = os.path.join(self.tempdir, "texts", id)
 
215
                outfile = file(path, "wb")
 
216
                outfile.write(self.tree.get_file(id).read())
 
217
                assert(os.path.exists(path))
286
218
                self.cached[id] = path
287
219
            return self.cached[id]
288
220
 
289
221
 
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
 
 
303
222
 
304
223
def merge(other_revision, base_revision,
305
224
          check_clean=True, ignore_zero=False,
316
235
    check_clean
317
236
        If true, this_dir must have no uncommitted changes before the
318
237
        merge begins.
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
    all available ancestors of other_revision and base_revision are
324
239
    automatically pulled into the branch.
325
240
    """
 
241
    from bzrlib.revision import common_ancestor, MultipleRevisionSources
 
242
    from bzrlib.errors import NoSuchRevision
326
243
    tempdir = tempfile.mkdtemp(prefix="bzr-")
327
244
    try:
328
245
        if this_dir is None:
329
246
            this_dir = '.'
330
 
        this_branch = Branch.open_containing(this_dir)
331
 
        this_rev_id = this_branch.last_revision()
 
247
        this_branch = find_branch(this_dir)
 
248
        this_rev_id = this_branch.last_patch()
332
249
        if this_rev_id is None:
333
250
            raise BzrCommandError("This branch has no commits")
334
251
        if check_clean:
339
256
        other_branch, other_tree = get_tree(other_revision, tempdir, "other",
340
257
                                            this_branch)
341
258
        if other_revision[1] == -1:
342
 
            other_rev_id = other_branch.last_revision()
 
259
            other_rev_id = other_branch.last_patch()
343
260
            if other_rev_id is None:
344
261
                raise NoCommits(other_branch)
345
262
            other_basis = other_rev_id
346
263
        elif other_revision[1] is not None:
347
 
            other_rev_id = other_branch.get_rev_id(other_revision[1])
 
264
            other_rev_id = other_branch.lookup_revision(other_revision[1])
348
265
            other_basis = other_rev_id
349
266
        else:
350
267
            other_rev_id = None
351
 
            other_basis = other_branch.last_revision()
 
268
            other_basis = other_branch.last_patch()
352
269
            if other_basis is None:
353
270
                raise NoCommits(other_branch)
354
271
        if base_revision == [None, None]:
363
280
        else:
364
281
            base_branch, base_tree = get_tree(base_revision, tempdir, "base")
365
282
            if base_revision[1] == -1:
366
 
                base_rev_id = base_branch.last_revision()
 
283
                base_rev_id = base_branch.last_patch()
367
284
            elif base_revision[1] is None:
368
285
                base_rev_id = None
369
286
            else:
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)
 
287
                base_rev_id = base_branch.lookup_revision(base_revision[1])
 
288
            if base_rev_id is not None:
 
289
                base_is_ancestor = is_ancestor(this_rev_id, base_rev_id, 
 
290
                                               MultipleRevisionSources(this_branch, 
 
291
                                                                       base_branch))
 
292
            else:
 
293
                base_is_ancestor = False
374
294
        if file_list is None:
375
295
            interesting_ids = None
376
296
        else:
432
352
 
433
353
    inv_changes = merge_flex(this_tree, base_tree, other_tree,
434
354
                             generate_cset_optimized, get_inventory,
435
 
                             MergeConflictHandler(this_tree, base_tree,
436
 
                             other_tree, ignore_zero=ignore_zero),
 
355
                             MergeConflictHandler(base_tree.root,
 
356
                                                  ignore_zero=ignore_zero),
437
357
                             merge_factory=merge_factory, 
438
358
                             interesting_ids=interesting_ids)
439
359