summaryrefslogtreecommitdiff
path: root/lib/gitlab/google_code_import
diff options
context:
space:
mode:
authorDouwe Maan <douwe@gitlab.com>2015-04-03 15:29:27 +0200
committerDouwe Maan <douwe@gitlab.com>2015-04-03 15:29:27 +0200
commit7b5bc32cadbf2c0a3ac1e80643e46786fd8b1b56 (patch)
tree0dfa9add1156d8ce9ff8709e36da577b7c94ad1c /lib/gitlab/google_code_import
parent9157985cfce1391973673ea278dc7506a90f8f53 (diff)
downloadgitlab-ce-7b5bc32cadbf2c0a3ac1e80643e46786fd8b1b56.tar.gz
Allow projects to be imported from Google Code.
Diffstat (limited to 'lib/gitlab/google_code_import')
-rw-r--r--lib/gitlab/google_code_import/client.rb23
-rw-r--r--lib/gitlab/google_code_import/importer.rb327
-rw-r--r--lib/gitlab/google_code_import/project_creator.rb40
-rw-r--r--lib/gitlab/google_code_import/repository.rb39
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