blob: 9129c28158dad57e20e56156efeb33006e82fa3f [file] [log] [blame]
Guido van Rossumf06ee5f1996-11-27 19:52:01 +00001#! /usr/bin/env python
Guido van Rossum097c55a1995-04-27 18:07:07 +00002
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 Rossuma79f5a31995-07-18 18:34:34 +000011# - cvs rm
12# - descend into directories (alraedy done for update)
13# - conflict resolution
Guido van Rossumd1972af1995-06-21 01:02:06 +000014# - other relevant commands?
15# - branches
16#
17# - Finesses:
18# - retain file mode's x bits
19# - complain when "nothing known about filename"
20# - edit log message the way CVS lets you edit it
21# - cvs diff -rREVA -rREVB
22# - send mail the way CVS sends it
23#
24# Performance:
25# - cache remote checksums (for every revision ever seen!)
26# - translate symbolic revisions to numeric revisions
27#
28# Reliability:
29# - remote locking
30#
31# Security:
32# - Authenticated RPC?
33
34
Guido van Rossumc218a7e1995-04-27 23:33:43 +000035from cvslib import CVS, File
Guido van Rossum5f07b841995-04-26 22:57:11 +000036import md5
37import os
38import string
39import sys
Guido van Rossumc218a7e1995-04-27 23:33:43 +000040from cmdfw import CommandFrameWork
Guido van Rossum5f07b841995-04-26 22:57:11 +000041
42
Guido van Rossumd1972af1995-06-21 01:02:06 +000043DEF_LOCAL = 1 # Default -l
44
45
Guido van Rossumc218a7e1995-04-27 23:33:43 +000046class MyFile(File):
Guido van Rossum5f07b841995-04-26 22:57:11 +000047
Guido van Rossumae21ced1995-04-28 14:32:26 +000048 def action(self):
49 """Return a code indicating the update status of this file.
50
51 The possible return values are:
Guido van Rossumc218a7e1995-04-27 23:33:43 +000052
Guido van Rossumae21ced1995-04-28 14:32:26 +000053 '=' -- everything's fine
54 '0' -- file doesn't exist anywhere
55 '?' -- exists locally only
56 'A' -- new locally
57 'R' -- deleted locally
58 'U' -- changed remotely, no changes locally
Guido van Rossum6bb4a511995-04-28 15:26:37 +000059 (includes new remotely or deleted remotely)
Guido van Rossumae21ced1995-04-28 14:32:26 +000060 'M' -- changed locally, no changes remotely
61 'C' -- conflict: changed locally as well as remotely
62 (includes cases where the file has been added
63 or removed locally and remotely)
Guido van Rossumba244681995-04-28 15:33:03 +000064 'D' -- deleted remotely
65 'N' -- new remotely
Guido van Rossum6bb4a511995-04-28 15:26:37 +000066 'r' -- get rid of entry
67 'c' -- create entry
68 'u' -- update entry
Guido van Rossum2f7ef911995-05-01 20:22:01 +000069
70 (and probably others :-)
Guido van Rossumae21ced1995-04-28 14:32:26 +000071 """
Guido van Rossumec8cfd41995-05-01 20:06:44 +000072 if not self.lseen:
73 self.getlocal()
74 if not self.rseen:
75 self.getremote()
Guido van Rossumae21ced1995-04-28 14:32:26 +000076 if not self.eseen:
Guido van Rossum2f7ef911995-05-01 20:22:01 +000077 if not self.lsum:
78 if not self.rsum: return '0' # Never heard of
Guido van Rossum6bb4a511995-04-28 15:26:37 +000079 else:
80 return 'N' # New remotely
Guido van Rossum2f7ef911995-05-01 20:22:01 +000081 else: # self.lsum
82 if not self.rsum: return '?' # Local only
Guido van Rossum6bb4a511995-04-28 15:26:37 +000083 # Local and remote, but no entry
84 if self.lsum == self.rsum:
85 return 'c' # Restore entry only
86 else: return 'C' # Real conflict
87 else: # self.eseen
Guido van Rossum2f7ef911995-05-01 20:22:01 +000088 if not self.lsum:
89 if self.edeleted:
90 if self.rsum: return 'R' # Removed
Guido van Rossum6bb4a511995-04-28 15:26:37 +000091 else: return 'r' # Get rid of entry
Guido van Rossum2f7ef911995-05-01 20:22:01 +000092 else: # not self.edeleted
93 if self.rsum:
Guido van Rossum6bb4a511995-04-28 15:26:37 +000094 print "warning:",
95 print self.file,
96 print "was lost"
97 return 'U'
98 else: return 'r' # Get rid of entry
Guido van Rossum2f7ef911995-05-01 20:22:01 +000099 else: # self.lsum
100 if not self.rsum:
Guido van Rossum6bb4a511995-04-28 15:26:37 +0000101 if self.enew: return 'A' # New locally
102 else: return 'D' # Deleted remotely
Guido van Rossum2f7ef911995-05-01 20:22:01 +0000103 else: # self.rsum
Guido van Rossum6bb4a511995-04-28 15:26:37 +0000104 if self.enew:
105 if self.lsum == self.rsum:
106 return 'u'
107 else:
108 return 'C'
109 if self.lsum == self.esum:
110 if self.esum == self.rsum:
111 return '='
112 else:
113 return 'U'
114 elif self.esum == self.rsum:
115 return 'M'
116 elif self.lsum == self.rsum:
117 return 'u'
118 else:
119 return 'C'
Guido van Rossumae21ced1995-04-28 14:32:26 +0000120
121 def update(self):
122 code = self.action()
Guido van Rossum8b5e0fa1995-04-28 21:48:16 +0000123 if code == '=': return
Guido van Rossumae21ced1995-04-28 14:32:26 +0000124 print code, self.file
Guido van Rossumba244681995-04-28 15:33:03 +0000125 if code in ('U', 'N'):
Guido van Rossumae21ced1995-04-28 14:32:26 +0000126 self.get()
127 elif code == 'C':
128 print "%s: conflict resolution not yet implemented" % \
129 self.file
Guido van Rossumba244681995-04-28 15:33:03 +0000130 elif code == 'D':
Guido van Rossum330e8841995-04-28 17:56:32 +0000131 remove(self.file)
Guido van Rossumba244681995-04-28 15:33:03 +0000132 self.eseen = 0
133 elif code == 'r':
134 self.eseen = 0
135 elif code in ('c', 'u'):
Guido van Rossumd22f59f1995-04-28 15:37:22 +0000136 self.eseen = 1
Guido van Rossumba244681995-04-28 15:33:03 +0000137 self.erev = self.rrev
138 self.enew = 0
139 self.edeleted = 0
140 self.esum = self.rsum
141 self.emtime, self.ectime = os.stat(self.file)[-2:]
Guido van Rossumd22f59f1995-04-28 15:37:22 +0000142 self.extra = ''
Guido van Rossumae21ced1995-04-28 14:32:26 +0000143
144 def commit(self, message = ""):
145 code = self.action()
146 if code in ('A', 'M'):
147 self.put(message)
Guido van Rossum7bde92b1995-10-07 19:47:26 +0000148 return 1
Guido van Rossumae21ced1995-04-28 14:32:26 +0000149 elif code == 'R':
150 print "%s: committing removes not yet implemented" % \
151 self.file
152 elif code == 'C':
153 print "%s: conflict resolution not yet implemented" % \
154 self.file
155
Guido van Rossum330e8841995-04-28 17:56:32 +0000156 def diff(self, opts = []):
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000157 self.action() # To update lseen, rseen
Guido van Rossum330e8841995-04-28 17:56:32 +0000158 flags = ''
Guido van Rossumd1972af1995-06-21 01:02:06 +0000159 rev = self.rrev
160 # XXX should support two rev options too!
Guido van Rossum330e8841995-04-28 17:56:32 +0000161 for o, a in opts:
Guido van Rossumd1972af1995-06-21 01:02:06 +0000162 if o == '-r':
163 rev = a
164 else:
165 flags = flags + ' ' + o + a
166 if rev == self.rrev and self.lsum == self.rsum:
167 return
Guido van Rossum330e8841995-04-28 17:56:32 +0000168 flags = flags[1:]
169 fn = self.file
Guido van Rossumd1972af1995-06-21 01:02:06 +0000170 data = self.proxy.get((fn, rev))
171 sum = md5.new(data).digest()
172 if self.lsum == sum:
173 return
174 import tempfile
Guido van Rossum330e8841995-04-28 17:56:32 +0000175 tfn = tempfile.mktemp()
176 try:
177 tf = open(tfn, 'w')
178 tf.write(data)
179 tf.close()
Guido van Rossumd1972af1995-06-21 01:02:06 +0000180 print 'diff %s -r%s %s' % (flags, rev, fn)
Guido van Rossum330e8841995-04-28 17:56:32 +0000181 sts = os.system('diff %s %s %s' % (flags, tfn, fn))
182 if sts:
183 print '='*70
184 finally:
185 remove(tfn)
186
Guido van Rossumae21ced1995-04-28 14:32:26 +0000187 def commitcheck(self):
188 return self.action() != 'C'
189
190 def put(self, message = ""):
Guido van Rossumbafc14d1995-04-28 15:41:51 +0000191 print "Checking in", self.file, "..."
192 data = open(self.file).read()
Guido van Rossuma79f5a31995-07-18 18:34:34 +0000193 if not self.enew:
194 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
Guido van Rossum7bde92b1995-10-07 19:47:26 +0000207 def log(self, otherflags):
208 print self.proxy.log(self.file, otherflags)
209
Guido van Rossuma79f5a31995-07-18 18:34:34 +0000210 def add(self):
211 self.eseen = 0 # While we're hacking...
212 self.esum = self.lsum
213 self.emtime, self.ectime = 0, 0
214 self.erev = ''
215 self.enew = 1
216 self.edeleted = 0
217 self.eseen = 1 # Done
218 self.extra = ''
219
Guido van Rossum8b5e0fa1995-04-28 21:48:16 +0000220 def setentry(self, erev, esum):
221 self.eseen = 0 # While we're hacking...
222 self.esum = esum
Guido van Rossumae21ced1995-04-28 14:32:26 +0000223 self.emtime, self.ectime = os.stat(self.file)[-2:]
Guido van Rossum8b5e0fa1995-04-28 21:48:16 +0000224 self.erev = erev
Guido van Rossumae21ced1995-04-28 14:32:26 +0000225 self.enew = 0
226 self.edeleted = 0
Guido van Rossum8b5e0fa1995-04-28 21:48:16 +0000227 self.eseen = 1 # Done
Guido van Rossumd1972af1995-06-21 01:02:06 +0000228 self.extra = ''
229
230
231SENDMAIL = "/usr/lib/sendmail -t"
232MAILFORM = """To: %s
233Subject: CVS changes: %s
234
235...Message from rcvs...
236
237Committed files:
238 %s
239
240Log message:
241 %s
242"""
Guido van Rossum5f07b841995-04-26 22:57:11 +0000243
244
Guido van Rossumc218a7e1995-04-27 23:33:43 +0000245class RCVS(CVS):
246
247 FileClass = MyFile
248
249 def __init__(self):
250 CVS.__init__(self)
251
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000252 def update(self, files):
253 for e in self.whichentries(files, 1):
254 e.update()
255
256 def commit(self, files, message = ""):
257 list = self.whichentries(files)
Guido van Rossumd1972af1995-06-21 01:02:06 +0000258 if not list: return
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000259 ok = 1
260 for e in list:
261 if not e.commitcheck():
262 ok = 0
263 if not ok:
264 print "correct above errors first"
265 return
266 if not message:
267 message = raw_input("One-liner: ")
Guido van Rossuma79f5a31995-07-18 18:34:34 +0000268 committed = []
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000269 for e in list:
Guido van Rossum7bde92b1995-10-07 19:47:26 +0000270 if e.commit(message):
271 committed.append(e.file)
Guido van Rossuma79f5a31995-07-18 18:34:34 +0000272 self.mailinfo(committed, message)
Guido van Rossumd1972af1995-06-21 01:02:06 +0000273
274 def mailinfo(self, files, message = ""):
275 towhom = "sjoerd@cwi.nl, jack@cwi.nl" # XXX
276 mailtext = MAILFORM % (towhom, string.join(files),
277 string.join(files), message)
278 print '-'*70
279 print mailtext
280 print '-'*70
281 ok = raw_input("OK to mail to %s? " % towhom)
282 if string.lower(string.strip(ok)) in ('y', 'ye', 'yes'):
283 p = os.popen(SENDMAIL, "w")
284 p.write(mailtext)
285 sts = p.close()
286 if sts:
287 print "Sendmail exit status %s" % str(sts)
288 else:
289 print "Mail sent."
290 else:
291 print "No mail sent."
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000292
293 def report(self, files):
294 for e in self.whichentries(files):
295 e.report()
296
297 def diff(self, files, opts):
298 for e in self.whichentries(files):
299 e.diff(opts)
300
Guido van Rossuma79f5a31995-07-18 18:34:34 +0000301 def add(self, files):
302 if not files:
303 raise RuntimeError, "'cvs add' needs at least one file"
304 list = []
305 for e in self.whichentries(files, 1):
Guido van Rossuma79f5a31995-07-18 18:34:34 +0000306 e.add()
Guido van Rossuma79f5a31995-07-18 18:34:34 +0000307
308 def rm(self, files):
309 if not files:
310 raise RuntimeError, "'cvs rm' needs at least one file"
311 raise RuntimeError, "'cvs rm' not yet imlemented"
312
Guido van Rossum7bde92b1995-10-07 19:47:26 +0000313 def log(self, files, opts):
314 flags = ''
315 for o, a in opts:
316 flags = flags + ' ' + o + a
317 for e in self.whichentries(files):
318 e.log(flags)
319
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000320 def whichentries(self, files, localfilestoo = 0):
321 if files:
322 list = []
Guido van Rossumae21ced1995-04-28 14:32:26 +0000323 for file in files:
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000324 if self.entries.has_key(file):
325 e = self.entries[file]
326 else:
327 e = self.FileClass(file)
328 self.entries[file] = e
329 list.append(e)
330 else:
331 list = self.entries.values()
332 for file in self.proxy.listfiles():
333 if self.entries.has_key(file):
334 continue
335 e = self.FileClass(file)
336 self.entries[file] = e
337 list.append(e)
338 if localfilestoo:
339 for file in os.listdir(os.curdir):
340 if not self.entries.has_key(file) \
341 and not self.ignored(file):
342 e = self.FileClass(file)
343 self.entries[file] = e
344 list.append(e)
345 list.sort()
346 if self.proxy:
347 for e in list:
348 if e.proxy is None:
349 e.proxy = self.proxy
350 return list
Guido van Rossumc218a7e1995-04-27 23:33:43 +0000351
352
353class rcvs(CommandFrameWork):
354
Guido van Rossum903afd01995-06-23 22:33:57 +0000355 GlobalFlags = 'd:h:p:qvL'
Guido van Rossumc218a7e1995-04-27 23:33:43 +0000356 UsageMessage = \
Guido van Rossum2d2a60e1995-04-28 19:24:50 +0000357"usage: rcvs [-d directory] [-h host] [-p port] [-q] [-v] [subcommand arg ...]"
358 PostUsageMessage = \
359 "If no subcommand is given, the status of all files is listed"
Guido van Rossumc218a7e1995-04-27 23:33:43 +0000360
361 def __init__(self):
362 """Constructor."""
363 CommandFrameWork.__init__(self)
364 self.proxy = None
365 self.cvs = RCVS()
Guido van Rossum7bde92b1995-10-07 19:47:26 +0000366
367 def close(self):
Guido van Rossumd1972af1995-06-21 01:02:06 +0000368 if self.proxy:
369 self.proxy._close()
370 self.proxy = None
Guido van Rossum7bde92b1995-10-07 19:47:26 +0000371
372 def recurse(self):
373 self.close()
Guido van Rossumd1972af1995-06-21 01:02:06 +0000374 names = os.listdir(os.curdir)
375 for name in names:
376 if name == os.curdir or name == os.pardir:
377 continue
378 if name == "CVS":
379 continue
380 if not os.path.isdir(name):
381 continue
382 if os.path.islink(name):
383 continue
384 print "--- entering subdirectory", name, "---"
385 os.chdir(name)
386 try:
387 if os.path.isdir("CVS"):
388 self.__class__().run()
389 else:
390 self.recurse()
391 finally:
392 os.chdir(os.pardir)
393 print "--- left subdirectory", name, "---"
394
Guido van Rossumc218a7e1995-04-27 23:33:43 +0000395 def options(self, opts):
396 self.opts = opts
397
398 def ready(self):
399 import rcsclient
400 self.proxy = rcsclient.openrcsclient(self.opts)
401 self.cvs.setproxy(self.proxy)
402 self.cvs.getentries()
Guido van Rossumc218a7e1995-04-27 23:33:43 +0000403
404 def default(self):
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000405 self.cvs.report([])
Guido van Rossumc218a7e1995-04-27 23:33:43 +0000406
Guido van Rossuma79f5a31995-07-18 18:34:34 +0000407 def do_report(self, opts, files):
408 self.cvs.report(files)
409
Guido van Rossumc218a7e1995-04-27 23:33:43 +0000410 def do_update(self, opts, files):
Guido van Rossumd1972af1995-06-21 01:02:06 +0000411 """update [-l] [-R] [file] ..."""
412 local = DEF_LOCAL
413 for o, a in opts:
414 if o == '-l': local = 1
415 if o == '-R': local = 0
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000416 self.cvs.update(files)
Guido van Rossum6bb4a511995-04-28 15:26:37 +0000417 self.cvs.putentries()
Guido van Rossumd1972af1995-06-21 01:02:06 +0000418 if not local and not files:
419 self.recurse()
420 flags_update = '-lR'
Guido van Rossum330e8841995-04-28 17:56:32 +0000421 do_up = do_update
Guido van Rossumd1972af1995-06-21 01:02:06 +0000422 flags_up = flags_update
Guido van Rossum5f07b841995-04-26 22:57:11 +0000423
Guido van Rossumae21ced1995-04-28 14:32:26 +0000424 def do_commit(self, opts, files):
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000425 """commit [-m message] [file] ..."""
426 message = ""
427 for o, a in opts:
428 if o == '-m': message = a
429 self.cvs.commit(files, message)
Guido van Rossum6bb4a511995-04-28 15:26:37 +0000430 self.cvs.putentries()
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000431 flags_commit = 'm:'
Guido van Rossumd1972af1995-06-21 01:02:06 +0000432 do_com = do_commit
433 flags_com = flags_commit
Guido van Rossum330e8841995-04-28 17:56:32 +0000434
435 def do_diff(self, opts, files):
436 """diff [difflags] [file] ..."""
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000437 self.cvs.diff(files, opts)
Guido van Rossumd1972af1995-06-21 01:02:06 +0000438 flags_diff = 'cbitwcefhnlr:sD:S:'
Guido van Rossum330e8841995-04-28 17:56:32 +0000439 do_dif = do_diff
Guido van Rossumd1972af1995-06-21 01:02:06 +0000440 flags_dif = flags_diff
Guido van Rossum330e8841995-04-28 17:56:32 +0000441
Guido van Rossuma79f5a31995-07-18 18:34:34 +0000442 def do_add(self, opts, files):
443 """add file ..."""
444 if not files:
445 print "'rcvs add' requires at least one file"
446 return
447 self.cvs.add(files)
448 self.cvs.putentries()
449
450 def do_remove(self, opts, files):
451 """remove file ..."""
452 if not files:
453 print "'rcvs remove' requires at least one file"
454 return
455 self.cvs.remove(files)
456 self.cvs.putentries()
457 do_rm = do_remove
458
Guido van Rossum7bde92b1995-10-07 19:47:26 +0000459 def do_log(self, opts, files):
460 """log [rlog-options] [file] ..."""
461 self.cvs.log(files, opts)
Guido van Rossuma176f581995-10-07 20:47:35 +0000462 flags_log = 'bhLNRtd:s:V:r:'
Guido van Rossum330e8841995-04-28 17:56:32 +0000463
464
465def remove(fn):
466 try:
467 os.unlink(fn)
468 except os.error:
469 pass
Guido van Rossumae21ced1995-04-28 14:32:26 +0000470
Guido van Rossum5f07b841995-04-26 22:57:11 +0000471
Guido van Rossumdeb627c1995-04-27 21:28:53 +0000472def main():
Guido van Rossum7bde92b1995-10-07 19:47:26 +0000473 r = rcvs()
474 try:
475 r.run()
476 finally:
477 r.close()
Guido van Rossum097c55a1995-04-27 18:07:07 +0000478
Guido van Rossum5f07b841995-04-26 22:57:11 +0000479
480if __name__ == "__main__":
Guido van Rossumdeb627c1995-04-27 21:28:53 +0000481 main()