/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
1105 by Martin Pool
- expose 'find-merge-base' as a new expert command,
6
from bzrlib.errors import BzrCommandError, UnrelatedBranches
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
493 by Martin Pool
- Merge aaron's merge command
21
class MergeConflictHandler(ExceptionConflictHandler):
1045 by Martin Pool
Doc
22
    """Handle conflicts encountered while merging.
23
24
    This subclasses ExceptionConflictHandler, so that any types of
25
    conflict that are not explicitly handled cause an exception and
26
    terminate the merge.
27
    """
622 by Martin Pool
Updated merge patch from Aaron
28
    def __init__(self, dir, ignore_zero=False):
29
        ExceptionConflictHandler.__init__(self, dir)
30
        self.conflicts = 0
31
        self.ignore_zero = ignore_zero
32
493 by Martin Pool
- Merge aaron's merge command
33
    def copy(self, source, dest):
34
        """Copy the text and mode of a file
35
        :param source: The path of the file to copy
36
        :param dest: The distination file to create
37
        """
38
        s_file = file(source, "rb")
39
        d_file = file(dest, "wb")
40
        for line in s_file:
41
            d_file.write(line)
42
        os.chmod(dest, 0777 & os.stat(source).st_mode)
43
1069 by Martin Pool
- merge merge improvements from aaron
44
    def dump(self, lines, dest):
45
        """Copy the text and mode of a file
46
        :param source: The path of the file to copy
47
        :param dest: The distination file to create
48
        """
49
        d_file = file(dest, "wb")
50
        for line in lines:
51
            d_file.write(line)
52
493 by Martin Pool
- Merge aaron's merge command
53
    def add_suffix(self, name, suffix, last_new_name=None):
54
        """Rename a file to append a suffix.  If the new name exists, the
55
        suffix is added repeatedly until a non-existant name is found
56
57
        :param name: The path of the file
58
        :param suffix: The suffix to append
59
        :param last_new_name: (used for recursive calls) the last name tried
60
        """
61
        if last_new_name is None:
62
            last_new_name = name
63
        new_name = last_new_name+suffix
64
        try:
65
            os.rename(name, new_name)
622 by Martin Pool
Updated merge patch from Aaron
66
            return new_name
493 by Martin Pool
- Merge aaron's merge command
67
        except OSError, e:
68
            if e.errno != errno.EEXIST and e.errno != errno.ENOTEMPTY:
69
                raise
622 by Martin Pool
Updated merge patch from Aaron
70
            return self.add_suffix(name, suffix, last_new_name=new_name)
71
72
    def conflict(self, text):
73
        warning(text)
74
        self.conflicts += 1
75
        
493 by Martin Pool
- Merge aaron's merge command
76
1069 by Martin Pool
- merge merge improvements from aaron
77
    def merge_conflict(self, new_file, this_path, base_lines, other_lines):
493 by Martin Pool
- Merge aaron's merge command
78
        """
79
        Handle diff3 conflicts by producing a .THIS, .BASE and .OTHER.  The
80
        main file will be a version with diff3 conflicts.
81
        :param new_file: Path to the output file with diff3 markers
82
        :param this_path: Path to the file text for the THIS tree
83
        :param base_path: Path to the file text for the BASE tree
84
        :param other_path: Path to the file text for the OTHER tree
85
        """
86
        self.add_suffix(this_path, ".THIS")
1069 by Martin Pool
- merge merge improvements from aaron
87
        self.dump(base_lines, this_path+".BASE")
88
        self.dump(other_lines, this_path+".OTHER")
493 by Martin Pool
- Merge aaron's merge command
89
        os.rename(new_file, this_path)
622 by Martin Pool
Updated merge patch from Aaron
90
        self.conflict("Diff3 conflict encountered in %s" % this_path)
493 by Martin Pool
- Merge aaron's merge command
91
1049 by Martin Pool
- add better handler for new_contents_conflict collision
92
    def new_contents_conflict(self, filename, other_contents):
93
        """Conflicting contents for newly added file."""
94
        self.copy(other_contents, filename + ".OTHER")
95
        self.conflict("Conflict in newly added file %s" % filename)
96
    
97
493 by Martin Pool
- Merge aaron's merge command
98
    def target_exists(self, entry, target, old_path):
99
        """Handle the case when the target file or dir exists"""
622 by Martin Pool
Updated merge patch from Aaron
100
        moved_path = self.add_suffix(target, ".moved")
101
        self.conflict("Moved existing %s to %s" % (target, moved_path))
102
850 by Martin Pool
- Merge merge updates from aaron
103
    def rmdir_non_empty(self, filename):
104
        """Handle the case where the dir to be removed still has contents"""
105
        self.conflict("Directory %s not removed because it is not empty"\
106
            % filename)
107
        return "skip"
108
622 by Martin Pool
Updated merge patch from Aaron
109
    def finalize(self):
110
        if not self.ignore_zero:
111
            print "%d conflicts encountered.\n" % self.conflicts
493 by Martin Pool
- Merge aaron's merge command
112
            
113
def get_tree(treespec, temp_root, label):
622 by Martin Pool
Updated merge patch from Aaron
114
    location, revno = treespec
115
    branch = find_branch(location)
493 by Martin Pool
- Merge aaron's merge command
116
    if revno is None:
117
        base_tree = branch.working_tree()
118
    elif revno == -1:
119
        base_tree = branch.basis_tree()
120
    else:
121
        base_tree = branch.revision_tree(branch.lookup_revision(revno))
122
    temp_path = os.path.join(temp_root, label)
123
    os.mkdir(temp_path)
622 by Martin Pool
Updated merge patch from Aaron
124
    return branch, MergeTree(base_tree, temp_path)
493 by Martin Pool
- Merge aaron's merge command
125
126
127
def file_exists(tree, file_id):
128
    return tree.has_filename(tree.id2path(file_id))
129
    
130
131
class MergeTree(object):
132
    def __init__(self, tree, tempdir):
133
        object.__init__(self)
134
        if hasattr(tree, "basedir"):
135
            self.root = tree.basedir
136
        else:
137
            self.root = None
138
        self.tree = tree
139
        self.tempdir = tempdir
140
        os.mkdir(os.path.join(self.tempdir, "texts"))
141
        self.cached = {}
142
1069 by Martin Pool
- merge merge improvements from aaron
143
    def __iter__(self):
144
        return self.tree.__iter__()
145
1051 by Martin Pool
- merge aaron's merge improvements up to
146
    def __contains__(self, file_id):
1069 by Martin Pool
- merge merge improvements from aaron
147
        return file_id in self.tree
148
149
    def get_file(self, file_id):
150
        return self.tree.get_file(file_id)
1051 by Martin Pool
- merge aaron's merge improvements up to
151
152
    def get_file_sha1(self, id):
153
        return self.tree.get_file_sha1(id)
154
1069 by Martin Pool
- merge merge improvements from aaron
155
    def id2path(self, file_id):
156
        return self.tree.id2path(file_id)
157
158
    def has_id(self, file_id):
159
        return self.tree.has_id(file_id)
160
1094 by Martin Pool
- merge aaron's merge improvements 999..1008
161
    def has_or_had_id(self, file_id):
162
        if file_id == self.tree.inventory.root.file_id:
163
            return True
164
        return self.tree.inventory.has_id(file_id)
165
493 by Martin Pool
- Merge aaron's merge command
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
1069 by Martin Pool
- merge merge improvements from aaron
253
def generate_cset_optimized(tree_a, tree_b, interesting_ids=None):
1051 by Martin Pool
- merge aaron's merge improvements up to
254
    """Generate a changeset.  If interesting_ids is supplied, only changes
255
    to those files will be shown.  Metadata changes are stripped.
974.1.10 by aaron.bentley at utoronto
Added file-selection to merge-revert
256
    """ 
1069 by Martin Pool
- merge merge improvements from aaron
257
    cset =  generate_changeset(tree_a, tree_b, interesting_ids)
493 by Martin Pool
- Merge aaron's merge command
258
    for entry in cset.entries.itervalues():
259
        entry.metadata_change = None
260
    return cset
261
262
622 by Martin Pool
Updated merge patch from Aaron
263
def merge_inner(this_branch, other_tree, base_tree, tempdir, 
974.1.10 by aaron.bentley at utoronto
Added file-selection to merge-revert
264
                ignore_zero=False, merge_type=ApplyMerge3, backup_files=False,
265
                interesting_ids=None):
974.1.4 by Aaron Bentley
Implemented merge3 as the default text merge
266
1069 by Martin Pool
- merge merge improvements from aaron
267
    def merge_factory(file_id, base, other):
268
        contents_change = merge_type(file_id, base, other)
974.1.8 by Aaron Bentley
Added default backups for merge-revert
269
        if backup_files:
270
            contents_change = BackupBeforeChange(contents_change)
