diff options
author | Douwe Maan <douwe@gitlab.com> | 2015-04-03 15:29:27 +0200 |
---|---|---|
committer | Douwe Maan <douwe@gitlab.com> | 2015-04-03 15:29:27 +0200 |
commit | 7b5bc32cadbf2c0a3ac1e80643e46786fd8b1b56 (patch) | |
tree | 0dfa9add1156d8ce9ff8709e36da577b7c94ad1c /lib | |
parent | 9157985cfce1391973673ea278dc7506a90f8f53 (diff) | |
download | gitlab-ce-7b5bc32cadbf2c0a3ac1e80643e46786fd8b1b56.tar.gz |
Allow projects to be imported from Google Code.
Diffstat (limited to 'lib')
-rw-r--r-- | lib/gitlab/google_code_import/client.rb | 23 | ||||
-rw-r--r-- | lib/gitlab/google_code_import/importer.rb | 327 | ||||
-rw-r--r-- | lib/gitlab/google_code_import/project_creator.rb | 40 | ||||
-rw-r--r-- | lib/gitlab/google_code_import/repository.rb | 39 |
4 files changed, 429 insertions, 0 deletions
diff --git a/lib/gitlab/google_code_import/client.rb b/lib/gitlab/google_code_import/client.rb new file mode 100644 index 00000000000..0514494d3ad --- /dev/null +++ b/lib/gitlab/google_code_import/client.rb @@ -0,0 +1,23 @@ +module Gitlab + module GoogleCodeImport + class Client + attr_reader :raw_data + + def initialize(raw_data) + @raw_data = raw_data + end + + def valid? + raw_data.is_a?(Hash) && raw_data["kind"] == "projecthosting#user" && raw_data.has_key?("projects") + end + + def repos + @repos ||= raw_data["projects"].map { |raw_repo| GoogleCodeImport::Repository.new(raw_repo) }.select(&:git?) + end + + def repo(id) + repos.find { |repo| repo.id == id } + end + end + end +end diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb new file mode 100644 index 00000000000..da0f9a10b03 --- /dev/null +++ b/lib/gitlab/google_code_import/importer.rb @@ -0,0 +1,327 @@ +module Gitlab + module GoogleCodeImport + class Importer + attr_reader :project, :repo + + def initialize(project) + @project = project + @repo = GoogleCodeImport::Repository.new(project.import_data) + + @closed_statuses = [] + @known_labels = Set.new + end + + def execute + return true unless repo.valid? + + import_status_labels + + import_labels + + import_issues + + true + end + + private + + 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 + return unless repo.raw_data["issues"] + + last_id = 0 + + deleted_issues = [] + + issues = repo.raw_data["issues"]["items"] + issues.each_with_index do |raw_issue, i| + while raw_issue["id"] > last_id + 1 + last_id += 1 + + issue = project.issues.create!( + title: "Deleted issue", + description: "*This issue has been deleted*", + author_id: project.creator_id, + state: "closed" + ) + deleted_issues << issue + end + last_id = raw_issue["id"] + + author = mask_email(raw_issue["author"]["name"]) + author_link = raw_issue["author"]["htmlLink"] + date = DateTime.parse(raw_issue["published"]).to_formatted_s(:long) + + body = [] + body << "*By [#{author}](#{author_link}) on #{date}*" + body << "---" + + comments = raw_issue["comments"]["items"] + + issue_comment = comments.shift + + content = format_content(issue_comment["content"]) + if content.blank? + content = "*(No description has been entered for this issue)*" + end + body << content + + attachments = format_attachments(raw_issue["id"], 0, issue_comment["attachments"]) + if attachments.any? + body << "---" + body += attachments + end + + 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"]) + + issue = project.issues.create!( + title: raw_issue["title"], + description: body.join("\n\n"), + author_id: project.creator_id, + state: raw_issue["state"] == "closed" ? "closed" : "opened" + ) + issue.add_labels_by_names(labels) + + import_issue_comments(issue, comments) + end + + deleted_issues.each(&:destroy!) + end + + def import_issue_comments(issue, comments) + comments.each_with_index do |raw_comment, i| + next if raw_comment.has_key?("deletedBy") + + author = mask_email(raw_comment["author"]["name"]) + author_link = raw_comment["author"]["htmlLink"] + date = DateTime.parse(raw_comment["published"]).to_formatted_s(:long) + + body = [] + body << "*By [#{author}](#{author_link}) on #{date}*" + body << "---" + + content = format_content(raw_comment["content"]) + if content.blank? + content = "*(No comment has been entered for this change)*" + end + body << content + + updates = format_updates(raw_comment["updates"]) + if updates.any? + body << "---" + body += updates + end + + attachments = format_attachments(issue.iid, raw_comment["id"], raw_comment["attachments"]) + if attachments.any? + body << "---" + body += attachments + end + + comment = issue.notes.create!( + project_id: project.id, + author_id: project.creator_id, + note: body.join("\n\n") + ) + end + end + + def nice_label_color(name) + case name + when /\AComponent:/ + "#fff39e" + when /\AOpSys:/ + "#e2e2e2" + when /\AMilestone:/ + "#fee3ff" + + when *@closed_statuses.map { |s| nice_status_name(s) } + "#cfcfcf" + when "Status: New" + "#428bca" + when "Status: Accepted" + "#5cb85c" + when "Status: NeedInfo" + "#f0ad4e" + when "Status: Started" + "#8e44ad" + when "Status: Wishlist" + "#a8d695" + # + when "Priority: Critical" + "#ffcfcf" + when "Priority: High" + "#deffcf" + when "Priority: Medium" + "#fff5cc" + when "Priority: Low" + "#cfe9ff" + # + when "Type: Defect" + "#d9534f" + when "Type: Enhancement" + "#44ad8e" + when "Type: Other" + "#7f8c8d" + when "Type: Review" + "#8e44ad" + when "Type: Task" + "#4b6dd0" + else + "#e2e2e2" + end + end + + def nice_label_name(name) + name.sub("-", ": ") + end + + def nice_status_name(name) + "Status: #{name}" + end + + def mask_email(author) + parts = author.split("@", 2) + parts[0] = "#{parts[0][0...-3]}..." + parts.join("@") + end + + def linkify_issues(s) + s.gsub(/([Ii]ssue) ([0-9]+)/, '\1 #\2') + end + + def escape_for_markdown(s) + s = s.gsub("*", "\\*") + s = s.gsub("#", "\\#") + s = s.gsub("`", "\\`") + s = s.gsub(":", "\\:") + s = s.gsub("-", "\\-") + s = s.gsub("+", "\\+") + s = s.gsub("_", "\\_") + s = s.gsub("(", "\\(") + s = s.gsub(")", "\\)") + s = s.gsub("[", "\\[") + s = s.gsub("]", "\\]") + s = s.gsub("<", "\\<") + s = s.gsub(">", "\\>") + s = s.gsub("\r", "") + s = s.gsub("\n", " \n") + s + end + + def create_label(name) + color = nice_label_color(name) + project.labels.create!(name: name, color: color) + 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 + + if raw_updates.has_key?("cc") + cc = raw_updates["cc"].map do |l| + deleted = l.start_with?("-") + l = l[1..-1] if deleted + l = mask_email(l) + l = "~~#{l}~~" if deleted + l + end + + updates << "*Cc: #{cc.join(", ")}*" + end + + if raw_updates.has_key?("labels") + labels = raw_updates["labels"].map do |l| + deleted = l.start_with?("-") + 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?("owner") + updates << "*Owner: #{raw_updates["owner"]}*" + 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| + name, id = raw_blocked_on.split(":", 2) + if name == project.import_source + "##{id}" + else + "#{project.namespace.path}/#{name}##{id}" + end + end + updates << "*Blocked on: #{blocked_ons.join(", ")}*" + end + + if raw_updates.has_key?("blocking") + blockings = raw_updates["blocking"].map do |raw_blocked_on| + name, id = raw_blocked_on.split(":", 2) + if name == project.import_source + "##{id}" + else + "#{project.namespace.path}/#{name}##{id}" + end + end + updates << "*Blocking: #{blockings.join(", ")}*" + end + + updates + end + + def format_attachments(issue_id, comment_id, raw_attachments) + return [] unless raw_attachments + + raw_attachments.map do |attachment| + next if attachment["isDeleted"] + + link = "https://storage.googleapis.com/google-code-attachments/#{@repo.name}/issue-#{issue_id}/comment-#{comment_id}/#{attachment["fileName"]}" + "[#{attachment["fileName"]}](#{link})" + end.compact + end + end + end +end diff --git a/lib/gitlab/google_code_import/project_creator.rb b/lib/gitlab/google_code_import/project_creator.rb new file mode 100644 index 00000000000..933e144d8ea --- /dev/null +++ b/lib/gitlab/google_code_import/project_creator.rb @@ -0,0 +1,40 @@ +module Gitlab + module GoogleCodeImport + class ProjectCreator + attr_reader :repo, :namespace, :current_user + + def initialize(repo, namespace, current_user) + @repo = repo + @namespace = namespace + @current_user = current_user + end + + def execute + @project = Project.new( + name: repo.name, + path: repo.name, + description: repo.summary, + namespace: namespace, + creator: current_user, + visibility_level: Gitlab::VisibilityLevel::PUBLIC, + import_type: "google_code", + import_source: repo.name, + import_url: repo.import_url, + import_data: repo.raw_data + ) + + if @project.save! + @project.reload + + if @project.import_failed? + @project.import_retry + else + @project.import_start + end + end + + @project + end + end + end +end diff --git a/lib/gitlab/google_code_import/repository.rb b/lib/gitlab/google_code_import/repository.rb new file mode 100644 index 00000000000..39a884d8292 --- /dev/null +++ b/lib/gitlab/google_code_import/repository.rb @@ -0,0 +1,39 @@ +module Gitlab + module GoogleCodeImport + class Repository + attr_accessor :raw_data + + def initialize(raw_data) + @raw_data = raw_data + end + + def valid? + raw_data.is_a?(Hash) && raw_data["kind"] == "projecthosting#project" + end + + def id + raw_data["externalId"] + end + + def name + raw_data["name"] + end + + def summary + raw_data["summary"] + end + + def description + raw_data["description"] + end + + def git? + raw_data["versionControlSystem"] == "git" + end + + def import_url + raw_data["repositoryUrls"].first + end + end + end +end |