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