1
# Copyright (C) 2004 Aaron Bentley <aaron.bentley@utoronto.ca>
 
 
3
#    This program is free software; you can redistribute it and/or modify
 
 
4
#    it under the terms of the GNU General Public License as published by
 
 
5
#    the Free Software Foundation; either version 2 of the License, or
 
 
6
#    (at your option) any later version.
 
 
8
#    This program is distributed in the hope that it will be useful,
 
 
9
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
 
10
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
 
11
#    GNU General Public License for more details.
 
 
13
#    You should have received a copy of the GNU General Public License
 
 
14
#    along with this program; if not, write to the Free Software
 
 
15
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 
17
"""Represent and apply a changeset.
 
 
19
Conflicts in applying a changeset are represented as exceptions.
 
 
21
This only handles the in-memory objects representing changesets, which are
 
 
22
primarily used by the merge code. 
 
 
28
from shutil import rmtree
 
 
29
from itertools import izip
 
 
31
from bzrlib.trace import mutter, warning
 
 
32
from bzrlib.osutils import rename, sha_file, pathjoin, mkdtemp
 
 
34
from bzrlib.errors import BzrCheckError
 
 
36
__docformat__ = "restructuredtext"
 
 
42
class OldFailedTreeOp(Exception):
 
 
44
        Exception.__init__(self, "bzr-tree-change contains files from a"
 
 
45
                           " previous failed merge operation.")
 
 
48
def invert_dict(dict):
 
 
50
    for (key,value) in dict.iteritems():
 
 
55
class ChangeExecFlag(object):
 
 
56
    """This is two-way change, suitable for file modification, creation,
 
 
58
    def __init__(self, old_exec_flag, new_exec_flag):
 
 
59
        self.old_exec_flag = old_exec_flag
 
 
60
        self.new_exec_flag = new_exec_flag
 
 
62
    def apply(self, filename, conflict_handler):
 
 
63
        from_exec_flag = self.old_exec_flag
 
 
64
        to_exec_flag = self.new_exec_flag
 
 
66
            current_exec_flag = bool(os.stat(filename).st_mode & 0111)
 
 
68
            if e.errno == errno.ENOENT:
 
 
69
                if conflict_handler.missing_for_exec_flag(filename) == "skip":
 
 
72
                    current_exec_flag = from_exec_flag
 
 
74
        if from_exec_flag is not None and current_exec_flag != from_exec_flag:
 
 
75
            if conflict_handler.wrong_old_exec_flag(filename,
 
 
76
                        from_exec_flag, current_exec_flag) != "continue":
 
 
79
        if to_exec_flag is not None:
 
 
80
            current_mode = os.stat(filename).st_mode
 
 
84
                to_mode = current_mode | (0100 & ~umask)
 
 
85
                # Enable x-bit for others only if they can read it.
 
 
86
                if current_mode & 0004:
 
 
87
                    to_mode |= 0001 & ~umask
 
 
88
                if current_mode & 0040:
 
 
89
                    to_mode |= 0010 & ~umask
 
 
91
                to_mode = current_mode & ~0111
 
 
93
                os.chmod(filename, to_mode)
 
 
95
                if e.errno == errno.ENOENT:
 
 
96
                    conflict_handler.missing_for_exec_flag(filename)
 
 
98
    def __eq__(self, other):
 
 
99
        return (isinstance(other, ChangeExecFlag) and
 
 
100
                self.old_exec_flag == other.old_exec_flag and
 
 
101
                self.new_exec_flag == other.new_exec_flag)
 
 
103
    def __ne__(self, other):
 
 
104
        return not (self == other)
 
 
107
def dir_create(filename, conflict_handler, reverse=False):
 
 
108
    """Creates the directory, or deletes it if reverse is true.  Intended to be
 
 
109
    used with ReplaceContents.
 
 
111
    :param filename: The name of the directory to create
 
 
113
    :param reverse: If true, delete the directory, instead
 
 
120
            if e.errno != errno.EEXIST:
 
 
122
            if conflict_handler.dir_exists(filename) == "continue":
 
 
125
            if e.errno == errno.ENOENT:
 
 
126
                if conflict_handler.missing_parent(filename)=="continue":
 
 
127
                    file(filename, "wb").write(self.contents)
 
 
132
            if e.errno != errno.ENOTEMPTY:
 
 
134
            if conflict_handler.rmdir_non_empty(filename) == "skip":
 
 
139
class SymlinkCreate(object):
 
 
140
    """Creates or deletes a symlink (for use with ReplaceContents)"""
 
 
141
    def __init__(self, contents):
 
 
144
        :param contents: The filename of the target the symlink should point to
 
 
147
        self.target = contents
 
 
150
        return "SymlinkCreate(%s)" % self.target
 
 
152
    def __call__(self, filename, conflict_handler, reverse=False):
 
 
153
        """Creates or destroys the symlink.
 
 
155
        :param filename: The name of the symlink to create
 
 
159
            assert(os.readlink(filename) == self.target)
 
 
163
                os.symlink(self.target, filename)
 
 
165
                if e.errno != errno.EEXIST:
 
 
167
                if conflict_handler.link_name_exists(filename) == "continue":
 
 
168
                    os.symlink(self.target, filename)
 
 
170
    def __eq__(self, other):
 
 
171
        if not isinstance(other, SymlinkCreate):
 
 
173
        elif self.target != other.target:
 
 
178
    def __ne__(self, other):
 
 
179
        return not (self == other)
 
 
182
class FileCreate(object):
 
 
183
    """Create or delete a file (for use with ReplaceContents)"""
 
 
184
    def __init__(self, contents):
 
 
187
        :param contents: The contents of the file to write
 
 
190
        self.contents = contents
 
 
193
        return "FileCreate(%i b)" % len(self.contents)
 
 
195
    def __eq__(self, other):
 
 
196
        if not isinstance(other, FileCreate):
 
 
198
        elif self.contents != other.contents:
 
 
203
    def __ne__(self, other):
 
 
204
        return not (self == other)
 
 
