treesync.py 5.67 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
#! /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:
Guido van Rossum's avatar
Guido van Rossum committed
15 16 17 18 19
            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
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
    
    normalizing the slave means replacing CRLF with LF when the master
    doesn't use CRLF

"""

import os, sys, stat, string, 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:
Guido van Rossum's avatar
Guido van Rossum committed
40 41 42 43 44 45 46 47 48 49 50 51 52 53
        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
54
    try:
Guido van Rossum's avatar
Guido van Rossum committed
55
        [slave, master] = args
56
    except ValueError:
Guido van Rossum's avatar
Guido van Rossum committed
57 58 59 60
        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
61 62 63 64 65
    process(slave, master)
    
def process(slave, master):
    cvsdir = os.path.join(master, "CVS")
    if not os.path.isdir(cvsdir):
Guido van Rossum's avatar
Guido van Rossum committed
66 67 68
        print "skipping master subdirectory", master
        print "-- not under CVS"
        return
69 70 71 72
    print "-"*40
    print "slave ", slave
    print "master", master
    if not os.path.isdir(slave):
Guido van Rossum's avatar
Guido van Rossum committed
73 74 75 76 77 78 79 80 81 82 83 84 85
        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
86 87 88 89
    cvsdir = None
    subdirs = []
    names = os.listdir(master)
    for name in names:
Guido van Rossum's avatar
Guido van Rossum committed
90 91 92 93 94 95 96
        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))
97
    if cvsdir:
Guido van Rossum's avatar
Guido van Rossum committed
98 99 100 101 102 103 104 105
        entries = os.path.join(cvsdir, "Entries")
        for e in open(entries).readlines():
            words = string.split(e, '/')
            if words[0] == '' and words[1:]:
                name = words[1]
                s = os.path.join(slave, name)
                m = os.path.join(master, name)
                compare(s, m)
106
    for (s, m) in subdirs:
Guido van Rossum's avatar
Guido van Rossum committed
107
        process(s, m)
108 109 110

def compare(slave, master):
    try:
Guido van Rossum's avatar
Guido van Rossum committed
111
        sf = open(slave, 'r')
112
    except IOError:
Guido van Rossum's avatar
Guido van Rossum committed
113
        sf = None
114
    try:
Guido van Rossum's avatar
Guido van Rossum committed
115
        mf = open(master, 'rb')
116
    except IOError:
Guido van Rossum's avatar
Guido van Rossum committed
117
        mf = None
118
    if not sf:
Guido van Rossum's avatar
Guido van Rossum committed
119 120 121 122 123 124
        if not mf:
            print "Neither master nor slave exists", master
            return
        print "Creating missing slave", slave
        copy(master, slave, answer=create_files)
        return
125
    if not mf:
Guido van Rossum's avatar
Guido van Rossum committed
126 127
        print "Not updating missing master", master
        return
128
    if sf and mf:
Guido van Rossum's avatar
Guido van Rossum committed
129 130
        if identical(sf, mf):
            return
131 132 133
    sft = mtime(sf)
    mft = mtime(mf)
    if mft > sft:
Guido van Rossum's avatar
Guido van Rossum committed
134 135 136 137 138 139 140
        # 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
141
    # Slave is newer -- copy slave to master
142
    print "Slave is", sft-mft, "seconds newer than master"
143 144 145 146 147 148
    # But first check what to do about CRLF
    mf.seek(0)
    fun = funnychars(mf)
    mf.close()
    sf.close()
    if fun:
Guido van Rossum's avatar
Guido van Rossum committed
149 150
        print "***UPDATING MASTER (BINARY COPY)***"
        copy(slave, master, "rb", answer=write_master)
151
    else:
Guido van Rossum's avatar
Guido van Rossum committed
152 153
        print "***UPDATING MASTER***"
        copy(slave, master, "r", answer=write_master)
154 155 156 157 158

BUFSIZE = 16*1024

def identical(sf, mf):
    while 1:
Guido van Rossum's avatar
Guido van Rossum committed
159 160 161 162
        sd = sf.read(BUFSIZE)
        md = mf.read(BUFSIZE)
        if sd != md: return 0
        if not sd: break
163 164 165 166 167 168 169 170
    return 1

def mtime(f):
    st = os.fstat(f.fileno())
    return st[stat.ST_MTIME]

def funnychars(f):
    while 1:
Guido van Rossum's avatar
Guido van Rossum committed
171 172 173
        buf = f.read(BUFSIZE)
        if not buf: break
        if '\r' in buf or '\0' in buf: return 1
174 175 176 177 178 179
    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):
Guido van Rossum's avatar
Guido van Rossum committed
180
        return
181 182 183
    f = open(src, rmode)
    g = open(dst, wmode)
    while 1:
Guido van Rossum's avatar
Guido van Rossum committed
184 185 186
        buf = f.read(BUFSIZE)
        if not buf: break
        g.write(buf)
187 188 189 190 191 192
    f.close()
    g.close()

def okay(prompt, answer='ask'):
    answer = string.lower(string.strip(answer))
    if not answer or answer[0] not in 'ny':
Guido van Rossum's avatar
Guido van Rossum committed
193 194 195 196
        answer = raw_input(prompt)
        answer = string.lower(string.strip(answer))
        if not answer:
            answer = default_answer
197
    if answer[:1] == 'y':
Guido van Rossum's avatar
Guido van Rossum committed
198
        return 1
199
    if answer[:1] == 'n':
Guido van Rossum's avatar
Guido van Rossum committed
200
        return 0
201 202 203 204
    print "Yes or No please -- try again:"
    return okay(prompt)

main()