Mercurial > hgweb.cgi > hwm
changeset 39:a444cc89d51e
Merge updated set-up information with exception on stat usage.
author | John Schneiderman <JohnMS@CodeGNU.com> |
---|---|
date | Wed, 13 Aug 2014 18:43:41 -0500 |
parents | bc8dd5547269 (diff) ba88b8114b98 (current diff) |
children | 23605f9372a8 |
files | doc/TODO src/manager.py |
diffstat | 8 files changed, 198 insertions(+), 83 deletions(-) [+] |
line wrap: on
line diff
--- a/README Fri Aug 08 13:15:38 2014 -0500 +++ b/README Wed Aug 13 18:43:41 2014 -0500 @@ -28,7 +28,8 @@ BUILD ENVIRONMENT HgWebManager is developed on Mageia 4 using just the standard development -libraries, mercurial, and python. +libraries, mercurial, and python. In addition, testing on Microsoft Windows +Server 2008. DIRECTORY MAP All the source code is listed in the src directory. Documentation is located in
--- a/doc/ChangeLog Fri Aug 08 13:15:38 2014 -0500 +++ b/doc/ChangeLog Wed Aug 13 18:43:41 2014 -0500 @@ -17,13 +17,16 @@ *** You should have received a copy of the GNU Affero General Public License*** *** along with this program. If not, see <http://www.gnu.org/licenses/>. *** ******************************************************************************** -0000-00-00 John Schneiderman <Licensing _AT_ CodeGNU _DOT_ com> 0.3.0 +2014-08-13 John Schneiderman <Licensing _AT_ CodeGNU _DOT_ com> 0.3.0 - Unregistering repositories will now only do so on an exact match of the storage name. - Generate default ignore file when creating a new managed repository. - Write an ignore file to an existing managed repository. - HgWeb configuration now managed cleanly, i.e. no longer a raw read/write. - Multiple managers cannot work on the set-up at the same time. +- Two repositories can no longer have the same display name. +- Ability to list all managed repositories. +- Modify now /only/ changes the values supplied, leaving others alone. 2014-07-29 John Schneiderman <Licensing _AT_ CodeGNU _DOT_ com> 0.2.1 - Fixed issue where the configuration file copy started before it closed. - Fixed issue in Windows locking temporary file and preventing copying.
--- a/doc/TODO Fri Aug 08 13:15:38 2014 -0500 +++ b/doc/TODO Wed Aug 13 18:43:41 2014 -0500 @@ -32,8 +32,8 @@ - User tutorials. - Manager functions should take both an output and error device instead of defaulting to std. - Manager should handle the HgWeb configuration file settings. + * Create a separate manager for the HgWeb configuration file. - Manager should provide a REST API for repository management. - Manager should have an API key per user permission values. -- Manager should prevent two repositories from using the same display name. -- Manager should list all managed repositories like Display_Name(Storage_Name). - Change functions to use exceptions for error handling, except for the command-line module. +- Prevent a push that would create multiple heads
--- a/setup.cfg Fri Aug 08 13:15:38 2014 -0500 +++ b/setup.cfg Wed Aug 13 18:43:41 2014 -0500 @@ -25,5 +25,5 @@ [bdist_rpm] # Use --release to set the release number group = Development -requires = python, mercurial +requires = python, mercurial, filelock doc_files = README, LICENSE, doc/ChangeLog, doc/CREDITS, doc/TODO
--- a/setup.py Fri Aug 08 13:15:38 2014 -0500 +++ b/setup.py Wed Aug 13 18:43:41 2014 -0500 @@ -31,7 +31,8 @@ 'mercurial.hg', 'mercurial.ui', 'mercurial.error', - 'mercurial.hgweb' + 'mercurial.hgweb', + 'filelock' ] # Basic package setup information
--- a/src/hwm.py Fri Aug 08 13:15:38 2014 -0500 +++ b/src/hwm.py Wed Aug 13 18:43:41 2014 -0500 @@ -53,7 +53,10 @@ def __extract_values(args): - """ Pulls the creation arguments out of the command-line. + """ Pulls the creation arguments out of the command-line. The returned + object is filled with the repository details when it exists and any of the + detail values supplied on the command-line will replace the one found in + the repository details. @param[in] args The command-line argument processor containing the creation argument values. @@ -62,9 +65,15 @@ """ import manrepo - managed = manrepo.Repository() - if args.repository: - managed.StorageName = args.repository + managed = None + if args.rename: + original = manrepo.Repository(args.storage) + managed = manrepo.Repository(args.rename) + managed.DisplayName = original.DisplayName + managed.Description = original.Description + managed.Contact = original.Contact + else: + managed = manrepo.Repository(args.storage) if args.name: managed.DisplayName = args.name @@ -74,6 +83,7 @@ if args.contact: managed.Contact = args.contact + return managed def main(args): @@ -82,59 +92,60 @@ import sys if not is_hgWeb_user(): - print >>sys.stderr, "Must execute as the Mercurial Web manager." + print >>sys.stderr, 'Must execute as the Mercurial Web manager.' exit(1) parser = argparse.ArgumentParser(description=DESCRIPTION) - parser.add_argument("repository", action="store", help='The storage-name of the repository to perform an action upon.') - parser.add_argument("-a", "--action", action="store", nargs=1, type=str, choices=['create','modify','delete', 'register'], help='Performs an action upon a managed repository.') - parser.add_argument("-n", "--name", action="store", nargs='?', default=None, help='The display name of the repository.') - parser.add_argument("-d", "--description", action="store", nargs='?', default=None, help='The description of the repository.') - parser.add_argument("-c", "--contact", action="store", nargs='?', default=None, help='The contact point of a repository.') - parser.add_argument("-r", "--rename", action="store", nargs='?', default=None, help='The new storage-name for a repository.') - parser.add_argument("-i", "--ignore", action="store", nargs='+', default=None, help='The groups to place in an ignore file.') + parser.add_argument('-l', '--list', action='store_true', help='Outputs a list of all the managed repositories.') + + manGrp = parser.add_argument_group('Manage', 'Options that operate upon managed repositories.') + manGrp.add_argument('storage', action='store', nargs='?', help='The storage-name of the repository to perform an action upon.') + manGrp.add_argument('-a', '--action', action='store', nargs=1, type=str, choices=['c', 'create', 'm', 'modify', 'd', 'delete', 'r', 'register'], help='Performs an action upon a managed repository.') + manGrp.add_argument('-i', '--ignore', action='store', nargs='+', default=None, help='The groups to place in an ignore file.') + manGrp.add_argument('-n', '--name', action='store', nargs='?', default=None, help='The display name of the repository.') + manGrp.add_argument('-d', '--description', action='store', nargs='?', default=None, help='The description of the repository.') + manGrp.add_argument('-c', '--contact', action='store', nargs='?', default=None, help='The contact point of a repository.') + manGrp.add_argument('-r', '--rename', action='store', nargs='?', default=None, help='The new storage-name for a repository.') + + # Examine the supplied arguments and perform requested actions. args = parser.parse_args() repo = __extract_values(args) - if 'create' == args.action[0]: + if args.list: + if not manager.list(): + exit(2) + elif args.action is None: + print >>sys.stderr, "No actionable arguments supplied." + exit(1) + elif ('create' == args.action[0]) or ('c' == args.action[0]): if args.ignore: ignores = args.ignore else: ignores = ['Common'] - if manager.create(repo, ignores): - exit(0) - else: + if not manager.create(repo, ignores): + exit(2) + elif ('modify' == args.action[0]) or ('m' == args.action[0]): + if not manager.modify(repo, args.storage, args.ignore): exit(2) - elif 'modify' == args.action[0]: - currentStorageName = repo.StorageName - if args.rename: - repo.StorageName = args.rename - if manager.modify(repo, currentStorageName, args.ignore): - exit(0) - else: + elif ('delete' == args.action[0]) or ('d' == args.action[0]): + if not manager.delete(repo): exit(2) - elif 'delete' == args.action[0]: - if manager.delete(repo): - exit(0) - else: - exit(2) - elif 'register' == args.action[0]: - if manager.register(repo): - exit(0) - else: + elif ('register' == args.action[0]) or ('r' == args.action[0]): + if not manager.register(repo): exit(2) else: - print >>sys.stderr, "Failure determine action request %s." % args.action + print >>sys.stderr, "Failure determine requested action '%s'." % args.action[0] exit(2) + exit(0) -if __name__ == "__main__": +if __name__ == '__main__': import sys # Display license print "%s %s Copyright (C) %s CodeGNU Solutions" % (LONG_NAME, VERSION, YEARS) print "%s comes with ABSOLUTELY NO WARRANTY;" % (SHORT_NAME) - print "This is free software, and you are welcome to redistribute it" - print "under certain conditions; see the LICENSE file for details," - print "or the Free Software Foundation's AGPL.\n" + print 'This is free software, and you are welcome to redistribute it' + print 'under certain conditions; see the LICENSE file for details,' + print 'or the Free Software Foundation\'s AGPL.\n' main(sys.argv)
--- a/src/manager.py Fri Aug 08 13:15:38 2014 -0500 +++ b/src/manager.py Wed Aug 13 18:43:41 2014 -0500 @@ -49,8 +49,12 @@ lock = guard.FileLock('HWM') with lock: print "Adding repository: %s" % repository.StorageName - if __doesRepositoryExist(repository): - print >>sys.stderr, "The repository '%s' already exists." % repository.StorageName + if __doesRepositoryStorageNameExist(repository): + print >>sys.stderr, "The repository storage name '%s' already exists." % repository.StorageName + return False + + if __doesRepositoryDisplayNameExist(repository): + print >>sys.stderr, "The display name '%s' already exists." % repository.DisplayName return False # Create the requested repository. @@ -98,7 +102,7 @@ lock = guard.FileLock('HWM') with lock: print "Registering the repository: %s" % repository.StorageName - if not __doesRepositoryExist(repository): + if not __doesRepositoryStorageNameExist(repository): print >>sys.stderr, "The repository '%s' does not exists." % repository.StorageName return False @@ -132,10 +136,10 @@ print "Acquiring manager lock ..." lock = guard.FileLock('HWM') with lock: - print "Modifying repository: " + currentStorageName + print "Modifying repository: %s" % currentStorageName oldRepository = Repository(currentStorageName) - if not __doesRepositoryExist(oldRepository): + if not __doesRepositoryStorageNameExist(oldRepository): print >>sys.stderr, "The repository '%s' was not found." % oldRepository.StorageName return False @@ -144,6 +148,12 @@ print >>sys.stderr, "Failed to unregister repository." return False + if __doesRepositoryDisplayNameExist(newRepository): + print >>sys.stderr, "The display name '%s' already exists." % newRepository.DisplayName + if not __registerRepository(oldRepository): + print >>sys.stderr, "Failed to register repository." + return False + # Update renamed repositories if not oldRepository == newRepository: if __renameStorage(oldRepository, newRepository.StorageName): @@ -202,7 +212,7 @@ print "Acquiring manager lock ..." lock = guard.FileLock('HWM') with lock: - if not __doesRepositoryExist(repository): + if not __doesRepositoryStorageNameExist(repository): print >>sys.stderr, "The repository %s was not found." % repository.StorageName return False @@ -220,6 +230,23 @@ print >>sys.stderr, "Failed to delete the repository." return False +def list(): + """ Outputs a listing of all the known managed repositories + + @return When the output generation is successful gives true, else-wise false. + """ + import manrepo + + print "Managed Repositories:" + try: + repoCol = manrepo.ManagedCollection() + for repo in repoCol.Repositories: + print "\t%s (%s)" % (repo.StorageName, repo.DisplayName) + return True + except Exception as e: + print >>sys.stderr, "Listing error({0}): {1}".format(e.errno, e.strerror) + return False + #--- Functions for fulfilling repository management. @@ -361,8 +388,8 @@ try: if oldRepo.StorageName == newName: return True - elif os.exists(settings.RepositoryPath + os.sep + newName): - print >>sys.stderr, "Cannot rename the existing repository %s because the name %s already exists." % (oldRepo.StorageName, newName) + elif os.path.isdir(settings.RepositoryPath + os.sep + newName): + print >>sys.stderr, "Cannot rename the existing repository '%s' because the name '%s' already exists." % (oldRepo.StorageName, newName) return False else: shutil.move(settings.RepositoryPath + os.sep + oldRepo.StorageName, settings.RepositoryPath + os.sep + newName) @@ -371,12 +398,12 @@ return False return True -def __doesRepositoryExist(repo): - """ Checks to see if a repository exists. +def __doesRepositoryStorageNameExist(repo): + """ Checks to see if a repository storage name exists. @param[in] repo The targeted repository for which to check. - @return Gives true when the repository exists, false else-wise. + @return Gives true when it exists, false else-wise. """ import os @@ -387,6 +414,25 @@ else: return False +def __doesRepositoryDisplayNameExist(repo): + """ Checks to see if a repository display name exists. + + @param[in] repo The targeted repository for which to check. + + @return Gives true when it exists, false else-wise. + """ + import os + from manrepo import Repository, ManagedCollection + + if (repo is None) or (repo.StorageName is None) or (repo.DisplayName is None): + return False + + repoCol = ManagedCollection() + for testRepo in repoCol.Repositories: + if (testRepo.DisplayName is not None) and (not testRepo == repo) and (testRepo.DisplayName.lower() == repo.DisplayName.lower()): + return True + return False + def __unregisterRepository(repo): """ Removes the registration of the supplied repository. @@ -455,6 +501,10 @@ import sys import os + if repo.StorageName is None: + print >>sys.stderr, 'Cannot create a managed repository without a storage name.' + return False + try: statusCode = subprocess.call([settings.HgCommand, 'init', settings.RepositoryPath + os.sep + repo.StorageName]) except OSError as e: @@ -479,26 +529,18 @@ import sys print "Saving display details ..." - config = settings.RepositoryPath + os.sep + repo.StorageName + os.sep + '.hg' + os.sep + "hgrc" + config = settings.RepositoryPath + os.sep + repo.StorageName + os.sep + '.hg' + os.sep + 'hgrc' if os.path.exists(config): hgrcMode = os.stat(config)[stat.ST_MODE] os.chmod(config, hgrcMode | stat.S_IWRITE) else: hgrcMode = stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH - with open(config, "w") as hgrc: - hgrc.write("[web]\n") - - if repo.DisplayName is None: - hgrc.write("name = " + repo.StorageName + '\n') - else: - hgrc.write("name = " + repo.DisplayName + '\n') - - if repo.Description is not None: - hgrc.write("description = " + repo.Description + '\n') - - if repo.Contact is not None: - hgrc.write("contact = " + repo.Contact + '\n') + try: + repo.save() + except IOError as e: + print >>sys.stderr, "Failed to save details, error({0}): {1}".format(e.errno, e.strerror) + return False # Ensure the repository details isn't accidentally modified. os.chmod(config, hgrcMode & (~stat.S_IWUSR & ~stat.S_IWGRP & ~stat.S_IWOTH))
--- a/src/manrepo.py Fri Aug 08 13:15:38 2014 -0500 +++ b/src/manrepo.py Wed Aug 13 18:43:41 2014 -0500 @@ -38,15 +38,14 @@ __description = None # The displayed web-site contact information of the managed repository. __contact = None + # The Mercurial repository interface. + __repo = None + @property def StorageName(self): """ Gets the name of the directory where the managed repository is located. """ return self.__storageName - @StorageName.setter - def StorageName(self, value): - """ Sets the name of the directory with a supplied name. """ - self.__storageName = value @property def DisplayName(self): @@ -87,18 +86,12 @@ self.__storageName = repoName if repoName: try: - repo = hg.repository(ui.ui(), settings.RepositoryPath + os.sep + repoName) - self.__displayName = repo.ui.config('web', 'name', default=None) - self.__description = repo.ui.config('web', 'description', default=None) - self.__contact = repo.ui.config('web', 'contact', default=None) + self.__repo = hg.repository(ui.ui(), settings.RepositoryPath + os.sep + self.__storageName) + self.__displayName = self.__repo.ui.config('web', 'name', default=None) + self.__description = self.__repo.ui.config('web', 'description', default=None) + self.__contact = self.__repo.ui.config('web', 'contact', default=None) except error.RepoError: - self.__displayName = None - self.__description = None - self.__contact = None - else: - self.__displayName = None - self.__description = None - self.__contact = None + pass def __eq__(self, other): """ Determines if two repositories are the same repository. @@ -110,3 +103,67 @@ else-wise gives false. """ return self.__storageName == other.__storageName + + def save(self): + """ Commits the repository detail properties to the repository + configuration file. + + @throws IOError When the configuration file for the repository + fails to be written. + """ + import os + + with open(settings.RepositoryPath + os.sep + self.__storageName + os.sep + '.hg' + os.sep + 'hgrc', 'w') as hgrc: + hgrc.write('[web]\n') + + if self.__displayName is None: + hgrc.write('name = ' + self.__storageName + '\n') + else: + hgrc.write('name = ' + self.__displayName + '\n') + + if self.__description is not None: + hgrc.write('description = ' + self.__description + '\n') + + if self.__contact is not None: + hgrc.write('contact = ' + self.__contact + '\n') + + if self.__repo is None: + from mercurial import ui, hg + import os + + self.__repo = hg.repository(ui.ui(), settings.RepositoryPath + os.sep + self.__storageName) + self.__displayName = self.__repo.ui.config('web', 'name', default=None) + self.__description = self.__repo.ui.config('web', 'description', default=None) + self.__contact = self.__repo.ui.config('web', 'contact', default=None) + + + +class ManagedCollection(object): + """ A container of all the managed repositories. """ + + # All the managed repositories + __repositories = [] + + + @property + def Repositories(self): + """A collection of all managed repositories. + + @return An array of the managed repositories. + """ + return self.__repositories + + def __init__(self): + """ Initialises the container with all the managed repositories. + + @throws Exception When the HgWeb configuration cannot be read. + """ + from ConfigParser import SafeConfigParser + import os + + parser = SafeConfigParser() + if not parser.read(settings.HgWebPath + os.sep + 'hgweb.config'): + raise Exception("Failed to read HgWeb configuration.") + + for name,path in parser.items('paths'): + self.__repositories.append(Repository(name))