blob: 13a8a04925bee4342d50aa76c34e9744f791ec11 [file] [log] [blame]
Guido van Rossum4acc25b2000-02-02 15:10:15 +00001"""A class to build directory diff tools on."""
Guido van Rossumc6360141990-10-13 19:23:40 +00002
Guido van Rossumc96207a1992-03-31 18:55:40 +00003import os
Guido van Rossumc6360141990-10-13 19:23:40 +00004
5import dircache
6import cmpcache
7import statcache
Guido van Rossum40d93041990-10-21 16:17:34 +00008from stat import *
Guido van Rossumc6360141990-10-13 19:23:40 +00009
Guido van Rossumbb3753d1991-12-26 13:03:23 +000010class dircmp:
Guido van Rossum4acc25b2000-02-02 15:10:15 +000011 """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 print
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 print
147 self.subdirs[x].report_phase4_closure()
Guido van Rossumc6360141990-10-13 19:23:40 +0000148
149
Guido van Rossumc6360141990-10-13 19:23:40 +0000150def cmpfiles(a, b, common):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000151 """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
Guido van Rossumc6360141990-10-13 19:23:40 +0000161
162
Guido van Rossumc6360141990-10-13 19:23:40 +0000163def cmp(a, b):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000164 """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
Guido van Rossumc6360141990-10-13 19:23:40 +0000175
176
Guido van Rossumc6360141990-10-13 19:23:40 +0000177def filter(list, skip):
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000178 """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
Guido van Rossumc6360141990-10-13 19:23:40 +0000184
185
Guido van Rossumc6360141990-10-13 19:23:40 +0000186def demo():
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000187 """Demonstration and testing."""
Guido van Rossumc6360141990-10-13 19:23:40 +0000188
Guido van Rossum4acc25b2000-02-02 15:10:15 +0000189 import sys
190 import getopt
191 options, args = getopt.getopt(sys.argv[1:], 'r')
192 if len(args) <> 2: raise getopt.error, 'need exactly two args'
193 dd = dircmp().new(args[0], args[1])
194 dd.run()
195 if ('-r', '') in options:
196 dd.report_phase4_closure()
197 else:
198 dd.report()
199
200if __name__ == "__main__":
201 demo()