~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Martin Pool
  • Date: 2005-06-06 04:17:53 UTC
  • Revision ID: mbp@sourcefrog.net-20050606041753-abe590daf0d7f959
Updated merge patch from Aaron

This patch contains all the changes to merge that I'd like to get into
0.5, namely
* common ancestor BASE selection
* merge reports conflicts when they are encountered
* merge refuses to operate in working trees with changes
* introduces revert command to revert the working tree to the
last-committed state
* Adds some reasonable help text

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
from merge_core import merge_flex
2
2
from changeset import generate_changeset, ExceptionConflictHandler
3
3
from changeset import Inventory
4
 
from bzrlib import Branch
 
4
from bzrlib import find_branch
5
5
import bzrlib.osutils
6
 
from trace import mutter
 
6
from bzrlib.errors import BzrCommandError
 
7
from bzrlib.diff import compare_trees
 
8
from trace import mutter, warning
7
9
import os.path
8
10
import tempfile
9
11
import shutil
10
12
import errno
11
13
 
 
14
class UnrelatedBranches(BzrCommandError):
 
15
    def __init__(self):
 
16
        msg = "Branches have no common ancestor, and no base revision"\
 
17
            " specified."
 
18
        BzrCommandError.__init__(self, msg)
 
19
 
 
20
 
12
21
class MergeConflictHandler(ExceptionConflictHandler):
13
22
    """Handle conflicts encountered while merging"""
 
23
    def __init__(self, dir, ignore_zero=False):
 
24
        ExceptionConflictHandler.__init__(self, dir)
 
25
        self.conflicts = 0
 
26
        self.ignore_zero = ignore_zero
 
27
 
14
28
    def copy(self, source, dest):
15
29
        """Copy the text and mode of a file
16
30
        :param source: The path of the file to copy
35
49
        new_name = last_new_name+suffix
36
50
        try:
37
51
            os.rename(name, new_name)
 
52
            return new_name
38
53
        except OSError, e:
39
54
            if e.errno != errno.EEXIST and e.errno != errno.ENOTEMPTY:
40
55
                raise
41
 
            self.add_suffix(name, suffix, last_new_name=new_name)
 
56
            return self.add_suffix(name, suffix, last_new_name=new_name)
 
57
 
 
58
    def conflict(self, text):
 
59
        warning(text)
 
60
        self.conflicts += 1
 
61
        
42
62
 
43
63
    def merge_conflict(self, new_file, this_path, base_path, other_path):
44
64
        """
53
73
        self.copy(base_path, this_path+".BASE")
54
74
        self.copy(other_path, this_path+".OTHER")
55
75
        os.rename(new_file, this_path)
 
76
        self.conflict("Diff3 conflict encountered in %s" % this_path)
56
77
 
57
78
    def target_exists(self, entry, target, old_path):
58
79
        """Handle the case when the target file or dir exists"""
59
 
        self.add_suffix(target, ".moved")
 
80
        moved_path = self.add_suffix(target, ".moved")
 
81
        self.conflict("Moved existing %s to %s" % (target, moved_path))
 
82
 
 
83
    def finalize(self):
 
84
        if not self.ignore_zero:
 
85
            print "%d conflicts encountered.\n" % self.conflicts
60
86
            
61
87
class SourceFile(object):
62
88
    def __init__(self, path, id, present=None, isdir=None):
70
96
        return "SourceFile(%s, %s)" % (self.path, self.id)
71
97
 
72
98
def get_tree(treespec, temp_root, label):
73
 
    dir, revno = treespec
74
 
    branch = Branch(dir)
 
99
    location, revno = treespec
 
100
    branch = find_branch(location)
75
101
    if revno is None:
76
102
        base_tree = branch.working_tree()
77
103
    elif revno == -1:
80
106
        base_tree = branch.revision_tree(branch.lookup_revision(revno))
81
107
    temp_path = os.path.join(temp_root, label)
82
108
    os.mkdir(temp_path)
83
 
    return MergeTree(base_tree, temp_path)
 
109
    return branch, MergeTree(base_tree, temp_path)
84
110
 
85
111
 
86
112
def abspath(tree, file_id):
129
155
                self.cached[id] = path
130
156
            return self.cached[id]
131
157
 
132
 
def merge(other_revision, base_revision):
 
158
def merge(other_revision, base_revision, no_changes=True, ignore_zero=False):
133
159
    tempdir = tempfile.mkdtemp(prefix="bzr-")
134
160
    try:
135
 
        this_branch = Branch('.') 
136
 
        other_tree = get_tree(other_revision, tempdir, "other")
137
 
        base_tree = get_tree(base_revision, tempdir, "base")
138
 
        merge_inner(this_branch, other_tree, base_tree, tempdir)
 
161
        this_branch = find_branch('.') 
 
162
        if no_changes:
 
163
            changes = compare_trees(this_branch.working_tree(), 
 
164
                                    this_branch.basis_tree(), False)
 
165
            if changes.has_changed():
 
166
                raise BzrCommandError("Working tree has uncommitted changes.")
 
167
        other_branch, other_tree = get_tree(other_revision, tempdir, "other")
 
168
        if base_revision == [None, None]:
 
169
            if other_revision[1] == -1:
 
170
                o_revno = None
 
171
            else:
 
172
                o_revno = other_revision[1]
 
173
            base_revno = this_branch.common_ancestor(other_branch, 
 
174
                                                     other_revno=o_revno)[0]
 
175
            if base_revno is None:
 
176
                raise UnrelatedBranches()
 
177
            base_revision = ['.', base_revno]
 
178
        base_branch, base_tree = get_tree(base_revision, tempdir, "base")
 
179
        merge_inner(this_branch, other_tree, base_tree, tempdir, 
 
180
                    ignore_zero=ignore_zero)
139
181
    finally:
140
182
        shutil.rmtree(tempdir)
141
183
 
164
206
    return cset
165
207
 
166
208
 
167
 
def merge_inner(this_branch, other_tree, base_tree, tempdir):
168
 
    this_tree = get_tree(('.', None), tempdir, "this")
 
209
def merge_inner(this_branch, other_tree, base_tree, tempdir, 
 
210
                ignore_zero=False):
 
211
    this_tree = get_tree(('.', None), tempdir, "this")[1]
169
212
 
170
213
    def get_inventory(tree):
171
214
        return tree.inventory
172
215
 
173
216
    inv_changes = merge_flex(this_tree, base_tree, other_tree,
174
217
                             generate_cset_optimized, get_inventory,
175
 
                             MergeConflictHandler(base_tree.root))
 
218
                             MergeConflictHandler(base_tree.root,
 
219
                                                  ignore_zero=ignore_zero))
176
220
 
177
221
    adjust_ids = []
178
222
    for id, path in inv_changes.iteritems():