tractive 1.0.8 → 1.0.12

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,8 +4,8 @@ module Migrator
4
4
  module Converter
5
5
  class TracToGithub
6
6
  def initialize(args)
7
- @tracticketbaseurl = args[:cfg]["trac"]["ticketbaseurl"]
8
- @attachurl = args[:opts][:attachurl] || args[:cfg].dig("attachments", "url")
7
+ @trac_ticket_base_url = args[:cfg]["trac"]["ticketbaseurl"]
8
+ @attachurl = args[:opts][:attachurl] || args[:cfg].dig("ticket", "attachments", "url")
9
9
  @changeset_base_url = args[:cfg]["trac"]["changeset_base_url"] || ""
10
10
  @singlepost = args[:opts][:singlepost]
11
11
  @labels_cfg = args[:cfg]["labels"].transform_values(&:to_h)
@@ -14,8 +14,12 @@ module Migrator
14
14
  @trac_mails_cache = {}
15
15
  @repo = args[:cfg]["github"]["repo"]
16
16
  @client = GithubApi::Client.new(access_token: args[:cfg]["github"]["token"])
17
- @wiki_attachments_url = args[:cfg]["trac"]["wiki_attachments_url"]
17
+ @wiki_attachments_url = args[:cfg].dig("wiki", "attachments", "url")
18
18
  @revmap_file_path = args[:opts][:revmapfile] || args[:cfg]["revmap_path"]
19
+ @attachment_options = {
20
+ url: @attachurl,
21
+ hashed: args[:cfg].dig("ticket", "attachments", "hashed")
22
+ }
19
23
 
20
24
  load_milestone_map
21
25
  create_labels_on_github(@labels_cfg["severity"].values)
@@ -24,12 +28,19 @@ module Migrator
24
28
  create_labels_on_github(@labels_cfg["component"].values)
25
29
 
26
30
  @uri_parser = URI::Parser.new
27
- @twf_to_markdown = Migrator::Converter::TwfToMarkdown.new(@tracticketbaseurl, @attachurl, @changeset_base_url, @wiki_attachments_url, @revmap_file_path)
31
+ @twf_to_markdown = Migrator::Converter::TwfToMarkdown.new(
32
+ @trac_ticket_base_url,
33
+ @attachment_options,
34
+ @changeset_base_url,
35
+ @wiki_attachments_url,
36
+ @revmap_file_path,
37
+ git_repo: @repo, home_page_name: args[:opts]["home-page-name"]
38
+ )
28
39
  end
29
40
 
30
41
  def compose(ticket)
31
- body = ""
32
- closed = nil
42
+ body = ""
43
+ closed_time = nil
33
44
 
34
45
  # summary line:
35
46
  # body += %i[id component priority resolution].map do |cat|
@@ -70,7 +81,7 @@ module Migrator
70
81
  @labels_cfg.fetch(x[:field], {})[x[:newvalue]]
71
82
  labels.delete(del) if del
72
83
  # labels.add(add) if add
73
- closed = x[:time] if (x[:field] == "status") && (x[:newvalue] == "closed")
84
+ closed_time = x[:time] if x[:field] == "status" && x[:newvalue] == "closed"
74
85
  end
75
86
 
76
87
  # we separate labels from badges
@@ -149,8 +160,12 @@ module Migrator
149
160
  # issue["updated_at"] = format_time(ticket[:changetime])
150
161
  end
151
162
 
152
- if issue["closed"] && closed
153
- # issue["closed_at"] = format_time(closed)
163
+ if issue["closed"]
164
+ issue["closed_at"] = if closed_time
165
+ format_time(closed_time)
166
+ else
167
+ format_time(ticket[:closed_at].to_i)
168
+ end
154
169
  end
155
170
 
