1 |
|
# Redmine - project management software
|
|
1 |
# redMine - project management software
|
2 |
2 |
# Copyright (C) 2006-2007 Jean-Philippe Lang
|
3 |
|
# Copyright (C) 2007-2011 Trac/Redmine Community
|
4 |
|
# References:
|
5 |
|
# - http://www.redmine.org/boards/1/topics/12273 (Trac Importer Patch Coordination)
|
6 |
|
# - http://github.com/landy2005/Redmine-migrate-from-Trac
|
7 |
3 |
#
|
8 |
4 |
# This program is free software; you can redistribute it and/or
|
9 |
5 |
# modify it under the terms of the GNU General Public License
|
... | ... | |
56 |
52 |
'blocker' => priorities[4]
|
57 |
53 |
}
|
58 |
54 |
|
59 |
|
TRACKER_BUG = Tracker.find_by_name('Bug')
|
60 |
|
TRACKER_FEATURE = Tracker.find_by_name('Feature')
|
61 |
|
TRACKER_SUPPORT = Tracker.find_by_name('Support')
|
|
55 |
TRACKER_BUG = Tracker.find_by_position(1)
|
|
56 |
TRACKER_FEATURE = Tracker.find_by_position(2)
|
|
57 |
# Add a fourth issue type for tasks as we use them heavily
|
|
58 |
t = Tracker.find_by_name('Task')
|
|
59 |
if !t
|
|
60 |
t = Tracker.create(:name => 'Task', :is_in_chlog => true, :is_in_roadmap => false, :position => 4)
|
|
61 |
t.workflows.copy(Tracker.find(1))
|
|
62 |
end
|
|
63 |
TRACKER_TASK = t
|
62 |
64 |
DEFAULT_TRACKER = TRACKER_BUG
|
63 |
65 |
TRACKER_MAPPING = {'defect' => TRACKER_BUG,
|
64 |
66 |
'enhancement' => TRACKER_FEATURE,
|
65 |
|
'task' => TRACKER_SUPPORT,
|
|
67 |
'task' => TRACKER_TASK,
|
66 |
68 |
'patch' =>TRACKER_FEATURE
|
67 |
69 |
}
|
68 |
70 |
|
... | ... | |
248 |
250 |
set_table_name :session_attribute
|
249 |
251 |
end
|
250 |
252 |
|
251 |
|
# TODO put your Login Mapping in this method and rename method below
|
252 |
|
# def self.find_or_create_user(username, project_member = false)
|
253 |
|
# TRAC_REDMINE_LOGIN_MAP = []
|
254 |
|
# return TRAC_REDMINE_LOGIN_MAP[username]
|
255 |
|
# OR more hard-coded:
|
256 |
|
# if username == 'TracX'
|
257 |
|
# username = 'RedmineX'
|
258 |
|
# elsif username == 'gilles'
|
259 |
|
# username = 'gcornu'
|
260 |
|
# #elseif ...
|
261 |
|
# else
|
262 |
|
# username = 'gcornu'
|
263 |
|
# end
|
264 |
|
# return User.find_by_login(username)
|
265 |
|
# end
|
266 |
|
|
267 |
253 |
def self.find_or_create_user(username, project_member = false)
|
268 |
254 |
return User.anonymous if username.blank?
|
269 |
255 |
|
... | ... | |
370 |
356 |
:name => encode(milestone.name[0, limit_for(Version, 'name')]),
|
371 |
357 |
:description => nil,
|
372 |
358 |
:wiki_page_title => milestone.name.to_s,
|
373 |
|
:effective_date => (!milestone.completed.blank? ? milestone.completed : (!milestone.due.blank? ? milestone.due : nil))
|
|
359 |
:effective_date => milestone.completed
|
374 |
360 |
|
375 |
361 |
next unless v.save
|
376 |
362 |
version_map[milestone.name] = v
|
... | ... | |
385 |
371 |
#print "Migrating custom fields"
|
386 |
372 |
custom_field_map = {}
|
387 |
373 |
TracTicketCustom.find_by_sql("SELECT DISTINCT name FROM #{TracTicketCustom.table_name}").each do |field|
|
388 |
|
# use line below and adapt the WHERE condifiton, if you want to skip some unused custom fields
|
389 |
|
# TracTicketCustom.find_by_sql("SELECT DISTINCT name FROM #{TracTicketCustom.table_name} WHERE name NOT IN ('duration', 'software')").each do |field|
|
390 |
374 |
#print '.' # Maybe not needed this out?
|
391 |
375 |
#STDOUT.flush
|
392 |
376 |
# Redmine custom field name
|
393 |
377 |
field_name = encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize
|
394 |
|
|
395 |
|
# # Ugly hack to skip custom field 'Browser', which is in 'list' format...
|
396 |
|
# next if field_name == 'browser'
|
397 |
|
|
398 |
378 |
# Find if the custom already exists in Redmine
|
399 |
379 |
f = IssueCustomField.find_by_name(field_name)
|
400 |
380 |
# Ugly hack to handle billable checkbox. Would require to read the ini file to be cleaner
|
... | ... | |
414 |
394 |
end
|
415 |
395 |
#puts
|
416 |
396 |
|
417 |
|
# # Trac custom field 'Browser' field as a Redmine custom field
|
418 |
|
# b = IssueCustomField.find(:first, :conditions => { :name => "Browser" })
|
419 |
|
# b = IssueCustomField.new(:name => 'Browser',
|
420 |
|
# :field_format => 'list',
|
421 |
|
# :is_filter => true) if b.nil?
|
422 |
|
# b.trackers << [TRACKER_BUG, TRACKER_FEATURE, TRACKER_SUPPORT]
|
423 |
|
# b.projects << @target_project
|
424 |
|
# b.possible_values = (b.possible_values + %w(IE6 IE7 IE8 IE9 Firefox Chrome Safari Opera)).flatten.compact.uniq
|
425 |
|
# b.save!
|
426 |
|
# custom_field_map['browser'] = b
|
427 |
|
|
428 |
397 |
# Trac 'resolution' field as a Redmine custom field
|
429 |
398 |
r = IssueCustomField.find(:first, :conditions => { :name => "Resolution" })
|
430 |
399 |
r = IssueCustomField.new(:name => 'Resolution',
|
431 |
400 |
:field_format => 'list',
|
432 |
401 |
:is_filter => true) if r.nil?
|
433 |
|
r.trackers << [TRACKER_BUG, TRACKER_FEATURE, TRACKER_SUPPORT]
|
|
402 |
r.trackers = Tracker.find(:all)
|
434 |
403 |
r.projects << @target_project
|
435 |
404 |
r.possible_values = (r.possible_values + %w(fixed invalid wontfix duplicate worksforme)).flatten.compact.uniq
|
436 |
405 |
r.save!
|
... | ... | |
446 |
415 |
k.save!
|
447 |
416 |
custom_field_map['keywords'] = k
|
448 |
417 |
|
449 |
|
# Trac 'version' field as a Redmine custom field, taking advantage of feature #2096 (available since Redmine 1.2.0)
|
450 |
|
v = IssueCustomField.find(:first, :conditions => { :name => "Found in Version" })
|
451 |
|
v = IssueCustomField.new(:name => 'Found in Version',
|
452 |
|
:field_format => 'version',
|
453 |
|
:is_filter => true) if v.nil?
|
454 |
|
# Only apply to BUG tracker (?)
|
455 |
|
v.trackers << TRACKER_BUG
|
456 |
|
#v.trackers << [TRACKER_BUG, TRACKER_FEATURE]
|
457 |
|
|
458 |
|
# Affect custom field to current Project
|
459 |
|
v.projects << @target_project
|
460 |
|
|
461 |
|
v.save!
|
462 |
|
custom_field_map['found_in_version'] = v
|
463 |
|
|
464 |
418 |
# Trac ticket id as a Redmine custom field
|
465 |
419 |
tid = IssueCustomField.find(:first, :conditions => { :name => "TracID" })
|
466 |
420 |
tid = IssueCustomField.new(:name => 'TracID',
|
467 |
421 |
:field_format => 'string',
|
468 |
422 |
:is_filter => true) if tid.nil?
|
469 |
|
tid.trackers << [TRACKER_BUG, TRACKER_FEATURE, TRACKER_SUPPORT]
|
|
423 |
tid.trackers = Tracker.find(:all)
|
470 |
424 |
tid.projects << @target_project
|
471 |
425 |
tid.save!
|
472 |
426 |
custom_field_map['tracid'] = tid
|
... | ... | |
488 |
442 |
i.fixed_version = version_map[ticket.milestone] unless ticket.milestone.blank?
|
489 |
443 |
i.status = STATUS_MAPPING[ticket.status] || DEFAULT_STATUS
|
490 |
444 |
i.tracker = TRACKER_MAPPING[ticket.ticket_type] || DEFAULT_TRACKER
|
491 |
|
# Use the Redmine-genereated new ticket ID anyway (no Ticket ID recycling)
|
492 |
|
#i.id = ticket.id unless Issue.exists?(ticket.id)
|
|
445 |
i.id = ticket.id unless Issue.exists?(ticket.id)
|
493 |
446 |
next unless Time.fake(ticket.changetime) { i.save }
|
494 |
447 |
TICKET_MAP[ticket.id] = i.id
|
495 |
448 |
migrated_tickets += 1
|
... | ... | |
500 |
453 |
Time.fake(ticket.changetime) { i.save }
|
501 |
454 |
end
|
502 |
455 |
# Handle CC field
|
503 |
|
# Feature disabled (CC field almost never used, No time to validate/test this recent improvments from A. Callegaro)
|
504 |
|
# ticket.cc.split(',').each do |email|
|
505 |
|
# w = Watcher.new :watchable_type => 'Issue',
|
506 |
|
# :watchable_id => i.id,
|
507 |
|
# :user_id => find_or_create_user(email.strip).id
|
508 |
|
# w.save
|
509 |
|
# end
|
|
456 |
ticket.cc.split(',').each do |email|
|
|
457 |
w = Watcher.new :watchable_type => 'Issue',
|
|
458 |
:watchable_id => i.id,
|
|
459 |
:user_id => find_or_create_user(email.strip).id
|
|
460 |
w.save
|
|
461 |
end
|
510 |
462 |
|
511 |
463 |
# Necessary to handle direct link to note from timelogs and putting the right start time in issue
|
512 |
464 |
noteid = 1
|
... | ... | |
659 |
611 |
if custom_field_map['tracid']
|
660 |
612 |
custom_values[custom_field_map['tracid'].id] = ticket.id
|
661 |
613 |
end
|
662 |
|
|
663 |
|
if !ticket.version.blank? && custom_field_map['found_in_version']
|
664 |
|
found_in = version_map[ticket.version]
|
665 |
|
if !found_in.nil?
|
666 |
|
puts "Issue #{i.id} found in #{found_in.name.to_s} (#{found_in.id.to_s}) - trac: #{ticket.version}"
|
667 |
|
else
|
668 |
|
#TODO: add better error management here...
|
669 |
|
puts "Issue #{i.id} : ouch... - trac: #{ticket.version}"
|
670 |
|
end
|
671 |
|
custom_values[custom_field_map['found_in_version'].id] = found_in.id.to_s
|
672 |
|
STDOUT.flush
|
673 |
|
end
|
674 |
|
|
675 |
614 |
i.custom_field_values = custom_values
|
676 |
615 |
i.save_custom_field_values
|
677 |
616 |
end
|
... | ... | |
738 |
677 |
puts if wiki_pages_count < wiki_pages_total
|
739 |
678 |
|
740 |
679 |
who = " in Issues"
|
741 |
|
#issues_total = TICKET_MAP.length #works with Ruby <= 1.8.6
|
742 |
|
issues_total = TICKET_MAP.count #works with Ruby >= 1.8.7
|
|
680 |
issues_total = TICKET_MAP.count
|
743 |
681 |
TICKET_MAP.each do |newId|
|
744 |
682 |
issues_count += 1
|
745 |
683 |
simplebar(who, issues_count, issues_total)
|
... | ... | |
759 |
697 |
puts if issues_count < issues_total
|
760 |
698 |
|
761 |
699 |
who = " in Milestone descriptions"
|
762 |
|
#milestone_wiki_total = milestone_wiki.length #works with Ruby <= 1.8.6
|
763 |
|
milestone_wiki_total = milestone_wiki.count #works with Ruby >= 1.8.7
|
|
700 |
milestone_wiki_total = milestone_wiki.count
|
764 |
701 |
milestone_wiki.each do |name|
|
765 |
702 |
milestone_wiki_count += 1
|
766 |
703 |
simplebar(who, milestone_wiki_count, milestone_wiki_total)
|
... | ... | |
865 |
802 |
project.identifier = identifier
|
866 |
803 |
puts "Unable to create a project with identifier '#{identifier}'!" unless project.save
|
867 |
804 |
# enable issues and wiki for the created project
|
868 |
|
# Enable only a minimal set of modules by default
|
869 |
|
project.enabled_module_names = ['issue_tracking', 'wiki']
|
|
805 |
# Enable all project modules by default
|
|
806 |
project.enabled_module_names = ['issue_tracking', 'wiki', 'time_tracking', 'news', 'documents', 'files', 'repository', 'boards', 'calendar', 'gantt']
|
870 |
807 |
else
|
871 |
808 |
puts
|
872 |
809 |
puts "This project already exists in your Redmine database."
|
... | ... | |
876 |
813 |
end
|
877 |
814 |
project.trackers << TRACKER_BUG unless project.trackers.include?(TRACKER_BUG)
|
878 |
815 |
project.trackers << TRACKER_FEATURE unless project.trackers.include?(TRACKER_FEATURE)
|
879 |
|
project.trackers << TRACKER_SUPPORT unless project.trackers.include?(TRACKER_SUPPORT)
|
|
816 |
# Add Task type to the project
|
|
817 |
project.trackers << TRACKER_TASK unless project.trackers.include?(TRACKER_TASK)
|
880 |
818 |
@target_project = project.new_record? ? nil : project
|
881 |
819 |
@target_project.reload
|
882 |
820 |
end
|
... | ... | |
952 |
890 |
|
953 |
891 |
|
954 |
892 |
desc 'Subversion migration script'
|
955 |
|
task :migrate_svn_commit_properties => :environment do
|
|
893 |
task :migrate_from_trac_svn => :environment do
|
956 |
894 |
|
957 |
895 |
require 'redmine/scm/adapters/abstract_adapter'
|
958 |
896 |
require 'redmine/scm/adapters/subversion_adapter'
|
... | ... | |
964 |
902 |
TICKET_MAP = []
|
965 |
903 |
|
966 |
904 |
class Commit
|
967 |
|
attr_accessor :revision, :message, :author
|
|
905 |
attr_accessor :revision, :message
|
968 |
906 |
|
969 |
907 |
def initialize(attributes={})
|
970 |
|
self.author = attributes[:author] || ""
|
971 |
908 |
self.message = attributes[:message] || ""
|
972 |
909 |
self.revision = attributes[:revision]
|
973 |
910 |
end
|
974 |
911 |
end
|
975 |
912 |
|
976 |
913 |
class SvnExtendedAdapter < Redmine::Scm::Adapters::SubversionAdapter
|
977 |
|
|
978 |
|
def set_author(path=nil, revision=nil, author=nil)
|
979 |
|
path ||= ''
|
980 |
|
|
981 |
|
cmd = "#{SVN_BIN} propset svn:author --quiet --revprop -r #{revision} \"#{author}\" "
|
982 |
|
cmd << credentials_string
|
983 |
|
cmd << ' ' + target(URI.escape(path))
|
984 |
|
|
985 |
|
shellout(cmd) do |io|
|
986 |
|
begin
|
987 |
|
loop do
|
988 |
|
line = io.readline
|
989 |
|
puts line
|
990 |
|
end
|
991 |
|
rescue EOFError
|
992 |
|
end
|
993 |
|
end
|
994 |
|
|
995 |
|
raise if $? && $?.exitstatus != 0
|
996 |
|
|
997 |
|
end
|
998 |
914 |
|
999 |
915 |
def set_message(path=nil, revision=nil, msg=nil)
|
1000 |
916 |
path ||= ''
|
... | ... | |
1044 |
960 |
commits << Commit.new(
|
1045 |
961 |
{
|
1046 |
962 |
:revision => logentry.attributes['revision'].to_i,
|
1047 |
|
:message => logentry.elements['msg'].text,
|
1048 |
|
:author => logentry.elements['author'].text
|
|
963 |
:message => logentry.elements['msg'].text
|
1049 |
964 |
})
|
1050 |
965 |
end
|
1051 |
966 |
rescue => e
|
... | ... | |
1059 |
974 |
|
1060 |
975 |
end
|
1061 |
976 |
|
1062 |
|
def self.migrate_authors
|
1063 |
|
svn = self.scm
|
1064 |
|
commits = svn.messages(@svn_url)
|
1065 |
|
commits.each do |commit|
|
1066 |
|
orig_author_name = commit.author
|
1067 |
|
new_author_name = orig_author_name
|
1068 |
|
|
1069 |
|
# TODO put your Trac/SVN/Redmine username mapping here:
|
1070 |
|
if (commit.author == 'TracX')
|
1071 |
|
new_author_name = 'RedmineY'
|
1072 |
|
elsif (commit.author == 'gilles')
|
1073 |
|
new_author_name = 'gcornu'
|
1074 |
|
#elsif (commit.author == 'seco')
|
1075 |
|
#...
|
1076 |
|
else
|
1077 |
|
new_author_name = 'RedmineY'
|
1078 |
|
end
|
1079 |
|
|
1080 |
|
if (new_author_name != orig_author_name)
|
1081 |
|
scm.set_author(@svn_url, commit.revision, new_author_name)
|
1082 |
|
puts "r#{commit.revision} - Author replaced: #{orig_author_name} -> #{new_author_name}"
|
1083 |
|
else
|
1084 |
|
puts "r#{commit.revision} - Author kept: #{orig_author_name} unchanged "
|
1085 |
|
end
|
1086 |
|
end
|
1087 |
|
end
|
1088 |
|
|
1089 |
|
def self.migrate_messages
|
|
977 |
def self.migrate
|
1090 |
978 |
|
1091 |
979 |
project = Project.find(@@redmine_project)
|
1092 |
980 |
if !project
|
... | ... | |
1120 |
1008 |
|
1121 |
1009 |
if newText != commit.message
|
1122 |
1010 |
puts "Updating message #{commit.revision}"
|
1123 |
|
|
1124 |
|
# Marcel Nadje enhancement, see http://www.redmine.org/issues/2748#note-3
|
1125 |
|
# Hint: enable charset conversion if needed...
|
1126 |
|
#newText = Iconv.conv('CP1252', 'UTF-8', newText)
|
1127 |
|
|
1128 |
1011 |
scm.set_message(@svn_url, commit.revision, newText)
|
1129 |
1012 |
end
|
1130 |
1013 |
end
|
... | ... | |
1154 |
1037 |
end
|
1155 |
1038 |
|
1156 |
1039 |
def self.scm
|
1157 |
|
# Thomas Recloux fix, see http://www.redmine.org/issues/2748#note-1
|
1158 |
|
# The constructor of the SvnExtendedAdapter has ony got four parameters,
|
1159 |
|
# => parameters 5,6 and 7 removed
|
|
1040 |
# Bugfix, with redmine 1.0.1 (Debian's) it wasn't working anymore
|
1160 |
1041 |
@scm ||= SvnExtendedAdapter.new @@svn_url, @@svn_url, @@svn_username, @@svn_password
|
1161 |
|
#@scm ||= SvnExtendedAdapter.new @@svn_url, @@svn_url, @@svn_username, @@svn_password, 0, "", nil
|
1162 |
1042 |
@scm
|
1163 |
1043 |
end
|
1164 |
1044 |
end
|
1165 |
1045 |
|
1166 |
1046 |
puts
|
|
1047 |
if Redmine::DefaultData::Loader.no_data?
|
|
1048 |
puts "Redmine configuration need to be loaded before importing data."
|
|
1049 |
puts "Please, run this first:"
|
|
1050 |
puts
|
|
1051 |
puts " rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\""
|
|
1052 |
exit
|
|
1053 |
end
|
|
1054 |
|
|
1055 |
puts "WARNING: all commit messages with references to trac pages will be modified"
|
|
1056 |
print "Are you sure you want to continue ? [y/N] "
|
|
1057 |
break unless STDIN.gets.match(/^y$/i)
|
|
1058 |
puts
|
|
1059 |
|
1167 |
1060 |
prompt('Subversion repository url') {|repository| SvnMigrate.set_svn_url repository.strip}
|
1168 |
1061 |
prompt('Subversion repository username') {|username| SvnMigrate.set_svn_username username}
|
1169 |
1062 |
prompt('Subversion repository password') {|password| SvnMigrate.set_svn_password password}
|
|
1063 |
prompt('Redmine project identifier') {|identifier| SvnMigrate.set_redmine_project_identifier identifier}
|
1170 |
1064 |
puts
|
1171 |
|
|
1172 |
|
author_migration_enabled = unsafe_prompt('1) Start Migration of SVN Commit Authors (y,n)?', {:default => 'n'}) == 'y'
|
1173 |
|
puts
|
1174 |
|
if author_migration_enabled
|
1175 |
|
puts "WARNING: Some (maybe all) commit authors will be replaced"
|
1176 |
|
print "Are you sure you want to continue ? [y/N] "
|
1177 |
|
break unless STDIN.gets.match(/^y$/i)
|
1178 |
|
|
1179 |
|
SvnMigrate.migrate_authors
|
1180 |
|
end
|
1181 |
1065 |
|
1182 |
|
message_migration_enabled = unsafe_prompt('2) Start Migration of SVN Commit Messages (y,n)?', {:default => 'n'}) == 'y'
|
1183 |
|
puts
|
1184 |
|
if message_migration_enabled
|
1185 |
|
if Redmine::DefaultData::Loader.no_data?
|
1186 |
|
puts "Redmine configuration need to be loaded before importing data."
|
1187 |
|
puts "Please, run this first:"
|
1188 |
|
puts
|
1189 |
|
puts " rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\""
|
1190 |
|
exit
|
1191 |
|
end
|
1192 |
|
|
1193 |
|
puts "WARNING: all commit messages with references to trac pages will be modified"
|
1194 |
|
print "Are you sure you want to continue ? [y/N] "
|
1195 |
|
break unless STDIN.gets.match(/^y$/i)
|
1196 |
|
puts
|
1197 |
|
|
1198 |
|
prompt('Redmine project identifier') {|identifier| SvnMigrate.set_redmine_project_identifier identifier}
|
1199 |
|
puts
|
1200 |
|
|
1201 |
|
SvnMigrate.migrate_messages
|
1202 |
|
end
|
|
1066 |
SvnMigrate.migrate
|
|
1067 |
|
1203 |
1068 |
end
|
1204 |
1069 |
|
1205 |
1070 |
# Prompt
|
... | ... | |
1214 |
1079 |
end
|
1215 |
1080 |
end
|
1216 |
1081 |
|
1217 |
|
# Sorry, I had troubles to intagrate 'prompt' and quickly went this way...
|
1218 |
|
def unsafe_prompt(text, options = {})
|
1219 |
|
default = options[:default] || ''
|
1220 |
|
print "#{text} [#{default}]: "
|
1221 |
|
value = STDIN.gets.chomp!
|
1222 |
|
value = default if value.blank?
|
1223 |
|
value
|
1224 |
|
end
|
1225 |
|
|
1226 |
1082 |
# Basic wiki syntax conversion
|
1227 |
1083 |
def convert_wiki_text_mapping(text, ticket_map = [])
|
1228 |
1084 |
# Hide links
|
... | ... | |
1385 |
1241 |
text = text.gsub(/''/, '_')
|
1386 |
1242 |
text = text.gsub(/__/, '+')
|
1387 |
1243 |
text = text.gsub(/~~/, '-')
|
1388 |
|
text = text.gsub(/`/, '@')
|
1389 |
1244 |
text = text.gsub(/,,/, '~')
|
1390 |
1245 |
# Tables
|
1391 |
1246 |
text = text.gsub(/\|\|/, '|')
|
... | ... | |
1403 |
1258 |
# TOC (is right-aligned, because that in Trac)
|
1404 |
1259 |
text = text.gsub(/\[\[TOC(?:\((.*?)\))?\]\]/m) {|s| "{{>toc}}\n"}
|
1405 |
1260 |
|
1406 |
|
# Thomas Recloux enhancements, see http://www.redmine.org/issues/2748#note-1
|
1407 |
|
# Redmine needs a space between keywords "refs,ref,fix" and the issue number (#1234) in subversion commit messages.
|
1408 |
|
# TODO: rewrite it in a more regex-style way
|
1409 |
|
|
1410 |
|
text = text.gsub("refs#", "refs #")
|
1411 |
|
text = text.gsub("Refs#", "refs #")
|
1412 |
|
text = text.gsub("REFS#", "refs #")
|
1413 |
|
text = text.gsub("ref#", "refs #")
|
1414 |
|
text = text.gsub("Ref#", "refs #")
|
1415 |
|
text = text.gsub("REF#", "refs #")
|
1416 |
|
|
1417 |
|
text = text.gsub("fix#", "fixes #")
|
1418 |
|
text = text.gsub("Fix#", "fixes #")
|
1419 |
|
text = text.gsub("FIX#", "fixes #")
|
1420 |
|
text = text.gsub("fixes#", "fixes #")
|
1421 |
|
text = text.gsub("Fixes#", "fixes #")
|
1422 |
|
text = text.gsub("FIXES#", "fixes #")
|
1423 |
|
|
1424 |
1261 |
# Restore and convert code blocks
|
1425 |
1262 |
text = code_convert(text)
|
1426 |
1263 |
|