Commit b7586612 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets
Browse files

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce

parents 3d9cbc5d b409c376
......@@ -77,6 +77,7 @@ v 7.10.0 (unreleased)
- Explicitly set image alt-attribute to prevent graphical glitches if gravatars could not be loaded
- Allow user to choose a public email to show on public profile
- Remove truncation from issue titles on milestone page (Jason Blanchard)
- Fix stuck Merge Request merging events from old installations (Ben Bodenmiller)
v 7.9.3
- Contains no changes
......
......@@ -119,6 +119,22 @@
li {
line-height: 1.5;
}
a[href*="/uploads/"], a[href*="storage.googleapis.com/google-code-attachments/"] {
&:before {
margin-right: 4px;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
content: "\f0c6";
}
&:hover:before {
text-decoration: none;
}
}
}
@mixin str-truncated($max_width: 82%) {
......
......@@ -62,20 +62,8 @@ ul.notes {
word-wrap: break-word;
@include md-typography;
a[href*="/uploads/"] {
&:before {
margin-right: 4px;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
content: "\f0c6";
}
&:hover:before {
text-decoration: none;
}
hr {
margin: 10px 0;
}
}
}
......
class Import::GoogleCodeController < Import::BaseController
before_filter :user_map, only: [:new_user_map, :create_user_map]
def new
end
def callback
dump_file = params[:dump_file]
unless dump_file.respond_to?(:read)
return redirect_to :back, alert: "You need to upload a Google Takeout archive."
end
begin
dump = JSON.parse(dump_file.read)
rescue
return redirect_to :back, alert: "The uploaded file is not a valid Google Takeout archive."
end
client = Gitlab::GoogleCodeImport::Client.new(dump)
unless client.valid?
return redirect_to :back, alert: "The uploaded file is not a valid Google Takeout archive."
end
session[:google_code_dump] = dump
if params[:create_user_map] == "1"
redirect_to new_user_map_import_google_code_path
else
redirect_to status_import_google_code_path
end
end
def new_user_map
end
def create_user_map
user_map_json = params[:user_map]
user_map_json = "{}" if user_map_json.blank?
begin
user_map = JSON.parse(user_map_json)
rescue
flash.now[:alert] = "The entered user map is not a valid JSON user map."
render "new_user_map" and return
end
unless user_map.is_a?(Hash) && user_map.all? { |k, v| k.is_a?(String) && v.is_a?(String) }
flash.now[:alert] = "The entered user map is not a valid JSON user map."
render "new_user_map" and return
end
session[:google_code_user_map] = user_map
flash[:notice] = "The user map has been saved. Continue by selecting the projects you want to import."
redirect_to status_import_google_code_path
end
def status
unless client.valid?
return redirect_to new_import_google_path
end
@repos = client.repos
@already_added_projects = current_user.created_projects.where(import_type: "google_code")
already_added_projects_names = @already_added_projects.pluck(:import_source)
@repos.reject! { |repo| already_added_projects_names.include? repo.name }
end
def jobs
jobs = current_user.created_projects.where(import_type: "google_code").to_json(only: [:id, :import_status])
render json: jobs
end
def create
@repo_id = params[:repo_id]
repo = client.repo(@repo_id)
@target_namespace = current_user.namespace
@project_name = repo.name
namespace = @target_namespace
user_map = session[:google_code_user_map]
@project = Gitlab::GoogleCodeImport::ProjectCreator.new(repo, namespace, current_user, user_map).execute
end
private
def client
@client ||= Gitlab::GoogleCodeImport::Client.new(session[:google_code_dump])
end
def user_map
@user_map ||= begin
user_map = client.user_map
stored_user_map = session[:google_code_user_map]
user_map.update(stored_user_map) if stored_user_map
Hash[user_map.sort]
end
end
end
......@@ -361,6 +361,8 @@ def source_branches
end
def locked_long_ago?
locked_at && locked_at < (Time.now - 1.day)
return false unless locked?
locked_at.nil? || locked_at < (Time.now - 1.day)
end
end
......@@ -27,6 +27,7 @@
# import_type :string(255)
# import_source :string(255)
# avatar :string(255)
# import_data :text
#
require 'carrierwave/orm/activerecord'
......@@ -50,6 +51,8 @@ class Project < ActiveRecord::Base
default_value_for :wall_enabled, false
default_value_for :snippets_enabled, gitlab_config_features.snippets
serialize :import_data, JSON
# set last_activity_at to the same as created_at
after_create :set_last_activity_at
def set_last_activity_at
......@@ -185,6 +188,7 @@ def set_last_activity_at
state :failed
after_transition any => :started, do: :add_import_job
after_transition any => :finished, do: :clear_import_data
end
class << self
......@@ -262,6 +266,11 @@ def add_import_job
RepositoryImportWorker.perform_in(2.seconds, id)
end
def clear_import_data
self.import_data = nil
self.save
end
def import?
import_url.present?
end
......
%h3.page-title
%i.fa.fa-google
Import projects from Google Code
%hr
= form_tag callback_import_google_code_path, class: 'form-horizontal', multipart: true do
%p
Follow the steps below to export your Google Code project data.
In the next step, you'll be able to select the projects you want to import.
%ol
%li
%p
Go to
#{link_to "Google Takeout", "https://www.google.com/settings/takeout", target: "_blank"}.
%li
%p
Make sure you're logged into the account that owns the projects you'd like to import.
%li
%p
Click the <strong>Select none</strong> button on the right, since we only need "Google Code Project Hosting".
%li
%p
Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right.
%li
%p
Choose <strong>Next</strong> at the bottom of the page.
%li
%p
Leave the "File type" and "Delivery method" options on their default values.
%li
%p
Choose <strong>Create archive</strong> and wait for archiving to complete.
%li
%p
Click the <strong>Download</strong> button and wait for downloading to complete.
%li
%p
Find the downloaded ZIP file and decompress it.
%li
%p
Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file.
%li
%p
Upload <code>GoogleCodeProjectHosting.json</code> here:
%p
%input{type: "file", name: "dump_file", id: "dump_file"}
%li
%p
Do you want to customize how Google Code email addresses and usernames are imported into GitLab?
%p
= label_tag :create_user_map_0 do
= radio_button_tag :create_user_map, 0, true
No, directly import the existing email addresses and usernames.
%p
= label_tag :create_user_map_1 do
= radio_button_tag :create_user_map, 1, false
Yes, let me map Google Code users to full names or GitLab users.
%li
%p
= submit_tag 'Continue to the next step', class: "btn btn-create"
%h3.page-title
%i.fa.fa-google
Import projects from Google Code
%hr
= form_tag create_user_map_import_google_code_path, class: 'form-horizontal' do
%p
Customize how Google Code email addresses and usernames are imported into GitLab.
In the next step, you'll be able to select the projects you want to import.
%p
The user map is a JSON document mapping Google Code users (as keys) to the way they will be imported into GitLab (as values). By default the username is masked to ensure users' privacy.
%p
To map a Google Code user to a full name or GitLab user, simply replace the value, e.g. <code>"johnsmith@gmail.com": "John Smith"</code> or <code>"johnsmith@gmail.com": "@johnsmith"</code>. Be sure to preserve the surrounding double quotes and other punctuation.
.form-group
.col-sm-12
= text_area_tag :user_map, JSON.pretty_generate(@user_map), class: 'form-control', rows: 15
.form-actions
= submit_tag 'Continue to the next step', class: "btn btn-create"
%h3.page-title
%i.fa.fa-google
Import projects from Google Code
%p.light
Select projects you want to import.
%p.light
Optionally, you can
= link_to "customize", new_user_map_import_google_code_path
how Google Code email addresses and usernames are imported into GitLab.
%hr
%p
= button_tag 'Import all projects', class: "btn btn-success js-import-all"
%table.table.import-jobs
%thead
%tr
%th From Google Code
%th To GitLab
%th Status
%tbody
- @already_added_projects.each do |project|
%tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
%td
= link_to project.import_source, "https://code.google.com/p/#{project.import_source}", target: "_blank"
%td
%strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
%td.job-status
- if project.import_status == 'finished'
%span
%i.fa.fa-check
done
- elsif project.import_status == 'started'
%i.fa.fa-spinner.fa-spin
started
- else
= project.human_import_status_name
- @repos.each do |repo|
%tr{id: "repo_#{repo.id}"}
%td
= link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank"
%td.import-target
= "#{current_user.username}/#{repo.name}"
%td.import-actions.job-status
= button_tag "Import", class: "btn js-add-to-import"
:coffeescript
new ImporterStatus("#{jobs_import_google_code_path}", "#{import_google_code_path}")
......@@ -62,6 +62,10 @@
%i.icon-gitorious.icon-gitorious-small
Gitorious.org
= link_to new_import_google_code_path, class: 'btn' do
%i.fa.fa-google
Google Code
= link_to "#", class: 'btn js-toggle-button' do
%i.fa.fa-git
%span Any repo by URL
......
......@@ -18,6 +18,8 @@ def perform(project_id)
Gitlab::GitlabImport::Importer.new(project).execute
elsif project.import_type == 'bitbucket'
Gitlab::BitbucketImport::Importer.new(project).execute
elsif project.import_type == 'google_code'
Gitlab::GoogleCodeImport::Importer.new(project).execute
else
true
end
......
......@@ -81,6 +81,15 @@
get :callback
get :jobs
end
resource :google_code, only: [:create, :new], controller: :google_code do
get :status
post :callback
get :jobs
get :new_user_map, path: :user_map
post :create_user_map, path: :user_map
end
end
#
......
class AddImportDataToProject < ActiveRecord::Migration
def change
add_column :projects, :import_data, :text
end
end
......@@ -343,6 +343,7 @@
t.integer "star_count", default: 0, null: false
t.string "import_type"
t.string "import_source"
t.text "import_data"
end
add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree
......
......@@ -61,8 +61,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I should see that I am unsubscribed' do
sleep 0.2
find(".subscribe-button span").text.should == "Subscribe"
find(".subscribe-button span").should have_content("Subscribe")
end
step 'I click button "Unsubscribe"' do
......
module Gitlab
module GoogleCodeImport
class Client
attr_reader :raw_data
def self.mask_email(author)
parts = author.split("@", 2)
parts[0] = "#{parts[0][0...-3]}..."
parts.join("@")
end
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
def user_map
user_map = Hash.new { |hash, user| hash[user] = self.class.mask_email(user) }
repos.each do |repo|
next unless repo.valid? && repo.issues
repo.issues.each do |raw_issue|
# Touching is enough to add the entry and masked email.
user_map[raw_issue["author"]["name"]]
raw_issue["comments"]["items"].each do |raw_comment|
user_map[raw_comment["author"]["name"]]
end
end
end
Hash[user_map.sort]
end
end
end
end
module Gitlab
module GoogleCodeImport
class Importer
attr_reader :project, :repo
def initialize(project)
@project = project
@repo = GoogleCodeImport::Repository.new(project.import_data["repo"])
@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 user_map
@user_map ||= begin
user_map = Hash.new { |hash, user| hash[user] = Client.mask_email(user) }
stored_user_map = project.import_data["user_map"]
user_map.update(stored_user_map) if stored_user_map
user_map
end
end
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.issues
last_id = 0
deleted_issues = []
repo.issues.each do |raw_issue|
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 = user_map[raw_issue["author"]["name"]]
date = DateTime.parse(raw_issue["published"]).to_formatted_s(:long)
comments = raw_issue["comments"]["items"]
issue_comment = comments.shift
content = format_content(issue_comment["content"])
attachments = format_attachments(raw_issue["id"], 0, issue_comment["attachments"])
body = format_issue_body(author, date, content, attachments)
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"])
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
issue = project.issues.create!(
title: raw_issue["title"],
description: body,
author_id: project.creator_id,
assignee_id: assignee_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 do |raw_comment|
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
)