summaryrefslogtreecommitdiffstats
path: root/Lib/dircmp.py
blob: 13a8a04925bee4342d50aa76c34e9744f791ec11 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
"""A class to build directory diff tools on."""

import os

import dircache
import cmpcache
import statcache
from stat import *

class dircmp:
    """Directory comparison class."""

    def new(self, a, b):
        """Initialize."""
        self.a = a
        self.b = b
        # Properties that caller may change before calling self.run():
        self.hide = [os.curdir, os.pardir] # Names never to be shown
        self.ignore = ['RCS', 'tags'] # Names ignored in comparison

        return self

    def run(self):
        """Compare everything except common subdirectories."""
        self.a_list = filter(dircache.listdir(self.a), self.hide)
        self.b_list = filter(dircache.listdir(self.b), self.hide)
        self.a_list.sort()
        self.b_list.sort()
        self.phase1()
        self.phase2()
        self.phase3()

    def phase1(self):
        """Compute common names."""
        self.a_only = []
        self.common = []
        for x in self.a_list:
            if x in self.b_list:
                self.common.append(x)
            else:
                self.a_only.append(x)

        self.b_only = []
        for x in self.b_list:
            if x not in self.common:
                self.b_only.append(x)

    def phase2(self):
        """Distinguish files, directories, funnies."""
        self.common_dirs = []
        self.common_files = []
        self.common_funny = []

        for x in self.common:
            a_path = os.path.join(self.a, x)
            b_path = os.path.join(self.b, x)

            ok = 1
            try:
                a_stat = statcache.stat(a_path)
            except os.error, why:
                # print 'Can\'t stat', a_path, ':', why[1]
                ok = 0
            try:
                b_stat = statcache.stat(b_path)
            except os.error, why:
                # print 'Can\'t stat', b_path, ':', why[1]
                ok = 0

            if ok:
                a_type = S_IFMT(a_stat[ST_MODE])
                b_type = S_IFMT(b_stat[ST_MODE])
                if a_type <> b_type:
                    self.common_funny.append(x)
                elif S_ISDIR(a_type):
                    self.common_dirs.append(x)
                elif S_ISREG(a_type):
                    self.common_files.append(x)
                else:
                    self.common_funny.append(x)
            else:
                self.common_funny.append(x)

    def phase3(self):
        """Find out differences between common files."""
        xx = cmpfiles(self.a, self.b, self.common_files)
        self.same_files, self.diff_files, self.funny_files = xx

    def phase4(self):
        """Find out differences between common subdirectories.
        A new dircmp object is created for each common subdirectory,
        these are stored in a dictionary indexed by filename.
        The hide and ignore properties are inherited from the parent."""
        self.subdirs = {}
        for x in self.common_dirs:
            a_x = os.path.join(self.a, x)
            b_x = os.path.join(self.b, x)
            self.subdirs[x] = newdd = dircmp().new(a_x, b_x)
            newdd.hide = self.hide
            newdd.ignore = self.ignore
            newdd.run()

    def phase4_closure(self):
        """Recursively call phase4() on subdirectories."""
        self.phase4()
        for x in self.subdirs.keys():
            self.subdirs[x].phase4_closure()

    def report(self):
        """Print a report on the differences between a and b."""
        # Assume that phases 1 to 3 have been executed
        # Output format is purposely lousy
        print 'diff', self.a, self.b
        if self.a_only:
            print 'Only in', self.a, ':', self.a_only
        if self.b_only:
            print 'Only in', self.b, ':', self.b_only
        if self.same_files:
            print 'Identical files :', self.same_files
        if self.diff_files:
            print 'Differing files :', self.diff_files
        if self.funny_files:
            print 'Trouble with common files :', self.funny_files
        if self.common_dirs:
            print 'Common subdirectories :', self.common_dirs
        if self.common_funny:
            print 'Common funny cases :', self.common_funny

    def report_closure(self):
        """Print reports on self and on subdirs.
        If phase 4 hasn't been done, no subdir reports are printed."""
        self.report()
        try:
            x = self.subdirs
        except AttributeError:
            return # No subdirectories computed
        for x in self.subdirs.keys():
            print
            self.subdirs[x].report_closure()

    def report_phase4_closure(self):
        """Report and do phase 4 recursively."""
        self.report()
        self.phase4()
        for x in self.subdirs.keys():
            print
            self.subdirs[x].report_phase4_closure()


def cmpfiles(a, b, common):
    """Compare common files in two directories.
    Return:
        - files that compare equal
        - files that compare different
        - funny cases (can't stat etc.)"""

    res = ([], [], [])
    for x in common:
        res[cmp(os.path.join(a, x), os.path.join(b, x))].append(x)
    return res


def cmp(a, b):
    """Compare two files.
    Return:
        0 for equal
        1 for different
        2 for funny cases (can't stat, etc.)"""

    try:
        if cmpcache.cmp(a, b): return 0
        return 1
    except os.error:
        return 2


def filter(list, skip):
    """Return a copy with items that occur in skip removed."""

    result = []
    for item in list:
        if item not in skip: result.append(item)
    return result


def demo():
    """Demonstration and testing."""

    import sys
    import getopt
    options, args = getopt.getopt(sys.argv[1:], 'r')
    if len(args) <> 2: raise getopt.error, 'need exactly two args'
    dd = dircmp().new(args[0], args[1])
    dd.run()
    if ('-r', '') in options:
        dd.report_phase4_closure()
    else:
        dd.report()

if __name__ == "__main__":
    demo()