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 .dedicated-runner: &dedicated-runner
retry: 1 retry: 1
...@@ -86,7 +86,9 @@ stages: ...@@ -86,7 +86,9 @@ stages:
.rails5: &rails5 .rails5: &rails5
allow_failure: true allow_failure: true
only: only:
- /rails5/ variables:
- $CI_COMMIT_REF_NAME =~ /rails5/
- $RAILS5_ENABLED
variables: variables:
BUNDLE_GEMFILE: "Gemfile.rails5" BUNDLE_GEMFILE: "Gemfile.rails5"
RAILS5: "true" RAILS5: "true"
...@@ -327,7 +329,7 @@ cloud-native-image: ...@@ -327,7 +329,7 @@ cloud-native-image:
cache: {} cache: {}
script: script:
- gem install gitlab --no-ri --no-rdoc - gem install gitlab --no-ri --no-rdoc
- ./scripts/trigger-build cng - BUILD_TRIGGER_TOKEN=$CI_JOB_TOKEN scripts/trigger-build cng
only: only:
- tags@gitlab-org/gitlab-ce - tags@gitlab-org/gitlab-ce
- tags@gitlab-org/gitlab-ee - tags@gitlab-org/gitlab-ee
...@@ -351,6 +353,7 @@ retrieve-tests-metadata: ...@@ -351,6 +353,7 @@ retrieve-tests-metadata:
danger-review: danger-review:
image: registry.gitlab.com/gitlab-org/gitaly/dangercontainer:latest image: registry.gitlab.com/gitlab-org/gitaly/dangercontainer:latest
stage: prepare stage: prepare
allow_failure: true
before_script: before_script:
- source scripts/utils.sh - source scripts/utils.sh
- retry gem install danger --no-ri --no-rdoc - retry gem install danger --no-ri --no-rdoc
......
...@@ -101,6 +101,7 @@ router.beforeEach((to, from, next) => { ...@@ -101,6 +101,7 @@ router.beforeEach((to, from, next) => {
store store
.dispatch('getMergeRequestData', { .dispatch('getMergeRequestData', {
projectId: fullProjectId, projectId: fullProjectId,
targetProjectId: to.query.target_project,
mergeRequestId: to.params.mrid, mergeRequestId: to.params.mrid,
}) })
.then(mr => { .then(mr => {
...@@ -119,12 +120,14 @@ router.beforeEach((to, from, next) => { ...@@ -119,12 +120,14 @@ router.beforeEach((to, from, next) => {
.then(() => .then(() =>
store.dispatch('getMergeRequestVersions', { store.dispatch('getMergeRequestVersions', {
projectId: fullProjectId, projectId: fullProjectId,
targetProjectId: to.query.target_project,
mergeRequestId: to.params.mrid, mergeRequestId: to.params.mrid,
}), }),
) )
.then(() => .then(() =>
store.dispatch('getMergeRequestChanges', { store.dispatch('getMergeRequestChanges', {
projectId: fullProjectId, projectId: fullProjectId,
targetProjectId: to.query.target_project,
mergeRequestId: to.params.mrid, mergeRequestId: to.params.mrid,
}), }),
) )
......
...@@ -4,12 +4,14 @@ import * as types from '../mutation_types'; ...@@ -4,12 +4,14 @@ import * as types from '../mutation_types';
export const getMergeRequestData = ( export const getMergeRequestData = (
{ commit, dispatch, state }, { commit, dispatch, state },
{ projectId, mergeRequestId, force = false } = {}, { projectId, mergeRequestId, targetProjectId = null, force = false } = {},
) => ) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
if (!state.projects[projectId].mergeRequests[mergeRequestId] || force) { if (!state.projects[projectId].mergeRequests[mergeRequestId] || force) {
service service
.getProjectMergeRequestData(projectId, mergeRequestId, { render_html: true }) .getProjectMergeRequestData(targetProjectId || projectId, mergeRequestId, {
render_html: true,
})
.then(({ data }) => { .then(({ data }) => {
commit(types.SET_MERGE_REQUEST, { commit(types.SET_MERGE_REQUEST, {
projectPath: projectId, projectPath: projectId,
...@@ -38,12 +40,12 @@ export const getMergeRequestData = ( ...@@ -38,12 +40,12 @@ export const getMergeRequestData = (
export const getMergeRequestChanges = ( export const getMergeRequestChanges = (
{ commit, dispatch, state }, { commit, dispatch, state },
{ projectId, mergeRequestId, force = false } = {}, { projectId, mergeRequestId, targetProjectId = null, force = false } = {},
) => ) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
if (!state.projects[projectId].mergeRequests[mergeRequestId].changes.length || force) { if (!state.projects[projectId].mergeRequests[mergeRequestId].changes.length || force) {
service service
.getProjectMergeRequestChanges(projectId, mergeRequestId) .getProjectMergeRequestChanges(targetProjectId || projectId, mergeRequestId)
.then(({ data }) => { .then(({ data }) => {
commit(types.SET_MERGE_REQUEST_CHANGES, { commit(types.SET_MERGE_REQUEST_CHANGES, {
projectPath: projectId, projectPath: projectId,
...@@ -71,12 +73,12 @@ export const getMergeRequestChanges = ( ...@@ -71,12 +73,12 @@ export const getMergeRequestChanges = (
export const getMergeRequestVersions = ( export const getMergeRequestVersions = (
{ commit, dispatch, state }, { commit, dispatch, state },
{ projectId, mergeRequestId, force = false } = {}, { projectId, mergeRequestId, targetProjectId = null, force = false } = {},
) => ) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
if (!state.projects[projectId].mergeRequests[mergeRequestId].versions.length || force) { if (!state.projects[projectId].mergeRequests[mergeRequestId].versions.length || force) {
service service
.getProjectMergeRequestVersions(projectId, mergeRequestId) .getProjectMergeRequestVersions(targetProjectId || projectId, mergeRequestId)
.then(res => res.data) .then(res => res.data)
.then(data => { .then(data => {
commit(types.SET_MERGE_REQUEST_VERSIONS, { commit(types.SET_MERGE_REQUEST_VERSIONS, {
......
<script> <script>
import $ from 'jquery'; import $ from 'jquery';
import PerformanceBarService from '../services/performance_bar_service';
import detailedMetric from './detailed_metric.vue'; import detailedMetric from './detailed_metric.vue';
import requestSelector from './request_selector.vue'; import requestSelector from './request_selector.vue';
import simpleMetric from './simple_metric.vue'; import simpleMetric from './simple_metric.vue';
import Flash from '../../flash';
export default { export default {
components: { components: {
detailedMetric, detailedMetric,
...@@ -69,37 +66,13 @@ export default { ...@@ -69,37 +66,13 @@ export default {
}, },
}, },
mounted() { mounted() {
this.interceptor = PerformanceBarService.registerInterceptor(
this.peekUrl,
this.loadRequestDetails,
);
this.loadRequestDetails(this.requestId, window.location.href);
this.currentRequest = this.requestId; this.currentRequest = this.requestId;
if (this.lineProfileModal.length) { if (this.lineProfileModal.length) {
this.lineProfileModal.modal('toggle'); this.lineProfileModal.modal('toggle');
} }
}, },
beforeDestroy() {
PerformanceBarService.removeInterceptor(this.interceptor);
},
methods: { 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) { changeCurrentRequest(newRequestId) {
this.currentRequest = newRequestId; this.currentRequest = newRequestId;
}, },
......
import Vue from 'vue'; 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'; import PerformanceBarStore from './stores/performance_bar_store';
export default ({ container }) => export default ({ container }) =>
new Vue({ new Vue({
el: container, el: container,
components: { components: {
performanceBarApp, performanceBarApp: () => import('./components/performance_bar_app.vue'),
}, },
data() { data() {
const performanceBarData = document.querySelector(this.$options.el) const performanceBarData = document.querySelector(this.$options.el)
...@@ -21,6 +22,34 @@ export default ({ container }) => ...@@ -21,6 +22,34 @@ export default ({ container }) =>
profileUrl: performanceBarData.profileUrl, 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) { render(createElement) {
return createElement('performance-bar-app', { return createElement('performance-bar-app', {
props: { props: {
......
<script> <script>
import tooltip from '~/vue_shared/directives/tooltip'; import tooltip from '~/vue_shared/directives/tooltip';
import { n__ } from '~/locale'; 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 Icon from '~/vue_shared/components/icon.vue';
import clipboardButton from '~/vue_shared/components/clipboard_button.vue'; import clipboardButton from '~/vue_shared/components/clipboard_button.vue';
...@@ -43,7 +43,10 @@ export default { ...@@ -43,7 +43,10 @@ export default {
return this.isBranchTitleLong(this.mr.targetBranch); return this.isBranchTitleLong(this.mr.targetBranch);
}, },
webIdePath() { 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: { methods: {
......
...@@ -16,10 +16,11 @@ export default class MergeRequestStore { ...@@ -16,10 +16,11 @@ export default class MergeRequestStore {
const pipelineStatus = data.pipeline ? data.pipeline.details.status : null; const pipelineStatus = data.pipeline ? data.pipeline.details.status : null;
this.squash = data.squash; this.squash = data.squash;
this.squashBeforeMergeHelpPath = this.squashBeforeMergeHelpPath || this.squashBeforeMergeHelpPath =
data.squash_before_merge_help_path; this.squashBeforeMergeHelpPath || data.squash_before_merge_help_path;
this.enableSquashBeforeMerge = this.enableSquashBeforeMerge || true; this.enableSquashBeforeMerge = this.enableSquashBeforeMerge || true;
this.iid = data.iid;
this.title = data.title; this.title = data.title;
this.targetBranch = data.target_branch; this.targetBranch = data.target_branch;
this.sourceBranch = data.source_branch; this.sourceBranch = data.source_branch;
...@@ -85,6 +86,8 @@ export default class MergeRequestStore { ...@@ -85,6 +86,8 @@ export default class MergeRequestStore {
this.isMergeAllowed = data.mergeable || false; this.isMergeAllowed = data.mergeable || false;
this.mergeOngoing = data.merge_ongoing; this.mergeOngoing = data.merge_ongoing;
this.allowCollaboration = data.allow_collaboration; 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 // Cherry-pick and Revert actions related
this.canCherryPickInCurrentMR = currentUser.can_cherry_pick_on_current_merge_request || false; this.canCherryPickInCurrentMR = currentUser.can_cherry_pick_on_current_merge_request || false;
...@@ -97,7 +100,8 @@ export default class MergeRequestStore { ...@@ -97,7 +100,8 @@ export default class MergeRequestStore {
this.hasCI = data.has_ci; this.hasCI = data.has_ci;
this.ciStatus = data.ci_status; this.ciStatus = data.ci_status;
this.isPipelineFailed = this.ciStatus === 'failed' || this.ciStatus === 'canceled'; 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.isPipelineSkipped = this.ciStatus === 'skipped';
this.pipelineDetailedStatus = pipelineStatus; this.pipelineDetailedStatus = pipelineStatus;
this.isPipelineActive = data.pipeline ? data.pipeline.active : false; this.isPipelineActive = data.pipeline ? data.pipeline.active : false;
......
...@@ -321,11 +321,18 @@ ...@@ -321,11 +321,18 @@
} }
&.activities { &.activities {
display: flex;
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
overflow: hidden;
.nav-links { .nav-links {
border-bottom: 0; border-bottom: 0;
} }
@include media-breakpoint-down(xs) {
display: block;
overflow: visible;
}
} }
} }
......
...@@ -653,6 +653,7 @@ module Ci ...@@ -653,6 +653,7 @@ module Ci
variables.append(key: 'CI_JOB_NAME', value: name) variables.append(key: 'CI_JOB_NAME', value: name)
variables.append(key: 'CI_JOB_STAGE', value: stage) variables.append(key: 'CI_JOB_STAGE', value: stage)
variables.append(key: 'CI_COMMIT_SHA', value: sha) 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_NAME', value: ref)
variables.append(key: 'CI_COMMIT_REF_SLUG', value: ref_slug) variables.append(key: 'CI_COMMIT_REF_SLUG', value: ref_slug)
variables.append(key: "CI_COMMIT_TAG", value: ref) if tag? variables.append(key: "CI_COMMIT_TAG", value: ref) if tag?
......
...@@ -92,10 +92,6 @@ class Deployment < ActiveRecord::Base ...@@ -92,10 +92,6 @@ class Deployment < ActiveRecord::Base
@stop_action ||= manual_actions.find_by(name: on_stop) @stop_action ||= manual_actions.find_by(name: on_stop)
end end
def stop_action?
stop_action.present?
end
def formatted_deployment_time def formatted_deployment_time
created_at.to_time.in_time_zone.to_s(:medium) created_at.to_time.in_time_zone.to_s(:medium)
end end
......
...@@ -117,7 +117,7 @@ class Environment < ActiveRecord::Base ...@@ -117,7 +117,7 @@ class Environment < ActiveRecord::Base
external_url.gsub(%r{\A.*?://}, '') external_url.gsub(%r{\A.*?://}, '')
end end
def stop_action? def stop_action_available?
available? && stop_action.present? available? && stop_action.present?
end end
......
...@@ -368,8 +368,10 @@ class Project < ActiveRecord::Base ...@@ -368,8 +368,10 @@ class Project < ActiveRecord::Base
chronic_duration_attr :build_timeout_human_readable, :build_timeout, default: 3600 chronic_duration_attr :build_timeout_human_readable, :build_timeout, default: 3600
validates :build_timeout, allow_nil: true, validates :build_timeout, allow_nil: true,
numericality: { greater_than_or_equal_to: 600, numericality: { greater_than_or_equal_to: 10.minutes,
message: 'needs to be at least 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 # Returns a collection of projects that is either public or visible to the
# logged in user. # logged in user.
......
...@@ -2,11 +2,12 @@ class EnvironmentPolicy < BasePolicy ...@@ -2,11 +2,12 @@ class EnvironmentPolicy < BasePolicy
delegate { @subject.project } delegate { @subject.project }
condition(:stop_with_deployment_allowed) do 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 end
condition(:stop_with_update_allowed) do condition(:stop_with_update_allowed) do
!@subject.stop_action? && can?(:update_environment, @subject) !@subject.stop_action_available? && can?(:update_environment, @subject)
end end
rule { stop_with_deployment_allowed | stop_with_update_allowed }.enable :stop_environment rule { stop_with_deployment_allowed | stop_with_update_allowed }.enable :stop_environment
......
...@@ -7,7 +7,7 @@ class EnvironmentEntity < Grape::Entity ...@@ -7,7 +7,7 @@ class EnvironmentEntity < Grape::Entity
expose :external_url expose :external_url
expose :environment_type expose :environment_type
expose :last_deployment, using: DeploymentEntity 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| expose :metrics_path, if: -> (environment, _) { environment.has_metrics? } do |environment|
metrics_project_environment_path(environment.project, environment) metrics_project_environment_path(environment.project, environment)
......
...@@ -10,9 +10,15 @@ class MergeRequestWidgetEntity < IssuableEntity ...@@ -10,9 +10,15 @@ class MergeRequestWidgetEntity < IssuableEntity
expose :merge_when_pipeline_succeeds expose :merge_when_pipeline_succeeds
expose :source_branch expose :source_branch
expose :source_project_id expose :source_project_id
expose :source_project_full_path do |merge_request|
merge_request.source_project&.full_path
end
expose :squash expose :squash
expose :target_branch expose :target_branch
expose :target_project_id expose :target_project_id
expose :target_project_full_path do |merge_request|
merge_request.project&.full_path
end
expose :allow_collaboration expose :allow_collaboration
expose :should_be_rebased?, as: :should_be_rebased expose :should_be_rebased?, as: :should_be_rebased
......
...@@ -8,7 +8,7 @@ module Ci ...@@ -8,7 +8,7 @@ module Ci
return unless @ref.present? return unless @ref.present?
environments.each do |environment| environments.each do |environment|
next unless environment.stop_action? next unless environment.stop_action_available?
next unless can?(current_user, :stop_environment, environment) next unless can?(current_user, :stop_environment, environment)
environment.stop_with_action!(current_user) environment.stop_with_action!(current_user)
......
class UploadService class UploadService
def initialize(model, file, uploader_class = FileUploader) def initialize(model, file, uploader_class = FileUploader, **uploader_context)
@model, @file, @uploader_class = model, file, uploader_class @model, @file, @uploader_class, @uploader_context = model, file, uploader_class, uploader_context
end end
def execute def execute
return nil unless @file && @file.size <= max_attachment_size 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.store!(@file)
uploader.to_h uploader.to_h
......
...@@ -15,7 +15,7 @@ class FileUploader < GitlabUploader ...@@ -15,7 +15,7 @@ class FileUploader < GitlabUploader
prepend ObjectStorage::Extension::RecordsUploads prepend ObjectStorage::Extension::RecordsUploads
MARKDOWN_PATTERN = %r{\!?\[.*?\]\(/uploads/(?<secret>[0-9a-f]{32})/(?<file>.*?)\)} 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 after :remove, :prune_store_dir
...@@ -67,6 +67,10 @@ class FileUploader < GitlabUploader ...@@ -67,6 +67,10 @@ class FileUploader < GitlabUploader
SecureRandom.hex SecureRandom.hex
end end
def self.extract_dynamic_path(path)
DYNAMIC_PATH_PATTERN.match(path)
end
def upload_paths(identifier) def upload_paths(identifier)
[ [
File.join(secret, identifier), File.join(secret, identifier),
...@@ -143,7 +147,7 @@ class FileUploader < GitlabUploader ...@@ -143,7 +147,7 @@ class FileUploader < GitlabUploader
return if apply_context!(value.uploader_context) return if apply_context!(value.uploader_context)
# fallback to the regex based extraction # 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] @secret = matches[:secret]
@identifier = matches[:identifier] @identifier = matches[:identifier]
end end
......
.nav-block.activities .nav-block.activities
= render 'shared/event_filter'
.controls .controls
= link_to dashboard_projects_path(rss_url_options), class: 'btn rss-btn has-tooltip', title: 'Subscribe' do = link_to dashboard_projects_path(rss_url_options), class: 'btn rss-btn has-tooltip', title: 'Subscribe' do
%i.fa.fa-rss %i.fa.fa-rss
= render 'shared/event_filter'
.content_list .content_list
= spinner = spinner
...@@ -2,9 +2,9 @@ ...@@ -2,9 +2,9 @@
= form_tag oauth_application_path(application) do = form_tag oauth_application_path(application) do
%input{ :name => "_method", :type => "hidden", :value => "delete" }/ %input{ :name => "_method", :type => "hidden", :value => "delete" }/
- if defined? small - 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 %span.sr-only
Destroy = _('Destroy')
= icon('trash') = icon('trash')
- else - 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 @@ ...@@ -10,16 +10,14 @@
= f.text_area :redirect_uri, class: 'form-control', required: true = f.text_area :redirect_uri, class: 'form-control', required: true
%span.form-text.text-muted %span.form-text.text-muted
Use one line per URI = _('Use one line per URI')
- if Doorkeeper.configuration.native_redirect_uri - if Doorkeeper.configuration.native_redirect_uri
%span.form-text.text-muted %span.form-text.text-muted
Use = _('Use <code>%{native_redirect_uri}</code> for local tests').html_safe % { native_redirect_uri: Doorkeeper.configuration.native_redirect_uri }
%code= Doorkeeper.configuration.native_redirect_uri
for local tests
.form-group .form-group
= f.label :scopes, class: 'label-light' = f.label :scopes, class: 'label-light'
= render 'shared/tokens/scopes_form', prefix: 'doorkeeper_application', token: application, scopes: @scopes = render 'shared/tokens/scopes_form', prefix: 'doorkeeper_application', token: application, scopes: @scopes
.prepend-top-default .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 - @content_class = "limit-container-width" unless fluid_layout
%h3.page-title Edit application %h3.page-title= _('Edit application')
= render 'form', application: @application = render 'form', application: @application
- page_title "Applications" - page_title _("Applications")
- @content_class = "limit-container-width" unless fluid_layout - @content_class = "limit-container-width" unless fluid_layout
.row.prepend-top-default .row.prepend-top-default
...@@ -7,28 +7,27 @@ ...@@ -7,28 +7,27 @@
= page_title = page_title
%p %p
- if user_oauth_applications? - if user_oauth_applications?
Manage applications that can use GitLab as an OAuth provider, = _("Manage applications that can use GitLab as an OAuth provider, and applications that you've authorized to use your account.")
and applications that you've authorized to use your account.
- else - else
Manage applications that you've authorized to use your account. = _("Manage applications that you've authorized to use your account.")
.col-lg-8 .col-lg-8
- if user_oauth_applications? - if user_oauth_applications?
%h5.prepend-top-0 %h5.prepend-top-0
Add new application = _('Add new application')
= render 'form', application: @application = render 'form', application: @application
%hr %hr
- if user_oauth_applications? - if user_oauth_applications?
.oauth-applications .oauth-applications
%h5 %h5
Your applications (#{@applications.size}) = _("Your applications (%{size})") % { size: @applications.size }
- if @applications.any? - if @applications.any?
.table-responsive .table-responsive
%table.table %table.table
%thead %thead
%tr %tr
%th Name %th= _('Name')
%th Callback URL %th= _('Callback URL')
%th Clients %th= _('Clients')
%th.last-heading %th.last-heading
%tbody %tbody
- @applications.each do |application| - @applications.each do |application|
...@@ -41,25 +40,25 @@ ...@@ -41,25 +40,25 @@
%td %td
= link_to edit_oauth_application_path(application), class: "btn btn-transparent append-right-5" do = link_to edit_oauth_application_path(application), class: "btn btn-transparent append-right-5" do
%span.sr-only %span.sr-only
Edit = _('Edit')
= icon('pencil') = icon('pencil')
= render 'delete_form', application: application, small: true = render 'delete_form', application: application, small: true
- else - else
.settings-message.text-center .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 .oauth-authorized-applications.prepend-top-20.append-bottom-default
- if user_oauth_applications? - if user_oauth_applications?
%h5 %h5
Authorized applications (#{@authorized_tokens.size}) = _("Authorized applications (%{size})") % { size: @authorized_tokens.size }
- if @authorized_tokens.any? - if @authorized_tokens.any?
.table-responsive .table-responsive
%table.table.table-striped %table.table.table-striped
%thead %thead
%tr %tr
%th Name %th= _('Name')
%th Authorized At %th= _('Authorized At')
%th Scope %th= _('Scope')
%th %th
%tbody %tbody
- @authorized_apps.each do |app| - @authorized_apps.each do |app|
...@@ -72,12 +71,12 @@ ...@@ -72,12 +71,12 @@
- @authorized_anonymous_tokens.each do |token| - @authorized_anonymous_tokens.each do |token|
%tr %tr
%td %td
Anonymous = _('Anonymous')
.form-text.text-muted .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.created_at
%td= token.scopes %td= token.scopes
%td= render 'doorkeeper/authorized_applications/delete_form', token: token %td= render 'doorkeeper/authorized_applications/delete_form', token: token
- else - else
.settings-message.text-center .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 %hr
......
- add_to_breadcrumbs "Applications", oauth_applications_path - add_to_breadcrumbs _("Applications"), oauth_applications_path
- breadcrumb_title @application.name - breadcrumb_title @application.name
- page_title @application.name, "Applications" - page_title @application.name, _("Applications")
- @content_class = "limit-container-width" unless fluid_layout - @content_class = "limit-container-width" unless fluid_layout
%h3.page-title %h3.page-title
Application: #{@application.name} = _("Application: %{name}") % { name: @application.name }
.table-holder.oauth-application-show .table-holder.oauth-application-show
%table.table %table.table
%tr %tr
%td %td
Application Id = _('Application Id')
%td %td
%code#application_id= @application.uid %code#application_id= @application.uid
%tr %tr
%td %td
Secret: = _('Secret:')
%td %td
%code#secret= @application.secret %code#secret= @application.secret
%tr %tr
%td %td
Callback url = _('Callback url')
%td %td
- @application.redirect_uri.split.each do |uri| - @application.redirect_uri.split.each do |uri|
%div %div
...@@ -30,5 +30,5 @@ ...@@ -30,5 +30,5 @@
= render "shared/tokens/scopes_list", token: @application = render "shared/tokens/scopes_list", token: @application
.form-actions .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' = 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" } %main{ :role => "main" }
%pre= @pre_auth.error_response.body[:error_description] %pre= @pre_auth.error_response.body[:error_description]
...@@ -3,34 +3,28 @@ ...@@ -3,34 +3,28 @@
.modal-content .modal-content
.modal-header .modal-header
%h3.page-title %h3.page-title
Authorize - link_to_client = link_to(@pre_auth.client.name, @pre_auth.redirect_uri, target: '_blank', rel: 'noopener noreferrer')
= link_to @pre_auth.client.name, @pre_auth.redirect_uri, target: '_blank', rel: 'noopener noreferrer' = _("Authorize %{link_to_client} to use your account?")
to use your account?
.modal-body .modal-body
- if current_user.admin? - if current_user.admin?
.text-warning .text-warning
%p %p
= icon("exclamation-triangle fw") = icon("exclamation-triangle fw")
You are an admin, which means granting access to = _('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 }
%strong= @pre_auth.client.name
will allow them to interact with GitLab as an admin as well. Proceed with caution.
%p %p
An application called - link_to_client = link_to(@pre_auth.client.name, @pre_auth.redirect_uri, target: '_blank', rel: 'noopener noreferrer')
= 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 }
is requesting access to your GitLab account.
- auth_app_owner = @pre_auth.client.application.owner - auth_app_owner = @pre_auth.client.application.owner
- if auth_app_owner - if auth_app_owner
This application was created by - link_to_owner = link_to(auth_app_owner.name, user_path(auth_app_owner))
= succeed "." do = _("This application was created by %{link_to_owner}.").html_safe % { link_to_owner: link_to_owner }
= link_to auth_app_owner.name, user_path(auth_app_owner)
Please note that this application is not provided by GitLab and you should verify its authenticity before = _("Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access.")
allowing access.
- if @pre_auth.scopes - if @pre_auth.scopes
%p %p
This application will be able to: = _("This application will be able to:")
%ul %ul
- @pre_auth.scopes.each do |scope| - @pre_auth.scopes.each do |scope|
%li %li
...@@ -44,7 +38,7 @@ ...@@ -44,7 +38,7 @@
= hidden_field_tag :response_type, @pre_auth.response_type = hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope = hidden_field_tag :scope, @pre_auth.scope
= hidden_field_tag :nonce, @pre_auth.nonce = 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 = form_tag oauth_authorization_path, method: :post, class: 'inline' do
= hidden_field_tag :client_id, @pre_auth.client.uid = hidden_field_tag :client_id, @pre_auth.client.uid
= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri = hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
...@@ -52,4 +46,4 @@ ...@@ -52,4 +46,4 @@
= hidden_field_tag :response_type, @pre_auth.response_type = hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope = hidden_field_tag :scope, @pre_auth.scope
= hidden_field_tag :nonce, @pre_auth.nonce = 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" } %main{ :role => "main" }
%code#authorization_code= params[:code] %code#authorization_code= params[:code]
...@@ -6,4 +6,4 @@ ...@@ -6,4 +6,4 @@
= form_tag path do = form_tag path do
%input{ :name => "_method", :type => "hidden", :value => "delete" }/ %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 %header
%h1 Your authorized applications %h1= _("Your authorized applications")
%main{ :role => "main" } %main{ :role => "main" }
.table-holder .table-holder
%table.table.table-striped %table.table.table-striped
%thead %thead
%tr %tr
%th Application %th= _('Application')
%th Created At %th= _('Created At')
%th %th
%th %th
%tbody %tbody
......
.nav-block.activities .nav-block.activities
= render 'shared/event_filter'
.controls .controls
= link_to group_path(@group, rss_url_options), class: 'btn rss-btn has-tooltip' , title: 'Subscribe' do = link_to group_path(@group, rss_url_options), class: 'btn rss-btn has-tooltip' , title: 'Subscribe' do
%i.fa.fa-rss %i.fa.fa-rss
= render 'shared/event_filter'
.content_list .content_list
= spinner = spinner
:plain :plain
job = $("tr#repo_#{@repo_id}") 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' - page_title _('Bitbucket import')
- header_title 'Projects', root_path - header_title _('Projects'), root_path
%h3.page-title %h3.page-title
%i.fa.fa-bitbucket %i.fa.fa-bitbucket
Import projects from Bitbucket = _('Import projects from Bitbucket')
- if @repos.any? - if @repos.any?
%p.light %p.light
Select projects you want to import. = _('Select projects you want to import.')
%hr %hr
%p %p
- if @incompatible_repos.any? - if @incompatible_repos.any?
= button_tag class: 'btn btn-import btn-success js-import-all' do = 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') = icon('spinner spin', class: 'loading-icon')
- else - else
= button_tag class: 'btn btn-import btn-success js-import-all' do = button_tag class: 'btn btn-import btn-success js-import-all' do
Import all projects = _('Import all projects')
= icon('spinner spin', class: 'loading-icon') = icon('spinner spin', class: 'loading-icon')
.table-responsive .table-responsive
...@@ -26,9 +26,9 @@ ...@@ -26,9 +26,9 @@
%colgroup.import-jobs-status-col %colgroup.import-jobs-status-col
%thead %thead
%tr %tr
%th From Bitbucket %th= _('From Bitbucket')
%th To GitLab %th= _('To GitLab')
%th Status %th= _('Status')
%tbody %tbody
- @already_added_projects.each do |project| - @already_added_projects.each do |project|
%tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" } %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" }
...@@ -40,10 +40,10 @@ ...@@ -40,10 +40,10 @@
- if project.import_status == 'finished' - if project.import_status == 'finished'
%span %span
%i.fa.fa-check %i.fa.fa-check
done = _('done')
- elsif project.import_status == 'started' - elsif project.import_status == 'started'
%i.fa.fa-spinner.fa-spin %i.fa.fa-spinner.fa-spin
started = _('started')
- else - else
= project.human_import_status_name = project.human_import_status_name
...@@ -66,7 +66,7 @@ ...@@ -66,7 +66,7 @@
= text_field_tag :path, repo.name, class: "input-mini form-control", tabindex: 2, autofocus: true, required: true = text_field_tag :path, repo.name, class: "input-mini form-control", tabindex: 2, autofocus: true, required: true
%td.import-actions.job-status %td.import-actions.job-status
= button_tag class: 'btn btn-import js-add-to-import' do = button_tag class: 'btn btn-import js-add-to-import' do
Import = _('Import')
= icon('spinner spin', class: 'loading-icon') = icon('spinner spin', class: 'loading-icon')
- @incompatible_repos.each do |repo| - @incompatible_repos.each do |repo|
%tr{ id: "repo_#{repo.owner}___#{repo.slug}" } %tr{ id: "repo_#{repo.owner}___#{repo.slug}" }
...@@ -74,16 +74,13 @@ ...@@ -74,16 +74,13 @@
= link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: '_blank', rel: 'noopener noreferrer' = link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: '_blank', rel: 'noopener noreferrer'
%td.import-target %td.import-target
%td.import-actions-job-status %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? - if @incompatible_repos.any?
%p %p
One or more of your Bitbucket projects cannot be imported into GitLab = _("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.")
directly because they use Subversion or Mercurial for version control, - link_to_git = link_to(_('Git'), 'https://www.atlassian.com/git/tutorials/migrating-overview')
rather than Git. Please convert - link_to_import_flow = link_to(_('import flow'), status_import_bitbucket_path)
= link_to 'them to Git,', 'https://www.atlassian.com/git/tutorials/migrating-overview' = _("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 }
and go through the
= link_to 'import flow', status_import_bitbucket_path
again.
.js-importer-status{ data: { jobs_import_path: "#{jobs_import_bitbucket_path}", import_path: "#{import_bitbucket_path}" } } .js-importer-status{ data: { jobs_import_path: "#{jobs_import_bitbucket_path}", import_path: "#{import_bitbucket_path}" } }
- page_title "FogBugz Import" - page_title _("FogBugz Import")
- header_title "Projects", root_path - header_title _("Projects"), root_path
%h3.page-title %h3.page-title
%i.fa.fa-bug %i.fa.fa-bug
Import projects from FogBugz = _('Import projects from FogBugz')
%hr %hr
= form_tag callback_import_fogbugz_path do = form_tag callback_import_fogbugz_path do
%p %p
To get started you enter your FogBugz URL and login information below. = _("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.")
In the next steps, you'll be able to map users and select the projects
you want to import.
.form-group.row .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 .col-md-4
= text_field_tag :uri, nil, placeholder: 'https://mycompany.fogbugz.com', class: 'form-control' = text_field_tag :uri, nil, placeholder: 'https://mycompany.fogbugz.com', class: 'form-control'
.form-group.row .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 .col-md-4
= text_field_tag :email, nil, class: 'form-control' = text_field_tag :email, nil, class: 'form-control'
.form-group.row .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 .col-md-4
= password_field_tag :password, nil, class: 'form-control' = password_field_tag :password, nil, class: 'form-control'
.form-actions .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' - page_title _('User map'), _('FogBugz import')
- header_title "Projects", root_path - header_title _("Projects"), root_path
%h3.page-title %h3.page-title
%i.fa.fa-bug %i.fa.fa-bug
Import projects from FogBugz = _('Import projects from FogBugz')
%hr %hr
= form_tag create_user_map_import_fogbugz_path do = form_tag create_user_map_import_fogbugz_path do
%p %p
Customize how FogBugz email addresses and usernames are imported into GitLab. = _("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.")
In the next step, you'll be able to select the projects you want to import.
%p %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 %ul
%li %li
%strong Default: Map a FogBugz account ID to a full name %strong= _("Default: Map a FogBugz account ID to a full name")
%p %p
An empty GitLab User field will add the FogBugz user's full name = _("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.")
(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 %li
%strong Map a FogBugz account ID to a GitLab user %strong= _("Map a FogBugz account ID to a GitLab user")
%p %p
Selecting a GitLab user will add a link to the GitLab user in the descriptions = _('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
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.
.table-holder .table-holder
%table.table %table.table
%thead %thead
%tr %tr
%th ID %th= _("ID")
%th Name %th= _("Name")
%th Email %th= _("Email")
%th GitLab User %th= _("GitLab User")
%tbody %tbody
- @user_map.each do |id, user| - @user_map.each do |id, user|
%tr %tr
...@@ -45,4 +39,4 @@ ...@@ -45,4 +39,4 @@
scope: :all, email_user: true, selected: user[:gitlab_user]) scope: :all, email_user: true, selected: user[:gitlab_user])
.form-actions .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" - page_title _("FogBugz import")
- header_title "Projects", root_path - header_title _("Projects"), root_path
%h3.page-title %h3.page-title
%i.fa.fa-bug %i.fa.fa-bug
Import projects from FogBugz = _('Import projects from FogBugz')
- if @repos.any? - if @repos.any?
%p.light %p.light
Select projects you want to import. = _('Select projects you want to import.')
%p.light %p.light
Optionally, you can - link_to_customize = link_to('customize', new_user_map_import_fogbugz_path)
= 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 }
how FogBugz email addresses and usernames are imported into GitLab.
%hr %hr
%p %p
= button_tag class: 'btn btn-import btn-success js-import-all' do = button_tag class: 'btn btn-import btn-success js-import-all' do
Import all projects = _('Import all projects')
= icon("spinner spin", class: "loading-icon") = icon("spinner spin", class: "loading-icon")
.table-responsive .table-responsive
...@@ -24,9 +23,9 @@ ...@@ -24,9 +23,9 @@
%colgroup.import-jobs-status-col %colgroup.import-jobs-status-col
%thead %thead
%tr %tr
%th From FogBugz %th= _("From FogBugz")
%th To GitLab %th= _("To GitLab")
%th Status %th= _("Status")
%tbody %tbody
- @already_added_projects.each do |project| - @already_added_projects.each do |project|
%tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" } %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" }
...@@ -38,10 +37,10 @@ ...@@ -38,10 +37,10 @@
- if project.import_status == 'finished' - if project.import_status == 'finished'
%span %span
%i.fa.fa-check %i.fa.fa-check
done = _("done")
- elsif project.import_status == 'started' - elsif project.import_status == 'started'
%i.fa.fa-spinner.fa-spin %i.fa.fa-spinner.fa-spin
started = _("started")
- else - else
= project.human_import_status_name = project.human_import_status_name
...@@ -53,7 +52,7 @@ ...@@ -53,7 +52,7 @@
#{current_user.username}/#{repo.name} #{current_user.username}/#{repo.name}
%td.import-actions.job-status %td.import-actions.job-status
= button_tag class: "btn btn-import js-add-to-import" do = button_tag class: "btn btn-import js-add-to-import" do
Import = _("Import")
= icon("spinner spin", class: "loading-icon") = icon("spinner spin", class: "loading-icon")
.js-importer-status{ data: { jobs_import_path: "#{jobs_import_fogbugz_path}", import_path: "#{import_fogbugz_path}" } } .js-importer-status{ data: { jobs_import_path: "#{jobs_import_fogbugz_path}", import_path: "#{import_fogbugz_path}" } }
- page_title "Gitea Import" - page_title _("Gitea Import")
- header_title "Projects", root_path - header_title _("Projects"), root_path
%h3.page-title %h3.page-title
= custom_icon('go_logo') = custom_icon('go_logo')
Import Projects from Gitea = _('Import Projects from Gitea')
%p %p
To get started, please enter your Gitea Host URL and a - link_to_personal_token = link_to(_('Personal Access Token'), 'https://github.com/gogits/go-gogs-client/wiki#access-token')
= succeed '.' do = _('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 }
= link_to 'Personal Access Token', 'https://github.com/gogits/go-gogs-client/wiki#access-token'
= form_tag personal_access_token_import_gitea_path do = form_tag personal_access_token_import_gitea_path do
.form-group.row .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 .col-sm-4
= text_field_tag :gitea_host_url, nil, placeholder: 'https://try.gitea.io', class: 'form-control' = text_field_tag :gitea_host_url, nil, placeholder: 'https://try.gitea.io', class: 'form-control'
.form-group.row .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 .col-sm-4
= text_field_tag :personal_access_token, nil, class: 'form-control' = text_field_tag :personal_access_token, nil, class: 'form-control'
.form-actions .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" - page_title _("Gitea Import")
- header_title "Projects", root_path - header_title _("Projects"), root_path
%h3.page-title %h3.page-title
= custom_icon('go_logo') = custom_icon('go_logo')
Import Projects from Gitea = _('Import Projects from Gitea')
= render 'import/githubish_status', provider: 'gitea' = render 'import/githubish_status', provider: 'gitea'
- title = has_ci_cd_only_params? ? _('Connect repositories from GitHub') : _('GitHub import') - title = has_ci_cd_only_params? ? _('Connect repositories from GitHub') : _('GitHub import')
- page_title title - page_title title
- breadcrumb_title title - breadcrumb_title title
- header_title "Projects", root_path - header_title _("Projects"), root_path
%h3.page-title %h3.page-title
= icon 'github', text: import_github_title = icon 'github', text: import_github_title
......
- title = has_ci_cd_only_params? ? _('Connect repositories from GitHub') : _('GitHub import') - title = has_ci_cd_only_params? ? _('Connect repositories from GitHub') : _('GitHub import')
- page_title title - page_title title
- breadcrumb_title title - breadcrumb_title title
- header_title "Projects", root_path - header_title _("Projects"), root_path
%h3.page-title %h3.page-title
= icon 'github', text: import_github_title = icon 'github', text: import_github_title
......
- page_title "GitLab.com import" - page_title _("GitLab.com import")
- header_title "Projects", root_path - header_title _("Projects"), root_path
%h3.page-title %h3.page-title
%i.fa.fa-heart %i.fa.fa-heart
Import projects from GitLab.com = _('Import projects from GitLab.com')
%p.light %p.light
Select projects you want to import. = _('Select projects you want to import.')
%hr %hr
%p %p
= button_tag class: "btn btn-import btn-success js-import-all" do = button_tag class: "btn btn-import btn-success js-import-all" do
Import all projects = _('Import all projects')
= icon("spinner spin", class: "loading-icon") = icon("spinner spin", class: "loading-icon")
.table-responsive .table-responsive
...@@ -19,9 +19,9 @@ ...@@ -19,9 +19,9 @@
%colgroup.import-jobs-status-col %colgroup.import-jobs-status-col
%thead %thead
%tr %tr
%th From GitLab.com %th= _('From GitLab.com')
%th To this GitLab instance %th= _('To this GitLab instance')
%th Status %th= _('Status')
%tbody %tbody
- @already_added_projects.each do |project| - @already_added_projects.each do |project|
%tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" } %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" }
...@@ -33,10 +33,10 @@ ...@@ -33,10 +33,10 @@
- if project.import_status == 'finished' - if project.import_status == 'finished'
%span %span
%i.fa.fa-check %i.fa.fa-check
done = _('done')
- elsif project.import_status == 'started' - elsif project.import_status == 'started'
%i.fa.fa-spinner.fa-spin %i.fa.fa-spinner.fa-spin
started = _('started')
- else - else
= project.human_import_status_name = project.human_import_status_name
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
= import_project_target(repo['namespace']['path'], repo['name']) = import_project_target(repo['namespace']['path'], repo['name'])
%td.import-actions.job-status %td.import-actions.job-status
= button_tag class: "btn btn-import js-add-to-import" do = button_tag class: "btn btn-import js-add-to-import" do
Import = _('Import')
= icon("spinner spin", class: "loading-icon") = icon("spinner spin", class: "loading-icon")
.js-importer-status{ data: { jobs_import_path: "#{jobs_import_gitlab_path}", import_path: "#{import_gitlab_path}" } } .js-importer-status{ data: { jobs_import_path: "#{jobs_import_gitlab_path}", import_path: "#{import_gitlab_path}" } }
- page_title "GitLab Import" - page_title _("GitLab Import")
- header_title "Projects", root_path - header_title _("Projects"), root_path
%h3.page-title %h3.page-title
= icon('gitlab') = icon('gitlab')
Import an exported GitLab project = _('Import an exported GitLab project')
%hr %hr
= form_tag import_gitlab_project_path, class: 'new_project', multipart: true do = form_tag import_gitlab_project_path, class: 'new_project', multipart: true do
...@@ -24,19 +24,19 @@ ...@@ -24,19 +24,19 @@
#{user_url(current_user.username)}/ #{user_url(current_user.username)}/
= hidden_field_tag :namespace_id, value: current_user.namespace_id = hidden_field_tag :namespace_id, value: current_user.namespace_id
.form-group.col-12.col-sm-6.project-path .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 = text_field_tag :path, @path, placeholder: "my-awesome-project", class: "js-path-name form-control", tabindex: 2, autofocus: true, required: true
.row .row
.form-group.col-md-12 .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 .row
.form-group.col-sm-12 .form-group.col-sm-12
= hidden_field_tag :namespace_id, @namespace.id = 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 .form-group
= file_field_tag :file, class: '' = file_field_tag :file, class: ''
.row .row
.form-actions.col-sm-12 .form-actions.col-sm-12
= submit_tag 'Import project', class: 'btn btn-create' = submit_tag _('Import project'), class: 'btn btn-create'
= link_to 'Cancel', new_project_path, class: 'btn btn-cancel' = link_to _('Cancel'), new_project_path, class: 'btn btn-cancel'
- page_title "Google Code import" - page_title _("Google Code import")
- header_title "Projects", root_path - header_title _("Projects"), root_path
%h3.page-title %h3.page-title
%i.fa.fa-google %i.fa.fa-google
Import projects from Google Code = _('Import projects from Google Code')
%hr %hr
= form_tag callback_import_google_code_path, multipart: true do = form_tag callback_import_google_code_path, multipart: true do
%p %p
Follow the steps below to export your Google Code project data. = _('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. = _("In the next step, you'll be able to select the projects you want to import.")
%ol %ol
%li %li
%p %p
Go to - link_to_google_takeout = link_to(_("Google Takeout"), "https://www.google.com/settings/takeout", target: '_blank', rel: 'noopener noreferrer')
#{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 %li
%p %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 %li
%p %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 %li
%p %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 %li
%p %p
Choose <strong>Next</strong> at the bottom of the page. = _('Choose <strong>Next</strong> at the bottom of the page.').html_safe
%li %li
%p %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 %li
%p %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 %li
%p %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 %li
%p %p
Find the downloaded ZIP file and decompress it. = _('Find the downloaded ZIP file and decompress it.')
%li %li
%p %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 %li
%p %p
Upload <code>GoogleCodeProjectHosting.json</code> here: = _('Upload <code>GoogleCodeProjectHosting.json</code> here:').html_safe
%p %p
%input{ type: "file", name: "dump_file", id: "dump_file" } %input{ type: "file", name: "dump_file", id: "dump_file" }
%li %li
%p %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 %p
= label_tag :create_user_map_0 do = label_tag :create_user_map_0 do
= radio_button_tag :create_user_map, 0, true = 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 %p
= label_tag :create_user_map_1 do = label_tag :create_user_map_1 do
= radio_button_tag :create_user_map, 1, false = 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 %li
%p %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" - page_title _("User map"), _("Google Code import")
- header_title "Projects", root_path - header_title _("Projects"), root_path
%h3.page-title %h3.page-title
%i.fa.fa-google %i.fa.fa-google
Import projects from Google Code = _('Import projects from Google Code')
%hr %hr
= form_tag create_user_map_import_google_code_path do = form_tag create_user_map_import_google_code_path do
%p %p
Customize how Google Code email addresses and usernames are imported into GitLab. = _("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.")
In the next step, you'll be able to select the projects you want to import.
%p %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 %ul
%li %li
%strong Default: Directly import the Google Code email address or username %strong= _("Default: Directly import the Google Code email address or username")
%p %p
<code>"johnsmith@example.com": "johnsm...@example.com"</code> = _('<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
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.
%li %li
%strong Map a Google Code user to a GitLab user %strong= _("Map a Google Code user to a GitLab user")
%p %p
<code>"johnsmith@example.com": "@johnsmith"</code> = _('<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
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.
%li %li
%strong Map a Google Code user to a full name %strong= _("Map a Google Code user to a full name")
%p %p
<code>"johnsmith@example.com": "John Smith"</code> = _('<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
will add "By John Smith" to all issues and comments originally created by johnsmith@example.com.
%li %li
%strong Map a Google Code user to a full email address %strong= _("Map a Google Code user to a full email address")
%p %p
<code>"johnsmith@example.com": "johnsmith@example.com"</code> = _('<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
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.
.form-group.row .form-group.row
.col-sm-12 .col-sm-12
= text_area_tag :user_map, JSON.pretty_generate(@user_map), class: 'form-control', rows: 15 = text_area_tag :user_map, JSON.pretty_generate(@user_map), class: 'form-control', rows: 15
.form-actions .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" - page_title _("Google Code import")
- header_title "Projects", root_path - header_title _("Projects"), root_path
%h3.page-title %h3.page-title
%i.fa.fa-google %i.fa.fa-google
Import projects from Google Code = _('Import projects from Google Code')
- if @repos.any? - if @repos.any?
%p.light %p.light
Select projects you want to import. = _('Select projects you want to import.')
%p.light %p.light
Optionally, you can - link_to_customize = link_to(_("customize"), new_user_map_import_google_code_path)
= 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 }
how Google Code email addresses and usernames are imported into GitLab.
%hr %hr
%p %p
- if @incompatible_repos.any? - if @incompatible_repos.any?
= button_tag class: "btn btn-import btn-success js-import-all" do = 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") = icon("spinner spin", class: "loading-icon")
- else - else
= button_tag class: "btn btn-import btn-success js-import-all" do = button_tag class: "btn btn-import btn-success js-import-all" do
Import all projects = _("Import all projects")
= icon("spinner spin", class: "loading-icon") = icon("spinner spin", class: "loading-icon")
.table-responsive .table-responsive
...@@ -29,9 +28,9 @@ ...@@ -29,9 +28,9 @@
%colgroup.import-jobs-status-col %colgroup.import-jobs-status-col
%thead %thead
%tr %tr
%th From Google Code %th= _("From Google Code")
%th To GitLab %th= _("To GitLab")
%th Status %th= _("Status")
%tbody %tbody
- @already_added_projects.each do |project| - @already_added_projects.each do |project|
%tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" } %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" }
...@@ -43,10 +42,10 @@ ...@@ -43,10 +42,10 @@
- if project.import_status == 'finished' - if project.import_status == 'finished'
%span %span
%i.fa.fa-check %i.fa.fa-check
done = _("done")
- elsif project.import_status == 'started' - elsif project.import_status == 'started'
%i.fa.fa-spinner.fa-spin %i.fa.fa-spinner.fa-spin
started = _("started")
- else - else
= project.human_import_status_name = project.human_import_status_name
...@@ -58,7 +57,7 @@ ...@@ -58,7 +57,7 @@
#{current_user.username}/#{repo.name} #{current_user.username}/#{repo.name}
%td.import-actions.job-status %td.import-actions.job-status
= button_tag class: "btn btn-import js-add-to-import" do = button_tag class: "btn btn-import js-add-to-import" do
Import = _("Import")
= icon("spinner spin", class: "loading-icon") = icon("spinner spin", class: "loading-icon")
- @incompatible_repos.each do |repo| - @incompatible_repos.each do |repo|
%tr{ id: "repo_#{repo.id}" } %tr{ id: "repo_#{repo.id}" }
...@@ -66,15 +65,12 @@ ...@@ -66,15 +65,12 @@
= link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank", rel: 'noopener noreferrer' = link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank", rel: 'noopener noreferrer'
%td.import-target %td.import-target
%td.import-actions-job-status %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? - if @incompatible_repos.any?
%p %p
One or more of your Google Code projects cannot be imported into GitLab = _("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.")
directly because they use Subversion or Mercurial for version control, - link_to_import_flow = link_to(_("import flow"), new_import_google_code_path)
rather than Git. Please convert them to Git on Google Code, and go = _("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 }
through the
= link_to "import flow", new_import_google_code_path
again.
.js-importer-status{ data: { jobs_import_path: "#{jobs_import_google_code_path}", import_path: "#{import_google_code_path}" } } .js-importer-status{ data: { jobs_import_path: "#{jobs_import_google_code_path}", import_path: "#{import_google_code_path}" } }
%div{ class: container_class } %div{ class: container_class }
.nav-block.activity-filter-block.activities .nav-block.activity-filter-block.activities
= render 'shared/event_filter'
.controls .controls
= link_to project_path(@project, rss_url_options), title: s_("ProjectActivityRSS|Subscribe"), class: 'btn rss-btn has-tooltip' do = link_to project_path(@project, rss_url_options), title: s_("ProjectActivityRSS|Subscribe"), class: 'btn rss-btn has-tooltip' do
= icon('rss') = icon('rss')
= render 'shared/event_filter'
.content_list.project-activity{ :"data-href" => activity_project_path(@project) } .content_list.project-activity{ :"data-href" => activity_project_path(@project) }
= spinner = spinner
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
? ?
.modal-body .modal-body
%p= s_('Environments|Are you sure you want to stop this environment?') %p= s_('Environments|Are you sure you want to stop this environment?')
- unless @environment.stop_action? - unless @environment.stop_action_available?
.warning_message .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, %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, emphasis_end: '</strong>'.html_safe,
......
...@@ -6,5 +6,5 @@ ...@@ -6,5 +6,5 @@
%td %td
= hidden_field_tag "allowed_to_push_#{protected_branch.id}", protected_branch.push_access_levels.first.access_level = 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') , = 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 }}) data: { field_name: "allowed_to_push_#{protected_branch.id}", access_level_id: protected_branch.push_access_levels.first.id }})
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
%tr.js-protected-branch-edit-form{ data: { url: namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) } } %tr.js-protected-branch-edit-form{ data: { url: namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) } }
%td %td
%span.ref-name.qa-protected-branch-name= protected_branch.name %span.ref-name= protected_branch.name
- if @project.root_ref?(protected_branch.name) - if @project.root_ref?(protected_branch.name)
%span.badge.badge-info.prepend-left-5 default %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-left= icon('angle-left')
.fade-right= icon('angle-right') .fade-right= icon('angle-right')
%ul.nav-links.event-filter.scrolling-tabs.nav.nav-tabs %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']. ...@@ -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['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['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['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['default_projects_features'] ||= {}
Settings.gitlab['webhook_timeout'] ||= 10 Settings.gitlab['webhook_timeout'] ||= 10
Settings.gitlab['max_attachment_size'] ||= 10 Settings.gitlab['max_attachment_size'] ||= 10
......
...@@ -38,12 +38,14 @@ def check_changelog(path) ...@@ -38,12 +38,14 @@ def check_changelog(path)
if yaml["merge_request"].nil? if yaml["merge_request"].nil?
message "Consider setting `merge_request` to #{gitlab.mr_json["iid"]} in #{gitlab.html_link(path)}. #{SEE_DOC}" 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}" fail "Merge request ID was not set to #{gitlab.mr_json["iid"]}! #{SEE_DOC}"
end end
rescue StandardError rescue Psych::SyntaxError, Psych::DisallowedClass, Psych::BadAlias
# YAML could not be parsed, fail the build. # YAML could not be parsed, fail the build.
fail "#{gitlab.html_link(path)} isn't valid YAML! #{SEE_DOC}" 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 end
def presented_no_changelog_labels def presented_no_changelog_labels
......
...@@ -47,6 +47,7 @@ future GitLab releases.** ...@@ -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_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_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_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_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_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 | | **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.** ...@@ -118,6 +119,7 @@ future GitLab releases.**
| `CI_BUILD_ID` | `CI_JOB_ID` | | `CI_BUILD_ID` | `CI_JOB_ID` |
| `CI_BUILD_REF` | `CI_COMMIT_SHA` | | `CI_BUILD_REF` | `CI_COMMIT_SHA` |
| `CI_BUILD_TAG` | `CI_COMMIT_TAG` | | `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_NAME` | `CI_COMMIT_REF_NAME` |
| `CI_BUILD_REF_SLUG` | `CI_COMMIT_REF_SLUG` | | `CI_BUILD_REF_SLUG` | `CI_COMMIT_REF_SLUG` |
| `CI_BUILD_NAME` | `CI_JOB_NAME` | | `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 ...@@ -197,4 +197,4 @@ Note: It is recommended to log into the `git` user using `sudo -i -u git` or `su
## GitLab.com ## 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. ...@@ -92,9 +92,9 @@ Is the system packaged Git too old? Remove it and compile from source.
# Download and compile from source # Download and compile from source
cd /tmp cd /tmp
curl --remote-name --progress https://www.kernel.org/pub/software/scm/git/git-2.16.3.tar.gz curl --remote-name --progress https://www.kernel.org/pub/software/scm/git/git-2.18.0.tar.gz
echo 'dda229e9c73f4fbb7d4324e0d993e11311673df03f73b194c554c2e9451e17cd git-2.16.3.tar.gz' | shasum -a256 -c - && tar -xzf git-2.16.3.tar.gz echo '94faf2c0b02a7920b0b46f4961d8e9cad08e81418614102898a55f980fa3e7e4 git-2.18.0.tar.gz' | shasum -a256 -c - && tar -xzf git-2.18.0.tar.gz
cd git-2.16.3/ cd git-2.18.0/
./configure ./configure
make prefix=/usr/local all make prefix=/usr/local all
......
...@@ -527,7 +527,7 @@ repo or by specifying a project variable: ...@@ -527,7 +527,7 @@ repo or by specifying a project variable:
- **Bundled chart** - If your project has a `./chart` directory with a `Chart.yaml` - **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 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. 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) - **Project variable** - Create a [project variable](../../ci/variables/README.md#secret-variables)
`AUTO_DEVOPS_CHART` with the URL of a custom chart to use. `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 ...@@ -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! 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 ### 1. Revert the code to the previous version
......
...@@ -6,6 +6,18 @@ module API ...@@ -6,6 +6,18 @@ module API
before { authorize! :download_code, user_project } 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 params do
requires :id, type: String, desc: 'The ID of a project' requires :id, type: String, desc: 'The ID of a project'
end end
...@@ -67,7 +79,7 @@ module API ...@@ -67,7 +79,7 @@ module API
optional :author_name, type: String, desc: 'Author name for commit' optional :author_name, type: String, desc: 'Author name for commit'
end end
post ':id/repository/commits' do post ':id/repository/commits' do
authorize! :push_code, user_project authorize_push_to_branch!(params[:branch])
attrs = declared_params attrs = declared_params
attrs[:branch_name] = attrs.delete(:branch) attrs[:branch_name] = attrs.delete(:branch)
...@@ -142,7 +154,7 @@ module API ...@@ -142,7 +154,7 @@ module API
requires :branch, type: String, desc: 'The name of the branch' requires :branch, type: String, desc: 'The name of the branch'
end end
post ':id/repository/commits/:sha/cherry_pick', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do 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]) commit = user_project.commit(params[:sha])
not_found!('Commit') unless commit not_found!('Commit') unless commit
......
...@@ -21,6 +21,10 @@ module Gitlab ...@@ -21,6 +21,10 @@ module Gitlab
Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
stdout.set_encoding(Encoding::ASCII_8BIT) 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? yield(stdin) if block_given?
stdin.close stdin.close
...@@ -32,7 +36,7 @@ module Gitlab ...@@ -32,7 +36,7 @@ module Gitlab
cmd_output << stdout.read cmd_output << stdout.read
end end
cmd_output << stderr.read cmd_output << err_reader.value
cmd_status = wait_thr.value.exitstatus cmd_status = wait_thr.value.exitstatus
end end
...@@ -55,16 +59,20 @@ module Gitlab ...@@ -55,16 +59,20 @@ module Gitlab
rerr, werr = IO.pipe rerr, werr = IO.pipe
pid = Process.spawn(vars, *cmd, out: wout, err: werr, chdir: path, pgroup: true) 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 begin
status = process_wait_with_timeout(pid, timeout)
# close write ends so we could read them # close write ends so we could read them
wout.close wout.close
werr.close werr.close
cmd_output = rout.readlines.join status = process_wait_with_timeout(pid, timeout)
cmd_output << rerr.readlines.join # Copying the behaviour of `popen` which merges stderr into output
cmd_output = out_reader.value
cmd_output << err_reader.value # Copying the behaviour of `popen` which merges stderr into output
[cmd_output, status.exitstatus] [cmd_output, status.exitstatus]
rescue Timeout::Error => e rescue Timeout::Error => e
......
...@@ -280,12 +280,6 @@ module Gitlab ...@@ -280,12 +280,6 @@ module Gitlab
end.map(&:name) end.map(&:name)
end end
def rugged_head
rugged.head
rescue Rugged::ReferenceError
nil
end
def archive_metadata(ref, storage_path, project_path, format = "tar.gz", append_sha:) def archive_metadata(ref, storage_path, project_path, format = "tar.gz", append_sha:)
ref ||= root_ref ref ||= root_ref
commit = Gitlab::Git::Commit.find(self, ref) commit = Gitlab::Git::Commit.find(self, ref)
...@@ -515,11 +509,6 @@ module Gitlab ...@@ -515,11 +509,6 @@ module Gitlab
@refs_hash @refs_hash
end 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 # Returns url for submodule
# #
# Ex. # Ex.
...@@ -916,20 +905,6 @@ module Gitlab ...@@ -916,20 +905,6 @@ module Gitlab
Gitlab::Git::Blob.batch(self, items, blob_size_limit: blob_size_limit) Gitlab::Git::Blob.batch(self, items, blob_size_limit: blob_size_limit)
end 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 def fsck
msg, status = gitaly_repository_client.fsck msg, status = gitaly_repository_client.fsck
...@@ -1356,23 +1331,6 @@ module Gitlab ...@@ -1356,23 +1331,6 @@ module Gitlab
end end
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) 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. # 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) commit_object = gitaly_commit_client.tree_entry(ref, path, 1)
...@@ -1395,68 +1353,6 @@ module Gitlab ...@@ -1395,68 +1353,6 @@ module Gitlab
Gitlab::Git::HookEnv.all(gl_repository).values_at(*ALLOWED_OBJECT_RELATIVE_DIRECTORIES_VARIABLES).flatten.compact Gitlab::Git::HookEnv.all(gl_repository).values_at(*ALLOWED_OBJECT_RELATIVE_DIRECTORIES_VARIABLES).flatten.compact
end 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+. # Return the Rugged patches for the diff between +from+ and +to+.
def diff_patches(from, to, options = {}, *paths) def diff_patches(from, to, options = {}, *paths)
options ||= {} options ||= {}
...@@ -1496,75 +1392,6 @@ module Gitlab ...@@ -1496,75 +1392,6 @@ module Gitlab
gitaly_repository_client.apply_gitattributes(revision) gitaly_repository_client.apply_gitattributes(revision)
end 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:) def local_fetch_ref(source_path, source_ref:, target_ref:)
args = %W(fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref}) args = %W(fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
run_git(args) run_git(args)
...@@ -1634,12 +1461,6 @@ module Gitlab ...@@ -1634,12 +1461,6 @@ module Gitlab
def sha_from_ref(ref) def sha_from_ref(ref)
rev_parse_target(ref).oid rev_parse_target(ref).oid
end end
def create_commit(params = {})
params[:message].delete!("\r")
Rugged::Commit.create(rugged, params)
end
end end
end end
end end
...@@ -11,7 +11,12 @@ module Gitlab ...@@ -11,7 +11,12 @@ module Gitlab
def save def save
return true unless @project.avatar.exists? 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 rescue => e
@shared.error(e) @shared.error(e)
false false
...@@ -19,10 +24,6 @@ module Gitlab ...@@ -19,10 +24,6 @@ module Gitlab
private private
def avatar_export_path
File.join(@shared.export_path, 'avatar', @project.avatar_identifier)
end
def avatar_path def avatar_path
@project.avatar.path @project.avatar.path
end 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 ...@@ -2,13 +2,30 @@ module Gitlab
module ImportExport module ImportExport
class UploadsRestorer < UploadsSaver class UploadsRestorer < UploadsSaver
def restore 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 rescue => e
@shared.error(e) @shared.error(e)
false false
end 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 end
end end
...@@ -9,21 +9,14 @@ module Gitlab ...@@ -9,21 +9,14 @@ module Gitlab
end end
def save def save
return true unless File.directory?(uploads_path) Gitlab::ImportExport::UploadsManager.new(
project: @project,
copy_files(uploads_path, uploads_export_path) shared: @shared
).save
rescue => e rescue => e
@shared.error(e) @shared.error(e)
false false
end end
def uploads_path
FileUploader.absolute_base_dir(@project)
end
def uploads_export_path
File.join(@shared.export_path, 'uploads')
end
end end
end end
end end
...@@ -4,10 +4,18 @@ module Gitlab ...@@ -4,10 +4,18 @@ module Gitlab
file_name_noext + '.log' file_name_noext + '.log'
end end
def self.debug(message)
build.debug(message)
end
def self.error(message) def self.error(message)
build.error(message) build.error(message)
end end
def self.warn(message)
build.warn(message)
end
def self.info(message) def self.info(message)
build.info(message) build.info(message)
end end
......
...@@ -20,7 +20,7 @@ module Gitlab ...@@ -20,7 +20,7 @@ module Gitlab
define_histogram :gitlab_sql_duration_seconds do define_histogram :gitlab_sql_duration_seconds do
docstring 'SQL time' docstring 'SQL time'
base_labels Transaction::BASE_LABELS 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 end
def current_transaction def current_transaction
......
...@@ -34,11 +34,16 @@ module Gitlab ...@@ -34,11 +34,16 @@ module Gitlab
start = Time.now start = Time.now
Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| 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? yield(stdin) if block_given?
stdin.close stdin.close
cmd_stdout = stdout.read cmd_stdout = out_reader.value
cmd_stderr = stderr.read cmd_stderr = err_reader.value
cmd_status = wait_thr.value cmd_status = wait_thr.value
end end
......
This diff is collapsed.
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
"autosize": "^4.0.0", "autosize": "^4.0.0",
"axios": "^0.17.1", "axios": "^0.17.1",
"babel-core": "^6.26.3", "babel-core": "^6.26.3",
"babel-loader": "^7.1.4", "babel-loader": "^7.1.5",
"babel-plugin-transform-define": "^1.3.0", "babel-plugin-transform-define": "^1.3.0",
"babel-preset-latest": "^6.24.1", "babel-preset-latest": "^6.24.1",
"babel-preset-stage-2": "^6.24.1", "babel-preset-stage-2": "^6.24.1",
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
"compression-webpack-plugin": "^1.1.11", "compression-webpack-plugin": "^1.1.11",
"core-js": "^2.4.1", "core-js": "^2.4.1",
"cropper": "^2.3.0", "cropper": "^2.3.0",
"css-loader": "^0.28.11", "css-loader": "^1.0.0",
"d3-array": "^1.2.1", "d3-array": "^1.2.1",
"d3-axis": "^1.0.8", "d3-axis": "^1.0.8",
"d3-brush": "^1.0.4", "d3-brush": "^1.0.4",
...@@ -90,15 +90,15 @@ ...@@ -90,15 +90,15 @@
"url-loader": "^1.0.1", "url-loader": "^1.0.1",
"visibilityjs": "^1.2.4", "visibilityjs": "^1.2.4",
"vue": "^2.5.16", "vue": "^2.5.16",
"vue-loader": "^15.2.0", "vue-loader": "^15.2.4",
"vue-resource": "^1.5.0", "vue-resource": "^1.5.0",
"vue-router": "^3.0.1", "vue-router": "^3.0.1",
"vue-template-compiler": "^2.5.16", "vue-template-compiler": "^2.5.16",
"vue-virtual-scroll-list": "^1.2.5", "vue-virtual-scroll-list": "^1.2.5",
"vuex": "^3.0.1", "vuex": "^3.0.1",
"webpack": "^4.11.1", "webpack": "^4.16.0",
"webpack-bundle-analyzer": "^2.11.1", "webpack-bundle-analyzer": "^2.13.1",
"webpack-cli": "^3.0.2", "webpack-cli": "^3.0.8",
"webpack-stats-plugin": "^0.2.1", "webpack-stats-plugin": "^0.2.1",
"worker-loader": "^2.0.0" "worker-loader": "^2.0.0"
}, },
...@@ -123,15 +123,16 @@ ...@@ -123,15 +123,16 @@
"ignore": "^3.3.7", "ignore": "^3.3.7",
"istanbul": "^0.4.5", "istanbul": "^0.4.5",
"jasmine-core": "^2.9.0", "jasmine-core": "^2.9.0",
"jasmine-diff": "^0.1.3",
"jasmine-jquery": "^2.1.1", "jasmine-jquery": "^2.1.1",
"karma": "^2.0.2", "karma": "^2.0.4",
"karma-chrome-launcher": "^2.2.0", "karma-chrome-launcher": "^2.2.0",
"karma-coverage-istanbul-reporter": "^1.4.2", "karma-coverage-istanbul-reporter": "^1.4.2",
"karma-jasmine": "^1.1.1", "karma-jasmine": "^1.1.2",
"karma-mocha-reporter": "^2.2.5", "karma-mocha-reporter": "^2.2.5",
"karma-sourcemap-loader": "^0.3.7", "karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "3.0.0", "karma-webpack": "^4.0.0-beta.0",
"nodemon": "^1.17.3", "nodemon": "^1.18.2",
"prettier": "1.12.1", "prettier": "1.12.1",
"webpack-dev-server": "^3.1.4" "webpack-dev-server": "^3.1.4"
} }
......
...@@ -9,18 +9,6 @@ module QA ...@@ -9,18 +9,6 @@ module QA
project.name = 'protected-branch-project' project.name = 'protected-branch-project'
end 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 def initialize
@branch_name = 'test/branch' @branch_name = 'test/branch'
@allow_to_push = true @allow_to_push = true
...@@ -80,15 +68,6 @@ module QA ...@@ -80,15 +68,6 @@ module QA
end end
page.protect_branch 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 end
end end
......
...@@ -3,7 +3,7 @@ module QA ...@@ -3,7 +3,7 @@ module QA
module Main module Main
class OAuth < Page::Base class OAuth < Page::Base
view 'app/views/doorkeeper/authorizations/new.html.haml' do view 'app/views/doorkeeper/authorizations/new.html.haml' do
element :authorization_button, 'submit_tag "Authorize"' element :authorization_button, 'submit_tag _("Authorize")'
end end
def needs_authorization? def needs_authorization?
......
...@@ -16,7 +16,6 @@ module QA ...@@ -16,7 +16,6 @@ module QA
end end
view 'app/views/projects/protected_branches/_update_protected_branch.html.haml' do view 'app/views/projects/protected_branches/_update_protected_branch.html.haml' do
element :allowed_to_push
element :allowed_to_merge element :allowed_to_merge
end end
...@@ -24,10 +23,6 @@ module QA ...@@ -24,10 +23,6 @@ module QA
element :protected_branches_list element :protected_branches_list
end end
view 'app/views/projects/protected_branches/shared/_protected_branch.html.haml' do
element :protected_branch_name
end
def select_branch(branch_name) def select_branch(branch_name)
click_element :protected_branch_select click_element :protected_branch_select
...@@ -62,18 +57,6 @@ module QA ...@@ -62,18 +57,6 @@ module QA
click_on 'Protect' click_on 'Protect'
end 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 private
def click_allow(action, text) def click_allow(action, text)
......
...@@ -21,11 +21,8 @@ module QA ...@@ -21,11 +21,8 @@ module QA
end end
context 'when developers and maintainers are allowed to push to a protected branch' do 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 it 'user with push rights successfully pushes to the protected branch' do
expect(protected_branch.name).to have_content(branch_name) create_protected_branch(allow_to_push: true)
expect(protected_branch.push_allowance).to have_content('Developers + Maintainers')
push = push_new_file(branch_name) push = push_new_file(branch_name)
......
...@@ -28,6 +28,13 @@ FactoryBot.define do ...@@ -28,6 +28,13 @@ FactoryBot.define do
secret SecureRandom.hex secret SecureRandom.hex
end 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 trait :object_storage do
store ObjectStorage::Store::REMOTE store ObjectStorage::Store::REMOTE
end end
......
...@@ -29,8 +29,10 @@ ...@@ -29,8 +29,10 @@
"merge_when_pipeline_succeeds": { "type": "boolean" }, "merge_when_pipeline_succeeds": { "type": "boolean" },
"source_branch": { "type": "string" }, "source_branch": { "type": "string" },
"source_project_id": { "type": "integer" }, "source_project_id": { "type": "integer" },
"source_project_full_path": { "type": ["string", "null"]},
"target_branch": { "type": "string" }, "target_branch": { "type": "string" },
"target_project_id": { "type": "integer" }, "target_project_id": { "type": "integer" },
"target_project_full_path": { "type": ["string", "null"]},
"allow_collaboration": { "type": "boolean"}, "allow_collaboration": { "type": "boolean"},
"metrics": { "metrics": {
"oneOf": [ "oneOf": [
......
...@@ -104,7 +104,7 @@ describe('Environment item', () => { ...@@ -104,7 +104,7 @@ describe('Environment item', () => {
}, },
], ],
}, },
'stop_action?': true, has_stop_action: true,
environment_path: 'root/ci-folders/environments/31', environment_path: 'root/ci-folders/environments/31',
created_at: '2016-11-07T11:11:16.525Z', created_at: '2016-11-07T11:11:16.525Z',
updated_at: '2016-11-10T15:55:58.778Z', updated_at: '2016-11-10T15:55:58.778Z',
......
...@@ -7,7 +7,7 @@ export const environmentsList = [ ...@@ -7,7 +7,7 @@ export const environmentsList = [
external_url: null, external_url: null,
environment_type: null, environment_type: null,
last_deployment: null, last_deployment: null,
'stop_action?': false, has_stop_action: false,
environment_path: '/root/review-app/environments/7', environment_path: '/root/review-app/environments/7',
stop_path: '/root/review-app/environments/7/stop', stop_path: '/root/review-app/environments/7/stop',
created_at: '2017-01-31T10:53:46.894Z', created_at: '2017-01-31T10:53:46.894Z',
...@@ -22,7 +22,7 @@ export const environmentsList = [ ...@@ -22,7 +22,7 @@ export const environmentsList = [
external_url: null, external_url: null,
environment_type: 'build', environment_type: 'build',
last_deployment: null, last_deployment: null,
'stop_action?': false, has_stop_action: false,
environment_path: '/root/review-app/environments/12', environment_path: '/root/review-app/environments/12',
stop_path: '/root/review-app/environments/12/stop', stop_path: '/root/review-app/environments/12/stop',
created_at: '2017-02-01T19:42:18.400Z', created_at: '2017-02-01T19:42:18.400Z',
...@@ -41,7 +41,7 @@ export const serverData = [ ...@@ -41,7 +41,7 @@ export const serverData = [
external_url: null, external_url: null,
environment_type: null, environment_type: null,
last_deployment: null, last_deployment: null,
'stop_action?': false, has_stop_action: false,
environment_path: '/root/review-app/environments/7', environment_path: '/root/review-app/environments/7',
stop_path: '/root/review-app/environments/7/stop', stop_path: '/root/review-app/environments/7/stop',
created_at: '2017-01-31T10:53:46.894Z', created_at: '2017-01-31T10:53:46.894Z',
...@@ -58,7 +58,7 @@ export const serverData = [ ...@@ -58,7 +58,7 @@ export const serverData = [
external_url: null, external_url: null,
environment_type: 'build', environment_type: 'build',
last_deployment: null, last_deployment: null,
'stop_action?': false, has_stop_action: false,
environment_path: '/root/review-app/environments/12', environment_path: '/root/review-app/environments/12',
stop_path: '/root/review-app/environments/12/stop', stop_path: '/root/review-app/environments/12/stop',
created_at: '2017-02-01T19:42:18.400Z', created_at: '2017-02-01T19:42:18.400Z',
...@@ -77,7 +77,7 @@ export const environment = { ...@@ -77,7 +77,7 @@ export const environment = {
external_url: null, external_url: null,
environment_type: null, environment_type: null,
last_deployment: null, last_deployment: null,
'stop_action?': false, has_stop_action: false,
environment_path: '/root/review-app/environments/7', environment_path: '/root/review-app/environments/7',
stop_path: '/root/review-app/environments/7/stop', stop_path: '/root/review-app/environments/7/stop',
created_at: '2017-01-31T10:53:46.894Z', created_at: '2017-01-31T10:53:46.894Z',
...@@ -95,7 +95,7 @@ export const folder = { ...@@ -95,7 +95,7 @@ export const folder = {
external_url: null, external_url: null,
environment_type: 'build', environment_type: 'build',
last_deployment: null, last_deployment: null,
'stop_action?': false, has_stop_action: false,
environment_path: '/root/review-app/environments/12', environment_path: '/root/review-app/environments/12',
stop_path: '/root/review-app/environments/12/stop', stop_path: '/root/review-app/environments/12/stop',
created_at: '2017-02-01T19:42:18.400Z', created_at: '2017-02-01T19:42:18.400Z',
......
import Vue from 'vue'; import Vue from 'vue';
import axios from '~/lib/utils/axios_utils';
import performanceBarApp from '~/performance_bar/components/performance_bar_app.vue'; 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 PerformanceBarStore from '~/performance_bar/stores/performance_bar_store';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
import MockAdapter from 'axios-mock-adapter';
describe('performance bar', () => { describe('performance bar app', () => {
let mock;
let vm; let vm;
beforeEach(() => { beforeEach(() => {
const store = new PerformanceBarStore(); 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), { vm = mountComponent(Vue.extend(performanceBarApp), {
store, store,
env: 'development', env: 'development',
...@@ -45,44 +21,9 @@ describe('performance bar', () => { ...@@ -45,44 +21,9 @@ describe('performance bar', () => {
afterEach(() => { afterEach(() => {
vm.$destroy(); vm.$destroy();
mock.restore();
}); });
it('sets the class to match the environment', () => { it('sets the class to match the environment', () => {
expect(vm.$el.getAttribute('class')).toContain('development'); 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'; ...@@ -6,6 +6,7 @@ import '~/commons';
import Vue from 'vue'; import Vue from 'vue';
import VueResource from 'vue-resource'; import VueResource from 'vue-resource';
import Translate from '~/vue_shared/translate'; import Translate from '~/vue_shared/translate';
import jasmineDiff from 'jasmine-diff';
import { getDefaultAdapter } from '~/lib/utils/axios_utils'; import { getDefaultAdapter } from '~/lib/utils/axios_utils';
import { FIXTURES_PATH, TEST_HOST } from './test_constants'; import { FIXTURES_PATH, TEST_HOST } from './test_constants';
...@@ -35,7 +36,15 @@ Vue.use(Translate); ...@@ -35,7 +36,15 @@ Vue.use(Translate);
jasmine.getFixtures().fixturesPath = FIXTURES_PATH; jasmine.getFixtures().fixturesPath = FIXTURES_PATH;
jasmine.getJSONFixtures().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 // globalize common libraries
window.$ = $; window.$ = $;
......
...@@ -119,6 +119,7 @@ describe('MRWidgetHeader', () => { ...@@ -119,6 +119,7 @@ describe('MRWidgetHeader', () => {
beforeEach(() => { beforeEach(() => {
vm = mountComponent(Component, { vm = mountComponent(Component, {
mr: { mr: {
iid: 1,
divergedCommitsCount: 12, divergedCommitsCount: 12,
sourceBranch: 'mr-widget-refactor', sourceBranch: 'mr-widget-refactor',
sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>', sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
...@@ -130,6 +131,8 @@ describe('MRWidgetHeader', () => { ...@@ -130,6 +131,8 @@ describe('MRWidgetHeader', () => {
emailPatchesPath: '/mr/email-patches', emailPatchesPath: '/mr/email-patches',
plainDiffPath: '/mr/plainDiffPath', plainDiffPath: '/mr/plainDiffPath',
statusPath: 'abc', statusPath: 'abc',
sourceProjectFullPath: 'root/gitlab-ce',
targetProjectFullPath: 'gitlab-org/gitlab-ce',
}, },
}); });
}); });
...@@ -146,16 +149,40 @@ describe('MRWidgetHeader', () => { ...@@ -146,16 +149,40 @@ describe('MRWidgetHeader', () => {
const button = vm.$el.querySelector('.js-web-ide'); const button = vm.$el.querySelector('.js-web-ide');
expect(button.textContent.trim()).toEqual('Open in 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'; gon.relative_url_root = '/gitlab';
vm.mr.iid = 2;
vm.$nextTick(() => {
const button = vm.$el.querySelector('.js-web-ide'); const button = vm.$el.querySelector('.js-web-ide');
expect(button.textContent.trim()).toEqual('Open in Web IDE'); expect(button.textContent.trim()).toEqual('Open in Web IDE');
expect(button.getAttribute('href')).toEqual('/-/ide/projectabc'); 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', () => { it('renders download dropdown with links', () => {
......
...@@ -29,8 +29,10 @@ export default { ...@@ -29,8 +29,10 @@ export default {
source_branch: 'daaaa', source_branch: 'daaaa',
source_branch_link: 'daaaa', source_branch_link: 'daaaa',
source_project_id: 19, source_project_id: 19,
source_project_full_path: '/group1/project1',
target_branch: 'master', target_branch: 'master',
target_project_id: 19, target_project_id: 19,
target_project_full_path: '/group2/project2',
metrics: { metrics: {
merged_by: { merged_by: {
name: 'Administrator', name: 'Administrator',
......
...@@ -379,6 +379,20 @@ describe Gitlab::ClosingIssueExtractor do ...@@ -379,6 +379,20 @@ describe Gitlab::ClosingIssueExtractor do
.to match_array([issue, other_issue, third_issue]) .to match_array([issue, other_issue, third_issue])
end 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 it 'fetches issues in multi-line message' do
message = "Awesome commit (closes #{reference})\nAlso fixes #{reference2}" message = "Awesome commit (closes #{reference})\nAlso fixes #{reference2}"
......
...@@ -269,7 +269,7 @@ EOT ...@@ -269,7 +269,7 @@ EOT
before do before do
# TODO use a Gitaly diff object instead # TODO use a Gitaly diff object instead
rugged_commit = Gitlab::GitalyClient::StorageSettings.allow_disk_access do rugged_commit = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
repository.lookup('5937ac0a7beb003549fc5fd26fc247adbce4a52e') repository.rugged.rev_parse('5937ac0a7beb003549fc5fd26fc247adbce4a52e')
end end
@diffs = rugged_commit.parents[0].diff(rugged_commit).patches @diffs = rugged_commit.parents[0].diff(rugged_commit).patches
......
...@@ -5,7 +5,7 @@ describe Gitlab::Git::Index, seed_helper: true do ...@@ -5,7 +5,7 @@ describe Gitlab::Git::Index, seed_helper: true do
let(:index) { described_class.new(repository) } let(:index) { described_class.new(repository) }
before do before do
index.read_tree(repository.lookup('master').tree) index.read_tree(lookup('master').tree)
end end
around do |example| around do |example|
...@@ -30,7 +30,7 @@ describe Gitlab::Git::Index, seed_helper: true do ...@@ -30,7 +30,7 @@ describe Gitlab::Git::Index, seed_helper: true do
entry = index.get(options[:file_path]) entry = index.get(options[:file_path])
expect(entry).not_to be_nil 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
end end
...@@ -54,7 +54,7 @@ describe Gitlab::Git::Index, seed_helper: true do ...@@ -54,7 +54,7 @@ describe Gitlab::Git::Index, seed_helper: true do
index.create(options) index.create(options)
entry = index.get(options[:file_path]) 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
end end
...@@ -68,7 +68,7 @@ describe Gitlab::Git::Index, seed_helper: true do ...@@ -68,7 +68,7 @@ describe Gitlab::Git::Index, seed_helper: true do
index.create(options) index.create(options)
entry = index.get(options[:file_path]) 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 end
end end
...@@ -135,7 +135,7 @@ describe Gitlab::Git::Index, seed_helper: true do ...@@ -135,7 +135,7 @@ describe Gitlab::Git::Index, seed_helper: true do
entry = index.get(options[:file_path]) 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 end
it 'preserves file mode' do it 'preserves file mode' do
...@@ -190,7 +190,7 @@ describe Gitlab::Git::Index, seed_helper: true do ...@@ -190,7 +190,7 @@ describe Gitlab::Git::Index, seed_helper: true do
entry = index.get(options[:file_path]) entry = index.get(options[:file_path])
expect(entry).not_to be_nil 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
it 'preserves file mode' do it 'preserves file mode' do
...@@ -232,4 +232,8 @@ describe Gitlab::Git::Index, seed_helper: true do ...@@ -232,4 +232,8 @@ describe Gitlab::Git::Index, seed_helper: true do
end end
end end
end end
def lookup(revision)
repository.rugged.rev_parse(revision)
end
end end
...@@ -2,6 +2,9 @@ require 'spec_helper' ...@@ -2,6 +2,9 @@ require 'spec_helper'
describe 'Gitlab::Git::Popen' do describe 'Gitlab::Git::Popen' do
let(:path) { Rails.root.join('tmp').to_s } 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 let(:klass) do
Class.new(Object) do Class.new(Object) do
...@@ -70,6 +73,15 @@ describe 'Gitlab::Git::Popen' do ...@@ -70,6 +73,15 @@ describe 'Gitlab::Git::Popen' do
end end
end 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 end
context 'popen_with_timeout' do context 'popen_with_timeout' do
...@@ -85,6 +97,17 @@ describe 'Gitlab::Git::Popen' do ...@@ -85,6 +97,17 @@ describe 'Gitlab::Git::Popen' do
it { expect(output).to include('tests') } it { expect(output).to include('tests') }
end 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 context 'non-zero status' do
let(:result) { klass.new.popen_with_timeout(%w(cat NOTHING), timeout, path) } let(:result) { klass.new.popen_with_timeout(%w(cat NOTHING), timeout, path) }
let(:output) { result.first } let(:output) { result.first }
...@@ -110,6 +133,13 @@ describe 'Gitlab::Git::Popen' do ...@@ -110,6 +133,13 @@ describe 'Gitlab::Git::Popen' do
it "handles processes that do not shutdown correctly" 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) expect { klass.new.popen_with_timeout(['bash', '-c', "trap -- '' SIGTERM; sleep 1000"], timeout, path) }.to raise_error(Timeout::Error)
end 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 end
context 'timeout period' do context 'timeout period' do
......
...@@ -321,90 +321,6 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -321,90 +321,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
end end
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 describe '#commit_count' do
shared_examples 'simple commit counting' do shared_examples 'simple commit counting' do
it { expect(repository.commit_count("master")).to eq(25) } it { expect(repository.commit_count("master")).to eq(25) }
......
...@@ -9,6 +9,7 @@ describe Gitlab::ImportExport::AvatarSaver do ...@@ -9,6 +9,7 @@ describe Gitlab::ImportExport::AvatarSaver do
before do before do
FileUtils.mkdir_p("#{shared.export_path}/avatar/") FileUtils.mkdir_p("#{shared.export_path}/avatar/")
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
stub_feature_flags(import_export_object_storage: false)
end end
after do 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 ...@@ -7,6 +7,7 @@ describe Gitlab::ImportExport::UploadsSaver do
let(:shared) { project.import_export_shared } let(:shared) { project.import_export_shared }
before do before do
stub_feature_flags(import_export_object_storage: false)
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
end end
...@@ -30,7 +31,7 @@ describe Gitlab::ImportExport::UploadsSaver do ...@@ -30,7 +31,7 @@ describe Gitlab::ImportExport::UploadsSaver do
it 'copies the uploads to the export path' do it 'copies the uploads to the export path' do
saver.save 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') expect(uploads).to include('banana_sample.gif')
end end
...@@ -52,7 +53,7 @@ describe Gitlab::ImportExport::UploadsSaver do ...@@ -52,7 +53,7 @@ describe Gitlab::ImportExport::UploadsSaver do
it 'copies the uploads to the export path' do it 'copies the uploads to the export path' do
saver.save 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') expect(uploads).to include('banana_sample.gif')
end end
......
...@@ -55,6 +55,19 @@ describe Gitlab::Popen do ...@@ -55,6 +55,19 @@ describe Gitlab::Popen do
end end
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 context 'without a directory argument' do
before do before do
@output, @status = @klass.new.popen(%w(ls)) @output, @status = @klass.new.popen(%w(ls))
......
...@@ -1613,6 +1613,7 @@ describe Ci::Build do ...@@ -1613,6 +1613,7 @@ describe Ci::Build do
{ key: 'CI_JOB_NAME', value: 'test', public: true }, { key: 'CI_JOB_NAME', value: 'test', public: true },
{ key: 'CI_JOB_STAGE', value: 'test', public: true }, { key: 'CI_JOB_STAGE', value: 'test', public: true },
{ key: 'CI_COMMIT_SHA', value: build.sha, 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_NAME', value: build.ref, public: true },
{ key: 'CI_COMMIT_REF_SLUG', value: build.ref_slug, public: true }, { key: 'CI_COMMIT_REF_SLUG', value: build.ref_slug, public: true },
{ key: 'CI_BUILD_REF', value: build.sha, public: true }, { key: 'CI_BUILD_REF', value: build.sha, public: true },
......
...@@ -157,22 +157,4 @@ describe Deployment do ...@@ -157,22 +157,4 @@ describe Deployment do
end end
end 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 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