| 
									
										
										
										
											2000-02-02 15:10:15 +00:00
										 |  |  | """A class to build directory diff tools on.""" | 
					
						
							| 
									
										
										
										
											1990-10-13 19:23:40 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1992-03-31 18:55:40 +00:00
										 |  |  | import os | 
					
						
							| 
									
										
										
										
											1990-10-13 19:23:40 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | import dircache | 
					
						
							|  |  |  | import cmpcache | 
					
						
							|  |  |  | import statcache | 
					
						
							| 
									
										
										
										
											1990-10-21 16:17:34 +00:00
										 |  |  | from stat import * | 
					
						
							| 
									
										
										
										
											1990-10-13 19:23:40 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1991-12-26 13:03:23 +00:00
										 |  |  | class dircmp: | 
					
						
							| 
									
										
										
										
											2000-02-02 15:10:15 +00:00
										 |  |  |     """Directory comparison class.""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def new(self, a, b): | 
					
						
							|  |  |  |         """Initialize.""" | 
					
						
							|  |  |  |         self.a = a | 
					
						
							|  |  |  |         self.b = b | 
					
						
							|  |  |  |         # Properties that caller may change before calling self.run(): | 
					
						
							|  |  |  |         self.hide = [os.curdir, os.pardir] # Names never to be shown | 
					
						
							|  |  |  |         self.ignore = ['RCS', 'tags'] # Names ignored in comparison | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return self | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def run(self): | 
					
						
							|  |  |  |         """Compare everything except common subdirectories.""" | 
					
						
							|  |  |  |         self.a_list = filter(dircache.listdir(self.a), self.hide) | 
					
						
							|  |  |  |         self.b_list = filter(dircache.listdir(self.b), self.hide) | 
					
						
							|  |  |  |         self.a_list.sort() | 
					
						
							|  |  |  |         self.b_list.sort() | 
					
						
							|  |  |  |         self.phase1() | 
					
						
							|  |  |  |         self.phase2() | 
					
						
							|  |  |  |         self.phase3() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def phase1(self): | 
					
						
							|  |  |  |         """Compute common names.""" | 
					
						
							|  |  |  |         self.a_only = [] | 
					
						
							|  |  |  |         self.common = [] | 
					
						
							|  |  |  |         for x in self.a_list: | 
					
						
							|  |  |  |             if x in self.b_list: | 
					
						
							|  |  |  |                 self.common.append(x) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 self.a_only.append(x) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.b_only = [] | 
					
						
							|  |  |  |         for x in self.b_list: | 
					
						
							|  |  |  |             if x not in self.common: | 
					
						
							|  |  |  |                 self.b_only.append(x) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def phase2(self): | 
					
						
							|  |  |  |         """Distinguish files, directories, funnies.""" | 
					
						
							|  |  |  |         self.common_dirs = [] | 
					
						
							|  |  |  |         self.common_files = [] | 
					
						
							|  |  |  |         self.common_funny = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for x in self.common: | 
					
						
							|  |  |  |             a_path = os.path.join(self.a, x) | 
					
						
							|  |  |  |             b_path = os.path.join(self.b, x) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             ok = 1 | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 a_stat = statcache.stat(a_path) | 
					
						
							|  |  |  |             except os.error, why: | 
					
						
							|  |  |  |                 # print 'Can\'t stat', a_path, ':', why[1] | 
					
						
							|  |  |  |                 ok = 0 | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 b_stat = statcache.stat(b_path) | 
					
						
							|  |  |  |             except os.error, why: | 
					
						
							|  |  |  |                 # print 'Can\'t stat', b_path, ':', why[1] | 
					
						
							|  |  |  |                 ok = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if ok: | 
					
						
							|  |  |  |                 a_type = S_IFMT(a_stat[ST_MODE]) | 
					
						
							|  |  |  |                 b_type = S_IFMT(b_stat[ST_MODE]) | 
					
						
							| 
									
										
										
										
											2000-12-12 23:11:42 +00:00
										 |  |  |                 if a_type != b_type: | 
					
						
							| 
									
										
										
										
											2000-02-02 15:10:15 +00:00
										 |  |  |                     self.common_funny.append(x) | 
					
						
							|  |  |  |                 elif S_ISDIR(a_type): | 
					
						
							|  |  |  |                     self.common_dirs.append(x) | 
					
						
							|  |  |  |                 elif S_ISREG(a_type): | 
					
						
							|  |  |  |                     self.common_files.append(x) | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     self.common_funny.append(x) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 self.common_funny.append(x) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def phase3(self): | 
					
						
							|  |  |  |         """Find out differences between common files.""" | 
					
						
							|  |  |  |         xx = cmpfiles(self.a, self.b, self.common_files) | 
					
						
							|  |  |  |         self.same_files, self.diff_files, self.funny_files = xx | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def phase4(self): | 
					
						
							|  |  |  |         """Find out differences between common subdirectories.
 | 
					
						
							|  |  |  |         A new dircmp object is created for each common subdirectory, | 
					
						
							|  |  |  |         these are stored in a dictionary indexed by filename. | 
					
						
							|  |  |  |         The hide and ignore properties are inherited from the parent."""
 | 
					
						
							|  |  |  |         self.subdirs = {} | 
					
						
							|  |  |  |         for x in self.common_dirs: | 
					
						
							|  |  |  |             a_x = os.path.join(self.a, x) | 
					
						
							|  |  |  |             b_x = os.path.join(self.b, x) | 
					
						
							|  |  |  |             self.subdirs[x] = newdd = dircmp().new(a_x, b_x) | 
					
						
							|  |  |  |             newdd.hide = self.hide | 
					
						
							|  |  |  |             newdd.ignore = self.ignore | 
					
						
							|  |  |  |             newdd.run() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def phase4_closure(self): | 
					
						
							|  |  |  |         """Recursively call phase4() on subdirectories.""" | 
					
						
							|  |  |  |         self.phase4() | 
					
						
							|  |  |  |         for x in self.subdirs.keys(): | 
					
						
							|  |  |  |             self.subdirs[x].phase4_closure() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def report(self): | 
					
						
							|  |  |  |         """Print a report on the differences between a and b.""" | 
					
						
							|  |  |  |         # Assume that phases 1 to 3 have been executed | 
					
						
							|  |  |  |         # Output format is purposely lousy | 
					
						
							|  |  |  |         print 'diff', self.a, self.b | 
					
						
							|  |  |  |         if self.a_only: | 
					
						
							|  |  |  |             print 'Only in', self.a, ':', self.a_only | 
					
						
							|  |  |  |         if self.b_only: | 
					
						
							|  |  |  |             print 'Only in', self.b, ':', self.b_only | 
					
						
							|  |  |  |         if self.same_files: | 
					
						
							|  |  |  |             print 'Identical files :', self.same_files | 
					
						
							|  |  |  |         if self.diff_files: | 
					
						
							|  |  |  |             print 'Differing files :', self.diff_files | 
					
						
							|  |  |  |         if self.funny_files: | 
					
						
							|  |  |  |             print 'Trouble with common files :', self.funny_files | 
					
						
							|  |  |  |         if self.common_dirs: | 
					
						
							|  |  |  |             print 'Common subdirectories :', self.common_dirs | 
					
						
							|  |  |  |         if self.common_funny: | 
					
						
							|  |  |  |             print 'Common funny cases :', self.common_funny | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def report_closure(self): | 
					
						
							|  |  |  |         """Print reports on self and on subdirs.
 | 
					
						
							|  |  |  |         If phase 4 hasn't been done, no subdir reports are printed.""" | 
					
						
							|  |  |  |         self.report() | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             x = self.subdirs | 
					
						
							|  |  |  |         except AttributeError: | 
					
						
							|  |  |  |             return # No subdirectories computed | 
					
						
							|  |  |  |         for x in self.subdirs.keys(): | 
					
						
							|  |  |  |             print | 
					
						
							|  |  |  |             self.subdirs[x].report_closure() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def report_phase4_closure(self): | 
					
						
							|  |  |  |         """Report and do phase 4 recursively.""" | 
					
						
							|  |  |  |         self.report() | 
					
						
							|  |  |  |         self.phase4() | 
					
						
							|  |  |  |         for x in self.subdirs.keys(): | 
					
						
							|  |  |  |             print | 
					
						
							|  |  |  |             self.subdirs[x].report_phase4_closure() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1990-10-13 19:23:40 +00:00
										 |  |  | def cmpfiles(a, b, common): | 
					
						
							| 
									
										
										
										
											2000-02-02 15:10:15 +00:00
										 |  |  |     """Compare common files in two directories.
 | 
					
						
							|  |  |  |     Return: | 
					
						
							|  |  |  |         - files that compare equal | 
					
						
							|  |  |  |         - files that compare different | 
					
						
							|  |  |  |         - funny cases (can't stat etc.)""" | 
					
						
							| 
									
										
										
										
											1990-10-13 19:23:40 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2000-02-02 15:10:15 +00:00
										 |  |  |     res = ([], [], []) | 
					
						
							|  |  |  |     for x in common: | 
					
						
							|  |  |  |         res[cmp(os.path.join(a, x), os.path.join(b, x))].append(x) | 
					
						
							|  |  |  |     return res | 
					
						
							| 
									
										
										
										
											1990-10-13 19:23:40 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2000-02-02 15:10:15 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | def cmp(a, b): | 
					
						
							|  |  |  |     """Compare two files.
 | 
					
						
							|  |  |  |     Return: | 
					
						
							|  |  |  |         0 for equal | 
					
						
							|  |  |  |         1 for different | 
					
						
							|  |  |  |         2 for funny cases (can't stat, etc.)""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         if cmpcache.cmp(a, b): return 0 | 
					
						
							|  |  |  |         return 1 | 
					
						
							|  |  |  |     except os.error: | 
					
						
							|  |  |  |         return 2 | 
					
						
							| 
									
										
										
										
											1990-10-13 19:23:40 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def filter(list, skip): | 
					
						
							| 
									
										
										
										
											2000-02-02 15:10:15 +00:00
										 |  |  |     """Return a copy with items that occur in skip removed.""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     result = [] | 
					
						
							|  |  |  |     for item in list: | 
					
						
							|  |  |  |         if item not in skip: result.append(item) | 
					
						
							|  |  |  |     return result | 
					
						
							| 
									
										
										
										
											1990-10-13 19:23:40 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def demo(): | 
					
						
							| 
									
										
										
										
											2000-02-02 15:10:15 +00:00
										 |  |  |     """Demonstration and testing.""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     import sys | 
					
						
							|  |  |  |     import getopt | 
					
						
							|  |  |  |     options, args = getopt.getopt(sys.argv[1:], 'r') | 
					
						
							| 
									
										
										
										
											2000-12-12 23:11:42 +00:00
										 |  |  |     if len(args) != 2: | 
					
						
							|  |  |  |         raise getopt.error, 'need exactly two args' | 
					
						
							| 
									
										
										
										
											2000-02-02 15:10:15 +00:00
										 |  |  |     dd = dircmp().new(args[0], args[1]) | 
					
						
							|  |  |  |     dd.run() | 
					
						
							|  |  |  |     if ('-r', '') in options: | 
					
						
							|  |  |  |         dd.report_phase4_closure() | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         dd.report() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if __name__ == "__main__": | 
					
						
							|  |  |  |     demo() |