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