# Perforce Defect Tracking Integration Project # # # CONFIGURE_BUGZILLA.PY -- BUILD P4DTI CONFIGURATION FOR BUGZILLA # # Richard Brooksby, Ravenbrook Limited, 2000-12-07 # # # 1. INTRODUCTION # # This is the automatic configurator for the Bugzilla integration of the # Perforce Defect Tracking Integration (P4DTI). Configuration # generators are documented in detail in [GDR 2000-10-16, 8]. # # This code takes a basic set of configuration parameters [RB # 2000-08-10, 5.1] and generates a full set of parameters for the # "dt_bugzilla", "p4", and "replicator" classes, as well as a Perforce # jobspec. # # The intended readership of this document is project developers. # # This document is not confidential. import bugzilla import catalog import check_config import dt_bugzilla import logger import os import string import translator import types import mysqldb_support error = "Bugzilla configuration error" # perforce keyword translator. keyword_translator = translator.keyword_translator() # enum translator (just like keyword translator except '' <-> 'NONE') enum_translator = dt_bugzilla.enum_translator(keyword_translator) # make_state_pairs: (strings * string) -> (string * string) list. Make # a list of pairs of state names (Bugzilla state, Perforce state). This # list will be used to translate between states, and also to generate # the possible values for the Status field in Perforce. # # The closed_state argument is the Bugzilla state which maps to the # special state 'closed' in Perforce, or None if there is no such state. # See requirement 45. See design decision [RB 2000-11-28 14:44:36 # GMT]. # # The case of state names in these pairs is normalized for usability in # Perforce: see design decision [RB 2000-11-28 14:24:32 GMT]. def make_state_pairs(states, closed_state): state_pairs = [] state_p4_to_dt = {} found_closed_state = 0 if closed_state != None: p4_closed_state = keyword_translator.translate_dt_to_p4( string.lower(closed_state)) # Perforce jobs can't have state "new" (this indicates a fresh job # and Perforce changes the state to "open"). Nor can they have # state "ignore", because that is used in the submit form to # indicate that a job shouldn't be fixed by the change. # # Unfortunately, "new" and "ignore" are common names for states in # defect trackers (the former is in the Bugzilla workflow and in # the default workflow in TeamTrack), so we don't disallow them, # but prefix them with 'bugzilla_'. Then we quit if two Bugzilla # states map to the same state in Perforce, ruling out the # unlikely situation that someone has a Bugzilla status of # 'BUGZILLA_CLOSED'. See job000141. prohibited_states = ['new', 'ignore'] prohibited_state_prefix = 'bugzilla_' for dt_state in states: p4_state = keyword_translator.translate_dt_to_p4( string.lower(dt_state)) if closed_state != None: if p4_state == p4_closed_state: p4_state = 'closed' found_closed_state = 1 elif p4_state == 'closed': p4_state = prohibited_state_prefix + p4_state if p4_state in prohibited_states: p4_state = prohibited_state_prefix + p4_state if (state_p4_to_dt.has_key(p4_state) and state_p4_to_dt[p4_state] != dt_state): # "Two Bugzilla states '%s' and '%s' map to the same # Perforce state '%s'." raise error, catalog.msg(300, (dt_state, state_p4_to_dt[p4_state], p4_state)) state_p4_to_dt[p4_state] = dt_state pair = (dt_state, p4_state) if pair not in state_pairs: state_pairs.append(pair) if closed_state != None and not found_closed_state: # "You specified the closed_state '%s', but there's no such # Bugzilla state." raise error, catalog.msg(301, closed_state) return state_pairs # Given the name of an enum column, calculate the values and default. def translate_enum(column, bz_type): if not bz_type['type'] == 'enum': # "The '%s' column of Bugzilla's 'bugs' table is not an enum # type." raise error, catalog.msg(302, column) values = bz_type['values'] values = map(enum_translator.translate_dt_to_p4, values) default = bz_type['default'] if default != None: default = enum_translator.translate_dt_to_p4(default) values = string.join(values,'/') return values, default # Some Bugzilla fields should not be changed from Perforce, either # because they can not be changed from Bugzilla # (e.g. 'creation_ts', 'delta_ts', 'lastdiffed'), or because they # can only be changed in Bugzilla in very restricted ways # (e.g. 'groupset', 'product', 'version', 'component', # 'target_milestone', 'everconfirmed'), or because changing them # in Bugzilla has complex side-effects which can't be sensibly # reproduced here (e.g. 'votes', 'keywords'). read_only_fields = ['bug_id', 'groupset', 'creation_ts', 'delta_ts', 'product', 'version', 'component', 'target_milestone', 'votes', 'keywords', 'lastdiffed', 'everconfirmed', 'estimated_time', 'remaining_time', 'alias'] # Some Bugzilla fields can only be appended to. # In particular, the 'Descriptions' section of a Bugzilla bug # is actually a number of rows from another table, to which # the Bugzilla web interface allows one to add a row. # We fake this as if it's a regular field of the table, but # retain the append-only restriction. append_only_fields = ['longdesc'] # These Bugzilla fields are replicated by default. If they are in # replicated_fields, that's a configuration error. If fields other # than these are in omitted_fields, that's a configuration error too. default_fields = ['bug_status', 'assigned_to', 'short_desc', 'resolution', ] # Map bugzilla field name to a name we can use in Perforce and a # comment. bz_field_map = { 'longdesc': ('Description', "Description and comments."), 'assigned_to': ('Assigned_To', "User to which the bug is assigned."), 'groupset': ('Groupset', None), 'bug_file_loc': ('URL', "The bug's URL."), 'bug_severity': ('Severity', "The bug's severity."), 'bug_status': ('Status', "The bug's status."), 'creation_ts': ('Creation_Timestamp', "Time created."), 'delta_ts': ('Update_Timestamp', "Time last updated."), 'short_desc': ('Summary', "The bug's summary."), 'op_sys': ('OS', "The OS to which the bug applies."), 'priority': ('Priority', "The bug's priority."), 'product': ('Product', "The product."), 'rep_platform': ('Platform', "The hardware platform to which the bug " "applies."), 'reporter': ('Reporter', "The Bugzilla user who reported the bug."), 'version': ('Version', "The product version."), 'component': ('Component', "The product component."), 'resolution': ('Resolution', "The manner in which the bug was resolved."), 'target_milestone': ('TargetMilestone', "The bug's target milestone."), 'qa_contact': ('QA_Contact', "The Bugzilla user who is the QA contact for " "this bug."), 'status_whiteboard': ('StatusWhiteboard', "The bug's status whiteboard."), 'votes': ('Votes', "The number of votes for this bug."), 'keywords': ('Keywords', "Keywords for this bug."), 'lastdiffed': ('LastDiffed', "Time last compared for automated e-mail."), 'everconfirmed': ('EverConfirmed', "Has this bug ever been confirmed?"), 'bug_id': ('Bug_number', "Bug number."), 'reporter_accessible': ('Reporter_accessible', "Accessible to the bug reporter."), 'assignee_accessible': ('Assignee_accessible', "Accessible to the assignee."), 'qacontact_accessible': ('QAContact_accessible', "Accessible to the QA Contact."), 'cclist_accessible': ('CCList_accessible', "Accessible to the CC List."), 'alias': ('Alias', "Bug alias."), 'estimated_time': ('EstimatedTime', "Estimated time in hours."), 'remaining_time': ('RemainingTime', "Remaining time in hours."), } def prepare_issue_advanced(config, bz, p4, dict, job): # Deduce a reporter for the issue, unless we have one already. if dict.get('reporter', "") == "": for field in ['P4DTI-user', config.job_owner_field, 'User']: if job.has_key(field): p4_user = job[field] # note: this is a lax translator bz_user = config.user_translator.translate_p4_to_dt( job[field], bz, p4) if bz_user != config.user_translator.bugzilla_id: dict['reporter'] = bz_user break # Set creation_ts to the 'Date' field, suitably translated. # (otherwise creation_ts gets now()). if job.has_key(config.job_date_field) and dict.get('creation_ts', '') == '': dict['creation_ts'] = config.date_translator.translate_p4_to_dt( job[config.job_date_field], 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.get('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 bugzilla_resolved_statuses = ['RESOLVED', 'VERIFIED', 'CLOSED'] # Must fill in resolution for new jobs. if (dict.has_key('bug_status') and dict['bug_status'] in bugzilla_resolved_statuses and dict.get('resolution','') == ''): if job.get(config.job_status_field, '') == 'suspended': dict['resolution'] = 'LATER' else: dict['resolution'] = 'FIXED' # Supply default values for product, component and version as # promised in [GDR 2001-11-14, 3]. bz.new_issue_defaults(dict) # Call user hook (see [GDR 2001-11-14, 3]). config.prepare_issue(dict, job) def translate_jobspec_advanced(config, dt, p4, job): # Call user hook (see [GDR 2001-11-14, 4.6]). return config.translate_jobspec(job) def get_fields_bz_to_p4(config, bugs_types): fields_bz_to_p4 = {} fields_p4_to_bz = {} # particular requested mappings field_names_bz_to_p4 = {} bz_fields = default_fields + config.replicated_fields for f in config.omitted_fields: bz_fields.remove(f) for (bz_field, p4_field) in config.field_names: field_names_bz_to_p4[bz_field] = p4_field for bz_field in bz_fields: if not bugs_types.has_key(bz_field): # "Bugzilla's table 'bugs' does not have a '%s' column." raise error, catalog.msg(307, bz_field) if field_names_bz_to_p4.has_key(bz_field): p4_field = field_names_bz_to_p4[bz_field] elif bz_field_map.has_key(bz_field): p4_field = bz_field_map[bz_field][0] else: p4_field = "bugzilla_" + bz_field if fields_p4_to_bz.has_key(p4_field): # "Bugzilla fields '%s' and '%s' both map to Perforce field '%s'." raise error, catalog.msg(324, (bz_field, fields_p4_to_bz[p4_field], p4_field)) fields_bz_to_p4[bz_field] = p4_field fields_p4_to_bz[p4_field] = bz_field return fields_bz_to_p4 def check_bugzilla_directory(dir): if dir is not None: # strip any trailing / character if (len(dir) > 1 and dir[-1:] == '/'): dir = dir[:-1] if not os.path.isdir(dir): # "Configuration parameter 'bugzilla_directory' does not # name a directory." raise error, catalog.msg(303) if os.name == 'posix': processmail = dir + '/processmail' elif os.name == 'nt': processmail = dir + '\\processmail.pl' if not os.access(processmail, os.X_OK): # "Configuration parameter 'bugzilla_directory' does not # name a directory containing a processmail script." raise error, catalog.msg(304) return dir def check_field_lists(config): # 1. Validate the replicated_fields config parameter. bz_fields = [] for f in config.replicated_fields: if f in default_fields: # "Field '%s' specified in 'replicated_fields' is a system field: leave it out!" raise error, catalog.msg(311, f) if f in bz_fields: # "Field '%s' appears twice in 'replicated_fields'." raise error, catalog.msg(312, f) else: bz_fields.append(f) # 2. Build a list of the fields we will replicate. bz_fields = bz_fields + default_fields # 3. Validate the omitted_fields config parameter. fields = [] for f in config.omitted_fields: if f not in default_fields: # "Field '%s' specified in 'omitted_fields' is not a system field: leave it out!" raise error, catalog.msg(319, f) if f in fields: # "Field '%s' appears twice in 'omitted_fields'." raise error, catalog.msg(320, f) else: fields.append(f) bz_fields.remove(f) # bz_fields is now the set of fields we will replicate. # 4. Validate the field_names config parameter. bz_fields_check = [] p4_fields_check = [] for (bz_field, p4_field) in config.field_names: if bz_field in bz_fields_check: # "Bugzilla field '%s' appears twice in 'field_names'." raise error, catalog.msg(322, bz_field) bz_fields_check.append(bz_field) if bz_field not in bz_fields: # "Field '%s' in 'field_names' is not a replicated field." raise error, catalog.msg(321, bz_field) if p4_field in p4_fields_check: # "Perforce field '%s' appears twice in 'field_names'." raise error, catalog.msg(323, p4_field) p4_fields_check.append(p4_field) def get_user_name_length(config): # Get the types of the 'profiles' table from Bugzilla. In # particular we need to know the size of the 'login_name' column. profiles_types = config.bugzilla.get_types('profiles') if not profiles_types.has_key('login_name'): # "Bugzilla's table 'profiles' does not have a 'login_name' # column." raise error, catalog.msg(305) if profiles_types['login_name']['type'] != 'text': # "The 'login_name' column of Bugzilla's 'profiles' table does # not have a 'text' type." raise error, catalog.msg(306) return profiles_types['login_name']['length'] def make_p4_field_spec(bz_field, p4_field, bz_type, user_name_length, strict_user_translator): if bz_field_map.has_key(bz_field): p4_comment = bz_field_map[bz_field][1] else: p4_comment = None if p4_comment is None: p4_comment = ("Bugzilla's '%s' field" % bz_field) bz_type_class = bz_type['type'] p4_values = None p4_length = None # if there is a default, use it. if bz_type.get('default'): p4_class = 'default' p4_default = bz_type['default'] else: p4_class = 'optional' p4_default = None # Figure out the Perforce types, lengths, values, and default, and # the translator. # Maybe this should be table-driven. if bz_type_class == 'float': # "Field '%s' specified in 'replicated_fields' list has # floating-point type: this is not yet supported by P4DTI." raise error, catalog.msg(315, bz_field) elif bz_type_class == 'user': p4_type = 'word' p4_length = user_name_length trans = strict_user_translator elif bz_type_class == 'enum': p4_type = 'select' p4_values, p4_default = translate_enum(bz_field, bz_type) trans = enum_translator elif bz_type_class == 'int': p4_type = 'word' trans = dt_bugzilla.int_translator() elif bz_type_class == 'date': p4_type = 'date' p4_length = 20 trans = dt_bugzilla.date_translator() elif bz_type_class == 'timestamp': p4_type = 'date' p4_length = 20 trans = dt_bugzilla.timestamp_translator() elif bz_type_class == 'text': p4_type = 'text' trans = dt_bugzilla.text_translator() else: # "Field '%s' specified in 'replicated_fields' list has type # '%s': this is not yet supported by P4DTI." raise error, catalog.msg(314, (bz_field, bz_type['sql_type'])) # "p4 -G" uses the field "code" to indicate whether the Perforce # command succeeded or failed. See job000003. if p4_field == 'code': # "You can't have a field called 'code' in the Perforce # jobspec." raise error, catalog.msg(316) # Fixed-length fields get the length from Bugzilla. if p4_length == None: p4_length = bz_type['length'] if bz_field in read_only_fields: p4_comment = (p4_comment + " DO NOT MODIFY.") if bz_field in append_only_fields: p4_comment = (p4_comment + " ONLY MODIFY BY APPENDING.") return ( p4_field, p4_type, p4_length, p4_class, p4_default, p4_values, p4_comment, trans, ) def configuration(config): # Check Bugzilla specific configuration parameters. check_config.check_string_or_none(config, 'bugzilla_directory') check_config.check_host(config, 'dbms_host') check_config.check_int(config, 'dbms_port') check_config.check_string(config, 'dbms_database') check_config.check_string(config, 'dbms_user') check_config.check_string(config, 'dbms_password') check_config.check_string(config, 'migrated_user_password') check_config.check_list_of(config, 'migrated_user_groups', types.StringType, 'strings') check_config.check_list_of(config, 'replicated_fields', types.StringType, 'strings') check_config.check_list_of(config, 'omitted_fields', types.StringType, 'strings') check_config.check_list_of_string_pairs(config, 'field_names') config.bugzilla_directory = check_bugzilla_directory(config.bugzilla_directory) check_field_lists(config) # Handle logger. We need a list of logger objects: log_params = { 'priority': config.log_level, 'max_length': config.log_max_message_length, } # The log messages should go to (up to) three places: # 1. to standard output (if running from a command line); loggers = [] if config.use_stdout_log: loggers.append(apply(logger.file_logger, (), log_params)) # 2. to the file named by the log_file configuration parameter (if # not None); if config.log_file != None: loggers.append(apply(logger.file_logger, (open(config.log_file, "a"),), log_params)) # 3. to the Windows event log (if use_windows_event_log is true). if os.name == 'nt' and config.use_windows_event_log: loggers.append(apply(logger.win32_event_logger, (config.rid,), log_params)) # 4. to the Unix syslog (if use_system_log is true). if os.name == 'posix' and config.use_system_log: loggers.append(apply(logger.sys_logger, (), log_params)) # now a single logger object which logs to all of the logger objects # in our list: config.logger = logger.multi_logger(loggers) # Open a connection to the Bugzilla database. This makes a DB-API # v2.0 connection object. To work with a database other than # MySQL, change this to make an appropriate connection object. # Note that in that case changes are also needed in bugzilla.py # where we deal with MySQL-specific types such as tinyint. db = mysqldb_support.connect(config) # Make a Bugzilla DB object. Note that this same object is used # subsequently by the replicator itself. config.bugzilla = bugzilla.bugzilla(db, config) # Get the types of the 'bugs' table from Bugzilla bugs_types = config.bugzilla.get_types('bugs') # Check field names against Bugzilla database and construct # a map, Bugzilla field name to Perforce field name. fields_bz_to_p4 = get_fields_bz_to_p4(config, bugs_types) user_name_length = get_user_name_length(config) # strict user translator doesn't allow unknown users strict_user_translator = dt_bugzilla.user_translator( config.replicator_address, config.p4_user, allow_unknown = 0) # lax user translator does allow unknown users lax_user_translator = dt_bugzilla.user_translator( config.replicator_address, config.p4_user, allow_unknown = 1) # p4_fields maps Bugzilla field name to the jobspec data (number, # name, type, length, dispositon, preset, values, help text, # translator). The fields Job and Date are special: they are not # replicated from Bugzilla but are required by Perforce, so we # have them here. Note that their help text is given (the other # help texts will be obtained from the bz_field_map). p4_fields = { \ '(JOB)': ( 101, 'Job', 'word', 32, 'required', None, None, "The job name.", None ), '(DATE)': ( 104, 'Date', 'date', 20, 'always', '$now', None, "The date this job was last modified.", None ), # P4DTI fields: '(FILESPECS)': ( 191, 'P4DTI-filespecs', 'text', 0, 'optional', None, None, "Associated filespecs.", None ), '(RID)': ( 192, 'P4DTI-rid', 'word', 32, 'required', 'None', None, "P4DTI replicator identifier. Do not edit!", None ), '(ISSUE)': ( 193, 'P4DTI-issue-id', 'word', 32, 'required', 'None', None, "Bugzilla issue database identifier. Do not " "edit!", None ), '(USER)': ( 194, 'P4DTI-user', 'word', 32, 'always', '$user', None, "Last user to edit this job. You can't edit " "this!", None ), } if fields_bz_to_p4.has_key('bug_status'): if bugs_types['bug_status']['type'] != 'enum': # "The 'bug_status' column of Bugzilla's 'bugs' table is not an # enum type." raise error, catalog.msg(308) # Make a list of (Bugzilla state, Perforce state) pairs. state_pairs = make_state_pairs(bugs_types['bug_status']['values'], config.closed_state) # Work out the legal values of the State field in the jobspec. Note # that "closed" must be a legal state because "p4 fix -c CHANGE # JOBNAME" always sets the State to "closed" even if "closed" is not # a legal value. See job000225. legal_states = map((lambda x: x[1]), state_pairs) if 'closed' not in legal_states: legal_states.append('closed') state_values = string.join(legal_states, '/') p4_fields['bug_status'] = ( 102, fields_bz_to_p4['bug_status'], 'select', bugs_types['bug_status']['length'], 'required', state_pairs[0][1], state_values, bz_field_map['bug_status'][1], dt_bugzilla.status_translator(state_pairs)) if fields_bz_to_p4.has_key('assigned_to'): p4_fields['assigned_to'] = ( 103, fields_bz_to_p4['assigned_to'], 'word', user_name_length, 'required', '$user', None, bz_field_map['assigned_to'][1], strict_user_translator) if fields_bz_to_p4.has_key('short_desc'): p4_fields['short_desc'] = ( 105, fields_bz_to_p4['short_desc'], 'text', bugs_types['short_desc']['length'], 'required', '$blank', None, bz_field_map['short_desc'][1], dt_bugzilla.text_translator() ) if fields_bz_to_p4.has_key('resolution'): if bugs_types['resolution']['type'] != 'enum': # "The 'resolution' column of Bugzilla's 'bugs' table is not an # enum type." raise error, catalog.msg(309) if not 'FIXED' in bugs_types['resolution']['values']: # "The 'resolution' column of Bugzilla's 'bugs' table does not # have a 'FIXED' value." raise error, catalog.msg(310) # Make a list of possible resolutions. (resolutions, default_resolution) = translate_enum('resolution', bugs_types['resolution']) p4_fields['resolution'] = ( 106, fields_bz_to_p4['resolution'], 'select', bugs_types['resolution']['length'], 'required', default_resolution, resolutions, bz_field_map['resolution'][1], enum_translator) # Additional replicated fields will be sequential from this field id. p4_field_id = 110 # Go through the replicated_fields list, build structures and add # them to p4_fields. for bz_field in config.replicated_fields: p4_field = fields_bz_to_p4[bz_field] p4_fields[bz_field] = ((p4_field_id, ) + make_p4_field_spec(bz_field, p4_field, bugs_types[bz_field], user_name_length, strict_user_translator)) p4_field_id = p4_field_id + 1 if p4_field_id >= 191: # "Too many fields to replicate: Perforce jobs can contain # only 99 fields." raise error, catalog.msg(317) comment = ("# A Perforce Job Specification automatically " "produced by the\n" "# Perforce Defect Tracking Integration\n") jobspec = (comment, p4_fields.values()) # Set configuration parameters needed by dt_bugzilla. config.append_only_fields = append_only_fields config.read_only_fields = read_only_fields config.jobname_function = lambda bug: 'bug%d' % bug['bug_id'] # Set configuration parameters needed by the replicator. config.date_translator = dt_bugzilla.date_translator() config.job_owner_field = fields_bz_to_p4.get('assigned_to', 'User') config.job_status_field = fields_bz_to_p4.get('bug_status', 'Status') config.job_date_field = 'Date' config.jobspec = jobspec config.prepare_issue_advanced = prepare_issue_advanced config.text_translator = dt_bugzilla.text_translator() config.translate_jobspec_advanced = translate_jobspec_advanced config.user_translator = lax_user_translator # The field_map parameter is a list of triples (Bugzilla database # field name, Perforce field name, translator) required by the # replicator. We use the filter to remove the fields that aren't # replicated: these have no translator. config.field_map = \ map(lambda item: (item[0], item[1][1], item[1][8]), filter(lambda item: item[1][8] != None, p4_fields.items())) return config # A. REFERENCES # # [GDR 2001-11-14] "Perforce Defect Tracking Integration Advanced # Administrator's Guide"; Gareth Rees; Ravenbrook Limited; 2001-11-14; # . # # [GDR 2000-10-16] "Perforce Defect Tracking Integration Integrator's # Guide"; Gareth Rees; Ravenbrook Limited; 2000-10-16; # . # # # B. DOCUMENT HISTORY # # 2000-12-07 RB Branched and adapted from configure_teamtrack.py. # # 2000-12-08 GDR Translate state name "ignore" to "_ignore". # # 2000-12-13 NB State names need to be lower case in Perforce. Also # logger object needed for the bugzilla.bugzilla object. # # 2000-12-15 NB Added verbosity support. # # 2001-01-11 NB Added support for replicated_fields. Also support # closed_state, and move MySQL connection code here (out of bugzilla.py) # so that we only open one connection when starting up. Also sorted out # the size of the jobspec fields. # # 2001-01-12 NB Moved configuration of read-only and append-only fields # here from dt_bugzilla.py. Added configuration of fields not recorded # in the bugs_activity table. Added comments for read-only and # append-only fields. # # 2001-01-15 NB Added a table for field names and comments, because the # automatically-generated ones were terrible. Added validation for # config parameters. # # 2001-01-18 NB Removed bugzilla_user. Moved configuration checks out # to check_config.py. Pass replicator_user to Bugzilla. # # 2001-01-19 NB closed_state and log_file may be None. Use system # logger. # # 2001-01-25 NB Added bugzilla_directory to support processmail. # # 2001-01-26 NB Pass p4_server_description to Bugzilla. # # 2001-02-04 GDR Added start_date parameter. # # 2001-02-08 NB Prevent the existence of DateTime from changing our # behaviour. (job000193). # # 2001-02-09 NB Added checks for bugzilla_directory. # # 2001-02-13 GDR Allow administrator_address and smtp_server to be None. # # 2001-02-16 NB Added replicate_p configuration parameter. # # 2001-02-19 NB Moved keyword translation to p4.py. # # 2001-02-22 GDR Moved keyword translation to keyword.py. Made sure # that 'closed' is a legal job state in Perforce. Added text translator # to replicator config. # # 2001-02-26 GDR Refer to "Bugzilla" explicitly in messages, not just # "defect tracker". # # 2001-03-02 RB Transferred copyright to Perforce under their license. # # 2001-03-12 GDR Use messages for errors. # # 2001-03-13 GDR Removed verbose parameter; added log_level. Removed # P4DTI-action field. Made P4DTI-filespec field optional. Get # keyword_translator from translator, not keyword. # # 2001-03-15 GDR Store configuration in config module. # # 2001-03-23 GDR Added job-date-field to replicator configuration. # # 2001-06-22 NB Moved common jobspec code into p4.py. # # 2001-06-22 NB Added initial comment to the jobspec description. # # 2001-09-19 NB Bugzilla 2.14 (job000390): new fields, some # functionality moved to bugzilla.py. # # 2001-10-25 NB Add check of MySQLdb module version. Moved code to # ensure MySQLdb uses strings for dates and times to after the point # at which we know that the MySQLdb release is supported. # # 2001-10-28 GDR Formatted as a document. # # 2001-11-05 GDR Added prepare_issue_advanced to replicator # configuration. # # 2001-11-20 GDR Added translate_jobspec_advanced to replicator # configuration. Put jobspec in config rather than returning it. # # 2001-11-26 GDR Support log_max_message_length configuration parameter. # # 2001-11-27 GDR Check migrated_user_groups. # # 2002-01-07 NB Use correct translator for enum fields (job000445). # # 2002-01-31 GDR Use mysqldb_support.connnect to connect to the MySQL # database. This turns off date/time conversion in a way that's # portable between MySQLdb releases. # # 2002-04-02 NB User fields should default to the current user, # otherwise we get '0' which is no user at all. job000491. # # 2002-04-03 NB Take default for user fields from bugzilla interface. # Clarify distinction between strict and lax user translators. # # 2002-06-26 RB Merged Bugzilla for Windows 2000 port. # # 2002-10-25 RB Added back logging to syslog under control of new # configuration parameter "use_system_log". # # 2003-05-21 NB Fixed some comments which incorrectly referred to TeamTrack. # # 2003-11-25 NB Rewrite the jobspec-creation code, and the handling of # the relevant configuration parameters. This is a step towards # configurable jobspec. # # # C. COPYRIGHT AND LICENSE # # This file is copyright (c) 2001 Perforce Software, Inc. All rights # reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in # the documentation and/or other materials provided with the # distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH # DAMAGE. # # # $Id: //info.ravenbrook.com/project/p4dti/version/2.1/code/replicator/configure_bugzilla.py#3 $