/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
1047 by Martin Pool
- add some comments on merge from talking to aaron
1
from bzrlib.merge_core import merge_flex, ApplyMerge3, BackupBeforeChange
1045 by Martin Pool
Doc
2
from bzrlib.changeset import generate_changeset, ExceptionConflictHandler
3
from bzrlib.changeset import Inventory, Diff3Merge
622 by Martin Pool
Updated merge patch from Aaron
4
from bzrlib import find_branch
493 by Martin Pool
- Merge aaron's merge command
5
import bzrlib.osutils
622 by Martin Pool
Updated merge patch from Aaron
6
from bzrlib.errors import BzrCommandError
1005 by Martin Pool
- split TreeDelta and compare_trees out into new module bzrlib.delta
7
from bzrlib.delta import compare_trees
622 by Martin Pool
Updated merge patch from Aaron
8
from trace import mutter, warning
493 by Martin Pool
- Merge aaron's merge command
9
import os.path
10
import tempfile
11
import shutil
12
import errno
13
1047 by Martin Pool
- add some comments on merge from talking to aaron
14
15
# comments from abentley on irc: merge happens in two stages, each
16
# of which generates a changeset object
17
18
# stage 1: generate OLD->OTHER,
19
# stage 2: use MINE and OLD->OTHER to generate MINE -> RESULT
20
622 by Martin Pool
Updated merge patch from Aaron
21
class UnrelatedBranches(BzrCommandError):
22
    def __init__(self):
23
        msg = "Branches have no common ancestor, and no base revision"\
24
            " specified."
25
        BzrCommandError.__init__(self, msg)
26
27
493 by Martin Pool
- Merge aaron's merge command
28
class MergeConflictHandler(ExceptionConflictHandler):
1045 by Martin Pool
Doc
29
    """Handle conflicts encountered while merging.
30
31
    This subclasses ExceptionConflictHandler, so that any types of
32
    conflict that are not explicitly handled cause an exception and
33
    terminate the merge.
34
    """
622 by Martin Pool
Updated merge patch from Aaron
35
    def __init__(self, dir, ignore_zero=False):
36
        ExceptionConflictHandler.__init__(self, dir)
37
        self.conflicts = 0
38
        self.ignore_zero = ignore_zero
39
493 by Martin Pool
- Merge aaron's merge command
40
    def copy(self, source, dest):
41
        """Copy the text and mode of a file
42
        :param source: The path of the file to copy
43
        :param dest: The distination file to create
44
        """
45
        s_file = file(source, "rb")
46
        d_file = file(dest, "wb")
47
        for line in s_file:
48
            d_file.write(line)
49
        os.chmod(dest, 0777 & os.stat(source).st_mode)
50
51
    def add_suffix(self, name, suffix, last_new_name=None):
52
        """Rename a file to append a suffix.  If the new name exists, the
53
        suffix is added repeatedly until a non-existant name is found
54
55
        :param name: The path of the file
56
        :param suffix: The suffix to append
57
        :param last_new_name: (used for recursive calls) the last name tried
58
        """
59
        if last_new_name is None:
60
            last_new_name = name
61
        new_name = last_new_name+suffix
62
        try:
63
            os.rename(name, new_name)
622 by Martin Pool
Updated merge patch from Aaron
64
            return new_name
493 by Martin Pool
- Merge aaron's merge command
65
        except OSError, e:
66
            if e.errno != errno.EEXIST and e.errno != errno.ENOTEMPTY:
67
                raise
622 by Martin Pool
Updated merge patch from Aaron
68
            return self.add_suffix(name, suffix, last_new_name=new_name)
69
70
    def conflict(self, text):
71
        warning(text)
72
        self.conflicts += 1
73
        
493 by Martin Pool
- Merge aaron's merge command
74
75
    def merge_conflict(self, new_file, this_path, base_path, other_path):
76
        """
77
        Handle diff3 conflicts by producing a .THIS, .BASE and .OTHER.  The
78
        main file will be a version with diff3 conflicts.
79
        :param new_file: Path to the output file with diff3 markers
80
        :param this_path: Path to the file text for the THIS tree
81
        :param base_path: Path to the file text for the BASE tree
82
        :param other_path: Path to the file text for the OTHER tree
83
        """
84
        self.add_suffix(this_path, ".THIS")
85
        self.copy(base_path, this_path+".BASE")
86
        self.copy(other_path, this_path+".OTHER")
87
        os.rename(new_file, this_path)
622 by Martin Pool
Updated merge patch from Aaron
88
        self.conflict("Diff3 conflict encountered in %s" % this_path)
