/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
1049 by Martin Pool
- add better handler for new_contents_conflict collision
90
    def new_contents_conflict(self, filename, other_contents):
91
        """Conflicting contents for newly added file."""
92
        self.copy(other_contents, filename + ".OTHER")
93
        self.conflict("Conflict in newly added file %s" % filename)
94
    
95
493 by Martin Pool
- Merge aaron's merge command
96
    def target_exists(self, entry, target, old_path):
97
        """Handle the case when the target file or dir exists"""
622 by Martin Pool
Updated merge patch from Aaron
98
        moved_path = self.add_suffix(target, ".moved")
99
        self.conflict("Moved existing %s to %s" % (target, moved_path))
100
850 by Martin Pool
- Merge merge updates from aaron
101
    def rmdir_non_empty(self, filename):
102
        """Handle the case where the dir to be removed still has contents"""
103
        self.conflict("Directory %s not removed because it is not empty"\
104
            % filename)
105
        return "skip"
106
622 by Martin Pool
Updated merge patch from Aaron
107
    def finalize(self):
108
        if not self.ignore_zero:
109
            print "%d conflicts encountered.\n" % self.conflicts
493 by Martin Pool
- Merge aaron's merge command
110
            
558 by Martin Pool
- All top-level classes inherit from object
111
class SourceFile(object):
493 by Martin Pool
- Merge aaron's merge command
112
    def __init__(self, path, id, present=None, isdir=None):
113
        self.path = path
114
        self.id = id
115
        self.present = present
116
        self.isdir = isdir
117
        self.interesting = True
118
119
    def __repr__(self):
120
        return "SourceFile(%s, %s)" % (self.path, self.id)
121
122
def get_tree(treespec, temp_root, label):
622 by Martin Pool
Updated merge patch from Aaron
123
    location, revno = treespec
124
    branch = find_branch(location)
493 by Martin Pool
- Merge aaron's merge command
125
    if revno is None:
126
        base_tree = branch.working_tree()
127
    elif revno == -1:
128
        base_tree = branch.basis_tree()
129
    else:
130
        base_tree = branch.revision_tree(branch.lookup_revision(revno))
131
    temp_path = os.path.join(temp_root, label)
132
    os.mkdir(temp_path)
622 by Martin Pool
Updated merge patch from Aaron
133
    return branch, MergeTree(base_tree, temp_path)
493 by Martin Pool
- Merge aaron's merge command
134
135
136
def abspath(tree, file_id):
137
    path = tree.inventory.id2path(file_id)
138
    if path == "":
139
        return "./."
140
    return "./" + path
141
142
def file_exists(tree, file_id):
143
    return tree.has_filename(tree.id2path(file_id))
144
    
145
def inventory_map(tree):
146
    inventory = {}
147
    for file_id in tree.inventory:
148
        path = abspath(tree, file_id)
149
        inventory[path] = SourceFile(path, file_id)
150
    return inventory
151
152
153
class MergeTree(object):
154
    def __init__(self, tree, tempdir):
155
        object.__init__(self)
156
        if hasattr(tree, "basedir"):
157
            self.root = tree.basedir
158
        else:
159
            self.root = None
160
        self.inventory = inventory_map(tree)
161
        self.tree = tree
162
        self.tempdir = tempdir
163
        os.mkdir(os.path.join(self.tempdir, "texts"))
164
        self.cached = {}
165
166
    def readonly_path(self, id):
850 by Martin Pool
- Merge merge updates from aaron
167
        if id not in self.tree:
168
            return None
493 by Martin Pool
- Merge aaron's merge command
169
        if self.root is not None:
170
            return self.tree.abspath(self.tree.id2path(id))
171
        else:
172
            if self.tree.inventory[id].kind in ("directory", "root_directory"):
173
                return self.tempdir
174
            if not self.cached.has_key(id):
175
                path = os.path.join(self.tempdir, "texts", id)
176
                outfile = file(path, "wb")
177
                outfile.write(self.tree.get_file(id).read())
178
                assert(os.path.exists(path))
179
                self.cached[id] = path
180
            return self.cached[id]
181
628 by Martin Pool
- merge aaron's updated merge/pull code
182
183
184
def merge(other_revision, base_revision,
185
          check_clean=True, ignore_zero=False,
974.1.10 by aaron.bentley at utoronto
Added file-selection to merge-revert
186
          this_dir=None, backup_files=False, merge_type=ApplyMerge3,
187
          file_list=None):
