Commit | Line | Data |
---|---|---|
920dae64 AT |
1 | """A class to build directory diff tools on.""" |
2 | ||
3 | import os | |
4 | ||
5 | import dircache | |
6 | import cmpcache | |
7 | import statcache | |
8 | from stat import * | |
9 | ||
10 | class dircmp: | |
11 | """Directory comparison class.""" | |
12 | ||
13 | def new(self, a, b): | |
14 | """Initialize.""" | |
15 | self.a = a | |
16 | self.b = b | |
17 | # Properties that caller may change before calling self.run(): | |
18 | self.hide = [os.curdir, os.pardir] # Names never to be shown | |
19 | self.ignore = ['RCS', 'tags'] # Names ignored in comparison | |
20 | ||
21 | return self | |
22 | ||
23 | def run(self): | |
24 | """Compare everything except common subdirectories.""" | |
25 | self.a_list = filter(dircache.listdir(self.a), self.hide) | |
26 | self.b_list = filter(dircache.listdir(self.b), self.hide) | |
27 | self.a_list.sort() | |
28 | self.b_list.sort() | |
29 | self.phase1() | |
30 | self.phase2() | |
31 | self.phase3() | |
32 | ||
33 | def phase1(self): | |
34 | """Compute common names.""" | |
35 | self.a_only = [] | |
36 | self.common = [] | |
37 | for x in self.a_list: | |
38 | if x in self.b_list: | |
39 | self.common.append(x) | |
40 | else: | |
41 | self.a_only.append(x) | |
42 | ||
43 | self.b_only = [] | |
44 | for x in self.b_list: | |
45 | if x not in self.common: | |
46 | self.b_only.append(x) | |
47 | ||
48 | def phase2(self): | |
49 | """Distinguish files, directories, funnies.""" | |
50 | self.common_dirs = [] | |
51 | self.common_files = [] | |
52 | self.common_funny = [] | |
53 | ||
54 | for x in self.common: | |
55 | a_path = os.path.join(self.a, x) | |
56 | b_path = os.path.join(self.b, x) | |
57 | ||
58 | ok = 1 | |
59 | try: | |
60 | a_stat = statcache.stat(a_path) | |
61 | except os.error, why: | |
62 | # print 'Can\'t stat', a_path, ':', why[1] | |
63 | ok = 0 | |
64 | try: | |
65 | b_stat = statcache.stat(b_path) | |
66 | except os.error, why: | |
67 | # print 'Can\'t stat', b_path, ':', why[1] | |
68 | ok = 0 | |
69 | ||
70 | if ok: | |
71 | a_type = S_IFMT(a_stat[ST_MODE]) | |
72 | b_type = S_IFMT(b_stat[ST_MODE]) | |
73 | if a_type != b_type: | |
74 | self.common_funny.append(x) | |
75 | elif S_ISDIR(a_type): | |
76 | self.common_dirs.append(x) | |
77 | elif S_ISREG(a_type): | |
78 | self.common_files.append(x) | |
79 | else: | |
80 | self.common_funny.append(x) | |
81 | else: | |
82 | self.common_funny.append(x) | |
83 | ||
84 | def phase3(self): | |
85 | """Find out differences between common files.""" | |
86 | xx = cmpfiles(self.a, self.b, self.common_files) | |
87 | self.same_files, self.diff_files, self.funny_files = xx | |
88 | ||
89 | def phase4(self): | |
90 | """Find out differences between common subdirectories. | |
91 | A new dircmp object is created for each common subdirectory, | |
92 | these are stored in a dictionary indexed by filename. | |
93 | The hide and ignore properties are inherited from the parent.""" | |
94 | self.subdirs = {} | |
95 | for x in self.common_dirs: | |
96 | a_x = os.path.join(self.a, x) | |
97 | b_x = os.path.join(self.b, x) | |
98 | self.subdirs[x] = newdd = dircmp().new(a_x, b_x) | |
99 | newdd.hide = self.hide | |
100 | newdd.ignore = self.ignore | |
101 | newdd.run() | |
102 | ||
103 | def phase4_closure(self): | |
104 | """Recursively call phase4() on subdirectories.""" | |
105 | self.phase4() | |
106 | for x in self.subdirs.keys(): | |
107 | self.subdirs[x].phase4_closure() | |
108 | ||
109 | def report(self): | |
110 | """Print a report on the differences between a and b.""" | |
111 | # Assume that phases 1 to 3 have been executed | |
112 | # Output format is purposely lousy | |
113 | print 'diff', self.a, self.b | |
114 | if self.a_only: | |
115 | print 'Only in', self.a, ':', self.a_only | |
116 | if self.b_only: | |
117 | print 'Only in', self.b, ':', self.b_only | |
118 | if self.same_files: | |
119 | print 'Identical files :', self.same_files | |
120 | if self.diff_files: | |
121 | print 'Differing files :', self.diff_files | |
122 | if self.funny_files: | |
123 | print 'Trouble with common files :', self.funny_files | |
124 | if self.common_dirs: | |
125 | print 'Common subdirectories :', self.common_dirs | |
126 | if self.common_funny: | |
127 | print 'Common funny cases :', self.common_funny | |
128 | ||
129 | def report_closure(self): | |
130 | """Print reports on self and on subdirs. | |
131 | If phase 4 hasn't been done, no subdir reports are printed.""" | |
132 | self.report() | |
133 | try: | |
134 | x = self.subdirs | |
135 | except AttributeError: | |
136 | return # No subdirectories computed | |
137 | for x in self.subdirs.keys(): | |
138 | ||
139 | self.subdirs[x].report_closure() | |
140 | ||
141 | def report_phase4_closure(self): | |
142 | """Report and do phase 4 recursively.""" | |
143 | self.report() | |
144 | self.phase4() | |
145 | for x in self.subdirs.keys(): | |
146 | ||
147 | self.subdirs[x].report_phase4_closure() | |
148 | ||
149 | ||
150 | def cmpfiles(a, b, common): | |
151 | """Compare common files in two directories. | |
152 | Return: | |
153 | - files that compare equal | |
154 | - files that compare different | |
155 | - funny cases (can't stat etc.)""" | |
156 | ||
157 | res = ([], [], []) | |
158 | for x in common: | |
159 | res[cmp(os.path.join(a, x), os.path.join(b, x))].append(x) | |
160 | return res | |
161 | ||
162 | ||
163 | def cmp(a, b): | |
164 | """Compare two files. | |
165 | Return: | |
166 | 0 for equal | |
167 | 1 for different | |
168 | 2 for funny cases (can't stat, etc.)""" | |
169 | ||
170 | try: | |
171 | if cmpcache.cmp(a, b): return 0 | |
172 | return 1 | |
173 | except os.error: | |
174 | return 2 | |
175 | ||
176 | ||
177 | def filter(list, skip): | |
178 | """Return a copy with items that occur in skip removed.""" | |
179 | ||
180 | result = [] | |
181 | for item in list: | |
182 | if item not in skip: result.append(item) | |
183 | return result | |
184 | ||
185 | ||
186 | def demo(): | |
187 | """Demonstration and testing.""" | |
188 | ||
189 | import sys | |
190 | import getopt | |
191 | options, args = getopt.getopt(sys.argv[1:], 'r') | |
192 | if len(args) != 2: | |
193 | raise getopt.error, 'need exactly two args' | |
194 | dd = dircmp().new(args[0], args[1]) | |
195 | dd.run() | |
196 | if ('-r', '') in options: | |
197 | dd.report_phase4_closure() | |
198 | else: | |
199 | dd.report() | |
200 | ||
201 | if __name__ == "__main__": | |
202 | demo() |