Commit 7206b29c authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch 'ce_upstream' into 'master'

CE upstream

Closes gitaly#677, gitaly#673, and gitlab-ce#39509

See merge request gitlab-org/gitlab-ee!3236
parents ec26ed59 73c7f444
...@@ -11,3 +11,5 @@ app/models/concerns/relative_positioning.rb ...@@ -11,3 +11,5 @@ app/models/concerns/relative_positioning.rb
app/workers/stuck_merge_jobs_worker.rb app/workers/stuck_merge_jobs_worker.rb
lib/gitlab/redis/*.rb lib/gitlab/redis/*.rb
lib/gitlab/gitaly_client/operation_service.rb lib/gitlab/gitaly_client/operation_service.rb
app/models/project_services/packagist_service.rb
lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb
# Documentation
- source: /doc/(.+?)\.md/ # doc/administration/build_artifacts.md
public: '\1.html' # doc/administration/build_artifacts.html
...@@ -121,7 +121,8 @@ linters: ...@@ -121,7 +121,8 @@ linters:
# Avoid nesting selectors too deeply. # Avoid nesting selectors too deeply.
NestingDepth: NestingDepth:
enabled: false enabled: true
max_depth: 6
# Always use placeholder selectors in @extend. # Always use placeholder selectors in @extend.
PlaceholderInExtend: PlaceholderInExtend:
......
0.49.0 0.51.0
\ No newline at end of file
...@@ -93,7 +93,7 @@ gem 'kaminari', '~> 1.0' ...@@ -93,7 +93,7 @@ gem 'kaminari', '~> 1.0'
gem 'hamlit', '~> 2.6.1' gem 'hamlit', '~> 2.6.1'
# Files attachments # Files attachments
gem 'carrierwave', '~> 1.1' gem 'carrierwave', '~> 1.2'
# Drag and Drop UI # Drag and Drop UI
gem 'dropzonejs-rails', '~> 0.7.1' gem 'dropzonejs-rails', '~> 0.7.1'
...@@ -414,7 +414,7 @@ group :ed25519 do ...@@ -414,7 +414,7 @@ group :ed25519 do
end end
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly-proto', '~> 0.48.0', require: 'gitaly' gem 'gitaly-proto', '~> 0.51.0', require: 'gitaly'
gem 'toml-rb', '~> 0.3.15', require: false gem 'toml-rb', '~> 0.3.15', require: false
......
...@@ -115,7 +115,7 @@ GEM ...@@ -115,7 +115,7 @@ GEM
capybara-screenshot (1.0.14) capybara-screenshot (1.0.14)
capybara (>= 1.0, < 3) capybara (>= 1.0, < 3)
launchy launchy
carrierwave (1.1.0) carrierwave (1.2.1)
activemodel (>= 4.0.0) activemodel (>= 4.0.0)
activesupport (>= 4.0.0) activesupport (>= 4.0.0)
mime-types (>= 1.16) mime-types (>= 1.16)
...@@ -297,7 +297,7 @@ GEM ...@@ -297,7 +297,7 @@ GEM
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gherkin-ruby (0.3.2) gherkin-ruby (0.3.2)
gitaly-proto (0.48.0) gitaly-proto (0.51.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
grpc (~> 1.0) grpc (~> 1.0)
github-linguist (4.7.6) github-linguist (4.7.6)
...@@ -1021,7 +1021,7 @@ DEPENDENCIES ...@@ -1021,7 +1021,7 @@ DEPENDENCIES
bundler-audit (~> 0.5.0) bundler-audit (~> 0.5.0)
capybara (~> 2.15.0) capybara (~> 2.15.0)
capybara-screenshot (~> 1.0.0) capybara-screenshot (~> 1.0.0)
carrierwave (~> 1.1) carrierwave (~> 1.2)
charlock_holmes (~> 0.7.5) charlock_holmes (~> 0.7.5)
chronic (~> 0.10.2) chronic (~> 0.10.2)
chronic_duration (~> 0.10.6) chronic_duration (~> 0.10.6)
...@@ -1065,7 +1065,7 @@ DEPENDENCIES ...@@ -1065,7 +1065,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0) gettext_i18n_rails_js (~> 1.2.0)
gitaly-proto (~> 0.48.0) gitaly-proto (~> 0.51.0)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 1.0) gitlab-license (~> 1.0)
......
import autosize from 'vendor/autosize'; import Autosize from 'autosize';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const autosizeEls = document.querySelectorAll('.js-autosize'); const autosizeEls = document.querySelectorAll('.js-autosize');
autosize(autosizeEls); Autosize(autosizeEls);
autosize.update(autosizeEls); Autosize.update(autosizeEls);
}); });
...@@ -64,19 +64,16 @@ export default class Clusters { ...@@ -64,19 +64,16 @@ export default class Clusters {
this.poll = new Poll({ this.poll = new Poll({
resource: this.service, resource: this.service,
method: 'fetchData', method: 'fetchData',
successCallback: (data) => { successCallback: data => this.handleSuccess(data),
const { status, status_reason } = data.data; errorCallback: () => Clusters.handleError(),
this.updateContainer(status, status_reason);
},
errorCallback: () => {
Flash(s__('ClusterIntegration|Something went wrong on our end.'));
},
}); });
if (!Visibility.hidden()) { if (!Visibility.hidden()) {
this.poll.makeRequest(); this.poll.makeRequest();
} else { } else {
this.service.fetchData(); this.service.fetchData()
.then(data => this.handleSuccess(data))
.catch(() => Clusters.handleError());
} }
Visibility.change(() => { Visibility.change(() => {
...@@ -88,6 +85,15 @@ export default class Clusters { ...@@ -88,6 +85,15 @@ export default class Clusters {
}); });
} }
static handleError() {
Flash(s__('ClusterIntegration|Something went wrong on our end.'));
}
handleSuccess(data) {
const { status, status_reason } = data.data;
this.updateContainer(status, status_reason);
}
hideAll() { hideAll() {
this.errorContainer.classList.add('hidden'); this.errorContainer.classList.add('hidden');
this.successContainer.classList.add('hidden'); this.successContainer.classList.add('hidden');
......
...@@ -256,12 +256,16 @@ import initGroupAnalytics from './init_group_analytics'; ...@@ -256,12 +256,16 @@ import initGroupAnalytics from './init_group_analytics';
case 'projects:milestones:new': case 'projects:milestones:new':
case 'projects:milestones:edit': case 'projects:milestones:edit':
case 'projects:milestones:update': case 'projects:milestones:update':
new ZenMode();
new DueDateSelectors();
new GLForm($('.milestone-form'), true);
break;
case 'groups:milestones:new': case 'groups:milestones:new':
case 'groups:milestones:edit': case 'groups:milestones:edit':
case 'groups:milestones:update': case 'groups:milestones:update':
new ZenMode(); new ZenMode();
new DueDateSelectors(); new DueDateSelectors();
new GLForm($('.milestone-form'), true); new GLForm($('.milestone-form'), false);
break; break;
case 'projects:compare:show': case 'projects:compare:show':
new Diff(); new Diff();
......
/* eslint-disable func-names, no-underscore-dangle, space-before-function-paren, no-var, one-var, one-var-declaration-per-line, prefer-rest-params, max-len, vars-on-top, wrap-iife, no-unused-vars, quotes, no-shadow, no-cond-assign, prefer-arrow-callback, no-return-assign, no-else-return, camelcase, comma-dangle, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, prefer-template, no-param-reassign, no-loop-func, no-mixed-operators */ /* eslint-disable func-names, no-underscore-dangle, space-before-function-paren, no-var, one-var, one-var-declaration-per-line, prefer-rest-params, max-len, vars-on-top, wrap-iife, no-unused-vars, quotes, no-shadow, no-cond-assign, prefer-arrow-callback, no-return-assign, no-else-return, camelcase, comma-dangle, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, prefer-template, no-param-reassign, no-loop-func, no-mixed-operators */
/* global fuzzaldrinPlus */ /* global fuzzaldrinPlus */
import _ from 'underscore'; import _ from 'underscore';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
import { isObject } from './lib/utils/type_utility'; import { isObject } from './lib/utils/type_utility';
var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote, GitLabDropdownInput; var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote, GitLabDropdownInput;
......
...@@ -12,7 +12,6 @@ import svg4everybody from 'svg4everybody'; ...@@ -12,7 +12,6 @@ import svg4everybody from 'svg4everybody';
// libraries with import side-effects // libraries with import side-effects
import 'mousetrap'; import 'mousetrap';
import 'mousetrap/plugins/pause/mousetrap-pause'; import 'mousetrap/plugins/pause/mousetrap-pause';
import 'vendor/fuzzaldrin-plus';
// expose common libraries as globals (TODO: remove these) // expose common libraries as globals (TODO: remove these)
window.jQuery = jQuery; window.jQuery = jQuery;
......
...@@ -12,7 +12,7 @@ newline-per-chained-call, no-useless-escape, class-methods-use-this */ ...@@ -12,7 +12,7 @@ newline-per-chained-call, no-useless-escape, class-methods-use-this */
import $ from 'jquery'; import $ from 'jquery';
import _ from 'underscore'; import _ from 'underscore';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import autosize from 'vendor/autosize'; import Autosize from 'autosize';
import 'vendor/jquery.caret'; // required by jquery.atwho import 'vendor/jquery.caret'; // required by jquery.atwho
import 'vendor/jquery.atwho'; import 'vendor/jquery.atwho';
import AjaxCache from '~/lib/utils/ajax_cache'; import AjaxCache from '~/lib/utils/ajax_cache';
...@@ -25,7 +25,7 @@ import TaskList from './task_list'; ...@@ -25,7 +25,7 @@ import TaskList from './task_list';
import { ajaxPost, isInViewport, getPagePath, scrollToElement, isMetaKey } from './lib/utils/common_utils'; import { ajaxPost, isInViewport, getPagePath, scrollToElement, isMetaKey } from './lib/utils/common_utils';
import imageDiffHelper from './image_diff/helpers/index'; import imageDiffHelper from './image_diff/helpers/index';
window.autosize = autosize; window.autosize = Autosize;
function normalizeNewlines(str) { function normalizeNewlines(str) {
return str.replace(/\r\n/g, '\n'); return str.replace(/\r\n/g, '\n');
......
<script> <script>
import { mapActions, mapGetters } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
import _ from 'underscore'; import _ from 'underscore';
import autosize from 'vendor/autosize'; import Autosize from 'autosize';
import Flash from '../../flash'; import Flash from '../../flash';
import Autosave from '../../autosave'; import Autosave from '../../autosave';
import TaskList from '../../task_list'; import TaskList from '../../task_list';
...@@ -219,7 +219,7 @@ ...@@ -219,7 +219,7 @@
}, },
resizeTextarea() { resizeTextarea() {
this.$nextTick(() => { this.$nextTick(() => {
autosize.update(this.$refs.textarea); Autosize.update(this.$refs.textarea);
}); });
}, },
}, },
......
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, consistent-return, one-var, one-var-declaration-per-line, no-cond-assign, max-len, object-shorthand, no-param-reassign, comma-dangle, prefer-template, no-unused-vars, no-return-assign */ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, consistent-return, one-var, one-var-declaration-per-line, no-cond-assign, max-len, object-shorthand, no-param-reassign, comma-dangle, prefer-template, no-unused-vars, no-return-assign */
/* global fuzzaldrinPlus */
import fuzzaldrinPlus from 'fuzzaldrin-plus';
(function() { (function() {
this.ProjectFindFile = (function() { this.ProjectFindFile = (function() {
......
...@@ -7,9 +7,10 @@ import ProjectSelectComboButton from './project_select_combo_button'; ...@@ -7,9 +7,10 @@ import ProjectSelectComboButton from './project_select_combo_button';
function ProjectSelect() { function ProjectSelect() {
$('.ajax-project-select').each(function(i, select) { $('.ajax-project-select').each(function(i, select) {
var placeholder; var placeholder;
const simpleFilter = $(select).data('simple-filter') || false;
this.groupId = $(select).data('group-id'); this.groupId = $(select).data('group-id');
this.includeGroups = $(select).data('include-groups'); this.includeGroups = $(select).data('include-groups');
this.allProjects = $(select).data('allprojects') || false; this.allProjects = $(select).data('all-projects') || false;
this.orderBy = $(select).data('order-by') || 'id'; this.orderBy = $(select).data('order-by') || 'id';
this.withIssuesEnabled = $(select).data('with-issues-enabled'); this.withIssuesEnabled = $(select).data('with-issues-enabled');
this.withMergeRequestsEnabled = $(select).data('with-merge-requests-enabled'); this.withMergeRequestsEnabled = $(select).data('with-merge-requests-enabled');
...@@ -52,12 +53,13 @@ import ProjectSelectComboButton from './project_select_combo_button'; ...@@ -52,12 +53,13 @@ import ProjectSelectComboButton from './project_select_combo_button';
order_by: _this.orderBy, order_by: _this.orderBy,
with_issues_enabled: _this.withIssuesEnabled, with_issues_enabled: _this.withIssuesEnabled,
with_merge_requests_enabled: _this.withMergeRequestsEnabled, with_merge_requests_enabled: _this.withMergeRequestsEnabled,
membership: !_this.allProjects membership: !_this.allProjects,
}, projectsCallback); }, projectsCallback);
} }
}; };
})(this), })(this),
id: function(project) { id: function(project) {
if (simpleFilter) return project.id;
return JSON.stringify({ return JSON.stringify({
name: project.name, name: project.name,
url: project.web_url, url: project.web_url,
...@@ -68,7 +70,7 @@ import ProjectSelectComboButton from './project_select_combo_button'; ...@@ -68,7 +70,7 @@ import ProjectSelectComboButton from './project_select_combo_button';
}, },
dropdownCssClass: "ajax-project-dropdown" dropdownCssClass: "ajax-project-dropdown"
}); });
if (simpleFilter) return select;
return new ProjectSelectComboButton(select); return new ProjectSelectComboButton(select);
}); });
} }
......
<script> <script>
import { mapState } from 'vuex'; import { mapState } from 'vuex';
import newModal from './modal.vue'; import newModal from './modal.vue';
import upload from './upload.vue';
export default { export default {
components: { components: {
newModal, newModal,
upload,
}, },
data() { data() {
return { return {
...@@ -55,6 +57,11 @@ ...@@ -55,6 +57,11 @@
{{ __('New file') }} {{ __('New file') }}
</a> </a>
</li> </li>
<li>
<upload
:path="path"
/>
</li>
<li> <li>
<a <a
href="#" href="#"
......
...@@ -5,11 +5,11 @@ ...@@ -5,11 +5,11 @@
export default { export default {
props: { props: {
path: { type: {
type: String, type: String,
required: true, required: true,
}, },
type: { path: {
type: String, type: String,
required: true, required: true,
}, },
......
<script>
import { mapActions } from 'vuex';
export default {
props: {
path: {
type: String,
required: true,
},
},
methods: {
...mapActions([
'createTempEntry',
]),
createFile(target, file, isText) {
const { name } = file;
let { result } = target;
if (!isText) {
result = result.split('base64,')[1];
}
this.createTempEntry({
name,
type: 'blob',
content: result,
base64: !isText,
});
},
readFile(file) {
const reader = new FileReader();
const isText = file.type.match(/text.*/) !== null;
reader.addEventListener('load', e => this.createFile(e.target, file, isText), { once: true });
if (isText) {
reader.readAsText(file);
} else {
reader.readAsDataURL(file);
}
},
openFile() {
Array.from(this.$refs.fileUpload.files).forEach(file => this.readFile(file));
},
},
mounted() {
this.$refs.fileUpload.addEventListener('change', this.openFile);
},
beforeDestroy() {
this.$refs.fileUpload.removeEventListener('change', this.openFile);
},
};
</script>
<template>
<label
role="button"
class="menu-item"
>
{{ __('Upload file') }}
<input
id="file-upload"
type="file"
class="hidden"
ref="fileUpload"
/>
</label>
</template>
...@@ -776,12 +776,15 @@ ...@@ -776,12 +776,15 @@
a, a,
button, button,
.menu-item { .menu-item {
margin-bottom: 0;
border-radius: 0; border-radius: 0;
box-shadow: none; box-shadow: none;
padding: 8px 16px; padding: 8px 16px;
text-align: left; text-align: left;
white-space: normal; white-space: normal;
width: 100%; width: 100%;
font-weight: $gl-font-weight-normal;
line-height: normal;
&.dropdown-menu-user-link { &.dropdown-menu-user-link {
white-space: nowrap; white-space: nowrap;
......
...@@ -216,12 +216,9 @@ body { ...@@ -216,12 +216,9 @@ body {
color: $theme-gray-900; color: $theme-gray-900;
} }
&.active > a { &.active > a,
&.active > a:hover {
color: $white-light; color: $white-light;
&:hover {
color: $white-light;
}
} }
} }
} }
......
...@@ -239,10 +239,8 @@ ...@@ -239,10 +239,8 @@
fill: currentColor; fill: currentColor;
} }
&.header-user-dropdown-toggle { &.header-user-dropdown-toggle .header-user-avatar {
.header-user-avatar { border-color: $white-light;
border-color: $white-light;
}
} }
} }
} }
......
...@@ -120,10 +120,10 @@ ...@@ -120,10 +120,10 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: flex-end; justify-content: flex-end;
}
&:hover { &:last-child:hover {
background-color: $gray-light; background-color: $gray-light;
}
} }
} }
} }
......
...@@ -269,7 +269,7 @@ ul.notes { ...@@ -269,7 +269,7 @@ ul.notes {
display: none; display: none;
} }
&.system-note-commit-list { &.system-note-commit-list:not(.hide-shade) {
max-height: 70px; max-height: 70px;
overflow: hidden; overflow: hidden;
display: block; display: block;
...@@ -291,16 +291,6 @@ ul.notes { ...@@ -291,16 +291,6 @@ ul.notes {
bottom: 0; bottom: 0;
background: linear-gradient(rgba($white-light, 0.1) -100px, $white-light 100%); background: linear-gradient(rgba($white-light, 0.1) -100px, $white-light 100%);
} }
&.hide-shade {
max-height: 100%;
overflow: auto;
&::after {
display: none;
background: transparent;
}
}
} }
} }
} }
......
...@@ -19,11 +19,7 @@ module NavHelper ...@@ -19,11 +19,7 @@ module NavHelper
end end
elsif current_path?('jobs#show') elsif current_path?('jobs#show')
%w[page-gutter build-sidebar right-sidebar-expanded] %w[page-gutter build-sidebar right-sidebar-expanded]
elsif current_path?('wikis#show') || elsif current_controller?('wikis') && current_action?('show', 'create', 'edit', 'update', 'history', 'git_access')
current_path?('wikis#edit') ||
current_path?('wikis#update') ||
current_path?('wikis#history') ||
current_path?('wikis#git_access')
%w[page-gutter wiki-sidebar right-sidebar-expanded] %w[page-gutter wiki-sidebar right-sidebar-expanded]
else else
[] []
......
...@@ -467,7 +467,7 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -467,7 +467,7 @@ class ApplicationSetting < ActiveRecord::Base
# the enabling/disabling is `performance_bar_allowed_group_id` # the enabling/disabling is `performance_bar_allowed_group_id`
# - If `enable` is false, we set `performance_bar_allowed_group_id` to `nil` # - If `enable` is false, we set `performance_bar_allowed_group_id` to `nil`
def performance_bar_enabled=(enable) def performance_bar_enabled=(enable)
return if enable return if Gitlab::Utils.to_boolean(enable)
self.performance_bar_allowed_group_id = nil self.performance_bar_allowed_group_id = nil
end end
......
...@@ -8,7 +8,10 @@ class Identity < ActiveRecord::Base ...@@ -8,7 +8,10 @@ class Identity < ActiveRecord::Base
validates :user_id, uniqueness: { scope: :provider } validates :user_id, uniqueness: { scope: :provider }
scope :with_provider, ->(provider) { where(provider: provider) } scope :with_provider, ->(provider) { where(provider: provider) }
scope :with_extern_uid, ->(provider, extern_uid) { where(extern_uid: extern_uid, provider: provider) } scope :with_extern_uid, ->(provider, extern_uid) do
extern_uid = Gitlab::LDAP::Person.normalize_dn(extern_uid) if provider.starts_with?('ldap')
where(extern_uid: extern_uid, provider: provider)
end
def ldap? def ldap?
provider.starts_with?('ldap') provider.starts_with?('ldap')
......
...@@ -404,6 +404,10 @@ class MergeRequest < ActiveRecord::Base ...@@ -404,6 +404,10 @@ class MergeRequest < ActiveRecord::Base
end end
def merge_ongoing? def merge_ongoing?
# While the MergeRequest is locked, it should present itself as 'merge ongoing'.
# The unlocking process is handled by StuckMergeJobsWorker scheduled in Cron.
return true if locked?
!!merge_jid && !merged? && Gitlab::SidekiqStatus.running?(merge_jid) !!merge_jid && !merged? && Gitlab::SidekiqStatus.running?(merge_jid)
end end
...@@ -899,7 +903,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -899,7 +903,7 @@ class MergeRequest < ActiveRecord::Base
# #
def all_commit_shas def all_commit_shas
if persisted? if persisted?
column_shas = MergeRequestDiffCommit.where(merge_request_diff: merge_request_diffs).pluck('DISTINCT(sha)') column_shas = MergeRequestDiffCommit.where(merge_request_diff: merge_request_diffs).limit(10_000).pluck('sha')
serialised_shas = merge_request_diffs.where.not(st_commits: nil).flat_map(&:commit_shas) serialised_shas = merge_request_diffs.where.not(st_commits: nil).flat_map(&:commit_shas)
(column_shas + serialised_shas).uniq (column_shas + serialised_shas).uniq
......
...@@ -29,7 +29,15 @@ class Project < ActiveRecord::Base ...@@ -29,7 +29,15 @@ class Project < ActiveRecord::Base
NUMBER_OF_PERMITTED_BOARDS = 1 NUMBER_OF_PERMITTED_BOARDS = 1
UNKNOWN_IMPORT_URL = 'http://unknown.git'.freeze UNKNOWN_IMPORT_URL = 'http://unknown.git'.freeze
LATEST_STORAGE_VERSION = 1 # Hashed Storage versions handle rolling out new storage to project and dependents models:
# nil: legacy
# 1: repository
# 2: attachments
LATEST_STORAGE_VERSION = 2
HASHED_STORAGE_FEATURES = {
repository: 1,
attachments: 2
}.freeze
cache_markdown_field :description, pipeline: :description cache_markdown_field :description, pipeline: :description
...@@ -124,6 +132,7 @@ class Project < ActiveRecord::Base ...@@ -124,6 +132,7 @@ class Project < ActiveRecord::Base
has_one :mock_deployment_service has_one :mock_deployment_service
has_one :mock_monitoring_service has_one :mock_monitoring_service
has_one :microsoft_teams_service has_one :microsoft_teams_service
has_one :packagist_service
# TODO: replace these relations with the fork network versions # TODO: replace these relations with the fork network versions
has_one :forked_project_link, foreign_key: "forked_to_project_id" has_one :forked_project_link, foreign_key: "forked_to_project_id"
...@@ -1083,6 +1092,7 @@ class Project < ActiveRecord::Base ...@@ -1083,6 +1092,7 @@ class Project < ActiveRecord::Base
def hook_attrs(backward: true) def hook_attrs(backward: true)
attrs = { attrs = {
id: id,
name: name, name: name,
description: description, description: description,
web_url: web_url, web_url: web_url,
...@@ -1394,6 +1404,19 @@ class Project < ActiveRecord::Base ...@@ -1394,6 +1404,19 @@ class Project < ActiveRecord::Base
end end
end end
def after_rename_repo
path_before_change = previous_changes['path'].first
# We need to check if project had been rolled out to move resource to hashed storage or not and decide
# if we need execute any take action or no-op.
unless hashed_storage?(:attachments)
Gitlab::UploadsTransfer.new.rename_project(path_before_change, self.path, namespace.full_path)
end
Gitlab::PagesTransfer.new.rename_project(path_before_change, self.path, namespace.full_path)
end
def rename_repo_notify! def rename_repo_notify!
send_move_instructions(full_path_was) send_move_instructions(full_path_was)
expires_full_path_cache expires_full_path_cache
...@@ -1404,13 +1427,6 @@ class Project < ActiveRecord::Base ...@@ -1404,13 +1427,6 @@ class Project < ActiveRecord::Base
reload_repository! reload_repository!
end end
def after_rename_repo
path_before_change = previous_changes['path'].first
Gitlab::UploadsTransfer.new.rename_project(path_before_change, self.path, namespace.full_path)
Gitlab::PagesTransfer.new.rename_project(path_before_change, self.path, namespace.full_path)
end
def running_or_pending_build_count(force: false) def running_or_pending_build_count(force: false)
Rails.cache.fetch(['projects', id, 'running_or_pending_build_count'], force: force) do Rails.cache.fetch(['projects', id, 'running_or_pending_build_count'], force: force) do
builds.running_or_pending.count(:all) builds.running_or_pending.count(:all)
...@@ -1600,8 +1616,13 @@ class Project < ActiveRecord::Base ...@@ -1600,8 +1616,13 @@ class Project < ActiveRecord::Base
[nil, 0].include?(self.storage_version) [nil, 0].include?(self.storage_version)
end end
def hashed_storage? # Check if Hashed Storage is enabled for the project with at least informed feature rolled out
self.storage_version && self.storage_version >= 1 #
# @param [Symbol] feature that needs to be rolled out for the project (:repository, :attachments)
def hashed_storage?(feature)
raise ArgumentError, "Invalid feature" unless HASHED_STORAGE_FEATURES.include?(feature)
self.storage_version && self.storage_version >= HASHED_STORAGE_FEATURES[feature]
end end
def renamed? def renamed?
...@@ -1637,7 +1658,7 @@ class Project < ActiveRecord::Base ...@@ -1637,7 +1658,7 @@ class Project < ActiveRecord::Base
end end
def migrate_to_hashed_storage! def migrate_to_hashed_storage!
return if hashed_storage? return if hashed_storage?(:repository)
update!(repository_read_only: true) update!(repository_read_only: true)
...@@ -1662,7 +1683,7 @@ class Project < ActiveRecord::Base ...@@ -1662,7 +1683,7 @@ class Project < ActiveRecord::Base
def storage def storage
@storage ||= @storage ||=
if hashed_storage? if hashed_storage?(:repository)
Storage::HashedProject.new(self) Storage::HashedProject.new(self)
else else
Storage::LegacyProject.new(self) Storage::LegacyProject.new(self)
......
class PackagistService < Service
include HTTParty
prop_accessor :username, :token, :server
validates :username, presence: true, if: :activated?
validates :token, presence: true, if: :activated?
default_value_for :push_events, true
default_value_for :tag_push_events, true
after_save :compose_service_hook, if: :activated?
def title
'Packagist'
end
def description
'Update your project on Packagist, the main Composer repository'
end
def self.to_param
'packagist'
end
def fields
[
{ type: 'text', name: 'username', placeholder: '', required: true },
{ type: 'text', name: 'token', placeholder: '', required: true },
{ type: 'text', name: 'server', placeholder: 'https://packagist.org', required: false }
]
end
def self.supported_events
%w(push merge_request tag_push)
end
def execute(data)
return unless supported_events.include?(data[:object_kind])
service_hook.execute(data)
end
def test(data)
begin
result = execute(data)
return { success: false, result: result[:message] } if result[:http_status] != 202
rescue StandardError => error
return { success: false, result: error }
end
{ success: true, result: result[:message] }
end
def compose_service_hook
hook = service_hook || build_service_hook
hook.url = hook_url
hook.save
end
def hook_url
base_url = server.present? ? server : 'https://packagist.org'
"#{base_url}/api/update-package?username=#{username}&apiToken=#{token}"
end
end
...@@ -240,6 +240,7 @@ class Service < ActiveRecord::Base ...@@ -240,6 +240,7 @@ class Service < ActiveRecord::Base
kubernetes kubernetes
mattermost_slash_commands mattermost_slash_commands
mattermost mattermost
packagist
pipelines_email pipelines_email
pivotaltracker pivotaltracker
prometheus prometheus
......
...@@ -9,6 +9,7 @@ module Issues ...@@ -9,6 +9,7 @@ module Issues
notification_service.reopen_issue(issue, current_user) notification_service.reopen_issue(issue, current_user)
execute_hooks(issue, 'reopen') execute_hooks(issue, 'reopen')
invalidate_cache_counts(issue, users: issue.assignees) invalidate_cache_counts(issue, users: issue.assignees)
issue.update_project_counter_caches
end end
issue issue
......
...@@ -106,16 +106,9 @@ module MergeRequests ...@@ -106,16 +106,9 @@ module MergeRequests
@merge_request.can_remove_source_branch?(branch_deletion_user) @merge_request.can_remove_source_branch?(branch_deletion_user)
end end
# Logs merge error message and cleans `MergeRequest#merge_jid`.
#
def handle_merge_error(log_message:, save_message_on_model: false) def handle_merge_error(log_message:, save_message_on_model: false)
Rails.logger.error("MergeService ERROR: #{merge_request_info} - #{log_message}") Rails.logger.error("MergeService ERROR: #{merge_request_info} - #{log_message}")
@merge_request.update(merge_error: log_message) if save_message_on_model
if save_message_on_model
@merge_request.update(merge_error: log_message, merge_jid: nil)
else
clean_merge_jid
end
end end
def merge_request_info def merge_request_info
......
...@@ -11,6 +11,7 @@ module MergeRequests ...@@ -11,6 +11,7 @@ module MergeRequests
merge_request.reload_diff(current_user) merge_request.reload_diff(current_user)
merge_request.mark_as_unchecked merge_request.mark_as_unchecked
invalidate_cache_counts(merge_request, users: merge_request.assignees) invalidate_cache_counts(merge_request, users: merge_request.assignees)
merge_request.update_project_counter_caches
end end
merge_request merge_request
......
...@@ -12,7 +12,7 @@ module Projects ...@@ -12,7 +12,7 @@ module Projects
end end
def execute def execute
return if project.hashed_storage? return if project.hashed_storage?(:repository)
@old_disk_path = project.disk_path @old_disk_path = project.disk_path
has_wiki = project.wiki.repository_exists? has_wiki = project.wiki.repository_exists?
......
...@@ -30,7 +30,7 @@ class FileUploader < GitlabUploader ...@@ -30,7 +30,7 @@ class FileUploader < GitlabUploader
# #
# Returns a String without a trailing slash # Returns a String without a trailing slash
def self.dynamic_path_segment(model) def self.dynamic_path_segment(model)
File.join(CarrierWave.root, base_dir, model.full_path) File.join(CarrierWave.root, base_dir, model.disk_path)
end end
attr_accessor :model attr_accessor :model
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
= f.label :description, "Description", class: "control-label" = f.label :description, "Description", class: "control-label"
.col-sm-10 .col-sm-10
= render layout: 'projects/md_preview', locals: { url: group_preview_markdown_path } do = render layout: 'projects/md_preview', locals: { url: group_preview_markdown_path } do
= render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: 'Write milestone description...' = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: 'Write milestone description...', supports_autocomplete: false
.clearfix .clearfix
.error-alert .error-alert
......
...@@ -23,7 +23,7 @@ class StuckMergeJobsWorker ...@@ -23,7 +23,7 @@ class StuckMergeJobsWorker
merge_requests = MergeRequest.where(id: completed_ids) merge_requests = MergeRequest.where(id: completed_ids)
merge_requests.where.not(merge_commit_sha: nil).update_all(state: :merged) merge_requests.where.not(merge_commit_sha: nil).update_all(state: :merged)
merge_requests.where(merge_commit_sha: nil).update_all(state: :opened) merge_requests.where(merge_commit_sha: nil).update_all(state: :opened, merge_jid: nil)
Rails.logger.info("Updated state of locked merge jobs. JIDs: #{completed_jids.join(', ')}") Rails.logger.info("Updated state of locked merge jobs. JIDs: #{completed_jids.join(', ')}")
end end
......
---
title: Adds project_id to pipeline hook data
merge_request: 15044
author: Jacopo Beschi @jacopo-beschi
type: added
---
title: Hashed Storage support for Attachments
merge_request: 15068
author:
type: added
---
title: Stop merge requests with thousands of commits from timing out
merge_request: 15063
author:
type: performance
---
title: Fix overlap of right-sidebar and main content when creating a Wiki page
merge_request:
author:
type: fixed
---
title: Allow to disable the Performance Bar
merge_request: 15084
author:
type: fixed
---
title: Bump carrierwave to 1.2.1
merge_request: 15072
author: Takuya Noguchi
type: other
---
title: Enable NestingDepth (level 6) on scss-lint
merge_request: 15073
author: Takuya Noguchi
type: other
---
title: Refresh open Issue and Merge Request project counter caches when re-opening.
merge_request: 15085
author: Rob Ede @robjtede
type: fixed
---
title: Only set Auto-Submitted header once for emails on push
merge_request:
author:
type: fixed
---
title: Fix namespacing for MergeWhenPipelineSucceedsService in MR API
merge_request:
author:
type: fixed
---
title: Adds callback functions for initial request in clusters page
merge_request:
author:
type: fixed
---
title: Add Packagist project service
merge_request: 14493
author: Matt Coleman
type: added
---
title: Normalize LDAP DN when looking up identity
merge_request:
author:
type: fixed
---
title: Fix 500 errors caused by empty diffs in some discussions
merge_request: 14945
author: Alexander Popov
type: fixed
---
title: Fix missing Import/Export issue assignees
merge_request:
author:
type: fixed
---
title: Use project select dropdown not only as a combobutton
merge_request: 15043
author:
type: fixed
---
title: Returns a ssh url for go-get=1
merge_request: 14990
author: gvieira37
type: fixed
---
title: Fix widget of locked merge requests not being presented
merge_request:
author:
type: fixed
---
title: Allow files to uploaded in the multi-file editor
merge_request:
author:
type: added
---
title: Fix broken Members link when relative URL root paths are used
merge_request:
author:
type: fixed
---
title: Update i18n section in FE docs for marking and interpolation
merge_request:
author:
type: changed
...@@ -28,6 +28,12 @@ will be allowed to display the Performance Bar. ...@@ -28,6 +28,12 @@ will be allowed to display the Performance Bar.
Make sure _Enable the Performance Bar_ is checked and hit Make sure _Enable the Performance Bar_ is checked and hit
**Save** to save the changes. **Save** to save the changes.
Once the Performance Bar is enabled, you will need to press the [<kbd>p</kbd> +
<kbd>b</kbd> keyboard shortcut](../../../workflow/shortcuts.md) to actually
display it.
You can toggle the Bar using the same shortcut.
--- ---
![GitLab Performance Bar Admin Settings](img/performance_bar_configuration_settings.png) ![GitLab Performance Bar Admin Settings](img/performance_bar_configuration_settings.png)
......
...@@ -20,7 +20,7 @@ it, the client IP needs to be [included in a whitelist][whitelist]. ...@@ -20,7 +20,7 @@ it, the client IP needs to be [included in a whitelist][whitelist].
Currently the embedded Prometheus server is not automatically configured to Currently the embedded Prometheus server is not automatically configured to
collect metrics from this endpoint. We recommend setting up another Prometheus collect metrics from this endpoint. We recommend setting up another Prometheus
server, because the embedded server configuration is overwritten once every server, because the embedded server configuration is overwritten once every
[reconfigure of GitLab][reconfigure]. In the future this will not be required. [reconfigure of GitLab][reconfigure]. In the future this will not be required.
## Metrics available ## Metrics available
...@@ -45,6 +45,8 @@ In this experimental phase, only a few metrics are available: ...@@ -45,6 +45,8 @@ In this experimental phase, only a few metrics are available:
| redis_ping_success | Gauge | 9.4 | Whether or not the last redis ping succeeded | | redis_ping_success | Gauge | 9.4 | Whether or not the last redis ping succeeded |
| redis_ping_latency_seconds | Gauge | 9.4 | Round trip time of the redis ping | | redis_ping_latency_seconds | Gauge | 9.4 | Round trip time of the redis ping |
| user_session_logins_total | Counter | 9.4 | Counter of how many users have logged in | | user_session_logins_total | Counter | 9.4 | Counter of how many users have logged in |
| filesystem_circuitbreaker_latency_seconds | Histogram | 9.5 | Latency of the stat check the circuitbreaker uses to probe a shard |
| filesystem_circuitbreaker | Gauge | 9.5 | Wether or not the circuit for a certain shard is broken or not |
## Metrics shared directory ## Metrics shared directory
......
...@@ -25,7 +25,10 @@ Any change in the URL will need to be reflected on disk (when groups / users or ...@@ -25,7 +25,10 @@ Any change in the URL will need to be reflected on disk (when groups / users or
of load in big installations, and can be even worst if they are using any type of network based filesystem. of load in big installations, and can be even worst if they are using any type of network based filesystem.
Last, for GitLab Geo, this storage type means we have to synchronize the disk state, replicate renames in the correct Last, for GitLab Geo, this storage type means we have to synchronize the disk state, replicate renames in the correct
order or we may end-up with wrong repository or missing data temporarily. order or we may end-up with wrong repository or missing data temporarily.
This pattern also exists in other objects stored in GitLab, like issue Attachments, GitLab Pages artifacts,
Docker Containers for the integrated Registry, etc.
## Hashed Storage ## Hashed Storage
...@@ -67,3 +70,23 @@ To migrate your existing projects to the new storage type, check the specific [r ...@@ -67,3 +70,23 @@ To migrate your existing projects to the new storage type, check the specific [r
[ce-28283]: https://gitlab.com/gitlab-org/gitlab-ce/issues/28283 [ce-28283]: https://gitlab.com/gitlab-org/gitlab-ce/issues/28283
[rake tasks]: raketasks/storage.md#migrate-existing-projects-to-hashed-storage [rake tasks]: raketasks/storage.md#migrate-existing-projects-to-hashed-storage
[storage-paths]: repository_storage_types.md [storage-paths]: repository_storage_types.md
### Hashed Storage coverage
We are incrementally moving every storable object in GitLab to the Hashed Storage pattern. You can check the current
coverage status below.
Not that things stored in S3 compatible endpoint, will not have the downsides mentioned earlier, if they are not
prefixed with `#{namespace}/#{project_name}`, which is true for CI Cache and LFS Objects.
| Storable Object | Legacy Storage | Hashed Storage | S3 Compatible | GitLab Version |
| ----------------| -------------- | -------------- | ------------- | -------------- |
| Repository | Yes | Yes | - | 10.0 |
| Attachments | Yes | Yes | - | 10.2 |
| Avatars | Yes | No | - | - |
| Pages | Yes | No | - | - |
| Docker Registry | Yes | No | - | - |
| CI Build Logs | No | No | - | - |
| CI Artifacts | No | No | - | - |
| CI Cache | No | No | Yes | - |
| LFS Objects | Yes | No | Yes (EEP) | - |
...@@ -57,7 +57,7 @@ GET /projects/:id/pipelines/:pipeline_id ...@@ -57,7 +57,7 @@ GET /projects/:id/pipelines/:pipeline_id
| `pipeline_id` | integer | yes | The ID of a pipeline | | `pipeline_id` | integer | yes | The ID of a pipeline |
``` ```
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/pipeline/46" curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/pipelines/46"
``` ```
Example of response Example of response
......
...@@ -582,6 +582,40 @@ Delete Mattermost slash command service for a project. ...@@ -582,6 +582,40 @@ Delete Mattermost slash command service for a project.
DELETE /projects/:id/services/mattermost-slash-commands DELETE /projects/:id/services/mattermost-slash-commands
``` ```
## Packagist
Update your project on Packagist, the main Composer repository, when commits or tags are pushed to GitLab.
### Create/Edit Packagist service
Set Packagist service for a project.
```
PUT /projects/:id/services/packagist
```
Parameters:
- `username` (**required**)
- `token` (**required**)
- `server` (optional)
### Delete Packagist service
Delete Packagist service for a project.
```
DELETE /projects/:id/services/packagist
```
### Get Packagist service settings
Get Packagist service settings for a project.
```
GET /projects/:id/services/packagist
```
## Pipeline-Emails ## Pipeline-Emails
Get emails for GitLab CI pipelines. Get emails for GitLab CI pipelines.
......
...@@ -106,6 +106,10 @@ Frontend security practices. ...@@ -106,6 +106,10 @@ Frontend security practices.
## [Accessibility](accessibility.md) ## [Accessibility](accessibility.md)
Our accessibility standards and resources. Our accessibility standards and resources.
## [Internationalization (i18n) and Translations](../i18n/externalization.md)
Frontend internationalization support is described in [this document](../i18n/).
The [externalization part of the guide](../i18n/externalization.md) explains the helpers/methods available.
[rails]: http://rubyonrails.org/ [rails]: http://rubyonrails.org/
[haml]: http://haml.info/ [haml]: http://haml.info/
......
...@@ -180,15 +180,43 @@ aren't in the message with id `1 pipeline`. ...@@ -180,15 +180,43 @@ aren't in the message with id `1 pipeline`.
## Working with special content ## Working with special content
### Just marking content for parsing
- In Ruby/HAML:
```ruby
_('Subscribe')
```
- In JavaScript:
```js
import { __ } from '../../../locale';
const label = __('Subscribe');
```
Sometimes there are some dynamic translations that can't be found by the
parser when running `bundle exec rake gettext:find`. For these scenarios you can
use the [`_N` method](https://github.com/grosser/gettext_i18n_rails/blob/c09e38d481e0899ca7d3fc01786834fa8e7aab97/Readme.md#unfound-translations-with-rake-gettextfind).
There is also and alternative method to [translate messages from validation errors](https://github.com/grosser/gettext_i18n_rails/blob/c09e38d481e0899ca7d3fc01786834fa8e7aab97/Readme.md#option-a).
### Interpolation ### Interpolation
- In Ruby/HAML: - In Ruby/HAML:
```ruby ```ruby
_("Hello %{name}") % { name: 'Joe' } _("Hello %{name}") % { name: 'Joe' } => 'Hello Joe'
``` ```
- In JavaScript: Not supported at this moment. - In JavaScript:
```js
import { __, sprintf } from '../../../locale';
sprintf(__('Hello %{username}'), { username: 'Joe' }) => 'Hello Joe'
```
### Plurals ### Plurals
...@@ -234,14 +262,6 @@ Sometimes you need to add some context to the text that you want to translate ...@@ -234,14 +262,6 @@ Sometimes you need to add some context to the text that you want to translate
s__('OpenedNDaysAgo|Opened') s__('OpenedNDaysAgo|Opened')
``` ```
### Just marking content for parsing
Sometimes there are some dynamic translations that can't be found by the
parser when running `bundle exec rake gettext:find`. For these scenarios you can
use the [`_N` method](https://github.com/grosser/gettext_i18n_rails/blob/c09e38d481e0899ca7d3fc01786834fa8e7aab97/Readme.md#unfound-translations-with-rake-gettextfind).
There is also and alternative method to [translate messages from validation errors](https://github.com/grosser/gettext_i18n_rails/blob/c09e38d481e0899ca7d3fc01786834fa8e7aab97/Readme.md#option-a).
## Adding a new language ## Adding a new language
Let's suppose you want to add translations for a new language, let's say French. Let's suppose you want to add translations for a new language, let's say French.
......
...@@ -19,24 +19,30 @@ For example, for GitLab version 10.5.7: ...@@ -19,24 +19,30 @@ For example, for GitLab version 10.5.7:
* `5` represents minor version * `5` represents minor version
* `7` represents patch number * `7` represents patch number
## Security releases ## Patch releases
The current stable release will receive security patches and bug fixes Patch releases usually only include bug fixes and are only done for the current
(eg. `8.9.0` -> `8.9.1`). stable release. That said, in some cases, we may backport it to previous stable
release, depending on the severity of the bug.
Feature releases will mark the next supported stable For instance, if we release `10.1.1` with a fix for a severe bug introduced in
release where the minor version is increased numerically by increments of one `10.0.0`, we could backport the fix to a new `10.0.x` patch release.
(eg. `8.9 -> 8.10`).
Our current policy is to support one stable release at any given time. ### Security releases
For medium-level security issues, we may consider backporting to the previous two
Security releases are a special kind of patch release that only include security
fixes and patches (see below).
Our current policy is to support one stable release at any given time, but for
medium-level security issues, we may backport security fixes to the previous two
monthly releases. monthly releases.
For very serious security issues, there is [precedent](https://about.gitlab.com/2016/05/02/cve-2016-4340-patches/) For very serious security issues, there is
to backport security fixes to even more monthly releases of GitLab. This decision [precedent](https://about.gitlab.com/2016/05/02/cve-2016-4340-patches/)
is made on a case-by-case basis. to backport security fixes to even more monthly releases of GitLab.
This decision is made on a case-by-case basis.
## Version support ## Upgrade recommendations
We encourage everyone to run the latest stable release to ensure that you can We encourage everyone to run the latest stable release to ensure that you can
easily upgrade to the most secure and feature-rich GitLab experience. In order easily upgrade to the most secure and feature-rich GitLab experience. In order
...@@ -70,7 +76,6 @@ Please see the table below for some examples: ...@@ -70,7 +76,6 @@ Please see the table below for some examples:
| -------------- | ------------ | ------------------------ | ---------------- | | -------------- | ------------ | ------------------------ | ---------------- |
| 9.4.5 | 8.13.4 | `8.13.4` -> `8.17.7` -> `9.4.5` | `8.17.7` is the last version in version `8` | | 9.4.5 | 8.13.4 | `8.13.4` -> `8.17.7` -> `9.4.5` | `8.17.7` is the last version in version `8` |
| 10.1.4 | 8.13.4 | `8.13.4` -> `8.17.7` -> `9.5.8` -> `10.1.4` | `8.17.7` is the last version in version `8`, `9.5.8` is the last version in version `9` | | 10.1.4 | 8.13.4 | `8.13.4` -> `8.17.7` -> `9.5.8` -> `10.1.4` | `8.17.7` is the last version in version `8`, `9.5.8` is the last version in version `9` |
|
More information about the release procedures can be found in our More information about the release procedures can be found in our
[release-tools documentation][rel]. You may also want to read our [release-tools documentation][rel]. You may also want to read our
......
...@@ -136,44 +136,54 @@ In the example below we use Amazon S3 for storage, but Fog also lets you use ...@@ -136,44 +136,54 @@ In the example below we use Amazon S3 for storage, but Fog also lets you use
for AWS, Google, OpenStack Swift, Rackspace and Aliyun as well. A local driver is for AWS, Google, OpenStack Swift, Rackspace and Aliyun as well. A local driver is
[also available](#uploading-to-locally-mounted-shares). [also available](#uploading-to-locally-mounted-shares).
For omnibus packages, add the following to `/etc/gitlab/gitlab.rb`: #### Using Amazon S3
```ruby For Omnibus GitLab packages:
gitlab_rails['backup_upload_connection'] = {
'provider' => 'AWS', 1. Add the following to `/etc/gitlab/gitlab.rb`:
'region' => 'eu-west-1',
'aws_access_key_id' => 'AKIAKIAKI', ```ruby
'aws_secret_access_key' => 'secret123' gitlab_rails['backup_upload_connection'] = {
# If using an IAM Profile, don't configure aws_access_key_id & aws_secret_access_key 'provider' => 'AWS',
# 'use_iam_profile' => true 'region' => 'eu-west-1',
} 'aws_access_key_id' => 'AKIAKIAKI',
gitlab_rails['backup_upload_remote_directory'] = 'my.s3.bucket' 'aws_secret_access_key' => 'secret123'
``` # If using an IAM Profile, don't configure aws_access_key_id & aws_secret_access_key
# 'use_iam_profile' => true
}
gitlab_rails['backup_upload_remote_directory'] = 'my.s3.bucket'
```
1. [Reconfigure GitLab] for the changes to take effect
Make sure to run `sudo gitlab-ctl reconfigure` after editing `/etc/gitlab/gitlab.rb` to reflect the changes. ---
For installations from source: For installations from source:
```yaml 1. Edit `home/git/gitlab/config/gitlab.yml`:
backup:
# snip ```yaml
upload: backup:
# Fog storage connection settings, see http://fog.io/storage/ . # snip
connection: upload:
provider: AWS # Fog storage connection settings, see http://fog.io/storage/ .
region: eu-west-1 connection:
aws_access_key_id: AKIAKIAKI provider: AWS
aws_secret_access_key: 'secret123' region: eu-west-1
# If using an IAM Profile, leave aws_access_key_id & aws_secret_access_key empty aws_access_key_id: AKIAKIAKI
# ie. aws_access_key_id: '' aws_secret_access_key: 'secret123'
# use_iam_profile: 'true' # If using an IAM Profile, leave aws_access_key_id & aws_secret_access_key empty
# The remote 'directory' to store your backups. For S3, this would be the bucket name. # ie. aws_access_key_id: ''
remote_directory: 'my.s3.bucket' # use_iam_profile: 'true'
# Turns on AWS Server-Side Encryption with Amazon S3-Managed Keys for backups, this is optional # The remote 'directory' to store your backups. For S3, this would be the bucket name.
# encryption: 'AES256' remote_directory: 'my.s3.bucket'
# Specifies Amazon S3 storage class to use for backups, this is optional # Turns on AWS Server-Side Encryption with Amazon S3-Managed Keys for backups, this is optional
# storage_class: 'STANDARD' # encryption: 'AES256'
``` # Specifies Amazon S3 storage class to use for backups, this is optional
# storage_class: 'STANDARD'
```
1. [Restart GitLab] for the changes to take effect
If you are uploading your backups to S3 you will probably want to create a new If you are uploading your backups to S3 you will probably want to create a new
IAM user with restricted access rights. To give the upload user access only for IAM user with restricted access rights. To give the upload user access only for
...@@ -226,6 +236,50 @@ with the name of your bucket: ...@@ -226,6 +236,50 @@ with the name of your bucket:
} }
``` ```
#### Using Google Cloud Storage
If you want to use Google Cloud Storage to save backups, you'll have to create
an access key from the Google console first:
1. Go to the storage settings page https://console.cloud.google.com/storage/settings
1. Select "Interoperability" and create an access key
1. Make note of the "Access Key" and "Secret" and replace them in the
configurations below
1. Make sure you already have a bucket created
For Omnibus GitLab packages:
1. Edit `/etc/gitlab/gitlab.rb`:
```ruby
gitlab_rails['backup_upload_connection'] = {
'provider' => 'Google',
'google_storage_access_key_id' => 'Access Key',
'google_storage_secret_access_key' => 'Secret'
}
gitlab_rails['backup_upload_remote_directory'] = 'my.google.bucket'
```
1. [Reconfigure GitLab] for the changes to take effect
---
For installations from source:
1. Edit `home/git/gitlab/config/gitlab.yml`:
```yaml
backup:
upload:
connection:
provider: 'Google'
google_storage_access_key_id: 'Access Key'
google_storage_secret_access_key: 'Secret'
remote_directory: 'my.google.bucket'
```
1. [Restart GitLab] for the changes to take effect
### Uploading to locally mounted shares ### Uploading to locally mounted shares
You may also send backups to a mounted share (`NFS` / `CIFS` / `SMB` / etc.) by You may also send backups to a mounted share (`NFS` / `CIFS` / `SMB` / etc.) by
...@@ -554,3 +608,6 @@ The rake task runs this as the `gitlab` user which does not have the superuser a ...@@ -554,3 +608,6 @@ The rake task runs this as the `gitlab` user which does not have the superuser a
Those objects have no influence on the database backup/restore but they give this annoying warning. Those objects have no influence on the database backup/restore but they give this annoying warning.
For more information see similar questions on postgresql issue tracker[here](http://www.postgresql.org/message-id/201110220712.30886.adrian.klaver@gmail.com) and [here](http://www.postgresql.org/message-id/2039.1177339749@sss.pgh.pa.us) as well as [stack overflow](http://stackoverflow.com/questions/4368789/error-must-be-owner-of-language-plpgsql). For more information see similar questions on postgresql issue tracker[here](http://www.postgresql.org/message-id/201110220712.30886.adrian.klaver@gmail.com) and [here](http://www.postgresql.org/message-id/2039.1177339749@sss.pgh.pa.us) as well as [stack overflow](http://stackoverflow.com/questions/4368789/error-must-be-owner-of-language-plpgsql).
[reconfigure GitLab]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
...@@ -44,6 +44,7 @@ Click on the service links to see further configuration instructions and details ...@@ -44,6 +44,7 @@ Click on the service links to see further configuration instructions and details
| [Mattermost slash commands](mattermost_slash_commands.md) | Mattermost chat and ChatOps slash commands | | [Mattermost slash commands](mattermost_slash_commands.md) | Mattermost chat and ChatOps slash commands |
| [Mattermost Notifications](mattermost.md) | Receive event notifications in Mattermost | | [Mattermost Notifications](mattermost.md) | Receive event notifications in Mattermost |
| [Microsoft teams](microsoft_teams.md) | Receive notifications for actions that happen on GitLab into a room on Microsoft Teams using Office 365 Connectors | | [Microsoft teams](microsoft_teams.md) | Receive notifications for actions that happen on GitLab into a room on Microsoft Teams using Office 365 Connectors |
| Packagist | Update your project on Packagist, the main Composer repository |
| Pipelines emails | Email the pipeline status to a list of recipients | | Pipelines emails | Email the pipeline status to a list of recipients |
| [Slack Notifications](slack.md) | Send GitLab events (e.g. issue created) to Slack as notifications | | [Slack Notifications](slack.md) | Send GitLab events (e.g. issue created) to Slack as notifications |
| [Slack slash commands](slack_slash_commands.md) | Use slash commands in Slack to control GitLab | | [Slack slash commands](slack_slash_commands.md) | Use slash commands in Slack to control GitLab |
......
...@@ -101,6 +101,7 @@ X-Gitlab-Event: Push Hook ...@@ -101,6 +101,7 @@ X-Gitlab-Event: Push Hook
"user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80", "user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80",
"project_id": 15, "project_id": 15,
"project":{ "project":{
"id": 15,
"name":"Diaspora", "name":"Diaspora",
"description":"", "description":"",
"web_url":"http://example.com/mike/diaspora", "web_url":"http://example.com/mike/diaspora",
...@@ -181,6 +182,7 @@ X-Gitlab-Event: Tag Push Hook ...@@ -181,6 +182,7 @@ X-Gitlab-Event: Tag Push Hook
"user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80", "user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80",
"project_id": 1, "project_id": 1,
"project":{ "project":{
"id": 1,
"name":"Example", "name":"Example",
"description":"", "description":"",
"web_url":"http://example.com/jsmith/example", "web_url":"http://example.com/jsmith/example",
...@@ -231,6 +233,7 @@ X-Gitlab-Event: Issue Hook ...@@ -231,6 +233,7 @@ X-Gitlab-Event: Issue Hook
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon" "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
}, },
"project": { "project": {
"id": 1,
"name":"Gitlab Test", "name":"Gitlab Test",
"description":"Aut reprehenderit ut est.", "description":"Aut reprehenderit ut est.",
"web_url":"http://example.com/gitlabhq/gitlab-test", "web_url":"http://example.com/gitlabhq/gitlab-test",
...@@ -360,6 +363,7 @@ X-Gitlab-Event: Note Hook ...@@ -360,6 +363,7 @@ X-Gitlab-Event: Note Hook
}, },
"project_id": 5, "project_id": 5,
"project":{ "project":{
"id": 5,
"name":"Gitlab Test", "name":"Gitlab Test",
"description":"Aut reprehenderit ut est.", "description":"Aut reprehenderit ut est.",
"web_url":"http://example.com/gitlabhq/gitlab-test", "web_url":"http://example.com/gitlabhq/gitlab-test",
...@@ -439,6 +443,7 @@ X-Gitlab-Event: Note Hook ...@@ -439,6 +443,7 @@ X-Gitlab-Event: Note Hook
}, },
"project_id": 5, "project_id": 5,
"project":{ "project":{
"id": 5,
"name":"Gitlab Test", "name":"Gitlab Test",
"description":"Aut reprehenderit ut est.", "description":"Aut reprehenderit ut est.",
"web_url":"http://example.com/gitlab-org/gitlab-test", "web_url":"http://example.com/gitlab-org/gitlab-test",
...@@ -565,6 +570,7 @@ X-Gitlab-Event: Note Hook ...@@ -565,6 +570,7 @@ X-Gitlab-Event: Note Hook
}, },
"project_id": 5, "project_id": 5,
"project":{ "project":{
"id": 5,
"name":"Gitlab Test", "name":"Gitlab Test",
"description":"Aut reprehenderit ut est.", "description":"Aut reprehenderit ut est.",
"web_url":"http://example.com/gitlab-org/gitlab-test", "web_url":"http://example.com/gitlab-org/gitlab-test",
...@@ -643,6 +649,7 @@ X-Gitlab-Event: Note Hook ...@@ -643,6 +649,7 @@ X-Gitlab-Event: Note Hook
}, },
"project_id": 5, "project_id": 5,
"project":{ "project":{
"id": 5,
"name":"Gitlab Test", "name":"Gitlab Test",
"description":"Aut reprehenderit ut est.", "description":"Aut reprehenderit ut est.",
"web_url":"http://example.com/gitlab-org/gitlab-test", "web_url":"http://example.com/gitlab-org/gitlab-test",
...@@ -717,6 +724,7 @@ X-Gitlab-Event: Merge Request Hook ...@@ -717,6 +724,7 @@ X-Gitlab-Event: Merge Request Hook
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon" "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
}, },
"project": { "project": {
"id": 1,
"name":"Gitlab Test", "name":"Gitlab Test",
"description":"Aut reprehenderit ut est.", "description":"Aut reprehenderit ut est.",
"web_url":"http://example.com/gitlabhq/gitlab-test", "web_url":"http://example.com/gitlabhq/gitlab-test",
...@@ -873,6 +881,7 @@ X-Gitlab-Event: Wiki Page Hook ...@@ -873,6 +881,7 @@ X-Gitlab-Event: Wiki Page Hook
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon" "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon"
}, },
"project": { "project": {
"id": 1,
"name": "awesome-project", "name": "awesome-project",
"description": "This is awesome", "description": "This is awesome",
"web_url": "http://example.com/root/awesome-project", "web_url": "http://example.com/root/awesome-project",
...@@ -944,6 +953,7 @@ X-Gitlab-Event: Pipeline Hook ...@@ -944,6 +953,7 @@ X-Gitlab-Event: Pipeline Hook
"avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon" "avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
}, },
"project":{ "project":{
"id": 1,
"name": "Gitlab Test", "name": "Gitlab Test",
"description": "Atque in sunt eos similique dolores voluptatem.", "description": "Atque in sunt eos similique dolores voluptatem.",
"web_url": "http://192.168.64.1:3005/gitlab-org/gitlab-test", "web_url": "http://192.168.64.1:3005/gitlab-org/gitlab-test",
......
...@@ -9,7 +9,7 @@ You can see GitLab's keyboard shortcuts by using 'shift + ?' ...@@ -9,7 +9,7 @@ You can see GitLab's keyboard shortcuts by using 'shift + ?'
| <kbd>n</kbd> | Main navigation | | <kbd>n</kbd> | Main navigation |
| <kbd>s</kbd> | Focus search | | <kbd>s</kbd> | Focus search |
| <kbd>f</kbd> | Focus filter | | <kbd>f</kbd> | Focus filter |
| <kbd>p b</kbd> | Show/hide the Performance Bar | | <kbd>p</kbd> + <kbd>b</kbd> | Show/hide the Performance Bar |
| <kbd>?</kbd> | Show/hide this dialog | | <kbd>?</kbd> | Show/hide this dialog |
| <kbd></kbd> + <kbd>shift</kbd> + <kbd>p</kbd> | Toggle markdown preview | | <kbd></kbd> + <kbd>shift</kbd> + <kbd>p</kbd> | Toggle markdown preview |
| <kbd></kbd> | Edit last comment (when focused on an empty textarea) | | <kbd></kbd> | Edit last comment (when focused on an empty textarea) |
......
class AdditionalEmailHeadersInterceptor class AdditionalEmailHeadersInterceptor
def self.delivering_email(message) def self.delivering_email(message)
message.headers( message.header['Auto-Submitted'] ||= 'auto-generated'
'Auto-Submitted' => 'auto-generated', message.header['X-Auto-Response-Suppress'] ||= 'All'
'X-Auto-Response-Suppress' => 'All'
)
end end
end end
...@@ -317,7 +317,7 @@ module API ...@@ -317,7 +317,7 @@ module API
unauthorized! unless merge_request.can_cancel_merge_when_pipeline_succeeds?(current_user) unauthorized! unless merge_request.can_cancel_merge_when_pipeline_succeeds?(current_user)
::MergeRequest::MergeWhenPipelineSucceedsService ::MergeRequests::MergeWhenPipelineSucceedsService
.new(merge_request.target_project, current_user) .new(merge_request.target_project, current_user)
.cancel(merge_request) .cancel(merge_request)
end end
......
...@@ -373,6 +373,26 @@ module API ...@@ -373,6 +373,26 @@ module API
desc: 'The Slack token' desc: 'The Slack token'
} }
], ],
'packagist' => [
{
required: true,
name: :username,
type: String,
desc: 'The username'
},
{
required: true,
name: :token,
type: String,
desc: 'The Packagist API token'
},
{
required: false,
name: :server,
type: String,
desc: 'The server'
}
],
'pipelines-email' => [ 'pipelines-email' => [
{ {
required: true, required: true,
...@@ -597,6 +617,7 @@ module API ...@@ -597,6 +617,7 @@ module API
KubernetesService, KubernetesService,
MattermostSlashCommandsService, MattermostSlashCommandsService,
SlackSlashCommandsService, SlackSlashCommandsService,
PackagistService,
PipelinesEmailService, PipelinesEmailService,
PivotaltrackerService, PivotaltrackerService,
PrometheusService, PrometheusService,
......
...@@ -394,6 +394,26 @@ module API ...@@ -394,6 +394,26 @@ module API
desc: 'The Slack token' desc: 'The Slack token'
} }
], ],
'packagist' => [
{
required: true,
name: :username,
type: String,
desc: 'The username'
},
{
required: true,
name: :token,
type: String,
desc: 'The Packagist API token'
},
{
required: false,
name: :server,
type: String,
desc: 'The server'
}
],
'pipelines-email' => [ 'pipelines-email' => [
{ {
required: true, required: true,
......
...@@ -94,7 +94,9 @@ module Gitlab ...@@ -94,7 +94,9 @@ module Gitlab
end end
def diff_file(repository) def diff_file(repository)
@diff_file ||= begin return @diff_file if defined?(@diff_file)
@diff_file = begin
if RequestStore.active? if RequestStore.active?
key = { key = {
project_id: repository.project.id, project_id: repository.project.id,
...@@ -122,8 +124,8 @@ module Gitlab ...@@ -122,8 +124,8 @@ module Gitlab
def find_diff_file(repository) def find_diff_file(repository)
return unless diff_refs.complete? return unless diff_refs.complete?
return unless comparison = diff_refs.compare_in(repository.project)
diff_refs.compare_in(repository.project).diffs(paths: paths, expanded: true).diff_files.first comparison.diffs(paths: paths, expanded: true).diff_files.first
end end
def get_formatter_class(type) def get_formatter_class(type)
......
...@@ -35,10 +35,14 @@ module Gitlab ...@@ -35,10 +35,14 @@ module Gitlab
end end
def delete_page(page_path, commit_details) def delete_page(page_path, commit_details)
assert_type!(commit_details, CommitDetails) @repository.gitaly_migrate(:wiki_delete_page) do |is_enabled|
if is_enabled
gollum_wiki.delete_page(gollum_page_by_path(page_path), commit_details.to_h) gitaly_delete_page(page_path, commit_details)
nil gollum_wiki.clear_cache
else
gollum_delete_page(page_path, commit_details)
end
end
end end
def update_page(page_path, title, format, content, commit_details) def update_page(page_path, title, format, content, commit_details)
...@@ -54,14 +58,13 @@ module Gitlab ...@@ -54,14 +58,13 @@ module Gitlab
end end
def page(title:, version: nil, dir: nil) def page(title:, version: nil, dir: nil)
if version @repository.gitaly_migrate(:wiki_find_page) do |is_enabled|
version = Gitlab::Git::Commit.find(@repository, version).id if is_enabled
gitaly_find_page(title: title, version: version, dir: dir)
else
gollum_find_page(title: title, version: version, dir: dir)
end
end end
gollum_page = gollum_wiki.page(title, version, dir)
return unless gollum_page
new_page(gollum_page)
end end
def file(name, version) def file(name, version)
...@@ -135,9 +138,38 @@ module Gitlab ...@@ -135,9 +138,38 @@ module Gitlab
raise Gitlab::Git::Wiki::DuplicatePageError, e.message raise Gitlab::Git::Wiki::DuplicatePageError, e.message
end end
def gollum_delete_page(page_path, commit_details)
assert_type!(commit_details, CommitDetails)
gollum_wiki.delete_page(gollum_page_by_path(page_path), commit_details.to_h)
nil
end
def gollum_find_page(title:, version: nil, dir: nil)
if version
version = Gitlab::Git::Commit.find(@repository, version).id
end
gollum_page = gollum_wiki.page(title, version, dir)
return unless gollum_page
new_page(gollum_page)
end
def gitaly_write_page(name, format, content, commit_details) def gitaly_write_page(name, format, content, commit_details)
gitaly_wiki_client.write_page(name, format, content, commit_details) gitaly_wiki_client.write_page(name, format, content, commit_details)
end end
def gitaly_delete_page(page_path, commit_details)
gitaly_wiki_client.delete_page(page_path, commit_details)
end
def gitaly_find_page(title:, version: nil, dir: nil)
wiki_page, version = gitaly_wiki_client.find_page(title: title, version: version, dir: dir)
return unless wiki_page
Gitlab::Git::WikiPage.new(wiki_page, version)
end
end end
end end
end end
module Gitlab
module GitalyClient
class WikiPage
FIELDS = %i(title format url_path path name historical raw_data).freeze
attr_accessor(*FIELDS)
def initialize(params)
params = params.with_indifferent_access
FIELDS.each do |field|
instance_variable_set("@#{field}", params[field])
end
end
def historical?
@historical
end
def format
@format.to_sym
end
end
end
end
...@@ -15,11 +15,7 @@ module Gitlab ...@@ -15,11 +15,7 @@ module Gitlab
repository: @gitaly_repo, repository: @gitaly_repo,
name: GitalyClient.encode(name), name: GitalyClient.encode(name),
format: format.to_s, format: format.to_s,
commit_details: Gitaly::WikiCommitDetails.new( commit_details: gitaly_commit_details(commit_details)
name: GitalyClient.encode(commit_details.name),
email: GitalyClient.encode(commit_details.email),
message: GitalyClient.encode(commit_details.message)
)
) )
strio = StringIO.new(content) strio = StringIO.new(content)
...@@ -40,6 +36,59 @@ module Gitlab ...@@ -40,6 +36,59 @@ module Gitlab
raise Gitlab::Git::Wiki::DuplicatePageError, error raise Gitlab::Git::Wiki::DuplicatePageError, error
end end
end end
def delete_page(page_path, commit_details)
request = Gitaly::WikiDeletePageRequest.new(
repository: @gitaly_repo,
page_path: GitalyClient.encode(page_path),
commit_details: gitaly_commit_details(commit_details)
)
GitalyClient.call(@repository.storage, :wiki_service, :wiki_delete_page, request)
end
def find_page(title:, version: nil, dir: nil)
request = Gitaly::WikiFindPageRequest.new(
repository: @gitaly_repo,
title: GitalyClient.encode(title),
revision: GitalyClient.encode(version),
directory: GitalyClient.encode(dir)
)
response = GitalyClient.call(@repository.storage, :wiki_service, :wiki_find_page, request)
wiki_page = version = nil
response.each do |message|
page = message.page
next unless page
if wiki_page
wiki_page.raw_data << page.raw_data
else
wiki_page = GitalyClient::WikiPage.new(page.to_h)
# All gRPC strings in a response are frozen, so we get
# an unfrozen version here so appending in the else clause below doesn't blow up.
wiki_page.raw_data = wiki_page.raw_data.dup
version = Gitlab::Git::WikiPageVersion.new(
Gitlab::Git::Commit.decorate(@repository, page.version.commit),
page.version.format
)
end
end
[wiki_page, version]
end
private
def gitaly_commit_details(commit_details)
Gitaly::WikiCommitDetails.new(
name: GitalyClient.encode(commit_details.name),
email: GitalyClient.encode(commit_details.email),
message: GitalyClient.encode(commit_details.message)
)
end
end end
end end
end end
...@@ -19,6 +19,7 @@ project_tree: ...@@ -19,6 +19,7 @@ project_tree:
- milestone: - milestone:
- events: - events:
- :push_event_payload - :push_event_payload
- :issue_assignees
- snippets: - snippets:
- :award_emoji - :award_emoji
- notes: - notes:
......
...@@ -4,7 +4,7 @@ module Gitlab ...@@ -4,7 +4,7 @@ module Gitlab
module LDAP module LDAP
class AuthHash < Gitlab::OAuth::AuthHash class AuthHash < Gitlab::OAuth::AuthHash
def uid def uid
Gitlab::LDAP::Person.normalize_dn(super) @uid ||= Gitlab::LDAP::Person.normalize_dn(super)
end end
private private
......
...@@ -11,10 +11,11 @@ module Gitlab ...@@ -11,10 +11,11 @@ module Gitlab
class << self class << self
def find_by_uid_and_provider(uid, provider) def find_by_uid_and_provider(uid, provider)
# LDAP distinguished name is case-insensitive uid = Gitlab::LDAP::Person.normalize_dn(uid)
identity = ::Identity identity = ::Identity
.where(provider: provider) .where(provider: provider)
.iwhere(extern_uid: uid).last .where(extern_uid: uid).last
identity && identity.user identity && identity.user
end end
end end
......
...@@ -4,6 +4,7 @@ module Gitlab ...@@ -4,6 +4,7 @@ module Gitlab
module Middleware module Middleware
class Go class Go
include ActionView::Helpers::TagHelper include ActionView::Helpers::TagHelper
include Gitlab::CurrentSettings
PROJECT_PATH_REGEX = %r{\A(#{Gitlab::PathRegex.full_namespace_route_regex}/#{Gitlab::PathRegex.project_route_regex})/}.freeze PROJECT_PATH_REGEX = %r{\A(#{Gitlab::PathRegex.full_namespace_route_regex}/#{Gitlab::PathRegex.project_route_regex})/}.freeze
...@@ -37,10 +38,20 @@ module Gitlab ...@@ -37,10 +38,20 @@ module Gitlab
end end
def go_body(path) def go_body(path)
project_url = URI.join(Gitlab.config.gitlab.url, path) config = Gitlab.config
project_url = URI.join(config.gitlab.url, path)
import_prefix = strip_url(project_url.to_s) import_prefix = strip_url(project_url.to_s)
meta_tag = tag :meta, name: 'go-import', content: "#{import_prefix} git #{project_url}.git" repository_url = case current_application_settings.enabled_git_access_protocol
when 'ssh'
shell = config.gitlab_shell
port = ":#{shell.ssh_port}" unless shell.ssh_port == 22
"ssh://#{shell.ssh_user}@#{shell.ssh_host}#{port}/#{path}.git"
when 'http', nil
"#{project_url}.git"
end
meta_tag = tag :meta, name: 'go-import', content: "#{import_prefix} git #{repository_url}"
head_tag = content_tag :head, meta_tag head_tag = content_tag :head, meta_tag
content_tag :html, head_tag content_tag :html, head_tag
end end
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
"webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js" "webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js"
}, },
"dependencies": { "dependencies": {
"autosize": "^4.0.0",
"axios": "^0.16.2", "axios": "^0.16.2",
"babel-core": "^6.22.1", "babel-core": "^6.22.1",
"babel-eslint": "^7.2.1", "babel-eslint": "^7.2.1",
...@@ -36,6 +37,7 @@ ...@@ -36,6 +37,7 @@
"eslint-plugin-html": "^2.0.1", "eslint-plugin-html": "^2.0.1",
"exports-loader": "^0.6.4", "exports-loader": "^0.6.4",
"file-loader": "^0.11.1", "file-loader": "^0.11.1",
"fuzzaldrin-plus": "^0.5.0",
"imports-loader": "^0.7.1", "imports-loader": "^0.7.1",
"jed": "^1.1.1", "jed": "^1.1.1",
"jquery": "^2.2.1", "jquery": "^2.2.1",
......
...@@ -8,6 +8,7 @@ module QA ...@@ -8,6 +8,7 @@ module QA
autoload :Release, 'qa/runtime/release' autoload :Release, 'qa/runtime/release'
autoload :User, 'qa/runtime/user' autoload :User, 'qa/runtime/user'
autoload :Namespace, 'qa/runtime/namespace' autoload :Namespace, 'qa/runtime/namespace'
autoload :Scenario, 'qa/runtime/scenario'
end end
## ##
...@@ -80,6 +81,11 @@ module QA ...@@ -80,6 +81,11 @@ module QA
module Admin module Admin
autoload :Menu, 'qa/page/admin/menu' autoload :Menu, 'qa/page/admin/menu'
end end
module Mattermost
autoload :Main, 'qa/page/mattermost/main'
autoload :Login, 'qa/page/mattermost/login'
end
end end
## ##
......
module QA
module Page
module Mattermost
class Login < Page::Base
def initialize
visit(Runtime::Scenario.mattermost + '/login')
end
def sign_in_using_oauth
click_link class: 'btn btn-custom-login gitlab'
if page.has_content?('Authorize GitLab Mattermost to use your account?')
click_button 'Authorize'
end
end
end
end
end
end
module QA
module Page
module Mattermost
class Main < Page::Base
def initialize
visit(Runtime::Scenario.mattermost)
end
end
end
end
end
module QA
module Runtime
module Scenario
extend self
attr_accessor :mattermost
end
end
end
...@@ -8,6 +8,11 @@ module QA ...@@ -8,6 +8,11 @@ module QA
# #
class Mattermost < Scenario::Entrypoint class Mattermost < Scenario::Entrypoint
tags :core, :mattermost tags :core, :mattermost
def perform(address, mattermost, *files)
Runtime::Scenario.mattermost = mattermost
super(address, files)
end
end end
end end
end end
......
module QA
feature 'logging in to Mattermost', :mattermost do
scenario 'can use gitlab oauth' do
Page::Main::Entry.act { sign_in_using_credentials }
Page::Mattermost::Login.act { sign_in_using_oauth }
Page::Mattermost::Main.perform do |page|
expect(page).to have_content(/(Welcome to: Mattermost|Logout GitLab Mattermost)/)
end
end
end
end
describe QA::Scenario::Entrypoint do
subject do
Class.new(QA::Scenario::Entrypoint) do
tags :rspec
end
end
context '#perform' do
let(:config) { spy('Specs::Config') }
let(:release) { spy('Runtime::Release') }
let(:runner) { spy('Specs::Runner') }
before do
allow(config).to receive(:perform) { |&block| block.call config }
allow(runner).to receive(:perform) { |&block| block.call runner }
stub_const('QA::Specs::Config', config)
stub_const('QA::Runtime::Release', release)
stub_const('QA::Specs::Runner', runner)
end
it 'should set address' do
subject.perform("hello")
expect(config).to have_received(:address=).with("hello")
end
context 'no paths' do
it 'should call runner with default arguments' do
subject.perform("test")
expect(runner).to have_received(:rspec)
.with(hash_including(files: 'qa/specs/features'))
end
end
context 'specifying paths' do
it 'should call runner with paths' do
subject.perform('test', 'path1', 'path2')
expect(runner).to have_received(:rspec)
.with(hash_including(files: %w(path1 path2)))
end
end
end
end
...@@ -119,7 +119,7 @@ FactoryGirl.define do ...@@ -119,7 +119,7 @@ FactoryGirl.define do
finished_at nil finished_at nil
end end
factory :ci_build_tag do trait :tag do
tag true tag true
end end
......
...@@ -118,6 +118,29 @@ feature 'Admin updates settings' do ...@@ -118,6 +118,29 @@ feature 'Admin updates settings' do
expect(find_field('ED25519 SSH keys').value).to eq(forbidden) expect(find_field('ED25519 SSH keys').value).to eq(forbidden)
end end
scenario 'Change Performance Bar settings' do
group = create(:group)
check 'Enable the Performance Bar'
fill_in 'Allowed group', with: group.path
click_on 'Save'
expect(page).to have_content 'Application settings saved successfully'
expect(find_field('Enable the Performance Bar')).to be_checked
expect(find_field('Allowed group').value).to eq group.path
uncheck 'Enable the Performance Bar'
click_on 'Save'
expect(page).to have_content 'Application settings saved successfully'
expect(find_field('Enable the Performance Bar')).not_to be_checked
expect(find_field('Allowed group').value).to be_nil
end
def check_all_events def check_all_events
page.check('Active') page.check('Active')
page.check('Push') page.check('Push')
......
...@@ -19,9 +19,9 @@ feature 'Group milestones', :js do ...@@ -19,9 +19,9 @@ feature 'Group milestones', :js do
end end
it 'renders description preview' do it 'renders description preview' do
form = find('.gfm-form') description = find('.note-textarea')
form.fill_in(:milestone_description, with: '') description.native.send_keys('')
click_link('Preview') click_link('Preview')
...@@ -31,7 +31,7 @@ feature 'Group milestones', :js do ...@@ -31,7 +31,7 @@ feature 'Group milestones', :js do
click_link('Write') click_link('Write')
form.fill_in(:milestone_description, with: ':+1: Nice') description.native.send_keys(':+1: Nice')
click_link('Preview') click_link('Preview')
...@@ -51,6 +51,13 @@ feature 'Group milestones', :js do ...@@ -51,6 +51,13 @@ feature 'Group milestones', :js do
expect(find('.start_date')).to have_content(Date.today.at_beginning_of_month.strftime('%b %-d, %Y')) expect(find('.start_date')).to have_content(Date.today.at_beginning_of_month.strftime('%b %-d, %Y'))
end end
it 'description input does not support autocomplete' do
description = find('.note-textarea')
description.native.send_keys('!')
expect(page).not_to have_selector('.atwho-view')
end
end end
context 'milestones list' do context 'milestones list' do
......
require 'spec_helper'
describe 'User activates Packagist' do
let(:project) { create(:project) }
let(:user) { create(:user) }
before do
project.add_master(user)
sign_in(user)
visit(project_settings_integrations_path(project))
click_link('Packagist')
end
it 'activates service' do
check('Active')
fill_in('Username', with: 'theUser')
fill_in('Token', with: 'verySecret')
click_button('Save')
expect(page).to have_content('Packagist activated.')
end
end
...@@ -21,5 +21,6 @@ describe 'User views services' do ...@@ -21,5 +21,6 @@ describe 'User views services' do
expect(page).to have_content('JetBrains TeamCity') expect(page).to have_content('JetBrains TeamCity')
expect(page).to have_content('Asana') expect(page).to have_content('Asana')
expect(page).to have_content('Irker (IRC gateway)') expect(page).to have_content('Irker (IRC gateway)')
expect(page).to have_content('Packagist')
end end
end end
require 'spec_helper'
feature 'Multi-file editor upload file', :js do
include WaitForRequests
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:txt_file) { File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt') }
let(:img_file) { File.join(Rails.root, 'spec', 'fixtures', 'dk.png') }
before do
project.add_master(user)
sign_in(user)
page.driver.set_cookie('new_repo', 'true')
visit project_tree_path(project, :master)
wait_for_requests
end
it 'uploads text file' do
find('.add-to-tree').click
# make the field visible so capybara can use it
execute_script('document.querySelector("#file-upload").classList.remove("hidden")')
attach_file('file-upload', txt_file)
find('.add-to-tree').click
expect(page).to have_selector('.repo-tab', text: 'doc_sample.txt')
expect(page).to have_content(File.open(txt_file, &:readline))
end
it 'uploads image file' do
find('.add-to-tree').click
# make the field visible so capybara can use it
execute_script('document.querySelector("#file-upload").classList.remove("hidden")')
attach_file('file-upload', img_file)
find('.add-to-tree').click
expect(page).to have_selector('.repo-tab', text: 'dk.png')
expect(page).not_to have_selector('.monaco-editor')
expect(page).to have_content('The source could not be displayed for this temporary file.')
end
end
import autosize from 'vendor/autosize'; import Autosize from 'autosize';
import GLForm from '~/gl_form'; import GLForm from '~/gl_form';
import '~/lib/utils/text_utility'; import '~/lib/utils/text_utility';
import '~/lib/utils/common_utils'; import '~/lib/utils/common_utils';
window.autosize = autosize; window.autosize = Autosize;
describe('GLForm', () => { describe('GLForm', () => {
describe('when instantiated', function () { describe('when instantiated', function () {
......
/* global Notes */ /* global Notes */
import 'vendor/autosize'; import 'autosize';
import '~/gl_form'; import '~/gl_form';
import '~/lib/utils/text_utility'; import '~/lib/utils/text_utility';
import '~/render_gfm'; import '~/render_gfm';
......
import Vue from 'vue'; import Vue from 'vue';
import autosize from 'vendor/autosize'; import Autosize from 'autosize';
import store from '~/notes/stores'; import store from '~/notes/stores';
import issueCommentForm from '~/notes/components/issue_comment_form.vue'; import issueCommentForm from '~/notes/components/issue_comment_form.vue';
import { loggedOutIssueData, notesDataMock, userDataMock, issueDataMock } from '../mock_data'; import { loggedOutIssueData, notesDataMock, userDataMock, issueDataMock } from '../mock_data';
...@@ -97,14 +97,14 @@ describe('issue_comment_form component', () => { ...@@ -97,14 +97,14 @@ describe('issue_comment_form component', () => {
}); });
it('should resize textarea after note discarded', (done) => { it('should resize textarea after note discarded', (done) => {
spyOn(autosize, 'update'); spyOn(Autosize, 'update');
spyOn(vm, 'discard').and.callThrough(); spyOn(vm, 'discard').and.callThrough();
vm.note = 'foo'; vm.note = 'foo';
vm.discard(); vm.discard();
Vue.nextTick(() => { Vue.nextTick(() => {
expect(autosize.update).toHaveBeenCalled(); expect(Autosize.update).toHaveBeenCalled();
done(); done();
}); });
}); });
......
/* eslint-disable space-before-function-paren, no-unused-expressions, no-var, object-shorthand, comma-dangle, max-len */ /* eslint-disable space-before-function-paren, no-unused-expressions, no-var, object-shorthand, comma-dangle, max-len */
/* global Notes */ /* global Notes */
import 'vendor/autosize'; import 'autosize';
import '~/gl_form'; import '~/gl_form';
import '~/lib/utils/text_utility'; import '~/lib/utils/text_utility';
import '~/render_gfm'; import '~/render_gfm';
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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