493 by Martin Pool
- Merge aaron's merge command
89
90
    def target_exists(self, entry, target, old_path):
91
        """Handle the case when the target file or dir exists"""
622 by Martin Pool
Updated merge patch from Aaron
92
        moved_path = self.add_suffix(target, ".moved")
93
        self.conflict("Moved existing %s to %s" % (target, moved_path))
94
850 by Martin Pool
- Merge merge updates from aaron
95
    def rmdir_non_empty(self, filename):
96
        """Handle the case where the dir to be removed still has contents"""
97
        self.conflict("Directory %s not removed because it is not empty"\
98
            % filename)
99
        return "skip"
100
622 by Martin Pool
Updated merge patch from Aaron
101
    def finalize(self):
102
        if not self.ignore_zero:
103
            print "%d conflicts encountered.\n" % self.conflicts
493 by Martin Pool
- Merge aaron's merge command
104
            
558 by Martin Pool
- All top-level classes inherit from object
105
class SourceFile(object):
493 by Martin Pool
- Merge aaron's merge command
106
    def __init__(self, path, id, present=None, isdir=None):
107
        self.path = path
108
        self.id = id
109
        self.present = present
110
        self.isdir = isdir
111
        self.interesting = True
112
113
    def __repr__(self):
114
        return "SourceFile(%s, %s)" % (self.path, self.id)
115
116
def get_tree(treespec, temp_root, label):
622 by Martin Pool
Updated merge patch from Aaron
117
    location, revno = treespec
118
    branch = find_branch(location)
493 by Martin Pool
- Merge aaron's merge command
119
    if revno is None:
120
        base_tree = branch.working_tree()
121
    elif revno == -1:
122
        base_tree = branch.basis_tree()
123
    else:
124
        base_tree = branch.revision_tree(branch.lookup_revision(revno))
125
    temp_path = os.path.join(temp_root, label)
126
    os.mkdir(temp_path)
622 by Martin Pool
Updated merge patch from Aaron
127
    return branch, MergeTree(base_tree, temp_path)
493 by Martin Pool
- Merge aaron's merge command
128
129
130
def abspath(tree, file_id):
131
    path = tree.inventory.id2path(file_id)
132
    if path == "":
133
        return "./."
134
    return "./" + path
135
136
def file_exists(tree, file_id):
137
    return tree.has_filename(tree.id2path(file_id))
138
    
139
def inventory_map(tree):
140
    inventory = {}
141
    for file_id in tree.inventory:
142
        path = abspath(tree, file_id)
143
        inventory[path] = SourceFile(path, file_id)
144
    return inventory
145
146
147
class MergeTree(object):
148
    def __init__(self, tree, tempdir):
149
        object.__init__(self)
150
        if hasattr(tree, "basedir"):
151
            self.root = tree.basedir
152
        else:
153
            self.root = None
154
        self.inventory = inventory_map(tree)
155
        self.tree = tree
156
        self.tempdir = tempdir
157
        os.mkdir(os.path.join(self.tempdir, "texts"))
158
        self.cached = {}
159
160
    def readonly_path(self, id):
850 by Martin Pool
- Merge merge updates from aaron
161
        if id not in self.tree:
162
            return None
493 by Martin Pool
- Merge aaron's merge command
163
        if self.root is not None:
164
            return self.tree.abspath(self.tree.id2path(id))
165
        else:
166
            if self.tree.inventory[id].kind in ("directory", "root_directory"):
167
                return self.tempdir
168
            if not self.cached.has_key(id):
169
                path = os.path.join(self.tempdir, "texts", id)
170
                outfile = file(path, "wb")
171
                outfile.write(self.tree.get_file(id).read())
172
                assert(os.path.exists(path))
173
                self.cached[id] = path
174
            return self.cached[id]
175
628 by Martin Pool
- merge aaron's updated merge/pull code
176
177
178
def merge(other_revision, base_revision,
179
          check_clean=True, ignore_zero=False,
974.1.10 by aaron.bentley at utoronto
Added file-selection to merge-revert
180
          this_dir=None, backup_files=False, merge_type=ApplyMerge3,
181
          file_list=None):
628 by Martin Pool
- merge aaron's updated merge/pull code
182
    """Merge changes into a tree.
183
184
    base_revision
185
        Base for three-way merge.
186
    other_revision
187
        Other revision for three-way merge.
188
    this_dir
189
        Directory to merge changes into; '.' by default.
190
    check_clean
191
        If true, this_dir must have no uncommitted changes before the
192
        merge begins.
193
    """
