importer.rb 10.0 KB
Newer Older
1 2 3 4 5 6 7
module Gitlab
  module GoogleCodeImport
    class Importer
      attr_reader :project, :repo

      def initialize(project)
        @project = project
8 9 10 11

        import_data = project.import_data.try(:data)
        repo_data = import_data["repo"] if import_data
        @repo = GoogleCodeImport::Repository.new(repo_data)
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

        @closed_statuses = []
        @known_labels = Set.new
      end

      def execute
        return true unless repo.valid?

        import_status_labels

        import_labels

        import_issues

        true
      end

      private

D
Douwe Maan 已提交
31 32
      def user_map
        @user_map ||= begin
33
          user_map = Hash.new do |hash, user|
34 35 36
            # Replace ... by \.\.\., so `johnsm...@gmail.com` isn't autolinked.
            Client.mask_email(user).sub("...", "\\.\\.\\.")
          end
D
Douwe Maan 已提交
37

38 39
          import_data = project.import_data.try(:data)
          stored_user_map = import_data["user_map"] if import_data
D
Douwe Maan 已提交
40 41 42 43 44 45
          user_map.update(stored_user_map) if stored_user_map

          user_map
        end
      end

46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
      def import_status_labels
        repo.raw_data["issuesConfig"]["statuses"].each do |status|
          closed = !status["meansOpen"]
          @closed_statuses << status["status"] if closed

          name = nice_status_name(status["status"])
          create_label(name)
          @known_labels << name
        end
      end

      def import_labels
        repo.raw_data["issuesConfig"]["labels"].each do |label|
          name = nice_label_name(label["label"])
          create_label(name)
          @known_labels << name
        end
      end

      def import_issues
D
Douwe Maan 已提交
66
        return unless repo.issues
67

68
        while raw_issue = repo.issues.shift
D
Douwe Maan 已提交
69
          author  = user_map[raw_issue["author"]["name"]]
70
          date    = DateTime.parse(raw_issue["published"]).to_formatted_s(:long)
71 72 73 74

          comments = raw_issue["comments"]["items"]
          issue_comment = comments.shift

75
          content     = format_content(issue_comment["content"])
76
          attachments = format_attachments(raw_issue["id"], 0, issue_comment["attachments"])
77 78

          body = format_issue_body(author, date, content, attachments)
79
          labels = import_issue_labels(raw_issue)
80

81 82 83 84 85 86 87 88 89 90 91 92 93
          assignee_id = nil
          if raw_issue.has_key?("owner")
            username = user_map[raw_issue["owner"]["name"]]

            if username.start_with?("@")
              username = username[1..-1]

              if user = User.find_by(username: username)
                assignee_id = user.id
              end
            end
          end

94
          issue = Issue.create!(
95 96 97 98 99 100 101
            iid:         raw_issue['id'],
            project_id:  project.id,
            title:       raw_issue['title'],
            description: body,
            author_id:   project.creator_id,
            assignee_id: assignee_id,
            state:       raw_issue['state'] == 'closed' ? 'closed' : 'opened'
102
          )
103

104
          issue_labels = ::LabelsFinder.new(nil, project_id: project.id, title: labels).execute(skip_authorization: true)
105
          issue.update_attribute(:label_ids, issue_labels.pluck(:id))
106

107 108 109 110
          import_issue_comments(issue, comments)
        end
      end

111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
      def import_issue_labels(raw_issue)
        labels = []

        raw_issue["labels"].each do |label|
          name = nice_label_name(label)
          labels << name

          unless @known_labels.include?(name)
            create_label(name)
            @known_labels << name
          end
        end

        labels << nice_status_name(raw_issue["status"])
        labels
      end

128
      def import_issue_comments(issue, comments)
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
        Note.transaction do
          while raw_comment = comments.shift
            next if raw_comment.has_key?("deletedBy")

            content     = format_content(raw_comment["content"])
            updates     = format_updates(raw_comment["updates"])
            attachments = format_attachments(issue.iid, raw_comment["id"], raw_comment["attachments"])

            next if content.blank? && updates.blank? && attachments.blank?

            author  = user_map[raw_comment["author"]["name"]]
            date    = DateTime.parse(raw_comment["published"]).to_formatted_s(:long)

            body = format_issue_comment_body(
              raw_comment["id"],
              author,
              date,
              content,
              updates,
              attachments
            )
150

151 152 153 154 155 156 157 158 159
            # Needs to match order of `comment_columns` below.
            Note.create!(
              project_id:     project.id,
              noteable_type:  "Issue",
              noteable_id:    issue.id,
              author_id:      project.creator_id,
              note:           body
            )
          end
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
        end
      end

      def nice_label_color(name)
        case name
        when /\AComponent:/
          "#fff39e"
        when /\AOpSys:/
          "#e2e2e2"
        when /\AMilestone:/
          "#fee3ff"

        when "Status: New"
          "#428bca"
        when "Status: Accepted"
          "#5cb85c"
        when "Status: Started"
          "#8e44ad"
178

179 180 181 182 183 184 185 186
        when "Priority: Critical"
          "#ffcfcf"
        when "Priority: High"
          "#deffcf"
        when "Priority: Medium"
          "#fff5cc"
        when "Priority: Low"
          "#cfe9ff"
187

188 189 190 191 192 193
        when "Type: Defect"
          "#d9534f"
        when "Type: Enhancement"
          "#44ad8e"
        when "Type: Task"
          "#4b6dd0"