271
        return contents_change
974.1.4 by Aaron Bentley
Implemented merge3 as the default text merge
272
628 by Martin Pool
- merge aaron's updated merge/pull code
273
    this_tree = get_tree((this_branch.base, None), tempdir, "this")[1]
493 by Martin Pool
- Merge aaron's merge command
274
275
    def get_inventory(tree):
1069 by Martin Pool
- merge merge improvements from aaron
276
        return tree.tree.inventory
493 by Martin Pool
- Merge aaron's merge command
277
278
    inv_changes = merge_flex(this_tree, base_tree, other_tree,
1069 by Martin Pool
- merge merge improvements from aaron
279
                             generate_cset_optimized, get_inventory,
622 by Martin Pool
Updated merge patch from Aaron
280
                             MergeConflictHandler(base_tree.root,
974.1.3 by Aaron Bentley
Added merge_factory parameter to merge_flex
281
                                                  ignore_zero=ignore_zero),
1069 by Martin Pool
- merge merge improvements from aaron
282
                             merge_factory=merge_factory, 
283
                             interesting_ids=interesting_ids)
493 by Martin Pool
- Merge aaron's merge command
284
285
    adjust_ids = []
286
    for id, path in inv_changes.iteritems():
287
        if path is not None:
288
            if path == '.':
289
                path = ''
290
            else:
1069 by Martin Pool
- merge merge improvements from aaron
291
                assert path.startswith('./'), "path is %s" % path
493 by Martin Pool
- Merge aaron's merge command
292
            path = path[2:]
293
        adjust_ids.append((path, id))
1051 by Martin Pool
- merge aaron's merge improvements up to
294
    if len(adjust_ids) > 0:
295
        this_branch.set_inventory(regen_inventory(this_branch, this_tree.root,
296
                                                  adjust_ids))
493 by Martin Pool
- Merge aaron's merge command
297
298
299
def regen_inventory(this_branch, root, new_entries):
300
    old_entries = this_branch.read_working_inventory()
301
    new_inventory = {}
302
    by_path = {}
1069 by Martin Pool
- merge merge improvements from aaron
303
    new_entries_map = {} 
304
    for path, file_id in new_entries:
305
        if path is None:
306
            continue
307
        new_entries_map[file_id] = path
308
309
    def id2path(file_id):
310
        path = new_entries_map.get(file_id)
311
        if path is not None:
312
            return path
313
        entry = old_entries[file_id]
314
        if entry.parent_id is None:
315
            return entry.name
316
        return os.path.join(id2path(entry.parent_id), entry.name)
317
        
493 by Martin Pool
- Merge aaron's merge command
318
    for file_id in old_entries:
319
        entry = old_entries[file_id]
1069 by Martin Pool
- merge merge improvements from aaron
320
        path = id2path(file_id)
493 by Martin Pool
- Merge aaron's merge command
321
        new_inventory[file_id] = (path, file_id, entry.parent_id, entry.kind)
322
        by_path[path] = file_id
323
    
324
    deletions = 0
325
    insertions = 0
326
    new_path_list = []
327
    for path, file_id in new_entries:
328
        if path is None:
329
            del new_inventory[file_id]
330
            deletions += 1
331
        else:
332
            new_path_list.append((path, file_id))
333
            if file_id not in old_entries:
334
                insertions += 1
335
    # Ensure no file is added before its parent
336
    new_path_list.sort()
337
    for path, file_id in new_path_list:
338
        if path == '':
339
            parent = None
340
        else:
341
            parent = by_path[os.path.dirname(path)]
342
        kind = bzrlib.osutils.file_kind(os.path.join(root, path))
343
        new_inventory[file_id] = (path, file_id, parent, kind)
344
        by_path[path] = file_id 
345
346
    # Get a list in insertion order
347
    new_inventory_list = new_inventory.values()
348
    mutter ("""Inventory regeneration:
349
old length: %i insertions: %i deletions: %i new_length: %i"""\
350
        % (len(old_entries), insertions, deletions, len(new_inventory_list)))
351
    assert len(new_inventory_list) == len(old_entries) + insertions - deletions
352
    new_inventory_list.sort()
353
    return new_inventory_list
974.1.9 by Aaron Bentley
Added merge-type parameter to merge.
354
355
merge_types = {     "merge3": (ApplyMerge3, "Native diff3-style merge"), 
356
                     "diff3": (Diff3Merge,  "Merge using external diff3")
357
              }
358