493 by Martin Pool
- Merge aaron's merge command
194
    tempdir = tempfile.mkdtemp(prefix="bzr-")
195
    try:
628 by Martin Pool
- merge aaron's updated merge/pull code
196
        if this_dir is None:
197
            this_dir = '.'
198
        this_branch = find_branch(this_dir)
199
        if check_clean:
622 by Martin Pool
Updated merge patch from Aaron
200
            changes = compare_trees(this_branch.working_tree(), 
201
                                    this_branch.basis_tree(), False)
202
            if changes.has_changed():
203
                raise BzrCommandError("Working tree has uncommitted changes.")
204
        other_branch, other_tree = get_tree(other_revision, tempdir, "other")
205
        if base_revision == [None, None]:
206
            if other_revision[1] == -1:
207
                o_revno = None
208
            else:
209
                o_revno = other_revision[1]
210
            base_revno = this_branch.common_ancestor(other_branch, 
211
                                                     other_revno=o_revno)[0]
212
            if base_revno is None:
213
                raise UnrelatedBranches()
214
            base_revision = ['.', base_revno]
215
        base_branch, base_tree = get_tree(base_revision, tempdir, "base")
974.1.10 by aaron.bentley at utoronto
Added file-selection to merge-revert
216
        if file_list is None:
217
            interesting_ids = None
218
        else:
219
            interesting_ids = set()
220
            this_tree = this_branch.working_tree()
221
            for fname in file_list:
222
                path = this_branch.relpath(fname)
223
                found_id = False
224
                for tree in (this_tree, base_tree.tree, other_tree.tree):
225
                    file_id = tree.inventory.path2id(path)
226
                    if file_id is not None:
227
                        interesting_ids.add(file_id)
228
                        found_id = True
229
                if not found_id:
230
                    raise BzrCommandError("%s is not a source file in any"
231
                                          " tree." % fname)
622 by Martin Pool
Updated merge patch from Aaron
232
        merge_inner(this_branch, other_tree, base_tree, tempdir, 
974.1.9 by Aaron Bentley
Added merge-type parameter to merge.
233
                    ignore_zero=ignore_zero, backup_files=backup_files, 
974.1.10 by aaron.bentley at utoronto
Added file-selection to merge-revert
234
                    merge_type=merge_type, interesting_ids=interesting_ids)
493 by Martin Pool
- Merge aaron's merge command
235
    finally:
236
        shutil.rmtree(tempdir)
237
238
974.1.10 by aaron.bentley at utoronto
Added file-selection to merge-revert
239
def set_interesting(inventory_a, inventory_b, interesting_ids):
240
    """Mark files whose ids are in interesting_ids as interesting
241
    """
242
    for inventory in (inventory_a, inventory_b):
243
        for path, source_file in inventory.iteritems():
244
             source_file.interesting = source_file.id in interesting_ids
245
246
247
def set_optimized(tree_a, tree_b, inventory_a, inventory_b):
248
    """Mark files that have changed texts as interesting
249
    """
493 by Martin Pool
- Merge aaron's merge command
250
    for file_id in tree_a.tree.inventory:
251
        if file_id not in tree_b.tree.inventory:
252
            continue
253
        entry_a = tree_a.tree.inventory[file_id]
254
        entry_b = tree_b.tree.inventory[file_id]
255
        if (entry_a.kind, entry_b.kind) != ("file", "file"):
256
            continue
257
        if None in (entry_a.text_id, entry_b.text_id):
258
            continue
259
        if entry_a.text_id != entry_b.text_id:
260
            continue
261
        inventory_a[abspath(tree_a.tree, file_id)].interesting = False
262
        inventory_b[abspath(tree_b.tree, file_id)].interesting = False
974.1.10 by aaron.bentley at utoronto
Added file-selection to merge-revert
263
264
265
def generate_cset_optimized(tree_a, tree_b, inventory_a, inventory_b,
266
                            interesting_ids=None):
267
    """Generate a changeset, with preprocessing to select interesting files.
268
    using the text_id to mark really-changed files.
269
    This permits blazing comparisons when text_ids are present.  It also
270
    disables metadata comparison for files with identical texts.
271
    """ 
272
    if interesting_ids is None:
273
        set_optimized(tree_a, tree_b, inventory_a, inventory_b)
274
    else:
275
        set_interesting(inventory_a, inventory_b, interesting_ids)
493 by Martin Pool
- Merge aaron's merge command
276
    cset =  generate_changeset(tree_a, tree_b, inventory_a, inventory_b)
