/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
1
from bzrlib.merge_core import merge_flex, ApplyMerge3, BackupBeforeChange
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
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
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
974.1.32 by aaron.bentley at utoronto
Made merge do greedy fetching.
13
from fetch import greedy_fetch
493 by Martin Pool
- Merge aaron's merge command
14
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
15
16
# comments from abentley on irc: merge happens in two stages, each
17
# of which generates a changeset object
18
19
# stage 1: generate OLD->OTHER,
20
# stage 2: use MINE and OLD->OTHER to generate MINE -> RESULT
21
622 by Martin Pool
Updated merge patch from Aaron
22
class UnrelatedBranches(BzrCommandError):
23
    def __init__(self):
24
        msg = "Branches have no common ancestor, and no base revision"\
25
            " specified."
26
        BzrCommandError.__init__(self, msg)
27
28
493 by Martin Pool
- Merge aaron's merge command
29
class MergeConflictHandler(ExceptionConflictHandler):
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
30
    """Handle conflicts encountered while merging.
31
32
    This subclasses ExceptionConflictHandler, so that any types of
33
    conflict that are not explicitly handled cause an exception and
34
    terminate the merge.
35
    """
622 by Martin Pool
Updated merge patch from Aaron
36
    def __init__(self, dir, ignore_zero=False):
37
        ExceptionConflictHandler.__init__(self, dir)
38
        self.conflicts = 0
39
        self.ignore_zero = ignore_zero
40
493 by Martin Pool
- Merge aaron's merge command
41
    def copy(self, source, dest):
42
        """Copy the text and mode of a file
43
        :param source: The path of the file to copy
44
        :param dest: The distination file to create
45
        """
46
        s_file = file(source, "rb")
47
        d_file = file(dest, "wb")
48
        for line in s_file:
49
            d_file.write(line)
50
        os.chmod(dest, 0777 & os.stat(source).st_mode)
51
974.1.23 by Aaron Bentley
Avoided unnecessary temp files
52
    def dump(self, lines, dest):
53
        """Copy the text and mode of a file
54
        :param source: The path of the file to copy
55
        :param dest: The distination file to create
56
        """
57
        d_file = file(dest, "wb")
58
        for line in lines:
59
            d_file.write(line)
60
493 by Martin Pool
- Merge aaron's merge command
61
    def add_suffix(self, name, suffix, last_new_name=None):
62
        """Rename a file to append a suffix.  If the new name exists, the
63
        suffix is added repeatedly until a non-existant name is found
64
65
        :param name: The path of the file
66
        :param suffix: The suffix to append
67
        :param last_new_name: (used for recursive calls) the last name tried
68
        """
69
        if last_new_name is None:
70
            last_new_name = name
71
        new_name = last_new_name+suffix
72
        try:
73
            os.rename(name, new_name)
622 by Martin Pool
Updated merge patch from Aaron
74
            return new_name
493 by Martin Pool
- Merge aaron's merge command
75
        except OSError, e:
76
            if e.errno != errno.EEXIST and e.errno != errno.ENOTEMPTY:
77
                raise
622 by Martin Pool
Updated merge patch from Aaron
78
            return self.add_suffix(name, suffix, last_new_name=new_name)
79
80
    def conflict(self, text):
81
        warning(text)
82
        self.conflicts += 1
83
        
493 by Martin Pool
- Merge aaron's merge command
84
974.1.23 by Aaron Bentley
Avoided unnecessary temp files
85
    def merge_conflict(self, new_file, this_path, base_lines, other_lines):
493 by Martin Pool
- Merge aaron's merge command
86
        """
87
        Handle diff3 conflicts by producing a .THIS, .BASE and .OTHER.  The
88
        main file will be a version with diff3 conflicts.
89
        :param new_file: Path to the output file with diff3 markers
90
        :param this_path: Path to the file text for the THIS tree
91
        :param base_path: Path to the file text for the BASE tree
92
        :param other_path: Path to the file text for the OTHER tree
93
        """
94
        self.add_suffix(this_path, ".THIS")
974.1.23 by Aaron Bentley
Avoided unnecessary temp files
95
        self.dump(base_lines, this_path+".BASE")
96
        self.dump(other_lines, this_path+".OTHER")
493 by Martin Pool
- Merge aaron's merge command
97
        os.rename(new_file, this_path)
622 by Martin Pool
Updated merge patch from Aaron
98
        self.conflict("Diff3 conflict encountered in %s" % this_path)
493 by Martin Pool
- Merge aaron's merge command
99
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
100
    def new_contents_conflict(self, filename, other_contents):
101
        """Conflicting contents for newly added file."""
102
        self.copy(other_contents, filename + ".OTHER")
103
        self.conflict("Conflict in newly added file %s" % filename)
104
    
105
493 by Martin Pool
- Merge aaron's merge command
106
    def target_exists(self, entry, target, old_path):
107
        """Handle the case when the target file or dir exists"""
622 by Martin Pool
Updated merge patch from Aaron
108
        moved_path = self.add_suffix(target, ".moved")
109
        self.conflict("Moved existing %s to %s" % (target, moved_path))
110
850 by Martin Pool
- Merge merge updates from aaron
111
    def rmdir_non_empty(self, filename):
112
        """Handle the case where the dir to be removed still has contents"""
113
        self.conflict("Directory %s not removed because it is not empty"\
114
            % filename)
115
        return "skip"
116
622 by Martin Pool
Updated merge patch from Aaron
117
    def finalize(self):
118
        if not self.ignore_zero:
119
            print "%d conflicts encountered.\n" % self.conflicts
493 by Martin Pool
- Merge aaron's merge command
120
            
974.1.32 by aaron.bentley at utoronto
Made merge do greedy fetching.
121
def get_tree(treespec, temp_root, label, local_branch=None):
622 by Martin Pool
Updated merge patch from Aaron
122
    location, revno = treespec
123
    branch = find_branch(location)
493 by Martin Pool
- Merge aaron's merge command
124
    if revno is None:
974.1.36 by aaron.bentley at utoronto
Committed it even though the test case doesn't work
125
        revision = None
126
    elif revno == -1:
127
        revision = branch.last_patch()
128
    else:
129
        revision = branch.lookup_revision(revno)
130
    return branch, get_revid_tree(branch, revision, temp_root, label,
131
                                  local_branch)
132
133
def get_revid_tree(branch, revision, temp_root, label, local_branch):
134
    if revision is None:
493 by Martin Pool
- Merge aaron's merge command
135
        base_tree = branch.working_tree()
136
    else:
974.1.32 by aaron.bentley at utoronto
Made merge do greedy fetching.
137
        if local_branch is not None:
974.1.36 by aaron.bentley at utoronto
Committed it even though the test case doesn't work
138
            greedy_fetch(local_branch, branch, revision)
974.1.32 by aaron.bentley at utoronto
Made merge do greedy fetching.
139
            base_tree = local_branch.revision_tree(revision)
140
        else:
141
            base_tree = branch.revision_tree(revision)
493 by Martin Pool
- Merge aaron's merge command
142
    temp_path = os.path.join(temp_root, label)
143
    os.mkdir(temp_path)
974.1.36 by aaron.bentley at utoronto
Committed it even though the test case doesn't work
144
    return MergeTree(base_tree, temp_path)
493 by Martin Pool
- Merge aaron's merge command
145
146
147
def file_exists(tree, file_id):
148
    return tree.has_filename(tree.id2path(file_id))
149
    
150
151
class MergeTree(object):
152
    def __init__(self, tree, tempdir):
153
        object.__init__(self)
154
        if hasattr(tree, "basedir"):
155
            self.root = tree.basedir
156
        else:
157
            self.root = None
158
        self.tree = tree
159
        self.tempdir = tempdir
160
        os.mkdir(os.path.join(self.tempdir, "texts"))
161
        self.cached = {}
162
974.1.19 by Aaron Bentley
Removed MergeTree.inventory
163
    def __iter__(self):
164
        return self.tree.__iter__()
165
974.1.12 by aaron.bentley at utoronto
Switched from text-id to hashcache for merge optimization
166
    def __contains__(self, file_id):
974.1.14 by aaron.bentley at utoronto
Fixed bugs in merge optimization
167
        return file_id in self.tree
974.1.12 by aaron.bentley at utoronto
Switched from text-id to hashcache for merge optimization
168
974.1.23 by Aaron Bentley
Avoided unnecessary temp files
169
    def get_file(self, file_id):
170
        return self.tree.get_file(file_id)
171
974.1.12 by aaron.bentley at utoronto
Switched from text-id to hashcache for merge optimization
172
    def get_file_sha1(self, id):
173
        return self.tree.get_file_sha1(id)
174
974.1.18 by Aaron Bentley
Stopped using SourceFile in inventory
175
    def id2path(self, file_id):
