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