206
    def __call__(self, filename, conflict_handler, reverse=False):
 
 
207
        """Create or delete a file
 
 
209
        :param filename: The name of the file to create
 
 
211
        :param reverse: Delete the file instead of creating it
 
 
216
                file(filename, "wb").write(self.contents)
 
 
218
                if e.errno == errno.ENOENT:
 
 
219
                    if conflict_handler.missing_parent(filename)=="continue":
 
 
220
                        file(filename, "wb").write(self.contents)
 
 
226
                if (file(filename, "rb").read() != self.contents):
 
 
227
                    direction = conflict_handler.wrong_old_contents(filename,
 
 
229
                    if  direction != "continue":
 
 
233
                if e.errno != errno.ENOENT:
 
 
235
                if conflict_handler.missing_for_rm(filename, undo) == "skip":
 
 
239
class TreeFileCreate(object):
 
 
240
    """Create or delete a file (for use with ReplaceContents)"""
 
 
241
    def __init__(self, tree, file_id):
 
 
244
        :param contents: The contents of the file to write
 
 
248
        self.file_id = file_id
 
 
251
        return "TreeFileCreate(%s)" % self.file_id
 
 
253
    def __eq__(self, other):
 
 
254
        if not isinstance(other, TreeFileCreate):
 
 
256
        return self.tree.get_file_sha1(self.file_id) == \
 
 
257
            other.tree.get_file_sha1(other.file_id)
 
 
259
    def __ne__(self, other):
 
 
260
        return not (self == other)
 
 
262
    def write_file(self, filename):
 
 
263
        outfile = file(filename, "wb")
 
 
264
        for line in self.tree.get_file(self.file_id):
 
 
267
    def same_text(self, filename):
 
 
268
        in_file = file(filename, "rb")
 
 
269
        return sha_file(in_file) == self.tree.get_file_sha1(self.file_id)
 
 
271
    def __call__(self, filename, conflict_handler, reverse=False):
 
 
272
        """Create or delete a file
 
 
274
        :param filename: The name of the file to create
 
 
276
        :param reverse: Delete the file instead of creating it
 
 
281
                self.write_file(filename)
 
 
283
                if e.errno == errno.ENOENT:
 
 
284
                    if conflict_handler.missing_parent(filename)=="continue":
 
 
285
                        self.write_file(filename)
 
 
291
                if not self.same_text(filename):
 
 
292
                    direction = conflict_handler.wrong_old_contents(filename,
 
 
293
                        self.tree.get_file(self.file_id).read())
 
 
294
                    if  direction != "continue":
 
 
298
                if e.errno != errno.ENOENT:
 
 
300
                if conflict_handler.missing_for_rm(filename, undo) == "skip":
 
 
304
class ReplaceContents(object):
 
 
305
    """A contents-replacement framework.  It allows a file/directory/symlink to
 
 
306
    be created, deleted, or replaced with another file/directory/symlink.
 
 
307
    Arguments must be callable with (filename, reverse).
 
 
309
    def __init__(self, old_contents, new_contents):
 
 
312
        :param old_contents: The change to reverse apply (e.g. a deletion), \
 
 
314
        :type old_contents: `dir_create`, `SymlinkCreate`, `FileCreate`, \
 
 
316
        :param new_contents: The second change to apply (e.g. a creation), \
 
 
318
        :type new_contents: `dir_create`, `SymlinkCreate`, `FileCreate`, \
 
 
321
        self.old_contents=old_contents
 
 
322
        self.new_contents=new_contents
 
 
325
        return "ReplaceContents(%r -> %r)" % (self.old_contents,
 
 
328
    def __eq__(self, other):
 
 
329
        if not isinstance(other, ReplaceContents):
 
 
331
        elif self.old_contents != other.old_contents:
 
 
333
        elif self.new_contents != other.new_contents:
 
 
337
    def __ne__(self, other):
 
 
338
        return not (self == other)
 
 
340
    def apply(self, filename, conflict_handler):
 
 
341
        """Applies the FileReplacement to the specified filename
 
 
343
        :param filename: The name of the file to apply changes to
 
 
346
        undo = self.old_contents
 
 
347
        perform = self.new_contents
 
 
351
                mode = os.lstat(filename).st_mode
 
 
352
                if stat.S_ISLNK(mode):
 
 
355
                if e.errno != errno.ENOENT:
 
 
357
                if conflict_handler.missing_for_rm(filename, undo) == "skip":
 
 
359
            undo(filename, conflict_handler, reverse=True)
 
 
360
        if perform is not None:
 
 
361
            perform(filename, conflict_handler)
 
 
363
                os.chmod(filename, mode)
 
 
365
    def is_creation(self):
 
 
366
        return self.new_contents is not None and self.old_contents is None
 
 
368
    def is_deletion(self):
 
 
369
        return self.old_contents is not None and self.new_contents is None
 
 
372
class Diff3Merge(object):
 
 
373
    history_based = False
 
 
374
    def __init__(self, file_id, base, other):
 
 
375
        self.file_id = file_id
 
 
379
    def is_creation(self):
 
 
382
    def is_deletion(self):
 
 
385
    def __eq__(self, other):
 
 
386
        if not isinstance(other, Diff3Merge):
 
 
388
        return (self.base == other.base and 
 
 
389
                self.other == other.other and self.file_id == other.file_id)
 
 
391
    def __ne__(self, other):
 
 
392
        return not (self == other)
 
 
394
    def dump_file(self, temp_dir, name, tree):
 
 
395
        out_path = pathjoin(temp_dir, name)
 
 
396
        out_file = file(out_path, "wb")
 
 
397
        in_file = tree.get_file(self.file_id)
 
 
402
    def apply(self, filename, conflict_handler):
 
 
404
        temp_dir = mkdtemp(prefix="bzr-")
 
 
406
            new_file = filename+".new"
 
 
407
            base_file = self.dump_file(temp_dir, "base", self.base)
 
 
408
            other_file = self.dump_file(temp_dir, "other", self.other)
 
 
