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