91
91
# action=svnupdate: Bring a file up to date with the head revision.
92
92
# path: The path to the file to be updated. Only one file may be
95
# action=svnpublish: Set the "published" flag on a file to True.
96
# path: The path to the file to be published. Can be specified
99
# action=svnunpublish: Set the "published" flag on a file to False.
100
# path: The path to the file to be unpublished. Can be specified
94
# revision: The revision number to update to. If not provided this
103
97
# action=svncommit: Commit a file(s) or directory(s) to the repository.
104
98
# path: The path to the file or directory to be committed. Can be
119
113
# path: The path to the directory to be checked (under the IVLE
120
114
# repository base).
116
# action=svncleanup: Recursively clean up the working copy, removing locks,
117
# resuming unfinished operations, etc.
118
# path: The path to the directory to be cleaned
122
120
# TODO: Implement the following actions:
123
121
# svnupdate (done?)
124
122
# TODO: Implement ZIP unpacking in putfiles (done?).
137
136
from ivle import (util, studpath, zip)
138
137
from ivle.fileservice_lib.exceptions import WillNotOverwrite
142
def get_login(_realm, existing_login, _may_save):
143
"""Callback function used by pysvn for authentication.
144
realm, existing_login, _may_save: The 3 arguments passed by pysvn to
146
The following has been determined empirically, not from docs:
147
existing_login will be the name of the user who owns the process on
148
the first attempt, "" on subsequent attempts. We use this fact.
150
# Only provide credentials on the _first_ attempt.
151
# If we're being asked again, then it means the credentials failed for
152
# some reason and we should just fail. (This is not desirable, but it's
153
# better than being asked an infinite number of times).
154
return (existing_login != "", str(ivle.conf.login),
155
str(ivle.conf.svn_pass), True)
157
# Make a Subversion client object
158
svnclient = pysvn.Client()
159
svnclient.callback_get_login = get_login
141
# Make a Subversion client object (which will log in with this user's
142
# credentials, upon request)
143
svnclient = ivle.svn.create_auth_svn_client(username=ivle.conf.login,
144
password=ivle.conf.svn_pass)
160
145
svnclient.exception_style = 0 # Simple (string) exceptions
162
147
DEFAULT_LOGMESSAGE = "No log message supplied."
333
318
frompath = fields.getfirst('from')
334
319
topath = fields.getfirst('to')
335
movefile(req, frompath, topath)
320
svn = fields.getfirst('svn')
322
svn_movefile(req, frompath, topath)
324
movefile(req, frompath, topath)
337
326
def action_mkdir(req, fields):
338
327
"""Creates a directory with the given path.
420
409
for datum in data:
421
410
# Each of the uploaded files
422
411
filepath = os.path.join(path, datum.filename)
423
(_, _, filepath_local) = studpath.url_to_jailpaths(filepath)
412
filepath_local = studpath.to_home_path(filepath)
424
413
if os.path.isdir(filepath_local):
425
414
raise ActionError("A directory already exists "
426
415
+ "with that name")
438
427
# First get the entire path (within jail)
439
_, _, abspath = studpath.url_to_jailpaths(path)
428
abspath = studpath.to_home_path(path)
440
429
abspath = os.path.join(os.sep, abspath)
441
430
zip.unzip(abspath, filedata)
442
431
except (OSError, IOError):
532
521
Reads fields: 'path'
534
523
paths = fields.getlist('path')
535
user = studpath.url_to_local(req.path)[0]
524
user = util.split_path(req.path)[0]
536
525
homedir = "/home/%s" % user
538
527
paths = map(lambda path: actionpath_to_local(req, path), paths)
540
paths = [studpath.url_to_jailpaths(req.path)[2]]
529
paths = [studpath.to_home_path(req.path)]
542
531
# Set all the dirs in home dir world browsable (o+r,o+x)
543
532
#FIXME: Should really only do those in the direct path not all of the
587
576
Reads fields: 'path' (multiple)
589
578
paths = fields.getlist('path')
590
paths = map(lambda path: actionpath_to_local(req, path), paths)
579
paths = map(lambda path: actionpath_to_local(req, path).decode('utf-8'),
593
583
svnclient.add(paths, recurse=True, force=True)
600
590
Reads fields: 'path' (multiple)
602
592
paths = fields.getlist('path')
603
paths = map(lambda path: actionpath_to_local(req, path), paths)
593
paths = map(lambda path: actionpath_to_local(req, path).decode('utf-8'),
606
597
svnclient.remove(paths, force=True)
610
601
def action_svnupdate(req, fields):
611
602
"""Performs a "svn update" to each file specified.
604
Reads fields: 'path' and 'revision'
615
606
path = fields.getfirst('path')
607
revision = fields.getfirst('revision')
617
609
raise ActionError("Required field missing")
618
path = actionpath_to_local(req, path)
611
revision = pysvn.Revision( pysvn.opt_revision_kind.head )
614
revision = pysvn.Revision(pysvn.opt_revision_kind.number,
616
except ValueError, e:
617
raise ActionError("Bad revision number: '%s'"%revision,)
618
path = actionpath_to_local(req, path).decode('utf-8')
621
svnclient.update(path, recurse=True)
621
svnclient.update(path, recurse=True, revision=revision)
622
622
except pysvn.ClientError, e:
623
623
raise ActionError(str(e))
643
643
Reads fields: 'path' (multiple)
645
645
paths = fields.getlist('path')
646
paths = map(lambda path: actionpath_to_local(req, path), paths)
646
paths = map(lambda path: actionpath_to_local(req, path).decode('utf-8'),
649
650
svnclient.revert(paths, recurse=True)
650
651
except pysvn.ClientError, e:
651
652
raise ActionError(str(e))
653
def action_svnpublish(req, fields):
654
"""Sets svn property "ivle:published" on each file specified.
655
Should only be called on directories (only effective on directories
660
XXX Currently unused by the client (calls action_publish instead, which
661
has a completely different publishing model).
663
paths = fields.getlist('path')
665
paths = map(lambda path: actionpath_to_local(req, path), paths)
667
paths = [studpath.url_to_jailpaths(req.path)[2]]
671
# Note: Property value doesn't matter
672
svnclient.propset("ivle:published", "", path, recurse=False)
673
except pysvn.ClientError, e:
674
raise ActionError("Directory could not be published")
676
def action_svnunpublish(req, fields):
677
"""Deletes svn property "ivle:published" on each file specified.
681
XXX Currently unused by the client (calls action_unpublish instead, which
682
has a completely different publishing model).
684
paths = fields.getlist('path')
685
paths = map(lambda path: actionpath_to_local(req, path), paths)
689
svnclient.propdel("ivle:published", path, recurse=False)
690
except pysvn.ClientError, e:
691
raise ActionError("Directory could not be unpublished")
693
654
def action_svncommit(req, fields):
694
655
"""Performs a "svn commit" to each file specified.
696
657
Reads fields: 'path' (multiple), 'logmsg' (optional)
698
659
paths = fields.getlist('path')
699
paths = map(lambda path: actionpath_to_local(req, str(path)), paths)
700
logmsg = str(fields.getfirst('logmsg', DEFAULT_LOGMESSAGE))
661
paths = map(lambda path:actionpath_to_local(req,path).decode('utf-8'),
664
paths = [studpath.to_home_path(req.path).decode('utf-8')]
665
logmsg = str(fields.getfirst('logmsg',
666
DEFAULT_LOGMESSAGE)).decode('utf-8')
701
667
if logmsg == '': logmsg = DEFAULT_LOGMESSAGE
713
679
paths = fields.getlist('path')
714
680
if len(paths) != 2:
715
681
raise ActionError("usage: svncheckout url local-path")
716
url = ivle.conf.svn_addr + "/" + paths[0]
682
url = ivle.conf.svn_addr + "/" + urllib.quote(paths[0])
717
683
local_path = actionpath_to_local(req, str(paths[1]))
684
url = url.decode('utf-8')
685
local_path = local_path.decode('utf-8')
719
svnclient.callback_get_login = get_login
720
687
svnclient.checkout(url, local_path, recurse=True)
721
688
except pysvn.ClientError, e:
722
689
raise ActionError(str(e))
729
696
path = fields.getfirst('path')
730
697
logmsg = fields.getfirst('logmsg')
731
url = ivle.conf.svn_addr + "/" + path
698
url = (ivle.conf.svn_addr + "/" + urllib.quote(path)).decode('utf-8')
733
svnclient.callback_get_login = get_login
734
700
svnclient.mkdir(url, log_message=logmsg)
735
701
except pysvn.ClientError, e:
736
702
raise ActionError(str(e))
738
704
def action_svnrepostat(req, fields):
739
705
"""Discovers whether a path exists in a repo under the IVLE SVN root.
707
If it does exist, returns a dict containing its metadata.
741
709
Reads fields: 'path'
743
711
path = fields.getfirst('path')
744
url = ivle.conf.svn_addr + "/" + path
745
svnclient.exception_style = 1
712
url = (ivle.conf.svn_addr + "/" + urllib.quote(path)).decode('utf-8')
713
svnclient.exception_style = 1
748
svnclient.callback_get_login = get_login
716
info = svnclient.info2(url,
717
revision=pysvn.Revision(pysvn.opt_revision_kind.head))[0][1]
718
return {'svnrevision': info['rev'].number
720
info['rev'].kind == pysvn.opt_revision_kind.number
750
722
except pysvn.ClientError, e:
751
723
# Error code 170000 means ENOENT in this revision.
752
724
if e[1][0][1] == 170000:
753
raise util.IVLEError(404, 'The specified repository path does not exist')
726
raise ActionError('The specified repository path does not exist')
755
728
raise ActionError(str(e[0]))
731
def action_svncleanup(req, fields):
732
"""Recursively clean up the working copy, removing locks, resuming
733
unfinished operations, etc.
734
path: The path to be cleaned"""
736
path = fields.getfirst('path')
738
raise ActionError("Required field missing")
739
path = actionpath_to_local(req, path).decode('utf-8')
742
svnclient.cleanup(path)
743
except pysvn.ClientError, e:
744
raise ActionError(str(e))
758
747
# Table of all action functions #
759
748
# Each function has the interface f(req, fields).
773
762
"svnupdate" : action_svnupdate,
774
763
"svnresolved" : action_svnresolved,
775
764
"svnrevert" : action_svnrevert,
776
"svnpublish" : action_svnpublish,
777
"svnunpublish" : action_svnunpublish,
778
765
"svncommit" : action_svncommit,
779
766
"svncheckout" : action_svncheckout,
780
767
"svnrepomkdir" : action_svnrepomkdir,
781
768
"svnrepostat" : action_svnrepostat,
769
"svncleanup" : action_svncleanup,