# Configuration file for migrating all Xebeo jobs to Bugzilla # # $Id: //info.ravenbrook.com/project/p4dti/branch/2001-08-07/migrate-bugzilla/code/replicator/configure_xebeo.py#1 $ # # The Xebeo jobspec is currently as follows: # # A Perforce Job Spec Specification. # # # # Updating this form can be dangerous! # # See 'p4 help jobspec' for proper directions. # # Fields: # 101 Job word 32 required # 102 Status select 10 required # 103 User word 32 required # 104 Date date 20 always # 105 Description text 0 required # # Values: # Status open/suspended/closed # # Presets: # Status open # User $user # Date $now # Description $blank # # Comments: # # A Perforce Job Specification. # # # # Job: The job name. 'new' generates a sequenced job number. # # Status: Either 'open', 'closed', or 'suspended'. Can be changed. # # User: The user who created the job. Can be changed. # # Date: The date this specification was last modified. # # Description: Comments about the job. Required. import configure_bugzilla import dt_bugzilla import translator import types import string import p4 import socket # Translators for status and resolution. These have been written to # work for both the initial migration, when there is no Perforce # 'Resolution' field, and the map is simply this: # # open -> ASSIGNED # closed -> RESOLVED/FIXED # suspended -> RESOLVED/LATER # # and for the subsequent replication, when Perforce gets a # 'Resolution' field (containing Bugzilla's status and resolution). # # Always prefer changes to Perforce's 'Status' field to changes to # 'Resolution' (because the 'Status' is changed by p4 fix etc). class my_status_translator(translator.translator): def translate_0_to_1(self, value, bz, p4, bug, job = None): assert isinstance(value, types.StringType) if bug['resolution'] == 'LATER': return 'suspended' elif bug['resolution'] == '': return 'open' else: return 'closed' # Translating a Perforce 'Status' to Bugzilla. # # If we can tell that the Perforce 'Status' has changed, then we # want to say 'ASSIGNED' (for open bugs) and 'RESOLVED' (for # closed bugs). This should make p4 fix work correctly. # # If the Perforce 'Status' has not changed, we should get the # status out of the new 'Resolution' field (which discriminates # between a number of different Bugzilla statuses). def translate_1_to_0(self, value, bz, p4, bug, job): assert isinstance(value, types.StringType) if bug: bug_status = self.translate_0_to_1(bug['bug_status'], bz, p4, bug, job) if bug_status == value: # 'Status' has not changed in Perforce, so use status # from the 'Resolution' field if we can. if job.has_key('Resolution'): resolution = job['Resolution'] offset = string.find(resolution, '-') if offset > 0: return string.upper(resolution[:offset]) else: return string.upper(resolution) # default status from the 'Status' field. if value == 'open': # default return 'ASSIGNED' else: return 'RESOLVED' class my_resolution_translator(translator.translator): status_translator = None def __init__(self): self.status_translator = my_status_translator() def translate_0_to_1(self, value, bz, p4, bug, job = None): assert isinstance(value, types.StringType) status = string.lower(bug['bug_status']) if value == '': return status else: return status + '-' + string.lower(value) def translate_1_to_0(self, value, bz, p4, bug, job): assert isinstance(value, types.StringType) job_status = job['Status'] if bug: # can see whether 'Status' has changed bug_status = self.status_translator.translate_0_to_1(bug['bug_status'], bz, p4, bug, job) if bug_status == job_status: # 'Status' has not changed in Perforce # use the resolution from 'Resolution' offset = string.find(value, '-') if offset > 0: return string.upper(value[offset+1:]) # 'Status' has changed or we are migrating if job_status == 'open': return '' elif job_status == 'closed': return 'FIXED' else: # job_status == 'suspended' return 'LATER' # using this translation mechanism, here's the set of resolutions. def perforce_resolutions(): bugzilla_unresolved_statuses = ['unconfirmed', 'new', 'assigned', 'reopened'] bugzilla_resolved_statuses = ['resolved', 'verified', 'closed'] bugzilla_resolutions = ['fixed', 'invalid', 'wontfix', 'remind', 'duplicate', 'worksforme', 'later'] resolutions = bugzilla_unresolved_statuses[:] # copy list for status in bugzilla_resolved_statuses: for resolution in bugzilla_resolutions: resolutions.append(status + '-' + resolution) return string.join(resolutions,'/') def migrate_p(job): return 1 default_resolution = 'new' defaults = { 'product': 'X-series', # new field in jobspec 'version': 'Pre-R1-Beta1', # new field in jobspec 'component': 'X-series', # new field in jobspec } # This function is called when migrating a job from Perforce to the # defect tracker, before the field_map has been applied. It is passed # the job. The purpose of this function is to modify the job in such # a way that the field_map can be safely applied. # # For instance, if the jobspec is to be changed after migration, the # field_map may not know how to translate values of fields which are # possible before migration but not afterwards. def pre_migrate_issue(config, bz, p4, job): pass # This function is called when migrating a job from Perforce to the # defect tracker, after the field_map has been applied. It is passed # a dictionary representing the issue, which has been filled in by # applying the field_map. The purpose of this function is then to # fill in any other fields required or useful in the defect tracker. # For instance, Bugzilla requires 'short_desc' and 'reporter' both to # be set (even if they are not replicated). # # During initial migration, fields may be added to the jobspec for # later replication with defect tracker fields. These fields will # appear in the field map, but migrated jobs will not have values for # them and so the field map translators will be passed the empty # string for them. This is then a good place to fill in meaningful # defaults for those fields. # # For Xebeo, such fields are 'product', 'component', 'version', and # 'short_desc'. # # Also during initial migration, one must consider any jobspec fields # which are not present in all jobs, even if they are 'required' in # the jobspec. For instance, a field may have been added to the # jobspec after some jobs had already been created. Such a field will # be passed to the field map as an empty string, and may be filled in # here. # # For Xebeo, there are no such fields. def migrate_issue(config, bz, p4, dict, job): # we need a reporter field to create a bug. If we've installed a # P4DTI jobspec, we have the P4DTI-user field, so should translate # that. Otherwise, we have to use the User field. if not dict.has_key('reporter'): if job.has_key('P4DTI-user'): p4user = job['P4DTI-user'] elif job.has_key('Assigned_To'): p4user = job['Assigned_To'] else: p4user = job['User'] dict['reporter'] = config.user_translator.translate_1_to_0(p4user, bz, p4) # Set creation_ts to the 'Date' field, suitably translated. # (otherwise creation_ts gets now()). if job.has_key('Date') and dict.get('creation_ts','') == '': dict['creation_ts'] = config.date_translator.translate_1_to_0(job['Date'], bz, p4) # If no summary, get short description from the first line of the # long description if dict.get('short_desc','') == '': short_desc = string.strip(job['Description']) newline_pos = string.find(short_desc, '\n') if newline_pos >= 0: short_desc = short_desc[:newline_pos] if short_desc == '': short_desc = 'No description' dict['short_desc'] = short_desc # default values for various fields which Bugzilla requires but # the field map may not supply. Defined in 'defaults' above. for k,v in defaults.items(): if dict.get(k,'') == '': dict[k] = v def configuration(config): default_jobspec, revised_config = configure_bugzilla.configuration(config) default_jobspec_comment, default_jobspec_fields = default_jobspec p4_interface = p4.p4(client = ('p4dti-%s' % socket.gethostname()), client_executable = revised_config.p4_client_executable, password = revised_config.p4_password, port = revised_config.p4_port, user = revised_config.p4_user, logger = revised_config.logger) jobspec = p4_interface.get_jobspec() jobspec_comment, jobspec_fields = jobspec fields_by_name = {} new_field_code = 0 for field in jobspec_fields: fields_by_name[field[1]] = field if field[0] >= new_field_code and field[0] < 190: new_field_code = field[0] + 1 # Add our fields to the jobspec: new_fields = [ ( 'Summary', 'text', 80, 'required', '$blank', None, 'One-line summary of the job.', None), ( 'Resolution', 'select', 32, 'default', default_resolution, perforce_resolutions(), 'Bugzilla status and resolution', None ), # these three job fields need to be selects, so that users # can't set them to bogus things. Currently there's only one # possible value. In future other values will be added and # the jobspec should then be modified by hand accordingly. ( 'Component', 'select', 32, 'default', defaults['component'], defaults['component'], 'Product component', None ), ( 'Version', 'select', 32, 'default', defaults['version'], defaults['version'], 'Product version', None ), ( 'Product', 'select', 32, 'default', defaults['product'], defaults['product'], 'Product', None ), # creation timestamp ( 'Creation-date', 'date', 20, 'once', '$now', None, 'The date this job was created', None ), ] for new_field in new_fields: if not fields_by_name.has_key(new_field[0]): jobspec_fields.append ((new_field_code,) + new_field) new_field_code = new_field_code + 1 # If the P4DTI fields are not already in the jobspec, get them out # of the default jobspec and add them to it. if not p4_interface.jobspec_has_p4dti_fields(jobspec): for field in default_jobspec_fields: if field[1][:6] == 'P4DTI-': jobspec_fields.append(field) # our field map. revised_config.field_map = [ ('longdesc', 'Description', dt_bugzilla.text_translator()), ('short_desc', 'Summary', dt_bugzilla.text_translator()), ('assigned_to', 'Assigned_To', revised_config.user_translator), ('bug_status', 'Status', my_status_translator()), ('resolution', 'Resolution', my_resolution_translator()), ('component', 'Component', translator.translator()), ('version', 'Version', translator.translator()), ('product', 'Product', translator.translator()), ] # function applied before replication to correct a job issue # (e.g. by filling in additional fields) before giving the new # issue to the defect tracker config.pre_migrate_issue = pre_migrate_issue # function applied after replication to correct an issue (e.g. by # filling in additional fields) before giving the new issue to # the defect tracker revised_config.migrate_issue = migrate_issue # function to apply to a job to decide whether or not to # migrate it to the defect tracker. revised_config.migrate_p = migrate_p # When a job is created by the replicator (because a new issue is # created in the defect tracker), should it use an # automatically-assigned Perforce jobname? revised_config.use_perforce_jobnames = 1 # to which Bugzilla groups should migrated users belong? revised_config.migrated_user_groups = ['editbugs', 'canconfirm'] # the Bugzilla password of a newly migrated user. revised_config.migrated_user_password = 'p4dti-new' revised_config.migrated_jobspec_description = (jobspec_comment, jobspec_fields) return None, revised_config