tractive 1.0.8 → 1.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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