176
        return self.tree.id2path(file_id)
177
178
    def has_id(self, file_id):
179
        return self.tree.has_id(file_id)
180
493 by Martin Pool
- Merge aaron's merge command
181
    def readonly_path(self, id):
850 by Martin Pool
- Merge merge updates from aaron
182
        if id not in self.tree:
183
            return None
493 by Martin Pool
- Merge aaron's merge command
184
        if self.root is not None:
185
            return self.tree.abspath(self.tree.id2path(id))
186
        else:
187
            if self.tree.inventory[id].kind in ("directory", "root_directory"):
188
                return self.tempdir
189
            if not self.cached.has_key(id):
190
                path = os.path.join(self.tempdir, "texts", id)
191
                outfile = file(path, "wb")
192
                outfile.write(self.tree.get_file(id).read())
193
                assert(os.path.exists(path))
194
                self.cached[id] = path
195
            return self.cached[id]
196
628 by Martin Pool
- merge aaron's updated merge/pull code
197
198
199
def merge(other_revision, base_revision,
200
          check_clean=True, ignore_zero=False,
974.1.10 by aaron.bentley at utoronto
Added file-selection to merge-revert
201
          this_dir=None, backup_files=False, merge_type=ApplyMerge3,
202
          file_list=None):
628 by Martin Pool
- merge aaron's updated merge/pull code
203
    """Merge changes into a tree.
204
205
    base_revision
206
        Base for three-way merge.
207
    other_revision
208
        Other revision for three-way merge.
209
    this_dir
210
        Directory to merge changes into; '.' by default.
211
    check_clean
212
        If true, this_dir must have no uncommitted changes before the
213
        merge begins.
974.1.32 by aaron.bentley at utoronto
Made merge do greedy fetching.
214
    all available ancestors of other_revision and base_revision are
215
    automatically pulled into the branch.
628 by Martin Pool
- merge aaron's updated merge/pull code
216
    """
974.1.37 by aaron.bentley at utoronto
pending merges, common ancestor work properly
217
    from bzrlib.revision import common_ancestor, is_ancestor
218
    from bzrlib.revision import MultipleRevisionSources
974.1.36 by aaron.bentley at utoronto
Committed it even though the test case doesn't work
219
    from bzrlib.errors import NoSuchRevision
493 by Martin Pool
- Merge aaron's merge command
220
    tempdir = tempfile.mkdtemp(prefix="bzr-")
221
    try:
628 by Martin Pool
- merge aaron's updated merge/pull code
222
        if this_dir is None:
223
            this_dir = '.'
224
        this_branch = find_branch(this_dir)
974.1.36 by aaron.bentley at utoronto
Committed it even though the test case doesn't work
225
        this_rev_id = this_branch.last_patch()
226
        if this_rev_id is None:
227
            raise BzrCommandError("This branch has no commits")
628 by Martin Pool
- merge aaron's updated merge/pull code
228
        if check_clean:
622 by Martin Pool
Updated merge patch from Aaron
229
            changes = compare_trees(this_branch.working_tree(), 
230
                                    this_branch.basis_tree(), False)
231
            if changes.has_changed():
232
                raise BzrCommandError("Working tree has uncommitted changes.")
974.1.32 by aaron.bentley at utoronto
Made merge do greedy fetching.
233
        other_branch, other_tree = get_tree(other_revision, tempdir, "other",
234
                                            this_branch)
974.1.36 by aaron.bentley at utoronto
Committed it even though the test case doesn't work
235
        if other_revision[1] == -1:
236
            other_rev_id = other_branch.last_patch()
237
            other_basis = other_rev_id
238
        elif other_revision[1] is not None:
239
            other_rev_id = other_branch.lookup_revision(other_revision[1])
240
            other_basis = other_rev_id
241
        else:
242
            other_rev_id = None
243
            other_basis = other_branch.last_patch()
622 by Martin Pool
Updated merge patch from Aaron
244
        if base_revision == [None, None]:
974.1.37 by aaron.bentley at utoronto
pending merges, common ancestor work properly
245
            base_rev_id = common_ancestor(this_rev_id, other_basis, 
246
                                          this_branch)
247
            if base_rev_id is None:
622 by Martin Pool
Updated merge patch from Aaron
248
                raise UnrelatedBranches()
974.1.37 by aaron.bentley at utoronto
pending merges, common ancestor work properly
249
            base_tree = get_revid_tree(this_branch, base_rev_id, tempdir, 
250
                                       "base", None)
