/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
1069 by Martin Pool
- merge merge improvements from aaron
51
    def dump(self, lines, dest):
52
        """Copy the text and mode of a file
53
        :param source: The path of the file to copy
54
        :param dest: The distination file to create
55
        """
56
        d_file = file(dest, "wb")
57
        for line in lines:
58
            d_file.write(line)
59
493 by Martin Pool
- Merge aaron's merge command
60
    def add_suffix(self, name, suffix, last_new_name=None):
61
        """Rename a file to append a suffix.  If the new name exists, the
62
        suffix is added repeatedly until a non-existant name is found
63
64
        :param name: The path of the file
65
        :param suffix: The suffix to append
66
        :param last_new_name: (used for recursive calls) the last name tried
67
        """
68
        if last_new_name is None:
69
            last_new_name = name
70
        new_name = last_new_name+suffix
71
        try:
72
            os.rename(name, new_name)
622 by Martin Pool
Updated merge patch from Aaron
73
            return new_name
493 by Martin Pool
- Merge aaron's merge command
74
        except OSError, e:
75
            if e.errno != errno.EEXIST and e.errno != errno.ENOTEMPTY:
76
                raise
622 by Martin Pool
Updated merge patch from Aaron
77
            return self.add_suffix(name, suffix, last_new_name=new_name)
78
79
    def conflict(self, text):
80
        warning(text)
81
        self.conflicts += 1
82
        
493 by Martin Pool
- Merge aaron's merge command
83
1069 by Martin Pool
- merge merge improvements from aaron
84
    def merge_conflict(self, new_file, this_path, base_lines, other_lines):
493 by Martin Pool
- Merge aaron's merge command
85
        """
86
        Handle diff3 conflicts by producing a .THIS, .BASE and .OTHER.  The
87
        main file will be a version with diff3 conflicts.
88
        :param new_file: Path to the output file with diff3 markers
89
        :param this_path: Path to the file text for the THIS tree
90
        :param base_path: Path to the file text for the BASE tree
91
        :param other_path: Path to the file text for the OTHER tree
92
        """
93
        self.add_suffix(this_path, ".THIS")
1069 by Martin Pool
- merge merge improvements from aaron
94
        self.dump(base_lines, this_path+".BASE")
95
        self.dump(other_lines, this_path+".OTHER")
493 by Martin Pool
- Merge aaron's merge command
96
        os.rename(new_file, this_path)
622 by Martin Pool
Updated merge patch from Aaron
97
        self.conflict("Diff3 conflict encountered in %s" % this_path)
493 by Martin Pool
- Merge aaron's merge command
98
1049 by Martin Pool
- add better handler for new_contents_conflict collision
99
    def new_contents_conflict(self, filename, other_contents):
100
        """Conflicting contents for newly added file."""
101
        self.copy(other_contents, filename + ".OTHER")
102
        self.conflict("Conflict in newly added file %s" % filename)
103
    
104
493 by Martin Pool
- Merge aaron's merge command
105
    def target_exists(self, entry, target, old_path):
106
        """Handle the case when the target file or dir exists"""
622 by Martin Pool
Updated merge patch from Aaron
107
        moved_path = self.add_suffix(target, ".moved")
108
        self.conflict("Moved existing %s to %s" % (target, moved_path))
109
850 by Martin Pool
- Merge merge updates from aaron
110
    def rmdir_non_empty(self, filename):
111
        """Handle the case where the dir to be removed still has contents"""
112
        self.conflict("Directory %s not removed because it is not empty"\
113
            % filename)
114
        return "skip"
115
622 by Martin Pool
Updated merge patch from Aaron
116
    def finalize(self):
117
        if not self.ignore_zero:
118
            print "%d conflicts encountered.\n" % self.conflicts
493 by Martin Pool
- Merge aaron's merge command
119
            
120
def get_tree(treespec, temp_root, label):
622 by Martin Pool
Updated merge patch from Aaron
121
    location, revno = treespec
122
    branch = find_branch(location)
493 by Martin Pool
- Merge aaron's merge command
123
    if revno is None:
124
        base_tree = branch.working_tree()
