Home | History | Annotate | Download | only in scripts
      1 #! /usr/bin/env python3
      2 
      3 """Script to synchronize two source trees.
      4 
      5 Invoke with two arguments:
      6 
      7 python treesync.py slave master
      8 
      9 The assumption is that "master" contains CVS administration while
     10 slave doesn't.  All files in the slave tree that have a CVS/Entries
     11 entry 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 
     26 import os, sys, stat, getopt
     27 
     28 # Interactivity options
     29 default_answer = "ask"
     30 create_files = "yes"
     31 create_directories = "no"
     32 write_slave = "ask"
     33 write_master = "ask"
     34 
     35 def 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 
     63 def 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 
    109 def 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 
    155 BUFSIZE = 16*1024
    156 
    157 def 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 
    165 def mtime(f):
    166     st = os.fstat(f.fileno())
    167     return st[stat.ST_MTIME]
    168 
    169 def 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 
    176 def 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 
    190 def raw_input(prompt):
    191     sys.stdout.write(prompt)
    192     sys.stdout.flush()
    193     return sys.stdin.readline()
    194 
    195 def 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 
    209 if __name__ == '__main__':
    210     main()
    211