# HG changeset patch # User John Schneiderman # Date 1406857248 18000 # Node ID 795d652ed4f3b3d99f563d9b8fab8f10d0099597 # Parent 5aaaa084fabd4b0ae4ab19a6d92df2deb744b22f Create an ignore file upon repository creation. IN: - diff -r 5aaaa084fabd -r 795d652ed4f3 data/ignores.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/ignores.xml Thu Jul 31 20:40:48 2014 -0500 @@ -0,0 +1,195 @@ + + + + + + .*(~|\.bak) + + + (.*\.(tmp|swp)|tmp) + + + + + .*\.py(o|c) + + + .*(\.egg(-info)?)|(develop-)?eggs + + + [s]dist + + + {build,parts} + + + {bin,var} + + + {.installed.cfg,pip-log.txt} + + + {.coverage,.tox} + + + *.mo + + + .mr.developer.cfg + + + *.pydevproject + + + + + \.(project|classpath|settings|loadpath|metadata) + + + {bin,target} + + + *~.nib + + + {local.properties,*.launch} + + + .externalToolBuilders + + + .cproject + + + .buildpath + + + + + build + + + *.pbxuser + + + .*\.mode(1|2)v3 + + + *.perspectivev3 + + + *.xcworkspace + + + {xcuserdata,profile} + + + *.moved-aside + + + + + *.{suo,user,sln.docstates,publishsettings} + + + {[Dd]ebug,[Rr]elease,[Pp]ublish,} + + + *.{[Pp]ublish.xml,pubxml} + + + {[Bb]in,[Oo]bj,TestResults,ClientBin,x64,.builds,build} + + + *.{ilk,meta,obj,pch,pdb,pgc,pgd,rsp,sbr,tlb,tli,tlh,dotCover,ipch,Cache,dbmdl,tmp_proj,log,pidb} + + + *_{i,p}.c + + + {*.vs[sp]scc,hgtfs.xml,*.scc} + + + *.{psess,vsp,vspx} + + + packages + + + [Ss]tyle[Cc]op.* + + + *.{aps,ncb,opensdf,sdf,cachefile} + + + Generated_Code + + + {_UpgradeReport_Files,Backup*,UpgradeLog*.XML,UpgradeLog*.htm} + + + {[Tt]est[Rr]esult*,[Bb]uild[Ll]og.*} + + + {App_Data/*.[lm]df,sql} + + + *.gpState + + + {csx,*.build.csdef} + + + AppPackages + + + {_ReSharper*,*.[Rr]e[Ss]harper} + + + [Ee]xpress + + + DocProject/(Help/(Html2|html|(.*\.(Hx[TC]|hh[ckp])))|buildhelp/.*) + + + _TeamCity* + + + {*.ncrunch*,.*crunch*.local.xml} + + + {~$*,*.pfx} + + + + + (eh)?[Tt]humbs\.db + + + {[Dd]esktop.ini,$RECYCLE.BIN} + + + + + .DS_Store + + + diff -r 5aaaa084fabd -r 795d652ed4f3 doc/ChangeLog --- a/doc/ChangeLog Thu Jul 31 19:30:38 2014 -0500 +++ b/doc/ChangeLog Thu Jul 31 20:40:48 2014 -0500 @@ -20,6 +20,7 @@ 0000-00-00 John Schneiderman 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. 2014-07-29 John Schneiderman 0.2.1 - Fixed issue where the configuration file copy started before it closed. - Fixed issue in Windows locking temporary file and preventing copying. diff -r 5aaaa084fabd -r 795d652ed4f3 doc/TODO --- a/doc/TODO Thu Jul 31 19:30:38 2014 -0500 +++ b/doc/TODO Thu Jul 31 20:40:48 2014 -0500 @@ -27,7 +27,6 @@ *** *** *** Feature Goals *** *** *** -- Generate default ignore file using XML. - Allow selection of multiple ignore groups during creation. - Allow plug-ins addition and removal from repositories. - Installation tutorial. @@ -41,3 +40,4 @@ - Manager should prevent two repositories from using the same display name. - Manager should prevent two administrators from editing the same repository. - 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. diff -r 5aaaa084fabd -r 795d652ed4f3 setup.py --- a/setup.py Thu Jul 31 19:30:38 2014 -0500 +++ b/setup.py Thu Jul 31 20:40:48 2014 -0500 @@ -24,54 +24,60 @@ import sys sys.path.append('./src') -from hwm import YEARS,VERSION,DESCRIPTION,LONG_DESCRIPTION,SHORT_NAME +from _app_info import YEARS,VERSION,DESCRIPTION,LONG_DESCRIPTION,SHORT_NAME -required= \ - [ - 'mercurial', - 'mercurial.hg', - 'mercurial.ui', - 'mercurial.error', - 'mercurial.hgweb' - ] +required = [ + 'mercurial', + 'mercurial.hg', + 'mercurial.ui', + 'mercurial.error', + 'mercurial.hgweb' +] # Basic package setup information setup( - name=SHORT_NAME, - version=VERSION, - description=DESCRIPTION, - long_description=LONG_DESCRIPTION, - author='CodeGNU Solutions', - author_email='Licensing@CodeGNU.com', - url='http://www.codegnu.com', - download_url="http://www.codegnu.com/files/hwm-{0}.tar.gz".format(VERSION), - license='AGPLv3+', - packages=['HgWebManager'], + name = SHORT_NAME, + version = VERSION, + description = DESCRIPTION, + long_description = LONG_DESCRIPTION, + author = 'CodeGNU Solutions', + author_email = 'Licensing@CodeGNU.com', + url = 'http://www.codegnu.com', + download_url = "http://www.codegnu.com/files/hwm-{0}.tar.gz".format(VERSION), + license = 'AGPLv3+', + packages = ['HgWebManager'], package_dir = {'HgWebManager': 'src'}, - provides=['HgWebManager'], - requires=required, - platforms= [ - 'GNU/Linux', - 'POSIX', - 'Mac OS', - 'Windows' - ], - classifiers=[ - 'Development Status :: 3 - Alpha', - 'Environment :: Console', - 'Environment :: Web Environment', - 'Intended Audience :: Developers', - 'Intended Audience :: System Administrators', - 'Intended Audience :: Information Technology', - 'License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)', - 'Natural Language :: English', - 'Operating System :: MacOS', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: POSIX :: Linux', - 'Programming Language :: Python :: 2.7', - 'Topic :: Internet :: WWW/HTTP :: Site Management', - 'Topic :: Software Development :: Version Control', - 'Topic :: System :: Systems Administration' - ], - data_files= None, + provides = ['HgWebManager'], + requires = required, + platforms = [ + 'GNU/Linux', + 'POSIX', + 'Mac OS', + 'Windows' + ], + classifiers = [ + 'Development Status :: 3 - Alpha', + 'Environment :: Console', + 'Environment :: Web Environment', + 'Intended Audience :: Developers', + 'Intended Audience :: System Administrators', + 'Intended Audience :: Information Technology', + 'License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)', + 'Natural Language :: English', + 'Operating System :: MacOS', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: POSIX :: Linux', + 'Programming Language :: Python :: 2.7', + 'Topic :: Internet :: WWW/HTTP :: Site Management', + 'Topic :: Software Development :: Version Control', + 'Topic :: System :: Systems Administration' + ], + data_files = [ + ( + "share/" + SHORT_NAME, + [ + 'data/ignores.xml' + ] + ), + ], ) diff -r 5aaaa084fabd -r 795d652ed4f3 src/__init__.py --- a/src/__init__.py Thu Jul 31 19:30:38 2014 -0500 +++ b/src/__init__.py Thu Jul 31 20:40:48 2014 -0500 @@ -24,7 +24,9 @@ __all__ = [ '_app_info.py' , 'config.py' + , 'enum.py' , 'hwm.py' + , 'ignorepo.py' , 'manager.py' , 'manrepo.py' ] diff -r 5aaaa084fabd -r 795d652ed4f3 src/_app_info.py --- a/src/_app_info.py Thu Jul 31 19:30:38 2014 -0500 +++ b/src/_app_info.py Thu Jul 31 20:40:48 2014 -0500 @@ -21,13 +21,18 @@ # The shortened name of the application SHORT_NAME='HWM' + # The full name of the application LONG_NAME='HgWeb Manager' + # The current version of the application VERSION = '0.3.0' + # The current copyright years. YEARS = '2014' + # A short description of the application DESCRIPTION = 'Manages HgWeb Repositories' + # A long description of the application LONG_DESCRIPTION = 'An application to simplify the management of repositories shared through the Mercurial web publishing method.' diff -r 5aaaa084fabd -r 795d652ed4f3 src/config.py --- a/src/config.py Thu Jul 31 19:30:38 2014 -0500 +++ b/src/config.py Thu Jul 31 20:40:48 2014 -0500 @@ -21,6 +21,7 @@ class Manager(object): """ Manages the configurations for HgWebManager """ + # The configuration file parser. __parser = None # The absolute directory path and file name of the hg command. diff -r 5aaaa084fabd -r 795d652ed4f3 src/enum.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/enum.py Thu Jul 31 20:40:48 2014 -0500 @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +#******************************************************************************* +#** This file is part of HgWeb Manager. *** +#** *** +#** Copyright (C) 2014 *** +#** CodeGNU Solutions *** +#** *** +#** This program is free software: you can redistribute it and/or modify it *** +#** under the terms of the GNU Affero General Public License as published *** +#** by the Free Software Foundation, either version 3 of the License, or *** +#** (at your option) any later version. *** +#** *** +#** This program is distributed in the hope that it will be useful, but *** +#** WITHOUT ANY WARRANTY; without even the implied warranty of *** +#** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *** +#** See the GNU Affero General Public License for more details. *** +#** *** +#** You should have received a copy of the GNU Affero General Public License*** +#** along with this program. If not, see . *** +#******************************************************************************* + +def create(*sequential, **named): + """ Creates a type which mimics the behaviour of an enumeration. + + @param[in] sequential + @param[in] named + + @return An object which contains the supplied enumerated values. + """ + enums = dict(zip(sequential, range(len(sequential))), **named) + reverse = dict((value, key) for key, value in enums.iteritems()) + enums['reverse_mapping'] = reverse + return type('Enum', (), enums) diff -r 5aaaa084fabd -r 795d652ed4f3 src/hwm.py --- a/src/hwm.py Thu Jul 31 19:30:38 2014 -0500 +++ b/src/hwm.py Thu Jul 31 20:40:48 2014 -0500 @@ -27,17 +27,18 @@ from _app_info import * -# Configuration values for HgWebManager. try: + # Configuration values for HgWebManager. settings = config.Manager() except Exception as e: import sys + print >>sys.stderr, 'Settings Error:', e exit(1) def is_hgWeb_user(): - """Determines if the current running user is the expected HgWeb user. + """ Determines if the current running user is the expected HgWeb user. @return Gives true when the user is the expected HgWeb user, false else-wise. @@ -52,7 +53,7 @@ def __extract_values(args): - """Pulls the creation arguments out of the command-line. + """ Pulls the creation arguments out of the command-line. @param[in] args The command-line argument processor containing the creation argument values. @@ -78,6 +79,7 @@ def main(args): import argparse import manager + import sys if not is_hgWeb_user(): print >>sys.stderr, "Must execute as the Mercurial Web manager." diff -r 5aaaa084fabd -r 795d652ed4f3 src/ignorepo.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ignorepo.py Thu Jul 31 20:40:48 2014 -0500 @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +#******************************************************************************* +#** This file is part of HgWeb Manager. *** +#** *** +#** Copyright (C) 2014 *** +#** CodeGNU Solutions *** +#** *** +#** This program is free software: you can redistribute it and/or modify it *** +#** under the terms of the GNU Affero General Public License as published *** +#** by the Free Software Foundation, either version 3 of the License, or *** +#** (at your option) any later version. *** +#** *** +#** This program is distributed in the hope that it will be useful, but *** +#** WITHOUT ANY WARRANTY; without even the implied warranty of *** +#** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *** +#** See the GNU Affero General Public License for more details. *** +#** *** +#** You should have received a copy of the GNU Affero General Public License*** +#** along with this program. If not, see . *** +#******************************************************************************* + +""" + IMPORTS +""" +import enum + + +# The types of filtering available for an ignore file. +FilterType = enum.create('Glob', 'RegularExpression') + +def ignore_syntax_name(filltertype): + """ Provides the mapping between an enumerated filter type and the + Mercurial ignore section header. If an invalid argument is supplied, an + empty string is given. + + @param[in] filltertype Is the enumerated value to map. + + @return The string value of the supplied enumerated value. + """ + if FilterType.Glob == filltertype: + return 'glob' + elif FilterType.RegularExpression == filltertype: + return 'regexp' + else: + return '' + + +class Pattern(object): + """ Describes the properties of an entry in a Mercurial ignore file. """ + + # A short entry describing the purpose of the ignore entry. + __description = None + # The type of ignore entry used by Mercurial. + __filter = None + # The ignore expression to provide for an ignore file. + __value = None + + @property + def Description(self): + """ A short entry describing the purpose of the ignore entry when + provided, else gives miscellaneous. + """ + if self.__description: + return self.__description + else: + return 'Miscellaneous' + + @property + def Filter(self): + """ The type of ignore entry used by Mercurial. """ + return self.__filter + + @property + def Value(self): + """ The ignore expression to provide for an ignore file. """ + return self.__value + + def __init__(self, desc, fil, val): + """ Initialises the object with the values supplied. + + @param[in] desc The description of the ignore entry. + @param[in] fil The type of ignore filtering. + @param[in] val The ignore expression. + """ + self.__description = desc.strip() + if fil == 'glob': + self.__filter = FilterType.Glob + elif fil == 'regex': + self.__filter = FilterType.RegularExpression + else: + raise Exception("Invalid filter type '%s' supplied" % fil) + self.__value = val.strip() + +def extract(ignoreFile): + """ Parses an XML file for ignore file patterns. Generates a dictionary + object whose key is the name of the group for a collection of ignore + patterns. The value in each dictionary is an array of all the ignore + pattern objects within that group. Upon any parsing error, no ignore + patterns are supplied. + + @param[in] ignoreFile The directory path and file name to an XML file + that contains the ignore patterns to extract. + + @throw Exception When the name of a group is reused + in the XML file. + @throw xml.parsers.expat.ExpatError When the XML file cannot be parsed. + + @return The extracted grouped ignore patterns. + """ + from xml.dom import minidom + + ignores = {} + xmlIgnore = minidom.parse(ignoreFile) + for group in xmlIgnore.getElementsByTagName('ignores')[0].getElementsByTagName('group'): + groupName = group.getAttribute('name') + if groupName in ignores: + raise Exception("The group '%s' was already defined." % groupName) + else: + ignores[groupName] = [] + for pattern in group.getElementsByTagName('pattern'): + ignores[groupName].append(Pattern( \ + pattern.getAttribute('description'), \ + pattern.getAttribute('type'), \ + pattern.firstChild.nodeValue)) + return ignores + diff -r 5aaaa084fabd -r 795d652ed4f3 src/manager.py --- a/src/manager.py Thu Jul 31 19:30:38 2014 -0500 +++ b/src/manager.py Thu Jul 31 20:40:48 2014 -0500 @@ -50,6 +50,8 @@ # Create the requested repository. if __addRepository(repository): print "Initialised repository directory." + if not create_ignores(repository): + return False else: print >>sys.stderr, "Failed to create repository directory." return False @@ -191,12 +193,115 @@ print >>sys.stderr, "Failed to delete the repository." return False +def create_ignores(repository): + """ Generates and commits an ignore file to an existing repository. + + @pre The repository to create an ignore file must already exist. The list + of ignore patterns to use is expected to be in an XML file named + "ignores.xml" and be in the hwm-ignore schema. + + @param[in] repository The targeted repository for the ignore file. + + @post The managed repository now has an ignore file committed by the + HgWeb manager. + + @return When the creation is successful gives true, else-wise false. + """ + import os + import ignorepo + import sys + + # Extract Ignore Collection + ignores = {} + try: + ignores = ignorepo.extract('ignores.xml') + except xml.parsers.expat.ExpatError as e: + print >>sys.stderr, "Failed to extract ignores, error: %s" % e.message + return False + + # Write Ignore File + ignoreFile = settings.RepositoryPath + os.sep + repository.StorageName + os.sep + '.hgignore' + with open(ignoreFile, 'w') as hgIgnore: + for group,patterns in ignores.items(): + hgIgnore.write("#\n#\tIgnore Group: %s\n#" % group) + # Reverse the sorting order to put the regular expressions first. + patterns.sort(key = lambda p: p.Filter, reverse = True) + filteringOn = None + for pattern in patterns: + if pattern.Filter == filteringOn: + hgIgnore.write("\n%s\t\t# %s" % (pattern.Value, pattern.Description)) + else: + hgIgnore.write("\nsyntax: %s\n%s\t\t# %s" % (ignorepo.ignore_syntax_name(pattern.Filter), pattern.Value, pattern.Description)) + filteringOn = pattern.Filter + hgIgnore.write('\n\n') + + if __addFile(repository, '.hgignore'): + print "Successfully added generated ignores." + else: + print >>sys.stderr, "Failed to add generated ignores." + return False + + if __commitChanges(repository, 'Created repository and set-up ignores.'): + print "Successfully committed generated ignores." + else: + print >>sys.stderr, "Failed to commit generated ignores." + return False + return True + #--- Functions for fulfilling repository management. +def __commitChanges(repo, message): + """ Commits all changes in a managed Mercurial repository as the HgWeb + manager. + + @pre The requested repository must already exist. + + @param[in] repo The targeted repository for which to commit. + @param[in] message The text to use as a commit message. + + @post Upon any error the standard error buffer contains an error message. + + @return When the process is successful gives true, else-wise false. + """ + import subprocess + import sys + import os + + try: + statusCode = subprocess.call([settings.HgCommand, '--repository', settings.RepositoryPath + os.sep + repo.StorageName, 'commit', '--message', message, '--user', settings.HgUser + ' - HgWeb Manager']) + except OSError as e: + print >>sys.stderr, "Commit Error({0}): {1}".format(e.errno, e.strerror) + return False + return (0 == statusCode) + +def __addFile(repo, fileName): + """ Marks a file as added in a managed Mercurial repository. + + @pre The requested repository must already exist. + + @param[in] repo The targeted repository for which to add the file. + @param[in] fileName The directory path and the file name to add + relative to the base of the repository. + + @post Upon any error the standard error buffer contains an error message. + + @return When the process is successful gives true, else-wise false. + """ + import subprocess + import sys + import os + + try: + statusCode = subprocess.call([settings.HgCommand, '--repository', settings.RepositoryPath + os.sep + repo.StorageName, 'add', settings.RepositoryPath + os.sep + repo.StorageName + os.sep + fileName]) + except OSError as e: + print >>sys.stderr, "Add Error({0}): {1}".format(e.errno, e.strerror) + return False + return (0 == statusCode) + def __renameStorage(oldRepo, newName): """ Changes the storage name of an existing repository. diff -r 5aaaa084fabd -r 795d652ed4f3 src/manrepo.py --- a/src/manrepo.py Thu Jul 31 19:30:38 2014 -0500 +++ b/src/manrepo.py Thu Jul 31 20:40:48 2014 -0500 @@ -26,6 +26,10 @@ class Repository(object): + """ Describes the properties of a repository managed by HWM and served up + by HgWeb. + """ + # The directory name of the managed repository. __storageName = None # The displayed web-site name of the managed repository.