628 by Martin Pool
- merge aaron's updated merge/pull code
188
    """Merge changes into a tree.
189
190
    base_revision
191
        Base for three-way merge.
192
    other_revision
193
        Other revision for three-way merge.
194
    this_dir
195
        Directory to merge changes into; '.' by default.
196
    check_clean
197
        If true, this_dir must have no uncommitted changes before the
198
        merge begins.
199
    """
493 by Martin Pool
- Merge aaron's merge command
200
    tempdir = tempfile.mkdtemp(prefix="bzr-")
201
    try:
628 by Martin Pool
- merge aaron's updated merge/pull code
202
        if this_dir is None:
203
            this_dir = '.'
204
        this_branch = find_branch(this_dir)
205
        if check_clean:
622 by Martin Pool
Updated merge patch from Aaron
206
            changes = compare_trees(this_branch.working_tree(), 
207
                                    this_branch.basis_tree(), False)
208
            if changes.has_changed():
209
                raise BzrCommandError("Working tree has uncommitted changes.")
210
        other_branch, other_tree = get_tree(other_revision, tempdir, "other")
211
        if base_revision == [None, None]:
212
            if other_revision[1] == -1:
213
                o_revno = None
214
            else:
215
                o_revno = other_revision[1]
216
            base_revno = this_branch.common_ancestor(other_branch, 
217
                                                     other_revno=o_revno)[0]
218
            if base_revno is None:
219
                raise UnrelatedBranches()
220
            base_revision = ['.', base_revno]
221
        base_branch, base_tree = get_tree(base_revision, tempdir, "base")
974.1.10 by aaron.bentley at utoronto
Added file-selection to merge-revert
222
        if file_list is None:
223
            interesting_ids = None
224
        else:
225
            interesting_ids = set()
226
            this_tree = this_branch.working_tree()
227
            for fname in file_list:
228
                path = this_branch.relpath(fname)
229
                found_id = False
230
                for tree in (this_tree, base_tree.tree, other_tree.tree):
231
                    file_id = tree.inventory.path2id(path)
232
                    if file_id is not None:
233
                        interesting_ids.add(file_id)
234
                        found_id = True
235
                if not found_id:
236
                    raise BzrCommandError("%s is not a source file in any"
237
                                          " tree." % fname)
622 by Martin Pool
Updated merge patch from Aaron
238
        merge_inner(this_branch, other_tree, base_tree, tempdir, 
974.1.9 by Aaron Bentley
Added merge-type parameter to merge.
239
                    ignore_zero=ignore_zero, backup_files=backup_files, 
974.1.10 by aaron.bentley at utoronto
Added file-selection to merge-revert
240
                    merge_type=merge_type, interesting_ids=interesting_ids)
493 by Martin Pool
- Merge aaron's merge command
241
    finally:
242
        shutil.rmtree(tempdir)
243
244
974.1.10 by aaron.bentley at utoronto
Added file-selection to merge-revert
245
def set_interesting(inventory_a, inventory_b, interesting_ids):
246
    """Mark files whose ids are in interesting_ids as interesting
247
    """
248
    for inventory in (inventory_a, inventory_b):
249
        for path, source_file in inventory.iteritems():
250
             source_file.interesting = source_file.id in interesting_ids
251
252
253
def set_optimized(tree_a, tree_b, inventory_a, inventory_b):
254
    """Mark files that have changed texts as interesting
255
    """
493 by Martin Pool
- Merge aaron's merge command
256
    for file_id in tree_a.tree.inventory:
257
        if file_id not in tree_b.tree.inventory:
258
            continue
259
        entry_a = tree_a.tree.inventory[file_id]
260
        entry_b = tree_b.tree.inventory[file_id]
261
        if (entry_a.kind, entry_b.kind) != ("file", "file"):
262
            continue
263
        if None in (entry_a.text_id, entry_b.text_id):
264
            continue
265
        if entry_a.text_id != entry_b.text_id:
266
            continue
267
        inventory_a[abspath(tree_a.tree, file_id)].interesting = False
268
        inventory_b[abspath(tree_b.tree, file_id)].interesting = False
974.1.10 by aaron.bentley at utoronto
Added file-selection to merge-revert
269
270
271
def generate_cset_optimized(tree_a, tree_b, inventory_a, inventory_b,
272
                            interesting_ids=None):
273
    """Generate a changeset, with preprocessing to select interesting files.
274
    using the text_id to mark really-changed files.
275
    This permits blazing comparisons when text_ids are present.  It also
276
    disables metadata comparison for files with identical texts.
277
    """ 
278
    if interesting_ids is None:
279
        set_optimized(tree_a, tree_b, inventory_a, inventory_b)
280
    else:
281
        set_interesting(inventory_a, inventory_b, interesting_ids)
