1#! /usr/bin/env python 2 3"""Script to synchronize two source trees. 4 5Invoke with two arguments: 6 7python treesync.py slave master 8 9The assumption is that "master" contains CVS administration while 10slave doesn't. All files in the slave tree that have a CVS/Entries 11entry in the master tree are synchronized. This means: 12 13 If the files differ: 14 if the slave file is newer: 15 normalize the slave file 16 if the files still differ: 17 copy the slave to the master 18 else (the master is newer): 19 copy the master to the slave 20 21 normalizing the slave means replacing CRLF with LF when the master 22 doesn't use CRLF 23 24""" 25 26import os, sys, stat, getopt 27 28# Interactivity options 29default_answer = "ask" 30create_files = "yes" 31create_directories = "no" 32write_slave = "ask" 33write_master = "ask" 34 35def main(): 36 global always_no, always_yes 37 global create_directories, write_master, write_slave 38 opts, args = getopt.getopt(sys.argv[1:], "nym:s:d:f:a:") 39 for o, a in opts: 40 if o == '-y': 41 default_answer = "yes" 42 if o == '-n': 43 default_answer = "no" 44 if o == '-s': 45 write_slave = a 46 if o == '-m': 47 write_master = a 48 if o == '-d': 49 create_directories = a 50 if o == '-f': 51 create_files = a 52 if o == '-a': 53 create_files = create_directories = write_slave = write_master = a 54 try: 55 [slave, master] = args 56 except ValueError: 57 print "usage: python", sys.argv[0] or "treesync.py", 58 print "[-n] [-y] [-m y|n|a] [-s y|n|a] [-d y|n|a] [-f n|y|a]", 59 print "slavedir masterdir" 60 return 61 process(slave, master) 62 63def process(slave, master): 64 cvsdir = os.path.join(master, "CVS") 65 if not os.path.isdir(cvsdir): 66 print "skipping master subdirectory", master 67 print "-- not under CVS" 68 return 69 print "-"*40 70 print "slave ", slave 71 print "master", master 72 if not os.path.isdir(slave): 73 if not okay("create slave directory %s?" % slave, 74 answer=create_directories): 75 print "skipping master subdirectory", master 76 print "-- no corresponding slave", slave 77 return 78 print "creating slave directory", slave 79 try: 80 os.mkdir(slave) 81 except os.error, msg: 82 print "can't make slave directory", slave, ":", msg 83 return 84 else: 85 print "made slave directory", slave 86 cvsdir = None 87 subdirs = [] 88 names = os.listdir(master) 89 for name in names: 90 mastername = os.path.join(master, name) 91 slavename = os.path.join(slave, name) 92 if name == "CVS": 93 cvsdir = mastername 94 else: 95 if os.path.isdir(mastername) and not os.path.islink(mastername): 96 subdirs.append((slavename, mastername)) 97 if cvsdir: 98 entries = os.path.join(cvsdir, "Entries") 99 for e in open(entries).readlines(): 100 words = e.split('/') 101 if words[0] == '' and words[1:]: 102 name = words[1] 103 s = os.path.join(slave, name) 104 m = os.path.join(master, name) 105 compare(s, m) 106 for (s, m) in subdirs: 107 process(s, m) 108 109def compare(slave, master): 110 try: 111 sf = open(slave, 'r') 112 except IOError: 113 sf = None 114 try: 115 mf = open(master, 'rb') 116 except IOError: 117 mf = None 118 if not sf: 119 if not mf: 120 print "Neither master nor slave exists", master 121 return 122 print "Creating missing slave", slave 123 copy(master, slave, answer=create_files) 124 return 125 if not mf: 126 print "Not updating missing master", master 127 return 128 if sf and mf: 129 if identical(sf, mf): 130 return 131 sft = mtime(sf) 132 mft = mtime(mf) 133 if mft > sft: 134 # Master is newer -- copy master to slave 135 sf.close() 136 mf.close() 137 print "Master ", master 138 print "is newer than slave", slave 139 copy(master, slave, answer=write_slave) 140 return 141 # Slave is newer -- copy slave to master 142 print "Slave is", sft-mft, "seconds newer than master" 143 # But first check what to do about CRLF 144 mf.seek(0) 145 fun = funnychars(mf) 146 mf.close() 147 sf.close() 148 if fun: 149 print "***UPDATING MASTER (BINARY COPY)***" 150 copy(slave, master, "rb", answer=write_master) 151 else: 152 print "***UPDATING MASTER***" 153 copy(slave, master, "r", answer=write_master) 154 155BUFSIZE = 16*1024 156 157def identical(sf, mf): 158 while 1: 159 sd = sf.read(BUFSIZE) 160 md = mf.read(BUFSIZE) 161 if sd != md: return 0 162 if not sd: break 163 return 1 164 165def mtime(f): 166 st = os.fstat(f.fileno()) 167 return st[stat.ST_MTIME] 168 169def funnychars(f): 170 while 1: 171 buf = f.read(BUFSIZE) 172 if not buf: break 173 if '\r' in buf or '\0' in buf: return 1 174 return 0 175 176def copy(src, dst, rmode="rb", wmode="wb", answer='ask'): 177 print "copying", src 178 print " to", dst 179 if not okay("okay to copy? ", answer): 180 return 181 f = open(src, rmode) 182 g = open(dst, wmode) 183 while 1: 184 buf = f.read(BUFSIZE) 185 if not buf: break 186 g.write(buf) 187 f.close() 188 g.close() 189 190def okay(prompt, answer='ask'): 191 answer = answer.strip().lower() 192 if not answer or answer[0] not in 'ny': 193 answer = raw_input(prompt) 194 answer = answer.strip().lower() 195 if not answer: 196 answer = default_answer 197 if answer[:1] == 'y': 198 return 1 199 if answer[:1] == 'n': 200 return 0 201 print "Yes or No please -- try again:" 202 return okay(prompt) 203 204if __name__ == '__main__': 205 main() 206