974.1.36 by aaron.bentley at utoronto
Committed it even though the test case doesn't work
251
            base_is_ancestor = True
252
        else:
253
            base_branch, base_tree = get_tree(base_revision, tempdir, "base")
254
            if base_revision[1] == -1:
255
                base_rev_id = base_branch.last_patch()
256
            elif base_revision[1] is None:
257
                base_rev_id = None
258
            else:
259
                base_rev_id = base_branch.lookup_revision(base_revision[1])
260
            if base_rev_id is not None:
261
                base_is_ancestor = is_ancestor(this_rev_id, base_rev_id, 
262
                                               MultipleRevisionSources(
263
                                               this_branch, 
264
                                               base_branch))
265
            else:
266
                base_is_ancestor = False
974.1.10 by aaron.bentley at utoronto
Added file-selection to merge-revert
267
        if file_list is None:
268
            interesting_ids = None
269
        else:
270
            interesting_ids = set()
271
            this_tree = this_branch.working_tree()
272
            for fname in file_list:
273
                path = this_branch.relpath(fname)
274
                found_id = False
275
                for tree in (this_tree, base_tree.tree, other_tree.tree):
276
                    file_id = tree.inventory.path2id(path)
277
                    if file_id is not None:
278
                        interesting_ids.add(file_id)
279
                        found_id = True
280
                if not found_id:
281
                    raise BzrCommandError("%s is not a source file in any"
282
                                          " tree." % fname)
622 by Martin Pool
Updated merge patch from Aaron
283
        merge_inner(this_branch, other_tree, base_tree, tempdir, 
974.1.9 by Aaron Bentley
Added merge-type parameter to merge.
284
                    ignore_zero=ignore_zero, backup_files=backup_files, 
974.1.10 by aaron.bentley at utoronto
Added file-selection to merge-revert
285
                    merge_type=merge_type, interesting_ids=interesting_ids)
974.1.36 by aaron.bentley at utoronto
Committed it even though the test case doesn't work
286
        if base_is_ancestor and other_rev_id is not None:
287
            this_branch.add_pending_merge(other_rev_id)
493 by Martin Pool
- Merge aaron's merge command
288
    finally:
289
        shutil.rmtree(tempdir)
290
291
974.1.10 by aaron.bentley at utoronto
Added file-selection to merge-revert
292
def set_interesting(inventory_a, inventory_b, interesting_ids):
293
    """Mark files whose ids are in interesting_ids as interesting
294
    """
295
    for inventory in (inventory_a, inventory_b):
296
        for path, source_file in inventory.iteritems():
297
             source_file.interesting = source_file.id in interesting_ids
298
299
974.1.19 by Aaron Bentley
Removed MergeTree.inventory
300
def generate_cset_optimized(tree_a, tree_b, interesting_ids=None):
974.1.12 by aaron.bentley at utoronto
Switched from text-id to hashcache for merge optimization
301
    """Generate a changeset.  If interesting_ids is supplied, only changes
302
    to those files will be shown.  Metadata changes are stripped.
974.1.10 by aaron.bentley at utoronto
Added file-selection to merge-revert
303
    """ 
974.1.19 by Aaron Bentley
Removed MergeTree.inventory
304
    cset =  generate_changeset(tree_a, tree_b, interesting_ids)
493 by Martin Pool
- Merge aaron's merge command
305
    for entry in cset.entries.itervalues():
306
        entry.metadata_change = None
307
    return cset
308
309
622 by Martin Pool
Updated merge patch from Aaron
310
def merge_inner(this_branch, other_tree, base_tree, tempdir, 
974.1.10 by aaron.bentley at utoronto
Added file-selection to merge-revert
311
                ignore_zero=False, merge_type=ApplyMerge3, backup_files=False,
312
                interesting_ids=None):
974.1.4 by Aaron Bentley
Implemented merge3 as the default text merge
313
974.1.23 by Aaron Bentley
Avoided unnecessary temp files
314
    def merge_factory(file_id, base, other):
315
        contents_change = merge_type(file_id, base, other)
974.1.8 by Aaron Bentley
Added default backups for merge-revert
316
        if backup_files:
317
            contents_change = BackupBeforeChange(contents_change)
318
        return contents_change
974.1.4 by Aaron Bentley
Implemented merge3 as the default text merge
319
628 by Martin Pool
- merge aaron's updated merge/pull code
320
    this_tree = get_tree((this_branch.base, None), tempdir, "this")[1]