411
            status = bzrlib.patch.diff3(new_file, filename, base, other)
 
 
413
                os.chmod(new_file, os.stat(filename).st_mode)
 
 
414
                rename(new_file, filename)
 
 
418
                def get_lines(filename):
 
 
419
                    my_file = file(filename, "rb")
 
 
420
                    lines = my_file.readlines()
 
 
423
                base_lines = get_lines(base)
 
 
424
                other_lines = get_lines(other)
 
 
425
                conflict_handler.merge_conflict(new_file, filename, base_lines, 
 
 
432
    """Convenience function to create a directory.
 
 
434
    :return: A ReplaceContents that will create a directory
 
 
435
    :rtype: `ReplaceContents`
 
 
437
    return ReplaceContents(None, dir_create)
 
 
441
    """Convenience function to delete a directory.
 
 
443
    :return: A ReplaceContents that will delete a directory
 
 
444
    :rtype: `ReplaceContents`
 
 
446
    return ReplaceContents(dir_create, None)
 
 
449
def CreateFile(contents):
 
 
450
    """Convenience fucntion to create a file.
 
 
452
    :param contents: The contents of the file to create 
 
 
454
    :return: A ReplaceContents that will create a file 
 
 
455
    :rtype: `ReplaceContents`
 
 
457
    return ReplaceContents(None, FileCreate(contents))
 
 
460
def DeleteFile(contents):
 
 
461
    """Convenience fucntion to delete a file.
 
 
463
    :param contents: The contents of the file to delete
 
 
465
    :return: A ReplaceContents that will delete a file 
 
 
466
    :rtype: `ReplaceContents`
 
 
468
    return ReplaceContents(FileCreate(contents), None)
 
 
471
def ReplaceFileContents(old_tree, new_tree, file_id):
 
 
472
    """Convenience fucntion to replace the contents of a file.
 
 
474
    :param old_contents: The contents of the file to replace 
 
 
475
    :type old_contents: str
 
 
476
    :param new_contents: The contents to replace the file with
 
 
477
    :type new_contents: str
 
 
478
    :return: A ReplaceContents that will replace the contents of a file a file 
 
 
479
    :rtype: `ReplaceContents`
 
 
481
    return ReplaceContents(TreeFileCreate(old_tree, file_id), 
 
 
482
                           TreeFileCreate(new_tree, file_id))
 
 
485
def CreateSymlink(target):
 
 
486
    """Convenience fucntion to create a symlink.
 
 
488
    :param target: The path the link should point to
 
 
490
    :return: A ReplaceContents that will delete a file 
 
 
491
    :rtype: `ReplaceContents`
 
 
493
    return ReplaceContents(None, SymlinkCreate(target))
 
 
496
def DeleteSymlink(target):
 
 
497
    """Convenience fucntion to delete a symlink.
 
 
499
    :param target: The path the link should point to
 
 
501
    :return: A ReplaceContents that will delete a file 
 
 
502
    :rtype: `ReplaceContents`
 
 
504
    return ReplaceContents(SymlinkCreate(target), None)
 
 
507
def ChangeTarget(old_target, new_target):
 
 
508
    """Convenience fucntion to change the target of a symlink.
 
 
510
    :param old_target: The current link target
 
 
511
    :type old_target: str
 
 
512
    :param new_target: The new link target to use
 
 
513
    :type new_target: str
 
 
514
    :return: A ReplaceContents that will delete a file 
 
 
515
    :rtype: `ReplaceContents`
 
 
517
    return ReplaceContents(SymlinkCreate(old_target), SymlinkCreate(new_target))
 
 
520
class InvalidEntry(Exception):
 
 
521
    """Raise when a ChangesetEntry is invalid in some way"""
 
 
522
    def __init__(self, entry, problem):
 
 
525
        :param entry: The invalid ChangesetEntry
 
 
526
        :type entry: `ChangesetEntry`
 
 
527
        :param problem: The problem with the entry
 
 
530
        msg = "Changeset entry for %s (%s) is invalid.\n%s" % (entry.id, 
 
 
533
        Exception.__init__(self, msg)
 
 
537
class SourceRootHasName(InvalidEntry):
 
 
538
    """This changeset entry has a name other than "", but its parent is !NULL"""
 
 
539
    def __init__(self, entry, name):
 
 
542
        :param entry: The invalid ChangesetEntry
 
 
543
        :type entry: `ChangesetEntry`
 
 
544
        :param name: The name of the entry
 
 
547
        msg = 'Child of !NULL is named "%s", not "./.".' % name
 
 
548
        InvalidEntry.__init__(self, entry, msg)
 
 
551
class NullIDAssigned(InvalidEntry):
 
 
552
    """The id !NULL was assigned to a real entry"""
 
 
553
    def __init__(self, entry):
 
 
556
        :param entry: The invalid ChangesetEntry
 
 
557
        :type entry: `ChangesetEntry`
 
 
559
        msg = '"!NULL" id assigned to a file "%s".' % entry.path
 
 
560
        InvalidEntry.__init__(self, entry, msg)
 
 
563
class ParentIDIsSelf(InvalidEntry):
 
 
564
    """An entry is marked as its own parent"""
 
 
565
    def __init__(self, entry):
 
 
568
        :param entry: The invalid ChangesetEntry
 
 
569
        :type entry: `ChangesetEntry`
 
 
571
        msg = 'file %s has "%s" id for both self id and parent id.' % \
 
 
572
            (entry.path, entry.id)
 
 
573
        InvalidEntry.__init__(self, entry, msg)
 
 
576
class ChangesetEntry(object):
 
 
577
    """An entry the changeset"""
 
 
578
    def __init__(self, id, parent, path):
 
 
579
        """Constructor. Sets parent and name assuming it was not
 
 
580
        renamed/created/deleted.
 
 
581
        :param id: The id associated with the entry
 
 
582
        :param parent: The id of the parent of this entry (or !NULL if no
 
 
584
        :param path: The file path relative to the tree root of this entry
 
 
590
        self.new_parent = parent
 
 
591
        self.contents_change = None
 
 
592
        self.metadata_change = None
 
 
593
        if parent == NULL_ID and path !='./.':
 
 
594
            raise SourceRootHasName(self, path)
 
 
595
        if self.id == NULL_ID:
 
 
596
            raise NullIDAssigned(self)
 
 
597
        if self.id  == self.parent:
 
 
598
            raise ParentIDIsSelf(self)
 
 
601
        return "ChangesetEntry(%s)" % self.id
 
 
606
        if self.path is None:
 
 
608
        return os.path.dirname(self.path)
 
 
610
    def __set_dir(self, dir):
 
 
611
        self.path = pathjoin(dir, os.path.basename(self.path))
 
 
613
    dir = property(__get_dir, __set_dir)
 
 
615
    def __get_name(self):
 
 
616
        if self.path is None:
 
 
618
        return os.path.basename(self.path)
 
 
620
    def __set_name(self, name):
 
 
621
        self.path = pathjoin(os.path.dirname(self.path), name)
 
 
623
    name = property(__get_name, __set_name)
 
 
625
    def __get_new_dir(self):
 
 
626
        if self.new_path is None:
 
 
628
        return os.path.dirname(self.new_path)
 
 
630
    def __set_new_dir(self, dir):
 
 
631
        self.new_path = pathjoin(dir, os.path.basename(self.new_path))
 
 
633
    new_dir = property(__get_new_dir, __set_new_dir)
 
 
635
    def __get_new_name(self):
 
 
636
        if self.new_path is None:
 
 
638
        return os.path.basename(self.new_path)
 
 
640
    def __set_new_name(self, name):
 
 
641
        self.new_path = pathjoin(os.path.dirname(self.new_path), name)
 
 
643
    new_name = property(__get_new_name, __set_new_name)
 
 
645
    def needs_rename(self):
 
 
646
        """Determines whether the entry requires renaming.
 
 
651
        return (self.parent != self.new_parent or self.name != self.new_name)
 
 
653
    def is_deletion(self, reverse=False):
 
 
654
        """Return true if applying the entry would delete a file/directory.
 
 
656
        :param reverse: if true, the changeset is being applied in reverse
 
 
659
        return self.is_creation(not reverse)
 
 
661
    def is_creation(self, reverse=False):
 
 
662
        """Return true if applying the entry would create a file/directory.
 
 
664
        :param reverse: if true, the changeset is being applied in reverse
 
 
667
        if self.contents_change is None:
 
 
670
            return self.contents_change.is_deletion()
 
 
672
            return self.contents_change.is_creation()
 
 
674
    def is_creation_or_deletion(self):
 
 
675
        """Return true if applying the entry would create or delete a 
 
 
680
        return self.is_creation() or self.is_deletion()
 
 
682
    def get_cset_path(self, mod=False):
 
 
683
        """Determine the path of the entry according to the changeset.
 
 
685
        :param changeset: The changeset to derive the path from
 
 
686
        :type changeset: `Changeset`
 
 
687
        :param mod: If true, generate the MOD path.  Otherwise, generate the \
 
 
689
        :return: the path of the entry, or None if it did not exist in the \
 
 
691
        :rtype: str or NoneType
 
 
694
            if self.new_parent == NULL_ID:
 
 
696
            elif self.new_parent is None:
 
 
700
            if self.parent == NULL_ID:
 
 
702
            elif self.parent is None:
 
 
706
    def summarize_name(self):
 
 
707
        """Produce a one-line summary of the filename.  Indicates renames as
 
 
708
        old => new, indicates creation as None => new, indicates deletion as
 
 
713
        orig_path = self.get_cset_path(False)
 
 
714
        mod_path = self.get_cset_path(True)
 
 
715
        if orig_path and orig_path.startswith('./'):
 
 
716
            orig_path = orig_path[2:]
 
 
717
        if mod_path and mod_path.startswith('./'):
 
 
718
            mod_path = mod_path[2:]
 
 
719
        if orig_path == mod_path:
 
 
722
            return "%s => %s" % (orig_path, mod_path)
 
 
724
    def get_new_path(self, id_map, changeset):
 
 
725
        """Determine the full pathname to rename to
 
 
727
        :param id_map: The map of ids to filenames for the tree
 
 
728
        :type id_map: Dictionary
 
 
729
        :param changeset: The changeset to get data from
 
 
730
        :type changeset: `Changeset`
 
 
733
        mutter("Finding new path for %s", self.summarize_name())
 
 
734
        parent = self.new_parent
 
 
735
        to_dir = self.new_dir
 
 
737
        to_name = self.new_name
 
 
738
        from_name = self.name
 
 
743
        if parent == NULL_ID or parent is None:
 
 
745
                raise SourceRootHasName(self, to_name)
 
 
748
        parent_entry = changeset.entries.get(parent)
 
 
749
        if parent_entry is None:
 
 
750
            dir = os.path.dirname(id_map[self.id])
 
 
752
            mutter("path, new_path: %r %r", self.path, self.new_path)
 
 
753
            dir = parent_entry.get_new_path(id_map, changeset)
 
 
754
        if from_name == to_name:
 
 
755
            name = os.path.basename(id_map[self.id])
 
 
758
            assert(from_name is None or from_name == os.path.basename(id_map[self.id]))
 
 
759
        return pathjoin(dir, name)
 
 
762
        """Determines whether the entry does nothing
 
 
764
        :return: True if the entry does no renames or content changes
 
 
767
        if self.contents_change is not None:
 
 
769
        elif self.metadata_change is not None:
 
 
771
        elif self.parent != self.new_parent:
 
 
773
        elif self.name != self.new_name:
 
 
778
    def apply(self, filename, conflict_handler):
 
 
779
        """Applies the file content and/or metadata changes.
 
 
781
        :param filename: the filename of the entry
 
 
784
        if self.is_deletion() and self.metadata_change is not None:
 
 
785
            self.metadata_change.apply(filename, conflict_handler)
 
 
786
        if self.contents_change is not None:
 
 
787
            self.contents_change.apply(filename, conflict_handler)
 
 
788
        if not self.is_deletion() and self.metadata_change is not None:
 
 
789
            self.metadata_change.apply(filename, conflict_handler)
 
 
792
class IDPresent(Exception):
 
 
793
    def __init__(self, id):
 
 
794
        msg = "Cannot add entry because that id has already been used:\n%s" %\
 
 
796
        Exception.__init__(self, msg)
 
 
800
class Changeset(object):
 
 
801
    """A set of changes to apply"""
 
 
805
    def add_entry(self, entry):
 
 
806
        """Add an entry to the list of entries"""
 
 
807
        if self.entries.has_key(entry.id):
 
 
808
            raise IDPresent(entry.id)
 
 
809
        self.entries[entry.id] = entry
 
 
812
def get_rename_entries(changeset, inventory):
 
 
813
    """Return a list of entries that will be renamed.  Entries are sorted from
 
 
814
    longest to shortest source path and from shortest to longest target path.
 
 
816
    :param changeset: The changeset to look in
 
 
817
    :type changeset: `Changeset`
 
 
818
    :param inventory: The source of current tree paths for the given ids
 
 
819
    :type inventory: Dictionary
 
 
820
    :return: source entries and target entries as a tuple
 
 
823
    source_entries = [x for x in changeset.entries.itervalues() 
 
 
824
                      if x.needs_rename() or x.is_creation_or_deletion()]
 
 
825
    # these are done from longest path to shortest, to avoid deleting a
 
 
826
    # parent before its children are deleted/renamed 
 
 
827
    def longest_to_shortest(entry):
 
 
828
        path = inventory.get(entry.id)
 
 
833
    source_entries.sort(None, longest_to_shortest, True)
 
 
835
    target_entries = source_entries[:]
 
 
836
    # These are done from shortest to longest path, to avoid creating a
 
 
837
    # child before its parent has been created/renamed
 
 
838
    def shortest_to_longest(entry):
 
 
839
        path = entry.get_new_path(inventory, changeset)
 
 
844
    target_entries.sort(None, shortest_to_longest)
 
 
845
    return (source_entries, target_entries)
 
 
848
def rename_to_temp_delete(source_entries, inventory, dir, temp_dir, 
 
 
850
    """Delete and rename entries as appropriate.  Entries are renamed to temp
 
 
851
    names.  A map of id -> temp name (or None, for deletions) is returned.
 
 
853
    :param source_entries: The entries to rename and delete
 
 
854
    :type source_entries: List of `ChangesetEntry`
 
 
855
    :param inventory: The map of id -> filename in the current tree
 
 
856
    :type inventory: Dictionary
 
 
857
    :param dir: The directory to apply changes to
 
 
859
    :return: a mapping of id to temporary name
 
 
863
    for i in range(len(source_entries)):
 
 
864
        entry = source_entries[i]
 
 
865
        if entry.is_deletion():
 
 
866
            path = pathjoin(dir, inventory[entry.id])
 
 
867
            entry.apply(path, conflict_handler)
 
 
868
            temp_name[entry.id] = None
 
 
870
        elif entry.needs_rename():
 
 
871
            if entry.is_creation():
 
 
873
            to_name = pathjoin(temp_dir, str(i))
 
 
874
            src_path = inventory.get(entry.id)
 
 
875
            if src_path is not None:
 
 
876
                src_path = pathjoin(dir, src_path)
 
 
878
                    rename(src_path, to_name)
 
 
879
                    temp_name[entry.id] = to_name
 
 
881
                    if e.errno != errno.ENOENT:
 
 
883
                    if conflict_handler.missing_for_rename(src_path, to_name) \
 
 
890
def rename_to_new_create(changed_inventory, target_entries, inventory, 
 
 
891
                         changeset, dir, conflict_handler):
 
 
892
    """Rename entries with temp names to their final names, create new files.
 
 
894
    :param changed_inventory: A mapping of id to temporary name
 
 
895
    :type changed_inventory: Dictionary
 
 
896
    :param target_entries: The entries to apply changes to
 
 
897
    :type target_entries: List of `ChangesetEntry`
 
 
898
    :param changeset: The changeset to apply
 
 
899
    :type changeset: `Changeset`
 
 
900
    :param dir: The directory to apply changes to
 
 
903
    for entry in target_entries:
 
 
904
        new_tree_path = entry.get_new_path(inventory, changeset)
 
 
905
        if new_tree_path is None:
 
 
907
        new_path = pathjoin(dir, new_tree_path)
 
 
908
        old_path = changed_inventory.get(entry.id)
 
 
909
        if bzrlib.osutils.lexists(new_path):
 
 
910
            if conflict_handler.target_exists(entry, new_path, old_path) == \
 
 
913
        if entry.is_creation():
 
 
914
            entry.apply(new_path, conflict_handler)
 
 
915
            changed_inventory[entry.id] = new_tree_path
 
 
916
        elif entry.needs_rename():
 
 
917
            if entry.is_deletion():
 
 
922
                mutter('rename %s to final name %s', old_path, new_path)
 
 
923
                rename(old_path, new_path)
 
 
924
                changed_inventory[entry.id] = new_tree_path
 
 
926
                raise BzrCheckError('failed to rename %s to %s for changeset entry %s: %s'
 
 
927
                        % (old_path, new_path, entry, e))
 
 
930
class TargetExists(Exception):
 
 
931
    def __init__(self, entry, target):
 
 
932
        msg = "The path %s already exists" % target
 
 
933
        Exception.__init__(self, msg)
 
 
938
class RenameConflict(Exception):
 
 
939
    def __init__(self, id, this_name, base_name, other_name):
 
 
940
        msg = """Trees all have different names for a file
 
 
944
   id: %s""" % (this_name, base_name, other_name, id)
 
 
945
        Exception.__init__(self, msg)
 
 
946
        self.this_name = this_name
 
 
947
        self.base_name = base_name
 
 
948
        self_other_name = other_name
 
 
951
class MoveConflict(Exception):
 
 
952
    def __init__(self, id, this_parent, base_parent, other_parent):
 
 
953
        msg = """The file is in different directories in every tree
 
 
957
   id: %s""" % (this_parent, base_parent, other_parent, id)
 
 
958
        Exception.__init__(self, msg)
 
 
959
        self.this_parent = this_parent
 
 
960
        self.base_parent = base_parent
 
 
961
        self_other_parent = other_parent
 
 
964
class MergeConflict(Exception):
 
 
965
    def __init__(self, this_path):
 
 
966
        Exception.__init__(self, "Conflict applying changes to %s" % this_path)
 
 
967
        self.this_path = this_path
 
 
970
class WrongOldContents(Exception):
 
 
971
    def __init__(self, filename):
 
 
972
        msg = "Contents mismatch deleting %s" % filename
 
 
973
        self.filename = filename
 
 
974
        Exception.__init__(self, msg)
 
 
977
class WrongOldExecFlag(Exception):
 
 
978
    def __init__(self, filename, old_exec_flag, new_exec_flag):
 
 
979
        msg = "Executable flag missmatch on %s:\n" \
 
 
980
        "Expected %s, got %s." % (filename, old_exec_flag, new_exec_flag)
 
 
981
        self.filename = filename
 
 
982
        Exception.__init__(self, msg)
 
 
985
class RemoveContentsConflict(Exception):
 
 
986
    def __init__(self, filename):
 
 
987
        msg = "Conflict deleting %s, which has different contents in BASE"\
 
 
988
            " and THIS" % filename
 
 
989
        self.filename = filename
 
 
990
        Exception.__init__(self, msg)
 
 
993
class DeletingNonEmptyDirectory(Exception):
 
 
994
    def __init__(self, filename):
 
 
995
        msg = "Trying to remove dir %s while it still had files" % filename
 
 
996
        self.filename = filename
 
 
997
        Exception.__init__(self, msg)
 
 
1000
class PatchTargetMissing(Exception):
 
 
1001
    def __init__(self, filename):
 
 
1002
        msg = "Attempt to patch %s, which does not exist" % filename
 
 
1003
        Exception.__init__(self, msg)
 
 
1004
        self.filename = filename
 
 
1007
class MissingForSetExec(Exception):
 
 
1008
    def __init__(self, filename):
 
 
1009
        msg = "Attempt to change permissions on  %s, which does not exist" %\
 
 
1011
        Exception.__init__(self, msg)
 
 
1012
        self.filename = filename
 
 
1015
class MissingForRm(Exception):
 
 
1016
    def __init__(self, filename):
 
 
1017
        msg = "Attempt to remove missing path %s" % filename
 
 
1018
        Exception.__init__(self, msg)
 
 
1019
        self.filename = filename
 
 
1022
class MissingForRename(Exception):
 
 
1023
    def __init__(self, filename, to_path):
 
 
1024
        msg = "Attempt to move missing path %s to %s" % (filename, to_path)
 
 
1025
        Exception.__init__(self, msg)
 
 
1026
        self.filename = filename
 
 
1029
class NewContentsConflict(Exception):
 
 
1030
    def __init__(self, filename):
 
 
1031
        msg = "Conflicting contents for new file %s" % (filename)
 
 
1032
        Exception.__init__(self, msg)
 
 
1035
class WeaveMergeConflict(Exception):
 
 
1036
    def __init__(self, filename):
 
 
1037
        msg = "Conflicting contents for file %s" % (filename)
 
 
1038
        Exception.__init__(self, msg)
 
 
1041
class ThreewayContentsConflict(Exception):
 
 
1042
    def __init__(self, filename):
 
 
1043
        msg = "Conflicting contents for file %s" % (filename)
 
 
1044
        Exception.__init__(self, msg)
 
 
1047
class MissingForMerge(Exception):
 
 
1048
    def __init__(self, filename):
 
 
1049
        msg = "The file %s was modified, but does not exist in this tree"\
 
 
1051
        Exception.__init__(self, msg)
 
 
1054
class ExceptionConflictHandler(object):
 
 
1055
    """Default handler for merge exceptions.
 
 
1057
    This throws an error on any kind of conflict.  Conflict handlers can
 
 
1058
    descend from this class if they have a better way to handle some or
 
 
1059
    all types of conflict.
 
 
1061
    def missing_parent(self, pathname):
 
 
1062
        parent = os.path.dirname(pathname)
 
 
1063
        raise Exception("Parent directory missing for %s" % pathname)
 
 
1065
    def dir_exists(self, pathname):
 
 
1066
        raise Exception("Directory already exists for %s" % pathname)
 
 
1068
    def failed_hunks(self, pathname):
 
 
1069
        raise Exception("Failed to apply some hunks for %s" % pathname)
 
 
1071
    def target_exists(self, entry, target, old_path):
 
 
1072
        raise TargetExists(entry, target)
 
 
1074
    def rename_conflict(self, id, this_name, base_name, other_name):
 
 
1075
        raise RenameConflict(id, this_name, base_name, other_name)
 
 
1077
    def move_conflict(self, id, this_dir, base_dir, other_dir):
 
 
1078
        raise MoveConflict(id, this_dir, base_dir, other_dir)
 
 
1080
    def merge_conflict(self, new_file, this_path, base_lines, other_lines):
 
 
1082
        raise MergeConflict(this_path)
 
 
1084
    def wrong_old_contents(self, filename, expected_contents):
 
 
1085
        raise WrongOldContents(filename)
 
 
1087
    def rem_contents_conflict(self, filename, this_contents, base_contents):
 
 
1088
        raise RemoveContentsConflict(filename)
 
 
1090
    def wrong_old_exec_flag(self, filename, old_exec_flag, new_exec_flag):
 
 
1091
        raise WrongOldExecFlag(filename, old_exec_flag, new_exec_flag)
 
 
1093
    def rmdir_non_empty(self, filename):
 
 
1094
        raise DeletingNonEmptyDirectory(filename)
 
 
1096
    def link_name_exists(self, filename):
 
 
1097
        raise TargetExists(filename)
 
 
1099
    def patch_target_missing(self, filename, contents):
 
 
1100
        raise PatchTargetMissing(filename)
 
 
1102
    def missing_for_exec_flag(self, filename):
 
 
1103
        raise MissingForExecFlag(filename)
 
 
1105
    def missing_for_rm(self, filename, change):
 
 
1106
        raise MissingForRm(filename)
 
 
1108
    def missing_for_rename(self, filename, to_path):
 
 
1109
        raise MissingForRename(filename, to_path)
 
 
1111
    def missing_for_merge(self, file_id, other_path):
 
 
1112
        raise MissingForMerge(other_path)
 
 
1114
    def new_contents_conflict(self, filename, other_contents):
 
 
1115
        raise NewContentsConflict(filename)
 
 
1117
    def weave_merge_conflict(self, filename, weave, other_i, out_file):
 
 
1118
        raise WeaveMergeConflict(filename)
 
 
1120
    def threeway_contents_conflict(self, filename, this_contents,
 
 
1121
                                   base_contents, other_contents):
 
 
1122
        raise ThreewayContentsConflict(filename)
 
 
1128
def apply_changeset(changeset, inventory, dir, conflict_handler=None):
 
 
1129
    """Apply a changeset to a directory.
 
 
1131
    :param changeset: The changes to perform
 
 
1132
    :type changeset: `Changeset`
 
 
1133
    :param inventory: The mapping of id to filename for the directory
 
 
1134
    :type inventory: Dictionary
 
 
1135
    :param dir: The path of the directory to apply the changes to
 
 
1137
    :return: The mapping of the changed entries
 
 
1140
    if conflict_handler is None:
 
 
1141
        conflict_handler = ExceptionConflictHandler()
 
 
1142
    temp_dir = pathjoin(dir, "bzr-tree-change")
 
 
1146
        if e.errno == errno.EEXIST:
 
 
1150
                if e.errno == errno.ENOTEMPTY:
 
 
1151
                    raise OldFailedTreeOp()
 
 
1156
    #apply changes that don't affect filenames
 
 
1157
    for entry in changeset.entries.itervalues():
 
 
1158
        if not entry.is_creation_or_deletion() and not entry.is_boring():
 
 
1159
            if entry.id not in inventory:
 
 
1160
                warning("entry {%s} no longer present, can't be updated",
 
 
1163
            path = pathjoin(dir, inventory[entry.id])
 
 
1164
            entry.apply(path, conflict_handler)
 
 
1166
    # Apply renames in stages, to minimize conflicts:
 
 
1167
    # Only files whose name or parent change are interesting, because their
 
 
1168
    # target name may exist in the source tree.  If a directory's name changes,
 
 
1169
    # that doesn't make its children interesting.
 
 
1170
    (source_entries, target_entries) = get_rename_entries(changeset, inventory)
 
 
1172
    changed_inventory = rename_to_temp_delete(source_entries, inventory, dir,
 
 
1173
                                              temp_dir, conflict_handler)
 
 
1175
    rename_to_new_create(changed_inventory, target_entries, inventory,
 
 
1176
                         changeset, dir, conflict_handler)
 
 
1178
    return changed_inventory
 
 
1181
def print_changeset(cset):
 
 
1182
    """Print all non-boring changeset entries
 
 
1184
    :param cset: The changeset to print
 
 
1185
    :type cset: `Changeset`
 
 
1187
    for entry in cset.entries.itervalues():
 
 
1188
        if entry.is_boring():
 
 
1191
        print entry.summarize_name(cset)
 
 
1194
class UnsupportedFiletype(Exception):
 
 
1195
    def __init__(self, kind, full_path):
 
 
1196
        msg = "The file \"%s\" is a %s, which is not a supported filetype." \
 
 
1198
        Exception.__init__(self, msg)
 
 
1199
        self.full_path = full_path
 
 
1203
def generate_changeset(tree_a, tree_b, interesting_ids=None):
 
 
1204
    return ChangesetGenerator(tree_a, tree_b, interesting_ids)()
 
 
1207
class ChangesetGenerator(object):
 
 
1208
    def __init__(self, tree_a, tree_b, interesting_ids=None):
 
 
1209
        object.__init__(self)
 
 
1210
        self.tree_a = tree_a
 
 
1211
        self.tree_b = tree_b
 
 
1212
        self._interesting_ids = interesting_ids
 
 
1214
    def iter_both_tree_ids(self):
 
 
1215
        for file_id in self.tree_a:
 
 
1217
        for file_id in self.tree_b:
 
 
1218
            if file_id not in self.tree_a:
 
 
1223
        for file_id in self.iter_both_tree_ids():
 
 
1224
            cs_entry = self.make_entry(file_id)
 
 
1225
            if cs_entry is not None and not cs_entry.is_boring():
 
 
1226
                cset.add_entry(cs_entry)
 
 
1228
        for entry in list(cset.entries.itervalues()):
 
 
1229
            if entry.parent != entry.new_parent:
 
 
1230
                if not cset.entries.has_key(entry.parent) and\
 
 
1231
                    entry.parent != NULL_ID and entry.parent is not None:
 
 
1232
                    parent_entry = self.make_boring_entry(entry.parent)
 
 
1233
                    cset.add_entry(parent_entry)
 
 
1234
                if not cset.entries.has_key(entry.new_parent) and\
 
 
1235
                    entry.new_parent != NULL_ID and \
 
 
1236
                    entry.new_parent is not None:
 
 
1237
                    parent_entry = self.make_boring_entry(entry.new_parent)
 
 
1238
                    cset.add_entry(parent_entry)
 
 
1241
    def iter_inventory(self, tree):
 
 
1242
        for file_id in tree:
 
 
1243
            yield self.get_entry(file_id, tree)
 
 
1245
    def get_entry(self, file_id, tree):
 
 
1246
        if not tree.has_or_had_id(file_id):
 
 
1248
        return tree.inventory[file_id]
 
 
1250
    def get_entry_parent(self, entry):
 
 
1253
        return entry.parent_id
 
 
1255
    def get_path(self, file_id, tree):
 
 
1256
        if not tree.has_or_had_id(file_id):
 
 
1258
        path = tree.id2path(file_id)
 
 
1264
    def make_basic_entry(self, file_id, only_interesting):
 
 
1265
        entry_a = self.get_entry(file_id, self.tree_a)
 
 
1266
        entry_b = self.get_entry(file_id, self.tree_b)
 
 
1267
        if only_interesting and not self.is_interesting(entry_a, entry_b):
 
 
1269
        parent = self.get_entry_parent(entry_a)
 
 
1270
        path = self.get_path(file_id, self.tree_a)
 
 
1271
        cs_entry = ChangesetEntry(file_id, parent, path)
 
 
1272
        new_parent = self.get_entry_parent(entry_b)
 
 
1274
        new_path = self.get_path(file_id, self.tree_b)
 
 
1276
        cs_entry.new_path = new_path
 
 
1277
        cs_entry.new_parent = new_parent
 
 
1280
    def is_interesting(self, entry_a, entry_b):
 
 
1281
        if self._interesting_ids is None:
 
 
1283
        if entry_a is not None:
 
 
1284
            file_id = entry_a.file_id
 
 
1285
        elif entry_b is not None:
 
 
1286
            file_id = entry_b.file_id
 
 
1289
        return file_id in self._interesting_ids
 
 
1291
    def make_boring_entry(self, id):
 
 
1292
        cs_entry = self.make_basic_entry(id, only_interesting=False)
 
 
1293
        if cs_entry.is_creation_or_deletion():
 
 
1294
            return self.make_entry(id, only_interesting=False)
 
 
1298
    def make_entry(self, id, only_interesting=True):
 
 
1299
        cs_entry = self.make_basic_entry(id, only_interesting)
 
 
1301
        if cs_entry is None:
 
 
1304
        cs_entry.metadata_change = self.make_exec_flag_change(id)
 
 
1306
        if id in self.tree_a and id in self.tree_b:
 
 
1307
            a_sha1 = self.tree_a.get_file_sha1(id)
 
 
1308
            b_sha1 = self.tree_b.get_file_sha1(id)
 
 
1309
            if None not in (a_sha1, b_sha1) and a_sha1 == b_sha1:
 
 
1312
        cs_entry.contents_change = self.make_contents_change(id)
 
 
1315
    def make_exec_flag_change(self, file_id):
 
 
1316
        exec_flag_a = exec_flag_b = None
 
 
1317
        if file_id in self.tree_a and self.tree_a.kind(file_id) == "file":
 
 
1318
            exec_flag_a = self.tree_a.is_executable(file_id)
 
 
1320
        if file_id in self.tree_b and self.tree_b.kind(file_id) == "file":
 
 
1321
            exec_flag_b = self.tree_b.is_executable(file_id)
 
 
1323
        if exec_flag_a == exec_flag_b:
 
 
1325
        return ChangeExecFlag(exec_flag_a, exec_flag_b)
 
 
1327
    def make_contents_change(self, file_id):
 
 
1328
        a_contents = get_contents(self.tree_a, file_id)
 
 
1329
        b_contents = get_contents(self.tree_b, file_id)
 
 
1330
        if a_contents == b_contents:
 
 
1332
        return ReplaceContents(a_contents, b_contents)
 
 
1335
def get_contents(tree, file_id):
 
 
1336
    """Return the appropriate contents to create a copy of file_id from tree"""
 
 
1337
    if file_id not in tree:
 
 
1339
    kind = tree.kind(file_id)
 
 
1341
        return TreeFileCreate(tree, file_id)
 
 
1342
    elif kind in ("directory", "root_directory"):
 
 
1344
    elif kind == "symlink":
 
 
1345
        return SymlinkCreate(tree.get_symlink_target(file_id))
 
 
1347
        raise UnsupportedFiletype(kind, tree.id2path(file_id))
 
 
1350
def full_path(entry, tree):
 
 
1351
    return pathjoin(tree.basedir, entry.path)
 
 
1354
def new_delete_entry(entry, tree, inventory, delete):
 
 
1355
    if entry.path == "":
 
 
1358
        parent = inventory[dirname(entry.path)].id
 
 
1359
    cs_entry = ChangesetEntry(parent, entry.path)
 
 
1361
        cs_entry.new_path = None
 
 
1362
        cs_entry.new_parent = None
 
 
1364
        cs_entry.path = None
 
 
1365
        cs_entry.parent = None
 
 
1366
    full_path = full_path(entry, tree)
 
 
1367
    status = os.lstat(full_path)
 
 
1368
    if stat.S_ISDIR(file_stat.st_mode):
 
 
1372
# XXX: Can't we unify this with the regular inventory object
 
 
1373
class Inventory(object):
 
 
1374
    def __init__(self, inventory):
 
 
1375
        self.inventory = inventory
 
 
1376
        self.rinventory = None
 
 
1378
    def get_rinventory(self):
 
 
1379
        if self.rinventory is None:
 
 
1380
            self.rinventory  = invert_dict(self.inventory)
 
 
1381
        return self.rinventory
 
 
1383
    def get_path(self, id):
 
 
1384
        return self.inventory.get(id)
 
 
1386
    def get_name(self, id):
 
 
1387
        path = self.get_path(id)
 
 
1391
            return os.path.basename(path)
 
 
1393
    def get_dir(self, id):
 
 
1394
        path = self.get_path(id)
 
 
1399
        return os.path.dirname(path)
 
 
1401
    def get_parent(self, id):
 
 
1402
        if self.get_path(id) is None:
 
 
1404
        directory = self.get_dir(id)
 
 
1405
        if directory == '.':
 
 
1407
        if directory is None:
 
 
1409
        return self.get_rinventory().get(directory)