1#! /usr/bin/env python3 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", end=' ') 58 print("[-n] [-y] [-m y|n|a] [-s y|n|a] [-d y|n|a] [-f n|y|a]", end=' ') 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 OSError as 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 raw_input(prompt): 191 sys.stdout.write(prompt) 192 sys.stdout.flush() 193 return sys.stdin.readline() 194 195def okay(prompt, answer='ask'): 196 answer = answer.strip().lower() 197 if not answer or answer[0] not in 'ny': 198 answer = input(prompt) 199 answer = answer.strip().lower() 200 if not answer: 201 answer = default_answer 202 if answer[:1] == 'y': 203 return 1 204 if answer[:1] == 'n': 205 return 0 206 print("Yes or No please -- try again:") 207 return okay(prompt) 208 209if __name__ == '__main__': 210 main() 211