156
171
  {
@@ -287,7 +302,7 @@ module Migrator
287
302
  name = meta[:filename]
288
303
  body = meta[:description]
289
304
  if @attachurl
290
- url = @uri_parser.escape("#{@attachurl}/#{meta[:id]}/#{name}")
305
+ url = @uri_parser.escape("#{@attachurl}/#{Tractive::Utilities.attachment_path(meta[:id], name, @attachment_options)}")
291
306
  text += "[`#{name}`](#{url})"
292
307
  body += "\n![#{name}](#{url})" if [".png", ".jpg", ".gif"].include? File.extname(name).downcase
293
308
  else
@@ -335,9 +350,9 @@ module Migrator
335
350
  end
336
351
 
337
352
  def trac_ticket_link(ticket)
338
- return "trac:#{ticket[:id]}" unless @tracticketbaseurl
353
+ return "trac:#{ticket[:id]}" unless @trac_ticket_base_url
339
354
 
340
- "[trac:#{ticket[:id]}](#{@tracticketbaseurl}/#{ticket[:id]})"
355
+ "[trac:#{ticket[:id]}](#{@trac_ticket_base_url}/#{ticket[:id]})"
341
356
  end
342
357
  end
343
358
  end
@@ -1,26 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "cgi"
4
+
3
5
  module Migrator
4
6
  module Converter
5
7
  # twf => Trac wiki format
6
8
  class TwfToMarkdown
7
- def initialize(base_url, attach_url, changeset_base_url, wiki_attachments_url, revmap_file_path)
9
+ def initialize(base_url, attachment_options, changeset_base_url, wiki_attachments_url, revmap_file_path, options = {})
8
10
  @base_url = base_url
9
- @attach_url = attach_url
11
+ @attach_url = attachment_options[:url]
12
+ @attach_hashed = attachment_options[:hashed]
10
13
  @changeset_base_url = changeset_base_url
11
14
  @wiki_attachments_url = wiki_attachments_url
12
15
  @revmap = load_revmap_file(revmap_file_path)
16
+
17
+ @git_repo = options[:git_repo]
18
+ @home_page_name = options[:home_page_name]
19
+ @wiki_extensions = options[:wiki_extensions] # || [".py", "changelog", "expire-ids"]
20
+ @source_folders = options[:source_folders] # || %w[personal attic sprint branch/hawk]
13
21
  end
14
22
 
15
23
  def convert(str)
24
+ # Fix 'Windows EOL' to 'Linux EOL'
25
+ str.gsub!("\r\n", "\n")
26
+
27
+ convert_tables(str)
16
28
  convert_newlines(str)
29
+ convert_comments(str)
30
+ convert_html_snippets(str)
17
31
  convert_code_snippets(str)
18
32
  convert_headings(str)
19
- convert_links(str)
33
+ convert_links(str, @git_repo)
20
34
  convert_font_styles(str)
21
35
  convert_changeset(str, @changeset_base_url)
22
36
  convert_image(str, @base_url, @attach_url, @wiki_attachments_url)
23
37
  convert_ticket(str, @base_url)
38
+ revert_intermediate_references(str)
24
39
 
25
40
  str
26
41
  end
@@ -45,18 +60,12 @@ module Migrator
45
60
  revmap
46
61
  end
47
62
 
48
- # CommitTicketReference
49
- def convert_ticket_reference(str)
50
- str.gsub!(/\{\{\{\n(#!CommitTicketReference .+?)\}\}\}/m, '\1')
51
- str.gsub!(/#!CommitTicketReference .+\n/, "")
52
- end
53
-
54
63
  # Ticket
55
64
  def convert_ticket(str, base_url)
56
65
  # replace a full ticket id with the github short refrence
57
66
  if base_url
58
67
  baseurlpattern = base_url.gsub("/", "\\/")
59
- str.gsub!(%r{#{baseurlpattern}/(\d+)}) { "ticket:#{Regexp.last_match[1]}" }
68
+ str.gsub!(%r{#{baseurlpattern}/(\d+)}, '#\1')
60
69
  end
61
70
 
62
71
  # Ticket
@@ -79,16 +88,35 @@ module Migrator
79
88
  str.gsub!("\r\n", "\n")
80
89
  end
81
90
 
91
+ # Comments
92
+ def convert_comments(str)
93
+ str.gsub!(/\{\{\{(?>((?!(?:}}}|{{{)).+?|\g<0>))*\}\}\}/m) do |str_match|
94
+ str_match.gsub(/\{\{\{\s*#!comment(\s*)(.*)\}\}\}/m, '<!--\1\2\1-->')
95
+ end
96
+ end
97
+
98
+ # HTML Snippets
99
+ def convert_html_snippets(str)
100
+ str.gsub!(/\{\{\{#!html(.*?)\}\}\}/m, '\1')
101
+ end
102
+
103
+ # CommitTicketReference
104
+ def convert_ticket_reference(str)
105
+ str.gsub!(/\{\{\{\n(#!CommitTicketReference .+?)\}\}\}/m, '\1')
106
+ str.gsub!(/#!CommitTicketReference .+\n/, "")
107
+ end
108
+
82
109
  # Code
83
110
  def convert_code_snippets(str)
84
111
  str.gsub!(/\{\{\{([^\n]+?)\}\}\}/, '`\1`')
112
+ str.gsub!(/\{\{\{#!(.*?)\n(.+?)\}\}\}/m, "```\\1\n\\2\n```")
85
113
  str.gsub!(/\{\{\{(.+?)\}\}\}/m, '```\1```')
86
114
  str.gsub!(/(?<=```)#!/m, "")
87
115
  end
88
116
 
89
117
  # Changeset
90
118
  def convert_changeset(str, changeset_base_url)
91
- str.gsub!(%r{#{Regexp.quote(changeset_base_url)}/(\d+)/?}, '[changeset:\1]') if changeset_base_url
119
+ str.gsub!(%r{#{Regexp.quote(changeset_base_url)}/(\d+)/?}, '[changeset:\1]') if changeset_base_url && !changeset_base_url.empty?
92
120
  str.gsub!(/\[changeset:"r(\d+)".*\]/, '[changeset:\1]')
93
121
  str.gsub!(/\[changeset:r(\d+)\]/, '[changeset:\1]')
94
122
  str.gsub!(/\br(\d+)\b/) { Tractive::Utilities.map_changeset(Regexp.last_match[1], @revmap, changeset_base_url) }
@@ -102,15 +130,170 @@ module Migrator
102
130
  def convert_font_styles(str)
103
131
  str.gsub!(/'''(.+?)'''/, '**\1**')
104
132
  str.gsub!(/''(.+?)''/, '*\1*')
105
- str.gsub!(%r{[^:]//(.+?[^:])//}, '_\1_')
133
+ str.gsub!(%r{([^:])//(.+?[^:])//}, '\1_\2_')
134
+ end
135
+
136
+ # Tables
137
+ def convert_tables(str)
138
+ str.gsub!(/^( *\|\|[^\n]+\|\| *[^\n|]+$)+$/, '\1 ||')
139
+
140
+ str.gsub!(/(?:^( *\|\|[^\n]+\|\| *)\n?)+/) do |match_result|
141
+ rows = match_result.gsub("||", "|").split("\n")
142
+ rows.insert(1, "| #{"--- | " * (rows[0].split("|").size - 1)}".strip)
143
+
144
+ "#{rows.join("\n")}\n"
145
+ end
106
146
  end
107
147
 
108
148
  # Links
109
- def convert_links(str)
110
- str.gsub!(/\[(http[^\s\[\]]+)\s([^\[\]]+)\]/, '[\2](\1)')
149
+ def convert_links(str, git_repo)
150
+ convert_camel_case_links(str, git_repo)
151
+ convert_double_bracket_wiki_links(str, git_repo)
152
+ convert_single_bracket_wiki_links(str, git_repo)
153
+
154
+ str.gsub!(/(^!)\[(http[^\s\[\]]+)\s([^\[\]]+)\]/, '[\2](\1)')
111
155
  str.gsub!(/!(([A-Z][a-z0-9]+){2,})/, '\1')
112
156
  end
113
157
 
158
+ def convert_single_bracket_wiki_links(str, git_repo)
159
+ str.gsub!(/(!?)\[((?:wiki|source):)?([^\s\]]*) ?(.*?)\]/) do |match_result|
160
+ source = Regexp.last_match[2]
161
+ path = Regexp.last_match[3]
162
+ name = Regexp.last_match[4]
163
+
164
+ formatted_link(
165
+ match_result,
166
+ git_repo,
167
+ { source: source, path: path, name: name }
168
+ )
169
+ end
170
+ end
171
+
172
+ def convert_double_bracket_wiki_links(str, git_repo)
173
+ str.gsub!(/(!?)\[\[((?:wiki|source):)?([^|\n]*)\|?(.*?)\]\]/) do |match_result|
174
+ source = Regexp.last_match[2]
175
+ path = Regexp.last_match[3]
176
+ name = Regexp.last_match[4]
177
+
178
+ formatted_link(
179
+ match_result,
180
+ git_repo,
181
+ { source: source, path: path, name: name }
182
+ )
183
+ end
184
+ end
185
+
186
+ def formatted_link(unformatted_text, git_repo, url_options = {})
187
+ return unformatted_text.gsub("!", "{~") if unformatted_text.start_with?("!")
188
+
189
+ if url_options[:source] == "wiki:"
190
+ link, internal_link = url_options[:path].split("#")
191
+ link = "Home" if link == @home_page_name
192
+ internal_link = Tractive::Utilities.dasharize(internal_link) if internal_link
193
+ url_options[:name] = link if url_options[:name].empty?
194
+ "{{#{url_options[:name]}}}(https://github.com/#{git_repo}/wiki/#{link}##{internal_link})"
195
+ elsif url_options[:source] == "source:"
196
+ url_options[:name] = url_options[:path] if url_options[:name].empty?
197
+ "{{#{url_options[:name]}}}(https://github.com/#{git_repo}/#{source_git_path(url_options[:path])})"
198
+ elsif url_options[:path].start_with?("http")
199
+ url_options[:name] = url_options[:path] if url_options[:name].empty?
200
+ "{{#{url_options[:name]}}}(#{url_options[:path]})"
201
+ else
202
+ unformatted_text
203
+ end
204
+ end
205
+
206
+ def source_git_path(trac_path)
207
+ trac_path = trac_path.gsub("trunk/", "main/")
208
+ trac_path = trac_path.delete_prefix("/").delete_suffix("/")
209
+
210
+ return "" if trac_path.empty?
211
+
212
+ uri = URI.parse(trac_path)
213
+
214
+ trac_path = uri.path
215
+ line_number = uri.fragment
216
+ trac_path, revision = trac_path.split("@")
217
+
218
+ if trac_path.split("/").count <= 1
219
+ wiki_path(trac_path)
220
+ else
221
+ unless trac_path.start_with?("tags")
222
+ params = CGI.parse(uri.query || "")
223
+ revision ||= params["rev"].first
224
+
225
+ # TODO: Currently @ does not work with file paths except for main branch
226
+ sha = @revmap[revision]&.strip
227
+
228
+ trac_path = if sha && file?(trac_path)
229
+ trac_path.gsub("main/", "#{sha}/")
230
+ else
231
+ sha || trac_path
232
+ end
233
+ end
234
+
235
+ wiki_path(trac_path.delete_prefix("tags/"), line_number)
236
+ end
237
+ end
238
+
239
+ def index_paths
240
+ @index_paths ||= {
241
+ "tags" => "tags",
242
+ "tags/" => "tags",
243
+ "branch" => "branches/all"
244
+ }
245
+ end
246
+
247
+ def file?(trac_path)
248
+ return false unless trac_path
249
+
250
+ @wiki_extensions.any? { |extension| trac_path.end_with?(extension) }
251
+ end
252
+
253
+ def wiki_path(path, line_number = "")
254
+ # TODO: This will not work for folders given in the source_folder parameter and
255
+ # will not work for subfolders paths like `personal/rjs` unless given in the parameters.
256
+ return "branches/all?query=#{path}" if @source_folders.any? { |folder| folder == path }
257
+ return index_paths[path] if index_paths[path]
258
+
259
+ prefix = if file?(path)
260
+ "blob"
261
+ else
262
+ "tree"
263
+ end
264
+
265
+ "#{prefix}/#{path}#{"#" unless line_number.to_s.empty?}#{line_number}"
266
+ end
267
+
268
+ # CamelCase page names follow these rules:
269
+ # 1. The name must consist of alphabetic characters only;
270
+ # no digits, spaces, punctuation or underscores are allowed.
271
+ # 2. A name must have at least two capital letters.
272
+ # 3. The first character must be capitalized.
273
+ # 4. Every capital letter must be followed by one or more lower-case letters.
274
+ # 5. The use of slash ( / ) is permitted in page names, where it typically represents a hierarchy.
275
+ def convert_camel_case_links(str, git_repo)
276
+ name_regex = %r{(^| )(!?)(/?[A-Z][a-z]+(/?[A-Z][a-z]+)+/?)}
277
+ wiki_pages_names = Tractive::Wiki.select(:name).distinct.map(:name)
278
+ str.gsub!(name_regex) do
279
+ start = Regexp.last_match[2]
280
+ name = Regexp.last_match[3]
281
+
282
+ wiki_link = if start != "!" && wiki_pages_names.include?(name)
283
+ make_wiki_link(name, git_repo)
284
+ else
285
+ name
286
+ end
287
+
288
+ "#{Regexp.last_match[1]}#{wiki_link}"
289
+ end
290
+ end
291
+
292
+ def make_wiki_link(wiki_name, git_repo)
293
+ wiki_name = "Home" if wiki_name == @home_page_name
294
+ "[#{wiki_name}](https://github.com/#{git_repo}/wiki/#{wiki_name})"
295
+ end
296
+
114
297
  def convert_image(str, base_url, attach_url, wiki_attachments_url)
115
298
  # https://trac.edgewall.org/wiki/WikiFormatting#Images
116
299
  # [[Image(picture.gif)]] Current page (Ticket, Wiki, Comment)
@@ -118,28 +301,37 @@ module Migrator
118
301
  # [[Image(ticket:1:picture.gif)]] (file attached to a ticket)
119
302
 
120
303
  image_regex = /\[\[Image\((?:(?<module>(?:source|wiki)):)?(?<path>[^)]+)\)\]\]/
121
- d = image_regex.match(str)
122
- return if d.nil?
123
-
124
- path = d[:path]
125
- mod = d[:module]
126
-
127
- image_path = if mod == "source"
128
- "![#{path.split("/").last}](#{base_url}#{path})"
129
- elsif mod == "wiki"
130
- _, file = path.split(":")
131
- upload_path = "#{wiki_attachments_url}/#{file}"
132
- "![#{file}](#{upload_path})"
133
- elsif path.start_with?("http")
134
- # [[Image(http://example.org/s.jpg)]]
135
- "![#{d[:path]}](#{d[:path]})"
136
- else
137
- _, id, file = path.split(":")
138
- file_path = "#{attach_url}/#{id}/#{file}"
139
- "![#{d[:path]}](#{file_path})"
140
- end
141
-
142
- str.gsub!(image_regex, image_path)
304
+
305
+ str.gsub!(image_regex) do
306
+ path = Regexp.last_match[:path]
307
+ mod = Regexp.last_match[:module]
308
+
309
+ converted_image = if mod == "source"
310
+ "!{{#{path.split("/").last}}}(#{base_url}#{path})"
311
+ elsif mod == "wiki"
312
+ id, file = path.split(":")
313
+ upload_path = "#{wiki_attachments_url}/#{Tractive::Utilities.attachment_path(id, file, hashed: @attach_hashed)}"
314
+ "!{{#{file}}}(#{upload_path})"
315
+ elsif path.start_with?("http")
316
+ # [[Image(http://example.org/s.jpg)]]
317
+ "!{{#{path}}}(#{path})"
318
+ else
319
+ _, id, file = path.split(":")
320
+ file_path = "#{attach_url}/#{Tractive::Utilities.attachment_path(id, file, hashed: @attach_hashed)}"
321
+ "!{{#{path}}}(#{file_path})"
322
+ end
323
+
324
+ # There are also ticket references in the format of ticket:1 so
325
+ # changing this now and will revert it at the end again
326
+ converted_image.gsub(/ticket:(\d+)/, 'ImageTicket~\1')
327
+ end
328
+ end
329
+
330
+ def revert_intermediate_references(str)
331
+ str.gsub!(/ImageTicket~(\d)/, 'ticket:\1')
332
+ str.gsub!("{{", "[")
333
+ str.gsub!("}}", "]")
334
+ str.gsub!(/(\{~)*/, "")
143
335
  end
144
336
  end
145
337
  end
@@ -27,7 +27,7 @@ module Migrator
27
27
  input_file_name = args[:opts][:importfromfile]
28
28
 
29
29
  @filter_applied = args[:opts][:filter]
30
- @filter_options = { column_name: args[:opts][:columnname], operator: args[:opts][:operator], column_value: args[:opts][:columnvalue] }
30
+ @filter_options = { column_name: args[:opts][:columnname], operator: args[:opts][:operator], column_value: args[:opts][:columnvalue], include_null: args[:opts][:includenull] }
31
31
 
32
32
  @trac = Tractive::Trac.new(db)
33
33
  @repo = github["repo"]
@@ -96,7 +96,7 @@ module Migrator
96
96
 
97
97
  def mock_ticket_details(ticket_id)
98
98
  summary = if @filter_applied
99
- "Not available in trac #{ticket_id}"
99
+ "Placeholder issue #{ticket_id} created to align Github issue and trac ticket numbers during migration."
100
100
  else
101
101
  "DELETED in trac #{ticket_id}"
102
102
  end
@@ -105,7 +105,8 @@ module Migrator
105
105
  summary: summary,
106
106
  time: Time.now.to_i,
107
107
  status: "closed",
108
- reporter: "tractive"
108
+ reporter: "tractive",
109
+ closed_at: Time.at(0).utc
109
110
  }
110
111
  end
111
112
 
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "csv"
4
+
5
+ module Migrator
6
+ module Wikis
7
+ class MigrateFromDb
8
+ def initialize(args)
9
+ $logger.debug("OPTIONS = #{args}")
10
+
11
+ @config = args[:cfg]
12
+ @options = args[:opts]
13
+ @authors_map = @config["users"].to_h
14
+
15
+ @tracticketbaseurl = @config["trac"]["ticketbaseurl"]
16
+ @git_repo = @config["github"]["repo"]
17
+ @changeset_base_url = @config["trac"]["changeset_base_url"] || ""
18
+ @revmap_path = @config["revmap_path"]
19
+ @attachments_hashed = @config.dig("wiki", "attachments", "hashed")
20
+
21
+ @wiki_attachments_url = @options["attachment-base-url"] || @config.dig("wiki", "attachments", "url") || ""
22
+ @repo_path = @options["repo-path"] || ""
23
+ @home_page_name = @options["home-page-name"]
24
+ @wiki_extensions = @options["wiki-extensions"]
25
+ @source_folders = @options["source-folders"]
26
+
27
+ @attachment_options = {
28
+ hashed: @attachments_hashed
29
+ }
30
+
31
+ verify_options
32
+ verify_locations
33
+
34
+ @twf_to_markdown = Migrator::Converter::TwfToMarkdown.new(
35
+ @tracticketbaseurl,
36
+ @attachment_options,
37
+ @changeset_base_url,
38
+ @wiki_attachments_url,
39
+ @revmap_path,
40
+ git_repo: @git_repo,
41
+ home_page_name: @home_page_name,
42
+ wiki_extensions: @wiki_extensions,
43
+ source_folders: @source_folders
44
+ )
45
+ end
46
+
47
+ def migrate_wikis
48
+ $logger.info("Processing the wiki...")
49
+
50
+ Dir.chdir(@options["repo-path"]) do
51
+ # For every version of every file in the wiki...
52
+ Tractive::Wiki.for_migration.each do |wiki|
53
+ next if skip_file(wiki[:name])
54
+
55
+ comment = if wiki[:comment].nil? || wiki[:comment].empty?
56
+ "Initial load of version #{wiki[:version]} of trac-file #{wiki[:name]}"
57
+ else
58
+ wiki[:comment].gsub('"', '\"')
59
+ end
60
+
61
+ file_name = filename_for_wiki(wiki)
62
+
63
+ $logger.info("Working with file [#{file_name}]")
64
+ $logger.debug("Object: #{wiki}")
65
+
66
+ wiki_markdown_text = @twf_to_markdown.convert(wiki[:text])
67
+ wiki_markdown_text += wiki_attachments(wiki)
68
+
69
+ # Create file with content
70
+ File.open(file_name, "w") do |f|
71
+ f.puts(wiki_markdown_text)
72
+ end
73
+
74
+ # git-add it
75
+ unless execute_command("git add #{file_name}").success?
76
+ $logger.error("ERROR at git-add #{file_name}!!!")
77
+ exit(1)
78
+ end
79
+
80
+ author = generate_author(wiki[:author])
81
+
82
+ # git-commit it
83
+ commit_command = "git commit --allow-empty -m \"#{comment}\" --author \"#{author}\" --date \"#{wiki[:fixeddate]}\""
84
+ unless execute_command(commit_command).success?
85
+ $logger.error("ERROR at git-commit #{file_name}!!!")
86
+ exit(1)
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ private
93
+
94
+ def filename_for_wiki(wiki)
95
+ return "Home.md" if @home_page_name == wiki[:name]
96
+
97
+ "#{cleanse_filename(wiki[:name])}.md"
98
+ end
99
+
100
+ def verify_options
101
+ $logger.info("Verifying options...")
102
+
103
+ missing_options = []
104
+ missing_options << "attachment-base-url" if @wiki_attachments_url.empty?
105
+ missing_options << "repo-path" if @repo_path.empty?
106
+
107
+ return if missing_options.empty?
108
+
109
+ $logger.error("Following options are missing: #{missing_options} - exiting...")
110
+ exit(1)
111
+ end
112
+
113
+ def verify_locations
114
+ $logger.info("Verifying locations...")
115
+ missing_directories = []
116
+
117
+ # git-root exists?
118
+ missing_directories << "repo-path" unless Dir.exist?(@repo_path)
119
+
120
+ return if missing_directories.empty?
121
+
122
+ $logger.error("Following directories are missing: #{missing_directories} - exiting ...")
123
+ exit(1)
124
+ end
125
+
126
+ def cleanse_filename(name)
127
+ # Get rid of 'magic' characters from potential filename - replace with '_'
128
+ # Magic: [ /<>- ]
129
+ name.gsub(%r{[/<>-]}, "_")
130
+ end
131
+
132
+ def skip_file(file_name)
133
+ file_name.start_with?("Trac") || (file_name.start_with?("Wiki") && !file_name.start_with?("WikiStart"))
134
+ end
135
+
136
+ def generate_author(author)
137
+ return "" if author.nil? || author.empty?
138
+
139
+ author_name = @authors_map[author]&.[]("name") || author.split("@").first
140
+ author_email = @authors_map[author]&.[]("email") || author
141
+
142
+ "#{author_name} <#{author_email}>"
143
+ end
144
+
145
+ def wiki_attachments(wiki)
146
+ attachments = wiki.attachments
147
+ return "" if attachments.count.zero?
148
+
149
+ attachments_list = ["# Attachments\n"]
150
+
151
+ attachments.each do |attachment|
152
+ attachment_path = Tractive::Utilities.attachment_path(
153
+ wiki.name, attachment.filename, hashed: @attachments_hashed
154
+ )
155
+ attachments_list << "- [#{attachment.filename}](#{@wiki_attachments_url}/#{attachment_path})"
156
+ end
157
+
158
+ attachments_list.join("\n")
159
+ end
160
+
161
+ def execute_command(command)
162
+ `#{command}`
163
+ $CHILD_STATUS
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "wikis/migrate_from_db"
@@ -1,3 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "migrator/engine"
4
+ require_relative "migrator/wikis"
@@ -4,6 +4,7 @@ module Tractive
4
4
  class Attachment < Sequel::Model(:attachment)
5
5
  dataset_module do
6
6
  where(:tickets_attachments, type: "ticket")
7
+ where(:wiki_attachments, type: "wiki")
7
8
  select(:for_export, :id, :filename)
8
9
  end
9
10
  end
@@ -19,14 +19,18 @@ module Tractive
19
19
  def filter_column(options)
20
20
  return self if options.nil? || options.values.compact.empty?
21
21
 
22
- case options[:operator].downcase
23
- when "like"
24
- where { Sequel.like(options[:column_name].to_sym, options[:column_value]) }
25
- when "not like"
26
- where { ~Sequel.like(options[:column_name].to_sym, options[:column_value]) }
27
- else
28
- where { Sequel.lit("#{options[:column_name]} #{options[:operator]} '#{options[:column_value]}'") }
29
- end
22
+ query = case options[:operator].downcase
23
+ when "like"
24
+ Sequel.like(options[:column_name].to_sym, options[:column_value])
25
+ when "not like"
26
+ ~Sequel.like(options[:column_name].to_sym, options[:column_value])
27
+ else
28
+ Sequel.lit("#{options[:column_name]} #{options[:operator]} '#{options[:column_value]}'")
29
+ end
30
+
31
+ query = Sequel.|(query, { options[:column_name].to_sym => nil }) if options[:include_null]
32
+
33
+ where { query }
30
34
  end
31
35
  end
32
36
 
@@ -35,5 +39,9 @@ module Tractive
35
39
  change_arr = changes + attachments
36
40
  change_arr.sort_by { |change| change[:time] }
37
41
  end
42
+
43
+ def closed_comments
44
+ changes_dataset.where(field: "status", newvalue: "closed")
45
+ end
38
46
  end
39
47
  end