| #! /usr/bin/env python | |
| """Script to synchronize two source trees. | |
| Invoke with two arguments: | |
| python treesync.py slave master | |
| The assumption is that "master" contains CVS administration while | |
| slave doesn't. All files in the slave tree that have a CVS/Entries | |
| entry in the master tree are synchronized. This means: | |
| If the files differ: | |
| if the slave file is newer: | |
| normalize the slave file | |
| if the files still differ: | |
| copy the slave to the master | |
| else (the master is newer): | |
| copy the master to the slave | |
| normalizing the slave means replacing CRLF with LF when the master | |
| doesn't use CRLF | |
| """ | |
| import os, sys, stat, getopt | |
| # Interactivity options | |
| default_answer = "ask" | |
| create_files = "yes" | |
| create_directories = "no" | |
| write_slave = "ask" | |
| write_master = "ask" | |
| def main(): | |
| global always_no, always_yes | |
| global create_directories, write_master, write_slave | |
| opts, args = getopt.getopt(sys.argv[1:], "nym:s:d:f:a:") | |
| for o, a in opts: | |
| if o == '-y': | |
| default_answer = "yes" | |
| if o == '-n': | |
| default_answer = "no" | |
| if o == '-s': | |
| write_slave = a | |
| if o == '-m': | |
| write_master = a | |
| if o == '-d': | |
| create_directories = a | |
| if o == '-f': | |
| create_files = a | |
| if o == '-a': | |
| create_files = create_directories = write_slave = write_master = a | |
| try: | |
| [slave, master] = args | |
| except ValueError: | |
| print "usage: python", sys.argv[0] or "treesync.py", | |
| print "[-n] [-y] [-m y|n|a] [-s y|n|a] [-d y|n|a] [-f n|y|a]", | |
| print "slavedir masterdir" | |
| return | |
| process(slave, master) | |
| def process(slave, master): | |
| cvsdir = os.path.join(master, "CVS") | |
| if not os.path.isdir(cvsdir): | |
| print "skipping master subdirectory", master | |
| print "-- not under CVS" | |
| return | |
| print "-"*40 | |
| print "slave ", slave | |
| print "master", master | |
| if not os.path.isdir(slave): | |
| if not okay("create slave directory %s?" % slave, | |
| answer=create_directories): | |
| print "skipping master subdirectory", master | |
| print "-- no corresponding slave", slave | |
| return | |
| print "creating slave directory", slave | |
| try: | |
| os.mkdir(slave) | |
| except os.error, msg: | |
| print "can't make slave directory", slave, ":", msg | |
| return | |
| else: | |
| print "made slave directory", slave | |
| cvsdir = None | |
| subdirs = [] | |
| names = os.listdir(master) | |
| for name in names: | |
| mastername = os.path.join(master, name) | |
| slavename = os.path.join(slave, name) | |
| if name == "CVS": | |
| cvsdir = mastername | |
| else: | |
| if os.path.isdir(mastername) and not os.path.islink(mastername): | |
| subdirs.append((slavename, mastername)) | |
| if cvsdir: | |
| entries = os.path.join(cvsdir, "Entries") | |
| for e in open(entries).readlines(): | |
| words = e.split('/') | |
| if words[0] == '' and words[1:]: | |
| name = words[1] | |
| s = os.path.join(slave, name) | |
| m = os.path.join(master, name) | |
| compare(s, m) | |
| for (s, m) in subdirs: | |
| process(s, m) | |
| def compare(slave, master): | |
| try: | |
| sf = open(slave, 'r') | |
| except IOError: | |
| sf = None | |
| try: | |
| mf = open(master, 'rb') | |
| except IOError: | |
| mf = None | |
| if not sf: | |
| if not mf: | |
| print "Neither master nor slave exists", master | |
| return | |
| print "Creating missing slave", slave | |
| copy(master, slave, answer=create_files) | |
| return | |
| if not mf: | |
| print "Not updating missing master", master | |
| return | |
| if sf and mf: | |
| if identical(sf, mf): | |
| return | |
| sft = mtime(sf) | |
| mft = mtime(mf) | |
| if mft > sft: | |
| # Master is newer -- copy master to slave | |
| sf.close() | |
| mf.close() | |
| print "Master ", master | |
| print "is newer than slave", slave | |
| copy(master, slave, answer=write_slave) | |
| return | |
| # Slave is newer -- copy slave to master | |
| print "Slave is", sft-mft, "seconds newer than master" | |
| # But first check what to do about CRLF | |
| mf.seek(0) | |
| fun = funnychars(mf) | |
| mf.close() | |
| sf.close() | |
| if fun: | |
| print "***UPDATING MASTER (BINARY COPY)***" | |
| copy(slave, master, "rb", answer=write_master) | |
| else: | |
| print "***UPDATING MASTER***" | |
| copy(slave, master, "r", answer=write_master) | |
| BUFSIZE = 16*1024 | |
| def identical(sf, mf): | |
| while 1: | |
| sd = sf.read(BUFSIZE) | |
| md = mf.read(BUFSIZE) | |
| if sd != md: return 0 | |
| if not sd: break | |
| return 1 | |
| def mtime(f): | |
| st = os.fstat(f.fileno()) | |
| return st[stat.ST_MTIME] | |
| def funnychars(f): | |
| while 1: | |
| buf = f.read(BUFSIZE) | |
| if not buf: break | |
| if '\r' in buf or '\0' in buf: return 1 | |
| return 0 | |
| def copy(src, dst, rmode="rb", wmode="wb", answer='ask'): | |
| print "copying", src | |
| print " to", dst | |
| if not okay("okay to copy? ", answer): | |
| return | |
| f = open(src, rmode) | |
| g = open(dst, wmode) | |
| while 1: | |
| buf = f.read(BUFSIZE) | |
| if not buf: break | |
| g.write(buf) | |
| f.close() | |
| g.close() | |
| def okay(prompt, answer='ask'): | |
| answer = answer.strip().lower() | |
| if not answer or answer[0] not in 'ny': | |
| answer = raw_input(prompt) | |
| answer = answer.strip().lower() | |
| if not answer: | |
| answer = default_answer | |
| if answer[:1] == 'y': | |
| return 1 | |
| if answer[:1] == 'n': | |
| return 0 | |
| print "Yes or No please -- try again:" | |
| return okay(prompt) | |
| if __name__ == '__main__': | |
| main() |