Home | History | Annotate | Download | only in scripts
      1 #! /usr/bin/env python
      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",
     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 
     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 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 
    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 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 
    204 if __name__ == '__main__':
    205     main()
    206