~bzr-pqm/bzr/bzr.dev

1 by mbp at sourcefrog
import from baz patch-364
1
#! /usr/bin/env python
2
# -*- coding: UTF-8 -*-
3
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
18
from sets import Set
19
20
from trace import mutter
21
22
23
24
25
26
27
def diff_trees(old_tree, new_tree):
28
    """Compute diff between two trees.
29
30
    They may be in different branches and may be working or historical
31
    trees.
32
33
    Yields a sequence of (state, id, old_name, new_name, kind).
34
    Each filename and each id is listed only once.
35
    """
36
37
    ## TODO: Compare files before diffing; only mention those that have changed
38
39
    ## TODO: Set nice names in the headers, maybe include diffstat
40
41
    ## TODO: Perhaps make this a generator rather than using
42
    ## a callback object?
43
44
    ## TODO: Allow specifying a list of files to compare, rather than
45
    ## doing the whole tree?  (Not urgent.)
46
47
    ## TODO: Allow diffing any two inventories, not just the
48
    ## current one against one.  We mgiht need to specify two
49
    ## stores to look for the files if diffing two branches.  That
50
    ## might imply this shouldn't be primarily a Branch method.
51
52
    ## XXX: This doesn't report on unknown files; that can be done
53
    ## from a separate method.
54
55
    old_it = old_tree.list_files()
56
    new_it = new_tree.list_files()
57
58
    def next(it):
59
        try:
60
            return it.next()
61
        except StopIteration:
62
            return None
63
64
    old_item = next(old_it)
65
    new_item = next(new_it)
66
67
    # We step through the two sorted iterators in parallel, trying to
68
    # keep them lined up.
69
70
    while (old_item != None) or (new_item != None):
71
        # OK, we still have some remaining on both, but they may be
72
        # out of step.        
73
        if old_item != None:
74
            old_name, old_class, old_kind, old_id = old_item
75
        else:
76
            old_name = None
77
            
78
        if new_item != None:
79
            new_name, new_class, new_kind, new_id = new_item
80
        else:
81
            new_name = None
82
83
        mutter("   diff pairwise %r" % (old_item,))
84
        mutter("                 %r" % (new_item,))
85
86
        if old_item:
87
            # can't handle the old tree being a WorkingTree
88
            assert old_class == 'V'
89
90
        if new_item and (new_class != 'V'):
91
            yield new_class, None, None, new_name, new_kind
92
            new_item = next(new_it)
93
        elif (not new_item) or (old_item and (old_name < new_name)):
94
            mutter("     extra entry in old-tree sequence")
95
            if new_tree.has_id(old_id):
96
                # will be mentioned as renamed under new name
97
                pass
98
            else:
99
                yield 'D', old_id, old_name, None, old_kind
100
            old_item = next(old_it)
101
        elif (not old_item) or (new_item and (new_name < old_name)):
102
            mutter("     extra entry in new-tree sequence")
103
            if old_tree.has_id(new_id):
104
                yield 'R', new_id, old_tree.id2path(new_id), new_name, new_kind
105
            else:
106
                yield 'A', new_id, None, new_name, new_kind
107
            new_item = next(new_it)
108
        elif old_id != new_id:
109
            assert old_name == new_name
110
            # both trees have a file of this name, but it is not the
111
            # same file.  in other words, the old filename has been
112
            # overwritten by either a newly-added or a renamed file.
113
            # (should we return something about the overwritten file?)
114
            if old_tree.has_id(new_id):
115
                # renaming, overlying a deleted file
116
                yield 'R', new_id, old_tree.id2path(new_id), new_name, new_kind
117
            else:
118
                yield 'A', new_id, None, new_name, new_kind
119
120
            new_item = next(new_it)
121
            old_item = next(old_it)
122
        else:
123
            assert old_id == new_id
124
            assert old_name == new_name
125
            assert old_kind == new_kind
126
127
            if old_kind == 'directory':
128
                yield '.', new_id, old_name, new_name, new_kind
129
            elif old_tree.get_file_size(old_id) != new_tree.get_file_size(old_id):
130
                mutter("    file size has changed, must be different")
131
                yield 'M', new_id, old_name, new_name, new_kind
132
            elif old_tree.get_file_sha1(old_id) == new_tree.get_file_sha1(old_id):
133
                mutter("      SHA1 indicates they're identical")
134
                ## assert compare_files(old_tree.get_file(i), new_tree.get_file(i))
135
                yield '.', new_id, old_name, new_name, new_kind
136
            else:
137
                mutter("      quick compare shows different")
138
                yield 'M', new_id, old_name, new_name, new_kind
139
140
            new_item = next(new_it)
141
            old_item = next(old_it)
142
143