125
    elif revno == -1:
126
        base_tree = branch.basis_tree()
127
    else:
128
        base_tree = branch.revision_tree(branch.lookup_revision(revno))
129
    temp_path = os.path.join(temp_root, label)
130
    os.mkdir(temp_path)
622 by Martin Pool
Updated merge patch from Aaron
131
    return branch, MergeTree(base_tree, temp_path)
493 by Martin Pool
- Merge aaron's merge command
132
133
134
def file_exists(tree, file_id):
135
    return tree.has_filename(tree.id2path(file_id))
136
    
137
138
class MergeTree(object):
139
    def __init__(self, tree, tempdir):
140
        object.__init__(self)
141
        if hasattr(tree, "basedir"):
142
            self.root = tree.basedir
143
        else:
144
            self.root = None
145
        self.tree = tree
146
        self.tempdir = tempdir
147
        os.mkdir(os.path.join(self.tempdir, "texts"))
148
        self.cached = {}
149
1069 by Martin Pool
- merge merge improvements from aaron
150
    def __iter__(self):
151
        return self.tree.__iter__()
152
1051 by Martin Pool
- merge aaron's merge improvements up to
153
    def __contains__(self, file_id):
1069 by Martin Pool
- merge merge improvements from aaron
154
        return file_id in self.tree
155
156
    def get_file(self, file_id):
157
        return self.tree.get_file(file_id)
1051 by Martin Pool
- merge aaron's merge improvements up to
158
159
    def get_file_sha1(self, id):
160
        return self.tree.get_file_sha1(id)
161
1069 by Martin Pool
- merge merge improvements from aaron
162
    def id2path(self, file_id):
163
        return self.tree.id2path(file_id)
164
165
    def has_id(self, file_id):
166
        return self.tree.has_id(file_id)
167
1094 by Martin Pool
- merge aaron's merge improvements 999..1008
168
    def has_or_had_id(self, file_id):
169
        if file_id == self.tree.inventory.root.file_id:
170
            return True
171
        return self.tree.inventory.has_id(file_id)
172
493 by Martin Pool
- Merge aaron's merge command
173
    def readonly_path(self, id):
850 by Martin Pool
- Merge merge updates from aaron
174
        if id not in self.tree:
175
            return None
493 by Martin Pool
- Merge aaron's merge command
176
        if self.root is not None:
177
            return self.tree.abspath(self.tree.id2path(id))
178
        else:
179
            if self.tree.inventory[id].kind in ("directory", "root_directory"):
180
                return self.tempdir
181
            if not self.cached.has_key(id):
182
                path = os.path.join(self.tempdir, "texts", id)
183
                outfile = file(path, "wb")
184
                outfile.write(self.tree.get_file(id).read())
185
                assert(os.path.exists(path))
186
                self.cached[id] = path
187
            return self.cached[id]
188
628 by Martin Pool
- merge aaron's updated merge/pull code
189
190
191
def merge(other_revision, base_revision,
192
          check_clean=True, ignore_zero=False,
974.1.10 by aaron.bentley at utoronto
Added file-selection to merge-revert
193
          this_dir=None, backup_files=False, merge_type=ApplyMerge3,
194
          file_list=None):
628 by Martin Pool
- merge aaron's updated merge/pull code
195
    """Merge changes into a tree.
196
197
    base_revision
198
        Base for three-way merge.
199
    other_revision
200
        Other revision for three-way merge.
201
    this_dir
202
        Directory to merge changes into; '.' by default.
203
    check_clean
204
        If true, this_dir must have no uncommitted changes before the
205
        merge begins.
206
    """
493 by Martin Pool
- Merge aaron's merge command
207
    tempdir = tempfile.mkdtemp(prefix="bzr-")
208
    try:
628 by Martin Pool
- merge aaron's updated merge/pull code
209
        if this_dir is None:
210
            this_dir = '.'
211
        this_branch = find_branch(this_dir)
212
        if check_clean:
622 by Martin Pool
Updated merge patch from Aaron
213
            changes = compare_trees(this_branch.working_tree(), 
214
                                    this_branch.basis_tree(), False)
215
            if changes.has_changed():
