3 """Remote CVS -- command line interface"""
8 # - if the remote file is deleted, "rcvs update" will fail
12 # - descend into directories (alraedy done for update)
13 # - conflict resolution
14 # - other relevant commands?
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
25 # - cache remote checksums (for every revision ever seen!)
26 # - translate symbolic revisions to numeric revisions
32 # - Authenticated RPC?
35 from cvslib
import CVS
, File
40 from cmdfw
import CommandFrameWork
43 DEF_LOCAL
= 1 # Default -l
49 """Return a code indicating the update status of this file.
51 The possible return values are:
53 '=' -- everything's fine
54 '0' -- file doesn't exist anywhere
55 '?' -- exists locally only
57 'R' -- deleted locally
58 'U' -- changed remotely, no changes locally
59 (includes new remotely or deleted remotely)
60 '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)
64 'D' -- deleted remotely
66 'r' -- get rid of entry
70 (and probably others :-)
78 if not self
.rsum
: return '0' # Never heard of
80 return 'N' # New remotely
82 if not self
.rsum
: return '?' # Local only
83 # Local and remote, but no entry
84 if self
.lsum
== self
.rsum
:
85 return 'c' # Restore entry only
86 else: return 'C' # Real conflict
90 if self
.rsum
: return 'R' # Removed
91 else: return 'r' # Get rid of entry
92 else: # not self.edeleted
98 else: return 'r' # Get rid of entry
101 if self
.enew
: return 'A' # New locally
102 else: return 'D' # Deleted remotely
105 if self
.lsum
== self
.rsum
:
109 if self
.lsum
== self
.esum
:
110 if self
.esum
== self
.rsum
:
114 elif self
.esum
== self
.rsum
:
116 elif self
.lsum
== self
.rsum
:
123 if code
== '=': return
124 print code
, self
.file
125 if code
in ('U', 'N'):
128 print "%s: conflict resolution not yet implemented" % \
135 elif code
in ('c', 'u'):
137 self
.erev
= self
.rrev
140 self
.esum
= self
.rsum
141 self
.emtime
, self
.ectime
= os
.stat(self
.file)[-2:]
144 def commit(self
, message
= ""):
146 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
)
207 def log(self
, otherflags
):
208 print self
.proxy
.log(self
.file, otherflags
)
211 self
.eseen
= 0 # While we're hacking...
212 self
.esum
= self
.lsum
213 self
.emtime
, self
.ectime
= 0, 0
217 self
.eseen
= 1 # Done
220 def setentry(self
, erev
, esum
):
221 self
.eseen
= 0 # While we're hacking...
223 self
.emtime
, self
.ectime
= os
.stat(self
.file)[-2:]
227 self
.eseen
= 1 # Done
231 SENDMAIL
= "/usr/lib/sendmail -t"
233 Subject: CVS changes: %s
235 ...Message from rcvs...
252 def update(self
, files
):
253 for e
in self
.whichentries(files
, 1):
256 def commit(self
, files
, message
= ""):
257 list = self
.whichentries(files
)
261 if not e
.commitcheck():
264 print "correct above errors first"
267 message
= raw_input("One-liner: ")
270 if e
.commit(message
):
271 committed
.append(e
.file)
272 self
.mailinfo(committed
, message
)
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
)
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")
287 print "Sendmail exit status %s" % str(sts
)
291 print "No mail sent."
293 def report(self
, files
):
294 for e
in self
.whichentries(files
):
297 def diff(self
, files
, opts
):
298 for e
in self
.whichentries(files
):
301 def add(self
, files
):
303 raise RuntimeError, "'cvs add' needs at least one file"
305 for e
in self
.whichentries(files
, 1):
310 raise RuntimeError, "'cvs rm' needs at least one file"
311 raise RuntimeError, "'cvs rm' not yet imlemented"
313 def log(self
, files
, opts
):
316 flags
= flags
+ ' ' + o
+ a
317 for e
in self
.whichentries(files
):
320 def whichentries(self
, files
, localfilestoo
= 0):
324 if self
.entries
.has_key(file):
325 e
= self
.entries
[file]
327 e
= self
.FileClass(file)
328 self
.entries
[file] = e
331 list = self
.entries
.values()
332 for file in self
.proxy
.listfiles():
333 if self
.entries
.has_key(file):
335 e
= self
.FileClass(file)
336 self
.entries
[file] = e
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
353 class rcvs(CommandFrameWork
):
355 GlobalFlags
= 'd:h:p:qvL'
357 "usage: rcvs [-d directory] [-h host] [-p port] [-q] [-v] [subcommand arg ...]"
359 "If no subcommand is given, the status of all files is listed"
363 CommandFrameWork
.__init
__(self
)
374 names
= os
.listdir(os
.curdir
)
376 if name
== os
.curdir
or name
== os
.pardir
:
380 if not os
.path
.isdir(name
):
382 if os
.path
.islink(name
):
384 print "--- entering subdirectory", name
, "---"
387 if os
.path
.isdir("CVS"):
388 self
.__class
__().run()
393 print "--- left subdirectory", name
, "---"
395 def options(self
, opts
):
400 self
.proxy
= rcsclient
.openrcsclient(self
.opts
)
401 self
.cvs
.setproxy(self
.proxy
)
402 self
.cvs
.getentries()
407 def do_report(self
, opts
, files
):
408 self
.cvs
.report(files
)
410 def do_update(self
, opts
, files
):
411 """update [-l] [-R] [file] ..."""
414 if o
== '-l': local
= 1
415 if o
== '-R': local
= 0
416 self
.cvs
.update(files
)
417 self
.cvs
.putentries()
418 if not local
and not files
:
422 flags_up
= flags_update
424 def do_commit(self
, opts
, files
):
425 """commit [-m message] [file] ..."""
428 if o
== '-m': message
= a
429 self
.cvs
.commit(files
, message
)
430 self
.cvs
.putentries()
433 flags_com
= flags_commit
435 def do_diff(self
, opts
, files
):
436 """diff [difflags] [file] ..."""
437 self
.cvs
.diff(files
, opts
)
438 flags_diff
= 'cbitwcefhnlr:sD:S:'
440 flags_dif
= flags_diff
442 def do_add(self
, opts
, files
):
445 print "'rcvs add' requires at least one file"
448 self
.cvs
.putentries()
450 def do_remove(self
, opts
, files
):
451 """remove file ..."""
453 print "'rcvs remove' requires at least one file"
455 self
.cvs
.remove(files
)
456 self
.cvs
.putentries()
459 def do_log(self
, opts
, files
):
460 """log [rlog-options] [file] ..."""
461 self
.cvs
.log(files
, opts
)
462 flags_log
= 'bhLNRtd:s:V:r:'
480 if __name__
== "__main__":