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 @@
"""Generate repository information on Business Templates.
"""
import posixpath
import tarfile
import os
import sys
import cgi
from base64 import b64encode
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
version
revision
description
license
dependency_list
provision_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:
text = property_file.read()
if property_name.endswith('_list'):
property_dict[property_name[:-5]] = text.splitlines()
else:
property_dict[property_name] = text
text = file.read()
finally:
property_file.close()
def readBusinessTemplate(tar):
"""Read an archived Business Template info.
"""
property_dict = {}
for info in tar:
name_list = info.name.split('/')
if len(name_list) == 3 and name_list[1] == 'bt' and name_list[2] in property_list:
property_file = tar.extractfile(info)
property_name = name_list[2]
readProperty(property_dict, property_name, property_file)
return property_dict
def readBusinessTemplateDirectory(dir):
"""Read Business Template Directory info.
"""
property_dict = {}
for property_name in property_list:
filename = os.path.join(dir, 'bt', property_name)
if os.path.isfile(filename):
property_file = open(filename, 'rb')
readProperty(property_dict, property_name, property_file)
return property_dict
file.close()
if path.startswith('bt/'):
name = path[3:]
if name in self.property_list:
if name.endswith('_list'):
self[name[:-5]] = text.splitlines()
else:
self[name] = text
elif name == 'revision':
return
self.revision.hash(unquote(path) if '%' in path else path, text)
def __iter__(self):
self['revision'] = self.revision.digest()
return iter(sorted(self.iteritems()))
@classmethod
def fromTar(cls, tar):
"""Read an archived Business Template info"""
self = cls()
for info in tar:
if not info.isdir():
name = info.name.split('/', 1)[1]
if name.split('/', 1)[0] in item_set:
self._read(name, tar.extractfile(info))
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):
xml = StringIO()
......@@ -100,16 +188,16 @@ def generateInformation(dir, info=id, err=None):
continue
raise
try:
property_dict = readBusinessTemplate(tar)
property_list = BusinessTemplate.fromTar(tar)
finally:
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)
property_dict = readBusinessTemplateDirectory(path)
property_list = BusinessTemplate.fromDir(path)
else:
continue
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:
xml.write(' <%s>%s</%s>\n' % (k, cgi.escape(v), k))
xml.write(' </template>\n')
......
......@@ -108,7 +108,7 @@
<string>my_id</string>
<string>my_title</string>
<string>my_version</string>
<string>my_revision</string>
<string>my_short_revision</string>
<string>my_translated_building_state_title</string>
<string>my_translated_installation_state_title</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 @@
<string>Version</string>
</tuple>
<tuple>
<string>revision</string>
<string>short_revision</string>
<string>Revision</string>
</tuple>
<tuple>
......
......@@ -340,7 +340,7 @@
<string>Version</string>
</tuple>
<tuple>
<string>revision</string>
<string>short_revision</string>
<string>Revision</string>
</tuple>
<tuple>
......
......@@ -343,14 +343,14 @@
<string>version</string>
<string>Version</string>
</tuple>
<tuple>
<string>revision</string>
<string>Revision</string>
</tuple>
<tuple>
<string>installed_version</string>
<string>Installed Version</string>
</tuple>
<tuple>
<string>short_revision</string>
<string>Revision</string>
</tuple>
<tuple>
<string>installed_revision</string>
<string>Installed Revision</string>
......@@ -367,10 +367,6 @@
<string>license</string>
<string>License</string>
</tuple>
<tuple>
<string>version_state_title</string>
<string>State</string>
</tuple>
</list>
</value>
</item>
......
bin/genbt5list
\ No newline at end of file
......@@ -2824,24 +2824,6 @@ class BusinessTemplateMixin(TestDeveloperMixin, ERP5TypeTestCase, LogInterceptor
self.failUnless(base_category_obj is not None)
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):
""" Check if bt has no missing dependency
"""
......@@ -5126,26 +5108,6 @@ class TestBusinessTemplate(BusinessTemplateMixin):
sequence_list.addSequenceString(sequence_string)
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):
"""Test if a new Business Template has no dependencies"""
sequence_list = SequenceList()
......
This diff is collapsed.
......@@ -461,43 +461,15 @@ class ERP5TypeTestCaseMixin(ProcessingNodeTestCase, PortalTestCase):
DeprecationWarning)
return self.createUserAssignment(user, assignment_kw)
def setupAutomaticBusinessTemplateRepository(self, accept_public=True,
searchable_business_template_list=None):
# Try to setup some valid Repository List by reusing ERP5TypeTestCase API.
# if accept_public we can accept public repository can be set, otherwise
# we let failure happens.
if searchable_business_template_list is None:
searchable_business_template_list = ["erp5_base"]
# Assume that the public official repository is a valid repository
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 setupAutomaticBusinessTemplateRepository(self,
searchable_business_template_list=("erp5_base",)):
template_tool = self.portal.portal_templates
bt_set = set(searchable_business_template_list).difference(x['title']
for x in template_tool.repository_dict.itervalues() for x in x)
if bt_set:
template_tool.updateRepositoryBusinessTemplateList(set(
os.path.dirname(x[0]) for x in self._getBTPathAndIdList(bt_set)),
genbt5list=1)
def failIfDifferentSet(self, a, b, msg=""):
if not msg:
......
......@@ -187,7 +187,7 @@ class WorkingCopy(Implicit):
"""
if business_template.getBuildingState() == 'draft':
business_template.edit()
business_template.build()
business_template.build(update_revision=False)
self._export(business_template)
def _export(self, business_template):
......@@ -200,16 +200,6 @@ class WorkingCopy(Implicit):
def update(self, keep=False):
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):
try:
hasDiff = aq_base(self).__hasDiff
......@@ -329,7 +319,7 @@ class WorkingCopy(Implicit):
title='tmp_bt_revert',
template_path_list=path_added_list)
tmp_bt.edit()
tmp_bt.build()
tmp_bt.build(update_revision=False)
# Install then uninstall it to remove objects from ZODB
tmp_bt.install()
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