216
                raise BzrCommandError("Working tree has uncommitted changes.")
217
        other_branch, other_tree = get_tree(other_revision, tempdir, "other")
218
        if base_revision == [None, None]:
219
            if other_revision[1] == -1:
220
                o_revno = None
221
            else:
222
                o_revno = other_revision[1]
223
            base_revno = this_branch.common_ancestor(other_branch, 
224
                                                     other_revno=o_revno)[0]
225
            if base_revno is None:
226
                raise UnrelatedBranches()
227
            base_revision = ['.', base_revno]
228
        base_branch, base_tree = get_tree(base_revision, tempdir, "base")
974.1.10 by aaron.bentley at utoronto
Added file-selection to merge-revert
229
        if file_list is None:
230
            interesting_ids = None
231
        else:
232
            interesting_ids = set()
233
            this_tree = this_branch.working_tree()
234
            for fname in file_list:
235
                path = this_branch.relpath(fname)
236
                found_id = False
237
                for tree in (this_tree, base_tree.tree, other_tree.tree):
238
                    file_id = tree.inventory.path2id(path)
239
                    if file_id is not None:
240
                        interesting_ids.add(file_id)
241
                        found_id = True
242
                if not found_id:
243
                    raise BzrCommandError("%s is not a source file in any"
244
                                          " tree." % fname)
622 by Martin Pool
Updated merge patch from Aaron
245
        merge_inner(this_branch, other_tree, base_tree, tempdir, 
974.1.9 by Aaron Bentley
Added merge-type parameter to merge.
246
                    ignore_zero=ignore_zero, backup_files=backup_files, 
974.1.10 by aaron.bentley at utoronto
Added file-selection to merge-revert
247
                    merge_type=merge_type, interesting_ids=interesting_ids)
493 by Martin Pool
- Merge aaron's merge command
248
    finally:
249
        shutil.rmtree(tempdir)
250
251
974.1.10 by aaron.bentley at utoronto
Added file-selection to merge-revert
252
def set_interesting(inventory_a, inventory_b, interesting_ids):
253
    """Mark files whose ids are in interesting_ids as interesting
254
    """
255
    for inventory in (inventory_a, inventory_b):
256
        for path, source_file in inventory.iteritems():
257
             source_file.interesting = source_file.id in interesting_ids
258
259
1069 by Martin Pool
- merge merge improvements from aaron
260
def generate_cset_optimized(tree_a, tree_b, interesting_ids=None):
1051 by Martin Pool
- merge aaron's merge improvements up to
261
    """Generate a changeset.  If interesting_ids is supplied, only changes
262
    to those files will be shown.  Metadata changes are stripped.
974.1.10 by aaron.bentley at utoronto
Added file-selection to merge-revert
263
    """ 
1069 by Martin Pool
- merge merge improvements from aaron
264
    cset =  generate_changeset(tree_a, tree_b, interesting_ids)
493 by Martin Pool
- Merge aaron's merge command
265
    for entry in cset.entries.itervalues():
266
        entry.metadata_change = None
267
    return cset
268
269
622 by Martin Pool
Updated merge patch from Aaron
270
def merge_inner(this_branch, other_tree, base_tree, tempdir, 
974.1.10 by aaron.bentley at utoronto
Added file-selection to merge-revert
271
                ignore_zero=False, merge_type=ApplyMerge3, backup_files=False,
272
                interesting_ids=None):
974.1.4 by Aaron Bentley
Implemented merge3 as the default text merge
273
1069 by Martin Pool
- merge merge improvements from aaron
274
    def merge_factory(file_id, base, other):
275
        contents_change = merge_type(file_id, base, other)
974.1.8 by Aaron Bentley
Added default backups for merge-revert
276
        if backup_files:
277
            contents_change = BackupBeforeChange(contents_change)
278
        return contents_change
974.1.4 by Aaron Bentley
Implemented merge3 as the default text merge
279
628 by Martin Pool
- merge aaron's updated merge/pull code
280
    this_tree = get_tree((this_branch.base, None), tempdir, "this")[1]
493 by Martin Pool
- Merge aaron's merge command
281
282
    def get_inventory(tree):