493 by Martin Pool
- Merge aaron's merge command
321
322
    def get_inventory(tree):
974.1.19 by Aaron Bentley
Removed MergeTree.inventory
323
        return tree.tree.inventory
493 by Martin Pool
- Merge aaron's merge command
324
325
    inv_changes = merge_flex(this_tree, base_tree, other_tree,
974.1.17 by Aaron Bentley
Switched to using a set of interesting file_ids instead of SourceFile attribute
326
                             generate_cset_optimized, get_inventory,
622 by Martin Pool
Updated merge patch from Aaron
327
                             MergeConflictHandler(base_tree.root,
974.1.3 by Aaron Bentley
Added merge_factory parameter to merge_flex
328
                                                  ignore_zero=ignore_zero),
974.1.17 by Aaron Bentley
Switched to using a set of interesting file_ids instead of SourceFile attribute
329
                             merge_factory=merge_factory, 
330
                             interesting_ids=interesting_ids)
493 by Martin Pool
- Merge aaron's merge command
331
332
    adjust_ids = []
333
    for id, path in inv_changes.iteritems():
334
        if path is not None:
335
            if path == '.':
336
                path = ''
337
            else:
974.1.19 by Aaron Bentley
Removed MergeTree.inventory
338
                assert path.startswith('./'), "path is %s" % path
493 by Martin Pool
- Merge aaron's merge command
339
            path = path[2:]
340
        adjust_ids.append((path, id))
974.1.13 by aaron.bentley at utoronto
Avoid rewriting inventory when not necessary
341
    if len(adjust_ids) > 0:
342
        this_branch.set_inventory(regen_inventory(this_branch, this_tree.root,
343
                                                  adjust_ids))
493 by Martin Pool
- Merge aaron's merge command
344
345
346
def regen_inventory(this_branch, root, new_entries):
347
    old_entries = this_branch.read_working_inventory()
348
    new_inventory = {}
349
    by_path = {}
974.1.21 by aaron.bentley at utoronto
Handled path generation properly
350
    new_entries_map = {} 
351
    for path, file_id in new_entries:
352
        if path is None:
353
            continue
354
        new_entries_map[file_id] = path
355
356
    def id2path(file_id):
357
        path = new_entries_map.get(file_id)
358
        if path is not None:
359
            return path
360
        entry = old_entries[file_id]
361
        if entry.parent_id is None:
362
            return entry.name
363
        return os.path.join(id2path(entry.parent_id), entry.name)
364
        
493 by Martin Pool
- Merge aaron's merge command
365
    for file_id in old_entries:
366
        entry = old_entries[file_id]
974.1.21 by aaron.bentley at utoronto
Handled path generation properly
367
        path = id2path(file_id)
493 by Martin Pool
- Merge aaron's merge command
368
        new_inventory[file_id] = (path, file_id, entry.parent_id, entry.kind)
369
        by_path[path] = file_id
370
    
371
    deletions = 0
372
    insertions = 0
373
    new_path_list = []
374
    for path, file_id in new_entries:
375
        if path is None:
376
            del new_inventory[file_id]
377
            deletions += 1
378
        else:
379
            new_path_list.append((path, file_id))
380
            if file_id not in old_entries:
381
                insertions += 1
382
    # Ensure no file is added before its parent
383
    new_path_list.sort()
384
    for path, file_id in new_path_list:
385
        if path == '':
386
            parent = None
387
        else:
388
            parent = by_path[os.path.dirname(path)]
389
        kind = bzrlib.osutils.file_kind(os.path.join(root, path))
390
        new_inventory[file_id] = (path, file_id, parent, kind)
391
        by_path[path] = file_id 
392
393
    # Get a list in insertion order
394
    new_inventory_list = new_inventory.values()
395
    mutter ("""Inventory regeneration:
396
old length: %i insertions: %i deletions: %i new_length: %i"""\
397
        % (len(old_entries), insertions, deletions, len(new_inventory_list)))
398
    assert len(new_inventory_list) == len(old_entries) + insertions - deletions
399
    new_inventory_list.sort()
400
    return new_inventory_list
974.1.9 by Aaron Bentley
Added merge-type parameter to merge.
401
402
merge_types = {     "merge3": (ApplyMerge3, "Native diff3-style merge"), 
403
                     "diff3": (Diff3Merge,  "Merge using external diff3")
404
              }
405