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))