1 #! /usr/local/bin/python
3 """Remote CVS -- command line interface"""
8 # - if the remote file is deleted, "rcvs update" will fail
13 # - descend into directories (alraedy done for update)
14 # - conflict resolution
15 # - other relevant commands?
19 # - retain file mode's x bits
20 # - complain when "nothing known about filename"
21 # - edit log message the way CVS lets you edit it
22 # - cvs diff -rREVA -rREVB
23 # - send mail the way CVS sends it
26 # - cache remote checksums (for every revision ever seen!)
27 # - translate symbolic revisions to numeric revisions
33 # - Authenticated RPC?
36 from cvslib
import CVS
, File
41 from cmdfw
import CommandFrameWork
44 DEF_LOCAL
= 1 # Default -l
50 """Return a code indicating the update status of this file.
52 The possible return values are:
54 '=' -- everything's fine
55 '0' -- file doesn't exist anywhere
56 '?' -- exists locally only
58 'R' -- deleted locally
59 'U' -- changed remotely, no changes locally
60 (includes new remotely or deleted remotely)
61 'M' -- changed locally, no changes remotely
62 'C' -- conflict: changed locally as well as remotely
63 (includes cases where the file has been added
64 or removed locally and remotely)
65 'D' -- deleted remotely
67 'r' -- get rid of entry
71 (and probably others :-)
79 if not self
.rsum
: return '0' # Never heard of
81 return 'N' # New remotely
83 if not self
.rsum
: return '?' # Local only
84 # Local and remote, but no entry
85 if self
.lsum
== self
.rsum
:
86 return 'c' # Restore entry only
87 else: return 'C' # Real conflict
91 if self
.rsum
: return 'R' # Removed
92 else: return 'r' # Get rid of entry
93 else: # not self.edeleted
99 else: return 'r' # Get rid of entry
102 if self
.enew
: return 'A' # New locally
103 else: return 'D' # Deleted remotely
106 if self
.lsum
== self
.rsum
:
110 if self
.lsum
== self
.esum
:
111 if self
.esum
== self
.rsum
:
115 elif self
.esum
== self
.rsum
:
117 elif self
.lsum
== self
.rsum
:
124 if code
== '=': return
125 print code
, self
.file
126 if code
in ('U', 'N'):
129 print "%s: conflict resolution not yet implemented" % \
136 elif code
in ('c', 'u'):
138 self
.erev
= self
.rrev
141 self
.esum
= self
.rsum
142 self
.emtime
, self
.ectime
= os
.stat(self
.file)[-2:]
145 def commit(self
, message
= ""):
147 if code
in ('A', 'M'):
150 print "%s: committing removes not yet implemented" % \
153 print "%s: conflict resolution not yet implemented" % \
156 def diff(self
, opts
= []):
157 self
.action() # To update lseen, rseen
160 # XXX should support two rev options too!
165 flags
= flags
+ ' ' + o
+ a
166 if rev
== self
.rrev
and self
.lsum
== self
.rsum
:
170 data
= self
.proxy
.get((fn
, rev
))
171 sum = md5
.new(data
).digest()
175 tfn
= tempfile
.mktemp()
180 print 'diff %s -r%s %s' % (flags
, rev
, fn
)
181 sts
= os
.system('diff %s %s %s' % (flags
, tfn
, fn
))
187 def commitcheck(self
):
188 return self
.action() != 'C'
190 def put(self
, message
= ""):
191 print "Checking in", self
.file, "..."
192 data
= open(self
.file).read()
194 self
.proxy
.lock(self
.file)
195 messages
= self
.proxy
.put(self
.file, data
, message
)
198 self
.setentry(self
.proxy
.head(self
.file), self
.lsum
)
201 data
= self
.proxy
.get(self
.file)
202 f
= open(self
.file, 'w')
205 self
.setentry(self
.rrev
, self
.rsum
)
208 self
.eseen
= 0 # While we're hacking...
209 self
.esum
= self
.lsum
210 self
.emtime
, self
.ectime
= 0, 0
214 self
.eseen
= 1 # Done
217 def setentry(self
, erev
, esum
):
218 self
.eseen
= 0 # While we're hacking...
220 self
.emtime
, self
.ectime
= os
.stat(self
.file)[-2:]
224 self
.eseen
= 1 # Done
228 SENDMAIL
= "/usr/lib/sendmail -t"
230 Subject: CVS changes: %s
232 ...Message from rcvs...
249 def update(self
, files
):
250 for e
in self
.whichentries(files
, 1):
253 def commit(self
, files
, message
= ""):
254 list = self
.whichentries(files
)
258 if not e
.commitcheck():
261 print "correct above errors first"
264 message
= raw_input("One-liner: ")
267 committed
.append(e
.file)
269 self
.mailinfo(committed
, message
)
271 def mailinfo(self
, files
, message
= ""):
272 towhom
= "sjoerd@cwi.nl, jack@cwi.nl" # XXX
273 mailtext
= MAILFORM
% (towhom
, string
.join(files
),
274 string
.join(files
), message
)
278 ok
= raw_input("OK to mail to %s? " % towhom
)
279 if string
.lower(string
.strip(ok
)) in ('y', 'ye', 'yes'):
280 p
= os
.popen(SENDMAIL
, "w")
284 print "Sendmail exit status %s" % str(sts
)
288 print "No mail sent."
290 def report(self
, files
):
291 for e
in self
.whichentries(files
):
294 def diff(self
, files
, opts
):
295 for e
in self
.whichentries(files
):
298 def add(self
, files
):
300 raise RuntimeError, "'cvs add' needs at least one file"
302 for e
in self
.whichentries(files
, 1):
314 raise RuntimeError, "'cvs rm' needs at least one file"
315 raise RuntimeError, "'cvs rm' not yet imlemented"
317 def whichentries(self
, files
, localfilestoo
= 0):
321 if self
.entries
.has_key(file):
322 e
= self
.entries
[file]
324 e
= self
.FileClass(file)
325 self
.entries
[file] = e
328 list = self
.entries
.values()
329 for file in self
.proxy
.listfiles():
330 if self
.entries
.has_key(file):
332 e
= self
.FileClass(file)
333 self
.entries
[file] = e
336 for file in os
.listdir(os
.curdir
):
337 if not self
.entries
.has_key(file) \
338 and not self
.ignored(file):
339 e
= self
.FileClass(file)
340 self
.entries
[file] = e
350 class rcvs(CommandFrameWork
):
352 GlobalFlags
= 'd:h:p:qvL'
354 "usage: rcvs [-d directory] [-h host] [-p port] [-q] [-v] [subcommand arg ...]"
356 "If no subcommand is given, the status of all files is listed"
360 CommandFrameWork
.__init
__(self
)
368 names
= os
.listdir(os
.curdir
)
370 if name
== os
.curdir
or name
== os
.pardir
:
374 if not os
.path
.isdir(name
):
376 if os
.path
.islink(name
):
378 print "--- entering subdirectory", name
, "---"
381 if os
.path
.isdir("CVS"):
382 self
.__class
__().run()
387 print "--- left subdirectory", name
, "---"
389 def options(self
, opts
):
394 self
.proxy
= rcsclient
.openrcsclient(self
.opts
)
395 self
.cvs
.setproxy(self
.proxy
)
396 self
.cvs
.getentries()
401 def do_report(self
, opts
, files
):
402 self
.cvs
.report(files
)
404 def do_update(self
, opts
, files
):
405 """update [-l] [-R] [file] ..."""
408 if o
== '-l': local
= 1
409 if o
== '-R': local
= 0
410 self
.cvs
.update(files
)
411 self
.cvs
.putentries()
412 if not local
and not files
:
416 flags_up
= flags_update
418 def do_commit(self
, opts
, files
):
419 """commit [-m message] [file] ..."""
422 if o
== '-m': message
= a
423 self
.cvs
.commit(files
, message
)
424 self
.cvs
.putentries()
427 flags_com
= flags_commit
429 def do_diff(self
, opts
, files
):
430 """diff [difflags] [file] ..."""
431 self
.cvs
.diff(files
, opts
)
432 flags_diff
= 'cbitwcefhnlr:sD:S:'
434 flags_dif
= flags_diff
436 def do_add(self
, opts
, files
):
439 print "'rcvs add' requires at least one file"
442 self
.cvs
.putentries()
444 def do_remove(self
, opts
, files
):
445 """remove file ..."""
447 print "'rcvs remove' requires at least one file"
449 self
.cvs
.remove(files
)
450 self
.cvs
.putentries()
467 if __name__
== "__main__":