277
    for entry in cset.entries.itervalues():
278
        entry.metadata_change = None
279
    return cset
280
281
622 by Martin Pool
Updated merge patch from Aaron
282
def merge_inner(this_branch, other_tree, base_tree, tempdir, 
974.1.10 by aaron.bentley at utoronto
Added file-selection to merge-revert
283
                ignore_zero=False, merge_type=ApplyMerge3, backup_files=False,
284
                interesting_ids=None):
974.1.4 by Aaron Bentley
Implemented merge3 as the default text merge
285
286
    def merge_factory(base_file, other_file):
974.1.9 by Aaron Bentley
Added merge-type parameter to merge.
287
        contents_change = merge_type(base_file, other_file)
974.1.8 by Aaron Bentley
Added default backups for merge-revert
288
        if backup_files:
289
            contents_change = BackupBeforeChange(contents_change)
290
        return contents_change
974.1.10 by aaron.bentley at utoronto
Added file-selection to merge-revert
291
    
292
    def generate_cset(tree_a, tree_b, inventory_a, inventory_b):
293
        return generate_cset_optimized(tree_a, tree_b, inventory_a, inventory_b,
294
                                       interesting_ids)
974.1.4 by Aaron Bentley
Implemented merge3 as the default text merge
295
628 by Martin Pool
- merge aaron's updated merge/pull code
296
    this_tree = get_tree((this_branch.base, None), tempdir, "this")[1]
493 by Martin Pool
- Merge aaron's merge command
297
298
    def get_inventory(tree):
299
        return tree.inventory
300
301
    inv_changes = merge_flex(this_tree, base_tree, other_tree,
974.1.10 by aaron.bentley at utoronto
Added file-selection to merge-revert
302
                             generate_cset, get_inventory,
622 by Martin Pool
Updated merge patch from Aaron
303
                             MergeConflictHandler(base_tree.root,
974.1.3 by Aaron Bentley
Added merge_factory parameter to merge_flex
304
                                                  ignore_zero=ignore_zero),
974.1.4 by Aaron Bentley
Implemented merge3 as the default text merge
305
                             merge_factory=merge_factory)
493 by Martin Pool
- Merge aaron's merge command
306
307
    adjust_ids = []
308
    for id, path in inv_changes.iteritems():
309
        if path is not None:
310
            if path == '.':
311
                path = ''
312
            else:
313
                assert path.startswith('./')
314
            path = path[2:]
315
        adjust_ids.append((path, id))
316
    this_branch.set_inventory(regen_inventory(this_branch, this_tree.root, adjust_ids))
317
318
319
def regen_inventory(this_branch, root, new_entries):
320
    old_entries = this_branch.read_working_inventory()
321
    new_inventory = {}
322
    by_path = {}
323
    for file_id in old_entries:
324
        entry = old_entries[file_id]
325
        path = old_entries.id2path(file_id)
326
        new_inventory[file_id] = (path, file_id, entry.parent_id, entry.kind)
327
        by_path[path] = file_id
328
    
329
    deletions = 0
330
    insertions = 0
331
    new_path_list = []
332
    for path, file_id in new_entries:
333
        if path is None:
334
            del new_inventory[file_id]
335
            deletions += 1
336
        else:
337
            new_path_list.append((path, file_id))
338
            if file_id not in old_entries:
339
                insertions += 1
340
    # Ensure no file is added before its parent
341
    new_path_list.sort()
342
    for path, file_id in new_path_list:
343
        if path == '':
344
            parent = None
345
        else:
346
            parent = by_path[os.path.dirname(path)]
347
        kind = bzrlib.osutils.file_kind(os.path.join(root, path))
348
        new_inventory[file_id] = (path, file_id, parent, kind)
349
        by_path[path] = file_id 
350
351
    # Get a list in insertion order
352
    new_inventory_list = new_inventory.values()
353
    mutter ("""Inventory regeneration:
354
old length: %i insertions: %i deletions: %i new_length: %i"""\
355
        % (len(old_entries), insertions, deletions, len(new_inventory_list)))
356
    assert len(new_inventory_list) == len(old_entries) + insertions - deletions
357
    new_inventory_list.sort()
358
    return new_inventory_list
974.1.9 by Aaron Bentley
Added merge-type parameter to merge.
359
360
merge_types = {     "merge3": (ApplyMerge3, "Native diff3-style merge"), 
361
                     "diff3": (Diff3Merge,  "Merge using external diff3")
362
              }
363