1069 by Martin Pool
- merge merge improvements from aaron
283
        return tree.tree.inventory
493 by Martin Pool
- Merge aaron's merge command
284
285
    inv_changes = merge_flex(this_tree, base_tree, other_tree,
1069 by Martin Pool
- merge merge improvements from aaron
286
                             generate_cset_optimized, get_inventory,
622 by Martin Pool
Updated merge patch from Aaron
287
                             MergeConflictHandler(base_tree.root,
974.1.3 by Aaron Bentley
Added merge_factory parameter to merge_flex
288
                                                  ignore_zero=ignore_zero),
1069 by Martin Pool
- merge merge improvements from aaron
289
                             merge_factory=merge_factory, 
290
                             interesting_ids=interesting_ids)
493 by Martin Pool
- Merge aaron's merge command
291
292
    adjust_ids = []
293
    for id, path in inv_changes.iteritems():
294
        if path is not None:
295
            if path == '.':
296
                path = ''
297
            else:
1069 by Martin Pool
- merge merge improvements from aaron
298
                assert path.startswith('./'), "path is %s" % path
493 by Martin Pool
- Merge aaron's merge command
299
            path = path[2:]
300
        adjust_ids.append((path, id))
1051 by Martin Pool
- merge aaron's merge improvements up to
301
    if len(adjust_ids) > 0:
302
        this_branch.set_inventory(regen_inventory(this_branch, this_tree.root,
303
                                                  adjust_ids))
493 by Martin Pool
- Merge aaron's merge command
304
305
306
def regen_inventory(this_branch, root, new_entries):
307
    old_entries = this_branch.read_working_inventory()
308
    new_inventory = {}
309
    by_path = {}
1069 by Martin Pool
- merge merge improvements from aaron
310
    new_entries_map = {} 
311
    for path, file_id in new_entries:
312
        if path is None:
313
            continue
314
        new_entries_map[file_id] = path
315
316
    def id2path(file_id):
317
        path = new_entries_map.get(file_id)
318
        if path is not None:
319
            return path
320
        entry = old_entries[file_id]
321
        if entry.parent_id is None:
322
            return entry.name
323
        return os.path.join(id2path(entry.parent_id), entry.name)
324
        
493 by Martin Pool
- Merge aaron's merge command
325
    for file_id in old_entries:
326
        entry = old_entries[file_id]
1069 by Martin Pool
- merge merge improvements from aaron
327
        path = id2path(file_id)
493 by Martin Pool
- Merge aaron's merge command
328
        new_inventory[file_id] = (path, file_id, entry.parent_id, entry.kind)
329
        by_path[path] = file_id
330
    
331
    deletions = 0
332
    insertions = 0
333
    new_path_list = []
334
    for path, file_id in new_entries:
335
        if path is None:
336
            del new_inventory[file_id]
337
            deletions += 1
338
        else:
339
            new_path_list.append((path, file_id))
340
            if file_id not in old_entries:
341
                insertions += 1
342
    # Ensure no file is added before its parent
343
    new_path_list.sort()
344
    for path, file_id in new_path_list:
345
        if path == '':
346
            parent = None
347
        else:
348
            parent = by_path[os.path.dirname(path)]
349
        kind = bzrlib.osutils.file_kind(os.path.join(root, path))
350
        new_inventory[file_id] = (path, file_id, parent, kind)
351
        by_path[path] = file_id 
352
353
    # Get a list in insertion order
354
    new_inventory_list = new_inventory.values()
355
    mutter ("""Inventory regeneration:
356
old length: %i insertions: %i deletions: %i new_length: %i"""\
357
        % (len(old_entries), insertions, deletions, len(new_inventory_list)))
358
    assert len(new_inventory_list) == len(old_entries) + insertions - deletions
359
    new_inventory_list.sort()
360
    return new_inventory_list
974.1.9 by Aaron Bentley
Added merge-type parameter to merge.
361
362
merge_types = {     "merge3": (ApplyMerge3, "Native diff3-style merge"), 
363
                     "diff3": (Diff3Merge,  "Merge using external diff3")
364
              }
365