Commit 68ddb4c7 authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'upstream/master' into 14995-custom_wiki_sidebar

* upstream/master: (53 commits)
  Fix invalid link to GitLab.com architecture.md
  i18n: externalize strings from 'app/views/import'
  Remove Repository#lookup and unreachable rugged code
  refactor code based on feedback
  Trigger rails5 tests either by variable or ref name
  Fix link in help doc which was linking to old mono-repo, now in its own repo
  Allow Danger step to fail
  update webpack to v4.16
  Backport logger changes from EE
  Add the CI Job trigger as the build trigger
  Remove flaky and redundant expectations
  use fileuploader dynamic path method in uploads manager and add spec
  fix typo in uploads manager
  add small comment to download method in uploads manager
  refactor uploads manager
  Update 10.6-to-10.7.md
  Lazy-load performance bar UI
  Update .gitlab-ci.yml
  fixed test to correctly text relative URLs doesnt add query param if source & target projects match
  Add changelog entry
  ...
parents 551955b5 a73f4807
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.4-golang-1.9-git-2.17-chrome-67.0-node-8.x-yarn-1.2-postgresql-9.6-graphicsmagick-1.3.29"
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.4-golang-1.9-git-2.18-chrome-67.0-node-8.x-yarn-1.2-postgresql-9.6-graphicsmagick-1.3.29"
.dedicated-runner: &dedicated-runner
retry: 1
......@@ -86,7 +86,9 @@ stages:
.rails5: &rails5
allow_failure: true
only:
- /rails5/
variables:
- $CI_COMMIT_REF_NAME =~ /rails5/
- $RAILS5_ENABLED
variables:
BUNDLE_GEMFILE: "Gemfile.rails5"
RAILS5: "true"
......@@ -327,7 +329,7 @@ cloud-native-image:
cache: {}
script:
- gem install gitlab --no-ri --no-rdoc
- ./scripts/trigger-build cng
- BUILD_TRIGGER_TOKEN=$CI_JOB_TOKEN scripts/trigger-build cng
only:
- tags@gitlab-org/gitlab-ce
- tags@gitlab-org/gitlab-ee
......@@ -351,6 +353,7 @@ retrieve-tests-metadata:
danger-review:
image: registry.gitlab.com/gitlab-org/gitaly/dangercontainer:latest
stage: prepare
allow_failure: true
before_script:
- source scripts/utils.sh
- retry gem install danger --no-ri --no-rdoc
......
......@@ -101,6 +101,7 @@ router.beforeEach((to, from, next) => {
store
.dispatch('getMergeRequestData', {
projectId: fullProjectId,
targetProjectId: to.query.target_project,
mergeRequestId: to.params.mrid,
})
.then(mr => {
......@@ -119,12 +120,14 @@ router.beforeEach((to, from, next) => {
.then(() =>
store.dispatch('getMergeRequestVersions', {
projectId: fullProjectId,
targetProjectId: to.query.target_project,
mergeRequestId: to.params.mrid,
}),
)
.then(() =>
store.dispatch('getMergeRequestChanges', {
projectId: fullProjectId,
targetProjectId: to.query.target_project,
mergeRequestId: to.params.mrid,
}),
)
......
......@@ -4,12 +4,14 @@ import * as types from '../mutation_types';
export const getMergeRequestData = (
{ commit, dispatch, state },
{ projectId, mergeRequestId, force = false } = {},
{ projectId, mergeRequestId, targetProjectId = null, force = false } = {},
) =>
new Promise((resolve, reject) => {
if (!state.projects[projectId].mergeRequests[mergeRequestId] || force) {
service
.getProjectMergeRequestData(projectId, mergeRequestId, { render_html: true })
.getProjectMergeRequestData(targetProjectId || projectId, mergeRequestId, {
render_html: true,
})
.then(({ data }) => {
commit(types.SET_MERGE_REQUEST, {
projectPath: projectId,
......@@ -38,12 +40,12 @@ export const getMergeRequestData = (
export const getMergeRequestChanges = (
{ commit, dispatch, state },
{ projectId, mergeRequestId, force = false } = {},
{ projectId, mergeRequestId, targetProjectId = null, force = false } = {},
) =>
new Promise((resolve, reject) => {
if (!state.projects[projectId].mergeRequests[mergeRequestId].changes.length || force) {
service
.getProjectMergeRequestChanges(projectId, mergeRequestId)
.getProjectMergeRequestChanges(targetProjectId || projectId, mergeRequestId)
.then(({ data }) => {
commit(types.SET_MERGE_REQUEST_CHANGES, {
projectPath: projectId,
......@@ -71,12 +73,12 @@ export const getMergeRequestChanges = (
export const getMergeRequestVersions = (
{ commit, dispatch, state },
{ projectId, mergeRequestId, force = false } = {},
{ projectId, mergeRequestId, targetProjectId = null, force = false } = {},
) =>
new Promise((resolve, reject) => {
if (!state.projects[projectId].mergeRequests[mergeRequestId].versions.length || force) {
service
.getProjectMergeRequestVersions(projectId, mergeRequestId)
.getProjectMergeRequestVersions(targetProjectId || projectId, mergeRequestId)
.then(res => res.data)
.then(data => {
commit(types.SET_MERGE_REQUEST_VERSIONS, {
......
<script>
import $ from 'jquery';
import PerformanceBarService from '../services/performance_bar_service';
import detailedMetric from './detailed_metric.vue';
import requestSelector from './request_selector.vue';
import simpleMetric from './simple_metric.vue';
import Flash from '../../flash';
export default {
components: {
detailedMetric,
......@@ -69,37 +66,13 @@ export default {
},
},
mounted() {
this.interceptor = PerformanceBarService.registerInterceptor(
this.peekUrl,
this.loadRequestDetails,
);
this.loadRequestDetails(this.requestId, window.location.href);
this.currentRequest = this.requestId;
if (this.lineProfileModal.length) {
this.lineProfileModal.modal('toggle');
}
},
beforeDestroy() {
PerformanceBarService.removeInterceptor(this.interceptor);
},
methods: {
loadRequestDetails(requestId, requestUrl) {
if (!this.store.canTrackRequest(requestUrl)) {
return;
}
this.store.addRequest(requestId, requestUrl);
PerformanceBarService.fetchRequestDetails(this.peekUrl, requestId)
.then(res => {
this.store.addRequestDetails(requestId, res.data.data);
})
.catch(() =>
Flash(`Error getting performance bar results for ${requestId}`),
);
},
changeCurrentRequest(newRequestId) {
this.currentRequest = newRequestId;
},
......
import Vue from 'vue';
import performanceBarApp from './components/performance_bar_app.vue';
import Flash from '../flash';
import PerformanceBarService from './services/performance_bar_service';
import PerformanceBarStore from './stores/performance_bar_store';
export default ({ container }) =>
new Vue({
el: container,
components: {
performanceBarApp,
performanceBarApp: () => import('./components/performance_bar_app.vue'),
},
data() {
const performanceBarData = document.querySelector(this.$options.el)
......@@ -21,6 +22,34 @@ export default ({ container }) =>
profileUrl: performanceBarData.profileUrl,
};
},
mounted() {
this.interceptor = PerformanceBarService.registerInterceptor(
this.peekUrl,
this.loadRequestDetails,
);
this.loadRequestDetails(this.requestId, window.location.href);
},
beforeDestroy() {
PerformanceBarService.removeInterceptor(this.interceptor);
},
methods: {
loadRequestDetails(requestId, requestUrl) {
if (!this.store.canTrackRequest(requestUrl)) {
return;
}
this.store.addRequest(requestId, requestUrl);
PerformanceBarService.fetchRequestDetails(this.peekUrl, requestId)
.then(res => {
this.store.addRequestDetails(requestId, res.data.data);
})
.catch(() =>
Flash(`Error getting performance bar results for ${requestId}`),
);
},
},
render(createElement) {
return createElement('performance-bar-app', {
props: {
......
<script>
import tooltip from '~/vue_shared/directives/tooltip';
import { n__ } from '~/locale';
import { webIDEUrl } from '~/lib/utils/url_utility';
import { mergeUrlParams, webIDEUrl } from '~/lib/utils/url_utility';
import Icon from '~/vue_shared/components/icon.vue';
import clipboardButton from '~/vue_shared/components/clipboard_button.vue';
......@@ -43,7 +43,10 @@ export default {
return this.isBranchTitleLong(this.mr.targetBranch);
},
webIdePath() {
return webIDEUrl(this.mr.statusPath.replace('.json', ''));
return mergeUrlParams({
target_project: this.mr.sourceProjectFullPath !== this.mr.targetProjectFullPath ?
this.mr.targetProjectFullPath : '',
}, webIDEUrl(`/${this.mr.sourceProjectFullPath}/merge_requests/${this.mr.iid}`));
},
},
methods: {
......
......@@ -16,10 +16,11 @@ export default class MergeRequestStore {
const pipelineStatus = data.pipeline ? data.pipeline.details.status : null;
this.squash = data.squash;
this.squashBeforeMergeHelpPath = this.squashBeforeMergeHelpPath ||
data.squash_before_merge_help_path;
this.squashBeforeMergeHelpPath =
this.squashBeforeMergeHelpPath || data.squash_before_merge_help_path;
this.enableSquashBeforeMerge = this.enableSquashBeforeMerge || true;
this.iid = data.iid;
this.title = data.title;
this.targetBranch = data.target_branch;
this.sourceBranch = data.source_branch;
......@@ -85,6 +86,8 @@ export default class MergeRequestStore {
this.isMergeAllowed = data.mergeable || false;
this.mergeOngoing = data.merge_ongoing;
this.allowCollaboration = data.allow_collaboration;
this.targetProjectFullPath = data.target_project_full_path;
this.sourceProjectFullPath = data.source_project_full_path;
// Cherry-pick and Revert actions related
this.canCherryPickInCurrentMR = currentUser.can_cherry_pick_on_current_merge_request || false;
......@@ -97,7 +100,8 @@ export default class MergeRequestStore {
this.hasCI = data.has_ci;
this.ciStatus = data.ci_status;
this.isPipelineFailed = this.ciStatus === 'failed' || this.ciStatus === 'canceled';
this.isPipelinePassing = this.ciStatus === 'success' || this.ciStatus === 'success_with_warnings';
this.isPipelinePassing =
this.ciStatus === 'success' || this.ciStatus === 'success_with_warnings';
this.isPipelineSkipped = this.ciStatus === 'skipped';
this.pipelineDetailedStatus = pipelineStatus;
this.isPipelineActive = data.pipeline ? data.pipeline.active : false;
......
......@@ -321,11 +321,18 @@
}
&.activities {
display: flex;
border-bottom: 1px solid $border-color;
overflow: hidden;
.nav-links {
border-bottom: 0;
}
@include media-breakpoint-down(xs) {
display: block;
overflow: visible;
}
}
}
......
......@@ -653,6 +653,7 @@ module Ci
variables.append(key: 'CI_JOB_NAME', value: name)
variables.append(key: 'CI_JOB_STAGE', value: stage)
variables.append(key: 'CI_COMMIT_SHA', value: sha)
variables.append(key: 'CI_COMMIT_BEFORE_SHA', value: before_sha)
variables.append(key: 'CI_COMMIT_REF_NAME', value: ref)
variables.append(key: 'CI_COMMIT_REF_SLUG', value: ref_slug)
variables.append(key: "CI_COMMIT_TAG", value: ref) if tag?
......
......@@ -92,10 +92,6 @@ class Deployment < ActiveRecord::Base
@stop_action ||= manual_actions.find_by(name: on_stop)
end
def stop_action?
stop_action.present?
end
def formatted_deployment_time
created_at.to_time.in_time_zone.to_s(:medium)
end
......
......@@ -117,7 +117,7 @@ class Environment < ActiveRecord::Base
external_url.gsub(%r{\A.*?://}, '')
end
def stop_action?
def stop_action_available?
available? && stop_action.present?
end
......
......@@ -368,8 +368,10 @@ class Project < ActiveRecord::Base
chronic_duration_attr :build_timeout_human_readable, :build_timeout, default: 3600
validates :build_timeout, allow_nil: true,
numericality: { greater_than_or_equal_to: 600,
message: 'needs to be at least 10 minutes' }
numericality: { greater_than_or_equal_to: 10.minutes,
less_than: 1.month,
only_integer: true,
message: 'needs to be beetween 10 minutes and 1 month' }
# Returns a collection of projects that is either public or visible to the
# logged in user.
......
......@@ -2,11 +2,12 @@ class EnvironmentPolicy < BasePolicy
delegate { @subject.project }
condition(:stop_with_deployment_allowed) do
@subject.stop_action? && can?(:create_deployment) && can?(:update_build, @subject.stop_action)
@subject.stop_action_available? &&
can?(:create_deployment) && can?(:update_build, @subject.stop_action)
end
condition(:stop_with_update_allowed) do
!@subject.stop_action? && can?(:update_environment, @subject)
!@subject.stop_action_available? && can?(:update_environment, @subject)
end
rule { stop_with_deployment_allowed | stop_with_update_allowed }.enable :stop_environment
......
......@@ -7,7 +7,7 @@ class EnvironmentEntity < Grape::Entity
expose :external_url
expose :environment_type
expose :last_deployment, using: DeploymentEntity
expose :stop_action?, as: :has_stop_action
expose :stop_action_available?, as: :has_stop_action
expose :metrics_path, if: -> (environment, _) { environment.has_metrics? } do |environment|
metrics_project_environment_path(environment.project, environment)
......
......@@ -10,9 +10,15 @@ class MergeRequestWidgetEntity < IssuableEntity
expose :merge_when_pipeline_succeeds
expose :source_branch
expose :source_project_id
expose :source_project_full_path do |merge_request|
merge_request.source_project&.full_path
end
expose :squash
expose :target_branch
expose :target_project_id
expose :target_project_full_path do |merge_request|
merge_request.project&.full_path
end
expose :allow_collaboration
expose :should_be_rebased?, as: :should_be_rebased
......
......@@ -8,7 +8,7 @@ module Ci
return unless @ref.present?
environments.each do |environment|
next unless environment.stop_action?
next unless environment.stop_action_available?
next unless can?(current_user, :stop_environment, environment)
environment.stop_with_action!(current_user)
......
class UploadService
def initialize(model, file, uploader_class = FileUploader)
@model, @file, @uploader_class = model, file, uploader_class
def initialize(model, file, uploader_class = FileUploader, **uploader_context)
@model, @file, @uploader_class, @uploader_context = model, file, uploader_class, uploader_context
end
def execute
return nil unless @file && @file.size <= max_attachment_size
uploader = @uploader_class.new(@model)
uploader = @uploader_class.new(@model, nil, @uploader_context)
uploader.store!(@file)
uploader.to_h
......
......@@ -15,7 +15,7 @@ class FileUploader < GitlabUploader
prepend ObjectStorage::Extension::RecordsUploads
MARKDOWN_PATTERN = %r{\!?\[.*?\]\(/uploads/(?<secret>[0-9a-f]{32})/(?<file>.*?)\)}
DYNAMIC_PATH_PATTERN = %r{(?<secret>\h{32})/(?<identifier>.*)}
DYNAMIC_PATH_PATTERN = %r{.*(?<secret>\h{32})/(?<identifier>.*)}
after :remove, :prune_store_dir
......@@ -67,6 +67,10 @@ class FileUploader < GitlabUploader
SecureRandom.hex
end
def self.extract_dynamic_path(path)
DYNAMIC_PATH_PATTERN.match(path)
end
def upload_paths(identifier)
[
File.join(secret, identifier),
......@@ -143,7 +147,7 @@ class FileUploader < GitlabUploader
return if apply_context!(value.uploader_context)
# fallback to the regex based extraction
if matches = DYNAMIC_PATH_PATTERN.match(value.path)
if matches = self.class.extract_dynamic_path(value.path)
@secret = matches[:secret]
@identifier = matches[:identifier]
end
......
.nav-block.activities
= render 'shared/event_filter'
.controls
= link_to dashboard_projects_path(rss_url_options), class: 'btn rss-btn has-tooltip', title: 'Subscribe' do
%i.fa.fa-rss
= render 'shared/event_filter'
.content_list
= spinner
......@@ -2,9 +2,9 @@
= form_tag oauth_application_path(application) do
%input{ :name => "_method", :type => "hidden", :value => "delete" }/
- if defined? small
= button_tag type: "submit", class: "btn btn-transparent", data: { confirm: "Are you sure?" } do
= button_tag type: "submit", class: "btn btn-transparent", data: { confirm: _("Are you sure?") } do
%span.sr-only
Destroy
= _('Destroy')
= icon('trash')
- else
= submit_tag 'Destroy', data: { confirm: "Are you sure?" }, class: submit_btn_css
= submit_tag _('Destroy'), data: { confirm: _("Are you sure?") }, class: submit_btn_css
......@@ -10,16 +10,14 @@
= f.text_area :redirect_uri, class: 'form-control', required: true
%span.form-text.text-muted
Use one line per URI
= _('Use one line per URI')
- if Doorkeeper.configuration.native_redirect_uri
%span.form-text.text-muted
Use
%code= Doorkeeper.configuration.native_redirect_uri
for local tests
= _('Use <code>%{native_redirect_uri}</code> for local tests').html_safe % { native_redirect_uri: Doorkeeper.configuration.native_redirect_uri }
.form-group
= f.label :scopes, class: 'label-light'
= render 'shared/tokens/scopes_form', prefix: 'doorkeeper_application', token: application, scopes: @scopes
.prepend-top-default
= f.submit 'Save application', class: "btn btn-create"
= f.submit _('Save application'), class: "btn btn-create"
- page_title "Edit", @application.name, "Applications"
- page_title _("Edit"), @application.name, _("Applications")
- @content_class = "limit-container-width" unless fluid_layout
%h3.page-title Edit application
%h3.page-title= _('Edit application')
= render 'form', application: @application
- page_title "Applications"
- page_title _("Applications")
- @content_class = "limit-container-width" unless fluid_layout
.row.prepend-top-default
......@@ -7,28 +7,27 @@
= page_title
%p
- if user_oauth_applications?
Manage applications that can use GitLab as an OAuth provider,
and applications that you've authorized to use your account.
= _("Manage applications that can use GitLab as an OAuth provider, and applications that you've authorized to use your account.")
- else
Manage applications that you've authorized to use your account.
= _("Manage applications that you've authorized to use your account.")
.col-lg-8
- if user_oauth_applications?
%h5.prepend-top-0
Add new application
= _('Add new application')
= render 'form', application: @application
%hr
- if user_oauth_applications?
.oauth-applications
%h5
Your applications (#{@applications.size})
= _("Your applications (%{size})") % { size: @applications.size }
- if @applications.any?
.table-responsive
%table.table
%thead
%tr
%th Name
%th Callback URL
%th Clients
%th= _('Name')
%th= _('Callback URL')
%th= _('Clients')
%th.last-heading
%tbody
- @applications.each do |application|
......@@ -41,25 +40,25 @@
%td
= link_to edit_oauth_application_path(application), class: "btn btn-transparent append-right-5" do
%span.sr-only
Edit
= _('Edit')
= icon('pencil')
= render 'delete_form', application: application, small: true
- else
.settings-message.text-center
You don't have any applications
= _("You don't have any applications")
.oauth-authorized-applications.prepend-top-20.append-bottom-default
- if user_oauth_applications?
%h5
Authorized applications (#{@authorized_tokens.size})
= _("Authorized applications (%{size})") % { size: @authorized_tokens.size }
- if @authorized_tokens.any?
.table-responsive
%table.table.table-striped
%thead
%tr
%th Name
%th Authorized At
%th Scope
%th= _('Name')
%th= _('Authorized At')
%th= _('Scope')
%th
%tbody
- @authorized_apps.each do |app|
......@@ -72,12 +71,12 @@
- @authorized_anonymous_tokens.each do |token|
%tr
%td
Anonymous
= _('Anonymous')
.form-text.text-muted
%em Authorization was granted by entering your username and password in the application.
%em= _("Authorization was granted by entering your username and password in the application.")
%td= token.created_at
%td= token.scopes
%td= render 'doorkeeper/authorized_applications/delete_form', token: token
- else
.settings-message.text-center
You don't have any authorized applications
= _("You don't have any authorized applications")
- page_title "New Application"
- page_title _("New Application")
%h3.page-title New Application
%h3.page-title= _("New Application")
%hr
......
- add_to_breadcrumbs "Applications", oauth_applications_path
- add_to_breadcrumbs _("Applications"), oauth_applications_path
- breadcrumb_title @application.name
- page_title @application.name, "Applications"
- page_title @application.name, _("Applications")
- @content_class = "limit-container-width" unless fluid_layout
%h3.page-title
Application: #{@application.name}
= _("Application: %{name}") % { name: @application.name }
.table-holder.oauth-application-show
%table.table
%tr
%td
Application Id
= _('Application Id')
%td
%code#application_id= @application.uid
%tr
%td
Secret:
= _('Secret:')
%td
%code#secret= @application.secret
%tr
%td
Callback url
= _('Callback url')
%td
- @application.redirect_uri.split.each do |uri|
%div
......@@ -30,5 +30,5 @@
= render "shared/tokens/scopes_list", token: @application
.form-actions
= link_to 'Edit', edit_oauth_application_path(@application), class: 'btn btn-primary wide float-left'
= link_to _('Edit'), edit_oauth_application_path(@application), class: 'btn btn-primary wide float-left'
= render 'delete_form', application: @application, submit_btn_css: 'btn btn-danger prepend-left-10'
%h3.page-title An error has occurred
%h3.page-title= _("An error has occurred")
%main{ :role => "main" }
%pre= @pre_auth.error_response.body[:error_description]
......@@ -3,34 +3,28 @@
.modal-content
.modal-header
%h3.page-title
Authorize
= link_to @pre_auth.client.name, @pre_auth.redirect_uri, target: '_blank', rel: 'noopener noreferrer'
to use your account?
- link_to_client = link_to(@pre_auth.client.name, @pre_auth.redirect_uri, target: '_blank', rel: 'noopener noreferrer')
= _("Authorize %{link_to_client} to use your account?")
.modal-body
- if current_user.admin?
.text-warning
%p
= icon("exclamation-triangle fw")
You are an admin, which means granting access to
%strong= @pre_auth.client.name
will allow them to interact with GitLab as an admin as well. Proceed with caution.
= _('You are an admin, which means granting access to <strong>%{client_name}</strong> will allow them to interact with GitLab as an admin as well. Proceed with caution.').html_safe % { client_name: @pre_auth.client.name }
%p
An application called
= link_to @pre_auth.client.name, @pre_auth.redirect_uri, target: '_blank', rel: 'noopener noreferrer'
is requesting access to your GitLab account.
- link_to_client = link_to(@pre_auth.client.name, @pre_auth.redirect_uri, target: '_blank', rel: 'noopener noreferrer')
= _("An application called %{link_to_client} is requesting access to your GitLab account.").html_safe % { link_to_client: link_to_client }
- auth_app_owner = @pre_auth.client.application.owner
- if auth_app_owner
This application was created by
= succeed "." do
= link_to auth_app_owner.name, user_path(auth_app_owner)
- link_to_owner = link_to(auth_app_owner.name, user_path(auth_app_owner))
= _("This application was created by %{link_to_owner}.").html_safe % { link_to_owner: link_to_owner }
Please note that this application is not provided by GitLab and you should verify its authenticity before
allowing access.
= _("Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access.")
- if @pre_auth.scopes
%p
This application will be able to:
= _("This application will be able to:")
%ul
- @pre_auth.scopes.each do |scope|
%li
......@@ -44,7 +38,7 @@
= hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope
= hidden_field_tag :nonce, @pre_auth.nonce
= submit_tag "Deny", class: "btn btn-danger"
= submit_tag _("Deny"), class: "btn btn-danger"
= form_tag oauth_authorization_path, method: :post, class: 'inline' do
= hidden_field_tag :client_id, @pre_auth.client.uid
= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
......@@ -52,4 +46,4 @@
= hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope
= hidden_field_tag :nonce, @pre_auth.nonce
= submit_tag "Authorize", class: "btn btn-success prepend-left-10"
= submit_tag _("Authorize"), class: "btn btn-success prepend-left-10"
%h3.page-title Authorization code:
%h3.page-title= _("Authorization code:")
%main{ :role => "main" }
%code#authorization_code= params[:code]
......@@ -6,4 +6,4 @@
= form_tag path do
%input{ :name => "_method", :type => "hidden", :value => "delete" }/
= submit_tag 'Revoke', onclick: "return confirm('Are you sure?')", class: 'btn btn-remove btn-sm'
= submit_tag _('Revoke'), onclick: "return confirm('#{_('Are you sure?')}')", class: 'btn btn-remove btn-sm'
%header
%h1 Your authorized applications
%h1= _("Your authorized applications")
%main{ :role => "main" }
.table-holder
%table.table.table-striped
%thead
%tr
%th Application
%th Created At
%th= _('Application')
%th= _('Created At')
%th
%th
%tbody
......
.nav-block.activities
= render 'shared/event_filter'
.controls
= link_to group_path(@group, rss_url_options), class: 'btn rss-btn has-tooltip' , title: 'Subscribe' do
%i.fa.fa-rss
= render 'shared/event_filter'
.content_list
= spinner
:plain
job = $("tr#repo_#{@repo_id}")
job.find(".import-actions").html("<p class='alert alert-danger'>Access denied! Please verify you can add deploy keys to this repository.</p>")
job.find(".import-actions").html("<p class='alert alert-danger'>#{_('Access denied! Please verify you can add deploy keys to this repository.')}</p>")
- page_title 'Bitbucket import'
- header_title 'Projects', root_path
- page_title _('Bitbucket import')
- header_title _('Projects'), root_path
%h3.page-title
%i.fa.fa-bitbucket
Import projects from Bitbucket
= _('Import projects from Bitbucket')
- if @repos.any?
%p.light
Select projects you want to import.
= _('Select projects you want to import.')
%hr
%p
- if @incompatible_repos.any?
= button_tag class: 'btn btn-import btn-success js-import-all' do
Import all compatible projects
= _('Import all compatible projects')
= icon('spinner spin', class: 'loading-icon')
- else
= button_tag class: 'btn btn-import btn-success js-import-all' do
Import all projects
= _('Import all projects')
= icon('spinner spin', class: 'loading-icon')
.table-responsive
......@@ -26,9 +26,9 @@
%colgroup.import-jobs-status-col
%thead
%tr
%th From Bitbucket
%th To GitLab
%th Status
%th= _('From Bitbucket')
%th= _('To GitLab')
%th= _('Status')
%tbody
- @already_added_projects.each do |project|
%tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" }
......@@ -40,10 +40,10 @@
- if project.import_status == 'finished'
%span
%i.fa.fa-check
done
= _('done')
- elsif project.import_status == 'started'
%i.fa.fa-spinner.fa-spin
started
= _('started')
- else
= project.human_import_status_name
......@@ -66,7 +66,7 @@
= text_field_tag :path, repo.name, class: "input-mini form-control", tabindex: 2, autofocus: true, required: true
%td.import-actions.job-status
= button_tag class: 'btn btn-import js-add-to-import' do
Import
= _('Import')
= icon('spinner spin', class: 'loading-icon')
- @incompatible_repos.each do |repo|
%tr{ id: "repo_#{repo.owner}___#{repo.slug}" }
......@@ -74,16 +74,13 @@
= link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: '_blank', rel: 'noopener noreferrer'
%td.import-target
%td.import-actions-job-status
= label_tag 'Incompatible Project', nil, class: 'label badge-danger'
= label_tag _('Incompatible Project'), nil, class: 'label badge-danger'
- if @incompatible_repos.any?
%p
One or more of your Bitbucket projects cannot be imported into GitLab
directly because they use Subversion or Mercurial for version control,
rather than Git. Please convert
= link_to 'them to Git,', 'https://www.atlassian.com/git/tutorials/migrating-overview'
and go through the
= link_to 'import flow', status_import_bitbucket_path
again.
= _("One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git.")
- link_to_git = link_to(_('Git'), 'https://www.atlassian.com/git/tutorials/migrating-overview')
- link_to_import_flow = link_to(_('import flow'), status_import_bitbucket_path)
= _("Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again.").html_safe % { link_to_git: link_to_git, link_to_import_flow: link_to_import_flow }
.js-importer-status{ data: { jobs_import_path: "#{jobs_import_bitbucket_path}", import_path: "#{import_bitbucket_path}" } }
- page_title "FogBugz Import"
- header_title "Projects", root_path
- page_title _("FogBugz Import")
- header_title _("Projects"), root_path
%h3.page-title
%i.fa.fa-bug
Import projects from FogBugz
= _('Import projects from FogBugz')
%hr
= form_tag callback_import_fogbugz_path do
%p
To get started you enter your FogBugz URL and login information below.
In the next steps, you'll be able to map users and select the projects
you want to import.
= _("To get started you enter your FogBugz URL and login information below. In the next steps, you'll be able to map users and select the projects you want to import.")
.form-group.row
= label_tag :uri, 'FogBugz URL', class: 'col-form-label col-md-2'
= label_tag :uri, _('FogBugz URL'), class: 'col-form-label col-md-2'
.col-md-4
= text_field_tag :uri, nil, placeholder: 'https://mycompany.fogbugz.com', class: 'form-control'
.form-group.row
= label_tag :email, 'FogBugz Email', class: 'col-form-label col-md-2'
= label_tag :email, _('FogBugz Email'), class: 'col-form-label col-md-2'
.col-md-4
= text_field_tag :email, nil, class: 'form-control'
.form-group.row
= label_tag :password, 'FogBugz Password', class: 'col-form-label col-md-2'
= label_tag :password, _('FogBugz Password'), class: 'col-form-label col-md-2'
.col-md-4
= password_field_tag :password, nil, class: 'form-control'
.form-actions
= submit_tag 'Continue to the next step', class: 'btn btn-create'
= submit_tag _('Continue to the next step'), class: 'btn btn-create'
- page_title 'User map', 'FogBugz import'
- header_title "Projects", root_path
- page_title _('User map'), _('FogBugz import')
- header_title _("Projects"), root_path
%h3.page-title
%i.fa.fa-bug
Import projects from FogBugz
= _('Import projects from FogBugz')
%hr
= form_tag create_user_map_import_fogbugz_path do
%p
Customize how FogBugz email addresses and usernames are imported into GitLab.
In the next step, you'll be able to select the projects you want to import.
= _("Customize how FogBugz email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import.")
%p
The user map is a mapping of the FogBugz users that participated on your projects to the way their email address and usernames will be imported into GitLab. You can change this by populating the table below.
= _("The user map is a mapping of the FogBugz users that participated on your projects to the way their email address and usernames will be imported into GitLab. You can change this by populating the table below.")
%ul
%li
%strong Default: Map a FogBugz account ID to a full name
%strong= _("Default: Map a FogBugz account ID to a full name")
%p
An empty GitLab User field will add the FogBugz user's full name
(e.g. "By John Smith") in the description of all issues and comments.
It will also associate and/or assign these issues and comments with
the project creator.
= _("An empty GitLab User field will add the FogBugz user's full name (e.g. \"By John Smith\") in the description of all issues and comments. It will also associate and/or assign these issues and comments with the project creator.")
%li
%strong Map a FogBugz account ID to a GitLab user
%strong= _("Map a FogBugz account ID to a GitLab user")
%p
Selecting a GitLab user will add a link to the GitLab user in the descriptions
of issues and comments (e.g. "By <a href="#">@johnsmith</a>"). It will also
associate and/or assign these issues and comments with the selected user.
= _('Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. "By <a href="#">@johnsmith</a>"). It will also associate and/or assign these issues and comments with the selected user.').html_safe
.table-holder
%table.table
%thead
%tr
%th ID
%th Name
%th Email
%th GitLab User
%th= _("ID")
%th= _("Name")
%th= _("Email")
%th= _("GitLab User")
%tbody
- @user_map.each do |id, user|
%tr
......@@ -45,4 +39,4 @@
scope: :all, email_user: true, selected: user[:gitlab_user])
.form-actions
= submit_tag 'Continue to the next step', class: 'btn btn-create'
= submit_tag _('Continue to the next step'), class: 'btn btn-create'
- page_title "FogBugz import"
- header_title "Projects", root_path
- page_title _("FogBugz import")
- header_title _("Projects"), root_path
%h3.page-title
%i.fa.fa-bug
Import projects from FogBugz
= _('Import projects from FogBugz')
- if @repos.any?
%p.light
Select projects you want to import.
= _('Select projects you want to import.')
%p.light
Optionally, you can
= link_to 'customize', new_user_map_import_fogbugz_path
how FogBugz email addresses and usernames are imported into GitLab.
- link_to_customize = link_to('customize', new_user_map_import_fogbugz_path)
= _('Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab.').html_safe % { link_to_customize: link_to_customize }
%hr
%p
= button_tag class: 'btn btn-import btn-success js-import-all' do
Import all projects
= _('Import all projects')
= icon("spinner spin", class: "loading-icon")
.table-responsive
......@@ -24,9 +23,9 @@
%colgroup.import-jobs-status-col
%thead
%tr
%th From FogBugz
%th To GitLab
%th Status
%th= _("From FogBugz")
%th= _("To GitLab")
%th= _("Status")
%tbody
- @already_added_projects.each do |project|
%tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" }
......@@ -38,10 +37,10 @@
- if project.import_status == 'finished'
%span
%i.fa.fa-check
done
= _("done")
- elsif project.import_status == 'started'
%i.fa.fa-spinner.fa-spin
started
= _("started")
- else
= project.human_import_status_name
......@@ -53,7 +52,7 @@
#{current_user.username}/#{repo.name}
%td.import-actions.job-status
= button_tag class: "btn btn-import js-add-to-import" do
Import
= _("Import")
= icon("spinner spin", class: "loading-icon")
.js-importer-status{ data: { jobs_import_path: "#{jobs_import_fogbugz_path}", import_path: "#{import_fogbugz_path}" } }
- page_title "Gitea Import"
- header_title "Projects", root_path
- page_title _("Gitea Import")
- header_title _("Projects"), root_path
%h3.page-title
= custom_icon('go_logo')
Import Projects from Gitea
= _('Import Projects from Gitea')
%p
To get started, please enter your Gitea Host URL and a
= succeed '.' do
= link_to 'Personal Access Token', 'https://github.com/gogits/go-gogs-client/wiki#access-token'
- link_to_personal_token = link_to(_('Personal Access Token'), 'https://github.com/gogits/go-gogs-client/wiki#access-token')
= _('To get started, please enter your Gitea Host URL and a %{link_to_personal_token}.').html_safe % { link_to_personal_token: link_to_personal_token }
= form_tag personal_access_token_import_gitea_path do
.form-group.row
= label_tag :gitea_host_url, 'Gitea Host URL', class: 'col-form-label col-sm-2'
= label_tag :gitea_host_url, _('Gitea Host URL'), class: 'col-form-label col-sm-2'
.col-sm-4
= text_field_tag :gitea_host_url, nil, placeholder: 'https://try.gitea.io', class: 'form-control'
.form-group.row
= label_tag :personal_access_token, 'Personal Access Token', class: 'col-form-label col-sm-2'
= label_tag :personal_access_token, _('Personal Access Token'), class: 'col-form-label col-sm-2'
.col-sm-4
= text_field_tag :personal_access_token, nil, class: 'form-control'
.form-actions
= submit_tag 'List Your Gitea Repositories', class: 'btn btn-create'
= submit_tag _('List Your Gitea Repositories'), class: 'btn btn-create'
- page_title "Gitea Import"
- header_title "Projects", root_path
- page_title _("Gitea Import")
- header_title _("Projects"), root_path
%h3.page-title
= custom_icon('go_logo')
Import Projects from Gitea
= _('Import Projects from Gitea')
= render 'import/githubish_status', provider: 'gitea'
- title = has_ci_cd_only_params? ? _('Connect repositories from GitHub') : _('GitHub import')
- page_title title
- breadcrumb_title title
- header_title "Projects", root_path
- header_title _("Projects"), root_path
%h3.page-title
= icon 'github', text: import_github_title
......
- title = has_ci_cd_only_params? ? _('Connect repositories from GitHub') : _('GitHub import')
- page_title title
- breadcrumb_title title
- header_title "Projects", root_path
- header_title _("Projects"), root_path
%h3.page-title
= icon 'github', text: import_github_title
......
- page_title "GitLab.com import"
- header_title "Projects", root_path
- page_title _("GitLab.com import")
- header_title _("Projects"), root_path
%h3.page-title
%i.fa.fa-heart
Import projects from GitLab.com
= _('Import projects from GitLab.com')
%p.light
Select projects you want to import.
= _('Select projects you want to import.')
%hr
%p
= button_tag class: "btn btn-import btn-success js-import-all" do
Import all projects
= _('Import all projects')
= icon("spinner spin", class: "loading-icon")
.table-responsive
......@@ -19,9 +19,9 @@
%colgroup.import-jobs-status-col
%thead
%tr
%th From GitLab.com
%th To this GitLab instance
%th Status
%th= _('From GitLab.com')
%th= _('To this GitLab instance')
%th= _('Status')
%tbody
- @already_added_projects.each do |project|
%tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" }
......@@ -33,10 +33,10 @@
- if project.import_status == 'finished'
%span
%i.fa.fa-check
done
= _('done')
- elsif project.import_status == 'started'
%i.fa.fa-spinner.fa-spin
started
= _('started')
- else
= project.human_import_status_name
......@@ -48,7 +48,7 @@
= import_project_target(repo['namespace']['path'], repo['name'])
%td.import-actions.job-status
= button_tag class: "btn btn-import js-add-to-import" do
Import
= _('Import')
= icon("spinner spin", class: "loading-icon")
.js-importer-status{ data: { jobs_import_path: "#{jobs_import_gitlab_path}", import_path: "#{import_gitlab_path}" } }
- page_title "GitLab Import"
- header_title "Projects", root_path
- page_title _("GitLab Import")
- header_title _("Projects"), root_path
%h3.page-title
= icon('gitlab')
Import an exported GitLab project
= _('Import an exported GitLab project')
%hr
= form_tag import_gitlab_project_path, class: 'new_project', multipart: true do
......@@ -24,19 +24,19 @@
#{user_url(current_user.username)}/
= hidden_field_tag :namespace_id, value: current_user.namespace_id
.form-group.col-12.col-sm-6.project-path
= label_tag :path, 'Project name', class: 'label-light'
= label_tag :path, _('Project name'), class: 'label-light'
= text_field_tag :path, @path, placeholder: "my-awesome-project", class: "js-path-name form-control", tabindex: 2, autofocus: true, required: true
.row
.form-group.col-md-12
To move or copy an entire GitLab project from another GitLab installation to this one, navigate to the original project's settings page, generate an export file, and upload it here.
= _("To move or copy an entire GitLab project from another GitLab installation to this one, navigate to the original project's settings page, generate an export file, and upload it here.")
.row
.form-group.col-sm-12
= hidden_field_tag :namespace_id, @namespace.id
= label_tag :file, 'GitLab project export', class: 'label-light'
= label_tag :file, _('GitLab project export'), class: 'label-light'
.form-group
= file_field_tag :file, class: ''
.row
.form-actions.col-sm-12
= submit_tag 'Import project', class: 'btn btn-create'
= link_to 'Cancel', new_project_path, class: 'btn btn-cancel'
= submit_tag _('Import project'), class: 'btn btn-create'
= link_to _('Cancel'), new_project_path, class: 'btn btn-cancel'
- page_title "Google Code import"
- header_title "Projects", root_path
- page_title _("Google Code import")
- header_title _("Projects"), root_path
%h3.page-title
%i.fa.fa-google
Import projects from Google Code
= _('Import projects from Google Code')
%hr
= form_tag callback_import_google_code_path, multipart: true do
%p
Follow the steps below to export your Google Code project data.
In the next step, you'll be able to select the projects you want to import.
= _('Follow the steps below to export your Google Code project data.')
= _("In the next step, you'll be able to select the projects you want to import.")
%ol
%li
%p
Go to
#{link_to "Google Takeout", "https://www.google.com/settings/takeout", target: '_blank', rel: 'noopener noreferrer'}.
- link_to_google_takeout = link_to(_("Google Takeout"), "https://www.google.com/settings/takeout", target: '_blank', rel: 'noopener noreferrer')
= _("Go to %{link_to_google_takeout}.").html_safe % { link_to_google_takeout: link_to_google_takeout }
%li
%p
Make sure you're logged into the account that owns the projects you'd like to import.
= _("Make sure you're logged into the account that owns the projects you'd like to import.")
%li
%p
Click the <strong>Select none</strong> button on the right, since we only need "Google Code Project Hosting".
= _('Click the <strong>Select none</strong> button on the right, since we only need "Google Code Project Hosting".').html_safe
%li
%p
Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right.
= _('Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right.').html_safe
%li
%p
Choose <strong>Next</strong> at the bottom of the page.
= _('Choose <strong>Next</strong> at the bottom of the page.').html_safe
%li
%p
Leave the "File type" and "Delivery method" options on their default values.
= _('Leave the "File type" and "Delivery method" options on their default values.')
%li
%p
Choose <strong>Create archive</strong> and wait for archiving to complete.
= _('Choose <strong>Create archive</strong> and wait for archiving to complete.').html_safe
%li
%p
Click the <strong>Download</strong> button and wait for downloading to complete.
= _('Click the <strong>Download</strong> button and wait for downloading to complete.').html_safe
%li
%p
Find the downloaded ZIP file and decompress it.
= _('Find the downloaded ZIP file and decompress it.')
%li
%p
Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file.
= _('Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file.').html_safe
%li
%p
Upload <code>GoogleCodeProjectHosting.json</code> here:
= _('Upload <code>GoogleCodeProjectHosting.json</code> here:').html_safe
%p
%input{ type: "file", name: "dump_file", id: "dump_file" }
%li
%p
Do you want to customize how Google Code email addresses and usernames are imported into GitLab?
= _('Do you want to customize how Google Code email addresses and usernames are imported into GitLab?')
%p
= label_tag :create_user_map_0 do
= radio_button_tag :create_user_map, 0, true
No, directly import the existing email addresses and usernames.
= _('No, directly import the existing email addresses and usernames.')
%p
= label_tag :create_user_map_1 do
= radio_button_tag :create_user_map, 1, false
Yes, let me map Google Code users to full names or GitLab users.
= _('Yes, let me map Google Code users to full names or GitLab users.')
%li
%p
= submit_tag 'Continue to the next step', class: "btn btn-create"
= submit_tag _('Continue to the next step'), class: "btn btn-create"
- page_title "User map", "Google Code import"
- header_title "Projects", root_path
- page_title _("User map"), _("Google Code import")
- header_title _("Projects"), root_path
%h3.page-title
%i.fa.fa-google
Import projects from Google Code
= _('Import projects from Google Code')
%hr
= form_tag create_user_map_import_google_code_path do
%p
Customize how Google Code email addresses and usernames are imported into GitLab.
In the next step, you'll be able to select the projects you want to import.
= _("Customize how Google Code email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import.")
%p
The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side.
= _("The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side.").html_safe
%ul
%li
%strong Default: Directly import the Google Code email address or username
%strong= _("Default: Directly import the Google Code email address or username")
%p
<code>"johnsmith@example.com": "johnsm...@example.com"</code>
will add "By johnsm...@example.com" to all issues and comments originally created by johnsmith@example.com.
The email address or username is masked to ensure the user's privacy.
= _('<code>"johnsmith@example.com": "johnsm...@example.com"</code> will add "By johnsm...@example.com" to all issues and comments originally created by johnsmith@example.com. The email address or username is masked to ensure the user\'s privacy.').html_safe
%li
%strong Map a Google Code user to a GitLab user
%strong= _("Map a Google Code user to a GitLab user")
%p
<code>"johnsmith@example.com": "@johnsmith"</code>
will add "By <a href="#">@johnsmith</a>" to all issues and comments originally created by johnsmith@example.com,
and will set <a href="#">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com.
= _('<code>"johnsmith@example.com": "@johnsmith"</code> will add "By <a href="#">@johnsmith</a>" to all issues and comments originally created by johnsmith@example.com, and will set <a href="#">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com.').html_safe
%li
%strong Map a Google Code user to a full name
%strong= _("Map a Google Code user to a full name")
%p
<code>"johnsmith@example.com": "John Smith"</code>
will add "By John Smith" to all issues and comments originally created by johnsmith@example.com.
= _('<code>"johnsmith@example.com": "John Smith"</code> will add "By John Smith" to all issues and comments originally created by johnsmith@example.com.').html_safe
%li
%strong Map a Google Code user to a full email address
%strong= _("Map a Google Code user to a full email address")
%p
<code>"johnsmith@example.com": "johnsmith@example.com"</code>
will add "By <a href="#">johnsmith@example.com</a>" to all issues and comments originally created by johnsmith@example.com.
By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address.
= _('<code>"johnsmith@example.com": "johnsmith@example.com"</code> will add "By <a href="#">johnsmith@example.com</a>" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user\'s privacy. Use this option if you want to show the full email address.').html_safe
.form-group.row
.col-sm-12
= text_area_tag :user_map, JSON.pretty_generate(@user_map), class: 'form-control', rows: 15
.form-actions
= submit_tag 'Continue to the next step', class: "btn btn-create"
= submit_tag _('Continue to the next step'), class: "btn btn-create"
- page_title "Google Code import"
- header_title "Projects", root_path
- page_title _("Google Code import")
- header_title _("Projects"), root_path
%h3.page-title
%i.fa.fa-google
Import projects from Google Code
= _('Import projects from Google Code')
- if @repos.any?
%p.light
Select projects you want to import.
= _('Select projects you want to import.')
%p.light
Optionally, you can
= link_to "customize", new_user_map_import_google_code_path
how Google Code email addresses and usernames are imported into GitLab.
- link_to_customize = link_to(_("customize"), new_user_map_import_google_code_path)
= _("Optionally, you can %{link_to_customize} how Google Code email addresses and usernames are imported into GitLab.").html_safe % { link_to_customize: link_to_customize }
%hr
%p
- if @incompatible_repos.any?
= button_tag class: "btn btn-import btn-success js-import-all" do
Import all compatible projects
= _("Import all compatible projects")
= icon("spinner spin", class: "loading-icon")
- else
= button_tag class: "btn btn-import btn-success js-import-all" do
Import all projects
= _("Import all projects")
= icon("spinner spin", class: "loading-icon")
.table-responsive
......@@ -29,9 +28,9 @@
%colgroup.import-jobs-status-col
%thead
%tr
%th From Google Code
%th To GitLab
%th Status
%th= _("From Google Code")
%th= _("To GitLab")
%th= _("Status")
%tbody
- @already_added_projects.each do |project|
%tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" }
......@@ -43,10 +42,10 @@
- if project.import_status == 'finished'
%span
%i.fa.fa-check
done
= _("done")
- elsif project.import_status == 'started'
%i.fa.fa-spinner.fa-spin
started
= _("started")
- else
= project.human_import_status_name
......@@ -58,7 +57,7 @@
#{current_user.username}/#{repo.name}
%td.import-actions.job-status
= button_tag class: "btn btn-import js-add-to-import" do
Import
= _("Import")
= icon("spinner spin", class: "loading-icon")
- @incompatible_repos.each do |repo|
%tr{ id: "repo_#{repo.id}" }
......@@ -66,15 +65,12 @@
= link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank", rel: 'noopener noreferrer'
%td.import-target
%td.import-actions-job-status
= label_tag "Incompatible Project", nil, class: "label badge-danger"
= label_tag _("Incompatible Project"), nil, class: "label badge-danger"
- if @incompatible_repos.any?
%p
One or more of your Google Code projects cannot be imported into GitLab
directly because they use Subversion or Mercurial for version control,
rather than Git. Please convert them to Git on Google Code, and go
through the
= link_to "import flow", new_import_google_code_path
again.
= _("One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git.")
- link_to_import_flow = link_to(_("import flow"), new_import_google_code_path)
= _("Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again.").html_safe % { link_to_import_flow: link_to_import_flow }
.js-importer-status{ data: { jobs_import_path: "#{jobs_import_google_code_path}", import_path: "#{import_google_code_path}" } }
%div{ class: container_class }
.nav-block.activity-filter-block.activities
= render 'shared/event_filter'
.controls
= 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'
.content_list.project-activity{ :"data-href" => activity_project_path(@project) }
= spinner
......@@ -16,7 +16,7 @@
?
.modal-body
%p= s_('Environments|Are you sure you want to stop this environment?')
- unless @environment.stop_action?
- unless @environment.stop_action_available?
.warning_message
%p= s_('Environments|Note that this action will stop the environment, but it will %{emphasis_start}not%{emphasis_end} have an effect on any existing deployment due to no “stop environment action” being defined in the %{ci_config_link_start}.gitlab-ci.yml%{ci_config_link_end} file.').html_safe % { emphasis_start: '<strong>'.html_safe,
emphasis_end: '</strong>'.html_safe,
......
......@@ -6,5 +6,5 @@
%td
= hidden_field_tag "allowed_to_push_#{protected_branch.id}", protected_branch.push_access_levels.first.access_level
= dropdown_tag( (protected_branch.push_access_levels.first.humanize || 'Select') ,
options: { toggle_class: 'js-allowed-to-push qa-allowed-to-push', dropdown_class: 'dropdown-menu-selectable js-allowed-to-push-container capitalize-header',
options: { toggle_class: 'js-allowed-to-push', dropdown_class: 'dropdown-menu-selectable js-allowed-to-push-container capitalize-header',
data: { field_name: "allowed_to_push_#{protected_branch.id}", access_level_id: protected_branch.push_access_levels.first.id }})
......@@ -2,7 +2,7 @@
%tr.js-protected-branch-edit-form{ data: { url: namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) } }
%td
%span.ref-name.qa-protected-branch-name= protected_branch.name
%span.ref-name= protected_branch.name
- if @project.root_ref?(protected_branch.name)
%span.badge.badge-info.prepend-left-5 default
......
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller.flex-fill
.fade-left= icon('angle-left')
.fade-right= icon('angle-right')
%ul.nav-links.event-filter.scrolling-tabs.nav.nav-tabs
......
---
title: Add uploader support to Import/Export uploads
merge_request: 20484
author:
type: added
---
title: Fix RSS button interaction on Dashboard, Project and Group activities
merge_request: 20549
author:
type: fixed
---
title: Add missing predefined variable and fix docs
merge_request:
author:
type: fixed
---
title: Limit maximum project build timeout setting to 1 month
merge_request: 20591
author:
type: fixed
---
title: Avoid process deadlock in popen by consuming input pipes
merge_request: 20600
author:
type: fixed
---
title: Add a 10 ms bucket for SQL timings
merge_request:
author:
type: changed
---
title: Update issue closing pattern
merge_request: 20554
author: George Tsiolis
type: changed
......@@ -135,7 +135,7 @@ Settings.gitlab['signup_enabled'] ||= true if Settings.gitlab['signup_enabled'].
Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil?
Settings.gitlab['restricted_visibility_levels'] = Settings.__send__(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)|[Ii]mplement(?:s|ed|ing)?)(:?) +(?:(?:issues? +)?%{issue_ref}(?:(?: *,? +and +| *, *)?)|([A-Z][A-Z0-9_]+-\d+))+)' if Settings.gitlab['issue_closing_pattern'].nil?
Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)|[Ii]mplement(?:s|ed|ing)?)(:?) +(?:(?:issues? +)?%{issue_ref}(?:(?: *,? +and +| *,? *)?)|([A-Z][A-Z0-9_]+-\d+))+)' if Settings.gitlab['issue_closing_pattern'].nil?
Settings.gitlab['default_projects_features'] ||= {}
Settings.gitlab['webhook_timeout'] ||= 10
Settings.gitlab['max_attachment_size'] ||= 10
......
......@@ -38,12 +38,14 @@ def check_changelog(path)
if yaml["merge_request"].nil?
message "Consider setting `merge_request` to #{gitlab.mr_json["iid"]} in #{gitlab.html_link(path)}. #{SEE_DOC}"
elsif yaml["merge_request"] != gitlab.mr_json["iid"] && !ce_port_changelog?(changelog_path)
elsif yaml["merge_request"] != gitlab.mr_json["iid"] && !ce_port_changelog?(path)
fail "Merge request ID was not set to #{gitlab.mr_json["iid"]}! #{SEE_DOC}"
end
rescue StandardError
rescue Psych::SyntaxError, Psych::DisallowedClass, Psych::BadAlias
# YAML could not be parsed, fail the build.
fail "#{gitlab.html_link(path)} isn't valid YAML! #{SEE_DOC}"
rescue StandardError => e
warn "There was a problem trying to check the Changelog. Exception: #{e.name} - #{e.message}"
end
def presented_no_changelog_labels
......
......@@ -47,6 +47,7 @@ future GitLab releases.**
| **CI_COMMIT_REF_NAME** | 9.0 | all | The branch or tag name for which project is built |
| **CI_COMMIT_REF_SLUG** | 9.0 | all | `$CI_COMMIT_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. No leading / trailing `-`. Use in URLs, host names and domain names. |
| **CI_COMMIT_SHA** | 9.0 | all | The commit revision for which project is built |
| **CI_COMMIT_BEFORE_SHA** | 11.2 | all | The previous latest commit present on a branch before a push request. |
| **CI_COMMIT_TAG** | 9.0 | 0.5 | The commit tag name. Present only when building tags. |
| **CI_COMMIT_MESSAGE** | 10.8 | all | The full commit message. |
| **CI_COMMIT_TITLE** | 10.8 | all | The title of the commit - the full first line of the message |
......@@ -118,6 +119,7 @@ future GitLab releases.**
| `CI_BUILD_ID` | `CI_JOB_ID` |
| `CI_BUILD_REF` | `CI_COMMIT_SHA` |
| `CI_BUILD_TAG` | `CI_COMMIT_TAG` |
| `CI_BUILD_BEFORE_SHA` | `CI_COMMIT_BEFORE_SHA` |
| `CI_BUILD_REF_NAME` | `CI_COMMIT_REF_NAME` |
| `CI_BUILD_REF_SLUG` | `CI_COMMIT_REF_SLUG` |
| `CI_BUILD_NAME` | `CI_JOB_NAME` |
......
......@@ -197,4 +197,4 @@ Note: It is recommended to log into the `git` user using `sudo -i -u git` or `su
## GitLab.com
We've also detailed [our architecture of GitLab.com](https://about.gitlab.com/handbook/infrastructure/production-architecture/) but this is probably over the top unless you have millions of users.
We've also detailed [our architecture of GitLab.com](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/) but this is probably over the top unless you have millions of users.
......@@ -92,9 +92,9 @@ Is the system packaged Git too old? Remove it and compile from source.
# Download and compile from source
cd /tmp
curl --remote-name --progress https://www.kernel.org/pub/software/scm/git/git-2.16.3.tar.gz
echo 'dda229e9c73f4fbb7d4324e0d993e11311673df03f73b194c554c2e9451e17cd git-2.16.3.tar.gz' | shasum -a256 -c - && tar -xzf git-2.16.3.tar.gz
cd git-2.16.3/
curl --remote-name --progress https://www.kernel.org/pub/software/scm/git/git-2.18.0.tar.gz
echo '94faf2c0b02a7920b0b46f4961d8e9cad08e81418614102898a55f980fa3e7e4 git-2.18.0.tar.gz' | shasum -a256 -c - && tar -xzf git-2.18.0.tar.gz
cd git-2.18.0/
./configure
make prefix=/usr/local all
......
......@@ -527,7 +527,7 @@ repo or by specifying a project variable:
- **Bundled chart** - If your project has a `./chart` directory with a `Chart.yaml`
file in it, Auto DevOps will detect the chart and use it instead of the [default
one](https://gitlab.com/charts/charts.gitlab.io/tree/master/charts/auto-deploy-app).
one](https://gitlab.com/charts/auto-deploy-app).
This can be a great way to control exactly how your application is deployed.
- **Project variable** - Create a [project variable](../../ci/variables/README.md#secret-variables)
`AUTO_DEVOPS_CHART` with the URL of a custom chart to use.
......
......@@ -340,7 +340,7 @@ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
If all items are green, then congratulations, the upgrade is complete!
## Things went south? Revert to previous version (10.5)
## Things went south? Revert to previous version (10.6)
### 1. Revert the code to the previous version
......
......@@ -6,6 +6,18 @@ module API
before { authorize! :download_code, user_project }
helpers do
def user_access
@user_access ||= Gitlab::UserAccess.new(current_user, project: user_project)
end
def authorize_push_to_branch!(branch)
unless user_access.can_push_to_branch?(branch)
forbidden!("You are not allowed to push into this branch")
end
end
end
params do
requires :id, type: String, desc: 'The ID of a project'
end
......@@ -67,7 +79,7 @@ module API
optional :author_name, type: String, desc: 'Author name for commit'
end
post ':id/repository/commits' do
authorize! :push_code, user_project
authorize_push_to_branch!(params[:branch])
attrs = declared_params
attrs[:branch_name] = attrs.delete(:branch)
......@@ -142,7 +154,7 @@ module API
requires :branch, type: String, desc: 'The name of the branch'
end
post ':id/repository/commits/:sha/cherry_pick', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
authorize! :push_code, user_project
authorize_push_to_branch!(params[:branch])
commit = user_project.commit(params[:sha])
not_found!('Commit') unless commit
......
......@@ -21,6 +21,10 @@ module Gitlab
Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
stdout.set_encoding(Encoding::ASCII_8BIT)
# stderr and stdout pipes can block if stderr/stdout aren't drained: https://bugs.ruby-lang.org/issues/9082
# Mimic what Ruby does with capture3: https://github.com/ruby/ruby/blob/1ec544695fa02d714180ef9c34e755027b6a2103/lib/open3.rb#L257-L273
err_reader = Thread.new { stderr.read }
yield(stdin) if block_given?
stdin.close
......@@ -32,7 +36,7 @@ module Gitlab
cmd_output << stdout.read
end
cmd_output << stderr.read
cmd_output << err_reader.value
cmd_status = wait_thr.value.exitstatus
end
......@@ -55,16 +59,20 @@ module Gitlab
rerr, werr = IO.pipe
pid = Process.spawn(vars, *cmd, out: wout, err: werr, chdir: path, pgroup: true)
# stderr and stdout pipes can block if stderr/stdout aren't drained: https://bugs.ruby-lang.org/issues/9082
# Mimic what Ruby does with capture3: https://github.com/ruby/ruby/blob/1ec544695fa02d714180ef9c34e755027b6a2103/lib/open3.rb#L257-L273
out_reader = Thread.new { rout.read }
err_reader = Thread.new { rerr.read }
begin
status = process_wait_with_timeout(pid, timeout)
# close write ends so we could read them
wout.close
werr.close
cmd_output = rout.readlines.join
cmd_output << rerr.readlines.join # Copying the behaviour of `popen` which merges stderr into output
status = process_wait_with_timeout(pid, timeout)
cmd_output = out_reader.value
cmd_output << err_reader.value # Copying the behaviour of `popen` which merges stderr into output
[cmd_output, status.exitstatus]
rescue Timeout::Error => e
......
......@@ -280,12 +280,6 @@ module Gitlab
end.map(&:name)
end
def rugged_head
rugged.head
rescue Rugged::ReferenceError
nil
end
def archive_metadata(ref, storage_path, project_path, format = "tar.gz", append_sha:)
ref ||= root_ref
commit = Gitlab::Git::Commit.find(self, ref)
......@@ -515,11 +509,6 @@ module Gitlab
@refs_hash
end
# Lookup for rugged object by oid or ref name
def lookup(oid_or_ref_name)
rugged.rev_parse(oid_or_ref_name)
end
# Returns url for submodule
#
# Ex.
......@@ -916,20 +905,6 @@ module Gitlab
Gitlab::Git::Blob.batch(self, items, blob_size_limit: blob_size_limit)
end
def commit_index(user, branch_name, index, options)
committer = user_to_committer(user)
OperationService.new(user, self).with_branch(branch_name) do
commit_params = options.merge(
tree: index.write_tree(rugged),
author: committer,
committer: committer
)
create_commit(commit_params)
end
end
def fsck
msg, status = gitaly_repository_client.fsck
......@@ -1356,23 +1331,6 @@ module Gitlab
end
end
# We are trying to deprecate this method because it does a lot of work
# but it seems to be used only to look up submodule URL's.
# https://gitlab.com/gitlab-org/gitaly/issues/329
def submodules(ref)
commit = rev_parse_target(ref)
return {} unless commit
begin
content = blob_content(commit, ".gitmodules")
rescue InvalidBlobName
return {}
end
parser = GitmodulesParser.new(content)
fill_submodule_ids(commit, parser.parse)
end
def gitaly_submodule_url_for(ref, path)
# We don't care about the contents so 1 byte is enough. Can't request 0 bytes, 0 means unlimited.
commit_object = gitaly_commit_client.tree_entry(ref, path, 1)
......@@ -1395,68 +1353,6 @@ module Gitlab
Gitlab::Git::HookEnv.all(gl_repository).values_at(*ALLOWED_OBJECT_RELATIVE_DIRECTORIES_VARIABLES).flatten.compact
end
# Get the content of a blob for a given commit. If the blob is a commit
# (for submodules) then return the blob's OID.
def blob_content(commit, blob_name)
blob_entry = tree_entry(commit, blob_name)
unless blob_entry
raise InvalidBlobName.new("Invalid blob name: #{blob_name}")
end
case blob_entry[:type]
when :commit
blob_entry[:oid]
when :tree
raise InvalidBlobName.new("#{blob_name} is a tree, not a blob")
when :blob
rugged.lookup(blob_entry[:oid]).content
end
end
# Fill in the 'id' field of a submodule hash from its values
# as-of +commit+. Return a Hash consisting only of entries
# from the submodule hash for which the 'id' field is filled.
def fill_submodule_ids(commit, submodule_data)
submodule_data.each do |path, data|
id = begin
blob_content(commit, path)
rescue InvalidBlobName
nil
end
data['id'] = id
end
submodule_data.select { |path, data| data['id'] }
end
# Find the entry for +path+ in the tree for +commit+
def tree_entry(commit, path)
pathname = Pathname.new(path)
first = true
tmp_entry = nil
pathname.each_filename do |dir|
if first
tmp_entry = commit.tree[dir]
first = false
elsif tmp_entry.nil?
return nil
else
begin
tmp_entry = rugged.lookup(tmp_entry[:oid])
rescue Rugged::OdbError, Rugged::InvalidError, Rugged::ReferenceError
return nil
end
return nil unless tmp_entry.type == :tree
tmp_entry = tmp_entry[dir]
end
end
tmp_entry
end
# Return the Rugged patches for the diff between +from+ and +to+.
def diff_patches(from, to, options = {}, *paths)
options ||= {}
......@@ -1496,75 +1392,6 @@ module Gitlab
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
def rugged_cherry_pick(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:)
OperationService.new(user, self).with_branch(
branch_name,
start_branch_name: start_branch_name,
start_repository: start_repository
) do |start_commit|
Gitlab::Git.check_namespace!(commit, start_repository)
cherry_pick_tree_id = check_cherry_pick_content(commit, start_commit.sha)
raise CreateTreeError unless cherry_pick_tree_id
committer = user_to_committer(user)
create_commit(message: message,
author: {
email: commit.author_email,
name: commit.author_name,
time: commit.authored_date
},
committer: committer,
tree: cherry_pick_tree_id,
parents: [start_commit.sha])
end
end
def check_cherry_pick_content(target_commit, source_sha)
args = [target_commit.sha, source_sha]
args << 1 if target_commit.merge_commit?
cherry_pick_index = rugged.cherrypick_commit(*args)
return false if cherry_pick_index.conflicts?
tree_id = cherry_pick_index.write_tree(rugged)
return false unless diff_exists?(source_sha, tree_id)
tree_id
end
def local_fetch_ref(source_path, source_ref:, target_ref:)
args = %W(fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
run_git(args)
......@@ -1634,12 +1461,6 @@ module Gitlab
def sha_from_ref(ref)
rev_parse_target(ref).oid
end
def create_commit(params = {})
params[:message].delete!("\r")
Rugged::Commit.create(rugged, params)
end
end
end
end
......@@ -11,7 +11,12 @@ module Gitlab
def save
return true unless @project.avatar.exists?
copy_files(avatar_path, avatar_export_path)
Gitlab::ImportExport::UploadsManager.new(
project: @project,
shared: @shared,
relative_export_path: 'avatar',
from: avatar_path
).save
rescue => e
@shared.error(e)
false
......@@ -19,10 +24,6 @@ module Gitlab
private
def avatar_export_path
File.join(@shared.export_path, 'avatar', @project.avatar_identifier)
end
def avatar_path
@project.avatar.path
end
......
module Gitlab
module ImportExport
class UploadsManager
include Gitlab::ImportExport::CommandLineUtil
UPLOADS_BATCH_SIZE = 100
def initialize(project:, shared:, relative_export_path: 'uploads', from: nil)
@project = project
@shared = shared
@relative_export_path = relative_export_path
@from = from || default_uploads_path
end
def save
copy_files(@from, uploads_export_path) if File.directory?(@from)
if File.file?(@from) && @relative_export_path == 'avatar'
copy_files(@from, File.join(uploads_export_path, @project.avatar.filename))
end
copy_from_object_storage
true
rescue => e
@shared.error(e)
false
end
def restore
Dir["#{uploads_export_path}/**/*"].each do |upload|
next if File.directory?(upload)
add_upload(upload)
end
true
rescue => e
@shared.error(e)
false
end
private
def add_upload(upload)
uploader_context = FileUploader.extract_dynamic_path(upload).named_captures.symbolize_keys
UploadService.new(@project, File.open(upload, 'r'), FileUploader, uploader_context).execute
end
def copy_from_object_storage
return unless Gitlab::ImportExport.object_storage?
each_uploader do |uploader|
next unless uploader.file
next if uploader.upload.local? # Already copied, using the old method
download_and_copy(uploader)
end
end
def default_uploads_path
FileUploader.absolute_base_dir(@project)
end
def uploads_export_path
@uploads_export_path ||= File.join(@shared.export_path, @relative_export_path)
end
def each_uploader
avatar_path = @project.avatar&.upload&.path
if @relative_export_path == 'avatar'
yield(@project.avatar)
else
project_uploads_except_avatar(avatar_path).find_each(batch_size: UPLOADS_BATCH_SIZE) do |upload|
yield(upload.build_uploader)
end
end
end
def project_uploads_except_avatar(avatar_path)
return @project.uploads unless avatar_path
@project.uploads.where("path != ?", avatar_path)
end
def download_and_copy(upload)
secret = upload.try(:secret) || ''
upload_path = File.join(uploads_export_path, secret, upload.filename)
mkdir_p(File.join(uploads_export_path, secret))
File.open(upload_path, 'w') do |file|
# Download (stream) file from the uploader's location
IO.copy_stream(URI.parse(upload.file.url).open, file)
end
end
end
end
end
......@@ -2,13 +2,30 @@ module Gitlab
module ImportExport
class UploadsRestorer < UploadsSaver
def restore
return true unless File.directory?(uploads_export_path)
if Gitlab::ImportExport.object_storage?
Gitlab::ImportExport::UploadsManager.new(
project: @project,
shared: @shared
).restore
elsif File.directory?(uploads_export_path)
copy_files(uploads_export_path, uploads_path)
copy_files(uploads_export_path, uploads_path)
true
else
true # Proceed without uploads
end
rescue => e
@shared.error(e)
false
end
def uploads_path
FileUploader.absolute_base_dir(@project)
end
def uploads_export_path
@uploads_export_path ||= File.join(@shared.export_path, 'uploads')
end
end
end
end
......@@ -9,21 +9,14 @@ module Gitlab
end
def save
return true unless File.directory?(uploads_path)
copy_files(uploads_path, uploads_export_path)
Gitlab::ImportExport::UploadsManager.new(
project: @project,
shared: @shared
).save
rescue => e
@shared.error(e)
false
end
def uploads_path
FileUploader.absolute_base_dir(@project)
end
def uploads_export_path
File.join(@shared.export_path, 'uploads')
end
end
end
end
......@@ -4,10 +4,18 @@ module Gitlab
file_name_noext + '.log'
end
def self.debug(message)
build.debug(message)
end
def self.error(message)
build.error(message)
end
def self.warn(message)
build.warn(message)
end
def self.info(message)
build.info(message)
end
......
......@@ -20,7 +20,7 @@ module Gitlab
define_histogram :gitlab_sql_duration_seconds do
docstring 'SQL time'
base_labels Transaction::BASE_LABELS
buckets [0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0]
buckets [0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0]
end
def current_transaction
......
......@@ -34,11 +34,16 @@ module Gitlab
start = Time.now
Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
# stderr and stdout pipes can block if stderr/stdout aren't drained: https://bugs.ruby-lang.org/issues/9082
# Mimic what Ruby does with capture3: https://github.com/ruby/ruby/blob/1ec544695fa02d714180ef9c34e755027b6a2103/lib/open3.rb#L257-L273
out_reader = Thread.new { stdout.read }
err_reader = Thread.new { stderr.read }
yield(stdin) if block_given?
stdin.close
cmd_stdout = stdout.read
cmd_stderr = stderr.read
cmd_stdout = out_reader.value
cmd_stderr = err_reader.value
cmd_status = wait_thr.value
end
......
This diff is collapsed.
......@@ -22,7 +22,7 @@
"autosize": "^4.0.0",
"axios": "^0.17.1",
"babel-core": "^6.26.3",
"babel-loader": "^7.1.4",
"babel-loader": "^7.1.5",
"babel-plugin-transform-define": "^1.3.0",
"babel-preset-latest": "^6.24.1",
"babel-preset-stage-2": "^6.24.1",
......@@ -36,7 +36,7 @@
"compression-webpack-plugin": "^1.1.11",
"core-js": "^2.4.1",
"cropper": "^2.3.0",
"css-loader": "^0.28.11",
"css-loader": "^1.0.0",
"d3-array": "^1.2.1",
"d3-axis": "^1.0.8",
"d3-brush": "^1.0.4",
......@@ -90,15 +90,15 @@
"url-loader": "^1.0.1",
"visibilityjs": "^1.2.4",
"vue": "^2.5.16",
"vue-loader": "^15.2.0",
"vue-loader": "^15.2.4",
"vue-resource": "^1.5.0",
"vue-router": "^3.0.1",
"vue-template-compiler": "^2.5.16",
"vue-virtual-scroll-list": "^1.2.5",
"vuex": "^3.0.1",
"webpack": "^4.11.1",
"webpack-bundle-analyzer": "^2.11.1",
"webpack-cli": "^3.0.2",
"webpack": "^4.16.0",
"webpack-bundle-analyzer": "^2.13.1",
"webpack-cli": "^3.0.8",
"webpack-stats-plugin": "^0.2.1",
"worker-loader": "^2.0.0"
},
......@@ -123,15 +123,16 @@
"ignore": "^3.3.7",
"istanbul": "^0.4.5",
"jasmine-core": "^2.9.0",
"jasmine-diff": "^0.1.3",
"jasmine-jquery": "^2.1.1",
"karma": "^2.0.2",
"karma": "^2.0.4",
"karma-chrome-launcher": "^2.2.0",
"karma-coverage-istanbul-reporter": "^1.4.2",
"karma-jasmine": "^1.1.1",
"karma-jasmine": "^1.1.2",
"karma-mocha-reporter": "^2.2.5",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "3.0.0",
"nodemon": "^1.17.3",
"karma-webpack": "^4.0.0-beta.0",
"nodemon": "^1.18.2",
"prettier": "1.12.1",
"webpack-dev-server": "^3.1.4"
}
......
......@@ -9,18 +9,6 @@ module QA
project.name = 'protected-branch-project'
end
product :name do
Page::Project::Settings::Repository.act do
expand_protected_branches(&:last_branch_name)
end
end
product :push_allowance do
Page::Project::Settings::Repository.act do
expand_protected_branches(&:last_push_allowance)
end
end
def initialize
@branch_name = 'test/branch'
@allow_to_push = true
......@@ -80,15 +68,6 @@ module QA
end
page.protect_branch
# Avoid Selenium::WebDriver::Error::StaleElementReferenceError
# without sleeping. I.e. this completes fast on fast machines.
page.refresh
# It is possible for the protected branch row to "disappear" at first
page.wait do
page.has_content?(branch_name)
end
end
end
end
......
......@@ -3,7 +3,7 @@ module QA
module Main
class OAuth < Page::Base
view 'app/views/doorkeeper/authorizations/new.html.haml' do
element :authorization_button, 'submit_tag "Authorize"'
element :authorization_button, 'submit_tag _("Authorize")'
end
def needs_authorization?
......
......@@ -16,7 +16,6 @@ module QA
end
view 'app/views/projects/protected_branches/_update_protected_branch.html.haml' do
element :allowed_to_push
element :allowed_to_merge
end
......@@ -24,10 +23,6 @@ module QA
element :protected_branches_list
end
view 'app/views/projects/protected_branches/shared/_protected_branch.html.haml' do
element :protected_branch_name
end
def select_branch(branch_name)
click_element :protected_branch_select
......@@ -62,18 +57,6 @@ module QA
click_on 'Protect'
end
def last_branch_name
within_element(:protected_branches_list) do
all('.qa-protected-branch-name').last
end
end
def last_push_allowance
within_element(:protected_branches_list) do
all('.qa-allowed-to-push').last
end
end
private
def click_allow(action, text)
......
......@@ -21,11 +21,8 @@ module QA
end
context 'when developers and maintainers are allowed to push to a protected branch' do
let!(:protected_branch) { create_protected_branch(allow_to_push: true) }
it 'user with push rights successfully pushes to the protected branch' do
expect(protected_branch.name).to have_content(branch_name)
expect(protected_branch.push_allowance).to have_content('Developers + Maintainers')
create_protected_branch(allow_to_push: true)
push = push_new_file(branch_name)
......
......@@ -28,6 +28,13 @@ FactoryBot.define do
secret SecureRandom.hex
end
trait :with_file do
after(:create) do |upload|
FileUtils.mkdir_p(File.dirname(upload.absolute_path))
FileUtils.touch(upload.absolute_path)
end
end
trait :object_storage do
store ObjectStorage::Store::REMOTE
end
......
......@@ -29,8 +29,10 @@
"merge_when_pipeline_succeeds": { "type": "boolean" },
"source_branch": { "type": "string" },
"source_project_id": { "type": "integer" },
"source_project_full_path": { "type": ["string", "null"]},
"target_branch": { "type": "string" },
"target_project_id": { "type": "integer" },
"target_project_full_path": { "type": ["string", "null"]},
"allow_collaboration": { "type": "boolean"},
"metrics": {
"oneOf": [
......
......@@ -104,7 +104,7 @@ describe('Environment item', () => {
},
],
},
'stop_action?': true,
has_stop_action: true,
environment_path: 'root/ci-folders/environments/31',
created_at: '2016-11-07T11:11:16.525Z',
updated_at: '2016-11-10T15:55:58.778Z',
......
......@@ -7,7 +7,7 @@ export const environmentsList = [
external_url: null,
environment_type: null,
last_deployment: null,
'stop_action?': false,
has_stop_action: false,
environment_path: '/root/review-app/environments/7',
stop_path: '/root/review-app/environments/7/stop',
created_at: '2017-01-31T10:53:46.894Z',
......@@ -22,7 +22,7 @@ export const environmentsList = [
external_url: null,
environment_type: 'build',
last_deployment: null,
'stop_action?': false,
has_stop_action: false,
environment_path: '/root/review-app/environments/12',
stop_path: '/root/review-app/environments/12/stop',
created_at: '2017-02-01T19:42:18.400Z',
......@@ -41,7 +41,7 @@ export const serverData = [
external_url: null,
environment_type: null,
last_deployment: null,
'stop_action?': false,
has_stop_action: false,
environment_path: '/root/review-app/environments/7',
stop_path: '/root/review-app/environments/7/stop',
created_at: '2017-01-31T10:53:46.894Z',
......@@ -58,7 +58,7 @@ export const serverData = [
external_url: null,
environment_type: 'build',
last_deployment: null,
'stop_action?': false,
has_stop_action: false,
environment_path: '/root/review-app/environments/12',
stop_path: '/root/review-app/environments/12/stop',
created_at: '2017-02-01T19:42:18.400Z',
......@@ -77,7 +77,7 @@ export const environment = {
external_url: null,
environment_type: null,
last_deployment: null,
'stop_action?': false,
has_stop_action: false,
environment_path: '/root/review-app/environments/7',
stop_path: '/root/review-app/environments/7/stop',
created_at: '2017-01-31T10:53:46.894Z',
......@@ -95,7 +95,7 @@ export const folder = {
external_url: null,
environment_type: 'build',
last_deployment: null,
'stop_action?': false,
has_stop_action: false,
environment_path: '/root/review-app/environments/12',
stop_path: '/root/review-app/environments/12/stop',
created_at: '2017-02-01T19:42:18.400Z',
......
import Vue from 'vue';
import axios from '~/lib/utils/axios_utils';
import performanceBarApp from '~/performance_bar/components/performance_bar_app.vue';
import PerformanceBarService from '~/performance_bar/services/performance_bar_service';
import PerformanceBarStore from '~/performance_bar/stores/performance_bar_store';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import MockAdapter from 'axios-mock-adapter';
describe('performance bar', () => {
let mock;
describe('performance bar app', () => {
let vm;
beforeEach(() => {
const store = new PerformanceBarStore();
mock = new MockAdapter(axios);
mock.onGet('/-/peek/results').reply(
200,
{
data: {
gc: {
invokes: 0,
invoke_time: '0.00',
use_size: 0,
total_size: 0,
total_object: 0,
gc_time: '0.00',
},
host: { hostname: 'web-01' },
},
},
{},
);
vm = mountComponent(Vue.extend(performanceBarApp), {
store,
env: 'development',
......@@ -45,44 +21,9 @@ describe('performance bar', () => {
afterEach(() => {
vm.$destroy();
mock.restore();
});
it('sets the class to match the environment', () => {
expect(vm.$el.getAttribute('class')).toContain('development');
});
describe('loadRequestDetails', () => {
beforeEach(() => {
spyOn(vm.store, 'addRequest').and.callThrough();
});
it('does nothing if the request cannot be tracked', () => {
spyOn(vm.store, 'canTrackRequest').and.callFake(() => false);
vm.loadRequestDetails('123', 'https://gitlab.com/');
expect(vm.store.addRequest).not.toHaveBeenCalled();
});
it('adds the request immediately', () => {
vm.loadRequestDetails('123', 'https://gitlab.com/');
expect(vm.store.addRequest).toHaveBeenCalledWith(
'123',
'https://gitlab.com/',
);
});
it('makes an HTTP request for the request details', () => {
spyOn(PerformanceBarService, 'fetchRequestDetails').and.callThrough();
vm.loadRequestDetails('456', 'https://gitlab.com/');
expect(PerformanceBarService.fetchRequestDetails).toHaveBeenCalledWith(
'/-/peek/results',
'456',
);
});
});
});
import axios from '~/lib/utils/axios_utils';
import performanceBar from '~/performance_bar';
import PerformanceBarService from '~/performance_bar/services/performance_bar_service';
import MockAdapter from 'axios-mock-adapter';
describe('performance bar wrapper', () => {
let mock;
let vm;
beforeEach(() => {
const peekWrapper = document.createElement('div');
peekWrapper.setAttribute('id', 'js-peek');
peekWrapper.setAttribute('data-env', 'development');
peekWrapper.setAttribute('data-request-id', '123');
peekWrapper.setAttribute('data-peek-url', '/-/peek/results');
peekWrapper.setAttribute('data-profile-url', '?lineprofiler=true');
document.body.appendChild(peekWrapper);
mock = new MockAdapter(axios);
mock.onGet('/-/peek/results').reply(
200,
{
data: {
gc: {
invokes: 0,
invoke_time: '0.00',
use_size: 0,
total_size: 0,
total_object: 0,
gc_time: '0.00',
},
host: { hostname: 'web-01' },
},
},
{},
);
vm = performanceBar({ container: '#js-peek' });
});
afterEach(() => {
vm.$destroy();
mock.restore();
});
describe('loadRequestDetails', () => {
beforeEach(() => {
spyOn(vm.store, 'addRequest').and.callThrough();
});
it('does nothing if the request cannot be tracked', () => {
spyOn(vm.store, 'canTrackRequest').and.callFake(() => false);
vm.loadRequestDetails('123', 'https://gitlab.com/');
expect(vm.store.addRequest).not.toHaveBeenCalled();
});
it('adds the request immediately', () => {
vm.loadRequestDetails('123', 'https://gitlab.com/');
expect(vm.store.addRequest).toHaveBeenCalledWith(
'123',
'https://gitlab.com/',
);
});
it('makes an HTTP request for the request details', () => {
spyOn(PerformanceBarService, 'fetchRequestDetails').and.callThrough();
vm.loadRequestDetails('456', 'https://gitlab.com/');
expect(PerformanceBarService.fetchRequestDetails).toHaveBeenCalledWith(
'/-/peek/results',
'456',
);
});
});
});
......@@ -6,6 +6,7 @@ import '~/commons';
import Vue from 'vue';
import VueResource from 'vue-resource';
import Translate from '~/vue_shared/translate';
import jasmineDiff from 'jasmine-diff';
import { getDefaultAdapter } from '~/lib/utils/axios_utils';
import { FIXTURES_PATH, TEST_HOST } from './test_constants';
......@@ -35,7 +36,15 @@ Vue.use(Translate);
jasmine.getFixtures().fixturesPath = FIXTURES_PATH;
jasmine.getJSONFixtures().fixturesPath = FIXTURES_PATH;
beforeAll(() => jasmine.addMatchers(customMatchers));
beforeAll(() => {
jasmine.addMatchers(
jasmineDiff(jasmine, {
colors: true,
inline: true,
}),
);
jasmine.addMatchers(customMatchers);
});
// globalize common libraries
window.$ = $;
......
......@@ -119,6 +119,7 @@ describe('MRWidgetHeader', () => {
beforeEach(() => {
vm = mountComponent(Component, {
mr: {
iid: 1,
divergedCommitsCount: 12,
sourceBranch: 'mr-widget-refactor',
sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
......@@ -130,6 +131,8 @@ describe('MRWidgetHeader', () => {
emailPatchesPath: '/mr/email-patches',
plainDiffPath: '/mr/plainDiffPath',
statusPath: 'abc',
sourceProjectFullPath: 'root/gitlab-ce',
targetProjectFullPath: 'gitlab-org/gitlab-ce',
},
});
});
......@@ -146,16 +149,40 @@ describe('MRWidgetHeader', () => {
const button = vm.$el.querySelector('.js-web-ide');
expect(button.textContent.trim()).toEqual('Open in Web IDE');
expect(button.getAttribute('href')).toEqual('/-/ide/projectabc');
expect(button.getAttribute('href')).toEqual(
'/-/ide/project/root/gitlab-ce/merge_requests/1?target_project=gitlab-org%2Fgitlab-ce',
);
});
it('renders web ide button with relative URL', () => {
it('renders web ide button with blank query string if target & source project branch', done => {
vm.mr.targetProjectFullPath = 'root/gitlab-ce';
vm.$nextTick(() => {
const button = vm.$el.querySelector('.js-web-ide');
expect(button.textContent.trim()).toEqual('Open in Web IDE');
expect(button.getAttribute('href')).toEqual(
'/-/ide/project/root/gitlab-ce/merge_requests/1?target_project=',
);
done();
});
});
it('renders web ide button with relative URL', done => {
gon.relative_url_root = '/gitlab';
vm.mr.iid = 2;
const button = vm.$el.querySelector('.js-web-ide');
vm.$nextTick(() => {
const button = vm.$el.querySelector('.js-web-ide');
expect(button.textContent.trim()).toEqual('Open in Web IDE');
expect(button.getAttribute('href')).toEqual('/-/ide/projectabc');
expect(button.textContent.trim()).toEqual('Open in Web IDE');
expect(button.getAttribute('href')).toEqual(
'/gitlab/-/ide/project/root/gitlab-ce/merge_requests/2?target_project=gitlab-org%2Fgitlab-ce',
);
done();
});
});
it('renders download dropdown with links', () => {
......
......@@ -29,8 +29,10 @@ export default {
source_branch: 'daaaa',
source_branch_link: 'daaaa',
source_project_id: 19,
source_project_full_path: '/group1/project1',
target_branch: 'master',
target_project_id: 19,
target_project_full_path: '/group2/project2',
metrics: {
merged_by: {
name: 'Administrator',
......
......@@ -379,6 +379,20 @@ describe Gitlab::ClosingIssueExtractor do
.to match_array([issue, other_issue, third_issue])
end
it 'allows non-comma-separated issue numbers in single line message' do
message = "Closes #{reference} #{reference2} #{reference3}"
expect(subject.closed_by_message(message))
.to match_array([issue, other_issue, third_issue])
end
it 'allows mixed comma-separated and non-comma-separated issue numbers in single line message' do
message = "Closes #{reference}, #{reference2} and #{reference3}"
expect(subject.closed_by_message(message))
.to match_array([issue, other_issue, third_issue])
end
it 'fetches issues in multi-line message' do
message = "Awesome commit (closes #{reference})\nAlso fixes #{reference2}"
......
......@@ -269,7 +269,7 @@ EOT
before do
# TODO use a Gitaly diff object instead
rugged_commit = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
repository.lookup('5937ac0a7beb003549fc5fd26fc247adbce4a52e')
repository.rugged.rev_parse('5937ac0a7beb003549fc5fd26fc247adbce4a52e')
end
@diffs = rugged_commit.parents[0].diff(rugged_commit).patches
......
......@@ -5,7 +5,7 @@ describe Gitlab::Git::Index, seed_helper: true do
let(:index) { described_class.new(repository) }
before do
index.read_tree(repository.lookup('master').tree)
index.read_tree(lookup('master').tree)
end
around do |example|
......@@ -30,7 +30,7 @@ describe Gitlab::Git::Index, seed_helper: true do
entry = index.get(options[:file_path])
expect(entry).not_to be_nil
expect(repository.lookup(entry[:oid]).content).to eq(options[:content])
expect(lookup(entry[:oid]).content).to eq(options[:content])
end
end
......@@ -54,7 +54,7 @@ describe Gitlab::Git::Index, seed_helper: true do
index.create(options)
entry = index.get(options[:file_path])
expect(repository.lookup(entry[:oid]).content).to eq(Base64.decode64(options[:content]))
expect(lookup(entry[:oid]).content).to eq(Base64.decode64(options[:content]))
end
end
......@@ -68,7 +68,7 @@ describe Gitlab::Git::Index, seed_helper: true do
index.create(options)
entry = index.get(options[:file_path])
expect(repository.lookup(entry[:oid]).content).to eq("Hello,\nWorld")
expect(lookup(entry[:oid]).content).to eq("Hello,\nWorld")
end
end
end
......@@ -135,7 +135,7 @@ describe Gitlab::Git::Index, seed_helper: true do
entry = index.get(options[:file_path])
expect(repository.lookup(entry[:oid]).content).to eq(options[:content])
expect(lookup(entry[:oid]).content).to eq(options[:content])
end
it 'preserves file mode' do
......@@ -190,7 +190,7 @@ describe Gitlab::Git::Index, seed_helper: true do
entry = index.get(options[:file_path])
expect(entry).not_to be_nil
expect(repository.lookup(entry[:oid]).content).to eq(options[:content])
expect(lookup(entry[:oid]).content).to eq(options[:content])
end
it 'preserves file mode' do
......@@ -232,4 +232,8 @@ describe Gitlab::Git::Index, seed_helper: true do
end
end
end
def lookup(revision)
repository.rugged.rev_parse(revision)
end
end
......@@ -2,6 +2,9 @@ require 'spec_helper'
describe 'Gitlab::Git::Popen' do
let(:path) { Rails.root.join('tmp').to_s }
let(:test_string) { 'The quick brown fox jumped over the lazy dog' }
# The pipe buffer is typically 64K. This string is about 440K.
let(:spew_command) { ['bash', '-c', "for i in {1..10000}; do echo '#{test_string}' 1>&2; done"] }
let(:klass) do
Class.new(Object) do
......@@ -70,6 +73,15 @@ describe 'Gitlab::Git::Popen' do
end
end
end
context 'with a process that writes a lot of data to stderr' do
it 'returns zero' do
output, status = klass.new.popen(spew_command, path)
expect(output).to include(test_string)
expect(status).to eq(0)
end
end
end
context 'popen_with_timeout' do
......@@ -85,6 +97,17 @@ describe 'Gitlab::Git::Popen' do
it { expect(output).to include('tests') }
end
context 'multi-line string' do
let(:test_string) { "this is 1 line\n2nd line\n3rd line\n" }
let(:result) { klass.new.popen_with_timeout(['echo', test_string], timeout, path) }
let(:output) { result.first }
let(:status) { result.last }
it { expect(status).to be_zero }
# echo adds its own line
it { expect(output).to eq(test_string + "\n") }
end
context 'non-zero status' do
let(:result) { klass.new.popen_with_timeout(%w(cat NOTHING), timeout, path) }
let(:output) { result.first }
......@@ -110,6 +133,13 @@ describe 'Gitlab::Git::Popen' do
it "handles processes that do not shutdown correctly" do
expect { klass.new.popen_with_timeout(['bash', '-c', "trap -- '' SIGTERM; sleep 1000"], timeout, path) }.to raise_error(Timeout::Error)
end
it 'handles process that writes a lot of data to stderr' do
output, status = klass.new.popen_with_timeout(spew_command, timeout, path)
expect(output).to include(test_string)
expect(status).to eq(0)
end
end
context 'timeout period' do
......
......@@ -321,90 +321,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
context '#submodules' do
around do |example|
# TODO #submodules will be removed, has been migrated to gitaly
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
example.run
end
end
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
context 'where repo has submodules' do
let(:submodules) { repository.send(:submodules, 'master') }
let(:submodule) { submodules.first }
it { expect(submodules).to be_kind_of Hash }
it { expect(submodules.empty?).to be_falsey }
it 'should have valid data' do
expect(submodule).to eq([
"six", {
"id" => "409f37c4f05865e4fb208c771485f211a22c4c2d",
"name" => "six",
"url" => "git://github.com/randx/six.git"
}
])
end
it 'should handle nested submodules correctly' do
nested = submodules['nested/six']
expect(nested['name']).to eq('nested/six')
expect(nested['url']).to eq('git://github.com/randx/six.git')
expect(nested['id']).to eq('24fb71c79fcabc63dfd8832b12ee3bf2bf06b196')
end
it 'should handle deeply nested submodules correctly' do
nested = submodules['deeper/nested/six']
expect(nested['name']).to eq('deeper/nested/six')
expect(nested['url']).to eq('git://github.com/randx/six.git')
expect(nested['id']).to eq('24fb71c79fcabc63dfd8832b12ee3bf2bf06b196')
end
it 'should not have an entry for an invalid submodule' do
expect(submodules).not_to have_key('invalid/path')
end
it 'should not have an entry for an uncommited submodule dir' do
submodules = repository.send(:submodules, 'fix-existing-submodule-dir')
expect(submodules).not_to have_key('submodule-existing-dir')
end
it 'should handle tags correctly' do
submodules = repository.send(:submodules, 'v1.2.1')
expect(submodules.first).to eq([
"six", {
"id" => "409f37c4f05865e4fb208c771485f211a22c4c2d",
"name" => "six",
"url" => "git://github.com/randx/six.git"
}
])
end
it 'should not break on invalid syntax' do
allow(repository).to receive(:blob_content).and_return(<<-GITMODULES.strip_heredoc)
[submodule "six"]
path = six
url = git://github.com/randx/six.git
[submodule]
foo = bar
GITMODULES
expect(submodules).to have_key('six')
end
end
context 'where repo doesn\'t have submodules' do
let(:submodules) { repository.send(:submodules, '6d39438') }
it 'should return an empty hash' do
expect(submodules).to be_empty
end
end
end
describe '#commit_count' do
shared_examples 'simple commit counting' do
it { expect(repository.commit_count("master")).to eq(25) }
......
......@@ -9,6 +9,7 @@ describe Gitlab::ImportExport::AvatarSaver do
before do
FileUtils.mkdir_p("#{shared.export_path}/avatar/")
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
stub_feature_flags(import_export_object_storage: false)
end
after do
......
require 'spec_helper'
describe Gitlab::ImportExport::UploadsManager do
let(:shared) { project.import_export_shared }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:project) { create(:project) }
let(:exported_file_path) { "#{shared.export_path}/uploads/#{upload.secret}/#{File.basename(upload.path)}" }
subject(:manager) { described_class.new(project: project, shared: shared) }
before do
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
FileUtils.mkdir_p(shared.export_path)
end
after do
FileUtils.rm_rf(shared.export_path)
end
describe '#save' do
context 'when the project has uploads locally stored' do
let(:upload) { create(:upload, :issuable_upload, :with_file, model: project) }
before do
project.uploads << upload
end
it 'does not cause errors' do
manager.save
expect(shared.errors).to be_empty
end
it 'copies the file in the correct location when there is an upload' do
manager.save
expect(File).to exist(exported_file_path)
end
end
context 'using object storage' do
let!(:upload) { create(:upload, :issuable_upload, :object_storage, model: project) }
before do
stub_feature_flags(import_export_object_storage: true)
stub_uploads_object_storage(FileUploader)
end
it 'saves the file' do
fake_uri = double
expect(fake_uri).to receive(:open).and_return(StringIO.new('File content'))
expect(URI).to receive(:parse).and_return(fake_uri)
manager.save
expect(File.read(exported_file_path)).to eq('File content')
end
end
describe '#restore' do
context 'using object storage' do
before do
stub_feature_flags(import_export_object_storage: true)
stub_uploads_object_storage(FileUploader)
FileUtils.mkdir_p(File.join(shared.export_path, 'uploads/72a497a02fe3ee09edae2ed06d390038'))
FileUtils.touch(File.join(shared.export_path, 'uploads/72a497a02fe3ee09edae2ed06d390038', "dummy.txt"))
end
it 'restores the file' do
manager.restore
expect(project.uploads.size).to eq(1)
expect(project.uploads.first.build_uploader.filename).to eq('dummy.txt')
end
end
end
end
end
......@@ -7,6 +7,7 @@ describe Gitlab::ImportExport::UploadsSaver do
let(:shared) { project.import_export_shared }
before do
stub_feature_flags(import_export_object_storage: false)
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
end
......@@ -30,7 +31,7 @@ describe Gitlab::ImportExport::UploadsSaver do
it 'copies the uploads to the export path' do
saver.save
uploads = Dir.glob(File.join(saver.uploads_export_path, '**/*')).map { |file| File.basename(file) }
uploads = Dir.glob(File.join(shared.export_path, 'uploads/**/*')).map { |file| File.basename(file) }
expect(uploads).to include('banana_sample.gif')
end
......@@ -52,7 +53,7 @@ describe Gitlab::ImportExport::UploadsSaver do
it 'copies the uploads to the export path' do
saver.save
uploads = Dir.glob(File.join(saver.uploads_export_path, '**/*')).map { |file| File.basename(file) }
uploads = Dir.glob(File.join(shared.export_path, 'uploads/**/*')).map { |file| File.basename(file) }
expect(uploads).to include('banana_sample.gif')
end
......
......@@ -55,6 +55,19 @@ describe Gitlab::Popen do
end
end
context 'with a process that writes a lot of data to stderr' do
let(:test_string) { 'The quick brown fox jumped over the lazy dog' }
# The pipe buffer is typically 64K. This string is about 440K.
let(:spew_command) { ['bash', '-c', "for i in {1..10000}; do echo '#{test_string}' 1>&2; done"] }
it 'returns zero' do
output, status = @klass.new.popen(spew_command, path)
expect(output).to include(test_string)
expect(status).to eq(0)
end
end
context 'without a directory argument' do
before do
@output, @status = @klass.new.popen(%w(ls))
......
......@@ -1613,6 +1613,7 @@ describe Ci::Build do
{ key: 'CI_JOB_NAME', value: 'test', public: true },
{ key: 'CI_JOB_STAGE', value: 'test', public: true },
{ key: 'CI_COMMIT_SHA', value: build.sha, public: true },
{ key: 'CI_COMMIT_BEFORE_SHA', value: build.before_sha, public: true },
{ key: 'CI_COMMIT_REF_NAME', value: build.ref, public: true },
{ key: 'CI_COMMIT_REF_SLUG', value: build.ref_slug, public: true },
{ key: 'CI_BUILD_REF', value: build.sha, public: true },
......
......@@ -157,22 +157,4 @@ describe Deployment do
end
end
end
describe '#stop_action?' do
subject { deployment.stop_action? }
context 'when no other actions' do
let(:deployment) { build(:deployment) }
it { is_expected.to be_falsey }
end
context 'when matching action is defined' do
let(:build) { create(:ci_build) }
let(:deployment) { FactoryBot.build(:deployment, deployable: build, on_stop: 'close_app') }
let!(:close_action) { create(:ci_build, :manual, pipeline: build.pipeline, name: 'close_app') }
it { is_expected.to be_truthy }
end
end
end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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