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()
|
---|