D
Douwe Maan 已提交
194 195 196 197
        when "Type: Review"
          "#8e44ad"
        when "Type: Other"
          "#7f8c8d"
G
Gabriel Mazetto 已提交
198 199
        when *@closed_statuses.map { |s| nice_status_name(s) }
          "#cfcfcf"
200 201 202 203 204 205 206 207 208 209 210 211 212 213
        else
          "#e2e2e2"
        end
      end

      def nice_label_name(name)
        name.sub("-", ": ")
      end

      def nice_status_name(name)
        "Status: #{name}"
      end

      def linkify_issues(s)
214 215 216
        s = s.gsub(/([Ii]ssue) ([0-9]+)/, '\1 #\2')
        s = s.gsub(/([Cc]omment) #([0-9]+)/, '\1 \2')
        s
217 218 219
      end

      def escape_for_markdown(s)
220 221 222 223 224
        # No headings and lists
        s = s.gsub(/^#/, "\\#")
        s = s.gsub(/^-/, "\\-")

        # No inline code
225
        s = s.gsub("`", "\\`")
226 227

        # Carriage returns make me sad
G
Gabriel Mazetto 已提交
228
        s = s.delete("\r")
229 230

        # Markdown ignores single newlines, but we need them as <br />.
231
        s = s.gsub("\n", "  \n")
232

233 234 235 236
        s
      end

      def create_label(name)
237
        params = { name: name, color: nice_label_color(name) }
238
        ::Labels::FindOrCreateService.new(nil, project, params).execute(skip_authorization: true)
239 240 241 242 243 244 245 246 247 248 249 250 251
      end

      def format_content(raw_content)
        linkify_issues(escape_for_markdown(raw_content))
      end

      def format_updates(raw_updates)
        updates = []

        if raw_updates.has_key?("status")
          updates << "*Status: #{raw_updates["status"]}*"
        end

D
Douwe Maan 已提交
252
        if raw_updates.has_key?("owner")
D
Douwe Maan 已提交
253
          updates << "*Owner: #{user_map[raw_updates["owner"]]}*"
D
Douwe Maan 已提交
254 255
        end

256
        if raw_updates.has_key?("cc")
257 258
          cc = raw_updates["cc"].map do |l|
            deleted = l.start_with?("-")
259
            l = l[1..-1] if deleted
D
Douwe Maan 已提交
260
            l = user_map[l]
261 262 263 264 265 266 267 268
            l = "~~#{l}~~" if deleted
            l
          end

          updates << "*Cc: #{cc.join(", ")}*"
        end

        if raw_updates.has_key?("labels")
269 270
          labels = raw_updates["labels"].map do |l|
            deleted = l.start_with?("-")
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
            l = l[1..-1] if deleted
            l = nice_label_name(l)
            l = "~~#{l}~~" if deleted
            l
          end

          updates << "*Labels: #{labels.join(", ")}*"
        end

        if raw_updates.has_key?("mergedInto")
          updates << "*Merged into: ##{raw_updates["mergedInto"]}*"
        end

        if raw_updates.has_key?("blockedOn")
          blocked_ons = raw_updates["blockedOn"].map do |raw_blocked_on|
286
            format_blocking_updates(raw_blocked_on)
287
          end
288

289 290 291 292 293
          updates << "*Blocked on: #{blocked_ons.join(", ")}*"
        end

        if raw_updates.has_key?("blocking")
          blockings = raw_updates["blocking"].map do |raw_blocked_on|
294
            format_blocking_updates(raw_blocked_on)
295
          end
296

297 298 299 300 301 302
          updates << "*Blocking: #{blockings.join(", ")}*"
        end

        updates
      end

303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
      def format_blocking_updates(raw_blocked_on)
        name, id = raw_blocked_on.split(":", 2)

        deleted = name.start_with?("-")
        name = name[1..-1] if deleted

        text =
          if name == project.import_source
            "##{id}"
          else
            "#{project.namespace.path}/#{name}##{id}"
          end
        text = "~~#{text}~~" if deleted
        text
      end

319 320 321 322 323 324
      def format_attachments(issue_id, comment_id, raw_attachments)
        return [] unless raw_attachments

        raw_attachments.map do |attachment|
          next if attachment["isDeleted"]

325 326
          filename = attachment["fileName"]
          link = "https://storage.googleapis.com/google-code-attachments/#{@repo.name}/issue-#{issue_id}/comment-#{comment_id}/#{filename}"
327

328
          text = "[#{filename}](#{link})"
329
          text = "!#{text}" if filename =~ /\.(png|jpg|jpeg|gif|bmp|tiff)\z/i
330
          text
331 332
        end.compact
      end
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358

      def format_issue_comment_body(id, author, date, content, updates, attachments)
        body = []
        body << "*Comment #{id} by #{author} on #{date}*"
        body << "---"

        if content.blank?
          content = "*(No comment has been entered for this change)*"
        end
        body << content

        if updates.any?
          body << "---"
          body += updates
        end

        if attachments.any?
          body << "---"
          body += attachments
        end

        body.join("\n\n")
      end

      def format_issue_body(author, date, content, attachments)
        body = []
359
        body << "*By #{author} on #{date} (imported from Google Code)*"
360 361 362 363 364 365 366 367 368 369 370 371 372 373
        body << "---"

        if content.blank?
          content = "*(No description has been entered for this issue)*"
        end
        body << content

        if attachments.any?
          body << "---"
          body += attachments
        end

        body.join("\n\n")
      end
374 375 376
    end
  end
end