blob: 46f41a8703120455d78e3d543001242a5ede08ee [file] [log] [blame]
Guido van Rossum097c55a1995-04-27 18:07:07 +00001#! /usr/local/bin/python
2
Guido van Rossumc218a7e1995-04-27 23:33:43 +00003"""Remote CVS -- command line interface"""
Guido van Rossum097c55a1995-04-27 18:07:07 +00004
Guido van Rossumd1972af1995-06-21 01:02:06 +00005# XXX To do:
6#
Guido van Rossum903afd01995-06-23 22:33:57 +00007# Bugs:
8# - if the remote file is deleted, "rcvs update" will fail
9#
Guido van Rossumd1972af1995-06-21 01:02:06 +000010# Functionality:
Guido van Rossum903afd01995-06-23 22:33:57 +000011# - descend into directories (alraedy done for update)
Guido van Rossumd1972af1995-06-21 01:02:06 +000012# - cvs add; cvs rm
13# - commit new files
14# - conflict resolution
15# - cvs log
16# - other relevant commands?
17# - branches
18#
19# - Finesses:
20# - retain file mode's x bits
21# - complain when "nothing known about filename"
22# - edit log message the way CVS lets you edit it
23# - cvs diff -rREVA -rREVB
24# - send mail the way CVS sends it
25#
26# Performance:
27# - cache remote checksums (for every revision ever seen!)
28# - translate symbolic revisions to numeric revisions
29#
30# Reliability:
31# - remote locking
32#
33# Security:
34# - Authenticated RPC?
35
36
Guido van Rossumc218a7e1995-04-27 23:33:43 +000037from cvslib import CVS, File
Guido van Rossum5f07b841995-04-26 22:57:11 +000038import md5
39import os
40import string
41import sys
Guido van Rossumc218a7e1995-04-27 23:33:43 +000042from cmdfw import CommandFrameWork
Guido van Rossum5f07b841995-04-26 22:57:11 +000043
44
Guido van Rossumd1972af1995-06-21 01:02:06 +000045DEF_LOCAL = 1 # Default -l
46
47
Guido van Rossumc218a7e1995-04-27 23:33:43 +000048class MyFile(File):
Guido van Rossum5f07b841995-04-26 22:57:11 +000049
Guido van Rossumae21ced1995-04-28 14:32:26 +000050 def action(self):
51 """Return a code indicating the update status of this file.
52
53 The possible return values are:
Guido van Rossumc218a7e1995-04-27 23:33:43 +000054
Guido van Rossumae21ced1995-04-28 14:32:26 +000055 '=' -- everything's fine
56 '0' -- file doesn't exist anywhere
57 '?' -- exists locally only
58 'A' -- new locally
59 'R' -- deleted locally
60 'U' -- changed remotely, no changes locally
Guido van Rossum6bb4a511995-04-28 15:26:37 +000061 (includes new remotely or deleted remotely)
Guido van Rossumae21ced1995-04-28 14:32:26 +000062 'M' -- changed locally, no changes remotely
63 'C' -- conflict: changed locally as well as remotely
64 (includes cases where the file has been added
65 or removed locally and remotely)
Guido van Rossumba244681995-04-28 15:33:03 +000066 'D' -- deleted remotely
67 'N' -- new remotely
Guido van Rossum6bb4a511995-04-28 15:26:37 +000068 'r' -- get rid of entry
69 'c' -- create entry
70 'u' -- update entry
Guido van Rossum2f7ef911995-05-01 20:22:01 +000071
72 (and probably others :-)
Guido van Rossumae21ced1995-04-28 14:32:26 +000073 """
Guido van Rossumec8cfd41995-05-01 20:06:44 +000074 if not self.lseen:
75 self.getlocal()
76 if not self.rseen:
77 self.getremote()
Guido van Rossumae21ced1995-04-28 14:32:26 +000078 if not self.eseen:
Guido van Rossum2f7ef911995-05-01 20:22:01 +000079 if not self.lsum:
80 if not self.rsum: return '0' # Never heard of
Guido van Rossum6bb4a511995-04-28 15:26:37 +000081 else:
82 return 'N' # New remotely
Guido van Rossum2f7ef911995-05-01 20:22:01 +000083 else: # self.lsum
84 if not self.rsum: return '?' # Local only
Guido van Rossum6bb4a511995-04-28 15:26:37 +000085 # Local and remote, but no entry
86 if self.lsum == self.rsum:
87 return 'c' # Restore entry only
88 else: return 'C' # Real conflict
89 else: # self.eseen
Guido van Rossum2f7ef911995-05-01 20:22:01 +000090 if not self.lsum:
91 if self.edeleted:
92 if self.rsum: return 'R' # Removed
Guido van Rossum6bb4a511995-04-28 15:26:37 +000093 else: return 'r' # Get rid of entry
Guido van Rossum2f7ef911995-05-01 20:22:01 +000094 else: # not self.edeleted
95 if self.rsum:
Guido van Rossum6bb4a511995-04-28 15:26:37 +000096 print "warning:",
97 print self.file,
98 print "was lost"
99 return 'U'
100 else: return 'r' # Get rid of entry
Guido van Rossum2f7ef911995-05-01 20:22:01 +0000101 else: # self.lsum
102 if not self.rsum:
Guido van Rossum6bb4a511995-04-28 15:26:37 +0000103 if self.enew: return 'A' # New locally
104 else: return 'D' # Deleted remotely
Guido van Rossum2f7ef911995-05-01 20:22:01 +0000105 else: # self.rsum
Guido van Rossum6bb4a511995-04-28 15:26:37 +0000106 if self.enew:
107 if self.lsum == self.rsum:
108 return 'u'
109 else:
110 return 'C'
111 if self.lsum == self.esum:
112 if self.esum == self.rsum:
113 return '='
114 else:
115 return 'U'
116 elif self.esum == self.rsum:
117 return 'M'
118 elif self.lsum == self.rsum:
119 return 'u'
120 else:
121 return 'C'
Guido van Rossumae21ced1995-04-28 14:32:26 +0000122
123 def update(self):
124 code = self.action()
Guido van Rossum8b5e0fa1995-04-28 21:48:16 +0000125 if code == '=': return
Guido van Rossumae21ced1995-04-28 14:32:26 +0000126 print code, self.file
Guido van Rossumba244681995-04-28 15:33:03 +0000127 if code in ('U', 'N'):
Guido van Rossumae21ced1995-04-28 14:32:26 +0000128 self.get()
129 elif code == 'C':
130 print "%s: conflict resolution not yet implemented" % \
131 self.file
Guido van Rossumba244681995-04-28 15:33:03 +0000132 elif code == 'D':
Guido van Rossum330e8841995-04-28 17:56:32 +0000133 remove(self.file)
Guido van Rossumba244681995-04-28 15:33:03 +0000134 self.eseen = 0
135 elif code == 'r':
136 self.eseen = 0
137 elif code in ('c', 'u'):
Guido van Rossumd22f59f1995-04-28 15:37:22 +0000138 self.eseen = 1
Guido van Rossumba244681995-04-28 15:33:03 +0000139 self.erev = self.rrev
140 self.enew = 0
141 self.edeleted = 0
142 self.esum = self.rsum
143 self.emtime, self.ectime = os.stat(self.file)[-2:]
Guido van Rossumd22f59f1995-04-28 15:37:22 +0000144 self.extra = ''
Guido van Rossumae21ced1995-04-28 14:32:26 +0000145
146 def commit(self, message = ""):
147 code = self.action()
148 if code in ('A', 'M'):
149 self.put(message)
150 elif code == 'R':
151 print "%s: committing removes not yet implemented" % \
152 self.file
153 elif code == 'C':
154 print "%s: conflict resolution not yet implemented" % \
155 self.file
156
Guido van Rossum330e8841995-04-28 17:56:32 +0000157 def diff(self, opts = []):
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000158 self.action() # To update lseen, rseen
Guido van Rossum330e8841995-04-28 17:56:32 +0000159 flags = ''
Guido van Rossumd1972af1995-06-21 01:02:06 +0000160 rev = self.rrev
161 # XXX should support two rev options too!
Guido van Rossum330e8841995-04-28 17:56:32 +0000162 for o, a in opts:
Guido van Rossumd1972af1995-06-21 01:02:06 +0000163 if o == '-r':
164 rev = a
165 else:
166 flags = flags + ' ' + o + a
167 if rev == self.rrev and self.lsum == self.rsum:
168 return
Guido van Rossum330e8841995-04-28 17:56:32 +0000169 flags = flags[1:]
170 fn = self.file
Guido van Rossumd1972af1995-06-21 01:02:06 +0000171 data = self.proxy.get((fn, rev))
172 sum = md5.new(data).digest()
173 if self.lsum == sum:
174 return
175 import tempfile
Guido van Rossum330e8841995-04-28 17:56:32 +0000176 tfn = tempfile.mktemp()
177 try:
178 tf = open(tfn, 'w')
179 tf.write(data)
180 tf.close()
Guido van Rossumd1972af1995-06-21 01:02:06 +0000181 print 'diff %s -r%s %s' % (flags, rev, fn)
Guido van Rossum330e8841995-04-28 17:56:32 +0000182 sts = os.system('diff %s %s %s' % (flags, tfn, fn))
183 if sts:
184 print '='*70
185 finally:
186 remove(tfn)
187
Guido van Rossumae21ced1995-04-28 14:32:26 +0000188 def commitcheck(self):
189 return self.action() != 'C'
190
191 def put(self, message = ""):
Guido van Rossumbafc14d1995-04-28 15:41:51 +0000192 print "Checking in", self.file, "..."
193 data = open(self.file).read()
Guido van Rossum903afd01995-06-23 22:33:57 +0000194 self.proxy.lock(self.file)
Guido van Rossumbafc14d1995-04-28 15:41:51 +0000195 messages = self.proxy.put(self.file, data, message)
196 if messages:
197 print messages
Guido van Rossum8b5e0fa1995-04-28 21:48:16 +0000198 self.setentry(self.proxy.head(self.file), self.lsum)
Guido van Rossumae21ced1995-04-28 14:32:26 +0000199
200 def get(self):
201 data = self.proxy.get(self.file)
202 f = open(self.file, 'w')
203 f.write(data)
204 f.close()
Guido van Rossum8b5e0fa1995-04-28 21:48:16 +0000205 self.setentry(self.rrev, self.rsum)
206
207 def setentry(self, erev, esum):
208 self.eseen = 0 # While we're hacking...
209 self.esum = esum
Guido van Rossumae21ced1995-04-28 14:32:26 +0000210 self.emtime, self.ectime = os.stat(self.file)[-2:]
Guido van Rossum8b5e0fa1995-04-28 21:48:16 +0000211 self.erev = erev
Guido van Rossumae21ced1995-04-28 14:32:26 +0000212 self.enew = 0
213 self.edeleted = 0
Guido van Rossum8b5e0fa1995-04-28 21:48:16 +0000214 self.eseen = 1 # Done
Guido van Rossumd1972af1995-06-21 01:02:06 +0000215 self.extra = ''
216
217
218SENDMAIL = "/usr/lib/sendmail -t"
219MAILFORM = """To: %s
220Subject: CVS changes: %s
221
222...Message from rcvs...
223
224Committed files:
225 %s
226
227Log message:
228 %s
229"""
Guido van Rossum5f07b841995-04-26 22:57:11 +0000230
231
Guido van Rossumc218a7e1995-04-27 23:33:43 +0000232class RCVS(CVS):
233
234 FileClass = MyFile
235
236 def __init__(self):
237 CVS.__init__(self)
238
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000239 def update(self, files):
240 for e in self.whichentries(files, 1):
241 e.update()
242
243 def commit(self, files, message = ""):
244 list = self.whichentries(files)
Guido van Rossumd1972af1995-06-21 01:02:06 +0000245 if not list: return
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000246 ok = 1
247 for e in list:
248 if not e.commitcheck():
249 ok = 0
250 if not ok:
251 print "correct above errors first"
252 return
253 if not message:
254 message = raw_input("One-liner: ")
255 for e in list:
256 e.commit(message)
Guido van Rossumd1972af1995-06-21 01:02:06 +0000257 self.mailinfo(files, message)
258
259 def mailinfo(self, files, message = ""):
260 towhom = "sjoerd@cwi.nl, jack@cwi.nl" # XXX
261 mailtext = MAILFORM % (towhom, string.join(files),
262 string.join(files), message)
263 print '-'*70
264 print mailtext
265 print '-'*70
266 ok = raw_input("OK to mail to %s? " % towhom)
267 if string.lower(string.strip(ok)) in ('y', 'ye', 'yes'):
268 p = os.popen(SENDMAIL, "w")
269 p.write(mailtext)
270 sts = p.close()
271 if sts:
272 print "Sendmail exit status %s" % str(sts)
273 else:
274 print "Mail sent."
275 else:
276 print "No mail sent."
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000277
278 def report(self, files):
279 for e in self.whichentries(files):
280 e.report()
281
282 def diff(self, files, opts):
283 for e in self.whichentries(files):
284 e.diff(opts)
285
286 def whichentries(self, files, localfilestoo = 0):
287 if files:
288 list = []
Guido van Rossumae21ced1995-04-28 14:32:26 +0000289 for file in files:
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000290 if self.entries.has_key(file):
291 e = self.entries[file]
292 else:
293 e = self.FileClass(file)
294 self.entries[file] = e
295 list.append(e)
296 else:
297 list = self.entries.values()
298 for file in self.proxy.listfiles():
299 if self.entries.has_key(file):
300 continue
301 e = self.FileClass(file)
302 self.entries[file] = e
303 list.append(e)
304 if localfilestoo:
305 for file in os.listdir(os.curdir):
306 if not self.entries.has_key(file) \
307 and not self.ignored(file):
308 e = self.FileClass(file)
309 self.entries[file] = e
310 list.append(e)
311 list.sort()
312 if self.proxy:
313 for e in list:
314 if e.proxy is None:
315 e.proxy = self.proxy
316 return list
Guido van Rossumc218a7e1995-04-27 23:33:43 +0000317
318
319class rcvs(CommandFrameWork):
320
Guido van Rossum903afd01995-06-23 22:33:57 +0000321 GlobalFlags = 'd:h:p:qvL'
Guido van Rossumc218a7e1995-04-27 23:33:43 +0000322 UsageMessage = \
Guido van Rossum2d2a60e1995-04-28 19:24:50 +0000323"usage: rcvs [-d directory] [-h host] [-p port] [-q] [-v] [subcommand arg ...]"
324 PostUsageMessage = \
325 "If no subcommand is given, the status of all files is listed"
Guido van Rossumc218a7e1995-04-27 23:33:43 +0000326
327 def __init__(self):
328 """Constructor."""
329 CommandFrameWork.__init__(self)
330 self.proxy = None
331 self.cvs = RCVS()
332
Guido van Rossumd1972af1995-06-21 01:02:06 +0000333 def recurse(self):
334 if self.proxy:
335 self.proxy._close()
336 self.proxy = None
337 names = os.listdir(os.curdir)
338 for name in names:
339 if name == os.curdir or name == os.pardir:
340 continue
341 if name == "CVS":
342 continue
343 if not os.path.isdir(name):
344 continue
345 if os.path.islink(name):
346 continue
347 print "--- entering subdirectory", name, "---"
348 os.chdir(name)
349 try:
350 if os.path.isdir("CVS"):
351 self.__class__().run()
352 else:
353 self.recurse()
354 finally:
355 os.chdir(os.pardir)
356 print "--- left subdirectory", name, "---"
357
Guido van Rossumc218a7e1995-04-27 23:33:43 +0000358 def options(self, opts):
359 self.opts = opts
360
361 def ready(self):
362 import rcsclient
363 self.proxy = rcsclient.openrcsclient(self.opts)
364 self.cvs.setproxy(self.proxy)
365 self.cvs.getentries()
Guido van Rossumc218a7e1995-04-27 23:33:43 +0000366
367 def default(self):
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000368 self.cvs.report([])
Guido van Rossumc218a7e1995-04-27 23:33:43 +0000369
370 def do_update(self, opts, files):
Guido van Rossumd1972af1995-06-21 01:02:06 +0000371 """update [-l] [-R] [file] ..."""
372 local = DEF_LOCAL
373 for o, a in opts:
374 if o == '-l': local = 1
375 if o == '-R': local = 0
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000376 self.cvs.update(files)
Guido van Rossum6bb4a511995-04-28 15:26:37 +0000377 self.cvs.putentries()
Guido van Rossumd1972af1995-06-21 01:02:06 +0000378 if not local and not files:
379 self.recurse()
380 flags_update = '-lR'
Guido van Rossum330e8841995-04-28 17:56:32 +0000381 do_up = do_update
Guido van Rossumd1972af1995-06-21 01:02:06 +0000382 flags_up = flags_update
Guido van Rossum5f07b841995-04-26 22:57:11 +0000383
Guido van Rossumae21ced1995-04-28 14:32:26 +0000384 def do_commit(self, opts, files):
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000385 """commit [-m message] [file] ..."""
386 message = ""
387 for o, a in opts:
388 if o == '-m': message = a
389 self.cvs.commit(files, message)
Guido van Rossum6bb4a511995-04-28 15:26:37 +0000390 self.cvs.putentries()
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000391 flags_commit = 'm:'
Guido van Rossumd1972af1995-06-21 01:02:06 +0000392 do_com = do_commit
393 flags_com = flags_commit
Guido van Rossum330e8841995-04-28 17:56:32 +0000394
395 def do_diff(self, opts, files):
396 """diff [difflags] [file] ..."""
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000397 self.cvs.diff(files, opts)
Guido van Rossumd1972af1995-06-21 01:02:06 +0000398 flags_diff = 'cbitwcefhnlr:sD:S:'
Guido van Rossum330e8841995-04-28 17:56:32 +0000399 do_dif = do_diff
Guido van Rossumd1972af1995-06-21 01:02:06 +0000400 flags_dif = flags_diff
Guido van Rossum330e8841995-04-28 17:56:32 +0000401
402
403
404def remove(fn):
405 try:
406 os.unlink(fn)
407 except os.error:
408 pass
Guido van Rossumae21ced1995-04-28 14:32:26 +0000409
Guido van Rossum5f07b841995-04-26 22:57:11 +0000410
Guido van Rossumdeb627c1995-04-27 21:28:53 +0000411def main():
Guido van Rossumc218a7e1995-04-27 23:33:43 +0000412 rcvs().run()
Guido van Rossum097c55a1995-04-27 18:07:07 +0000413
Guido van Rossum5f07b841995-04-26 22:57:11 +0000414
415if __name__ == "__main__":
Guido van Rossumdeb627c1995-04-27 21:28:53 +0000416 main()