diff --git a/migrate.cfg.sagetracmigrationarchive b/migrate.cfg.sagetracmigrationarchive index 6ca79f826e..c8554ede25 100644 --- a/migrate.cfg.sagetracmigrationarchive +++ b/migrate.cfg.sagetracmigrationarchive @@ -11,7 +11,13 @@ url: https://trac.sagemath.org/xmlrpc # url: http://username:password@example.com/trac/login/xmlrpc # optional path to trac instance used to convert some attachments -# path: /path/to/trac/instance +path: sage_trac + +# optional prefix used for Trac milestones +milestone_prefix: sage- + +# optional url for cgit repository access +cgit_url: https://git.sagemath.org/sage.git/ # if no, a trac ticket reference is converted to the corresponding issue reference keep_trac_ticket_references: yes @@ -41,6 +47,43 @@ keywords_to_labels: {'beginner': 'good first issue'} # Migrate milestones migrate_milestones: yes +# Map for certain Trac milestones to GitHub labels +milestones_to_labels = { + 'sage-duplicate/invalid/wontfix': 'duplicate/invalid/wontfix', + 'sage-duplicate/invalid': 'duplicate/invalid/wontfix', + 'sage-duplicate': 'duplicate/invalid/wontfix', + 'sage-wait': 'pending', + 'sage-pending': 'pending', + 'sage-feature': 'feature', + 'sage-wishlist': 'wishlist', + 'sage-combinat': 'component: combinatorics', + 'sage-symbolics': 'component: symbolics', + 'sage-i18n': 'component: translations'} + +# Map for canceld Trac milestones to GitHub milestones +canceled_milestones = { + 'sage-2.8.4.3': 'sage-2.8.5', + 'sage-3.2.4': 'sage-3.3', + 'sage-4.0.3': 'sage-4.1', + 'sage-4.1.3': 'sage-4.2', + 'sage-4.4.5': 'sage-4.5', + 'sage-4.7.3': 'sage-4.8', + 'sage-6.11': 'sage-7.0', + 'sage-7.7': 'sage-8.0'} + +# Map for certain Trac components to GitHub labels +components_to_labels = { + 'solaris': 'porting: solaris', + 'cygwin': 'porting: cygwin', + 'freebsd': 'porting: bsd', + 'aix or hp-ux ports': 'porting: aix or hp-ux', + 'experimental package': 'packages: experimental', + 'optional packages': 'packages: optional', + 'plotting': 'graphics', + 'doctest': 'doctest coverage', + 'sage-check': 'spkg-check'} + + [attachments] # Export attachement as files to the local filesystem or try to upload them as Gist? @@ -84,6 +127,9 @@ project_name: sagemath/sage # GitHub password (if no token specified) #password: secret +# optional prefix used for GitHub milestones +milestone_prefix: sage- + # Where to write a migration archive migration_archive: archive @@ -1055,3 +1101,6 @@ usernames = { 'arattan': None, 'joskarsson': None, 'shahuwang': None} + + +unknown_users_prefix: sagetrac- diff --git a/migrate.cfg.sagetracwikionly b/migrate.cfg.sagetracwikionly index cb9a7dce10..90bcb59b88 100644 --- a/migrate.cfg.sagetracwikionly +++ b/migrate.cfg.sagetracwikionly @@ -14,6 +14,9 @@ url: https://trac.sagemath.org/xmlrpc # optional path to trac instance used to convert some attachments # path: /path/to/trac/instance +# optional url for cgit repository access +cgit_url: https://git.sagemath.org/sage.git/ + # if no, a trac ticket reference is converted to the corresponding issue reference keep_trac_ticket_references: yes diff --git a/migrate.py b/migrate.py index fd9feb69e2..6b71bcb0fe 100755 --- a/migrate.py +++ b/migrate.py @@ -55,6 +55,7 @@ from github.NamedUser import NamedUser from github.Repository import Repository from github.GithubException import IncompletableObject +from enum import Enum from migration_archive_writer import MigrationArchiveWritingRequester @@ -101,25 +102,70 @@ config.read('migrate.cfg') trac_url = config.get('source', 'url') -trac_url_dir = os.path.dirname(trac_url) -trac_url_ticket = os.path.join(trac_url_dir, 'ticket') -trac_url_wiki = os.path.join(trac_url_dir, 'wiki') -trac_url_query = os.path.join(trac_url_dir, 'query') -trac_url_report = os.path.join(trac_url_dir, 'report') -trac_url_attachment = os.path.join(trac_url_dir, 'attachment') + +cgit_url = None +if config.has_option('source', 'cgit_url'): + cgit_url = config.get('source', 'cgit_url') + +milestone_prefix_from = '' +if config.has_option('source', 'milestone_prefix'): + milestone_prefix_from = config.get('source', 'milestone_prefix') + +trac_path = None +if config.has_option('source', 'path') : + trac_path = config.get('source', 'path') keep_trac_ticket_references = config.getboolean('source', 'keep_trac_ticket_references') +class subdir(Enum): + """ + Enum for subdirectories of `trac_url_dir` + """ + def optional_path(self): + """ + Return the optional path for this sub directory + according to `trac_path` + """ + if trac_path: + return os.path.join(trac_path, self.value) + + ticket = 'ticket' + wiki = 'wiki' + query = 'query' + report = 'report' + attachment = 'attachment' + raw_attachment = 'raw-attachment' + attachment_ticket = 'attachment/ticket' + raw_attachment_ticket = 'raw-attachment/ticket' + root = '' + +class cgit_cmd(Enum): + """ + Enum for git commands used in the cgit web interface. + """ + commit = 'commit' + diff = 'diff' + tree = 'tree' + log = 'log' + tag = 'tag' + refs = 'refs' + plain = 'plain' + patch = 'patch' + default = '' + +trac_url_dir = os.path.dirname(trac_url) +trac_url_ticket = os.path.join(trac_url_dir, subdir.ticket.value) +trac_url_wiki = os.path.join(trac_url_dir, subdir.wiki.value) +trac_url_query = os.path.join(trac_url_dir, subdir.query.value) +trac_url_report = os.path.join(trac_url_dir, subdir.report.value) +trac_url_attachment = os.path.join(trac_url_dir, subdir.attachment.value) + if config.has_option('target', 'issues_repo_url'): target_url_issues_repo = config.get('target', 'issues_repo_url') target_url_git_repo = config.get('target', 'git_repo_url') if config.has_option('wiki', 'url'): target_url_wiki = config.get('wiki', 'url') -trac_path = None -if config.has_option('source', 'path') : - trac_path = config.get('source', 'path') - github_api_url = config.get('target', 'url') github_token = None if config.has_option('target', 'token') : @@ -136,6 +182,15 @@ migration_archive = config.get('target', 'migration_archive') users_map = ast.literal_eval(config.get('target', 'usernames')) + +unknown_users_prefix = '' +if config.has_option('target', 'unknown_users_prefix'): + unknown_users_prefix = config.get('target', 'unknown_users_prefix') + +milestone_prefix_to = '' +if config.has_option('target', 'milestone_prefix'): + milestone_prefix_to = config.get('target', 'milestone_prefix') + must_convert_issues = config.getboolean('issues', 'migrate') only_issues = None if config.has_option('issues', 'only_issues'): @@ -151,6 +206,19 @@ except ValueError: keywords_to_labels = ast.literal_eval(config.get('issues', 'keywords_to_labels')) migrate_milestones = config.getboolean('issues', 'migrate_milestones') + +milestones_to_labels = {} +if config.has_option('issues', 'milestones_to_labels'): + milestones_to_labels = ast.literal_eval(config.get('issues', 'milestones_to_labels')) + +canceled_milestones = {} +if config.has_option('issues', 'canceled_milestones'): + canceled_milestones = ast.literal_eval(config.get('issues', 'canceled_milestones')) + +components_to_labels = {} +if config.has_option('issues', 'components_to_labels'): + components_to_labels = ast.literal_eval(config.get('issues', 'components_to_labels')) + add_label = None if config.has_option('issues', 'add_label'): @@ -288,136 +356,272 @@ def read_closing_commits(): RE_BRANCH_FORCED_PUSH = re.compile(r'^(Branch pushed to git repo; I updated commit sha1[.] This was a forced push[.])') RE_BRANCH_PUSH = re.compile(r'^(Branch pushed to git repo; I updated commit sha1( and set ticket back to needs_review)?[.])') -def convert_wiki_link(match): - trac_path = match.group(1) - - if trac_path in wiki_path_conversion_table: - wiki_path = wiki_path_conversion_table[trac_path] - return os.path.join(target_url_wiki, wiki_path) - - return match.group(0) - -def convert_git_link_diff1(match): - path = match.group(1) - hash1 = match.group(2) - return os.path.join(target_url_git_repo, 'blob', hash1, path) - -def convert_git_link_diff2(match): - path = match.group(1) - hash1 = match.group(2) - return os.path.join(target_url_git_repo, 'compare', hash1 + '...' + path) - -def convert_git_link_diff3(match): - hash1 = match.group(1) - hash2 = match.group(2) - return os.path.join(target_url_git_repo, 'compare', hash1 + '...' + hash2) - -def convert_git_link_diff4(match): - hash1 = match.group(1) - return os.path.join(target_url_git_repo, 'commit', hash1) - -def convert_git_link_diff5(match): - path1 = match.group(1) - path2 = match.group(2) - hash1 = match.group(3) - return os.path.join(target_url_git_repo, 'compare', hash1 + '...' + path2) - -def convert_git_link_diff6(match): - branch = match.group(1) - path = match.group(2) - return os.path.join(target_url_git_repo, 'compare', path + '...' + branch) - -def convert_git_link_diff7(match): - branch = match.group(1) - return os.path.join(target_url_git_repo, 'commits', branch) - -def convert_git_link_diff8(match): - branch = match.group(1) - return os.path.join(target_url_git_repo, 'commits', branch) - -def convert_git_link_commit1(match): - path = match.group(1) - hash1 = match.group(2) - return os.path.join(target_url_git_repo, 'commit', hash1) - -def convert_git_link_commit3(match): - hash1 = match.group(1) - return os.path.join(target_url_git_repo, 'commit', hash1) - -def convert_git_link_commit4(match): - path = match.group(1) - return os.path.join(target_url_git_repo, 'commits', path) - -def convert_git_link_commit5(match): - path = match.group(1) - return os.path.join(target_url_git_repo, 'commit', path) - -def convert_git_link_commit6(match): - path = match.group(1) - branch = match.group(2) - hash1 = match.group(3) - return os.path.join(target_url_git_repo, 'compare', hash1 + '...' + branch) - -def convert_git_link_commit7(match): - path = match.group(1) - hash1 = match.group(2) - return os.path.join(target_url_git_repo, 'commit', hash1, path) - -def convert_git_link_tree1(match): - path = match.group(1) - return os.path.join(target_url_git_repo, 'blob/develop', path) - -def convert_git_link_tree3(match): - branch = match.group(1) - return os.path.join(target_url_git_repo, 'tree', branch) - -def convert_git_link_log1(match): - path = match.group(1) - return os.path.join(target_url_git_repo, 'commits', path) - -def convert_git_link_log3(match): - hash1 = match.group(1) - hash2 = match.group(2) - hash3 = match.group(3) - return os.path.join(target_url_git_repo, 'compare', hash1 + '...' + hash2) - -def convert_git_link_log4(match): - path = match.group(1) - hash1 = match.group(2) - return os.path.join(target_url_git_repo, 'commits', 'develop?after=' + hash1 + '+0' + '&branch=develop' - + '&path%5B%5D=' + '&path%5B%5D='.join(path.split('/')) + '&qualified_name=refs%2Fheads%2Fdevelop') - -def convert_git_link_log5(match): - path = match.group(1) - return os.path.join(target_url_git_repo, 'commits/develop', path) - -def convert_git_link_plain(match): - path = match.group(1) - branch = match.group(2) - return os.path.join(target_url_git_repo, 'blob', branch, path) - -def convert_git_link_patch(match): - hash1 = match.group(1) - return os.path.join(target_url_git_repo, 'commit', hash1 + '.patch') - -def convert_git_link(match): # catch all missed git link - import pdb; pdb.set_trace() + +class CodeTag: + """ + Handler for code protectors. + """ + def replace(self, text): + """ + Return the given string with protection tags replaced by their proper counterparts. + """ + text = text.replace(self.tag, self._code) + return text + + def __init__(self, tag, code): + self.tag = tag + self._code = code + +at_sign = CodeTag('AT__SIGN__IN__CODE', '@') + +class Brackets: + """ + Handler for bracket protectors. + """ + def replace(self, text): + """ + Return the given string with protection tags replaced by their proper counterparts. + """ + text = text.replace(self.open, self._open_bracket) + text = text.replace(self.close, self._close_bracket) + return text + + def __init__(self, open_tag, close_tag, open_bracket, close_bracket): + self.open = open_tag + self.close = close_tag + self._open_bracket = open_bracket + self._close_bracket = close_bracket + +link_displ = Brackets('OPENING__LEFT__BRACKET', 'CLOSING__RIGHT__BRACKET', '[', ']') +proc_code = Brackets('OPENING__PROCESSOR__CODE', 'CLOSING__PROCESSOR__CODE', '```', '```') +proc_td = Brackets('OPENING__PROCESSOR__TD', 'CLOSING__PROCESSOR__TD', r'
', r'
') + +class SourceUrlConversionHelper: + """ + Conversion helper for pattern involving url-data from source configuration. + """ + class regex(Enum): + pass + + def __init__(self, url): + self._re = {} + if not url: + # path might be optional dependend on configuration + return + for reg in self.regex: + expr, path, argument = reg.value + if isinstance(path, Enum): + path = path.value + if path is None: + # path might be optional dependend on configuration + continue + path = os.path.join(url, path) + self._re[reg] = re.compile(r'%s%s' % (self._url_pattern(path), expr)) + + def _url_pattern(self, url): + pattern = url.replace('https', 'https?') + pattern = pattern.replace('.', '\\.') + return pattern + + def sub(self, text): + if not len(self._re): + # all expressions are optional and not activ + return text + for reg in self._re.keys(): + expr, path, argument = reg.value + text = self._re[reg].sub(argument, text) + return text + + +class TracUrlConversionHelper(SourceUrlConversionHelper): + """ + Conversion helper for pattern involving the Trac url. + """ + class regex(Enum): + """ + """ + def convert_wiki_link(match): + trac_path = match.group(1) + + if trac_path in wiki_path_conversion_table: + wiki_path = wiki_path_conversion_table[trac_path] + return os.path.join(target_url_wiki, wiki_path) + return match.group(0) + + def convert_ticket_attachment(match): + ticket_id = match.group(1) + filename = match.group(2) + if keep_trac_ticket_references: + return os.path.join(trac_url_attachment, 'ticket', ticket_id, filename) + return gh_attachment_url(ticket_id, filename) + + TICKET1 = [r'/(\d+)#comment:(\d+)?', subdir.ticket, r'ticket:\1#comment:\2'] + TICKET2 = [r'/(\d+)', subdir.ticket.optional_path(), r'%s/issues/\1' % target_url_issues_repo] + WIKI1= [r'/([/\-\w0-9@:%._+~#=]+)', subdir.wiki, convert_wiki_link] + ATTACHMENT1 = [r'/(\d+)/([/\-\w0-9@:%._+~#=]+)', subdir.attachment_ticket, convert_ticket_attachment] + ATTACHMENT2 = [r'/(\d+)/([/\-\w0-9@:%._+~#=]+)', subdir.attachment_ticket.optional_path(), convert_ticket_attachment] + ATTACHMENT3 = [r'/(\d+)/([/\-\w0-9@:%._+~#=]+)', subdir.raw_attachment_ticket.optional_path(), convert_ticket_attachment] + +class CgitConversionHelper(SourceUrlConversionHelper): + """ + Conversion helper for pattern involving the cgit web interface. + """ + class regex(Enum): + """ + """ + def convert_git_link_diff1(match): + path = match.group(1) + hash1 = match.group(2) + return os.path.join(target_url_git_repo, 'blob', hash1, path) + + def convert_git_link_diff2(match): + path = match.group(1) + hash1 = match.group(2) + return os.path.join(target_url_git_repo, 'compare', hash1 + '...' + path) + + def convert_git_link_diff3(match): + hash1 = match.group(1) + hash2 = match.group(2) + return os.path.join(target_url_git_repo, 'compare', hash1 + '...' + hash2) + + def convert_git_link_diff4(match): + hash1 = match.group(1) + return os.path.join(target_url_git_repo, 'commit', hash1) + + def convert_git_link_diff5(match): + path1 = match.group(1) + path2 = match.group(2) + hash1 = match.group(3) + return os.path.join(target_url_git_repo, 'compare', hash1 + '...' + path2) + + def convert_git_link_diff6(match): + branch = match.group(1) + path = match.group(2) + return os.path.join(target_url_git_repo, 'compare', path + '...' + branch) + + def convert_git_link_diff7(match): + branch = match.group(1) + return os.path.join(target_url_git_repo, 'commits', branch) + + def convert_git_link_diff8(match): + branch = match.group(1) + return os.path.join(target_url_git_repo, 'commits', branch) + + def convert_git_link_commit1(match): + path = match.group(1) + hash1 = match.group(2) + return os.path.join(target_url_git_repo, 'commit', hash1) + + def convert_git_link_commit3(match): + hash1 = match.group(1) + return os.path.join(target_url_git_repo, 'commit', hash1) + + def convert_git_link_commit4(match): + path = match.group(1) + return os.path.join(target_url_git_repo, 'commits', path) + + def convert_git_link_commit5(match): + path = match.group(1) + return os.path.join(target_url_git_repo, 'commit', path) + + def convert_git_link_commit6(match): + path = match.group(1) + branch = match.group(2) + hash1 = match.group(3) + return os.path.join(target_url_git_repo, 'compare', hash1 + '...' + branch) + + def convert_git_link_commit7(match): + path = match.group(1) + hash1 = match.group(2) + return os.path.join(target_url_git_repo, 'commit', hash1, path) + + def convert_git_link_tree1(match): + path = match.group(1) + return os.path.join(target_url_git_repo, 'blob/develop', path) + + def convert_git_link_tree2(match): + branch = match.group(1) + return os.path.join(target_url_git_repo, 'tree', branch) + + def convert_git_link_log1(match): + path = match.group(1) + return os.path.join(target_url_git_repo, 'commits', path) + + def convert_git_link_log3(match): + hash1 = match.group(1) + hash2 = match.group(2) + hash3 = match.group(3) + return os.path.join(target_url_git_repo, 'compare', hash1 + '...' + hash2) + + def convert_git_link_log4(match): + path = match.group(1) + hash1 = match.group(2) + return os.path.join(target_url_git_repo, 'commits', 'develop?after=' + hash1 + '+0' + '&branch=develop' + + '&path%5B%5D=' + '&path%5B%5D='.join(path.split('/')) + '&qualified_name=refs%2Fheads%2Fdevelop') + + def convert_git_link_log5(match): + path = match.group(1) + return os.path.join(target_url_git_repo, 'commits/develop', path) + + def convert_git_link_plain(match): + path = match.group(1) + branch = match.group(2) + return os.path.join(target_url_git_repo, 'blob', branch, path) + + def convert_git_link_patch(match): + hash1 = match.group(1) + return os.path.join(target_url_git_repo, 'commit', hash1 + '.patch') + + def convert_git_link(match): # catch all missed git link + import pdb; pdb.set_trace() + + DIFF1 = [r'/([/\-\w0-9@:%._+~#=]+)\?id=([0-9a-f]+)', cgit_cmd.diff, convert_git_link_diff1] + DIFF2 = [r'/?\?h=([/\-\w0-9@:%._+~#=]+)&id2=([0-9a-f]+)', cgit_cmd.diff, convert_git_link_diff2] + DIFF3 = [r'/?\?id2?=([0-9a-f]+)&id=([0-9a-f]+)', cgit_cmd.diff, convert_git_link_diff3] + DIFF4 = [r'/?\?id=([0-9a-f]+)', cgit_cmd.diff, convert_git_link_diff4] + DIFF5 = [r'/?([/\-\w0-9@:%._+~#=]+)\?h=([/\-\w0-9@:%._+~#=]+)&id=([0-9a-f]+)', cgit_cmd.diff, convert_git_link_diff5] + DIFF6 = [r'/?\?id2=([/\-\w0-9@:%._+~#=]+)&id=([/\-\w0-9@:%._+~#=]+)', cgit_cmd.diff, convert_git_link_diff6] + DIFF7 = [r'/?\?h=([/\-\w0-9@:%._+~#=]+)', cgit_cmd.diff, convert_git_link_diff7] + DIFF8 = [r'/?\?id=([/\-\w0-9@:%._+~#=]+)', cgit_cmd.diff, convert_git_link_diff8] + + COMMIT1 = [r'/?\?h=([/\-\w0-9@:%._+~#=]+)&id=([0-9a-f]+)', cgit_cmd.commit, convert_git_link_commit1] + COMMIT2 = [r'id=([0-9a-f]+)', cgit_cmd.commit, convert_git_link_commit3] # misspelled + COMMIT3 = [r'/?\?id=([0-9a-f]+)', cgit_cmd.commit, convert_git_link_commit3] + COMMIT4 = [r'/?\?h=([/\-\w0-9@:%._+~#=]+)', cgit_cmd.commit, convert_git_link_commit4] + COMMIT5 = [r'/?\?id=([/\-\w0-9@:%._+~#=]+)', cgit_cmd.commit, convert_git_link_commit5] + COMMIT6 = [r'/([/\-\w0-9@:%._+~#=]+)\?h=([/\-\w0-9@:%._+~#=]+)&id=([0-9a-f]+)', cgit_cmd.commit, convert_git_link_commit6] + COMMIT7 = [r'/([/\-\w0-9@:%._+~#=]+)\?id=([0-9a-f]+)', cgit_cmd.commit, convert_git_link_commit7] + COMMIT8 = [r'/([/\-\w0-9@:%._+~#=]+)\?h=([0-9a-f]+)', cgit_cmd.commit, convert_git_link_commit7] + + TREE1 = [r'/([/\-\w0-9@:%._+~#=]+)', cgit_cmd.tree, convert_git_link_tree1] + TREE2 = [r'/?\?h=([/\-\w0-9@:%._+~#=]+)', cgit_cmd.tree, convert_git_link_tree2] + TREE3 = [r'/src/?', cgit_cmd.tree, r'%s/blob/master/src' % target_url_git_repo] + + LOG1 = [r'/?\?h=([/\-\w0-9@:%._+~#=]+)', cgit_cmd.log, convert_git_link_log1] + LOG2 = [r'/?\?q=([0-9a-f]+)..([0-9a-f]+)&h=([0-9a-f]+)&qt=range', cgit_cmd.log, convert_git_link_log3] + LOG3 = [r'/?([/\-\w0-9@:%._+~#=]+)\?h=([0-9a-f]+)', cgit_cmd.log, convert_git_link_log4] + LOG4 = [r'/?([/\-\w0-9@:%._+~#=]+)', cgit_cmd.log, convert_git_link_log5] + + PLAIN1 = [r'/([/\-\w0-9@:%._+~#=]+)\?h=([/\-\w0-9@:%._+~#=]+)', cgit_cmd.plain, convert_git_link_plain] + PATCH1 = [r'/?\?id=([0-9a-f]+)', cgit_cmd.patch, convert_git_link_patch] + REFS1 = [r'/?', cgit_cmd.refs, r'%s/branches' % target_url_git_repo] + TAG1 = [r'/?\?id=([/\-\w0-9@:%._+~#=]+)', cgit_cmd.tag, r'%s/releases/tag/\1' % target_url_git_repo] + DEF = [r'/(.*)', cgit_cmd.default, convert_git_link] # catch all missed + +trac_url_conv_help = TracUrlConversionHelper(trac_url_dir) +cgit_conv_help = CgitConversionHelper(cgit_url) + +RE_WRONG_FORMAT1 = re.compile(r'comment:(\d+):ticket:(\d+)') +RE_REPLYING_TO = re.compile(r'Replying to \[comment:(\d+)\s([\-\w0-9@._]+)\]') def inline_code_snippet(match): code = match.group(1) - code = code.replace('@', 'AT__SIGN__IN__CODE') + code = code.replace('@', at_sign.tag) if '`' in code: return '' + code.replace('`', r'\`') + '' else: return '`' + code + '`' -def convert_ticket_attachment(match): - ticket_id = match.group(1) - filename = match.group(2) - if keep_trac_ticket_references: - return os.path.join(trac_url_attachment, 'ticket', ticket_id, filename) - return gh_attachment_url(ticket_id, filename) - def convert_replying_to(match): comment_id = match.group(1) username = match.group(2) @@ -429,82 +633,6 @@ def convert_replying_to(match): return 'Replying to [comment:{} {}]'.format(comment_id, name) -RE_SAGE_TICKET1 = re.compile(r'https?://trac\.sagemath\.org/ticket/(\d+)#comment:(\d+)?') -RE_SAGE_TICKET2 = re.compile(r'https?://trac\.sagemath\.org/sage_trac/ticket/(\d+)') -RE_SAGE_WIKI1= re.compile(r'https?://trac\.sagemath\.org/wiki/([/\-\w0-9@:%._+~#=]+)') -RE_SAGE_ATTACHMENT1 = re.compile(r'https?://trac\.sagemath\.org/attachment/ticket/(\d+)/([/\-\w0-9@:%._+~#=]+)') -RE_SAGE_ATTACHMENT2 = re.compile(r'https?://trac\.sagemath\.org/sage_trac/attachment/ticket/(\d+)/([/\-\w0-9@:%._+~#=]+)') -RE_SAGE_ATTACHMENT3= re.compile(r'https?://trac\.sagemath\.org/sage_trac/raw-attachment/ticket/(\d+)/([/\-\w0-9@:%._+~#=]+)') -RE_SAGE_GIT_DIFF1 = re.compile(r'https?://git\.sagemath\.org/sage\.git/diff/([/\-\w0-9@:%._+~#=]+)\?id=([0-9a-f]+)') -RE_SAGE_GIT_DIFF2 = re.compile(r'https?://git\.sagemath\.org/sage\.git/diff/?\?h=([/\-\w0-9@:%._+~#=]+)&id2=([0-9a-f]+)') -RE_SAGE_GIT_DIFF3 = re.compile(r'https?://git\.sagemath\.org/sage\.git/diff/?\?id2?=([0-9a-f]+)&id=([0-9a-f]+)') -RE_SAGE_GIT_DIFF4 = re.compile(r'https?://git\.sagemath\.org/sage\.git/diff/?\?id=([0-9a-f]+)') -RE_SAGE_GIT_DIFF5 = re.compile(r'https?://git\.sagemath\.org/sage\.git/diff/?([/\-\w0-9@:%._+~#=]+)\?h=([/\-\w0-9@:%._+~#=]+)&id=([0-9a-f]+)') -RE_SAGE_GIT_DIFF6 = re.compile(r'https?://git\.sagemath\.org/sage\.git/diff/?\?id2=([/\-\w0-9@:%._+~#=]+)&id=([/\-\w0-9@:%._+~#=]+)') -RE_SAGE_GIT_DIFF7 = re.compile(r'https?://git\.sagemath\.org/sage\.git/diff/?\?h=([/\-\w0-9@:%._+~#=]+)') -RE_SAGE_GIT_DIFF8 = re.compile(r'https?://git\.sagemath\.org/sage\.git/diff/?\?id=([/\-\w0-9@:%._+~#=]+)') -RE_SAGE_GIT_COMMIT1 = re.compile(r'https?://git\.sagemath\.org/sage\.git/commit/?\?h=([/\-\w0-9@:%._+~#=]+)&id=([0-9a-f]+)') -RE_SAGE_GIT_COMMIT2 = re.compile(r'https?://git\.sagemath\.org/sage\.git/commitid=([0-9a-f]+)') -RE_SAGE_GIT_COMMIT3 = re.compile(r'https?://git\.sagemath\.org/sage\.git/commit/?\?id=([0-9a-f]+)') -RE_SAGE_GIT_COMMIT4 = re.compile(r'https?://git\.sagemath\.org/sage\.git/commit/?\?h=([/\-\w0-9@:%._+~#=]+)') -RE_SAGE_GIT_COMMIT5 = re.compile(r'https?://git\.sagemath\.org/sage\.git/commit/?\?id=([/\-\w0-9@:%._+~#=]+)') -RE_SAGE_GIT_COMMIT6 = re.compile(r'https?://git\.sagemath\.org/sage\.git/commit/([/\-\w0-9@:%._+~#=]+)\?h=([/\-\w0-9@:%._+~#=]+)&id=([0-9a-f]+)') -RE_SAGE_GIT_COMMIT7 = re.compile(r'https?://git\.sagemath\.org/sage\.git/commit/([/\-\w0-9@:%._+~#=]+)\?id=([0-9a-f]+)') -RE_SAGE_GIT_COMMIT8 = re.compile(r'https?://git\.sagemath\.org/sage\.git/commit/([/\-\w0-9@:%._+~#=]+)\?h=([0-9a-f]+)') -RE_SAGE_GIT_TREE1 = re.compile(r'https?://git\.sagemath\.org/sage\.git/tree/([/\-\w0-9@:%._+~#=]+)') -RE_SAGE_GIT_TREE2 = re.compile(r'https?://git\.sagemath\.org/sage\.git/tree/?\?h=([/\-\w0-9@:%._+~#=]+)') -RE_SAGE_GIT_TREE3 = re.compile(r'https?://git\.sagemath\.org/sage\.git/tree/src/?') -RE_SAGE_GIT_LOG1 = re.compile(r'https?://git\.sagemath\.org/sage\.git/log/?\?h=([/\-\w0-9@:%._+~#=]+)') -RE_SAGE_GIT_LOG2 = re.compile(r'https?://git\.sagemath\.org/sage\.git/log/?\?q=([0-9a-f]+)..([0-9a-f]+)&h=([0-9a-f]+)&qt=range') -RE_SAGE_GIT_LOG3 = re.compile(r'https?://git\.sagemath\.org/sage\.git/log/?([/\-\w0-9@:%._+~#=]+)\?h=([0-9a-f]+)') -RE_SAGE_GIT_LOG4 = re.compile(r'https?://git\.sagemath\.org/sage\.git/log/?([/\-\w0-9@:%._+~#=]+)') -RE_SAGE_GIT_PLAIN1 = re.compile(r'https?://git\.sagemath\.org/sage\.git/plain/([/\-\w0-9@:%._+~#=]+)\?h=([/\-\w0-9@:%._+~#=]+)') -RE_SAGE_GIT_PATCH1 = re.compile(r'https?://git\.sagemath\.org/sage\.git/patch/?\?id=([0-9a-f]+)') -RE_SAGE_GIT_REFS1 = re.compile(r'https?://git\.sagemath\.org/sage\.git/refs/?') -RE_SAGE_GIT_TAG1 = re.compile(r'https?://git\.sagemath\.org/sage\.git/tag/?\?id=([/\-\w0-9@:%._+~#=]+)') -RE_SAGE_GIT = re.compile(r'https?://git\.sagemath\.org/sage\.git/(.*)') -RE_SAGE_WRONG_FORMAT1 = re.compile(r'comment:(\d+):ticket:(\d+)') -RE_SAGE_REPLYING_TO = re.compile(r'Replying to \[comment:(\d+)\s([\-\w0-9@._]+)\]') - -def project_specific_normalization(text, conv_help): - text = RE_SAGE_TICKET1.sub(r'ticket:\1#comment:\2', text) - text = RE_SAGE_TICKET2.sub(r'%s/issues/\1' % target_url_issues_repo, text) - text = RE_SAGE_WIKI1.sub(convert_wiki_link, text) - text = RE_SAGE_ATTACHMENT1.sub(convert_ticket_attachment, text) - text = RE_SAGE_ATTACHMENT2.sub(convert_ticket_attachment, text) - text = RE_SAGE_ATTACHMENT3.sub(convert_ticket_attachment, text) - text = RE_SAGE_GIT_DIFF1.sub(convert_git_link_diff1, text) - text = RE_SAGE_GIT_DIFF2.sub(convert_git_link_diff2, text) - text = RE_SAGE_GIT_DIFF3.sub(convert_git_link_diff3, text) - text = RE_SAGE_GIT_DIFF4.sub(convert_git_link_diff4, text) - text = RE_SAGE_GIT_DIFF5.sub(convert_git_link_diff5, text) - text = RE_SAGE_GIT_DIFF6.sub(convert_git_link_diff6, text) - text = RE_SAGE_GIT_DIFF7.sub(convert_git_link_diff7, text) - text = RE_SAGE_GIT_DIFF8.sub(convert_git_link_diff8, text) - text = RE_SAGE_GIT_COMMIT1.sub(convert_git_link_commit1, text) - text = RE_SAGE_GIT_COMMIT2.sub(convert_git_link_commit3, text) # misspelled - text = RE_SAGE_GIT_COMMIT3.sub(convert_git_link_commit3, text) - text = RE_SAGE_GIT_COMMIT4.sub(convert_git_link_commit4, text) - text = RE_SAGE_GIT_COMMIT5.sub(convert_git_link_commit5, text) - text = RE_SAGE_GIT_COMMIT6.sub(convert_git_link_commit6, text) - text = RE_SAGE_GIT_COMMIT7.sub(convert_git_link_commit7, text) - text = RE_SAGE_GIT_COMMIT8.sub(convert_git_link_commit7, text) - text = RE_SAGE_GIT_TREE1.sub(convert_git_link_tree1, text) - text = RE_SAGE_GIT_TREE2.sub(convert_git_link_tree3, text) - text = RE_SAGE_GIT_TREE3.sub(r'%s/blob/master/src' % target_url_git_repo, text) - text = RE_SAGE_GIT_LOG1.sub(convert_git_link_log1, text) - text = RE_SAGE_GIT_LOG2.sub(convert_git_link_log3, text) - text = RE_SAGE_GIT_LOG3.sub(convert_git_link_log4, text) - text = RE_SAGE_GIT_LOG4.sub(convert_git_link_log5, text) - text = RE_SAGE_GIT_PLAIN1.sub(convert_git_link_plain, text) - text = RE_SAGE_GIT_PATCH1.sub(convert_git_link_patch, text) - text = RE_SAGE_GIT_REFS1.sub(r'%s/branches' % target_url_git_repo, text) - text = RE_SAGE_GIT_TAG1.sub(r'%s/releases/tag/\1' % target_url_git_repo, text) - text = RE_SAGE_GIT.sub(convert_git_link, text) # catch all missed - text = RE_SAGE_WRONG_FORMAT1.sub(r'ticket:\2#comment:\1', text) - text = RE_SAGE_REPLYING_TO.sub(convert_replying_to, text) - return text - def commits_list(match): t = '**' + match.group(1) +'**\n' t += '' @@ -540,9 +668,15 @@ def github_mention(match): return '`@`' + username def trac2markdown(text, base_path, conv_help, multilines=default_multilines): - text = project_specific_normalization(text, conv_help) + + # conversion of url + text = trac_url_conv_help.sub(text) + text = cgit_conv_help.sub(text) # some normalization + text = RE_WRONG_FORMAT1.sub(r'ticket:\2#comment:\1', text) + text = RE_REPLYING_TO.sub(convert_replying_to, text) + text = re.sub('\r\n', '\n', text) text = re.sub(r'\swiki:([a-zA-Z]+)', r' [wiki:\1]', text) @@ -628,7 +762,7 @@ def heading_replace(match): in_td_prefix = re.search('{{{', line).start() in_td_n = 0 in_td_defect = 0 - line = re.sub(r'{{{#!td', r'OPENING__PROCESSOR__TD', line) + line = re.sub(r'{{{#!td', r'%s' % proc_td.open, line) level += 1 elif line_temporary.startswith('{{{#!html') and not (in_code or in_html): in_html = True @@ -646,7 +780,7 @@ def heading_replace(match): in_code_defect = 0 if non_blank_previous_line: line = '\n' + line - line = re.sub(r'{{{#!([^\s]+)', r'OPENING__PROCESSOR__CODE\1', line) + line = re.sub(r'{{{#!([^\s]+)', r'%s\1' % proc_code.open, line) level += 1 elif line_temporary.rstrip() == '{{{' and not (in_code or in_html): # check dangling #!... @@ -671,11 +805,11 @@ def heading_replace(match): if line_temporary.rstrip() == '{{{': if non_blank_previous_line: line = '\n' + line - line = line.replace('{{{', 'OPENING__PROCESSOR__CODE', 1) + line = line.replace('{{{', proc_code.open, 1) else: if non_blank_previous_line: line = '\n' + line - line = line.replace('{{{', 'OPENING__PROCESSOR__CODE' +'\n' , 1) + line = line.replace('{{{', proc_code.open, +'\n' , 1) level += 1 elif line_temporary.rstrip() == '}}}': level -= 1 @@ -686,7 +820,7 @@ def heading_replace(match): for i in range(in_td_n): prev_line = a[-i-1] a[-i-1] = prev_line[:len(quote_prefix)] + in_td_defect*' ' + prev_line[len(quote_prefix):] - line = re.sub(r'}}}', r'CLOSING__PROCESSOR__TD', line) + line = re.sub(r'}}}', r'%s' % proc_td.close, line) elif in_html and in_html_level == level: in_html = False id_html_prefix = 0 @@ -702,7 +836,7 @@ def heading_replace(match): for i in range(in_code_n): prev_line = a[-i-1] a[-i-1] = prev_line[:len(quote_prefix)] + in_code_defect*' ' + prev_line[len(quote_prefix):] - line = re.sub(r'}}}', r'CLOSING__PROCESSOR__CODE', line) + line = re.sub(r'}}}', r'%s' % proc_code.close, line) else: # adjust badly indented codeblocks if in_td: @@ -771,14 +905,14 @@ def heading_replace(match): line = RE_SUBSCRIPT1.sub(r'\1', line) # subscript ,,abc,, line = RE_QUERY1.sub(r'[%s?' % trac_url_query, line) # preconversion to URL format - line = RE_HTTPS1.sub(r'OPENING__LEFT__BRACKET\2CLOSING__RIGHT__BRACKET(\1)', line) - line = RE_HTTPS2.sub(r'OPENING__LEFT__BRACKET\1CLOSING__RIGHT__BRACKET(\1)', line) # link without display text - line = RE_HTTPS3.sub(r'OPENING__LEFT__BRACKET\2CLOSING__RIGHT__BRACKET(\1)', line) - line = RE_HTTPS4.sub(r'OPENING__LEFT__BRACKET\1CLOSING__RIGHT__BRACKET(\1)', line) - - line = RE_IMAGE1.sub(r'!OPENING__LEFT__BRACKETCLOSING__RIGHT__BRACKET(%s/\1)' % os.path.relpath('/tree/master/'), line) - line = RE_IMAGE2.sub(r'!OPENING__LEFT__BRACKETCLOSING__RIGHT__BRACKET(\1)', line) - line = RE_IMAGE3.sub(r'!OPENING__LEFT__BRACKET\2CLOSING__RIGHT__BRACKET(\1)', line) + line = RE_HTTPS1.sub(conv_help.wiki_link, line) + line = RE_HTTPS2.sub(conv_help.wiki_link, line) # link without display text + line = RE_HTTPS3.sub(conv_help.wiki_link, line) + line = RE_HTTPS4.sub(conv_help.wiki_link, line) + + line = RE_IMAGE1.sub(conv_help.image_link_under_tree, line) + line = RE_IMAGE2.sub(conv_help.image_link, line) + line = RE_IMAGE3.sub(conv_help.image_link, line) line = RE_IMAGE4.sub(r'', line) line = RE_IMAGE5.sub(conv_help.wiki_image, line) # \2 is the image width @@ -800,8 +934,8 @@ def heading_replace(match): line = RE_TICKET1.sub(r' #\1', line) # replace global ticket references line = RE_TICKET2.sub(conv_help.ticket_link, line) - line = RE_COMMENT1.sub(r'OPENING__LEFT__BRACKET\2CLOSING__RIGHT__BRACKET(#comment%3A\1)', line) - line = RE_COMMENT2.sub(r'OPENING__LEFT__BRACKETcomment:\1CLOSING__RIGHT__BRACKET(#comment%3A\1)', line) + line = RE_COMMENT1.sub(conv_help.comment_link, line) + line = RE_COMMENT2.sub(conv_help.comment_link, line) line = RE_TICKET_COMMENT1.sub(conv_help.ticket_comment_link, line) line = RE_TICKET_COMMENT2.sub(conv_help.ticket_comment_link, line) @@ -941,7 +1075,7 @@ def heading_replace(match): table.append('|' + 'NEW__LINE'.join(block) +'|') block = [] continue - if line.startswith('OPENING__PROCESSOR__TD'): + if line.startswith(proc_td.open): if len(block) > 1: block.append('|') block.append(line) @@ -950,7 +1084,7 @@ def heading_replace(match): line = re.sub('\n', 'NEW__LINE', line) block.append(line) continue - if line.startswith('CLOSING__PROCESSOR__TD'): + if line.startswith(proc_td.close): block.append(line) continue if line.startswith('|'): @@ -968,10 +1102,9 @@ def heading_replace(match): if table: table_text = '\n'.join(table) - if 'OPENING__PROCESSOR__TD' in table_text: + if proc_td.open in table_text: html = markdown.markdown(table_text, extensions=[TableExtension(use_align_attribute=True)]) - html = html.replace('OPENING__PROCESSOR__TD', r'
') - html = html.replace('CLOSING__PROCESSOR__TD', r'
') + html = proc_td.replace(html) else: html = table_text line = html.replace('NEW__LINE', '\n') + '\n' + line @@ -986,11 +1119,9 @@ def heading_replace(match): text = '\n'.join(a) # remove artifacts - text = text.replace('OPENING__PROCESSOR__CODE', '```') - text = text.replace('CLOSING__PROCESSOR__CODE', '```') - text = text.replace('OPENING__LEFT__BRACKET', '[') - text = text.replace('CLOSING__RIGHT__BRACKET', ']') - text = text.replace('AT__SIGN__IN__CODE', '@') + text = proc_code.replace(text) + text = link_displ.replace(text) + text = at_sign.replace(text) # Some rewritings text = RE_COLOR.sub(r'$\\textcolor{\1}{\\text{\2}}$', text) @@ -1002,7 +1133,6 @@ def heading_replace(match): return text - class WikiConversionHelper: """ A class that provides conversion methods that depend on information collected @@ -1076,6 +1206,44 @@ def ticket_comment_link(self, match): return r'[%s](%s/%s#comment:%s)' % (label, trac_url_ticket, ticket, comment) return r'[%s](%s/issues/%s#comment:%s)' % (label, target_url_issues_repo, ticket, comment) + def comment_link(self, match): + """ + Return a formatted string that replaces the match object found by re + in the case of a comment link. + """ + mg = match.groups() + comment = mg[0] + s = '%3A' + if len(mg) > 1: + label = mg[1] + return r'%s%s%s(#comment%s%s)' % (link_displ.open, label, link_displ.close, s, comment) + else: + return r'%scomment:%s%s(#comment%s%s)' % (link_displ.open, comment, link_displ.close, s, comment) + + def image_link(self, match): + """ + Return a formatted string that replaces the match object found by re + in the case of a image link. + """ + mg = match.groups() + filename = mg[0] + if len(mg) > 1: + descr = mg[1] + return r'!%s%s%s(%s)' % (link_displ.open, descr, link_displ.close, filename) + else: + return r'!%s%s(%s)' % (link_displ.open, link_displ.close, filename) + + def image_link_under_tree(self, match): + """ + Return a formatted string that replaces the match object found by re + in the case of a image link under the tree path. + """ + mg = match.groups() + filename = mg[0] + path = os.path.relpath('/tree/master/') + return r'!%s%s(%s/\1)' % (link_displ.open, link_displ.close, filename, path) + + def wiki_image(self, match): """ Return a formatted string that replaces the match object found by re @@ -1088,6 +1256,15 @@ def wiki_image(self, match): else: return r'' % filename + def protect_wiki_link(self, display, link): + """ + Return the given string encapsuled with protection tags. These will + be replaced at the end of conversion of a line by the brackets (see + method `link_displ.replace`). This is needed to avoid a mixture + with Trac wiki syntax. + """ + return r'%s%s%s(%s)' % (link_displ.open, display, link_displ.close, link) + def wiki_link(self, match): """ Return a formatted string that replaces the match object found by re @@ -1112,13 +1289,13 @@ def wiki_link(self, match): if pagename.startswith('http'): link = pagename_ori.strip() - return r'OPENING__LEFT__BRACKET%sCLOSING__RIGHT__BRACKET(%s)' % (display, link) + return self.protect_wiki_link(display, link) elif pagename in self._pagenames_splitted: link = pagename_ori.replace(' ', '-') - return r'OPENING__LEFT__BRACKET%sCLOSING__RIGHT__BRACKET(%s)' % (display, link) + return self.protect_wiki_link(display, link) elif pagename in self._pagenames_not_splitted: link = pagename_ori.replace('/', ' ').replace(' ', '-') # convert to github link - return r'OPENING__LEFT__BRACKET%sCLOSING__RIGHT__BRACKET(%s)' % (display, link) + return self.protect_wiki_link(display, link) else: # we assume that this is a Trac macro like TicketQuery macro_split = pagename.split('(') @@ -1129,8 +1306,8 @@ def wiki_link(self, match): display = 'This is the Trac macro *%s* that was inherited from the migration' % macro link = '%s/WikiMacros#%s-macro' % (trac_url_wiki, macro) if args: - return r'OPENING__LEFT__BRACKET%s called with arguments (%s)CLOSING__RIGHT__BRACKET(%s)' % (display, args, link) - return r'OPENING__LEFT__BRACKET%sCLOSING__RIGHT__BRACKET(%s)' % (display, link) + return self.protect_wiki_link('%s called with arguments (%s)' % (display, args), link) + return self.protect_wiki_link(display, link) def camelcase_wiki_link(self, match): """ @@ -1195,21 +1372,21 @@ def wiki_link(self, match): if pagename.startswith('http'): link = pagename_ori.strip() - return r'OPENING__LEFT__BRACKET%sCLOSING__RIGHT__BRACKET(%s)' % (display, link) + return self.protect_wiki_link(display, link) elif pagename in self._pagenames_splitted: link = pagename_ori.replace(' ', '') if link in wiki_path_conversion_table: link = wiki_path_conversion_table[link] else: link = pagename_ori.replace(' ', '-') - return r'OPENING__LEFT__BRACKET%sCLOSING__RIGHT__BRACKET(%s)' % (display, '../wiki/' + link) + return self.protect_wiki_link(display, '../wiki/' + link) elif pagename in self._pagenames_not_splitted: link = pagename_ori.replace(' ', '') if link in wiki_path_conversion_table: link = wiki_path_conversion_table[link] else: link = pagename_ori.replace(' ', '-') - return r'OPENING__LEFT__BRACKET%sCLOSING__RIGHT__BRACKET(%s)' % (display, '../wiki/' + link) + return self.protect_wiki_link(display, '../wiki/' + link) else: # we assume that this is a Trac macro like TicketQuery macro_split = pagename.split('(') @@ -1220,8 +1397,8 @@ def wiki_link(self, match): display = 'This is the Trac macro *%s* that was inherited from the migration' % macro link = '%s/WikiMacros#%s-macro' % (trac_url_wiki, macro) if args: - return r'OPENING__LEFT__BRACKET%s called with arguments (%s)CLOSING__RIGHT__BRACKET(%s)' % (display, args, link) - return r'OPENING__LEFT__BRACKET%sCLOSING__RIGHT__BRACKET(%s)' % (display, link) + return self.protect_wiki_link('%s called with arguments (%s)' % (display, args), link) + return self.protect_wiki_link(display, link) def github_ref_url(ref): @@ -1272,22 +1449,9 @@ def mapcomponent(component): if component == 'PLEASE CHANGE': return None component = component.replace('_', ' ').lower() - if component in ['solaris', 'cygwin']: - component = 'porting: ' + component - elif component == 'freebsd': - component = 'porting: bsd' - elif component == 'aix or hp-ux ports': - component = 'porting: aix or hp-ux' - elif component == 'experimental package': - component = 'packages: experimental' - elif component == 'optional packages': - component = 'packages: optional' - elif component == 'plotting': - component = 'graphics' - elif component == 'doctest': - component = 'doctest coverage' - elif component == 'sage-check': - component = 'spkg-check' + + if component in components_to_labels.keys(): + component = components_to_labels[component] component_frequency[component] += 1 # Prefix it with "component: " so that they show up as one group in the GitHub dropdown list return f'component: {component}' @@ -1355,40 +1519,15 @@ def mapmilestone(title): if not title: return None, None title = title.lower() - if title in ['sage-duplicate/invalid/wontfix', 'sage-duplicate/invalid', 'sage-duplicate']: - return None, 'duplicate/invalid/wontfix' - if title == 'sage-wait': - title = 'sage-pending' - if title in ['sage-feature', 'sage-pending', 'sage-wishlist']: - return None, title[5:] - if title == 'sage-combinat': - return None, mapcomponent('combinatorics') - if title == 'sage-symbolics': - return None, mapcomponent('symbolics') - if title == 'sage-i18n': - return None, mapcomponent('translations') + if title in milestones_to_labels.keys(): + return None, milestones_to_labels[title] + # some normalization if re.match('^[0-9]', title): - title = 'sage-' + title - if re.fullmatch('sage-[1-9]', title): + title = milestone_prefix_to + title + if re.fullmatch('%s[1-9]' % milestone_prefix_from, title): title = title + '.0' - # Remap milestones for releases that were canceled/renamed - if title == 'sage-2.8.4.3': - title = 'sage-2.8.5' - elif title == 'sage-3.2.4': - title = 'sage-3.3' - elif title == 'sage-4.0.3': - title = 'sage-4.1' - elif title == 'sage-4.1.3': - title = 'sage-4.2' - elif title == 'sage-4.4.5': - title = 'sage-4.5' - elif title == 'sage-4.7.3': - title = 'sage-4.8' - elif title == 'sage-6.11': - title = 'sage-7.0' - elif title == 'sage-7.7': - title = 'sage-8.0' - + if title in canceled_milestones.keys(): + title = canceled_milestones[title] return title, None def gh_create_milestone(dest, milestone_data) : @@ -1674,7 +1813,7 @@ def gh_user_url(dest, username): # heuristic pattern for valid Trac account name (not an email address or junk) # Use this URL as the id (this is current best guess what a mannequin user would look like) username = username.replace('.', '-').replace('_', '-').strip('-') - username = f'sagetrac-{username}' + username = f'{unknown_users_prefix}{username}' else: return None try: