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
|
3 |
7 |
#
|
4 |
8 |
# This program is free software; you can redistribute it and/or
|
5 |
9 |
# modify it under the terms of the GNU General Public License
|
... | ... | |
52 |
56 |
'blocker' => priorities[4]
|
53 |
57 |
}
|
54 |
58 |
|
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
|
|
59 |
TRACKER_BUG = Tracker.find_by_name('Bug')
|
|
60 |
TRACKER_FEATURE = Tracker.find_by_name('Feature')
|
|
61 |
TRACKER_SUPPORT = Tracker.find_by_name('Support')
|
64 |
62 |
DEFAULT_TRACKER = TRACKER_BUG
|
65 |
63 |
TRACKER_MAPPING = {'defect' => TRACKER_BUG,
|
66 |
64 |
'enhancement' => TRACKER_FEATURE,
|
67 |
|
'task' => TRACKER_TASK,
|
|
65 |
'task' => TRACKER_SUPPORT,
|
68 |
66 |
'patch' =>TRACKER_FEATURE
|
69 |
67 |
}
|
70 |
68 |
|
... | ... | |
250 |
248 |
set_table_name :session_attribute
|
251 |
249 |
end
|
252 |
250 |
|
|
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 |
|
253 |
267 |
def self.find_or_create_user(username, project_member = false)
|
254 |
268 |
return User.anonymous if username.blank?
|
255 |
269 |
|
... | ... | |
356 |
370 |
:name => encode(milestone.name[0, limit_for(Version, 'name')]),
|
357 |
371 |
:description => nil,
|
358 |
372 |
:wiki_page_title => milestone.name.to_s,
|
359 |
|
:effective_date => milestone.completed
|
|
373 |
:effective_date => (!milestone.completed.blank? ? milestone.completed : (!milestone.due.blank? ? milestone.due : nil))
|
360 |
374 |
|
361 |
375 |
next unless v.save
|
362 |
376 |
version_map[milestone.name] = v
|
... | ... | |
371 |
385 |
#print "Migrating custom fields"
|
372 |
386 |
custom_field_map = {}
|
373 |
387 |
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|
|
374 |
390 |
#print '.' # Maybe not needed this out?
|
375 |
391 |
#STDOUT.flush
|
376 |
392 |
# Redmine custom field name
|
377 |
393 |
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 |
|
378 |
398 |
# Find if the custom already exists in Redmine
|
379 |
399 |
f = IssueCustomField.find_by_name(field_name)
|
380 |
400 |
# Ugly hack to handle billable checkbox. Would require to read the ini file to be cleaner
|
... | ... | |
394 |
414 |
end
|
395 |
415 |
#puts
|
396 |
416 |
|
|
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 |
|
397 |
428 |
# Trac 'resolution' field as a Redmine custom field
|
398 |
429 |
r = IssueCustomField.find(:first, :conditions => { :name => "Resolution" })
|
399 |
430 |
r = IssueCustomField.new(:name => 'Resolution',
|
400 |
431 |
:field_format => 'list',
|
401 |
432 |
:is_filter => true) if r.nil?
|
402 |
|
r.trackers = Tracker.find(:all)
|
|
433 |
r.trackers << [TRACKER_BUG, TRACKER_FEATURE, TRACKER_SUPPORT]
|
403 |
434 |
r.projects << @target_project
|
404 |
435 |
r.possible_values = (r.possible_values + %w(fixed invalid wontfix duplicate worksforme)).flatten.compact.uniq
|
405 |
436 |
r.save!
|
... | ... | |
415 |
446 |
k.save!
|
416 |
447 |
custom_field_map['keywords'] = k
|
417 |
448 |
|
|
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 |
|
418 |
464 |
# Trac ticket id as a Redmine custom field
|
419 |
465 |
tid = IssueCustomField.find(:first, :conditions => { :name => "TracID" })
|
420 |
466 |
tid = IssueCustomField.new(:name => 'TracID',
|
421 |
467 |
:field_format => 'string',
|
422 |
468 |
:is_filter => true) if tid.nil?
|
423 |
|
tid.trackers = Tracker.find(:all)
|
|
469 |
tid.trackers << [TRACKER_BUG, TRACKER_FEATURE, TRACKER_SUPPORT]
|
424 |
470 |
tid.projects << @target_project
|
425 |
471 |
tid.save!
|
426 |
472 |
custom_field_map['tracid'] = tid
|
... | ... | |
442 |
488 |
i.fixed_version = version_map[ticket.milestone] unless ticket.milestone.blank?
|
443 |
489 |
i.status = STATUS_MAPPING[ticket.status] || DEFAULT_STATUS
|
444 |
490 |
i.tracker = TRACKER_MAPPING[ticket.ticket_type] || DEFAULT_TRACKER
|
445 |
|
i.id = ticket.id unless Issue.exists?(ticket.id)
|
|
491 |
# Use the Redmine-genereated new ticket ID anyway (no Ticket ID recycling)
|
|
492 |
#i.id = ticket.id unless Issue.exists?(ticket.id)
|
446 |
493 |
next unless Time.fake(ticket.changetime) { i.save }
|
447 |
494 |
TICKET_MAP[ticket.id] = i.id
|
448 |
495 |
migrated_tickets += 1
|
... | ... | |
453 |
500 |
Time.fake(ticket.changetime) { i.save }
|
454 |
501 |
end
|
455 |
502 |
# Handle CC field
|
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
|
|
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
|
462 |
510 |
|
463 |
511 |
# Necessary to handle direct link to note from timelogs and putting the right start time in issue
|
464 |
512 |
noteid = 1
|
... | ... | |
611 |
659 |
if custom_field_map['tracid']
|
612 |
660 |
custom_values[custom_field_map['tracid'].id] = ticket.id
|
613 |
661 |
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 |
|
614 |
675 |
i.custom_field_values = custom_values
|
615 |
676 |
i.save_custom_field_values
|
616 |
677 |
end
|
... | ... | |
677 |
738 |
puts if wiki_pages_count < wiki_pages_total
|
678 |
739 |
|
679 |
740 |
who = " in Issues"
|
680 |
|
issues_total = TICKET_MAP.count
|
|
741 |
#issues_total = TICKET_MAP.length #works with Ruby <= 1.8.6
|
|
742 |
issues_total = TICKET_MAP.count #works with Ruby >= 1.8.7
|
681 |
743 |
TICKET_MAP.each do |newId|
|
682 |
744 |
issues_count += 1
|
683 |
745 |
simplebar(who, issues_count, issues_total)
|
... | ... | |
697 |
759 |
puts if issues_count < issues_total
|
698 |
760 |
|
699 |
761 |
who = " in Milestone descriptions"
|
700 |
|
milestone_wiki_total = milestone_wiki.count
|
|
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
|
701 |
764 |
milestone_wiki.each do |name|
|
702 |
765 |
milestone_wiki_count += 1
|
703 |
766 |
simplebar(who, milestone_wiki_count, milestone_wiki_total)
|
... | ... | |
802 |
865 |
project.identifier = identifier
|
803 |
866 |
puts "Unable to create a project with identifier '#{identifier}'!" unless project.save
|
804 |
867 |
# enable issues and wiki for the created project
|
805 |
|
# Enable all project modules by default
|
806 |
|
project.enabled_module_names = ['issue_tracking', 'wiki', 'time_tracking', 'news', 'documents', 'files', 'repository', 'boards', 'calendar', 'gantt']
|
|
868 |
# Enable only a minimal set of modules by default
|
|
869 |
project.enabled_module_names = ['issue_tracking', 'wiki']
|
807 |
870 |
else
|
808 |
871 |
puts
|
809 |
872 |
puts "This project already exists in your Redmine database."
|
... | ... | |
813 |
876 |
end
|
814 |
877 |
project.trackers << TRACKER_BUG unless project.trackers.include?(TRACKER_BUG)
|
815 |
878 |
project.trackers << TRACKER_FEATURE unless project.trackers.include?(TRACKER_FEATURE)
|
816 |
|
# Add Task type to the project
|
817 |
|
project.trackers << TRACKER_TASK unless project.trackers.include?(TRACKER_TASK)
|
|
879 |
project.trackers << TRACKER_SUPPORT unless project.trackers.include?(TRACKER_SUPPORT)
|
818 |
880 |
@target_project = project.new_record? ? nil : project
|
819 |
881 |
@target_project.reload
|
820 |
882 |
end
|
... | ... | |
890 |
952 |
|
891 |
953 |
|
892 |
954 |
desc 'Subversion migration script'
|
893 |
|
task :migrate_from_trac_svn => :environment do
|
|
955 |
task :migrate_svn_commit_properties => :environment do
|
894 |
956 |
|
895 |
957 |
require 'redmine/scm/adapters/abstract_adapter'
|
896 |
958 |
require 'redmine/scm/adapters/subversion_adapter'
|
... | ... | |
902 |
964 |
TICKET_MAP = []
|
903 |
965 |
|
904 |
966 |
class Commit
|
905 |
|
attr_accessor :revision, :message
|
|
967 |
attr_accessor :revision, :message, :author
|
906 |
968 |
|
907 |
969 |
def initialize(attributes={})
|
|
970 |
self.author = attributes[:author] || ""
|
908 |
971 |
self.message = attributes[:message] || ""
|
909 |
972 |
self.revision = attributes[:revision]
|
910 |
973 |
end
|
911 |
974 |
end
|
912 |
975 |
|
913 |
976 |
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
|
914 |
998 |
|
915 |
999 |
def set_message(path=nil, revision=nil, msg=nil)
|
916 |
1000 |
path ||= ''
|
... | ... | |
960 |
1044 |
commits << Commit.new(
|
961 |
1045 |
{
|
962 |
1046 |
:revision => logentry.attributes['revision'].to_i,
|
963 |
|
:message => logentry.elements['msg'].text
|
|
1047 |
:message => logentry.elements['msg'].text,
|
|
1048 |
:author => logentry.elements['author'].text
|
964 |
1049 |
})
|
965 |
1050 |
end
|
966 |
1051 |
rescue => e
|
... | ... | |
974 |
1059 |
|
975 |
1060 |
end
|
976 |
1061 |
|
977 |
|
def self.migrate
|
|
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
|
978 |
1090 |
|
979 |
1091 |
project = Project.find(@@redmine_project)
|
980 |
1092 |
if !project
|
... | ... | |
1008 |
1120 |
|
1009 |
1121 |
if newText != commit.message
|
1010 |
1122 |
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 |
|
1011 |
1128 |
scm.set_message(@svn_url, commit.revision, newText)
|
1012 |
1129 |
end
|
1013 |
1130 |
end
|
... | ... | |
1037 |
1154 |
end
|
1038 |
1155 |
|
1039 |
1156 |
def self.scm
|
1040 |
|
# Bugfix, with redmine 1.0.1 (Debian's) it wasn't working anymore
|
|
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
|
1041 |
1160 |
@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
|
1042 |
1162 |
@scm
|
1043 |
1163 |
end
|
1044 |
1164 |
end
|
1045 |
1165 |
|
1046 |
1166 |
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 |
|
|
1060 |
1167 |
prompt('Subversion repository url') {|repository| SvnMigrate.set_svn_url repository.strip}
|
1061 |
1168 |
prompt('Subversion repository username') {|username| SvnMigrate.set_svn_username username}
|
1062 |
1169 |
prompt('Subversion repository password') {|password| SvnMigrate.set_svn_password password}
|
1063 |
|
prompt('Redmine project identifier') {|identifier| SvnMigrate.set_redmine_project_identifier identifier}
|
1064 |
1170 |
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
|
1065 |
1181 |
|
1066 |
|
SvnMigrate.migrate
|
1067 |
|
|
|
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
|
1068 |
1203 |
end
|
1069 |
1204 |
|
1070 |
1205 |
# Prompt
|
... | ... | |
1079 |
1214 |
end
|
1080 |
1215 |
end
|
1081 |
1216 |
|
|
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 |
|
1082 |
1226 |
# Basic wiki syntax conversion
|
1083 |
1227 |
def convert_wiki_text_mapping(text, ticket_map = [])
|
1084 |
1228 |
# Hide links
|
... | ... | |
1241 |
1385 |
text = text.gsub(/''/, '_')
|
1242 |
1386 |
text = text.gsub(/__/, '+')
|
1243 |
1387 |
text = text.gsub(/~~/, '-')
|
|
1388 |
text = text.gsub(/`/, '@')
|
1244 |
1389 |
text = text.gsub(/,,/, '~')
|
1245 |
1390 |
# Tables
|
1246 |
1391 |
text = text.gsub(/\|\|/, '|')
|
... | ... | |
1258 |
1403 |
# TOC (is right-aligned, because that in Trac)
|
1259 |
1404 |
text = text.gsub(/\[\[TOC(?:\((.*?)\))?\]\]/m) {|s| "{{>toc}}\n"}
|
1260 |
1405 |
|
|
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 |
|
1261 |
1424 |
# Restore and convert code blocks
|
1262 |
1425 |
text = code_convert(text)
|
1263 |
1426 |
|