Index: csvn/wc.py =================================================================== --- csvn/wc.py (revision 0) +++ csvn/wc.py (revision 0) @@ -0,0 +1,228 @@ +import csvn.core as svn +from csvn.core import * +from ctypes import * +import os +import re + +class WC(object): + """A SVN working copy.""" + def __init__(self, path=os.getcwd(), lock=1, depth=-1): + self.pool = Pool() + self.path = path + self.depth = depth + + self._as_parameter_ = POINTER(svn_wc_adm_access_t)() + + if(lock != 0 or lock != 1): + lock = 1 + + self.url = pointer(String()) + self.rev = c_int() + + svn_wc_adm_open3(byref(self._as_parameter_), + NULL, path, TRUE, depth, + svn_cancel_func_t(), NULL, self.pool) + + svn_wc_get_ancestry( self.url, byref(self.rev), self.path, + self._as_parameter_, self.pool) + + self.url = self.url.contents + self.rev = self.rev.value + self.entry = self.wc_entry().contents + + + + def text_modified(self, path, force=0): + """Check if the text of PATH is modified.""" + modified = c_int32() + svn_wc_text_modified_p(byref(modified), self.path+"/"+path, force, + self._as_parameter_, self.pool) + return modified.value + + def prop_modified(self, path): + """Check if the props of PATH are modified.""" + modified = c_int32() + svn_wc_props_modified_p(byref(modified), self.path+"/"+path, + self._as_parameter_,self.pool) + return modified.value + + def has_conflict(self): + """If the wc has conflicts, return the conflicted entry. Otherwise, + returns 0.""" + path = self.path + prop_conflict = c_int32() + text_conflict = c_int32() + entry = svn_wc_entry_t() + svn_wc_conflicted_p(byref(prop_conflict), byref(text_conflict), path, + byref(entry), self.pool) + if(prop_conflict.value | text_conflict.value): + return entry + else: + return 0 + + def copy(self, src, dest): + """Copy a file from SRC to DEST. The new file will be marked for + addition.""" + svn_wc_copy2(self.path+"/"+src, self._as_parameter_, + self.path+"/"+dest, svn_cancel_func_t(), + NULL, svn_wc_notify_func2_t(), NULL, self.pool) + + def move(self, src, dest): + """Move a file from SRC to DEST. The original file will be + scheduled for removal, and the new file will be scheduled + for addition.""" + self.copy(src, dest) + self.delete(src) + + def delete(self, path): + """Schedule PATH to be deleted.""" + return svn_wc_delete2(self.path+"/"+path, self._as_parameter_, + svn_cancel_func_t(),NULL, + svn_wc_notify_func2_t(), NULL, self.pool) + + def add(self, path): + """Schedule PATH to be added.""" + svn_wc_add2(self.path+"/"+path, self._as_parameter_, NULL, + svn_revnum_t(),svn_cancel_func_t(),NULL, + svn_wc_notify_func2_t(), NULL,self.pool) + + def resolve(self, path, text=TRUE, props=TRUE, recurse=FALSE): + """Resolve a conflict on PATH.""" + svn_wc_resolved_conflict2(self.path+"/"+path, self._as_parameter_, + text, props, recurse, + svn_wc_notify_func2_t(), NULL, + svn_cancel_func_t(), NULL, self.pool) + + def revert(self, path, recurse=FALSE, commit_time=FALSE): + """Revert PATH to the most recent version. If RECURSE is TRUE, the + revert will recurse through directories. If COMMINT_TIME is TRUE, + the last changed date of the file will be set to the commit time.""" + svn_wc_revert2(self.path+"/"+path, self._as_parameter_, recurse, + commit_time, svn_cancel_func_t(), NULL, + svn_wc_notify_func2_t(), NULL, self.pool) + + def revert_all(self, show_hidden=FALSE): + """Revert all files in the wc. If SHOW_HIDDEN is TRUE, hidden files + will be reverted too. Files that can't be reverted for any reason + will be skipped.""" + walk_callbacks = svn_wc_entry_callbacks_t(); + WALKCALL = CFUNCTYPE(UNCHECKED(POINTER(svn_error_t)), String, + POINTER(svn_wc_entry_t), POINTER(None), + POINTER(apr_pool_t)) + walk_callbacks.found_entry = WALKCALL(self._walk_revert) + svn_wc_walk_entries2(self.path, self._as_parameter_, + byref(walk_callbacks), NULL, show_hidden, + svn_cancel_func_t(), NULL, self.pool) + + def _walk_revert(self, path, entry, walk_baton, pool): + """Function to revert files while walking the wc, for use by + revert_all().""" + #Since this method is called during a walk, some files may not be + #revertable. To cope with this, this method 'handles' the error + #by ignoring it. Not the best answer, but it does allow all the + #revertable files to be reverted. + try: + svn_wc_revert2(path, self._as_parameter_, TRUE, FALSE, + svn_cancel_func_t(), NULL, svn_wc_notify_func2_t(), + NULL, self.pool) + except: + pass + + def wc_entry(self, path=None, show_hidden=TRUE): + """Returns a svn_wc_entry_t for PATH. If show_hidden is TRUE, hidden + wc entries can be handled.""" + entry = POINTER(svn_wc_entry_t)() + if(path == None): + svn_wc_entry(byref(entry), self.path, self._as_parameter_, + show_hidden, self.pool) + else: + svn_wc_entry(byref(entry), self.path+"/"+path, self._as_parameter_, + show_hidden, self.pool) + return entry + + def is_scheduled_add(self, path): + """Return True if PATH is scheduled for addition, False otherwise.""" + entry = self.wc_entry(path) + if(entry): + entry = entry.contents + else: + return FALSE + return (entry.schedule == 1) + + def is_scheduled_delete(self, path): + """Return True if PATH is scheduled for deletion, False otherwise""" + entry = self.wc_entry(path) + if(entry): + entry = entry.contents + else: + return FALSE + return (entry.schedule == 2) + + def info(self): + """Returns a string just like running 'svn info' on the command line. + + NOTE: At present, the Repository Root field is always blank. + """ + output = "Path: "+self.path+"\n" + output = output+"URL: " + self.url +"\n" + #How can we get the repository root? + #Needs work + output = output+"Repository Root: "+"\n" + output = output+"Repository UUID: "+self.entry.uuid+"\n" + output = output+"Revision: "+self.rev+"\n" + output = output+"Node Kind: "+svn_kind_to_string(self.entry.kind) + output = output+"\n" + output = output+"Schedule: " + output = output+svn_schedule_to_string(self.entry.schedule)+"\n" + output = output+"Last Changed Author: "+self.entry.cmt_author+"\n" + output = output+"Last Changed Rev: "+self.entry.cmt_rev+"\n" + output = output+"Last Changed Date: " + output = output+svn_time_to_human_cstring(self.entry.cmt_date, + self.pool) + return output + + def diff_path(self, modified): + """Return a diff of a file in the WC and the original in the WC. + The diff is returned in the form of a string. + + Only works on files that exist in the most recent revision for now. + Files for which there is no original will return an empty string. + """ + modified = self.path+"/"+modified + reobj = re.match('(.*)/([\w\.]*)',modified) + original = reobj.group(1)+"/.svn/text-base/" + original = original+reobj.group(2)+".svn-base" + if(not os.path.exists(original)): + return "" + diff_result = pointer(svn_diff_t()) + svn_diff_file_diff(byref(diff_result), original, modified, self.pool) + stringbuf = svn_stringbuf_create("", self.pool) + output_stream = pointer(svn_stream_t()) + output_stream = svn_stream_from_stringbuf(stringbuf, self.pool) + svn_diff_file_output_unified(output_stream, diff_result, original, + modified, NULL, NULL, self.pool) + return stringbuf.contents.data + +def svn_kind_to_string(kind): + """Convert a node kind into a human-readable string.""" + if(kind == 0): + return "absent" + elif(kind == 1): + return "file" + elif(kind == 2): + return "directory" + else: + return "unknown" + +def svn_schedule_to_string(schedule): + """Convert a schedule into a human-readable string.""" + if(schedule == 0): + return "normal" + elif(schedule == 1): + return "add" + elif(schedule == 2): + return "delete" + elif(schedule == 3): + return "replace" + else: + return "unknown" Index: wcexample.py =================================================================== --- wcexample.py (revision 0) +++ wcexample.py (revision 0) @@ -0,0 +1,54 @@ +from csvn.wc import * +import os + +wc = WC() + +print wc.info() + + +if(wc.has_conflict()): + print "Has conflicts" +else: + print "No conflict" + +try: + wc.delete("mucc.py") + + if(wc.is_scheduled_delete("mucc.py")): + print "Delete worked" + else: + print "Delete failed" + + wc.add("svn_all.py") + + if(wc.is_scheduled_add("svn_all.py")): + print "Add worked" + else: + print "Add failed" + + wc.copy("trunkify.py", "copied.py") + + if(wc.is_scheduled_add("copied.py")): + print "Copy worked" + else: + print "Copy failed" + + +finally: + wc.revert("mucc.py") + + wc.revert("svn_all.py") + + wc.revert("copied.py") + + #wc.revert_all() + + os.system("rm -f copied.py") + + if(wc.is_scheduled_delete("mucc.py") | wc.is_scheduled_add("svn_all.py") + | wc.is_scheduled_add("copied.py")): + print "Revert failed" + else: + print "Revert worked" + + print wc.diff_path("csvn/core/__init__.py")