493 by Martin Pool
- Merge aaron's merge command
282
    cset =  generate_changeset(tree_a, tree_b, inventory_a, inventory_b)
283
    for entry in cset.entries.itervalues():
284
        entry.metadata_change = None
285
    return cset
286
287
622 by Martin Pool
Updated merge patch from Aaron
288
def merge_inner(this_branch, other_tree, base_tree, tempdir, 
974.1.10 by aaron.bentley at utoronto
Added file-selection to merge-revert
289
                ignore_zero=False, merge_type=ApplyMerge3, backup_files=False,
290
                interesting_ids=None):
974.1.4 by Aaron Bentley
Implemented merge3 as the default text merge
291
292
    def merge_factory(base_file, other_file):
974.1.9 by Aaron Bentley
Added merge-type parameter to merge.
293
        contents_change = merge_type(base_file, other_file)
974.1.8 by Aaron Bentley
Added default backups for merge-revert
294
        if backup_files:
295
            contents_change = BackupBeforeChange(contents_change)
296
        return contents_change
974.1.10 by aaron.bentley at utoronto
Added file-selection to merge-revert
297
    
298
    def generate_cset(tree_a, tree_b, inventory_a, inventory_b):
299
        return generate_cset_optimized(tree_a, tree_b, inventory_a, inventory_b,
300
                                       interesting_ids)
974.1.4 by Aaron Bentley
Implemented merge3 as the default text merge
301
628 by Martin Pool
- merge aaron's updated merge/pull code
302
    this_tree = get_tree((this_branch.base, None), tempdir, "this")[1]
493 by Martin Pool
- Merge aaron's merge command
303
304
    def get_inventory(tree):
305
        return tree.inventory
306
307
    inv_changes = merge_flex(this_tree, base_tree, other_tree,
974.1.10 by aaron.bentley at utoronto
Added file-selection to merge-revert
308
                             generate_cset, get_inventory,
622 by Martin Pool
Updated merge patch from Aaron
309
                             MergeConflictHandler(base_tree.root,
974.1.3 by Aaron Bentley
Added merge_factory parameter to merge_flex
310
                                                  ignore_zero=ignore_zero),
974.1.4 by Aaron Bentley
Implemented merge3 as the default text merge
311
                             merge_factory=merge_factory)
493 by Martin Pool
- Merge aaron's merge command
312
313
    adjust_ids = []
314
    for id, path in inv_changes.iteritems():
315
        if path is not None:
316
            if path == '.':
317
                path = ''
318
            else:
319
                assert path.startswith('./')
320
            path = path[2:]
321
        adjust_ids.append((path, id))
322
    this_branch.set_inventory(regen_inventory(this_branch, this_tree.root, adjust_ids))
323
324
325
def regen_inventory(this_branch, root, new_entries):
326
    old_entries = this_branch.read_working_inventory()
327
    new_inventory = {}
328
    by_path = {}
329
    for file_id in old_entries:
330
        entry = old_entries[file_id]
331
        path = old_entries.id2path(file_id)
332
        new_inventory[file_id] = (path, file_id, entry.parent_id, entry.kind)
333
        by_path[path] = file_id
334
    
335
    deletions = 0
336
    insertions = 0
337
    new_path_list = []
338
    for path, file_id in new_entries:
339
        if path is None:
340
            del new_inventory[file_id]
341
            deletions += 1
342
        else:
343
            new_path_list.append((path, file_id))
344
            if file_id not in old_entries:
345
                insertions += 1
346
    # Ensure no file is added before its parent
347
    new_path_list.sort()
348
    for path, file_id in new_path_list:
349
        if path == '':
350
            parent = None
351
        else:
352
            parent = by_path[os.path.dirname(path)]
353
        kind = bzrlib.osutils.file_kind(os.path.join(root, path))
354
        new_inventory[file_id] = (path, file_id, parent, kind)
355
        by_path[path] = file_id 
356
357
    # Get a list in insertion order
358
    new_inventory_list = new_inventory.values()
359
    mutter ("""Inventory regeneration:
360
old length: %i insertions: %i deletions: %i new_length: %i"""\
361
        % (len(old_entries), insertions, deletions, len(new_inventory_list)))
362
    assert len(new_inventory_list) == len(old_entries) + insertions - deletions
363
    new_inventory_list.sort()
364
    return new_inventory_list
974.1.9 by Aaron Bentley
Added merge-type parameter to merge.
365
366
merge_types = {     "merge3": (ApplyMerge3, "Native diff3-style merge"), 
367
                     "diff3": (Diff3Merge,  "Merge using external diff3")
368
              }
369