Commit 841a5ef5 authored by Stan Hu's avatar Stan Hu

Merge branch 'master' into sh-headless-chrome-support

parents 3e75b7fa 84336b84
......@@ -16,7 +16,7 @@ gem 'mysql2', '~> 0.4.5', group: :mysql
gem 'pg', '~> 0.18.2', group: :postgres
gem 'rugged', '~> 0.26.0'
gem 'grape-route-helpers', '~> 2.0.0'
gem 'grape-route-helpers', '~> 2.1.0'
gem 'faraday', '~> 0.12'
......@@ -76,7 +76,7 @@ gem 'gollum-rugged_adapter', '~> 0.4.4', require: false
gem 'github-linguist', '~> 4.7.0', require: 'linguist'
# API
gem 'grape', '~> 0.19.2'
gem 'grape', '~> 1.0'
gem 'grape-entity', '~> 0.6.0'
gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
......@@ -144,8 +144,6 @@ end
# State machine
gem 'state_machines-activerecord', '~> 0.4.0'
# Run events after state machine commits
gem 'after_commit_queue', '~> 1.3.0'
# Issue tags
gem 'acts-as-taggable-on', '~> 4.0'
......@@ -289,7 +287,7 @@ group :metrics do
gem 'influxdb', '~> 0.2', require: false
# Prometheus
gem 'prometheus-client-mmap', '~>0.7.0.beta11'
gem 'prometheus-client-mmap', '~>0.7.0.beta12'
gem 'raindrops', '~> 0.18'
end
......@@ -403,7 +401,7 @@ group :ed25519 do
end
# Gitaly GRPC client
gem 'gitaly', '~> 0.27.0'
gem 'gitaly', '~> 0.29.0'
gem 'toml-rb', '~> 0.3.15', require: false
......
......@@ -46,8 +46,6 @@ GEM
ice_nine (~> 0.11.0)
memoizable (~> 0.4.0)
addressable (2.3.8)
after_commit_queue (1.3.0)
activerecord (>= 3.0)
akismet (2.0.0)
allocations (1.0.5)
arel (6.0.4)
......@@ -278,7 +276,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
gitaly (0.27.0)
gitaly (0.29.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6)
......@@ -343,12 +341,9 @@ GEM
signet (~> 0.7)
gpgme (2.0.13)
mini_portile2 (~> 2.1)
grape (0.19.2)
grape (1.0.0)
activesupport
builder
hashie (>= 2.1.0)
multi_json (>= 1.3.2)
multi_xml (>= 0.5.2)
mustermann-grape (~> 1.0.0)
rack (>= 1.3.0)
rack-accept
......@@ -356,11 +351,11 @@ GEM
grape-entity (0.6.0)
activesupport
multi_json (>= 1.3.2)
grape-route-helpers (2.0.0)
grape-route-helpers (2.1.0)
activesupport
grape (~> 0.16, >= 0.16.0)
grape (>= 0.16.0)
rake
grpc (1.4.0)
grpc (1.4.5)
google-protobuf (~> 3.1)
googleauth (~> 0.5.1)
haml (4.0.7)
......@@ -376,7 +371,7 @@ GEM
thor
tilt
hashdiff (0.3.4)
hashie (3.5.5)
hashie (3.5.6)
hashie-forbidden_attributes (0.1.1)
hashie (>= 3.0)
health_check (2.6.0)
......@@ -621,7 +616,7 @@ GEM
parser
unparser
procto (0.0.3)
prometheus-client-mmap (0.7.0.beta11)
prometheus-client-mmap (0.7.0.beta12)
mmap2 (~> 2.2, >= 2.2.7)
pry (0.10.4)
coderay (~> 1.1.0)
......@@ -960,7 +955,6 @@ DEPENDENCIES
activerecord_sane_schema_dumper (= 0.2)
acts-as-taggable-on (~> 4.0)
addressable (~> 2.3.8)
after_commit_queue (~> 1.3.0)
akismet (~> 2.0)
allocations (~> 1.0)
asana (~> 0.6.0)
......@@ -1022,7 +1016,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
gitaly (~> 0.27.0)
gitaly (~> 0.29.0)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.5.1)
......@@ -1032,9 +1026,9 @@ DEPENDENCIES
gon (~> 6.1.0)
google-api-client (~> 0.8.6)
gpgme
grape (~> 0.19.2)
grape (~> 1.0)
grape-entity (~> 0.6.0)
grape-route-helpers (~> 2.0.0)
grape-route-helpers (~> 2.1.0)
haml_lint (~> 0.26.0)
hamlit (~> 2.6.1)
hashie-forbidden_attributes
......@@ -1096,7 +1090,7 @@ DEPENDENCIES
peek-sidekiq (~> 1.0.3)
pg (~> 0.18.2)
premailer-rails (~> 1.9.7)
prometheus-client-mmap (~> 0.7.0.beta11)
prometheus-client-mmap (~> 0.7.0.beta12)
pry-byebug (~> 3.4.1)
pry-rails (~> 0.3.4)
rack-attack (~> 4.4.1)
......
......@@ -414,7 +414,7 @@ import initChangesDropdown from './init_changes_dropdown';
case 'projects:tree:show':
shortcut_handler = new ShortcutsNavigation();
if (UserFeatureHelper.isNewRepo()) break;
if (UserFeatureHelper.isNewRepoEnabled()) break;
new TreeView();
new BlobViewer();
......@@ -434,7 +434,7 @@ import initChangesDropdown from './init_changes_dropdown';
shortcut_handler = true;
break;
case 'projects:blob:show':
if (UserFeatureHelper.isNewRepo()) break;
if (UserFeatureHelper.isNewRepoEnabled()) break;
new BlobViewer();
initBlob();
break;
......
......@@ -111,8 +111,7 @@ window.GroupsSelect = (function() {
};
GroupsSelect.prototype.forceOverflow = function (e) {
const itemHeight = this.dropdown.querySelector('.select2-result:first-child').clientHeight;
this.dropdown.style.height = `${Math.floor(this.dropdown.scrollHeight - (itemHeight * 0.9))}px`;
this.dropdown.style.height = `${Math.floor(this.dropdown.scrollHeight)}px`;
};
GroupsSelect.PER_PAGE = 20;
......
import Cookies from 'js-cookie';
function isNewRepo() {
export default {
isNewRepoEnabled() {
return Cookies.get('new_repo') === 'true';
}
const UserFeatureHelper = {
isNewRepo,
},
};
export default UserFeatureHelper;
......@@ -378,15 +378,15 @@
w.gl.utils.backOff = (fn, timeout = 60000) => {
const maxInterval = 32000;
let nextInterval = 2000;
const startTime = Date.now();
let timeElapsed = 0;
return new Promise((resolve, reject) => {
const stop = arg => ((arg instanceof Error) ? reject(arg) : resolve(arg));
const next = () => {
if (Date.now() - startTime < timeout) {
setTimeout(fn.bind(null, next, stop), nextInterval);
if (timeElapsed < timeout) {
setTimeout(() => fn(next, stop), nextInterval);
timeElapsed += nextInterval;
nextInterval = Math.min(nextInterval + nextInterval, maxInterval);
} else {
reject(new Error('BACKOFF_TIMEOUT'));
......
......@@ -74,7 +74,8 @@ export default {
<tbody>
<repo-file-options
:is-mini="isMini"
:project-name="projectName"/>
:project-name="projectName"
/>
<repo-previous-directory
v-if="isRoot"
:prev-url="prevURL"
......@@ -84,7 +85,8 @@ export default {
:key="n"
:loading="loading"
:has-files="!!files.length"
:is-mini="isMini"/>
:is-mini="isMini"
/>
<repo-file
v-for="file in files"
:key="file.id"
......@@ -93,7 +95,8 @@ export default {
@linkclicked="fileClicked(file)"
:is-tree="isTree"
:has-files="!!files.length"
:active-file="activeFile"/>
:active-file="activeFile"
/>
</tbody>
</table>
</div>
......
......@@ -728,26 +728,41 @@
@mixin new-style-dropdown($selector: '') {
#{$selector}.dropdown-menu,
#{$selector}.dropdown-menu-nav {
.divider {
margin: 6px 0;
}
li {
padding: 0 1px;
&:hover {
background-color: transparent;
}
&.divider {
margin: 6px 0;
&:hover {
background-color: $dropdown-divider-color;
}
}
&.dropdown-header {
padding: 8px 16px;
}
a {
a,
button {
border-radius: 0;
padding: 8px 16px;
// make sure the text color is not overriden
&.text-danger {
@extend .text-danger;
}
&.is-focused,
&:hover,
&:active,
&:focus {
background-color: $gray-darker;
color: $gl-text-color;
}
&.is-active {
......
......@@ -50,6 +50,8 @@
}
.filtered-search-wrapper {
@include new-style-dropdown;
display: -webkit-flex;
display: flex;
......@@ -411,8 +413,6 @@
}
%filter-dropdown-item-btn-hover {
background-color: $dropdown-hover-color;
color: $white-light;
text-decoration: none;
outline: 0;
......@@ -422,8 +422,6 @@
}
.droplab-dropdown .dropdown-menu .filter-dropdown-item {
padding: 0;
.btn {
border: none;
width: 100%;
......
......@@ -132,6 +132,8 @@ ul.content-list {
}
.controls {
@include new-style-dropdown;
float: right;
> .control-text {
......
......@@ -18,7 +18,8 @@
background-color: $gray-lightest;
}
img.js-lazy-loaded {
img.js-lazy-loaded,
img.emoji {
min-width: inherit;
min-height: inherit;
background-color: inherit;
......
......@@ -103,7 +103,10 @@ $new-sidebar-collapsed-width: 50px;
&.sidebar-icons-only {
width: $new-sidebar-collapsed-width;
.nav-sidebar-inner-scroll {
overflow-x: hidden;
}
.badge,
.project-title {
......@@ -111,7 +114,11 @@ $new-sidebar-collapsed-width: 50px;
}
.nav-item-name {
opacity: 0;
display: none;
}
.sidebar-top-level-items > li > a {
min-height: 44px;
}
}
......
......@@ -260,7 +260,7 @@
padding-top: 10px;
}
&:not(.issue-boards-sidebar):not([data-signed-in]) {
&:not(.issue-boards-sidebar):not([data-signed-in]):not([data-always-show-toggle]) {
.issuable-sidebar-header {
display: none;
}
......
......@@ -35,18 +35,18 @@ module EventsHelper
[event.action_name, target].join(" ")
end
def event_filter_link(key, tooltip)
def event_filter_link(key, text, tooltip)
key = key.to_s
active = 'active' if @event_filter.active?(key)
link_opts = {
class: "event-filter-link",
class: "event-filter-link has-tooltip",
id: "#{key}_event_filter",
title: "Filter by #{tooltip.downcase}"
title: tooltip
}
content_tag :li, class: active do
link_to request.path, link_opts do
content_tag(:span, ' ' + tooltip)
content_tag(:span, ' ' + text)
end
end
end
......
......@@ -9,7 +9,7 @@ module BlobViewer
end
def manager_url
'https://getcomposer.com/'
'https://getcomposer.org/'
end
def package_name
......
......@@ -25,6 +25,11 @@ module Referable
to_reference(from_project)
end
included do
alias_method :non_referable_inspect, :inspect
alias_method :inspect, :referable_inspect
end
def referable_inspect
if respond_to?(:id)
"#<#{self.class.name} id:#{id} #{to_reference(full: true)}>"
......@@ -33,10 +38,6 @@ module Referable
end
end
def inspect
referable_inspect
end
module ClassMethods
# The character that prefixes the actual reference identifier
#
......
......@@ -60,7 +60,7 @@ class Project < ActiveRecord::Base
end
before_destroy :remove_private_deploy_keys
after_destroy :remove_pages
after_destroy -> { run_after_commit { remove_pages } }
# update visibility_level of forks
after_update :update_forks_visibility_level
......@@ -369,7 +369,10 @@ class Project < ActiveRecord::Base
state :failed
after_transition [:none, :finished, :failed] => :scheduled do |project, _|
project.run_after_commit { add_import_job }
project.run_after_commit do
job_id = add_import_job
update(import_jid: job_id) if job_id
end
end
after_transition started: :finished do |project, _|
......@@ -524,17 +527,26 @@ class Project < ActiveRecord::Base
def add_import_job
job_id =
if forked?
RepositoryForkWorker.perform_async(id, forked_from_project.repository_storage_path,
RepositoryForkWorker.perform_async(id,
forked_from_project.repository_storage_path,
forked_from_project.full_path,
self.namespace.full_path)
else
RepositoryImportWorker.perform_async(self.id)
end
log_import_activity(job_id)
job_id
end
def log_import_activity(job_id, type: :import)
job_type = type.to_s.capitalize
if job_id
Rails.logger.info "Import job started for #{full_path} with job ID #{job_id}"
Rails.logger.info("#{job_type} job scheduled for #{full_path} with job ID #{job_id}.")
else
Rails.logger.error "Import job failed to start for #{full_path}"
Rails.logger.error("#{job_type} job failed to create for #{full_path}.")
end
end
......@@ -543,6 +555,7 @@ class Project < ActiveRecord::Base
ProjectCacheWorker.perform_async(self.id)
end
update(import_error: nil)
remove_import_data
end
......@@ -1224,6 +1237,9 @@ class Project < ActiveRecord::Base
# TODO: what to do here when not using Legacy Storage? Do we still need to rename and delay removal?
def remove_pages
# Projects with a missing namespace cannot have their pages removed
return unless namespace
::Projects::UpdatePagesConfigurationService.new(self).execute
# 1. We rename pages to temporary directory
......
......@@ -47,11 +47,6 @@ class User < ActiveRecord::Base
devise :lockable, :recoverable, :rememberable, :trackable,
:validatable, :omniauthable, :confirmable, :registerable
# devise overrides #inspect, so we manually use the Referable one
def inspect
referable_inspect
end
# Override Devise::Models::Trackable#update_tracked_fields!
# to limit database writes to at most once every hour
def update_tracked_fields!(request)
......
......@@ -40,9 +40,10 @@
%li
= link_to 'Remove user', admin_user_path(user),
data: { confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?" },
class: 'text-danger',
method: :delete
%li
= link_to 'Remove user and contributions', admin_user_path(user, hard_delete: true),
data: { confirm: "USER #{user.name} WILL BE REMOVED! All issues, merge requests and comments authored by this user, and groups owned solely by them, will also be removed! Are you sure?" },
class: 'btn btn-remove btn-block',
class: 'text-danger',
method: :delete
......@@ -92,8 +92,7 @@
Update username
%hr
- if signup_enabled?
.row.prepend-top-default
.row.prepend-top-default
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0.danger-title
Remove account
......
%div{ class: container_class }
.nav-block.activity-filter-block.activities
.controls
= link_to project_path(@project, rss_url_options), title: "Subscribe", class: 'btn rss-btn has-tooltip' do
= link_to project_path(@project, rss_url_options), title: s_("ProjectActivityRSS|Subscribe"), class: 'btn rss-btn has-tooltip' do
= icon('rss')
= render 'shared/event_filter'
......
......@@ -3,16 +3,16 @@
.row-content-block.top-block.hidden-xs.white
.event-last-push
.event-last-push-text
%span You pushed to
%span= s_("LastPushEvent|You pushed to")
%strong
= link_to event.ref_name, project_commits_path(event.project, event.ref_name), class: 'ref-name'
- if event.project != @project
%span at
%span= s_("LastPushEvent|at")
%strong= link_to_project event.project
#{time_ago_with_tooltip(event.created_at)}
.pull-right
= link_to new_mr_path_from_push_event(event), title: "New merge request", class: "btn btn-info btn-sm" do
= link_to new_mr_path_from_push_event(event), title: _("New merge request"), class: "btn btn-info btn-sm" do
#{ _('Create merge request') }
......@@ -5,6 +5,6 @@
Blank
- Gitlab::ProjectTemplate.all.each do |template|
.btn
%input{ type: "radio", autocomplete: "off", name: "project_templates", id: template.name }
%input{ type: "radio", autocomplete: "off", name: "project[template_name]", id: template.name, value: template.name }
= custom_icon(template.logo)
= template.title
- @no_container = true
- if show_new_nav?
- add_to_breadcrumbs("Project", project_path(@project))
- add_to_breadcrumbs(_("Project"), project_path(@project))
- page_title "Activity"
- page_title _("Activity")
= render "projects/head"
= render 'projects/last_push'
......
......@@ -5,7 +5,7 @@
- notes = commit.notes
- note_count = notes.user.count
- cache_key = [project.full_path, commit.id, current_application_settings, note_count, @path.presence, current_controller?(:commits)]
- cache_key = [project.full_path, commit.id, current_application_settings, note_count, @path.presence, current_controller?(:commits), I18n.locale]
- cache_key.push(commit.status(ref)) if commit.status(ref)
= cache(cache_key, expires_in: 1.day) do
......
......@@ -25,7 +25,7 @@
.form-group
= f.label :template_project, class: 'label-light' do
Create from template
= link_to icon('question-circle'), help_page_path("public_access/public_access"), aria: { label: "What’s included in a template?" }, title: "What’s included in a template?", class: 'has-tooltip', data: { placement: 'top'}
= link_to icon('question-circle'), help_page_path("gitlab-basics/create-project"), target: '_blank', aria: { label: "What’s included in a template?" }, title: "What’s included in a template?", class: 'has-tooltip', data: { placement: 'top'}
%div
= render 'project_templates', f: f
.second-column
......
%ul.nav-links.event-filter.scrolling-tabs
= event_filter_link EventFilter.all, 'All'
= event_filter_link EventFilter.all, _('All'), s_('EventFilterBy|Filter by all')
- if event_filter_visible(:repository)
= event_filter_link EventFilter.push, 'Push events'
= event_filter_link EventFilter.push, _('Push events'), s_('EventFilterBy|Filter by push events')
- if event_filter_visible(:merge_requests)
= event_filter_link EventFilter.merged, 'Merge events'
= event_filter_link EventFilter.merged, _('Merge events'), s_('EventFilterBy|Filter by merge events')
- if event_filter_visible(:issues)
= event_filter_link EventFilter.issue, 'Issue events'
= event_filter_link EventFilter.issue, _('Issue events'), s_('EventFilterBy|Filter by issue events')
- if comments_visible?
= event_filter_link EventFilter.comments, 'Comments'
= event_filter_link EventFilter.team, 'Team'
= event_filter_link EventFilter.comments, _('Comments'), s_('EventFilterBy|Filter by comments')
= event_filter_link EventFilter.team, _('Team'), s_('EventFilterBy|Filter by team')
......@@ -13,4 +13,4 @@
%li
The import will time out after 15 minutes. For repositories that take longer, use a clone/push combination.
%li
To migrate an SVN repository, check out #{link_to "this document", help_page_path('workflow/importing/migrating_from_svn')}.
To migrate an SVN repository, check out #{link_to "this document", help_page_path('user/project/import/svn')}.
......@@ -57,7 +57,7 @@
%li.filter-dropdown-item{ data: { value: 'none' } }
%button.btn.btn-link
No Assignee
%li.divider
%li.divider.droplab-item-ignore
- if current_user
= render 'shared/issuable/user_dropdown_item',
user: current_user
......@@ -76,7 +76,7 @@
%li.filter-dropdown-item{ 'data-value' => 'started' }
%button.btn.btn-link
Started
%li.divider
%li.divider.droplab-item-ignore
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item
%button.btn.btn-link.js-data-value
......@@ -86,7 +86,7 @@
%li.filter-dropdown-item{ data: { value: 'none' } }
%button.btn.btn-link
No Label
%li.divider
%li.divider.droplab-item-ignore
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item
%button.btn.btn-link
......
- affix_offset = local_assigns.fetch(:affix_offset, "50")
- project = local_assigns[:project]
%aside.right-sidebar.js-right-sidebar{ data: { "offset-top" => affix_offset, "spy" => "affix" }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' }
%aside.right-sidebar.js-right-sidebar{ data: { "offset-top" => affix_offset, "spy" => "affix", "always-show-toggle" => true }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' }
.issuable-sidebar.milestone-sidebar
.block.milestone-progress.issuable-sidebar-header
%a.gutter-toggle.pull-right.js-sidebar-toggle{ role: "button", href: "#", "aria-label" => "Toggle sidebar" }
......
......@@ -24,10 +24,6 @@ class NamespacelessProjectDestroyWorker
unlink_fork(project) if project.forked?
# Override Project#remove_pages for this instance so it doesn't do anything
def project.remove_pages
end
project.destroy!
end
......
......@@ -5,14 +5,17 @@ class RepositoryForkWorker
include Gitlab::ShellAdapter
include DedicatedSidekiqQueue
sidekiq_options status_expiration: StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION
def perform(project_id, forked_from_repository_storage_path, source_path, target_path)
project = Project.find(project_id)
return unless start_fork(project)
Gitlab::Metrics.add_event(:fork_repository,
source_path: source_path,
target_path: target_path)
project = Project.find(project_id)
project.import_start
result = gitlab_shell.fork_repository(forked_from_repository_storage_path, source_path,
project.repository_storage_path, target_path)
raise ForkError, "Unable to fork project #{project_id} for repository #{source_path} -> #{target_path}" unless result
......@@ -33,6 +36,13 @@ class RepositoryForkWorker
private
def start_fork(project)
return true if project.import_start
Rails.logger.info("Project #{project.full_path} was in inconsistent state (#{project.import_status}) while forking.")
false
end
def fail_fork(project, message)
Rails.logger.error(message)
project.mark_import_as_failed(message)
......
......@@ -4,23 +4,18 @@ class RepositoryImportWorker
include Sidekiq::Worker
include DedicatedSidekiqQueue
sidekiq_options status_expiration: StuckImportJobsWorker::IMPORT_EXPIRATION
attr_accessor :project, :current_user
sidekiq_options status_expiration: StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION
def perform(project_id)
@project = Project.find(project_id)
@current_user = @project.creator
project = Project.find(project_id)
project.import_start
return unless start_import(project)
Gitlab::Metrics.add_event(:import_repository,
import_url: @project.import_url,
path: @project.full_path)
project.update_columns(import_jid: self.jid, import_error: nil)
import_url: project.import_url,
path: project.full_path)
result = Projects::ImportService.new(project, current_user).execute
result = Projects::ImportService.new(project, project.creator).execute
raise ImportError, result[:message] if result[:status] == :error
project.repository.after_import
......@@ -37,6 +32,13 @@ class RepositoryImportWorker
private
def start_import(project)
return true if project.import_start
Rails.logger.info("Project #{project.full_path} was in inconsistent state (#{project.import_status}) while importing.")
false
end
def fail_import(project, message)
project.mark_import_as_failed(message)
end
......
......@@ -2,36 +2,60 @@ class StuckImportJobsWorker
include Sidekiq::Worker
include CronjobQueue
IMPORT_EXPIRATION = 15.hours.to_i
IMPORT_JOBS_EXPIRATION = 15.hours.to_i
def perform
stuck_projects.find_in_batches(batch_size: 500) do |group|
projects_without_jid_count = mark_projects_without_jid_as_failed!
projects_with_jid_count = mark_projects_with_jid_as_failed!
Gitlab::Metrics.add_event(:stuck_import_jobs,
projects_without_jid_count: projects_without_jid_count,
projects_with_jid_count: projects_with_jid_count)
end
private
def mark_projects_without_jid_as_failed!
started_projects_without_jid.each do |project|
project.mark_import_as_failed(error_message)
end.count
end
def mark_projects_with_jid_as_failed!
completed_jids_count = 0
started_projects_with_jid.find_in_batches(batch_size: 500) do |group|
jids = group.map(&:import_jid)
# Find the jobs that aren't currently running or that exceeded the threshold.
completed_jids = Gitlab::SidekiqStatus.completed_jids(jids)
completed_jids = Gitlab::SidekiqStatus.completed_jids(jids).to_set
if completed_jids.any?
completed_ids = group.select { |project| completed_jids.include?(project.import_jid) }.map(&:id)
fail_batch!(completed_jids, completed_ids)
completed_jids_count += completed_jids.count
group.each do |project|
project.mark_import_as_failed(error_message) if completed_jids.include?(project.import_jid)
end
Rails.logger.info("Marked stuck import jobs as failed. JIDs: #{completed_jids.to_a.join(', ')}")
end
end
private
completed_jids_count
end
def stuck_projects
Project.select('id, import_jid').with_import_status(:started).where.not(import_jid: nil)
def started_projects
Project.with_import_status(:started)
end
def fail_batch!(completed_jids, completed_ids)
Project.where(id: completed_ids).update_all(import_status: 'failed', import_error: error_message)
def started_projects_with_jid
started_projects.where.not(import_jid: nil)
end
Rails.logger.info("Marked stuck import jobs as failed. JIDs: #{completed_jids.join(', ')}")
def started_projects_without_jid
started_projects.where(import_jid: nil)
end
def error_message
"Import timed out. Import took longer than #{IMPORT_EXPIRATION} seconds"
"Import timed out. Import took longer than #{IMPORT_JOBS_EXPIRATION} seconds"
end
end
---
title: Fix deleting GitLab Pages files when a project is removed
merge_request: 13631
author:
type: fixed
---
title: Don't escape html entities in InlineDiffMarkdownMarker
merge_request:
author:
---
title: allow all users to delete their account
merge_request: 13636
author: Jacopo Beschi @jacopo-beschi
type: changed
---
title: Fix external link to Composer website
merge_request:
author:
type: fixed
---
title: Commit rows would occasionally render with the wrong language
merge_request:
author:
type: fixed
---
title: Implement the Gitaly RefService::RefExists endpoint
merge_request: 13528
author: Andrew Newdigate
---
title: Fix project milestones import when projects belongs to a group
merge_request:
author:
---
title: Improve API pagination headers when no record found
merge_request: 13629
author: Jordan Patterson
type: fixed
---
title: Upgrade grape to 1.0
merge_request:
author:
type: other
......@@ -139,6 +139,8 @@ if Settings.ldap['enabled'] || Rails.env.test?
end
Settings.ldap['servers'].each do |key, server|
server = Settingslogic.new(server)
server['label'] ||= 'LDAP'
server['timeout'] ||= 10.seconds
server['block_auto_created_users'] = false if server['block_auto_created_users'].nil?
......@@ -165,6 +167,8 @@ if Settings.ldap['enabled'] || Rails.env.test?
MSG
Rails.logger.warn(message)
end
Settings.ldap['servers'][key] = server
end
end
......@@ -436,7 +440,9 @@ unless Settings.repositories.storages['default']
Settings.repositories.storages['default']['path'] ||= Settings.gitlab['user_home'] + '/repositories/'
end
Settings.repositories.storages.values.each do |storage|
Settings.repositories.storages.each do |key, storage|
storage = Settingslogic.new(storage)
# Expand relative paths
storage['path'] = Settings.absolute(storage['path'])
# Set failure defaults
......@@ -450,6 +456,8 @@ Settings.repositories.storages.values.each do |storage|
storage['failure_reset_time'] = storage['failure_reset_time'].to_i
# We might want to have a timeout shorter than 1 second.
storage['storage_timeout'] = storage['storage_timeout'].to_f
Settings.repositories.storages[key] = storage
end
#
......
......@@ -102,7 +102,7 @@ Manage your [repositories](user/project/repository/index.md) from the UI (user i
### Migrate and import your projects from other platforms
- [Importing to GitLab](workflow/importing/README.md): Import your projects from GitHub, Bitbucket, GitLab.com, FogBugz and SVN into GitLab.
- [Importing to GitLab](user/project/import/index.md): Import your projects from GitHub, Bitbucket, GitLab.com, FogBugz and SVN into GitLab.
- [Migrating from SVN](workflow/importing/migrating_from_svn.md): Convert a SVN repository to Git and GitLab.
### Continuous Integration, Delivery, and Deployment
......
# Group-level Variables API
> [Introduced][ce-34519] in GitLab 9.5
## List group variables
Get list of a group's variables.
......@@ -123,3 +125,5 @@ DELETE /groups/:id/variables/:key
```
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/groups/1/variables/VARIABLE_1"
```
[ce-34519]: https://gitlab.com/gitlab-org/gitlab-ce/issues/34519
......@@ -150,4 +150,4 @@ Example response:
}
```
[ce-[ce-29508]: https://gitlab.com/gitlab-org/gitlab-ce/issues/29508]: https://gitlab.com/gitlab-org/gitlab-ce/issues/29508
[ce-29508]: https://gitlab.com/gitlab-org/gitlab-ce/issues/29508
......@@ -17,8 +17,8 @@ Explore GitLab's supported [authentications methods](../topics/authentication/in
| Article title | Category | Publishing date |
| :------------ | :------: | --------------: |
| **LDAP** |
| [How to configure LDAP with GitLab CE](how_to_configure_ldap_gitlab_ce/index.md)| Admin guide | 2017/05/03 |
| [How to configure LDAP with GitLab EE](https://docs.gitlab.com/ee/articles/how_to_configure_ldap_gitlab_ee/) | Admin guide | 2017/05/03 |
| [How to configure LDAP with GitLab CE](how_to_configure_ldap_gitlab_ce/index.md)| Admin guide | 2017-05-03 |
| [How to configure LDAP with GitLab EE](https://docs.gitlab.com/ee/articles/how_to_configure_ldap_gitlab_ee/) | Admin guide | 2017-05-03 |
## Build, test, and deploy with GitLab CI/CD
......@@ -27,17 +27,17 @@ Build, test, and deploy the software you develop with [GitLab CI/CD](../ci/READM
| Article title | Category | Publishing date |
| :------------ | :------: | --------------: |
| [How to deploy Maven projects to Artifactory with GitLab CI/CD](artifactory_and_gitlab/index.md) | Tutorial | 2017-08-15 |
| [Making CI Easier with GitLab](https://about.gitlab.com/2017/07/13/making-ci-easier-with-gitlab/) | Concepts | 2017/07/13 |
| [Dockerizing GitLab Review Apps](https://about.gitlab.com/2017/07/11/dockerizing-review-apps/) | Concepts | 2017/07/11 |
| [Continuous Integration: From Jenkins to GitLab Using Docker](https://about.gitlab.com/2017/07/27/docker-my-precious/) | Concepts | 2017/07/27 |
| [Continuous Delivery of a Spring Boot application with GitLab CI and Kubernetes](https://about.gitlab.com/2016/12/14/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/) | Tutorial | 2016/12/14 |
| [Setting up GitLab CI for Android projects](https://about.gitlab.com/2016/11/30/setting-up-gitlab-ci-for-android-projects/) | Tutorial | 2016/11/30 |
| [Automated Debian Package Build with GitLab CI](https://about.gitlab.com/2016/10/12/automated-debian-package-build-with-gitlab-ci/) | Tutorial | 2016/10/12 |
| [Building an Elixir Release into a Docker image using GitLab CI](https://about.gitlab.com/2016/08/11/building-an-elixir-release-into-docker-image-using-gitlab-ci-part-1/) | Tutorial | 2016/08/11 |
| [Continuous Delivery with GitLab and Convox](https://about.gitlab.com/2016/06/09/continuous-delivery-with-gitlab-and-convox/) | Technical overview | 2016/06/09 |
| [GitLab Container Registry](https://about.gitlab.com/2016/05/23/gitlab-container-registry/) | Technical overview | 2016/05/23 |
| [How to use GitLab CI and MacStadium to build your macOS or iOS projects](https://about.gitlab.com/2017/05/15/how-to-use-macstadium-and-gitlab-ci-to-build-your-macos-or-ios-projects/) | Technical overview | 2017/05/15 |
| [Setting up GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/) | Tutorial | 2016/03/10 |
| [Making CI Easier with GitLab](https://about.gitlab.com/2017/07/13/making-ci-easier-with-gitlab/) | Concepts | 2017-07-13 |
| [Dockerizing GitLab Review Apps](https://about.gitlab.com/2017/07/11/dockerizing-review-apps/) | Concepts | 2017-07-11 |
| [Continuous Integration: From Jenkins to GitLab Using Docker](https://about.gitlab.com/2017/07/27/docker-my-precious/) | Concepts | 2017-07-27 |
| [Continuous Delivery of a Spring Boot application with GitLab CI and Kubernetes](https://about.gitlab.com/2016/12/14/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/) | Tutorial | 2016-12-14 |
| [Setting up GitLab CI for Android projects](https://about.gitlab.com/2016/11/30/setting-up-gitlab-ci-for-android-projects/) | Tutorial | 2016-11-30 |
| [Automated Debian Package Build with GitLab CI](https://about.gitlab.com/2016/10/12/automated-debian-package-build-with-gitlab-ci/) | Tutorial | 2016-10-12 |
| [Building an Elixir Release into a Docker image using GitLab CI](https://about.gitlab.com/2016/08/11/building-an-elixir-release-into-docker-image-using-gitlab-ci-part-1/) | Tutorial | 2016-08-11 |
| [Continuous Delivery with GitLab and Convox](https://about.gitlab.com/2016/06/09/continuous-delivery-with-gitlab-and-convox/) | Technical overview | 2016-06-09 |
| [GitLab Container Registry](https://about.gitlab.com/2016/05/23/gitlab-container-registry/) | Technical overview | 2016-05-23 |
| [How to use GitLab CI and MacStadium to build your macOS or iOS projects](https://about.gitlab.com/2017/05/15/how-to-use-macstadium-and-gitlab-ci-to-build-your-macos-or-ios-projects/) | Technical overview | 2017-05-15 |
| [Setting up GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/) | Tutorial | 2016-03-10 |
## Git
......@@ -45,10 +45,11 @@ Learn how to use [Git with GitLab](../topics/git/index.md):
| Article title | Category | Publishing date |
| :------------ | :------: | --------------: |
| [Why Git is Worth the Learning Curve](https://about.gitlab.com/2017/05/17/learning-curve-is-the-biggest-challenge-developers-face-with-git/) | Concepts | 2017/05/17 |
| [How to install Git](how_to_install_git/index.md) | Tutorial | 2017/05/15 |
| [Getting Started with Git LFS](https://about.gitlab.com/2017/01/30/getting-started-with-git-lfs-tutorial/) | Tutorial | 2017/01/30 |
| [Git Tips & Tricks](https://about.gitlab.com/2016/12/08/git-tips-and-tricks/) | Technical overview | 2016/12/08 |
| [Numerous _undo_ possibilities in Git](numerous_undo_possibilities_in_git/index.md) | Tutorial | 2017-08-17 |
| [Why Git is Worth the Learning Curve](https://about.gitlab.com/2017/05/17/learning-curve-is-the-biggest-challenge-developers-face-with-git/) | Concepts | 2017-05-17 |
| [How to install Git](how_to_install_git/index.md) | Tutorial | 2017-05-15 |
| [Getting Started with Git LFS](https://about.gitlab.com/2017/01/30/getting-started-with-git-lfs-tutorial/) | Tutorial | 2017-01-30 |
| [Git Tips & Tricks](https://about.gitlab.com/2016/12/08/git-tips-and-tricks/) | Technical overview | 2016-12-08 |
## GitLab Pages
......@@ -57,21 +58,21 @@ Learn how to deploy a static website with [GitLab Pages](../user/project/pages/i
| Article title | Category | Publishing date |
| :------------ | :------: | --------------: |
| **Series: GitLab Pages from A to Z:** |
| [- Part 1: Static sites and GitLab Pages domains](../user/project/pages/getting_started_part_one.md)| User guide | 2017/02/22 |
| [- Part 2: Quick start guide - Setting up GitLab Pages](../user/project/pages/getting_started_part_two.md)| User guide | 2017/02/22 |
| [- Part 3: Setting Up Custom Domains - DNS Records and SSL/TLS Certificates](../user/project/pages/getting_started_part_three.md)| User guide | 2017/02/22 |
| [- Part 4: Creating and tweaking `.gitlab-ci.yml` for GitLab Pages](../user/project/pages/getting_started_part_four.md)| User guide | 2017/02/22 |
| [Setting up GitLab Pages with CloudFlare Certificates](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/) | Tutorial | 2017/02/07 |
| [Building a new GitLab Docs site with Nanoc, GitLab CI, and GitLab Pages](https://about.gitlab.com/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/) | Tutorial | 2016/12/07 |
| [Publish Code Coverage Report with GitLab Pages](https://about.gitlab.com/2016/11/03/publish-code-coverage-report-with-gitlab-pages/) | Tutorial | 2016/11/03 |
| [GitLab CI: Deployment & Environments](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/) | Tutorial | 2016/08/26 |
| [Posting to your GitLab Pages blog from iOS](https://about.gitlab.com/2016/08/19/posting-to-your-gitlab-pages-blog-from-ios/) | Tutorial | 2016/08/19 |
| [- Part 1: Static sites and GitLab Pages domains](../user/project/pages/getting_started_part_one.md)| User guide | 2017-02-22 |
| [- Part 2: Quick start guide - Setting up GitLab Pages](../user/project/pages/getting_started_part_two.md)| User guide | 2017-02-22 |
| [- Part 3: Setting Up Custom Domains - DNS Records and SSL/TLS Certificates](../user/project/pages/getting_started_part_three.md)| User guide | 2017-02-22 |
| [- Part 4: Creating and tweaking `.gitlab-ci.yml` for GitLab Pages](../user/project/pages/getting_started_part_four.md)| User guide | 2017-02-22 |
| [Setting up GitLab Pages with CloudFlare Certificates](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/) | Tutorial | 2017-02-07 |
| [Building a new GitLab Docs site with Nanoc, GitLab CI, and GitLab Pages](https://about.gitlab.com/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/) | Tutorial | 2016-12-07 |
| [Publish Code Coverage Report with GitLab Pages](https://about.gitlab.com/2016/11/03/publish-code-coverage-report-with-gitlab-pages/) | Tutorial | 2016-11-03 |
| [GitLab CI: Deployment & Environments](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/) | Tutorial | 2016-08-26 |
| [Posting to your GitLab Pages blog from iOS](https://about.gitlab.com/2016/08/19/posting-to-your-gitlab-pages-blog-from-ios/) | Tutorial | 2016-08-19 |
| **Series: Static Site Generator:** |
| [- Part 1: Dynamic vs Static Websites](https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/) | Tutorial | 2016/06/03 |
| [- Part 2: Modern Static Site Generators](https://about.gitlab.com/2016/06/10/ssg-overview-gitlab-pages-part-2/) | Tutorial | 2016/06/10 |
| [- Part 3: Build any SSG site with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/) | Tutorial | 2016/06/17 |
| [Securing your GitLab Pages with TLS and Let's Encrypt](https://about.gitlab.com/2016/04/11/tutorial-securing-your-gitlab-pages-with-tls-and-letsencrypt/) | Tutorial | 2016/04/11 |
| [Hosting on GitLab.com with GitLab Pages](https://about.gitlab.com/2016/04/07/gitlab-pages-setup/) | Tutorial | 2016/04/07 |
| [- Part 1: Dynamic vs Static Websites](https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/) | Tutorial | 2016-06-03 |
| [- Part 2: Modern Static Site Generators](https://about.gitlab.com/2016/06/10/ssg-overview-gitlab-pages-part-2/) | Tutorial | 2016-06-10 |
| [- Part 3: Build any SSG site with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/) | Tutorial | 2016-06-17 |
| [Securing your GitLab Pages with TLS and Let's Encrypt](https://about.gitlab.com/2016/04/11/tutorial-securing-your-gitlab-pages-with-tls-and-letsencrypt/) | Tutorial | 2016-04-11 |
| [Hosting on GitLab.com with GitLab Pages](https://about.gitlab.com/2016/04/07/gitlab-pages-setup/) | Tutorial | 2016-04-07 |
## Install and maintain GitLab
......@@ -79,10 +80,10 @@ Install, upgrade, integrate, migrate to GitLab:
| Article title | Category | Publishing date |
| :------------ | :------: | --------------: |
| [Video Tutorial: Idea to Production on Google Container Engine (GKE)](https://about.gitlab.com/2017/01/23/video-tutorial-idea-to-production-on-google-container-engine-gke/) | Tutorial | 2017/01/23 |
| [How to Setup a GitLab Instance on Microsoft Azure](https://about.gitlab.com/2016/07/13/how-to-setup-a-gitlab-instance-on-microsoft-azure/) | Tutorial | 2016/07/13 |
| [Get started with OpenShift Origin 3 and GitLab](openshift_and_gitlab/index.md) | Tutorial | 2016/06/28 |
| [Getting started with GitLab and DigitalOcean](https://about.gitlab.com/2016/04/27/getting-started-with-gitlab-and-digitalocean/) | Tutorial | 2016/04/27 |
| [Video Tutorial: Idea to Production on Google Container Engine (GKE)](https://about.gitlab.com/2017/01/23/video-tutorial-idea-to-production-on-google-container-engine-gke/) | Tutorial | 2017-01-23 |
| [How to Setup a GitLab Instance on Microsoft Azure](https://about.gitlab.com/2016/07/13/how-to-setup-a-gitlab-instance-on-microsoft-azure/) | Tutorial | 2016-07-13 |
| [Get started with OpenShift Origin 3 and GitLab](openshift_and_gitlab/index.md) | Tutorial | 2016-06-28 |
| [Getting started with GitLab and DigitalOcean](https://about.gitlab.com/2016/04/27/getting-started-with-gitlab-and-digitalocean/) | Tutorial | 2016-04-27 |
## Software development
......@@ -90,25 +91,25 @@ Explore the best of GitLab's software development's capabilities:
| Article title | Category | Publishing date |
| :------------ | :------: | --------------: |
| [Making CI Easier with GitLab](https://about.gitlab.com/2017/07/13/making-ci-easier-with-gitlab/) | Concepts | 2017/07/13 |
| [From 2/3 of the Self-Hosted Git Market, to the Next-Generation CI System, to Auto DevOps](https://about.gitlab.com/2017/06/29/whats-next-for-gitlab-ci/)| Concepts | 2017/06/29 |
| [Fast and Natural Continuous Integration with GitLab CI](https://about.gitlab.com/2017/05/22/fast-and-natural-continuous-integration-with-gitlab-ci/) | Concepts | 2017/05/22 |
| [Demo: Auto-Deploy from GitLab to an OpenShift Container Cluster](https://about.gitlab.com/2017/05/16/devops-containers-gitlab-openshift/) | Technical overview | 2017/05/16 |
| [Demo: GitLab Service Desk](https://about.gitlab.com/2017/05/09/demo-service-desk/) | Feature highlight | 2017/05/09 |
| [Demo: Mapping Work Versus Time, With Burndown Charts](https://about.gitlab.com/2017/04/25/mapping-work-to-do-versus-time-with-burndown-charts/) | Feature highlight | 2017/04/25 |
| [Demo: Cloud Native Development with GitLab](https://about.gitlab.com/2017/04/18/cloud-native-demo/) | Feature highlight | 2017/04/18 |
| [Demo: Mastering Code Review With GitLab](https://about.gitlab.com/2017/03/17/demo-mastering-code-review-with-gitlab/) | Feature highlight | 2017/03/17 |
| [In 13 minutes from Kubernetes to a complete application development tool](https://about.gitlab.com/2016/11/14/idea-to-production/) | Technical overview | 2016/11/14 |
| [GitLab Workflow, an Overview](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/) | Technical overview | 2016/10/25 |
| [Trends in Version Control Land: Microservices](https://about.gitlab.com/2016/08/16/trends-in-version-control-land-microservices/) | Concepts | 2016/08/16 |
| [Continuous Integration, Delivery, and Deployment with GitLab](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/) | Concepts | 2016/08/05 |
| [Trends in Version Control Land: Innersourcing](https://about.gitlab.com/2016/07/07/trends-version-control-innersourcing/) | Concepts | 2016/07/07 |
| [Tutorial: It's all connected in GitLab](https://about.gitlab.com/2016/03/08/gitlab-tutorial-its-all-connected/) | Technical overview | 2016/03/08 |
| [Making CI Easier with GitLab](https://about.gitlab.com/2017/07/13/making-ci-easier-with-gitlab/) | Concepts | 2017-07-13 |
| [From 2/3 of the Self-Hosted Git Market, to the Next-Generation CI System, to Auto DevOps](https://about.gitlab.com/2017/06/29/whats-next-for-gitlab-ci/)| Concepts | 2017-06-29 |
| [Fast and Natural Continuous Integration with GitLab CI](https://about.gitlab.com/2017/05/22/fast-and-natural-continuous-integration-with-gitlab-ci/) | Concepts | 2017-05-22 |
| [Demo: Auto-Deploy from GitLab to an OpenShift Container Cluster](https://about.gitlab.com/2017/05/16/devops-containers-gitlab-openshift/) | Technical overview | 2017-05-16 |
| [Demo: GitLab Service Desk](https://about.gitlab.com/2017/05/09/demo-service-desk/) | Feature highlight | 2017-05-09 |
| [Demo: Mapping Work Versus Time, With Burndown Charts](https://about.gitlab.com/2017/04/25/mapping-work-to-do-versus-time-with-burndown-charts/) | Feature highlight | 2017-04-25 |
| [Demo: Cloud Native Development with GitLab](https://about.gitlab.com/2017/04/18/cloud-native-demo/) | Feature highlight | 2017-04-18 |
| [Demo: Mastering Code Review With GitLab](https://about.gitlab.com/2017/03/17/demo-mastering-code-review-with-gitlab/) | Feature highlight | 2017-03-17 |
| [In 13 minutes from Kubernetes to a complete application development tool](https://about.gitlab.com/2016/11/14/idea-to-production/) | Technical overview | 2016-11-14 |
| [GitLab Workflow, an Overview](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/) | Technical overview | 2016-10-25 |
| [Trends in Version Control Land: Microservices](https://about.gitlab.com/2016/08/16/trends-in-version-control-land-microservices/) | Concepts | 2016-08-16 |
| [Continuous Integration, Delivery, and Deployment with GitLab](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/) | Concepts | 2016-08-05 |
| [Trends in Version Control Land: Innersourcing](https://about.gitlab.com/2016/07/07/trends-version-control-innersourcing/) | Concepts | 2016-07-07 |
| [Tutorial: It's all connected in GitLab](https://about.gitlab.com/2016/03/08/gitlab-tutorial-its-all-connected/) | Technical overview | 2016-03-08 |
## Technologies
| Article title | Category | Publishing date |
| :------------ | :------: | --------------: |
| [Why we are not leaving the cloud](https://about.gitlab.com/2017/03/02/why-we-are-not-leaving-the-cloud/) | Concepts | 2017/03/02 |
| [Why We Chose Vue.js](https://about.gitlab.com/2016/10/20/why-we-chose-vue/) | Concepts | 2016/10/20 |
| [Markdown Kramdown Tips & Tricks](https://about.gitlab.com/2016/07/19/markdown-kramdown-tips-and-tricks/) | Technical overview | 2016/07/19 |
| [Why we are not leaving the cloud](https://about.gitlab.com/2017/03/02/why-we-are-not-leaving-the-cloud/) | Concepts | 2017-03-02 |
| [Why We Chose Vue.js](https://about.gitlab.com/2016/10/20/why-we-chose-vue/) | Concepts | 2016-10-20 |
| [Markdown Kramdown Tips & Tricks](https://about.gitlab.com/2016/07/19/markdown-kramdown-tips-and-tricks/) | Technical overview | 2016-07-19 |
# Numerous undo possibilities in Git
> **Article [Type](../../development/writing_documentation.md#types-of-technical-articles):** tutorial ||
> **Level:** intermediary ||
> **Author:** [Crt Mori](https://gitlab.com/Letme) ||
> **Publication date:** 2017/08/17
## Introduction
In this tutorial, we will show you different ways of undoing your work in Git, for which
we will assume you have a basic working knowledge of. Check GitLab's
[Git documentation](../../topics/git/index.md#git-documentation) for reference.
Also, we will only provide some general info of the commands, which is enough
to get you started for the easy cases/examples, but for anything more advanced please refer to the [Git book](https://git-scm.com/book/en/v2).
We will explain a few different techniques to undo your changes based on the stage
of the change in your current development. Also, keep in mind that [nothing in
Git is really deleted.][git-autoclean-ref]
This means that until Git automatically cleans detached commits (which cannot be
accessed by branch or tag) it will be possible to view them with `git reflog` command
and access them with direct commit-id. Read more about _[redoing the undo](#redoing-the-undo)_ on the section below.
This guide is organized depending on the [stage of development][git-basics]
where you want to undo your changes from and if they were shared with other developers
or not. Because Git is tracking changes a created or edited file is in the unstaged state
(if created it is untracked by Git). After you add it to a repository (`git add`) you put
a file into the **staged** state, which is then committed (`git commit`) to your
local repository. After that, file can be shared with other developers (`git push`).
Here's what we'll cover in this tutorial:
- [Undo local changes](#undo-local-changes) which were not pushed to remote repository
- Before you commit, in both unstaged and staged state
- After you committed
- Undo changes after they are pushed to remote repository
- [Without history modification](#undo-remote-changes-without-changing-history) (preferred way)
- [With history modification](#undo-remote-changes-with-modifying-history) (requires
coordination with team and force pushes).
- [Usecases when modifying history is generally acceptable](#where-modifying-history-is-generally-acceptable)
- [How to modify history](#how-modifying-history-is-done)
- [How to remove sensitive information from repository](#deleting-sensitive-information-from-commits)
### Branching strategy
[Git][git-official] is a de-centralized version control system, which means that beside regular
versioning of the whole repository, it has possibilities to exchange changes
with other repositories. To avoid chaos with
[multiple sources of truth][git-distributed], various
development workflows have to be followed, and it depends on your internal
workflow how certain changes or commits can be undone or changed.
[GitLab Flow][gitlab-flow] provides a good
balance between developers clashing with each other while
developing the same feature and cooperating seamlessly, but it does not enable
joined development of the same feature by multiple developers by default.
When multiple developers develop the same feature on the same branch, clashing
with every synchronization is unavoidable, but a proper or chosen Git Workflow will
prevent that anything is lost or out of sync when feature is complete. You can also
read through this blog post on [Git Tips & Tricks][gitlab-git-tips-n-tricks]
to learn how to easily **do** things in Git.
## Undo local changes
Until you push your changes to any remote repository, they will only affect you.
That broadens your options on how to handle undoing them. Still, local changes
can be on various stages and each stage has a different approach on how to tackle them.
### Unstaged local changes (before you commit)
When a change is made, but it is not added to the staged tree, Git itself
proposes a solution to discard changes to certain file.
Suppose you edited a file to change the content using your favorite editor:
```shell
vim <file>
```
Since you did not `git add <file>` to staging, it should be under unstaged files (or
untracked if file was created). You can confirm that with:
```shell
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: <file>
no changes added to commit (use "git add" and/or "git commit -a")
```
At this point there are 3 options to undo the local changes you have:
- Discard all local changes, but save them for possible re-use [later](#quickly-save-local-changes)
```shell
git stash
```
- Discarding local changes (permanently) to a file
```shell
git checkout -- <file>
```
- Discard all local changes to all files permanently
```shell
git reset --hard
```
Before executing `git reset --hard`, keep in mind that there is also a way to
just temporary store the changes without committing them using `git stash`.
This command resets the changes to all files, but it also saves them in case
you would like to apply them at some later time. You can read more about it in
[section below](#quickly-save-local-changes).
### Quickly save local changes
You are working on a feature when a boss drops by with an urgent task. Since your
feature is not complete, but you need to swap to another branch, you can use
`git stash` to save what you had done, swap to another branch, commit, push,
test, then get back to previous feature branch, do `git stash pop` and continue
where you left.
The example above shows that discarding all changes is not always a preferred option,
but Git provides a way to save them for later, while resetting the repository to state without
them. This is achieved by Git stashing command `git stash`, which in fact saves your
current work and runs `git reset --hard`, but it also has various
additional options like:
- `git stash save`, which enables including temporary commit message, which will help you identify changes, among with other options
- `git stash list`, which lists all previously stashed commits (yes, there can be more) that were not `pop`ed
- `git stash pop`, which redoes previously stashed changes and removes them from stashed list
- `git stash apply`, which redoes previously stashed changes, but keeps them on stashed list
### Staged local changes (before you commit)
Let's say you have added some files to staging, but you want to remove them from the
current commit, yet you want to retain those changes - just move them outside
of the staging tree. You also have an option to discard all changes with
`git reset --hard` or think about `git stash` [as described earlier.](#quickly-save-local-changes)
Lets start the example by editing a file, with your favorite editor, to change the
content and add it to staging
```
vim <file>
git add <file>
```
The file is now added to staging as confirmed by `git status` command:
```shell
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: <file>
```
Now you have 4 options to undo your changes:
- Unstage the file to current commit (HEAD)
```shell
git reset HEAD <file>
```
- Unstage everything - retain changes
```shell
git reset
```
- Discard all local changes, but save them for [later](#quickly-save-local-changes)
```shell
git stash
```
- Discard everything permanently
```shell
git reset --hard
```
## Committed local changes
Once you commit, your changes are recorded by the version control system.
Because you haven't pushed to your remote repository yet, your changes are
still not public (or shared with other developers). At this point, undoing
things is a lot easier, we have quite some workaround options. Once you push
your code, you'll have less options to troubleshoot your work.
### Without modifying history
Through the development process some of the previously committed changes do not
fit anymore in the end solution, or are source of the bugs. Once you find the
commit which triggered bug, or once you have a faulty commit, you can simply
revert it with `git revert commit-id`. This command inverts (swaps) the additions and
deletions in that commit, so that it does not modify history. Retaining history
can be helpful in future to notice that some changes have been tried
unsuccessfully in the past.
In our example we will assume there are commits `A`,`B`,`C`,`D`,`E` committed in this order: `A-B-C-D-E`,
and `B` is the commit you want to undo. There are many different ways to identify commit
`B` as bad, one of them is to pass a range to `git bisect` command. The provided range includes
last known good commit (we assume `A`) and first known bad commit (where bug was detected - we will assume `E`).
```shell
git bisect A..E
```
Bisect will provide us with commit-id of the middle commit to test, and then guide us
through simple bisection process. You can read more about it [in official Git Tools][git-debug]
In our example we will end up with commit `B`, that introduced bug/error. We have
4 options on how to remove it (or part of it) from our repository.
- Undo (swap additions and deletions) changes introduced by commit `B`.
```shell
git revert commit-B-id
```
- Undo changes on a single file or directory from commit `B`, but retain them in the staged state
```shell
git checkout commit-B-id <file>
```
- Undo changes on a single file or directory from commit `B`, but retain them in the unstaged state
```shell
git reset commit-B-id <file>
```
- There is one command we also must not forget: **creating a new branch**
from the point where changes are not applicable or where the development has hit a
dead end. For example you have done commits `A-B-C-D` on your feature-branch
and then you figure `C` and `D` are wrong. At this point you either reset to `B`
and do commit `F` (which will cause problems with pushing and if forced pushed also with other developers)
since branch now looks `A-B-F`, which clashes with what other developers have locally (you will
[change history](#with-history-modification)), or you simply checkout commit `B` create
a new branch and do commit `F`. In the last case, everyone else can still do their work while you
have your new way to get it right and merge it back in later. Alternatively, with GitLab,
you can [cherry-pick](../../user/project/merge_requests/cherry_pick_changes.md#cherry-picking-a-commit)
that commit into a new merge request.
![Create a new branch to avoid clashing](img/branching.png)
```shell
git checkout commit-B-id
git checkout -b new-path-of-feature
# Create <commit F>
git commit -a
```
### With history modification
There is one command for history modification and that is `git rebase`. Command
provides interactive mode (`-i` flag) which enables you to:
- **reword** commit messages (there is also `git commit --amend` for editing
last commit message)
- **edit** the commit content (changes introduced by commit) and message
- **squash** multiple commits into a single one, and have a custom or aggregated
commit message
- **drop** commits - simply delete them
- and few more options
Let us check few examples. Again there are commits `A-B-C-D` where you want to
delete commit `B`.
- Rebase the range from current commit D to A:
```shell
git rebase -i A
```
- Command opens your favorite editor where you write `drop` in front of commit
`B`, but you leave default `pick` with all other commits. Save and exit the
editor to perform a rebase. Remember: if you want to cancel delete whole
file content before saving and exiting the editor
In case you want to modify something introduced in commit `B`.
- Rebase the range from current commit D to A:
```shell
git rebase -i A
```
- Command opens your favorite text editor where you write `edit` in front of commit
`B`, but leave default `pick` with all other commits. Save and exit the editor to
perform a rebase
- Now do your edits and commit changes:
```shell
git commit -a
```
You can find some more examples in [below section where we explain how to modify
history](#how-modifying-history-is-done)
### Redoing the Undo
Sometimes you realize that the changes you undid were useful and you want them
back. Well because of first paragraph you are in luck. Command `git reflog`
enables you to *recall* detached local commits by referencing or applying them
via commit-id. Although, do not expect to see really old commits in reflog, because
Git regularly [cleans the commits which are *unreachable* by branches or tags][git-autoclean-ref].
To view repository history and to track older commits you can use below command:
```shell
$ git reflog show
# Example output:
b673187 HEAD@{4}: merge 6e43d5987921bde189640cc1e37661f7f75c9c0b: Merge made by the 'recursive' strategy.
eb37e74 HEAD@{5}: rebase -i (finish): returning to refs/heads/master
eb37e74 HEAD@{6}: rebase -i (pick): Commit C
97436c6 HEAD@{7}: rebase -i (start): checkout 97436c6eec6396c63856c19b6a96372705b08b1b
...
88f1867 HEAD@{12}: commit: Commit D
97436c6 HEAD@{13}: checkout: moving from 97436c6eec6396c63856c19b6a96372705b08b1b to test
97436c6 HEAD@{14}: checkout: moving from master to 97436c6
05cc326 HEAD@{15}: commit: Commit C
6e43d59 HEAD@{16}: commit: Commit B
```
Output of command shows repository history. In first column there is commit-id,
in following column, number next to `HEAD` indicates how many commits ago something
was made, after that indicator of action that was made (commit, rebase, merge, ...)
and then on end description of that action.
## Undo remote changes without changing history
This topic is roughly same as modifying committed local changes without modifying
history. **It should be the preferred way of undoing changes on any remote repository
or public branch.** Keep in mind that branching is the best solution when you want
to retain the history of faulty development, yet start anew from certain point. Branching
enables you to include the existing changes in new development (by merging) and
it also provides a clear timeline and development structure.
![Use revert to keep branch flowing](img/revert.png)
If you want to revert changes introduced in certain `commit-id` you can simply
revert that `commit-id` (swap additions and deletions) in newly created commit:
You can do this with
```shell
git revert commit-id
```
or creating a new branch:
```shell
git checkout commit-id
git checkout -b new-path-of-feature
```
## Undo remote changes with modifying history
This is useful when you want to *hide* certain things - like secret keys,
passwords, SSH keys, etc. It is and should not be used to hide mistakes, as
it will make it harder to debug in case there are some other bugs. The main
reason for this is that you loose the real development progress. **Also keep in
mind that, even with modified history, commits are just detached and can still be
accessed through commit-id** - at least until all repositories perform
the cleanup of detached commits (happens automatically).
![Modifying history causes problems on remote branch](img/rebase_reset.png)
### Where modifying history is generally acceptable
Modified history breaks the development chain of other developers, as changed
history does not have matching commits'ids. For that reason it should not
be used on any public branch or on branch that *might* be used by other
developers. When contributing to big open source repositories (e.g. [GitLab CE][gitlab-ce]),
it is acceptable to *squash* commits into a single one, to present
a nicer history of your contribution.
Keep in mind that this also removes the comments attached to certain commits
in merge requests, so if you need to retain traceability in GitLab, then
modifying history is not acceptable.
A feature-branch of a merge request is a public branch and might be used by
other developers, but project process and rules might allow or require
you to use `git rebase` (command that changes history) to reduce number of
displayed commits on target branch after reviews are done (for example
GitLab). There is a `git merge --squash` command which does exactly that
(squashes commits on feature-branch to a single commit on target branch
at merge).
>**Note:**
Never modify the commit history of `master` or shared branch
### How modifying history is done
After you know what you want to modify (how far in history or how which range of
old commits), use `git rebase -i commit-id`. This command will then display all the commits from
current version to chosen commit-id and allow modification, squashing, deletion
of that commits.
```shell
$ git rebase -i commit1-id..commit3-id
pick <commit1-id> <commit1-commit-message>
pick <commit2-id> <commit2-commit-message>
pick <commit3-id> <commit3-commit-message>
# Rebase commit1-id..commit3-id onto <commit4-id> (3 command(s))
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
```
>**Note:**
It is important to notice that comment from the output clearly states that, if
you decide to abort, then do not just close your editor (as that will in-fact
modify history), but remove all uncommented lines and save.
That is one of the reasons why `git rebase` should be used carefully on
shared and remote branches. But don't worry, there will be nothing broken until
you push back to the remote repository (so you can freely explore the
different outcomes locally).
```shell
# Modify history from commit-id to HEAD (current commit)
git rebase -i commit-id
```
### Deleting sensitive information from commits
Git also enables you to delete sensitive information from your past commits and
it does modify history in the progress. That is why we have included it in this
section and not as a standalone topic. To do so, you should run the
`git filter-branch`, which enables you to rewrite history with
[certain filters][git-filters-manual].
This command uses rebase to modify history and if you want to remove certain
file from history altogether use:
```shell
git filter-branch --tree-filter 'rm filename' HEAD
```
Since `git filter-branch` command might be slow on big repositories, there are
tools that can use some of Git specifics to enable faster execution of common
tasks (which is exactly what removing sensitive information file is about).
An alternative is [BFG Repo-cleaner][bfg-repo-cleaner]. Keep in mind that these
tools are faster because they do not provide a same fully feature set as `git filter-branch`
does, but focus on specific usecases.
## Conclusion
There are various options of undoing your work with any version control system, but
because of de-centralized nature of Git, these options are multiplied (or limited)
depending on the stage of your process. Git also enables rewriting history, but that
should be avoided as it might cause problems when multiple developers are
contributing to the same codebase.
<!-- Identifiers, in alphabetical order -->
[bfg-repo-cleaner]: https://rtyley.github.io/bfg-repo-cleaner/
[git-autoclean-ref]: https://git-scm.com/book/en/v2/Git-Internals-Maintenance-and-Data-Recovery
[git-basics]: https://git-scm.com/book/en/v2/Git-Basics-Recording-Changes-to-the-Repository
[git-debug]: https://git-scm.com/book/en/v2/Git-Tools-Debugging-with-Git
[git-distributed]: https://git-scm.com/about/distributed
[git-filters-manual]: https://git-scm.com/docs/git-filter-branch#_options
[git-official]: https://git-scm.com/
[gitlab-ce]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#contribution-acceptance-criteria
[gitlab-flow]: https://about.gitlab.com/2014/09/29/gitlab-flow/
[gitlab-git-tips-n-tricks]: https://about.gitlab.com/2016/12/08/git-tips-and-tricks/
......@@ -117,7 +117,7 @@ following these rules:
created (requires GitLab Runner v1.1.0 or higher)
To override the default behavior, you can
[specify a service alias](#available-settings-for-services-entry).
[specify a service alias](#available-settings-for-services).
## Define `image` and `services` from `.gitlab-ci.yml`
......@@ -183,8 +183,7 @@ test:
## Extended Docker configuration options
> **Note:**
This feature requires GitLab 9.4 and GitLab Runner 9.4 or higher.
> Introduced in GitLab and GitLab Runner 9.4.
When configuring the `image` or `services` entries, you can use a string or a map as
options:
......@@ -221,28 +220,29 @@ For example, the following two definitions are equal:
### Available settings for `image`
> **Note:**
This feature requires GitLab 9.4 and GitLab Runner 9.4 or higher.
> Introduced in GitLab and GitLab Runner 9.4.
| Setting | Required | Description |
|------------|----------|-------------|
| `name` | yes, when used with any other option | Full name of the image that should be used. It should contain the Registry part if needed. |
| `entrypoint` | no | Command or script that should be executed as the container's entrypoint. It will be translated to Docker's `--entrypoint` option while creating the container. The syntax is similar to [`Dockerfile`'s `ENTRYPOINT`][entrypoint] directive, where each shell token is a separate string in the array. |
| Setting | Required | GitLab version | Description |
|------------|----------|----------------| ----------- |
| `name` | yes, when used with any other option | 9.4 |Full name of the image that should be used. It should contain the Registry part if needed. |
| `entrypoint` | no | 9.4 |Command or script that should be executed as the container's entrypoint. It will be translated to Docker's `--entrypoint` option while creating the container. The syntax is similar to [`Dockerfile`'s `ENTRYPOINT`][entrypoint] directive, where each shell token is a separate string in the array. |
### Available settings for `services`
> **Note:**
This feature requires GitLab 9.4 and GitLab Runner 9.4 or higher.
> Introduced in GitLab and GitLab Runner 9.4.
| Setting | Required | Description |
|------------|----------|-------------|
| `name` | yes, when used with any other option | Full name of the image that should be used. It should contain the Registry part if needed. |
| `entrypoint` | no | Command or script that should be executed as the container's entrypoint. It will be translated to Docker's `--entrypoint` option while creating the container. The syntax is similar to [`Dockerfile`'s `ENTRYPOINT`][entrypoint] directive, where each shell token is a separate string in the array. |
| `command` | no | Command or script that should be used as the container's command. It will be translated to arguments passed to Docker after the image's name. The syntax is similar to [`Dockerfile`'s `CMD`][cmd] directive, where each shell token is a separate string in the array. |
| `alias` | no | Additional alias that can be used to access the service from the job's container. Read [Accessing the services](#accessing-the-services) for more information. |
| Setting | Required | GitLab version | Description |
|------------|----------|----------------| ----------- |
| `name` | yes, when used with any other option | 9.4 | Full name of the image that should be used. It should contain the Registry part if needed. |
| `entrypoint` | no | 9.4 |Command or script that should be executed as the container's entrypoint. It will be translated to Docker's `--entrypoint` option while creating the container. The syntax is similar to [`Dockerfile`'s `ENTRYPOINT`][entrypoint] directive, where each shell token is a separate string in the array. |
| `command` | no | 9.4 |Command or script that should be used as the container's command. It will be translated to arguments passed to Docker after the image's name. The syntax is similar to [`Dockerfile`'s `CMD`][cmd] directive, where each shell token is a separate string in the array. |
| `alias` | no | 9.4 |Additional alias that can be used to access the service from the job's container. Read [Accessing the services](#accessing-the-services) for more information. |
### Starting multiple services from the same image
> Introduced in GitLab and GitLab Runner 9.4. Read more about the [extended
configuration options](#extended-docker-configuration-options).
Before the new extended Docker configuration options, the following configuration
would not work properly:
......@@ -274,6 +274,9 @@ in `.gitlab-ci.yml` file.
### Setting a command for the service
> Introduced in GitLab and GitLab Runner 9.4. Read more about the [extended
configuration options](#extended-docker-configuration-options).
Let's assume you have a `super/sql:latest` image with some SQL database
inside it and you would like to use it as a service for your job. Let's also
assume that this image doesn't start the database process while starting
......@@ -313,6 +316,9 @@ As you can see, the syntax of `command` is similar to [Dockerfile's `CMD`][cmd].
### Overriding the entrypoint of an image
> Introduced in GitLab and GitLab Runner 9.4. Read more about the [extended
configuration options](#extended-docker-configuration-options).
Let's assume you have a `super/sql:experimental` image with some SQL database
inside it and you would like to use it as a base image for your job because you
want to execute some tests with this database binary. Let's also assume that
......
......@@ -29,5 +29,12 @@
1. Click **Create project**.
## From a template
To kickstart your development GitLab projects can be started from a template.
For example, one of the templates included is Ruby on Rails. When filling out the
form for new projects, click the 'Ruby on Rails' button. During project creation,
this will import a Ruby on Rails template with GitLab CI preconfigured.
[import it]: ../workflow/importing/README.md
[reserved]: ../user/reserved_names.md
......@@ -22,6 +22,7 @@ We've gathered some resources to help you to get the best from Git with GitLab.
- [Cherry-picking a commit](../../user/project/merge_requests/cherry_pick_changes.md#cherry-picking-a-commit)
- [Squashing commits](../../workflow/gitlab_flow.md#squashing-commits-with-rebase)
- **Articles:**
- [Numerous _undo_ possibilities in Git](../../articles/numerous_undo_possibilities_in_git/index.md)
- [How to install Git](../../articles/how_to_install_git/index.md)
- [Git Tips & Tricks](https://about.gitlab.com/2016/12/08/git-tips-and-tricks/)
- [Eight Tips to help you work better with Git](https://about.gitlab.com/2015/02/19/8-tips-to-help-you-work-better-with-git/)
......
# Import your project from Bitbucket to GitLab
Import your projects from Bitbucket to GitLab with minimal effort.
## Overview
>**Note:**
The [Bitbucket integration][bb-import] must be first enabled in order to be
able to import your projects from Bitbucket. Ask your GitLab administrator
to enable this if not already.
- At its current state, the Bitbucket importer can import:
- the repository description (GitLab 7.7+)
- the Git repository data (GitLab 7.7+)
- the issues (GitLab 7.7+)
- the issue comments (GitLab 8.15+)
- the pull requests (GitLab 8.4+)
- the pull request comments (GitLab 8.15+)
- the milestones (GitLab 8.15+)
- the wiki (GitLab 8.15+)
- References to pull requests and issues are preserved (GitLab 8.7+)
- Repository public access is retained. If a repository is private in Bitbucket
it will be created as private in GitLab as well.
## How it works
When issues/pull requests are being imported, the Bitbucket importer tries to find
the Bitbucket author/assignee in GitLab's database using the Bitbucket ID. For this
to work, the Bitbucket author/assignee should have signed in beforehand in GitLab
and **associated their Bitbucket account**. If the user is not
found in GitLab's database, the project creator (most of the times the current
user that started the import process) is set as the author, but a reference on
the issue about the original Bitbucket author is kept.
The importer will create any new namespaces (groups) if they don't exist or in
the case the namespace is taken, the repository will be imported under the user's
namespace that started the import process.
## Importing your Bitbucket repositories
1. Sign in to GitLab and go to your dashboard.
1. Click on **New project**.
![New project in GitLab](img/bitbucket_import_new_project.png)
1. Click on the "Bitbucket" button
![Bitbucket](img/import_projects_from_new_project_page.png)
1. Grant GitLab access to your Bitbucket account
![Grant access](img/bitbucket_import_grant_access.png)
1. Click on the projects that you'd like to import or **Import all projects**.
You can also select the namespace under which each project will be
imported.
![Import projects](img/bitbucket_import_select_project.png)
[bb-import]: ../../../integration/bitbucket.md
[social sign-in]: ../../profile/account/social_sign_in.md
# Migrating from ClearCase
[ClearCase](https://www-03.ibm.com/software/products/en/clearcase/) is a set of
tools developed by IBM which also include a centralized version control system
similar to Git.
A good read of ClearCase's basic concepts is can be found in this [StackOverflow
post](https://stackoverflow.com/a/645771/974710).
The following table illustrates the main differences between ClearCase and Git:
| Aspect | ClearCase | Git |
| ------ | --------- | --- |
| Repository model | Client-server | Distributed |
| Revision IDs | Branch + number | Global alphanumeric ID |
| Scope of Change | File | Directory tree snapshot |
| Concurrency model | Merge | Merge |
| Storage Method | Deltas | Full content |
| Client | CLI, Eclipse, CC Client | CLI, Eclipse, Git client/GUIs |
| Server | UNIX, Windows legacy systems | UNIX, macOS |
| License | Proprietary | GPL |
_Taken from the slides [ClearCase and the journey to Git](https://www.open.collab.net/media/pdfs/ClearCase-and-the-journey-to-Git.pdf) provided by collab.net_
## Why migrate
ClearCase can be difficult to manage both from a user and an admin perspective.
Migrating to Git/GitLab there is:
- **No licensing costs**, Git is GPL while ClearCase is proprietary.
- **Shorter learning curve**, Git has a big community and a vast number of
tutorials to get you started.
- **Integration with modern tools**, migrating to Git and GitLab you can have
an open source end-to-end software development platform with built-in version
control, issue tracking, code review, CI/CD, and more.
## How to migrate
While there doesn't exist a tool to fully migrate from ClearCase to Git, here
are some useful links to get you started:
- [Bridge for Git and ClearCase](https://github.com/charleso/git-cc)
- [Slides "ClearCase and the journey to Git"](https://www.open.collab.net/media/pdfs/ClearCase-and-the-journey-to-Git.pdf)
- [ClearCase to Git](https://therub.org/2013/07/19/clearcase-to-git/)
- [Dual syncing ClearCase to Git](https://therub.org/2013/10/22/dual-syncing-clearcase-and-git/)
- [Moving to Git from ClearCase](https://sateeshkumarb.wordpress.com/2011/01/15/moving-to-git-from-clearcase/)
- [ClearCase to Git webinar](https://www.brighttalk.com/webcast/11817/162473/clearcase-to-git)
# Import your project from FogBugz to GitLab
It only takes a few simple steps to import your project from FogBugz.
The importer will import all of your cases and comments with original case
numbers and timestamps. You will also have the opportunity to map FogBugz
users to GitLab users.
1. From your GitLab dashboard click 'New project'
1. Click on the 'FogBugz' button
![FogBugz](img/fogbugz_import_select_fogbogz.png)
1. Enter your FogBugz URL, email address, and password.
![Login](img/fogbugz_import_login.png)
1. Create mapping from FogBugz users to GitLab users.
![User Map](img/fogbugz_import_user_map.png)
1. Select the projects you wish to import by clicking the Import buttons
![Import Project](img/fogbugz_import_select_project.png)
1. Once the import has finished click the link to take you to the project
dashboard. Follow the directions to push your existing repository.
![Finished](img/fogbugz_import_finished.png)
# Import your project from Gitea to GitLab
Import your projects from Gitea to GitLab with minimal effort.
## Overview
>**Note:**
This requires Gitea `v1.0.0` or newer.
- At its current state, Gitea importer can import:
- the repository description (GitLab 8.15+)
- the Git repository data (GitLab 8.15+)
- the issues (GitLab 8.15+)
- the pull requests (GitLab 8.15+)
- the milestones (GitLab 8.15+)
- the labels (GitLab 8.15+)
- Repository public access is retained. If a repository is private in Gitea
it will be created as private in GitLab as well.
## How it works
Since Gitea is currently not an OAuth provider, author/assignee cannot be mapped
to users in your GitLab's instance. This means that the project creator (most of
the times the current user that started the import process) is set as the author,
but a reference on the issue about the original Gitea author is kept.
The importer will create any new namespaces (groups) if they don't exist or in
the case the namespace is taken, the repository will be imported under the user's
namespace that started the import process.
## Importing your Gitea repositories
The importer page is visible when you create a new project.
![New project page on GitLab](img/import_projects_from_new_project_page.png)
Click on the **Gitea** link and the import authorization process will start.
![New Gitea project import](img/import_projects_from_gitea_new_import.png)
### Authorize access to your repositories using a personal access token
With this method, you will perform a one-off authorization with Gitea to grant
GitLab access your repositories:
1. Go to <https://you-gitea-instance/user/settings/applications> (replace
`you-gitea-instance` with the host of your Gitea instance).
1. Click **Generate New Token**.
1. Enter a token description.
1. Click **Generate Token**.
1. Copy the token hash.
1. Go back to GitLab and provide the token to the Gitea importer.
1. Hit the **List Your Gitea Repositories** button and wait while GitLab reads
your repositories' information. Once done, you'll be taken to the importer
page to select the repositories to import.
### Select which repositories to import
After you've authorized access to your Gitea repositories, you will be
redirected to the Gitea importer page.
From there, you can see the import statuses of your Gitea repositories.
- Those that are being imported will show a _started_ status,
- those already successfully imported will be green with a _done_ status,
- whereas those that are not yet imported will have an **Import** button on the
right side of the table.
If you want, you can import all your Gitea projects in one go by hitting
**Import all projects** in the upper left corner.
![Gitea importer page](img/import_projects_from_github_importer.png)
---
You can also choose a different name for the project and a different namespace,
if you have the privileges to do so.
# Import your project from GitHub to GitLab
Import your projects from GitHub to GitLab with minimal effort.
## Overview
>**Note:**
If you are an administrator you can enable the [GitHub integration][gh-import]
in your GitLab instance sitewide. This configuration is optional, users will
still be able to import their GitHub repositories with a
[personal access token][gh-token].
>**Note:**
Administrators of a GitLab instance (Community or Enterprise Edition) can also
use the [GitHub rake task][gh-rake] to import projects from GitHub without the
constrains of a Sidekiq worker.
- At its current state, GitHub importer can import:
- the repository description (GitLab 7.7+)
- the Git repository data (GitLab 7.7+)
- the issues (GitLab 7.7+)
- the pull requests (GitLab 8.4+)
- the wiki pages (GitLab 8.4+)
- the milestones (GitLab 8.7+)
- the labels (GitLab 8.7+)
- the release note descriptions (GitLab 8.12+)
- References to pull requests and issues are preserved (GitLab 8.7+)
- Repository public access is retained. If a repository is private in GitHub
it will be created as private in GitLab as well.
## How it works
When issues/pull requests are being imported, the GitHub importer tries to find
the GitHub author/assignee in GitLab's database using the GitHub ID. For this
to work, the GitHub author/assignee should have signed in beforehand in GitLab
and **associated their GitHub account**. If the user is not
found in GitLab's database, the project creator (most of the times the current
user that started the import process) is set as the author, but a reference on
the issue about the original GitHub author is kept.
The importer will create any new namespaces (groups) if they don't exist or in
the case the namespace is taken, the repository will be imported under the user's
namespace that started the import process.
## Importing your GitHub repositories
The importer page is visible when you create a new project.
![New project page on GitLab](img/import_projects_from_new_project_page.png)
Click on the **GitHub** link and the import authorization process will start.
There are two ways to authorize access to your GitHub repositories:
1. [Using the GitHub integration][gh-integration] (if it's enabled by your
GitLab administrator). This is the preferred way as it's possible to
preserve the GitHub authors/assignees. Read more in the [How it works](#how-it-works)
section.
1. [Using a personal access token][gh-token] provided by GitHub.
![Select authentication method](img/import_projects_from_github_select_auth_method.png)
### Authorize access to your repositories using the GitHub integration
If the [GitHub integration][gh-import] is enabled by your GitLab administrator,
you can use it instead of the personal access token.
1. First you may want to connect your GitHub account to GitLab in order for
the username mapping to be correct.
1. Once you connect GitHub, click the **List your GitHub repositories** button
and you will be redirected to GitHub for permission to access your projects.
1. After accepting, you'll be automatically redirected to the importer.
You can now go on and [select which repositories to import](#select-which-repositories-to-import).
### Authorize access to your repositories using a personal access token
>**Note:**
For a proper author/assignee mapping for issues and pull requests, the
[GitHub integration][gh-integration] should be used instead of the
[personal access token][gh-token]. If the GitHub integration is enabled by your
GitLab administrator, it should be the preferred method to import your repositories.
Read more in the [How it works](#how-it-works) section.
If you are not using the GitHub integration, you can still perform a one-off
authorization with GitHub to grant GitLab access your repositories:
1. Go to <https://github.com/settings/tokens/new>.
1. Enter a token description.
1. Check the `repo` scope.
1. Click **Generate token**.
1. Copy the token hash.
1. Go back to GitLab and provide the token to the GitHub importer.
1. Hit the **List Your GitHub Repositories** button and wait while GitLab reads
your repositories' information. Once done, you'll be taken to the importer
page to select the repositories to import.
### Select which repositories to import
After you've authorized access to your GitHub repositories, you will be
redirected to the GitHub importer page.
From there, you can see the import statuses of your GitHub repositories.
- Those that are being imported will show a _started_ status,
- those already successfully imported will be green with a _done_ status,
- whereas those that are not yet imported will have an **Import** button on the
right side of the table.
If you want, you can import all your GitHub projects in one go by hitting
**Import all projects** in the upper left corner.
![GitHub importer page](img/import_projects_from_github_importer.png)
---
You can also choose a different name for the project and a different namespace,
if you have the privileges to do so.
[gh-import]: ../../../integration/github.md "GitHub integration"
[gh-rake]: ../../../administration/raketasks/github_import.md "GitHub rake task"
[gh-integration]: #authorize-access-to-your-repositories-using-the-github-integration
[gh-token]: #authorize-access-to-your-repositories-using-a-personal-access-token
# Project importing from GitLab.com to your private GitLab instance
You can import your existing GitLab.com projects to your GitLab instance. But keep in mind that it is possible only if
GitLab support is enabled on your GitLab instance.
You can read more about GitLab support [here](http://docs.gitlab.com/ce/integration/gitlab.html)
To get to the importer page you need to go to "New project" page.
>**Note:**
If you are interested in importing Wiki and Merge Request data to your new
instance, you'll need to follow the instructions for [project export](../settings/import_export.md)
![New project page](img/gitlab_new_project_page.png)
Click on the "Import projects from GitLab.com" link and you will be redirected to GitLab.com
for permission to access your projects. After accepting, you'll be automatically redirected to the importer.
![Importer page](img/gitlab_importer.png)
To import a project, you can simple click "Import". The importer will import your repository and issues.
Once the importer is done, a new GitLab project will be created with your imported data.
# Migrating projects to a GitLab instance
1. [From Bitbucket.org](bitbucket.md)
1. [From GitHub.com of GitHub Enterprise](github.md)
1. [From GitLab.com](gitlab_com.md)
1. [From FogBugz](fogbugz.md)
1. [From Gitea](gitea.md)
1. [From SVN](svn.md)
1. [From ClearCase](clearcase.md)
In addition to the specific migration documentation above, you can import any
Git repository via HTTP from the New Project page. Be aware that if the
repository is too large the import can timeout.
## Migrating from self-hosted GitLab to GitLab.com
You can copy your repos by changing the remote and pushing to the new server,
but issues and merge requests can't be imported.
If you want to retain all metadata like issues and merge requests, you can use
the [import/export feature](../settings/import_export.md).
# Migrating from SVN to GitLab
Subversion (SVN) is a central version control system (VCS) while
Git is a distributed version control system. There are some major differences
between the two, for more information consult your favorite search engine.
## Overview
There are two approaches to SVN to Git migration:
1. [Git/SVN Mirror](#smooth-migration-with-a-gitsvn-mirror-using-subgit) which:
- Makes the GitLab repository to mirror the SVN project.
- Git and SVN repositories are kept in sync; you can use either one.
- Smoothens the migration process and allows to manage migration risks.
1. [Cut over migration](#cut-over-migration-with-svn2git) which:
- Translates and imports the existing data and history from SVN to Git.
- Is a fire and forget approach, good for smaller teams.
## Smooth migration with a Git/SVN mirror using SubGit
[SubGit](https://subgit.com) is a tool for a smooth, stress-free SVN to Git
migration. It creates a writable Git mirror of a local or remote Subversion
repository and that way you can use both Subversion and Git as long as you like.
It requires access to your GitLab server as it talks with the Git repositories
directly in a filesystem level.
### SubGit prerequisites
1. Install Oracle JRE 1.8 or newer. On Debian-based Linux distributions you can
follow [this article](http://www.webupd8.org/2012/09/install-oracle-java-8-in-ubuntu-via-ppa.html).
1. Download SubGit from https://subgit.com/download/.
1. Unpack the downloaded SubGit zip archive to the `/opt` directory. The `subgit`
command will be available at `/opt/subgit-VERSION/bin/subgit`.
### SubGit configuration
The first step to mirror you SVN repository in GitLab is to create a new empty
project which will be used as a mirror. For Omnibus installations the path to
the repository will be located at
`/var/opt/gitlab/git-data/repositories/USER/REPO.git` by default. For
installations from source, the default repository directory will be
`/home/git/repositories/USER/REPO.git`. For convenience, assign this path to a
variable:
```
GIT_REPO_PATH=/var/opt/gitlab/git-data/repositories/USER/REPOS.git
```
SubGit will keep this repository in sync with a remote SVN project. For
convenience, assign your remote SVN project URL to a variable:
```
SVN_PROJECT_URL=http://svn.company.com/repos/project
```
Next you need to run SubGit to set up a Git/SVN mirror. Make sure the following
`subgit` command is ran on behalf of the same user that keeps ownership of
GitLab Git repositories (by default `git`):
```
subgit configure --layout auto $SVN_PROJECT_URL $GIT_REPO_PATH
```
Adjust authors and branches mappings, if necessary. Open with your favorite
text editor:
```
edit $GIT_REPO_PATH/subgit/authors.txt
edit $GIT_REPO_PATH/subgit/config
```
For more information regarding the SubGit configuration options, refer to
[SubGit's documentation](https://subgit.com/documentation.html) website.
### Initial translation
Now that SubGit has configured the Git/SVN repos, run `subgit` to perform the
initial translation of existing SVN revisions into the Git repository:
```
subgit install $GIT_REPO_PATH
```
After the initial translation is completed, the Git repository and the SVN
project will be kept in sync by `subgit` - new Git commits will be translated to
SVN revisions and new SVN revisions will be translated to Git commits. Mirror
works transparently and does not require any special commands.
If you would prefer to perform one-time cut over migration with `subgit`, use
the `import` command instead of `install`:
```
subgit import $GIT_REPO_PATH
```
### SubGit licensing
Running SubGit in a mirror mode requires a
[registration](https://subgit.com/pricing.html). Registration is free for open
source, academic and startup projects.
We're currently working on deeper GitLab/SubGit integration. You may track our
progress at [this issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/990).
### SubGit support
For any questions related to SVN to GitLab migration with SubGit, you can
contact the SubGit team directly at [support@subgit.com](mailto:support@subgit.com).
## Cut over migration with svn2git
If you are currently using an SVN repository, you can migrate the repository
to Git and GitLab. We recommend a hard cut over - run the migration command once
and then have all developers start using the new GitLab repository immediately.
Otherwise, it's hard to keep changing in sync in both directions. The conversion
process should be run on a local workstation.
Install `svn2git`. On all systems you can install as a Ruby gem if you already
have Ruby and Git installed.
```bash
sudo gem install svn2git
```
On Debian-based Linux distributions you can install the native packages:
```bash
sudo apt-get install git-core git-svn ruby
```
Optionally, prepare an authors file so `svn2git` can map SVN authors to Git authors.
If you choose not to create the authors file then commits will not be attributed
to the correct GitLab user. Some users may not consider this a big issue while
others will want to ensure they complete this step. If you choose to map authors
you will be required to map every author that is present on changes in the SVN
repository. If you don't, the conversion will fail and you will have to update
the author file accordingly. The following command will search through the
repository and output a list of authors.
```bash
svn log --quiet | grep -E "r[0-9]+ \| .+ \|" | cut -d'|' -f2 | sed 's/ //g' | sort | uniq
```
Use the output from the last command to construct the authors file.
Create a file called `authors.txt` and add one mapping per line.
```
janedoe = Jane Doe <janedoe@example.com>
johndoe = John Doe <johndoe@example.com>
```
If your SVN repository is in the standard format (trunk, branches, tags,
not nested) the conversion is simple. For a non-standard repository see
[svn2git documentation](https://github.com/nirvdrum/svn2git). The following
command will checkout the repository and do the conversion in the current
working directory. Be sure to create a new directory for each repository before
running the `svn2git` command. The conversion process will take some time.
```bash
svn2git https://svn.example.com/path/to/repo --authors /path/to/authors.txt
```
If your SVN repository requires a username and password add the
`--username <username>` and `--password <password` flags to the above command.
`svn2git` also supports excluding certain file paths, branches, tags, etc. See
[svn2git documentation](https://github.com/nirvdrum/svn2git) or run
`svn2git --help` for full documentation on all of the available options.
Create a new GitLab project, where you will eventually push your converted code.
Copy the SSH or HTTP(S) repository URL from the project page. Add the GitLab
repository as a Git remote and push all the changes. This will push all commits,
branches and tags.
```bash
git remote add origin git@gitlab.com:<group>/<project>.git
git push --all origin
git push --tags origin
```
## Contribute to this guide
We welcome all contributions that would expand this guide with instructions on
how to migrate from SVN and other version control systems.
......@@ -90,11 +90,11 @@ from your fork to the upstream project
## Import or export a project
- Import a project from:
- [GitHub to GitLab](../../workflow/importing/import_projects_from_github.md)
- [BitBucket to GitLab](../../workflow/importing/import_projects_from_bitbucket.md)
- [Gitea to GitLab](../../workflow/importing/import_projects_from_gitea.md)
- [FogBugz to GitLab](../../workflow/importing/import_projects_from_fogbugz.md)
- [Import a project](import/index.md) from:
- [GitHub to GitLab](import/github.md)
- [BitBucket to GitLab](import/bitbucket.md)
- [Gitea to GitLab](import/gitea.md)
- [FogBugz to GitLab](import/fogbugz.md)
- [Export a project from GitLab](settings/import_export.md#exporting-a-project-and-its-data)
- [Importing and exporting projects between GitLab instances](settings/import_export.md)
......
# Migrating projects to a GitLab instance
1. [Bitbucket](import_projects_from_bitbucket.md)
1. [GitHub](import_projects_from_github.md)
1. [GitLab.com](import_projects_from_gitlab_com.md)
1. [FogBugz](import_projects_from_fogbugz.md)
1. [Gitea](import_projects_from_gitea.md)
1. [SVN](migrating_from_svn.md)
In addition to the specific migration documentation above, you can import any
Git repository via HTTP from the New Project page. Be aware that if the
repository is too large the import can timeout.
### Migrating from self-hosted GitLab to GitLab.com
You can copy your repos by changing the remote and pushing to the new server;
but issues and merge requests can't be imported.
This document was moved to a [new location](../../user/project/import/index.md).
# Import your project from Bitbucket to GitLab
Import your projects from Bitbucket to GitLab with minimal effort.
## Overview
>**Note:**
The [Bitbucket integration][bb-import] must be first enabled in order to be
able to import your projects from Bitbucket. Ask your GitLab administrator
to enable this if not already.
- At its current state, the Bitbucket importer can import:
- the repository description (GitLab 7.7+)
- the Git repository data (GitLab 7.7+)
- the issues (GitLab 7.7+)
- the issue comments (GitLab 8.15+)
- the pull requests (GitLab 8.4+)
- the pull request comments (GitLab 8.15+)
- the milestones (GitLab 8.15+)
- the wiki (GitLab 8.15+)
- References to pull requests and issues are preserved (GitLab 8.7+)
- Repository public access is retained. If a repository is private in Bitbucket
it will be created as private in GitLab as well.
## How it works
When issues/pull requests are being imported, the Bitbucket importer tries to find
the Bitbucket author/assignee in GitLab's database using the Bitbucket ID. For this
to work, the Bitbucket author/assignee should have signed in beforehand in GitLab
and **associated their Bitbucket account**. If the user is not
found in GitLab's database, the project creator (most of the times the current
user that started the import process) is set as the author, but a reference on
the issue about the original Bitbucket author is kept.
The importer will create any new namespaces (groups) if they don't exist or in
the case the namespace is taken, the repository will be imported under the user's
namespace that started the import process.
## Importing your Bitbucket repositories
1. Sign in to GitLab and go to your dashboard.
1. Click on **New project**.
![New project in GitLab](img/bitbucket_import_new_project.png)
1. Click on the "Bitbucket" button
![Bitbucket](img/import_projects_from_new_project_page.png)
1. Grant GitLab access to your Bitbucket account
![Grant access](img/bitbucket_import_grant_access.png)
1. Click on the projects that you'd like to import or **Import all projects**.
You can also select the namespace under which each project will be
imported.
![Import projects](img/bitbucket_import_select_project.png)
[bb-import]: ../../integration/bitbucket.md
[social sign-in]: ../../user/profile/account/social_sign_in.md
This document was moved to a [new location](../../user/project/import/bitbucket.md).
# Import your project from FogBugz to GitLab
It only takes a few simple steps to import your project from FogBugz.
The importer will import all of your cases and comments with original case
numbers and timestamps. You will also have the opportunity to map FogBugz
users to GitLab users.
* From your GitLab dashboard click 'New project'
* Click on the 'FogBugz' button
![FogBugz](fogbugz_importer/fogbugz_import_select_fogbogz.png)
* Enter your FogBugz URL, email address, and password.
![Login](fogbugz_importer/fogbugz_import_login.png)
* Create mapping from FogBugz users to GitLab users.
![User Map](fogbugz_importer/fogbugz_import_user_map.png)
* Select the projects you wish to import by clicking the Import buttons
![Import Project](fogbugz_importer/fogbugz_import_select_project.png)
* Once the import has finished click the link to take you to the project
dashboard. Follow the directions to push your existing repository.
![Finished](fogbugz_importer/fogbugz_import_finished.png)
This document was moved to a [new location](../../user/project/import/fogbugz.md).
# Import your project from Gitea to GitLab
Import your projects from Gitea to GitLab with minimal effort.
## Overview
>**Note:**
This requires Gitea `v1.0.0` or newer.
- At its current state, Gitea importer can import:
- the repository description (GitLab 8.15+)
- the Git repository data (GitLab 8.15+)
- the issues (GitLab 8.15+)
- the pull requests (GitLab 8.15+)
- the milestones (GitLab 8.15+)
- the labels (GitLab 8.15+)
- Repository public access is retained. If a repository is private in Gitea
it will be created as private in GitLab as well.
## How it works
Since Gitea is currently not an OAuth provider, author/assignee cannot be mapped
to users in your GitLab's instance. This means that the project creator (most of
the times the current user that started the import process) is set as the author,
but a reference on the issue about the original Gitea author is kept.
The importer will create any new namespaces (groups) if they don't exist or in
the case the namespace is taken, the repository will be imported under the user's
namespace that started the import process.
## Importing your Gitea repositories
The importer page is visible when you create a new project.
![New project page on GitLab](img/import_projects_from_new_project_page.png)
Click on the **Gitea** link and the import authorization process will start.
![New Gitea project import](img/import_projects_from_gitea_new_import.png)
### Authorize access to your repositories using a personal access token
With this method, you will perform a one-off authorization with Gitea to grant
GitLab access your repositories:
1. Go to <https://you-gitea-instance/user/settings/applications> (replace
`you-gitea-instance` with the host of your Gitea instance).
1. Click **Generate New Token**.
1. Enter a token description.
1. Click **Generate Token**.
1. Copy the token hash.
1. Go back to GitLab and provide the token to the Gitea importer.
1. Hit the **List Your Gitea Repositories** button and wait while GitLab reads
your repositories' information. Once done, you'll be taken to the importer
page to select the repositories to import.
### Select which repositories to import
After you've authorized access to your Gitea repositories, you will be
redirected to the Gitea importer page.
From there, you can see the import statuses of your Gitea repositories.
- Those that are being imported will show a _started_ status,
- those already successfully imported will be green with a _done_ status,
- whereas those that are not yet imported will have an **Import** button on the
right side of the table.
If you want, you can import all your Gitea projects in one go by hitting
**Import all projects** in the upper left corner.
![Gitea importer page](img/import_projects_from_github_importer.png)
---
You can also choose a different name for the project and a different namespace,
if you have the privileges to do so.
This document was moved to a [new location](../../user/project/import/gitea.md).
# Import your project from GitHub to GitLab
Import your projects from GitHub to GitLab with minimal effort.
## Overview
>**Note:**
If you are an administrator you can enable the [GitHub integration][gh-import]
in your GitLab instance sitewide. This configuration is optional, users will
still be able to import their GitHub repositories with a
[personal access token][gh-token].
>**Note:**
Administrators of a GitLab instance (Community or Enterprise Edition) can also
use the [GitHub rake task][gh-rake] to import projects from GitHub without the
constrains of a Sidekiq worker.
- At its current state, GitHub importer can import:
- the repository description (GitLab 7.7+)
- the Git repository data (GitLab 7.7+)
- the issues (GitLab 7.7+)
- the pull requests (GitLab 8.4+)
- the wiki pages (GitLab 8.4+)
- the milestones (GitLab 8.7+)
- the labels (GitLab 8.7+)
- the release note descriptions (GitLab 8.12+)
- References to pull requests and issues are preserved (GitLab 8.7+)
- Repository public access is retained. If a repository is private in GitHub
it will be created as private in GitLab as well.
## How it works
When issues/pull requests are being imported, the GitHub importer tries to find
the GitHub author/assignee in GitLab's database using the GitHub ID. For this
to work, the GitHub author/assignee should have signed in beforehand in GitLab
and **associated their GitHub account**. If the user is not
found in GitLab's database, the project creator (most of the times the current
user that started the import process) is set as the author, but a reference on
the issue about the original GitHub author is kept.
The importer will create any new namespaces (groups) if they don't exist or in
the case the namespace is taken, the repository will be imported under the user's
namespace that started the import process.
## Importing your GitHub repositories
The importer page is visible when you create a new project.
![New project page on GitLab](img/import_projects_from_new_project_page.png)
Click on the **GitHub** link and the import authorization process will start.
There are two ways to authorize access to your GitHub repositories:
1. [Using the GitHub integration][gh-integration] (if it's enabled by your
GitLab administrator). This is the preferred way as it's possible to
preserve the GitHub authors/assignees. Read more in the [How it works](#how-it-works)
section.
1. [Using a personal access token][gh-token] provided by GitHub.
![Select authentication method](img/import_projects_from_github_select_auth_method.png)
### Authorize access to your repositories using the GitHub integration
If the [GitHub integration][gh-import] is enabled by your GitLab administrator,
you can use it instead of the personal access token.
1. First you may want to connect your GitHub account to GitLab in order for
the username mapping to be correct.
1. Once you connect GitHub, click the **List your GitHub repositories** button
and you will be redirected to GitHub for permission to access your projects.
1. After accepting, you'll be automatically redirected to the importer.
You can now go on and [select which repositories to import](#select-which-repositories-to-import).
### Authorize access to your repositories using a personal access token
>**Note:**
For a proper author/assignee mapping for issues and pull requests, the
[GitHub integration][gh-integration] should be used instead of the
[personal access token][gh-token]. If the GitHub integration is enabled by your
GitLab administrator, it should be the preferred method to import your repositories.
Read more in the [How it works](#how-it-works) section.
If you are not using the GitHub integration, you can still perform a one-off
authorization with GitHub to grant GitLab access your repositories:
1. Go to <https://github.com/settings/tokens/new>.
1. Enter a token description.
1. Check the `repo` scope.
1. Click **Generate token**.
1. Copy the token hash.
1. Go back to GitLab and provide the token to the GitHub importer.
1. Hit the **List Your GitHub Repositories** button and wait while GitLab reads
your repositories' information. Once done, you'll be taken to the importer
page to select the repositories to import.
### Select which repositories to import
After you've authorized access to your GitHub repositories, you will be
redirected to the GitHub importer page.
From there, you can see the import statuses of your GitHub repositories.
- Those that are being imported will show a _started_ status,
- those already successfully imported will be green with a _done_ status,
- whereas those that are not yet imported will have an **Import** button on the
right side of the table.
If you want, you can import all your GitHub projects in one go by hitting
**Import all projects** in the upper left corner.
![GitHub importer page](img/import_projects_from_github_importer.png)
---
You can also choose a different name for the project and a different namespace,
if you have the privileges to do so.
[gh-import]: ../../integration/github.md "GitHub integration"
[gh-rake]: ../../administration/raketasks/github_import.md "GitHub rake task"
[gh-integration]: #authorize-access-to-your-repositories-using-the-github-integration
[gh-token]: #authorize-access-to-your-repositories-using-a-personal-access-token
This document was moved to a [new location](../../user/project/import/github.md).
# Project importing from GitLab.com to your private GitLab instance
You can import your existing GitLab.com projects to your GitLab instance. But keep in mind that it is possible only if
GitLab support is enabled on your GitLab instance.
You can read more about GitLab support [here](http://docs.gitlab.com/ce/integration/gitlab.html)
To get to the importer page you need to go to "New project" page.
>**Note:**
If you are interested in importing Wiki and Merge Request data to your new instance, you'll need to follow the instructions for [project export](../../user/project/settings/import_export.md)
![New project page](gitlab_importer/new_project_page.png)
Click on the "Import projects from GitLab.com" link and you will be redirected to GitLab.com
for permission to access your projects. After accepting, you'll be automatically redirected to the importer.
![Importer page](gitlab_importer/importer.png)
To import a project, you can simple click "Import". The importer will import your repository and issues.
Once the importer is done, a new GitLab project will be created with your imported data.
\ No newline at end of file
This document was moved to a [new location](../../user/project/import/gitlab_com.md).
# Migrating from SVN to GitLab
Subversion (SVN) is a central version control system (VCS) while
Git is a distributed version control system. There are some major differences
between the two, for more information consult your favorite search engine.
## Overview
There are two approaches to SVN to Git migration:
1. [Git/SVN Mirror](#smooth-migration-with-a-gitsvn-mirror-using-subgit) which:
- Makes the GitLab repository to mirror the SVN project.
- Git and SVN repositories are kept in sync; you can use either one.
- Smoothens the migration process and allows to manage migration risks.
1. [Cut over migration](#cut-over-migration-with-svn2git) which:
- Translates and imports the existing data and history from SVN to Git.
- Is a fire and forget approach, good for smaller teams.
## Smooth migration with a Git/SVN mirror using SubGit
[SubGit](https://subgit.com) is a tool for a smooth, stress-free SVN to Git
migration. It creates a writable Git mirror of a local or remote Subversion
repository and that way you can use both Subversion and Git as long as you like.
It requires access to your GitLab server as it talks with the Git repositories
directly in a filesystem level.
### SubGit prerequisites
1. Install Oracle JRE 1.8 or newer. On Debian-based Linux distributions you can
follow [this article](http://www.webupd8.org/2012/09/install-oracle-java-8-in-ubuntu-via-ppa.html).
1. Download SubGit from https://subgit.com/download/.
1. Unpack the downloaded SubGit zip archive to the `/opt` directory. The `subgit`
command will be available at `/opt/subgit-VERSION/bin/subgit`.
### SubGit configuration
The first step to mirror you SVN repository in GitLab is to create a new empty
project which will be used as a mirror. For Omnibus installations the path to
the repository will be located at
`/var/opt/gitlab/git-data/repositories/USER/REPO.git` by default. For
installations from source, the default repository directory will be
`/home/git/repositories/USER/REPO.git`. For convenience, assign this path to a
variable:
```
GIT_REPO_PATH=/var/opt/gitlab/git-data/repositories/USER/REPOS.git
```
SubGit will keep this repository in sync with a remote SVN project. For
convenience, assign your remote SVN project URL to a variable:
```
SVN_PROJECT_URL=http://svn.company.com/repos/project
```
Next you need to run SubGit to set up a Git/SVN mirror. Make sure the following
`subgit` command is ran on behalf of the same user that keeps ownership of
GitLab Git repositories (by default `git`):
```
subgit configure --layout auto $SVN_PROJECT_URL $GIT_REPO_PATH
```
Adjust authors and branches mappings, if necessary. Open with your favorite
text editor:
```
edit $GIT_REPO_PATH/subgit/authors.txt
edit $GIT_REPO_PATH/subgit/config
```
For more information regarding the SubGit configuration options, refer to
[SubGit's documentation](https://subgit.com/documentation.html) website.
### Initial translation
Now that SubGit has configured the Git/SVN repos, run `subgit` to perform the
initial translation of existing SVN revisions into the Git repository:
```
subgit install $GIT_REPO_PATH
```
After the initial translation is completed, the Git repository and the SVN
project will be kept in sync by `subgit` - new Git commits will be translated to
SVN revisions and new SVN revisions will be translated to Git commits. Mirror
works transparently and does not require any special commands.
If you would prefer to perform one-time cut over migration with `subgit`, use
the `import` command instead of `install`:
```
subgit import $GIT_REPO_PATH
```
### SubGit licensing
Running SubGit in a mirror mode requires a
[registration](https://subgit.com/pricing.html). Registration is free for open
source, academic and startup projects.
We're currently working on deeper GitLab/SubGit integration. You may track our
progress at [this issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/990).
### SubGit support
For any questions related to SVN to GitLab migration with SubGit, you can
contact the SubGit team directly at [support@subgit.com](mailto:support@subgit.com).
## Cut over migration with svn2git
If you are currently using an SVN repository, you can migrate the repository
to Git and GitLab. We recommend a hard cut over - run the migration command once
and then have all developers start using the new GitLab repository immediately.
Otherwise, it's hard to keep changing in sync in both directions. The conversion
process should be run on a local workstation.
Install `svn2git`. On all systems you can install as a Ruby gem if you already
have Ruby and Git installed.
```bash
sudo gem install svn2git
```
On Debian-based Linux distributions you can install the native packages:
```bash
sudo apt-get install git-core git-svn ruby
```
Optionally, prepare an authors file so `svn2git` can map SVN authors to Git authors.
If you choose not to create the authors file then commits will not be attributed
to the correct GitLab user. Some users may not consider this a big issue while
others will want to ensure they complete this step. If you choose to map authors
you will be required to map every author that is present on changes in the SVN
repository. If you don't, the conversion will fail and you will have to update
the author file accordingly. The following command will search through the
repository and output a list of authors.
```bash
svn log --quiet | grep -E "r[0-9]+ \| .+ \|" | cut -d'|' -f2 | sed 's/ //g' | sort | uniq
```
Use the output from the last command to construct the authors file.
Create a file called `authors.txt` and add one mapping per line.
```
janedoe = Jane Doe <janedoe@example.com>
johndoe = John Doe <johndoe@example.com>
```
If your SVN repository is in the standard format (trunk, branches, tags,
not nested) the conversion is simple. For a non-standard repository see
[svn2git documentation](https://github.com/nirvdrum/svn2git). The following
command will checkout the repository and do the conversion in the current
working directory. Be sure to create a new directory for each repository before
running the `svn2git` command. The conversion process will take some time.
```bash
svn2git https://svn.example.com/path/to/repo --authors /path/to/authors.txt
```
If your SVN repository requires a username and password add the
`--username <username>` and `--password <password` flags to the above command.
`svn2git` also supports excluding certain file paths, branches, tags, etc. See
[svn2git documentation](https://github.com/nirvdrum/svn2git) or run
`svn2git --help` for full documentation on all of the available options.
Create a new GitLab project, where you will eventually push your converted code.
Copy the SSH or HTTP(S) repository URL from the project page. Add the GitLab
repository as a Git remote and push all the changes. This will push all commits,
branches and tags.
```bash
git remote add origin git@gitlab.com:<group>/<project>.git
git push --all origin
git push --tags origin
```
## Contribute to this guide
We welcome all contributions that would expand this guide with instructions on
how to migrate from SVN and other version control systems.
This document was moved to a [new location](../../user/project/import/svn.md).
module AfterCommitQueue
extend ActiveSupport::Concern
included do
after_commit :_run_after_commit_queue
after_rollback :_clear_after_commit_queue
end
def run_after_commit(method = nil, &block)
_after_commit_queue << proc { self.send(method) } if method
_after_commit_queue << block if block
true
end
protected
def _run_after_commit_queue
while action = _after_commit_queue.pop
self.instance_eval(&action)
end
end
def _after_commit_queue
@after_commit_queue ||= []
end
def _clear_after_commit_queue
_after_commit_queue.clear
end
end
......@@ -290,7 +290,7 @@ module API
def uploaded_file(field, uploads_path)
if params[field]
bad_request!("#{field} is not a file") unless params[field].respond_to?(:filename)
bad_request!("#{field} is not a file") unless params[field][:filename]
return params[field]
end
......
......@@ -11,7 +11,7 @@ module API
def add_pagination_headers(paginated_data)
header 'X-Total', paginated_data.total_count.to_s
header 'X-Total-Pages', paginated_data.total_pages.to_s
header 'X-Total-Pages', total_pages(paginated_data).to_s
header 'X-Per-Page', paginated_data.limit_value.to_s
header 'X-Page', paginated_data.current_page.to_s
header 'X-Next-Page', paginated_data.next_page.to_s
......@@ -26,20 +26,25 @@ module API
links = []
request_params[:page] = paginated_data.current_page - 1
links << %(<#{request_url}?#{request_params.to_query}>; rel="prev") unless paginated_data.first_page?
request_params[:page] = paginated_data.prev_page
links << %(<#{request_url}?#{request_params.to_query}>; rel="prev") if request_params[:page]
request_params[:page] = paginated_data.current_page + 1
links << %(<#{request_url}?#{request_params.to_query}>; rel="next") unless paginated_data.last_page?
request_params[:page] = paginated_data.next_page
links << %(<#{request_url}?#{request_params.to_query}>; rel="next") if request_params[:page]
request_params[:page] = 1
links << %(<#{request_url}?#{request_params.to_query}>; rel="first")
request_params[:page] = paginated_data.total_pages
request_params[:page] = total_pages(paginated_data)
links << %(<#{request_url}?#{request_params.to_query}>; rel="last")
links.join(', ')
end
def total_pages(paginated_data)
# Ensure there is in total at least 1 page
[paginated_data.total_pages, 1].max
end
end
end
end
......@@ -16,9 +16,9 @@ module API
case scope
when String
[scope]
when Hashie::Mash
when ::Hash
scope.values
when Hashie::Array
when ::Array
scope
else
['unknown']
......
......@@ -57,7 +57,7 @@ module API
end
get "templates/licenses" do
options = {
featured: declared(params).popular.present? ? true : nil
featured: declared(params)[:popular].present? ? true : nil
}
licences = ::Kaminari.paginate_array(Licensee::License.all(options))
present paginate(licences), with: Entities::RepoLicense
......@@ -71,7 +71,7 @@ module API
requires :name, type: String, desc: 'The name of the template'
end
get "templates/licenses/:name", requirements: { name: /[\w\.-]+/ } do
not_found!('License') unless Licensee::License.find(declared(params).name)
not_found!('License') unless Licensee::License.find(declared(params)[:name])
template = parsed_license_template
......@@ -102,7 +102,7 @@ module API
requires :name, type: String, desc: 'The name of the template'
end
get "templates/#{template_type}/:name" do
new_template = klass.find(declared(params).name)
new_template = klass.find(declared(params)[:name])
render_response(template_type, new_template)
end
......
......@@ -16,7 +16,7 @@ module API
coerce_with: ->(scope) {
if scope.is_a?(String)
[scope]
elsif scope.is_a?(Hashie::Mash)
elsif scope.is_a?(::Hash)
scope.values
else
['unknown']
......
......@@ -59,7 +59,7 @@ module API
end
get route do
options = {
featured: declared(params).popular.present? ? true : nil
featured: declared(params)[:popular].present? ? true : nil
}
present Licensee::License.all(options), with: ::API::Entities::RepoLicense
end
......@@ -76,7 +76,7 @@ module API
requires :name, type: String, desc: 'The name of the template'
end
get route, requirements: { name: /[\w\.-]+/ } do
not_found!('License') unless Licensee::License.find(declared(params).name)
not_found!('License') unless Licensee::License.find(declared(params)[:name])
template = parsed_license_template
......@@ -111,7 +111,7 @@ module API
requires :name, type: String, desc: 'The name of the template'
end
get route do
new_template = klass.find(declared(params).name)
new_template = klass.find(declared(params)[:name])
render_response(template_type, new_template)
end
......
......@@ -81,12 +81,15 @@ module Gitlab
relative_order: index
)
# Compatibility with old diffs created with Psych.
diff_hash.tap do |hash|
diff_text = hash[:diff]
hash[:too_large] = !!hash[:too_large]
hash[:a_mode] ||= guess_mode(hash[:new_file], hash[:diff])
hash[:b_mode] ||= guess_mode(hash[:deleted_file], hash[:diff])
# Compatibility with old diffs created with Psych.
if diff_text.encoding == Encoding::BINARY && !diff_text.ascii_only?
hash[:binary] = true
hash[:diff] = [diff_text].pack('m0')
......@@ -97,6 +100,15 @@ module Gitlab
[commit_rows, file_rows]
end
# This doesn't have to be 100% accurate, because it's only used for
# display - it won't change file modes in the repository. Submodules are
# created as 600, regular files as 644.
def guess_mode(file_missing, diff)
return '0' if file_missing
diff.include?('Subproject commit') ? '160000' : '100644'
end
# Unlike MergeRequestDiff#valid_raw_diff?, don't count Rugged objects as
# valid, because we don't render them usefully anyway.
def valid_raw_diffs?(diffs)
......
......@@ -204,21 +204,26 @@ module Gitlab
#
# name - The name of the tag as a String.
def tag_exists?(name)
!!rugged.tags[name]
gitaly_migrate(:ref_exists_tags) do |is_enabled|
if is_enabled
gitaly_ref_exists?("refs/tags/#{name}")
else
rugged_tag_exists?(name)
end
end
end
# Returns true if the given branch exists
#
# name - The name of the branch as a String.
def branch_exists?(name)
rugged.branches.exists?(name)
# If the branch name is invalid (e.g. ".foo") Rugged will raise an error.
# Whatever code calls this method shouldn't have to deal with that so
# instead we just return `false` (which is true since a branch doesn't
# exist when it has an invalid name).
rescue Rugged::ReferenceError
false
gitaly_migrate(:ref_exists_branches) do |is_enabled|
if is_enabled
gitaly_ref_exists?("refs/heads/#{name}")
else
rugged_branch_exists?(name)
end
end
end
# Returns an Array of branch and tag names
......@@ -653,33 +658,15 @@ module Gitlab
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/328
def copy_gitattributes(ref)
begin
commit = lookup(ref)
rescue Rugged::ReferenceError
raise InvalidRef.new("Ref #{ref} is invalid")
end
# Create the paths
info_dir_path = File.join(path, 'info')
info_attributes_path = File.join(info_dir_path, 'attributes')
begin
# Retrieve the contents of the blob
gitattributes_content = blob_content(commit, '.gitattributes')
rescue InvalidBlobName
# No .gitattributes found. Should now remove any info/attributes and return
File.delete(info_attributes_path) if File.exist?(info_attributes_path)
return
Gitlab::GitalyClient.migrate(:apply_gitattributes) do |is_enabled|
if is_enabled
gitaly_copy_gitattributes(ref)
else
rugged_copy_gitattributes(ref)
end
# Create the info directory if needed
Dir.mkdir(info_dir_path) unless File.directory?(info_dir_path)
# Write the contents of the .gitattributes file to info/attributes
# Use binary mode to prevent Rails from converting ASCII-8BIT to UTF-8
File.open(info_attributes_path, "wb") do |file|
file.write(gitattributes_content)
end
rescue GRPC::InvalidArgument
raise InvalidRef
end
# Returns the Git attributes for the given file path.
......@@ -1012,6 +999,68 @@ module Gitlab
raw_output.compact
end
# Returns true if the given ref name exists
#
# Ref names must start with `refs/`.
def gitaly_ref_exists?(ref_name)
gitaly_ref_client.ref_exists?(ref_name)
end
# Returns true if the given tag exists
#
# name - The name of the tag as a String.
def rugged_tag_exists?(name)
!!rugged.tags[name]
end
# Returns true if the given branch exists
#
# name - The name of the branch as a String.
def rugged_branch_exists?(name)
rugged.branches.exists?(name)
# If the branch name is invalid (e.g. ".foo") Rugged will raise an error.
# Whatever code calls this method shouldn't have to deal with that so
# instead we just return `false` (which is true since a branch doesn't
# exist when it has an invalid name).
rescue Rugged::ReferenceError
false
end
def gitaly_copy_gitattributes(revision)
gitaly_repository_client.apply_gitattributes(revision)
end
def rugged_copy_gitattributes(ref)
begin
commit = lookup(ref)
rescue Rugged::ReferenceError
raise InvalidRef.new("Ref #{ref} is invalid")
end
# Create the paths
info_dir_path = File.join(path, 'info')
info_attributes_path = File.join(info_dir_path, 'attributes')
begin
# Retrieve the contents of the blob
gitattributes_content = blob_content(commit, '.gitattributes')
rescue InvalidBlobName
# No .gitattributes found. Should now remove any info/attributes and return
File.delete(info_attributes_path) if File.exist?(info_attributes_path)
return
end
# Create the info directory if needed
Dir.mkdir(info_dir_path) unless File.directory?(info_dir_path)
# Write the contents of the .gitattributes file to info/attributes
# Use binary mode to prevent Rails from converting ASCII-8BIT to UTF-8
File.open(info_attributes_path, "wb") do |file|
file.write(gitattributes_content)
end
end
end
end
end
......@@ -13,10 +13,17 @@ module Gitlab
)
response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_blob, request)
blob = response.first
return unless blob.oid.present?
data = ''
blob = nil
response.each do |msg|
if blob.nil?
blob = msg
end
data << msg.data
end
data = response.reduce(blob.data.dup) { |memo, msg| memo << msg.data.dup }
return nil if blob.oid.blank?
Gitlab::Git::Blob.new(
id: blob.oid,
......
......@@ -60,15 +60,21 @@ module Gitlab
)
response = GitalyClient.call(@repository.storage, :commit_service, :tree_entry, request)
entry = response.first
return unless entry.oid.present?
if entry.type == :BLOB
rest_of_data = response.reduce("") { |memo, msg| memo << msg.data }
entry.data += rest_of_data
entry = nil
data = ''
response.each do |msg|
if entry.nil?
entry = msg
break unless entry.type == :BLOB
end
data << msg.data
end
entry.data = data
entry
entry unless entry.oid.blank?
end
def tree_entries(repository, revision, path)
......
......@@ -70,6 +70,14 @@ module Gitlab
consume_tags_response(response)
end
def ref_exists?(ref_name)
request = Gitaly::RefExistsRequest.new(repository: @gitaly_repo, ref: ref_name)
response = GitalyClient.call(@storage, :ref_service, :ref_exists, request)
response.value
rescue GRPC::InvalidArgument => e
raise ArgumentError, e.message
end
private
def consume_refs_response(response)
......
......@@ -32,6 +32,11 @@ module Gitlab
request = Gitaly::RepositorySizeRequest.new(repository: @gitaly_repo)
GitalyClient.call(@storage, :repository_service, :repository_size, request).size
end
def apply_gitattributes(revision)
request = Gitaly::ApplyGitattributesRequest.new(repository: @gitaly_repo, revision: revision)
GitalyClient.call(@storage, :repository_service, :apply_gitattributes, request)
end
end
end
end
module Gitlab
module ImportExport
class ProjectTreeRestorer
# Relations which cannot have both group_id and project_id at the same time
RESTRICT_PROJECT_AND_GROUP = %i(milestones).freeze
def initialize(user:, shared:, project:)
@path = File.join(shared.export_path, 'project.json')
@user = user
......@@ -118,9 +121,11 @@ module Gitlab
end
def create_relation(relation, relation_hash_list)
relation_type = relation.to_sym
relation_array = [relation_hash_list].flatten.map do |relation_hash|
Gitlab::ImportExport::RelationFactory.create(relation_sym: relation.to_sym,
relation_hash: parsed_relation_hash(relation_hash),
Gitlab::ImportExport::RelationFactory.create(relation_sym: relation_type,
relation_hash: parsed_relation_hash(relation_hash, relation_type),
members_mapper: members_mapper,
user: @user,
project: restored_project)
......@@ -129,8 +134,16 @@ module Gitlab
relation_hash_list.is_a?(Array) ? relation_array : relation_array.first
end
def parsed_relation_hash(relation_hash)
relation_hash.merge!('group_id' => restored_project.group.try(:id), 'project_id' => restored_project.id)
def parsed_relation_hash(relation_hash, relation_type)
if RESTRICT_PROJECT_AND_GROUP.include?(relation_type)
params = {}
params['group_id'] = restored_project.group.try(:id) if relation_hash['group_id']
params['project_id'] = restored_project.id if relation_hash['project_id']
else
params = { 'group_id' => restored_project.group.try(:id), 'project_id' => restored_project.id }
end
relation_hash.merge(params)
end
end
end
......
......@@ -90,9 +90,14 @@ module Gitlab
#
# Returns an array of completed JIDs
def self.completed_jids(job_ids)
Sidekiq.redis do |redis|
job_ids.reject { |jid| redis.exists(key_for(jid)) }
statuses = job_status(job_ids)
completed = []
job_ids.zip(statuses).each do |job_id, status|
completed << job_id unless status
end
completed
end
def self.key_for(jid)
......
module Gitlab
class StringRangeMarker
attr_accessor :raw_line, :rich_line
attr_accessor :raw_line, :rich_line, :html_escaped
def initialize(raw_line, rich_line = raw_line)
@raw_line = raw_line
def initialize(raw_line, rich_line = nil)
@raw_line = raw_line.dup
if rich_line.nil?
@rich_line = raw_line.dup
@html_escaped = false
else
@rich_line = ERB::Util.html_escape(rich_line)
@html_escaped = true
end
end
def mark(marker_ranges)
return rich_line unless marker_ranges
if html_escaped
rich_marker_ranges = []
marker_ranges.each do |range|
# Map the inline-diff range based on the raw line to character positions in the rich line
......@@ -17,6 +24,9 @@ module Gitlab
# Turn the array of character positions into ranges
rich_marker_ranges.concat(collapse_ranges(rich_positions))
end
else
rich_marker_ranges = marker_ranges
end
offset = 0
# Mark each range
......@@ -31,7 +41,7 @@ module Gitlab
offset += text.length - original_text.length
end
rich_line.html_safe
@html_escaped ? rich_line.html_safe : rich_line
end
private
......
......@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-07-13 12:07-0500\n"
"PO-Revision-Date: 2017-07-13 12:07-0500\n"
"POT-Creation-Date: 2017-08-18 14:15+0530\n"
"PO-Revision-Date: 2017-08-18 14:15+0530\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
......@@ -31,6 +31,23 @@ msgstr[1] ""
msgid "%{commit_author_link} committed %{commit_timeago}"
msgstr ""
msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt."
msgstr ""
msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will block access for %{number_of_seconds} seconds."
msgstr ""
msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved."
msgstr ""
msgid "%{storage_name}: failed storage access attempt on host:"
msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:"
msgstr[0] ""
msgstr[1] ""
msgid "(checkout the %{link} for information on how to install it)."
msgstr ""
msgid "1 pipeline"
msgid_plural "%d pipelines"
msgstr[0] ""
......@@ -42,6 +59,9 @@ msgstr ""
msgid "About auto deploy"
msgstr ""
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
msgid "Active"
msgstr ""
......@@ -63,12 +83,27 @@ msgstr ""
msgid "Add new directory"
msgstr ""
msgid "All"
msgstr ""
msgid "Archived project! Repository is read-only"
msgstr ""
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
msgid "Are you sure you want to discard your changes?"
msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
msgid "Are you sure you want to reset the health check token?"
msgstr ""
msgid "Are you sure?"
msgstr ""
msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr ""
......@@ -110,6 +145,9 @@ msgstr ""
msgid "Cancel"
msgstr ""
msgid "Cancel edit"
msgstr ""
msgid "ChangeTypeActionLabel|Pick into branch"
msgstr ""
......@@ -188,6 +226,9 @@ msgstr ""
msgid "CiStatus|running"
msgstr ""
msgid "Comments"
msgstr ""
msgid "Commit"
msgid_plural "Commits"
msgstr[0] ""
......@@ -235,6 +276,9 @@ msgstr ""
msgid "Create New Directory"
msgstr ""
msgid "Create a new branch"
msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
......@@ -312,9 +356,15 @@ msgstr[1] ""
msgid "Description"
msgstr ""
msgid "Details"
msgstr ""
msgid "Directory name"
msgstr ""
msgid "Discard changes"
msgstr ""
msgid "Don't show again"
msgstr ""
......@@ -351,6 +401,24 @@ msgstr ""
msgid "Edit Pipeline Schedule %{id}"
msgstr ""
msgid "EventFilterBy|Filter by all"
msgstr ""
msgid "EventFilterBy|Filter by comments"
msgstr ""
msgid "EventFilterBy|Filter by issue events"
msgstr ""
msgid "EventFilterBy|Filter by merge events"
msgstr ""
msgid "EventFilterBy|Filter by push events"
msgstr ""
msgid "EventFilterBy|Filter by team"
msgstr ""
msgid "Every day (at 4:00am)"
msgstr ""
......@@ -398,12 +466,36 @@ msgstr ""
msgid "From merge request merge until deploy to production"
msgstr ""
msgid "Git storage health information has been reset"
msgstr ""
msgid "GitLab Runner section"
msgstr ""
msgid "Go to your fork"
msgstr ""
msgid "GoToYourFork|Fork"
msgstr ""
msgid "Health Check"
msgstr ""
msgid "Health information can be retrieved from the following endpoints. More information is available"
msgstr ""
msgid "HealthCheck|Access token is"
msgstr ""
msgid "HealthCheck|Healthy"
msgstr ""
msgid "HealthCheck|No Health Problems Detected"
msgstr ""
msgid "HealthCheck|Unhealthy"
msgstr ""
msgid "Home"
msgstr ""
......@@ -413,12 +505,18 @@ msgstr ""
msgid "Import repository"
msgstr ""
msgid "Install a Runner compatible with GitLab CI"
msgstr ""
msgid "Interval Pattern"
msgstr ""
msgid "Introducing Cycle Analytics"
msgstr ""
msgid "Issue events"
msgstr ""
msgid "Jobs for last month"
msgstr ""
......@@ -448,6 +546,12 @@ msgstr ""
msgid "Last commit"
msgstr ""
msgid "LastPushEvent|You pushed to"
msgstr ""
msgid "LastPushEvent|at"
msgstr ""
msgid "Learn more in the"
msgstr ""
......@@ -468,9 +572,15 @@ msgstr[1] ""
msgid "Median"
msgstr ""
msgid "Merge events"
msgstr ""
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
msgid "More information is available|here"
msgstr ""
msgid "New Issue"
msgid_plural "New Issues"
msgstr[0] ""
......@@ -668,6 +778,9 @@ msgstr ""
msgid "Pipeline|with stages"
msgstr ""
msgid "Project"
msgstr ""
msgid "Project '%{project_name}' queued for deletion."
msgstr ""
......@@ -683,6 +796,9 @@ msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
msgid "Project details"
msgstr ""
msgid "Project export could not be deleted."
msgstr ""
......@@ -698,6 +814,9 @@ msgstr ""
msgid "Project home"
msgstr ""
msgid "ProjectActivityRSS|Subscribe"
msgstr ""
msgid "ProjectFeature|Disabled"
msgstr ""
......@@ -719,6 +838,9 @@ msgstr ""
msgid "ProjectNetworkGraph|Graph"
msgstr ""
msgid "Push events"
msgstr ""
msgid "Read more"
msgstr ""
......@@ -755,9 +877,21 @@ msgstr ""
msgid "Remove project"
msgstr ""
msgid "Repository"
msgstr ""
msgid "Request Access"
msgstr ""
msgid "Reset git storage health information"
msgstr ""
msgid "Reset health check access token"
msgstr ""
msgid "Reset runners registration token"
msgstr ""
msgid "Revert this commit"
msgstr ""
......@@ -782,6 +916,9 @@ msgstr ""
msgid "Select a timezone"
msgstr ""
msgid "Select existing branch"
msgstr ""
msgid "Select target branch"
msgstr ""
......@@ -808,12 +945,18 @@ msgstr[1] ""
msgid "Source code"
msgstr ""
msgid "Specify the following URL during the Runner setup:"
msgstr ""
msgid "StarProject|Star"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
msgid "Start the Runner!"
msgstr ""
msgid "Switch branch/tag"
msgstr ""
......@@ -828,6 +971,9 @@ msgstr ""
msgid "Target Branch"
msgstr ""
msgid "Team"
msgstr ""
msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request."
msgstr ""
......@@ -876,6 +1022,9 @@ msgstr ""
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
msgid "There are problems accessing Git storage: "
msgstr ""
msgid "This means you can not push code until you create an empty repository or import existing one."
msgstr ""
......@@ -1045,6 +1194,9 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
msgid "Use the following registration token during setup:"
msgstr ""
msgid "Use your global notification setting"
msgstr ""
......
......@@ -50,7 +50,7 @@ describe 'Help Pages' do
it 'hides the version check image if the image request fails' do
# We use '--load-images=yes' with poltergeist so the image fails to load
expect(find('.js-version-status-badge', visible: false)).not_to be_visible
expect(page).to have_selector('.js-version-status-badge', visible: false)
end
end
......
......@@ -7,9 +7,8 @@ describe 'Profile account page' do
sign_in(user)
end
describe 'when signup is enabled' do
describe 'when I delete my account' do
before do
stub_application_setting(signup_enabled: true)
visit profile_account_path
end
......@@ -21,18 +20,6 @@ describe 'Profile account page' do
end
end
describe 'when signup is disabled' do
before do
stub_application_setting(signup_enabled: false)
visit profile_account_path
end
it 'does not have option to remove account' do
expect(page).not_to have_content('Remove account')
expect(current_path).to eq(profile_account_path)
end
end
describe 'when I reset private token' do
before do
visit profile_account_path
......
......@@ -18,7 +18,7 @@ feature 'Project' do
click_button "Create project"
end
expect(page).to have_content 'This project Loading..'
expect(page).to have_content template.name
end
end
......
......@@ -135,10 +135,10 @@ describe DiffHelper do
it "returns strings with marked inline diffs" do
marked_old_line, marked_new_line = mark_inline_diffs(old_line, new_line)
expect(marked_old_line).to eq(%q{abc <span class="idiff left right deletion">&#39;def&#39;</span>})
expect(marked_old_line).to be_html_safe
expect(marked_new_line).to eq(%q{abc <span class="idiff left right addition">&quot;def&quot;</span>})
expect(marked_new_line).to be_html_safe
expect(marked_old_line).to eq(%q{abc <span class="idiff left right deletion">'def'</span>})
expect(marked_old_line).not_to be_html_safe
expect(marked_new_line).to eq(%q{abc <span class="idiff left right addition">"def"</span>})
expect(marked_new_line).not_to be_html_safe
end
end
......
......@@ -2,6 +2,22 @@ require 'spec_helper'
require_relative '../../config/initializers/1_settings'
describe Settings do
describe '#ldap' do
it 'can be accessed with dot syntax all the way down' do
expect(Gitlab.config.ldap.servers.main.label).to eq('ldap')
end
# Specifically trying to cause this error discovered in EE when removing the
# reassignment of each server element with Settingslogic.
#
# `undefined method `label' for #<Hash:0x007fbd18b59c08>`
#
it 'can be accessed in a very specific way that breaks without reassigning each element with Settingslogic' do
server_settings = Gitlab.config.ldap.servers['main']
expect(server_settings.label).to eq('ldap')
end
end
describe '#repositories' do
it 'assigns the default failure attributes' do
repository_settings = Gitlab.config.repositories.storages['broken']
......@@ -11,6 +27,15 @@ describe Settings do
expect(repository_settings['failure_reset_time']).to eq(1800)
expect(repository_settings['storage_timeout']).to eq(5)
end
it 'can be accessed with dot syntax all the way down' do
expect(Gitlab.config.repositories.storages.broken.failure_count_threshold).to eq(10)
end
it 'can be accessed in a very specific way that breaks without reassigning each element with Settingslogic' do
storage_settings = Gitlab.config.repositories.storages['broken']
expect(storage_settings.failure_count_threshold).to eq(10)
end
end
describe '#host_without_www' do
......
......@@ -266,6 +266,12 @@ import '~/lib/utils/common_utils';
});
describe('gl.utils.backOff', () => {
beforeEach(() => {
// shortcut our timeouts otherwise these tests will take a long time to finish
const origSetTimeout = window.setTimeout;
spyOn(window, 'setTimeout').and.callFake(cb => origSetTimeout(cb, 0));
});
it('solves the promise from the callback', (done) => {
const expectedResponseValue = 'Success!';
gl.utils.backOff((next, stop) => (
......@@ -299,9 +305,8 @@ import '~/lib/utils/common_utils';
let numberOfCalls = 1;
const expectedResponseValue = 'Success!';
gl.utils.backOff((next, stop) => (
new Promise((resolve) => {
resolve(expectedResponseValue);
}).then((resp) => {
Promise.resolve(expectedResponseValue)
.then((resp) => {
if (numberOfCalls < 3) {
numberOfCalls += 1;
next();
......@@ -310,26 +315,23 @@ import '~/lib/utils/common_utils';
}
})
)).then((respBackoff) => {
const timeouts = window.setTimeout.calls.allArgs().map(([, timeout]) => timeout);
expect(timeouts).toEqual([2000, 4000]);
expect(respBackoff).toBe(expectedResponseValue);
expect(numberOfCalls).toBe(3);
done();
});
}, 10000);
});
it('rejects the backOff promise after timing out', (done) => {
const expectedResponseValue = 'Success!';
gl.utils.backOff(next => (
new Promise((resolve) => {
resolve(expectedResponseValue);
}).then(() => {
setTimeout(next(), 5000); // it will time out
})
), 3000).catch((errBackoffResp) => {
gl.utils.backOff(next => next(), 64000)
.catch((errBackoffResp) => {
const timeouts = window.setTimeout.calls.allArgs().map(([, timeout]) => timeout);
expect(timeouts).toEqual([2000, 4000, 8000, 16000, 32000, 32000]);
expect(errBackoffResp instanceof Error).toBe(true);
expect(errBackoffResp.message).toBe('BACKOFF_TIMEOUT');
done();
});
}, 10000);
});
});
describe('gl.utils.setFavicon', () => {
......
require 'spec_helper'
describe AfterCommitQueue do
it 'runs after transaction is committed' do
called = false
test_proc = proc { called = true }
project = build(:project)
project.run_after_commit(&test_proc)
project.save
expect(called).to be true
end
end
......@@ -52,7 +52,13 @@ describe API::Helpers::Pagination do
expect_header('X-Page', '1')
expect_header('X-Next-Page', '2')
expect_header('X-Prev-Page', '')
expect_header('Link', any_args)
expect_header('Link', anything) do |_key, val|
expect(val).to include('rel="first"')
expect(val).to include('rel="last"')
expect(val).to include('rel="next"')
expect(val).not_to include('rel="prev"')
end
subject.paginate(resource)
end
......@@ -75,15 +81,53 @@ describe API::Helpers::Pagination do
expect_header('X-Page', '2')
expect_header('X-Next-Page', '')
expect_header('X-Prev-Page', '1')
expect_header('Link', any_args)
expect_header('Link', anything) do |_key, val|
expect(val).to include('rel="first"')
expect(val).to include('rel="last"')
expect(val).to include('rel="prev"')
expect(val).not_to include('rel="next"')
end
subject.paginate(resource)
end
end
end
context 'when resource empty' do
describe 'first page' do
before do
allow(subject).to receive(:params)
.and_return({ page: 1, per_page: 2 })
end
it 'returns appropriate amount of resources' do
expect(subject.paginate(resource).count).to eq 0
end
it 'adds appropriate headers' do
expect_header('X-Total', '0')
expect_header('X-Total-Pages', '1')
expect_header('X-Per-Page', '2')
expect_header('X-Page', '1')
expect_header('X-Next-Page', '')
expect_header('X-Prev-Page', '')
expect_header('Link', anything) do |_key, val|
expect(val).to include('rel="first"')
expect(val).to include('rel="last"')
expect(val).not_to include('rel="prev"')
expect(val).not_to include('rel="next"')
expect(val).not_to include('page=0')
end
subject.paginate(resource)
end
end
end
def expect_header(name, value)
expect(subject).to receive(:header).with(name, value)
def expect_header(*args, &block)
expect(subject).to receive(:header).with(*args, &block)
end
def expect_message(method)
......
......@@ -134,6 +134,17 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits do
include_examples 'updated MR diff'
end
context 'when the merge request diffs do not have a_mode and b_mode set' do
let(:commits) { merge_request_diff.commits.map(&:to_hash) }
let(:expected_diffs) { diffs_to_hashes(merge_request_diff.merge_request_diff_files) }
let(:diffs) do
expected_diffs.map { |diff| diff.except(:a_mode, :b_mode) }
end
include_examples 'updated MR diff'
end
context 'when the merge request diffs have binary content' do
let(:commits) { merge_request_diff.commits.map(&:to_hash) }
let(:expected_diffs) { diffs }
......
......@@ -210,7 +210,11 @@ describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads::Event do
end
end
describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads do
##
# The background migration relies on a temporary table, hence we're migrating
# to a specific version of the database where said table is still present.
#
describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads, :migration, schema: 20170608152748 do
let(:migration) { described_class.new }
let(:project) { create(:project_empty_repo) }
let(:author) { create(:user) }
......@@ -229,21 +233,6 @@ describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads do
)
end
# The background migration relies on a temporary table, hence we're migrating
# to a specific version of the database where said table is still present.
before :all do
ActiveRecord::Migration.verbose = false
ActiveRecord::Migrator
.migrate(ActiveRecord::Migrator.migrations_paths, 20170608152748)
end
after :all do
ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths)
ActiveRecord::Migration.verbose = true
end
describe '#perform' do
it 'returns if data should not be migrated' do
allow(migration).to receive(:migrate?).and_return(false)
......
......@@ -6,9 +6,9 @@ describe Gitlab::Diff::InlineDiffMarkdownMarker do
let(:inline_diffs) { [2..5] }
let(:subject) { described_class.new(raw).mark(inline_diffs, mode: :deletion) }
it 'marks the range' do
expect(subject).to eq("ab{-c &#39;d-}ef&#39;")
expect(subject).to be_html_safe
it 'does not escape html etities and marks the range' do
expect(subject).to eq("ab{-c 'd-}ef'")
expect(subject).not_to be_html_safe
end
end
end
......@@ -2,11 +2,13 @@ require 'spec_helper'
describe Gitlab::Diff::InlineDiffMarker do
describe '#mark' do
context "when the rich text is html safe" do
let(:inline_diffs) { [2..5] }
let(:raw) { "abc 'def'" }
subject { described_class.new(raw, rich).mark(inline_diffs) }
context "when the rich text is html safe" do
let(:rich) { %{<span class="abc">abc</span><span class="space"> </span><span class="def">&#39;def&#39;</span>}.html_safe }
let(:inline_diffs) { [2..5] }
let(:subject) { described_class.new(raw, rich).mark(inline_diffs) }
it 'marks the range' do
expect(subject).to eq(%{<span class="abc">ab<span class="idiff left">c</span></span><span class="space"><span class="idiff"> </span></span><span class="def"><span class="idiff right">&#39;d</span>ef&#39;</span>})
......@@ -15,12 +17,10 @@ describe Gitlab::Diff::InlineDiffMarker do
end
context "when the text text is not html safe" do
let(:raw) { "abc 'def'" }
let(:inline_diffs) { [2..5] }
let(:subject) { described_class.new(raw).mark(inline_diffs) }
let(:rich) { "abc 'def' differs" }
it 'marks the range' do
expect(subject).to eq(%{ab<span class="idiff left right">c &#39;d</span>ef&#39;})
expect(subject).to eq(%{ab<span class="idiff left right">c &#39;d</span>ef&#39; differs})
expect(subject).to be_html_safe
end
end
......
......@@ -1098,6 +1098,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
describe '#tag_exists?' do
shared_examples 'checks the existence of tags' do
it 'returns true for an existing tag' do
tag = repository.tag_names.first
......@@ -1109,7 +1110,17 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
context 'when Gitaly ref_exists_tags feature is enabled' do
it_behaves_like 'checks the existence of tags'
end
context 'when Gitaly ref_exists_tags feature is disabled', skip_gitaly_mock: true do
it_behaves_like 'checks the existence of tags'
end
end
describe '#branch_exists?' do
shared_examples 'checks the existence of branches' do
it 'returns true for an existing branch' do
expect(repository.branch_exists?('master')).to eq(true)
end
......@@ -1123,6 +1134,15 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
context 'when Gitaly ref_exists_branches feature is enabled' do
it_behaves_like 'checks the existence of branches'
end
context 'when Gitaly ref_exists_branches feature is disabled', skip_gitaly_mock: true do
it_behaves_like 'checks the existence of branches'
end
end
describe '#local_branches' do
before(:all) do
@repo = Gitlab::Git::Repository.new('default', File.join(TEST_MUTABLE_REPO_PATH, '.git'))
......
require 'spec_helper'
describe Gitlab::GitalyClient::RefService do
let(:project) { create(:project) }
let(:project) { create(:project, :repository) }
let(:storage_name) { project.repository_storage }
let(:relative_path) { project.disk_path + '.git' }
let(:client) { described_class.new(project.repository) }
let(:repository) { project.repository }
let(:client) { described_class.new(repository) }
describe '#branches' do
it 'sends a find_all_branches message' do
......@@ -84,11 +85,23 @@ describe Gitlab::GitalyClient::RefService do
end
describe '#find_ref_name', seed_helper: true do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) }
let(:client) { described_class.new(repository) }
subject { client.find_ref_name(SeedRepo::Commit::ID, 'refs/heads/master') }
it { is_expected.to be_utf8 }
it { is_expected.to eq('refs/heads/master') }
end
describe '#ref_exists?', seed_helper: true do
it 'finds the master branch ref' do
expect(client.ref_exists?('refs/heads/master')).to eq(true)
end
it 'returns false for an illegal tag name ref' do
expect(client.ref_exists?('refs/tags/.this-tag-name-is-illegal')).to eq(false)
end
it 'raises an argument error if the ref name parameter does not start with refs/' do
expect { client.ref_exists?('reXXXXX') }.to raise_error(ArgumentError)
end
end
end
require 'spec_helper'
describe Gitlab::GitalyClient::RepositoryService do
let(:project) { create(:project) }
let(:storage_name) { project.repository_storage }
let(:relative_path) { project.disk_path + '.git' }
let(:client) { described_class.new(project.repository) }
describe '#exists?' do
it 'sends a repository_exists message' do
expect_any_instance_of(Gitaly::RepositoryService::Stub)
.to receive(:repository_exists)
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
.and_return(double(exists: true))
client.exists?
end
end
describe '#garbage_collect' do
it 'sends a garbage_collect message' do
expect_any_instance_of(Gitaly::RepositoryService::Stub)
.to receive(:garbage_collect)
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
.and_return(double(:garbage_collect_response))
client.garbage_collect(true)
end
end
describe '#repack_full' do
it 'sends a repack_full message' do
expect_any_instance_of(Gitaly::RepositoryService::Stub)
.to receive(:repack_full)
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
.and_return(double(:repack_full_response))
client.repack_full(true)
end
end
describe '#repack_incremental' do
it 'sends a repack_incremental message' do
expect_any_instance_of(Gitaly::RepositoryService::Stub)
.to receive(:repack_incremental)
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
.and_return(double(:repack_incremental_response))
client.repack_incremental
end
end
describe '#repository_size' do
it 'sends a repository_size message' do
expect_any_instance_of(Gitaly::RepositoryService::Stub)
.to receive(:repository_size)
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
.and_return(size: 0)
client.repository_size
end
end
describe '#apply_gitattributes' do
let(:revision) { 'master' }
it 'sends an apply_gitattributes message' do
expect_any_instance_of(Gitaly::RepositoryService::Stub)
.to receive(:apply_gitattributes)
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
.and_return(double(:apply_gitattributes_response))
client.apply_gitattributes(revision)
end
end
end
......@@ -2,6 +2,20 @@
"description": "Nisi et repellendus ut enim quo accusamus vel magnam.",
"visibility_level": 10,
"archived": false,
"milestones": [
{
"id": 1,
"title": "test milestone",
"project_id": 8,
"description": "test milestone",
"due_date": null,
"created_at": "2016-06-14T15:02:04.415Z",
"updated_at": "2016-06-14T15:02:04.415Z",
"state": "active",
"iid": 1,
"group_id": null
}
],
"labels": [
{
"id": 2,
......@@ -13,20 +27,6 @@
"template": false,
"description": "",
"type": "ProjectLabel",
"priorities": [
]
},
{
"id": 3,
"title": "test3",
"color": "#428bca",
"group_id": 8,
"created_at": "2016-07-22T08:55:44.161Z",
"updated_at": "2016-07-22T08:55:44.161Z",
"template": false,
"description": "",
"project_id": null,
"type": "GroupLabel",
"priorities": [
{
"id": 1,
......@@ -39,6 +39,76 @@
]
}
],
"issues": [
{
"id": 1,
"title": "Fugiat est minima quae maxime non similique.",
"assignee_id": null,
"project_id": 8,
"author_id": 1,
"created_at": "2017-07-07T18:13:01.138Z",
"updated_at": "2017-08-15T18:37:40.807Z",
"branch_name": null,
"description": "Quam totam fuga numquam in eveniet.",
"state": "opened",
"iid": 20,
"updated_by_id": 1,
"confidential": false,
"deleted_at": null,
"due_date": null,
"moved_to_id": null,
"lock_version": null,
"time_estimate": 0,
"closed_at": null,
"last_edited_at": null,
"last_edited_by_id": null,
"group_milestone_id": null,
"label_links": [
{
"id": 11,
"label_id": 6,
"target_id": 1,
"target_type": "Issue",
"created_at": "2017-08-15T18:37:40.795Z",
"updated_at": "2017-08-15T18:37:40.795Z",
"label": {
"id": 6,
"title": "group label",
"color": "#A8D695",
"project_id": null,
"created_at": "2017-08-15T18:37:19.698Z",
"updated_at": "2017-08-15T18:37:19.698Z",
"template": false,
"description": "",
"group_id": 5,
"type": "GroupLabel",
"priorities": []
}
},
{
"id": 11,
"label_id": 2,
"target_id": 1,
"target_type": "Issue",
"created_at": "2017-08-15T18:37:40.795Z",
"updated_at": "2017-08-15T18:37:40.795Z",
"label": {
"id": 6,
"title": "project label",
"color": "#A8D695",
"project_id": null,
"created_at": "2017-08-15T18:37:19.698Z",
"updated_at": "2017-08-15T18:37:19.698Z",
"template": false,
"description": "",
"group_id": 5,
"type": "ProjectLabel",
"priorities": []
}
}
]
}
],
"snippets": [
],
......
......@@ -183,7 +183,8 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
let(:restored_project_json) { project_tree_restorer.restore }
before do
allow(ImportExport).to receive(:project_filename).and_return('project.light.json')
project_tree_restorer.instance_variable_set(:@path, "spec/lib/gitlab/import_export/project.light.json")
allow(shared).to receive(:export_path).and_return('spec/lib/gitlab/import_export/')
end
......@@ -195,7 +196,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
restored_project_json
expect(shared.errors.first).not_to include('test')
expect(shared.errors.first).to be_nil
end
end
end
......@@ -219,15 +220,42 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end
before do
project_tree_restorer.instance_variable_set(:@path, "spec/lib/gitlab/import_export/project.light.json")
restored_project_json
end
it 'has group labels' do
expect(GroupLabel.count).to eq(1)
it 'correctly restores project' do
expect(restored_project_json).to be_truthy
expect(shared.errors).to be_empty
end
it 'has labels' do
expect(project.labels.count).to eq(2)
end
it 'creates group label' do
expect(project.group.labels.count).to eq(1)
end
it 'has label priorities' do
expect(GroupLabel.first.priorities).not_to be_empty
expect(project.labels.first.priorities).not_to be_empty
end
it 'has milestones' do
expect(project.milestones.count).to eq(1)
end
it 'has issue' do
expect(project.issues.count).to eq(1)
expect(project.issues.first.labels.count).to eq(2)
end
it 'has issue with group label and project label' do
labels = project.issues.first.labels
expect(labels.where(type: "GroupLabel").count).to eq(1)
expect(labels.where(type: "ProjectLabel").count).to eq(1)
end
end
end
......
......@@ -2,34 +2,39 @@ require 'spec_helper'
describe Gitlab::StringRangeMarker do
describe '#mark' do
context "when the rich text is html safe" do
let(:raw) { "abc <def>" }
let(:rich) { %{<span class="abc">abc</span><span class="space"> </span><span class="def">&lt;def&gt;</span>}.html_safe }
let(:inline_diffs) { [2..5] }
subject do
def mark_diff(rich = nil)
raw = 'abc <def>'
inline_diffs = [2..5]
described_class.new(raw, rich).mark(inline_diffs) do |text, left:, right:|
"LEFT#{text}RIGHT"
end
end
context "when the rich text is html safe" do
let(:rich) { %{<span class="abc">abc</span><span class="space"> </span><span class="def">&lt;def&gt;</span>}.html_safe }
it 'marks the inline diffs' do
expect(subject).to eq(%{<span class="abc">abLEFTcRIGHT</span><span class="space">LEFT RIGHT</span><span class="def">LEFT&lt;dRIGHTef&gt;</span>})
expect(subject).to be_html_safe
expect(mark_diff(rich)).to eq(%{<span class="abc">abLEFTcRIGHT</span><span class="space">LEFT RIGHT</span><span class="def">LEFT&lt;dRIGHTef&gt;</span>})
expect(mark_diff(rich)).to be_html_safe
end
end
context "when the rich text is not html safe" do
let(:raw) { "abc <def>" }
let(:inline_diffs) { [2..5] }
subject do
described_class.new(raw).mark(inline_diffs) do |text, left:, right:|
"LEFT#{text}RIGHT"
context 'when rich text equals raw text' do
it 'marks the inline diffs' do
expect(mark_diff).to eq(%{abLEFTc <dRIGHTef>})
expect(mark_diff).not_to be_html_safe
end
end
context 'when rich text doeas not equal raw text' do
let(:rich) { "abc <def> differs" }
it 'marks the inline diffs' do
expect(subject).to eq(%{abLEFTc &lt;dRIGHTef&gt;})
expect(subject).to be_html_safe
expect(mark_diff(rich)).to eq(%{abLEFTc &lt;dRIGHTef&gt; differs})
expect(mark_diff(rich)).to be_html_safe
end
end
end
end
......
......@@ -28,6 +28,14 @@ The `after` hook will migrate the database **up** and reinstitutes the latest
schema version, so that the process does not affect subsequent specs and
ensures proper isolation.
## Testing a class that is not an ActiveRecord::Migration
In order to test a class that is not a migration itself, you will need to
manually provide a required schema version. Please add a `schema` tag to a
context that you want to switch the database schema within.
Example: `describe SomeClass, :migration, schema: 20170608152748`.
## Available helpers
Use `table` helper to create a temporary `ActiveRecord::Base` derived model
......@@ -80,8 +88,6 @@ end
## Best practices
1. Use only one test example per migration unless there is a good reason to
use more.
1. Note that this type of tests do not run within the transaction, we use
a truncation database cleanup strategy. Do not depend on transaction being
present.
......@@ -1610,8 +1610,7 @@ describe Project do
it 'imports a project' do
expect_any_instance_of(RepositoryImportWorker).to receive(:perform).and_call_original
project.import_schedule
expect { project.import_schedule }.to change { project.import_jid }
expect(project.reload.import_status).to eq('finished')
end
end
......@@ -1624,6 +1623,13 @@ describe Project do
allow(Projects::HousekeepingService).to receive(:new) { housekeeping_service }
end
it 'resets project import_error' do
error_message = 'Some error'
mirror = create(:project_empty_repo, :import_started, import_error: error_message)
expect { mirror.import_finish }.to change { mirror.import_error }.from(error_message).to(nil)
end
it 'performs housekeeping when an import of a fresh project is completed' do
project = create(:project_empty_repo, :import_started, import_type: :github)
......@@ -1730,17 +1736,21 @@ describe Project do
end
describe '#add_import_job' do
let(:import_jid) { '123' }
context 'forked' do
let(:forked_project_link) { create(:forked_project_link, :forked_to_empty_project) }
let(:forked_from_project) { forked_project_link.forked_from_project }
let(:project) { forked_project_link.forked_to_project }
it 'schedules a RepositoryForkWorker job' do
expect(RepositoryForkWorker).to receive(:perform_async)
.with(project.id, forked_from_project.repository_storage_path,
forked_from_project.disk_path, project.namespace.full_path)
expect(RepositoryForkWorker).to receive(:perform_async).with(
project.id,
forked_from_project.repository_storage_path,
forked_from_project.disk_path,
project.namespace.full_path).and_return(import_jid)
project.add_import_job
expect(project.add_import_job).to eq(import_jid)
end
end
......@@ -1748,9 +1758,8 @@ describe Project do
it 'schedules a RepositoryImportWorker job' do
project = create(:project, import_url: generate(:url))
expect(RepositoryImportWorker).to receive(:perform_async).with(project.id)
project.add_import_job
expect(RepositoryImportWorker).to receive(:perform_async).with(project.id).and_return(import_jid)
expect(project.add_import_job).to eq(import_jid)
end
end
end
......@@ -2311,6 +2320,44 @@ describe Project do
end
end
describe '#remove_pages' do
let(:project) { create(:project) }
let(:namespace) { project.namespace }
let(:pages_path) { project.pages_path }
around do |example|
FileUtils.mkdir_p(pages_path)
begin
example.run
ensure
FileUtils.rm_rf(pages_path)
end
end
it 'removes the pages directory' do
expect_any_instance_of(Projects::UpdatePagesConfigurationService).to receive(:execute)
expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project).and_return(true)
expect(PagesWorker).to receive(:perform_in).with(5.minutes, :remove, namespace.full_path, anything)
project.remove_pages
end
it 'is a no-op when there is no namespace' do
project.update_column(:namespace_id, nil)
expect_any_instance_of(Projects::UpdatePagesConfigurationService).not_to receive(:execute)
expect_any_instance_of(Gitlab::PagesTransfer).not_to receive(:rename_project)
project.remove_pages
end
it 'is run when the project is destroyed' do
expect(project).to receive(:remove_pages).and_call_original
project.destroy
end
end
describe '#forks_count' do
it 'returns the number of forks' do
project = build(:project)
......
......@@ -132,17 +132,12 @@ RSpec.configure do |config|
Sidekiq.redis(&:flushall)
end
config.before(:example, :migration) do
ActiveRecord::Migrator
.migrate(migrations_paths, previous_migration.version)
reset_column_in_migration_models
config.before(:each, :migration) do
schema_migrate_down!
end
config.after(:example, :migration) do
ActiveRecord::Migrator.migrate(migrations_paths)
reset_column_in_migration_models
config.after(:context, :migration) do
schema_migrate_up!
end
config.around(:each, :nested_groups) do |example|
......
......@@ -20,7 +20,7 @@ RSpec.configure do |config|
end
config.before(:each, :migration) do
DatabaseCleaner.strategy = :truncation
DatabaseCleaner.strategy = :truncation, { cache_tables: false }
end
config.before(:each) do
......
......@@ -31,6 +31,35 @@ module MigrationsHelpers
end
end
def migration_schema_version
self.class.metadata[:schema] || previous_migration.version
end
def schema_migrate_down!
disable_migrations_output do
ActiveRecord::Migrator.migrate(migrations_paths,
migration_schema_version)
end
reset_column_in_migration_models
end
def schema_migrate_up!
disable_migrations_output do
ActiveRecord::Migrator.migrate(migrations_paths)
end
reset_column_in_migration_models
end
def disable_migrations_output
ActiveRecord::Migration.verbose = false
yield
ensure
ActiveRecord::Migration.verbose = true
end
def migrate!
ActiveRecord::Migrator.up(migrations_paths) do |migration|
migration.name == described_class.name
......
......@@ -22,8 +22,8 @@ describe RepositoryImportWorker do
it 'hide the credentials that were used in the import URL' do
error = %q{remote: Not Found fatal: repository 'https://user:pass@test.com/root/repoC.git/' not found }
project.update_attributes(import_jid: '123')
expect_any_instance_of(Projects::ImportService).to receive(:execute).and_return({ status: :error, message: error })
allow(subject).to receive(:jid).and_return('123')
expect do
subject.perform(project.id)
......
......@@ -8,29 +8,29 @@ describe StuckImportJobsWorker do
allow_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain).and_return(exclusive_lease_uuid)
end
describe 'long running import' do
let(:project) { create(:project, import_jid: '123', import_status: 'started') }
describe 'with started import_status' do
let(:project) { create(:project, :import_started, import_jid: '123') }
before do
describe 'long running import' do
it 'marks the project as failed' do
allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return(['123'])
end
it 'marks the project as failed' do
expect { worker.perform }.to change { project.reload.import_status }.to('failed')
end
end
describe 'running import' do
let(:project) { create(:project, import_jid: '123', import_status: 'started') }
before do
it 'does not mark the project as failed' do
allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return([])
end
it 'does not mark the project as failed' do
worker.perform
expect { worker.perform }.not_to change { project.reload.import_status }
end
expect(project.reload.import_status).to eq('started')
describe 'import without import_jid' do
it 'marks the project as failed' do
expect { worker.perform }.to change { project.reload.import_status }.to('failed')
end
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment