#! /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() |