Commit f73b625c authored by Julien Muchembled's avatar Julien Muchembled

BT: don't export 'revision' anymore

Because on DVCS with branches, history is not always linear, we must stop
using increasing integer for revisions because they cause too many conflicts
and a higher revision number does not necessarily means the a BT newer.
They're now a cryptographic hash of all the contents of the Business Template,
and they're computed automatically:
- by genbt5list
- at download
- at building (by default)
- at export
parent ce483b26
This diff is collapsed.
This diff is collapsed.
...@@ -31,59 +31,147 @@ ...@@ -31,59 +31,147 @@
"""Generate repository information on Business Templates. """Generate repository information on Business Templates.
""" """
import posixpath
import tarfile import tarfile
import os import os
import sys import sys
import cgi import cgi
from base64 import b64encode
from cStringIO import StringIO from cStringIO import StringIO
from hashlib import sha1
from urllib import unquote
property_list = '''
# Order is important for installation
# We want to have:
# * path after module, because path can be module content
# * path after categories, because path can be categories content
# * path after portal types roles so that roles in the current bt can be used
# * path before workflow chain, because path can be a portal type
# (until chains are set on portal types with categories)
# * skin after paths, because we can install a custom connection string as
# path and use it with SQLMethods in a skin.
# ( and more )
item_name_list = (
'registered_version_priority_selection',
'product',
'document',
'property_sheet',
'constraint',
'extension',
'test',
'role',
'tool',
'message_translation',
'workflow',
'site_property',
'portal_type',
'portal_type_allowed_content_type',
'portal_type_hidden_content_type',
'portal_type_property_sheet',
'portal_type_base_category',
'category',
'module',
'portal_type_roles',
'path',
'skin',
'registered_skin_selection',
'preference',
'action',
'local_roles',
'portal_type_workflow_chain',
'catalog_method',
'catalog_result_key',
'catalog_related_key',
'catalog_result_table',
'catalog_search_key',
'catalog_keyword_key',
'catalog_datetime_key',
'catalog_full_text_key',
'catalog_request_key',
'catalog_multivalue_key',
'catalog_topic_key',
'catalog_scriptable_key',
'catalog_role_key',
'catalog_local_role_key',
'catalog_security_uid_column',
)
item_set = set(('CatalogDateTimeKey' if x == 'catalog_datetime_key' else
''.join(map(str.title, x.split('_')))) + 'TemplateItem'
for x in item_name_list)
item_set.add('bt')
item_name_list = tuple('_%s_item' % x for x in item_name_list)
class BusinessTemplateRevision(list):
def hash(self, path, text):
self.append((path, sha1(text).digest()))
def digest(self):
self.sort()
return b64encode(sha1('\0'.join(h + p for (h, p) in self)).digest())
class BusinessTemplate(dict):
property_list = frozenset('''
title title
version version
revision
description description
license license
dependency_list dependency_list
provision_list provision_list
copyright_list copyright_list
'''.split() '''.split())
bt_title_path = os.path.join('bt', 'title') def __init__(self):
self.revision = BusinessTemplateRevision()
def readProperty(property_dict, property_name, property_file): def _read(self, path, file):
try: try:
text = property_file.read() text = file.read()
if property_name.endswith('_list'):
property_dict[property_name[:-5]] = text.splitlines()
else:
property_dict[property_name] = text
finally: finally:
property_file.close() file.close()
if path.startswith('bt/'):
def readBusinessTemplate(tar): name = path[3:]
"""Read an archived Business Template info. if name in self.property_list:
""" if name.endswith('_list'):
property_dict = {} self[name[:-5]] = text.splitlines()
for info in tar: else:
name_list = info.name.split('/') self[name] = text
if len(name_list) == 3 and name_list[1] == 'bt' and name_list[2] in property_list: elif name == 'revision':
property_file = tar.extractfile(info) return
property_name = name_list[2] self.revision.hash(unquote(path) if '%' in path else path, text)
readProperty(property_dict, property_name, property_file)
def __iter__(self):
return property_dict self['revision'] = self.revision.digest()
return iter(sorted(self.iteritems()))
def readBusinessTemplateDirectory(dir):
"""Read Business Template Directory info. @classmethod
""" def fromTar(cls, tar):
property_dict = {} """Read an archived Business Template info"""
for property_name in property_list: self = cls()
filename = os.path.join(dir, 'bt', property_name) for info in tar:
if os.path.isfile(filename): if not info.isdir():
property_file = open(filename, 'rb') name = info.name.split('/', 1)[1]
readProperty(property_dict, property_name, property_file) if name.split('/', 1)[0] in item_set:
self._read(name, tar.extractfile(info))
return property_dict return iter(self)
@classmethod
def fromDir(cls, dir):
"""Read Business Template Directory info"""
self = cls()
lstrip_len = len(dir + os.sep)
for root, dirs, files in os.walk(dir):
if root:
for path in files:
path = os.path.join(root, path)
self._read(posixpath.normpath(path[lstrip_len:]), open(path, 'rb'))
else:
dirs[:] = item_set.intersection(dirs)
return iter(self)
def generateInformation(dir, info=id, err=None): def generateInformation(dir, info=id, err=None):
xml = StringIO() xml = StringIO()
...@@ -100,16 +188,16 @@ def generateInformation(dir, info=id, err=None): ...@@ -100,16 +188,16 @@ def generateInformation(dir, info=id, err=None):
continue continue
raise raise
try: try:
property_dict = readBusinessTemplate(tar) property_list = BusinessTemplate.fromTar(tar)
finally: finally:
tar.close() tar.close()
elif os.path.isfile(os.path.join(path, bt_title_path)): elif os.path.isfile(os.path.join(path, 'bt', 'title')):
info('Reading Directory %s... ' % name) info('Reading Directory %s... ' % name)
property_dict = readBusinessTemplateDirectory(path) property_list = BusinessTemplate.fromDir(path)
else: else:
continue continue
xml.write(' <template id="%s">\n' % name) xml.write(' <template id="%s">\n' % name)
for k, v in sorted(property_dict.iteritems()): for k, v in property_list:
for v in (v,) if type(v) is str else v: for v in (v,) if type(v) is str else v:
xml.write(' <%s>%s</%s>\n' % (k, cgi.escape(v), k)) xml.write(' <%s>%s</%s>\n' % (k, cgi.escape(v), k))
xml.write(' </template>\n') xml.write(' </template>\n')
......
...@@ -108,7 +108,7 @@ ...@@ -108,7 +108,7 @@
<string>my_id</string> <string>my_id</string>
<string>my_title</string> <string>my_title</string>
<string>my_version</string> <string>my_version</string>
<string>my_revision</string> <string>my_short_revision</string>
<string>my_translated_building_state_title</string> <string>my_translated_building_state_title</string>
<string>my_translated_installation_state_title</string> <string>my_translated_installation_state_title</string>
<string>my_description</string> <string>my_description</string>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>description</string>
<string>editable</string>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_short_revision</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>description</string> </key>
<value> <string>A shortened hash of the contents of the Business Template. The hash is computed at download, build and export.</string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_string_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Revision Number</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -349,7 +349,7 @@ ...@@ -349,7 +349,7 @@
<string>Version</string> <string>Version</string>
</tuple> </tuple>
<tuple> <tuple>
<string>revision</string> <string>short_revision</string>
<string>Revision</string> <string>Revision</string>
</tuple> </tuple>
<tuple> <tuple>
......
...@@ -340,7 +340,7 @@ ...@@ -340,7 +340,7 @@
<string>Version</string> <string>Version</string>
</tuple> </tuple>
<tuple> <tuple>
<string>revision</string> <string>short_revision</string>
<string>Revision</string> <string>Revision</string>
</tuple> </tuple>
<tuple> <tuple>
......
...@@ -343,14 +343,14 @@ ...@@ -343,14 +343,14 @@
<string>version</string> <string>version</string>
<string>Version</string> <string>Version</string>
</tuple> </tuple>
<tuple>
<string>revision</string>
<string>Revision</string>
</tuple>
<tuple> <tuple>
<string>installed_version</string> <string>installed_version</string>
<string>Installed Version</string> <string>Installed Version</string>
</tuple> </tuple>
<tuple>
<string>short_revision</string>
<string>Revision</string>
</tuple>
<tuple> <tuple>
<string>installed_revision</string> <string>installed_revision</string>
<string>Installed Revision</string> <string>Installed Revision</string>
...@@ -367,10 +367,6 @@ ...@@ -367,10 +367,6 @@
<string>license</string> <string>license</string>
<string>License</string> <string>License</string>
</tuple> </tuple>
<tuple>
<string>version_state_title</string>
<string>State</string>
</tuple>
</list> </list>
</value> </value>
</item> </item>
......
bin/genbt5list
\ No newline at end of file
...@@ -2824,24 +2824,6 @@ class BusinessTemplateMixin(TestDeveloperMixin, ERP5TypeTestCase, LogInterceptor ...@@ -2824,24 +2824,6 @@ class BusinessTemplateMixin(TestDeveloperMixin, ERP5TypeTestCase, LogInterceptor
self.failUnless(base_category_obj is not None) self.failUnless(base_category_obj is not None)
self.assertEquals(len(base_category_obj.objectIds()), 0) self.assertEquals(len(base_category_obj.objectIds()), 0)
def stepCheckInitialRevision(self, sequence=None, **kw):
""" Check if revision of a new bt is an empty string
"""
bt = sequence.get('current_bt')
self.assertEqual(bt.getRevision(), '')
def stepCheckFirstRevision(self, sequence=None, **kw):
""" Check if revision of the bt is 1
"""
bt = sequence.get('current_bt')
self.assertEqual(bt.getRevision(), '1')
def stepCheckSecondRevision(self, sequence=None, **kw):
""" Check if revision of the bt is 2
"""
bt = sequence.get('current_bt')
self.assertEqual(bt.getRevision(), '2')
def stepCheckNoMissingDependencies(self, sequence=None, **kw): def stepCheckNoMissingDependencies(self, sequence=None, **kw):
""" Check if bt has no missing dependency """ Check if bt has no missing dependency
""" """
...@@ -5126,26 +5108,6 @@ class TestBusinessTemplate(BusinessTemplateMixin): ...@@ -5126,26 +5108,6 @@ class TestBusinessTemplate(BusinessTemplateMixin):
sequence_list.addSequenceString(sequence_string) sequence_list.addSequenceString(sequence_string)
sequence_list.play(self) sequence_list.play(self)
# test of portal types
def test_22_RevisionNumberIsIncremented(self):
"""Test is revision number is incremented with the bt is built"""
sequence_list = SequenceList()
sequence_string = '\
CreatePortalType \
CreateNewBusinessTemplate \
UseExportBusinessTemplate \
CheckInitialRevision \
BuildBusinessTemplate \
CheckBuiltBuildingState \
stepCheckFirstRevision \
BuildBusinessTemplate \
stepCheckSecondRevision \
RemoveBusinessTemplate \
RemovePortalType \
'
sequence_list.addSequenceString(sequence_string)
sequence_list.play(self)
def test_23_CheckNoDependencies(self): def test_23_CheckNoDependencies(self):
"""Test if a new Business Template has no dependencies""" """Test if a new Business Template has no dependencies"""
sequence_list = SequenceList() sequence_list = SequenceList()
......
This diff is collapsed.
...@@ -461,43 +461,15 @@ class ERP5TypeTestCaseMixin(ProcessingNodeTestCase, PortalTestCase): ...@@ -461,43 +461,15 @@ class ERP5TypeTestCaseMixin(ProcessingNodeTestCase, PortalTestCase):
DeprecationWarning) DeprecationWarning)
return self.createUserAssignment(user, assignment_kw) return self.createUserAssignment(user, assignment_kw)
def setupAutomaticBusinessTemplateRepository(self, accept_public=True, def setupAutomaticBusinessTemplateRepository(self,
searchable_business_template_list=None): searchable_business_template_list=("erp5_base",)):
# Try to setup some valid Repository List by reusing ERP5TypeTestCase API. template_tool = self.portal.portal_templates
# if accept_public we can accept public repository can be set, otherwise bt_set = set(searchable_business_template_list).difference(x['title']
# we let failure happens. for x in template_tool.repository_dict.itervalues() for x in x)
if searchable_business_template_list is None: if bt_set:
searchable_business_template_list = ["erp5_base"] template_tool.updateRepositoryBusinessTemplateList(set(
os.path.dirname(x[0]) for x in self._getBTPathAndIdList(bt_set)),
# Assume that the public official repository is a valid repository genbt5list=1)
public_bt5_repository_list = ['http://www.erp5.org/dists/snapshot/bt5/']
template_list = []
for bt_id in searchable_business_template_list:
bt_template_list = self._getBTPathAndIdList([bt_id])
if len(bt_template_list):
template_list.append(bt_template_list[0])
if len(template_list) > 0:
bt5_repository_path_list = ["/".join(x[0].split("/")[:-1])
for x in template_list]
if accept_public:
try:
self.portal.portal_templates.updateRepositoryBusinessTemplateList(
bt5_repository_path_list, None)
except (RuntimeError, IOError), e:
# If bt5 repository is not a repository use public one.
self.portal.portal_templates.updateRepositoryBusinessTemplateList(
public_bt5_repository_list)
else:
self.portal.portal_templates.updateRepositoryBusinessTemplateList(
bt5_repository_path_list, None)
elif accept_public:
self.portal.portal_templates.updateRepositoryBusinessTemplateList(
public_bt5_repository_list)
else:
raise ValueError("ERP5 was unable to determinate a valid local " + \
"repository, please check your environment or " + \
"use accept_public as True")
def failIfDifferentSet(self, a, b, msg=""): def failIfDifferentSet(self, a, b, msg=""):
if not msg: if not msg:
......
...@@ -187,7 +187,7 @@ class WorkingCopy(Implicit): ...@@ -187,7 +187,7 @@ class WorkingCopy(Implicit):
""" """
if business_template.getBuildingState() == 'draft': if business_template.getBuildingState() == 'draft':
business_template.edit() business_template.edit()
business_template.build() business_template.build(update_revision=False)
self._export(business_template) self._export(business_template)
def _export(self, business_template): def _export(self, business_template):
...@@ -200,16 +200,6 @@ class WorkingCopy(Implicit): ...@@ -200,16 +200,6 @@ class WorkingCopy(Implicit):
def update(self, keep=False): def update(self, keep=False):
raise NotAWorkingCopyError raise NotAWorkingCopyError
def newRevision(self):
path = os.path.join('bt', 'revision')
try:
revision = int(self.showOld(path)) + 1
except NotVersionedError:
return 1
with open(os.path.join(self.working_copy, path), 'w') as file:
file.write(str(revision))
return revision
def hasDiff(self, path): def hasDiff(self, path):
try: try:
hasDiff = aq_base(self).__hasDiff hasDiff = aq_base(self).__hasDiff
...@@ -329,7 +319,7 @@ class WorkingCopy(Implicit): ...@@ -329,7 +319,7 @@ class WorkingCopy(Implicit):
title='tmp_bt_revert', title='tmp_bt_revert',
template_path_list=path_added_list) template_path_list=path_added_list)
tmp_bt.edit() tmp_bt.edit()
tmp_bt.build() tmp_bt.build(update_revision=False)
# Install then uninstall it to remove objects from ZODB # Install then uninstall it to remove objects from ZODB
tmp_bt.install() tmp_bt.install()
tmp_bt.uninstall() tmp_bt.uninstall()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment