blob: c44010f35f18b00e398a14ce334aee67b380ae56 [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#
7# Functionality:
8# - descend into directories
9# - cvs add; cvs rm
10# - commit new files
11# - conflict resolution
12# - cvs log
13# - other relevant commands?
14# - branches
15#
16# - Finesses:
17# - retain file mode's x bits
18# - complain when "nothing known about filename"
19# - edit log message the way CVS lets you edit it
20# - cvs diff -rREVA -rREVB
21# - send mail the way CVS sends it
22#
23# Performance:
24# - cache remote checksums (for every revision ever seen!)
25# - translate symbolic revisions to numeric revisions
26#
27# Reliability:
28# - remote locking
29#
30# Security:
31# - Authenticated RPC?
32
33
Guido van Rossumc218a7e1995-04-27 23:33:43 +000034from cvslib import CVS, File
Guido van Rossum5f07b841995-04-26 22:57:11 +000035import md5
36import os
37import string
38import sys
Guido van Rossumc218a7e1995-04-27 23:33:43 +000039from cmdfw import CommandFrameWork
Guido van Rossum5f07b841995-04-26 22:57:11 +000040
41
Guido van Rossumd1972af1995-06-21 01:02:06 +000042DEF_LOCAL = 1 # Default -l
43
44
Guido van Rossumc218a7e1995-04-27 23:33:43 +000045class MyFile(File):
Guido van Rossum5f07b841995-04-26 22:57:11 +000046
Guido van Rossumae21ced1995-04-28 14:32:26 +000047 def action(self):
48 """Return a code indicating the update status of this file.
49
50 The possible return values are:
Guido van Rossumc218a7e1995-04-27 23:33:43 +000051
Guido van Rossumae21ced1995-04-28 14:32:26 +000052 '=' -- everything's fine
53 '0' -- file doesn't exist anywhere
54 '?' -- exists locally only
55 'A' -- new locally
56 'R' -- deleted locally
57 'U' -- changed remotely, no changes locally
Guido van Rossum6bb4a511995-04-28 15:26:37 +000058 (includes new remotely or deleted remotely)
Guido van Rossumae21ced1995-04-28 14:32:26 +000059 'M' -- changed locally, no changes remotely
60 'C' -- conflict: changed locally as well as remotely
61 (includes cases where the file has been added
62 or removed locally and remotely)
Guido van Rossumba244681995-04-28 15:33:03 +000063 'D' -- deleted remotely
64 'N' -- new remotely
Guido van Rossum6bb4a511995-04-28 15:26:37 +000065 'r' -- get rid of entry
66 'c' -- create entry
67 'u' -- update entry
Guido van Rossum2f7ef911995-05-01 20:22:01 +000068
69 (and probably others :-)
Guido van Rossumae21ced1995-04-28 14:32:26 +000070 """
Guido van Rossumec8cfd41995-05-01 20:06:44 +000071 if not self.lseen:
72 self.getlocal()
73 if not self.rseen:
74 self.getremote()
Guido van Rossumae21ced1995-04-28 14:32:26 +000075 if not self.eseen:
Guido van Rossum2f7ef911995-05-01 20:22:01 +000076 if not self.lsum:
77 if not self.rsum: return '0' # Never heard of
Guido van Rossum6bb4a511995-04-28 15:26:37 +000078 else:
79 return 'N' # New remotely
Guido van Rossum2f7ef911995-05-01 20:22:01 +000080 else: # self.lsum
81 if not self.rsum: return '?' # Local only
Guido van Rossum6bb4a511995-04-28 15:26:37 +000082 # Local and remote, but no entry
83 if self.lsum == self.rsum:
84 return 'c' # Restore entry only
85 else: return 'C' # Real conflict
86 else: # self.eseen
Guido van Rossum2f7ef911995-05-01 20:22:01 +000087 if not self.lsum:
88 if self.edeleted:
89 if self.rsum: return 'R' # Removed
Guido van Rossum6bb4a511995-04-28 15:26:37 +000090 else: return 'r' # Get rid of entry
Guido van Rossum2f7ef911995-05-01 20:22:01 +000091 else: # not self.edeleted
92 if self.rsum:
Guido van Rossum6bb4a511995-04-28 15:26:37 +000093 print "warning:",
94 print self.file,
95 print "was lost"
96 return 'U'
97 else: return 'r' # Get rid of entry
Guido van Rossum2f7ef911995-05-01 20:22:01 +000098 else: # self.lsum
99 if not self.rsum:
Guido van Rossum6bb4a511995-04-28 15:26:37 +0000100 if self.enew: return 'A' # New locally
101 else: return 'D' # Deleted remotely
Guido van Rossum2f7ef911995-05-01 20:22:01 +0000102 else: # self.rsum
Guido van Rossum6bb4a511995-04-28 15:26:37 +0000103 if self.enew:
104 if self.lsum == self.rsum:
105 return 'u'
106 else:
107 return 'C'
108 if self.lsum == self.esum:
109 if self.esum == self.rsum:
110 return '='
111 else:
112 return 'U'
113 elif self.esum == self.rsum:
114 return 'M'
115 elif self.lsum == self.rsum:
116 return 'u'
117 else:
118 return 'C'
Guido van Rossumae21ced1995-04-28 14:32:26 +0000119
120 def update(self):
121 code = self.action()
Guido van Rossum8b5e0fa1995-04-28 21:48:16 +0000122 if code == '=': return
Guido van Rossumae21ced1995-04-28 14:32:26 +0000123 print code, self.file
Guido van Rossumba244681995-04-28 15:33:03 +0000124 if code in ('U', 'N'):
Guido van Rossumae21ced1995-04-28 14:32:26 +0000125 self.get()
126 elif code == 'C':
127 print "%s: conflict resolution not yet implemented" % \
128 self.file
Guido van Rossumba244681995-04-28 15:33:03 +0000129 elif code == 'D':
Guido van Rossum330e8841995-04-28 17:56:32 +0000130 remove(self.file)
Guido van Rossumba244681995-04-28 15:33:03 +0000131 self.eseen = 0
132 elif code == 'r':
133 self.eseen = 0
134 elif code in ('c', 'u'):
Guido van Rossumd22f59f1995-04-28 15:37:22 +0000135 self.eseen = 1
Guido van Rossumba244681995-04-28 15:33:03 +0000136 self.erev = self.rrev
137 self.enew = 0
138 self.edeleted = 0
139 self.esum = self.rsum
140 self.emtime, self.ectime = os.stat(self.file)[-2:]
Guido van Rossumd22f59f1995-04-28 15:37:22 +0000141 self.extra = ''
Guido van Rossumae21ced1995-04-28 14:32:26 +0000142
143 def commit(self, message = ""):
144 code = self.action()
145 if code in ('A', 'M'):
146 self.put(message)
147 elif code == 'R':
148 print "%s: committing removes not yet implemented" % \
149 self.file
150 elif code == 'C':
151 print "%s: conflict resolution not yet implemented" % \
152 self.file
153
Guido van Rossum330e8841995-04-28 17:56:32 +0000154 def diff(self, opts = []):
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000155 self.action() # To update lseen, rseen
Guido van Rossum330e8841995-04-28 17:56:32 +0000156 flags = ''
Guido van Rossumd1972af1995-06-21 01:02:06 +0000157 rev = self.rrev
158 # XXX should support two rev options too!
Guido van Rossum330e8841995-04-28 17:56:32 +0000159 for o, a in opts:
Guido van Rossumd1972af1995-06-21 01:02:06 +0000160 if o == '-r':
161 rev = a
162 else:
163 flags = flags + ' ' + o + a
164 if rev == self.rrev and self.lsum == self.rsum:
165 return
Guido van Rossum330e8841995-04-28 17:56:32 +0000166 flags = flags[1:]
167 fn = self.file
Guido van Rossumd1972af1995-06-21 01:02:06 +0000168 data = self.proxy.get((fn, rev))
169 sum = md5.new(data).digest()
170 if self.lsum == sum:
171 return
172 import tempfile
Guido van Rossum330e8841995-04-28 17:56:32 +0000173 tfn = tempfile.mktemp()
174 try:
175 tf = open(tfn, 'w')
176 tf.write(data)
177 tf.close()
Guido van Rossumd1972af1995-06-21 01:02:06 +0000178 print 'diff %s -r%s %s' % (flags, rev, fn)
Guido van Rossum330e8841995-04-28 17:56:32 +0000179 sts = os.system('diff %s %s %s' % (flags, tfn, fn))
180 if sts:
181 print '='*70
182 finally:
183 remove(tfn)
184
Guido van Rossumae21ced1995-04-28 14:32:26 +0000185 def commitcheck(self):
186 return self.action() != 'C'
187
188 def put(self, message = ""):
Guido van Rossumbafc14d1995-04-28 15:41:51 +0000189 print "Checking in", self.file, "..."
190 data = open(self.file).read()
191 messages = self.proxy.put(self.file, data, message)
192 if messages:
193 print messages
Guido van Rossum8b5e0fa1995-04-28 21:48:16 +0000194 self.setentry(self.proxy.head(self.file), self.lsum)
Guido van Rossumae21ced1995-04-28 14:32:26 +0000195
196 def get(self):
197 data = self.proxy.get(self.file)
198 f = open(self.file, 'w')
199 f.write(data)
200 f.close()
Guido van Rossum8b5e0fa1995-04-28 21:48:16 +0000201 self.setentry(self.rrev, self.rsum)
202
203 def setentry(self, erev, esum):
204 self.eseen = 0 # While we're hacking...
205 self.esum = esum
Guido van Rossumae21ced1995-04-28 14:32:26 +0000206 self.emtime, self.ectime = os.stat(self.file)[-2:]
Guido van Rossum8b5e0fa1995-04-28 21:48:16 +0000207 self.erev = erev
Guido van Rossumae21ced1995-04-28 14:32:26 +0000208 self.enew = 0
209 self.edeleted = 0
Guido van Rossum8b5e0fa1995-04-28 21:48:16 +0000210 self.eseen = 1 # Done
Guido van Rossumd1972af1995-06-21 01:02:06 +0000211 self.extra = ''
212
213
214SENDMAIL = "/usr/lib/sendmail -t"
215MAILFORM = """To: %s
216Subject: CVS changes: %s
217
218...Message from rcvs...
219
220Committed files:
221 %s
222
223Log message:
224 %s
225"""
Guido van Rossum5f07b841995-04-26 22:57:11 +0000226
227
Guido van Rossumc218a7e1995-04-27 23:33:43 +0000228class RCVS(CVS):
229
230 FileClass = MyFile
231
232 def __init__(self):
233 CVS.__init__(self)
234
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000235 def update(self, files):
236 for e in self.whichentries(files, 1):
237 e.update()
238
239 def commit(self, files, message = ""):
240 list = self.whichentries(files)
Guido van Rossumd1972af1995-06-21 01:02:06 +0000241 if not list: return
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000242 ok = 1
243 for e in list:
244 if not e.commitcheck():
245 ok = 0
246 if not ok:
247 print "correct above errors first"
248 return
249 if not message:
250 message = raw_input("One-liner: ")
251 for e in list:
252 e.commit(message)
Guido van Rossumd1972af1995-06-21 01:02:06 +0000253 self.mailinfo(files, message)
254
255 def mailinfo(self, files, message = ""):
256 towhom = "sjoerd@cwi.nl, jack@cwi.nl" # XXX
257 mailtext = MAILFORM % (towhom, string.join(files),
258 string.join(files), message)
259 print '-'*70
260 print mailtext
261 print '-'*70
262 ok = raw_input("OK to mail to %s? " % towhom)
263 if string.lower(string.strip(ok)) in ('y', 'ye', 'yes'):
264 p = os.popen(SENDMAIL, "w")
265 p.write(mailtext)
266 sts = p.close()
267 if sts:
268 print "Sendmail exit status %s" % str(sts)
269 else:
270 print "Mail sent."
271 else:
272 print "No mail sent."
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000273
274 def report(self, files):
275 for e in self.whichentries(files):
276 e.report()
277
278 def diff(self, files, opts):
279 for e in self.whichentries(files):
280 e.diff(opts)
281
282 def whichentries(self, files, localfilestoo = 0):
283 if files:
284 list = []
Guido van Rossumae21ced1995-04-28 14:32:26 +0000285 for file in files:
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000286 if self.entries.has_key(file):
287 e = self.entries[file]
288 else:
289 e = self.FileClass(file)
290 self.entries[file] = e
291 list.append(e)
292 else:
293 list = self.entries.values()
294 for file in self.proxy.listfiles():
295 if self.entries.has_key(file):
296 continue
297 e = self.FileClass(file)
298 self.entries[file] = e
299 list.append(e)
300 if localfilestoo:
301 for file in os.listdir(os.curdir):
302 if not self.entries.has_key(file) \
303 and not self.ignored(file):
304 e = self.FileClass(file)
305 self.entries[file] = e
306 list.append(e)
307 list.sort()
308 if self.proxy:
309 for e in list:
310 if e.proxy is None:
311 e.proxy = self.proxy
312 return list
Guido van Rossumc218a7e1995-04-27 23:33:43 +0000313
314
315class rcvs(CommandFrameWork):
316
317 GlobalFlags = 'd:h:p:qv'
318 UsageMessage = \
Guido van Rossum2d2a60e1995-04-28 19:24:50 +0000319"usage: rcvs [-d directory] [-h host] [-p port] [-q] [-v] [subcommand arg ...]"
320 PostUsageMessage = \
321 "If no subcommand is given, the status of all files is listed"
Guido van Rossumc218a7e1995-04-27 23:33:43 +0000322
323 def __init__(self):
324 """Constructor."""
325 CommandFrameWork.__init__(self)
326 self.proxy = None
327 self.cvs = RCVS()
328
Guido van Rossumd1972af1995-06-21 01:02:06 +0000329 def recurse(self):
330 if self.proxy:
331 self.proxy._close()
332 self.proxy = None
333 names = os.listdir(os.curdir)
334 for name in names:
335 if name == os.curdir or name == os.pardir:
336 continue
337 if name == "CVS":
338 continue
339 if not os.path.isdir(name):
340 continue
341 if os.path.islink(name):
342 continue
343 print "--- entering subdirectory", name, "---"
344 os.chdir(name)
345 try:
346 if os.path.isdir("CVS"):
347 self.__class__().run()
348 else:
349 self.recurse()
350 finally:
351 os.chdir(os.pardir)
352 print "--- left subdirectory", name, "---"
353
Guido van Rossumc218a7e1995-04-27 23:33:43 +0000354 def options(self, opts):
355 self.opts = opts
356
357 def ready(self):
358 import rcsclient
359 self.proxy = rcsclient.openrcsclient(self.opts)
360 self.cvs.setproxy(self.proxy)
361 self.cvs.getentries()
Guido van Rossumc218a7e1995-04-27 23:33:43 +0000362
363 def default(self):
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000364 self.cvs.report([])
Guido van Rossumc218a7e1995-04-27 23:33:43 +0000365
366 def do_update(self, opts, files):
Guido van Rossumd1972af1995-06-21 01:02:06 +0000367 """update [-l] [-R] [file] ..."""
368 local = DEF_LOCAL
369 for o, a in opts:
370 if o == '-l': local = 1
371 if o == '-R': local = 0
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000372 self.cvs.update(files)
Guido van Rossum6bb4a511995-04-28 15:26:37 +0000373 self.cvs.putentries()
Guido van Rossumd1972af1995-06-21 01:02:06 +0000374 if not local and not files:
375 self.recurse()
376 flags_update = '-lR'
Guido van Rossum330e8841995-04-28 17:56:32 +0000377 do_up = do_update
Guido van Rossumd1972af1995-06-21 01:02:06 +0000378 flags_up = flags_update
Guido van Rossum5f07b841995-04-26 22:57:11 +0000379
Guido van Rossumae21ced1995-04-28 14:32:26 +0000380 def do_commit(self, opts, files):
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000381 """commit [-m message] [file] ..."""
382 message = ""
383 for o, a in opts:
384 if o == '-m': message = a
385 self.cvs.commit(files, message)
Guido van Rossum6bb4a511995-04-28 15:26:37 +0000386 self.cvs.putentries()
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000387 flags_commit = 'm:'
Guido van Rossumd1972af1995-06-21 01:02:06 +0000388 do_com = do_commit
389 flags_com = flags_commit
Guido van Rossum330e8841995-04-28 17:56:32 +0000390
391 def do_diff(self, opts, files):
392 """diff [difflags] [file] ..."""
Guido van Rossumec8cfd41995-05-01 20:06:44 +0000393 self.cvs.diff(files, opts)
Guido van Rossumd1972af1995-06-21 01:02:06 +0000394 flags_diff = 'cbitwcefhnlr:sD:S:'
Guido van Rossum330e8841995-04-28 17:56:32 +0000395 do_dif = do_diff
Guido van Rossumd1972af1995-06-21 01:02:06 +0000396 flags_dif = flags_diff
Guido van Rossum330e8841995-04-28 17:56:32 +0000397
398
399
400def remove(fn):
401 try:
402 os.unlink(fn)
403 except os.error:
404 pass
Guido van Rossumae21ced1995-04-28 14:32:26 +0000405
Guido van Rossum5f07b841995-04-26 22:57:11 +0000406
Guido van Rossumdeb627c1995-04-27 21:28:53 +0000407def main():
Guido van Rossumc218a7e1995-04-27 23:33:43 +0000408 rcvs().run()
Guido van Rossum097c55a1995-04-27 18:07:07 +0000409
Guido van Rossum5f07b841995-04-26 22:57:11 +0000410
411if __name__ == "__main__":
Guido van Rossumdeb627c1995-04-27 21:28:53 +0000412 main()