summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile6
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/behaviors/index.js1
-rw-r--r--app/assets/javascripts/boards/components/board_list.js2
-rw-r--r--app/assets/javascripts/dispatcher.js24
-rw-r--r--app/assets/javascripts/main.js1
-rw-r--r--app/assets/javascripts/pages/dashboard/issues/index.js7
-rw-r--r--app/assets/javascripts/pages/dashboard/milestones/index/index.js3
-rw-r--r--app/assets/javascripts/pages/dashboard/milestones/show/index.js7
-rw-r--r--app/assets/javascripts/pages/dashboard/projects/index.js3
-rw-r--r--app/assets/javascripts/pages/dashboard/todos/index/index.js3
-rw-r--r--app/assets/javascripts/pages/dashboard/todos/index/todos.js (renamed from app/assets/javascripts/todos.js)6
-rw-r--r--app/assets/javascripts/pipelines/pipelines_charts.js12
-rw-r--r--app/assets/javascripts/preview_markdown.js364
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss4
-rw-r--r--app/assets/stylesheets/framework/filters.scss2
-rw-r--r--app/assets/stylesheets/framework/layout.scss4
-rw-r--r--app/assets/stylesheets/framework/variables.scss3
-rw-r--r--app/assets/stylesheets/pages/diff.scss4
-rw-r--r--app/assets/stylesheets/pages/issuable.scss1
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss10
-rw-r--r--app/assets/stylesheets/pages/projects.scss7
-rw-r--r--app/helpers/application_settings_helper.rb1
-rw-r--r--app/helpers/diff_helper.rb8
-rw-r--r--app/models/application_setting.rb1
-rw-r--r--app/models/repository.rb4
-rw-r--r--app/views/admin/application_settings/_form.html.haml16
-rw-r--r--app/views/dashboard/projects/_nav.html.haml2
-rw-r--r--app/views/layouts/devise.html.haml2
-rw-r--r--app/views/layouts/devise_empty.html.haml2
-rw-r--r--app/views/projects/diffs/_stats.html.haml2
-rw-r--r--app/views/projects/pipelines/charts/_pipelines.haml4
-rw-r--r--changelogs/unreleased/36906-reordering-issues-to-the-bottom.yml5
-rw-r--r--changelogs/unreleased/41744-substitute-ui-charcoal-with-ui-indigo.yml5
-rw-r--r--changelogs/unreleased/changes-dropdown-ellipsis.yml5
-rw-r--r--changelogs/unreleased/fix-dashboard-projects-nav-links-height.yml5
-rw-r--r--changelogs/unreleased/fj-41477-fix-bug-wiki-last-version.yml5
-rw-r--r--changelogs/unreleased/fj-41681-add-param-disable-commit-stats-api.yml5
-rw-r--r--changelogs/unreleased/jej-backport-authorized-keys-to-ce.yml5
-rw-r--r--changelogs/unreleased/sh-fix-bare-import-hooks.yml5
-rw-r--r--changelogs/unreleased/sh-store-user-in-api-logs.yml5
-rw-r--r--config/initializers/gollum.rb20
-rw-r--r--config/webpack.config.js30
-rw-r--r--db/migrate/20160301174731_add_fingerprint_index.rb17
-rw-r--r--db/migrate/20170531180233_add_authorized_keys_enabled_to_application_settings.rb19
-rw-r--r--db/post_migrate/20171128214150_schedule_populate_merge_request_metrics_with_events_data.rb32
-rw-r--r--db/schema.rb1
-rw-r--r--doc/administration/operations/fast_ssh_key_lookup.md170
-rw-r--r--doc/administration/operations/img/write_to_authorized_keys_setting.pngbin0 -> 94218 bytes
-rw-r--r--doc/administration/operations/index.md3
-rw-r--r--doc/administration/operations/speed_up_ssh.md1
-rw-r--r--doc/api/commits.md1
-rw-r--r--doc/api/repositories.md2
-rw-r--r--doc/ci/examples/code_climate.md3
-rw-r--r--doc/ci/ssh_keys/README.md2
-rw-r--r--doc/development/architecture.md2
-rw-r--r--doc/development/changelog.md2
-rw-r--r--doc/development/fe_guide/style_guide_scss.md2
-rw-r--r--doc/user/project/clusters/index.md3
-rw-r--r--doc/user/project/integrations/irker.md6
-rw-r--r--doc/user/project/integrations/webhooks.md6
-rw-r--r--lib/api/api.rb3
-rw-r--r--lib/api/commits.rb3
-rw-r--r--lib/api/entities.rb2
-rw-r--r--lib/api/helpers.rb7
-rw-r--r--lib/api/internal.rb12
-rw-r--r--lib/api/v3/commits.rb3
-rw-r--r--lib/gitlab/bare_repository_import/importer.rb1
-rw-r--r--lib/gitlab/git/gitlab_projects.rb30
-rw-r--r--lib/gitlab/gitaly_client/remote_service.rb8
-rw-r--r--lib/gitlab/gitaly_client/repository_service.rb7
-rw-r--r--lib/gitlab/grape_logging/loggers/user_logger.rb18
-rw-r--r--lib/gitlab/insecure_key_fingerprint.rb23
-rw-r--r--lib/gitlab/regex.rb2
-rw-r--r--lib/gitlab/shell.rb94
-rw-r--r--package.json2
-rw-r--r--spec/helpers/diff_helper_spec.rb10
-rw-r--r--spec/initializers/gollum_spec.rb62
-rw-r--r--spec/javascripts/boards/board_list_spec.js12
-rw-r--r--spec/javascripts/merge_request_spec.js15
-rw-r--r--spec/javascripts/todos_spec.js2
-rw-r--r--spec/javascripts/vue_shared/components/table_pagination_spec.js2
-rw-r--r--spec/lib/gitlab/bare_repository_import/importer_spec.rb8
-rw-r--r--spec/lib/gitlab/git/gitlab_projects_spec.rb45
-rw-r--r--spec/lib/gitlab/insecure_key_fingerprint_spec.rb18
-rw-r--r--spec/lib/gitlab/regex_spec.rb3
-rw-r--r--spec/lib/gitlab/shell_spec.rb378
-rw-r--r--spec/models/repository_spec.rb22
-rw-r--r--spec/requests/api/commits_spec.rb25
-rw-r--r--spec/requests/api/helpers_spec.rb6
-rw-r--r--spec/requests/api/internal_spec.rb48
-rw-r--r--spec/requests/api/v3/commits_spec.rb27
-rw-r--r--spec/workers/gitlab_shell_worker_spec.rb12
95 files changed, 1398 insertions, 403 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index e40e4fc339c..328185caaeb 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.66.0
+0.67.0
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index e030a0157c9..c68d476cc8e 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-5.10.3
+5.11.0
diff --git a/Gemfile b/Gemfile
index 23d0b3d9b3e..6f9fb91848d 100644
--- a/Gemfile
+++ b/Gemfile
@@ -70,6 +70,10 @@ gem 'net-ldap'
# Git Wiki
# Required manually in config/initializers/gollum.rb to control load order
gem 'gollum-lib', '~> 4.2', require: false
+
+# Before updating this gem, check if
+# https://github.com/gollum/rugged_adapter/pull/28 has been merged.
+# If it has, then remove the monkey patch for tree_entry in config/initializers/gollum.rb
gem 'gollum-rugged_adapter', '~> 0.4.4', require: false
# Language detection
@@ -402,7 +406,7 @@ group :ed25519 do
end
# Gitaly GRPC client
-gem 'gitaly-proto', '~> 0.64.0', require: 'gitaly'
+gem 'gitaly-proto', '~> 0.69.0', require: 'gitaly'
gem 'toml-rb', '~> 0.3.15', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index d10da1bd1c3..40c4f73b8a6 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -284,7 +284,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
- gitaly-proto (0.64.0)
+ gitaly-proto (0.69.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6)
@@ -1053,7 +1053,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
- gitaly-proto (~> 0.64.0)
+ gitaly-proto (~> 0.69.0)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.6.2)
diff --git a/app/assets/javascripts/behaviors/index.js b/app/assets/javascripts/behaviors/index.js
index 34e905222b4..8d021de7998 100644
--- a/app/assets/javascripts/behaviors/index.js
+++ b/app/assets/javascripts/behaviors/index.js
@@ -7,6 +7,7 @@ import installGlEmojiElement from './gl_emoji';
import './quick_submit';
import './requires_input';
import './toggler_behavior';
+import '../preview_markdown';
installGlEmojiElement();
initCopyAsGFM();
diff --git a/app/assets/javascripts/boards/components/board_list.js b/app/assets/javascripts/boards/components/board_list.js
index 84b76a6f1b1..d8cf532fe78 100644
--- a/app/assets/javascripts/boards/components/board_list.js
+++ b/app/assets/javascripts/boards/components/board_list.js
@@ -187,7 +187,7 @@ export default {
<li
class="board-list-count text-center"
v-if="showCount"
- data-id="-1">
+ data-issue-id="-1">
<loading-icon
v-show="list.loadingMore"
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 42f61d33f6e..81d16cdbbe7 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -56,7 +56,6 @@ import GfmAutoComplete from './gfm_auto_complete';
import ShortcutsBlob from './shortcuts_blob';
import SigninTabsMemoizer from './signin_tabs_memoizer';
import Star from './star';
-import Todos from './todos';
import TreeView from './tree';
import UsagePing from './usage_ping';
import UsernameValidator from './username_validator';
@@ -111,6 +110,7 @@ import Activities from './activities';
}
const fail = () => Flash('Error loading dynamic module');
+ const callDefault = m => m.default();
path = page.split(':');
shortcut_handler = null;
@@ -190,15 +190,25 @@ import Activities from './activities';
initIssuableSidebar();
break;
case 'dashboard:milestones:index':
- projectSelect();
+ import('./pages/dashboard/milestones/index')
+ .then(callDefault)
+ .catch(fail);
break;
case 'projects:milestones:show':
case 'groups:milestones:show':
- case 'dashboard:milestones:show':
new Milestone();
new Sidebar();
break;
+ case 'dashboard:milestones:show':
+ import('./pages/dashboard/milestones/show')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'dashboard:issues':
+ import('./pages/dashboard/issues')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'dashboard:merge_requests':
projectSelect();
initLegacyFilters();
@@ -212,10 +222,14 @@ import Activities from './activities';
projectSelect();
break;
case 'dashboard:todos:index':
- new Todos();
+ import('./pages/dashboard/todos/index').then(callDefault).catch(fail);
break;
case 'dashboard:projects:index':
case 'dashboard:projects:starred':
+ import('./pages/dashboard/projects')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'explore:projects:index':
case 'explore:projects:trending':
case 'explore:projects:starred':
@@ -542,7 +556,7 @@ import Activities from './activities';
new CILintEditor();
break;
case 'users:show':
- import('./pages/users/show').then(m => m.default()).catch(fail);
+ import('./pages/users/show').then(callDefault).catch(fail);
break;
case 'admin:conversational_development_index:show':
new UserCallout();
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 59bfa482bb0..ce6f91439b4 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -46,7 +46,6 @@ import LazyLoader from './lazy_loader';
import './line_highlighter';
import initLogoAnimation from './logo';
import './milestone_select';
-import './preview_markdown';
import './projects_dropdown';
import './render_gfm';
import initBreadcrumbs from './breadcrumb';
diff --git a/app/assets/javascripts/pages/dashboard/issues/index.js b/app/assets/javascripts/pages/dashboard/issues/index.js
new file mode 100644
index 00000000000..b7353669e65
--- /dev/null
+++ b/app/assets/javascripts/pages/dashboard/issues/index.js
@@ -0,0 +1,7 @@
+import projectSelect from '~/project_select';
+import initLegacyFilters from '~/init_legacy_filters';
+
+export default () => {
+ projectSelect();
+ initLegacyFilters();
+};
diff --git a/app/assets/javascripts/pages/dashboard/milestones/index/index.js b/app/assets/javascripts/pages/dashboard/milestones/index/index.js
new file mode 100644
index 00000000000..0f2f1bd4a25
--- /dev/null
+++ b/app/assets/javascripts/pages/dashboard/milestones/index/index.js
@@ -0,0 +1,3 @@
+import projectSelect from '~/project_select';
+
+export default projectSelect;
diff --git a/app/assets/javascripts/pages/dashboard/milestones/show/index.js b/app/assets/javascripts/pages/dashboard/milestones/show/index.js
new file mode 100644
index 00000000000..2e7a08a369c
--- /dev/null
+++ b/app/assets/javascripts/pages/dashboard/milestones/show/index.js
@@ -0,0 +1,7 @@
+import Milestone from '~/milestone';
+import Sidebar from '~/right_sidebar';
+
+export default () => {
+ new Milestone(); // eslint-disable-line no-new
+ new Sidebar(); // eslint-disable-line no-new
+};
diff --git a/app/assets/javascripts/pages/dashboard/projects/index.js b/app/assets/javascripts/pages/dashboard/projects/index.js
new file mode 100644
index 00000000000..c88cbf1a6ba
--- /dev/null
+++ b/app/assets/javascripts/pages/dashboard/projects/index.js
@@ -0,0 +1,3 @@
+import ProjectsList from '~/projects_list';
+
+export default () => new ProjectsList();
diff --git a/app/assets/javascripts/pages/dashboard/todos/index/index.js b/app/assets/javascripts/pages/dashboard/todos/index/index.js
new file mode 100644
index 00000000000..77c23685943
--- /dev/null
+++ b/app/assets/javascripts/pages/dashboard/todos/index/index.js
@@ -0,0 +1,3 @@
+import Todos from './todos';
+
+export default () => new Todos();
diff --git a/app/assets/javascripts/todos.js b/app/assets/javascripts/pages/dashboard/todos/index/todos.js
index 748caecf153..e976a3d2f1d 100644
--- a/app/assets/javascripts/todos.js
+++ b/app/assets/javascripts/pages/dashboard/todos/index/todos.js
@@ -1,7 +1,7 @@
/* eslint-disable class-methods-use-this, no-unneeded-ternary, quote-props */
-import { visitUrl } from './lib/utils/url_utility';
-import UsersSelect from './users_select';
-import { isMetaClick } from './lib/utils/common_utils';
+import { visitUrl } from '~/lib/utils/url_utility';
+import UsersSelect from '~/users_select';
+import { isMetaClick } from '~/lib/utils/common_utils';
export default class Todos {
constructor() {
diff --git a/app/assets/javascripts/pipelines/pipelines_charts.js b/app/assets/javascripts/pipelines/pipelines_charts.js
index 001faf4be33..821aa7e229f 100644
--- a/app/assets/javascripts/pipelines/pipelines_charts.js
+++ b/app/assets/javascripts/pipelines/pipelines_charts.js
@@ -6,16 +6,16 @@ document.addEventListener('DOMContentLoaded', () => {
const data = {
labels: chartScope.labels,
datasets: [{
- fillColor: '#7f8fa4',
- strokeColor: '#7f8fa4',
- pointColor: '#7f8fa4',
+ fillColor: '#707070',
+ strokeColor: '#707070',
+ pointColor: '#707070',
pointStrokeColor: '#EEE',
data: chartScope.totalValues,
},
{
- fillColor: '#44aa22',
- strokeColor: '#44aa22',
- pointColor: '#44aa22',
+ fillColor: '#1aaa55',
+ strokeColor: '#1aaa55',
+ pointColor: '#1aaa55',
pointStrokeColor: '#fff',
data: chartScope.successValues,
},
diff --git a/app/assets/javascripts/preview_markdown.js b/app/assets/javascripts/preview_markdown.js
index ffaafb3ee9e..86c7b56198d 100644
--- a/app/assets/javascripts/preview_markdown.js
+++ b/app/assets/javascripts/preview_markdown.js
@@ -6,195 +6,193 @@
// (including the explanation of quick actions), and showing a warning when
// more than `x` users are referenced.
//
-(function () {
- var lastTextareaPreviewed;
- var lastTextareaHeight = null;
- var markdownPreview;
- var previewButtonSelector;
- var writeButtonSelector;
-
- window.MarkdownPreview = (function () {
- function MarkdownPreview() {}
-
- // Minimum number of users referenced before triggering a warning
- MarkdownPreview.prototype.referenceThreshold = 10;
- MarkdownPreview.prototype.emptyMessage = 'Nothing to preview.';
-
- MarkdownPreview.prototype.ajaxCache = {};
-
- MarkdownPreview.prototype.showPreview = function ($form) {
- var mdText;
- var preview = $form.find('.js-md-preview');
- var url = preview.data('url');
- if (preview.hasClass('md-preview-loading')) {
- return;
- }
- mdText = $form.find('textarea.markdown-area').val();
-
- if (mdText.trim().length === 0) {
- preview.text(this.emptyMessage);
- this.hideReferencedUsers($form);
- } else {
- preview.addClass('md-preview-loading').text('Loading...');
- this.fetchMarkdownPreview(mdText, url, (function (response) {
- var body;
- if (response.body.length > 0) {
- body = response.body;
- } else {
- body = this.emptyMessage;
- }
-
- preview.removeClass('md-preview-loading').html(body);
- preview.renderGFM();
- this.renderReferencedUsers(response.references.users, $form);
-
- if (response.references.commands) {
- this.renderReferencedCommands(response.references.commands, $form);
- }
- }).bind(this));
- }
- };
- MarkdownPreview.prototype.fetchMarkdownPreview = function (text, url, success) {
- if (!url) {
- return;
- }
- if (text === this.ajaxCache.text) {
- success(this.ajaxCache.response);
- return;
- }
- $.ajax({
- type: 'POST',
- url: url,
- data: {
- text: text
- },
- dataType: 'json',
- success: (function (response) {
- this.ajaxCache = {
- text: text,
- response: response
- };
- success(response);
- }).bind(this)
- });
- };
-
- MarkdownPreview.prototype.hideReferencedUsers = function ($form) {
- $form.find('.referenced-users').hide();
- };
-
- MarkdownPreview.prototype.renderReferencedUsers = function (users, $form) {
- var referencedUsers;
- referencedUsers = $form.find('.referenced-users');
- if (referencedUsers.length) {
- if (users.length >= this.referenceThreshold) {
- referencedUsers.show();
- referencedUsers.find('.js-referenced-users-count').text(users.length);
- } else {
- referencedUsers.hide();
- }
- }
- };
-
- MarkdownPreview.prototype.hideReferencedCommands = function ($form) {
- $form.find('.referenced-commands').hide();
- };
-
- MarkdownPreview.prototype.renderReferencedCommands = function (commands, $form) {
- var referencedCommands;
- referencedCommands = $form.find('.referenced-commands');
- if (commands.length > 0) {
- referencedCommands.html(commands);
- referencedCommands.show();
+var lastTextareaPreviewed;
+var lastTextareaHeight = null;
+var markdownPreview;
+var previewButtonSelector;
+var writeButtonSelector;
+
+function MarkdownPreview() {}
+
+// Minimum number of users referenced before triggering a warning
+MarkdownPreview.prototype.referenceThreshold = 10;
+MarkdownPreview.prototype.emptyMessage = 'Nothing to preview.';
+
+MarkdownPreview.prototype.ajaxCache = {};
+
+MarkdownPreview.prototype.showPreview = function ($form) {
+ var mdText;
+ var preview = $form.find('.js-md-preview');
+ var url = preview.data('url');
+ if (preview.hasClass('md-preview-loading')) {
+ return;
+ }
+ mdText = $form.find('textarea.markdown-area').val();
+
+ if (mdText.trim().length === 0) {
+ preview.text(this.emptyMessage);
+ this.hideReferencedUsers($form);
+ } else {
+ preview.addClass('md-preview-loading').text('Loading...');
+ this.fetchMarkdownPreview(mdText, url, (function (response) {
+ var body;
+ if (response.body.length > 0) {
+ body = response.body;
} else {
- referencedCommands.html('');
- referencedCommands.hide();
+ body = this.emptyMessage;
}
- };
-
- return MarkdownPreview;
- }());
-
- markdownPreview = new window.MarkdownPreview();
- previewButtonSelector = '.js-md-preview-button';
- writeButtonSelector = '.js-md-write-button';
- lastTextareaPreviewed = null;
- const markdownToolbar = $('.md-header-toolbar');
-
- $.fn.setupMarkdownPreview = function () {
- var $form = $(this);
- $form.find('textarea.markdown-area').on('input', function () {
- markdownPreview.hideReferencedUsers($form);
- });
- };
-
- $(document).on('markdown-preview:show', function (e, $form) {
- if (!$form) {
- return;
- }
-
- lastTextareaPreviewed = $form.find('textarea.markdown-area');
- lastTextareaHeight = lastTextareaPreviewed.height();
-
- // toggle tabs
- $form.find(writeButtonSelector).parent().removeClass('active');
- $form.find(previewButtonSelector).parent().addClass('active');
- // toggle content
- $form.find('.md-write-holder').hide();
- $form.find('.md-preview-holder').show();
- markdownToolbar.removeClass('active');
- markdownPreview.showPreview($form);
- });
-
- $(document).on('markdown-preview:hide', function (e, $form) {
- if (!$form) {
- return;
- }
- lastTextareaPreviewed = null;
-
- if (lastTextareaHeight) {
- $form.find('textarea.markdown-area').height(lastTextareaHeight);
- }
-
- // toggle tabs
- $form.find(writeButtonSelector).parent().addClass('active');
- $form.find(previewButtonSelector).parent().removeClass('active');
-
- // toggle content
- $form.find('.md-write-holder').show();
- $form.find('textarea.markdown-area').focus();
- $form.find('.md-preview-holder').hide();
- markdownToolbar.addClass('active');
+ preview.removeClass('md-preview-loading').html(body);
+ preview.renderGFM();
+ this.renderReferencedUsers(response.references.users, $form);
- markdownPreview.hideReferencedCommands($form);
+ if (response.references.commands) {
+ this.renderReferencedCommands(response.references.commands, $form);
+ }
+ }).bind(this));
+ }
+};
+
+MarkdownPreview.prototype.fetchMarkdownPreview = function (text, url, success) {
+ if (!url) {
+ return;
+ }
+ if (text === this.ajaxCache.text) {
+ success(this.ajaxCache.response);
+ return;
+ }
+ $.ajax({
+ type: 'POST',
+ url: url,
+ data: {
+ text: text
+ },
+ dataType: 'json',
+ success: (function (response) {
+ this.ajaxCache = {
+ text: text,
+ response: response
+ };
+ success(response);
+ }).bind(this)
});
-
- $(document).on('markdown-preview:toggle', function (e, keyboardEvent) {
- var $target;
- $target = $(keyboardEvent.target);
- if ($target.is('textarea.markdown-area')) {
- $(document).triggerHandler('markdown-preview:show', [$target.closest('form')]);
- keyboardEvent.preventDefault();
- } else if (lastTextareaPreviewed) {
- $target = lastTextareaPreviewed;
- $(document).triggerHandler('markdown-preview:hide', [$target.closest('form')]);
- keyboardEvent.preventDefault();
+};
+
+MarkdownPreview.prototype.hideReferencedUsers = function ($form) {
+ $form.find('.referenced-users').hide();
+};
+
+MarkdownPreview.prototype.renderReferencedUsers = function (users, $form) {
+ var referencedUsers;
+ referencedUsers = $form.find('.referenced-users');
+ if (referencedUsers.length) {
+ if (users.length >= this.referenceThreshold) {
+ referencedUsers.show();
+ referencedUsers.find('.js-referenced-users-count').text(users.length);
+ } else {
+ referencedUsers.hide();
}
+ }
+};
+
+MarkdownPreview.prototype.hideReferencedCommands = function ($form) {
+ $form.find('.referenced-commands').hide();
+};
+
+MarkdownPreview.prototype.renderReferencedCommands = function (commands, $form) {
+ var referencedCommands;
+ referencedCommands = $form.find('.referenced-commands');
+ if (commands.length > 0) {
+ referencedCommands.html(commands);
+ referencedCommands.show();
+ } else {
+ referencedCommands.html('');
+ referencedCommands.hide();
+ }
+};
+
+markdownPreview = new MarkdownPreview();
+
+previewButtonSelector = '.js-md-preview-button';
+writeButtonSelector = '.js-md-write-button';
+lastTextareaPreviewed = null;
+const markdownToolbar = $('.md-header-toolbar');
+
+$.fn.setupMarkdownPreview = function () {
+ var $form = $(this);
+ $form.find('textarea.markdown-area').on('input', function () {
+ markdownPreview.hideReferencedUsers($form);
});
+};
+
+$(document).on('markdown-preview:show', function (e, $form) {
+ if (!$form) {
+ return;
+ }
+
+ lastTextareaPreviewed = $form.find('textarea.markdown-area');
+ lastTextareaHeight = lastTextareaPreviewed.height();
+
+ // toggle tabs
+ $form.find(writeButtonSelector).parent().removeClass('active');
+ $form.find(previewButtonSelector).parent().addClass('active');
+
+ // toggle content
+ $form.find('.md-write-holder').hide();
+ $form.find('.md-preview-holder').show();
+ markdownToolbar.removeClass('active');
+ markdownPreview.showPreview($form);
+});
+
+$(document).on('markdown-preview:hide', function (e, $form) {
+ if (!$form) {
+ return;
+ }
+ lastTextareaPreviewed = null;
- $(document).on('click', previewButtonSelector, function (e) {
- var $form;
- e.preventDefault();
- $form = $(this).closest('form');
- $(document).triggerHandler('markdown-preview:show', [$form]);
- });
-
- $(document).on('click', writeButtonSelector, function (e) {
- var $form;
- e.preventDefault();
- $form = $(this).closest('form');
- $(document).triggerHandler('markdown-preview:hide', [$form]);
- });
-}());
+ if (lastTextareaHeight) {
+ $form.find('textarea.markdown-area').height(lastTextareaHeight);
+ }
+
+ // toggle tabs
+ $form.find(writeButtonSelector).parent().addClass('active');
+ $form.find(previewButtonSelector).parent().removeClass('active');
+
+ // toggle content
+ $form.find('.md-write-holder').show();
+ $form.find('textarea.markdown-area').focus();
+ $form.find('.md-preview-holder').hide();
+ markdownToolbar.addClass('active');
+
+ markdownPreview.hideReferencedCommands($form);
+});
+
+$(document).on('markdown-preview:toggle', function (e, keyboardEvent) {
+ var $target;
+ $target = $(keyboardEvent.target);
+ if ($target.is('textarea.markdown-area')) {
+ $(document).triggerHandler('markdown-preview:show', [$target.closest('form')]);
+ keyboardEvent.preventDefault();
+ } else if (lastTextareaPreviewed) {
+ $target = lastTextareaPreviewed;
+ $(document).triggerHandler('markdown-preview:hide', [$target.closest('form')]);
+ keyboardEvent.preventDefault();
+ }
+});
+
+$(document).on('click', previewButtonSelector, function (e) {
+ var $form;
+ e.preventDefault();
+ $form = $(this).closest('form');
+ $(document).triggerHandler('markdown-preview:show', [$form]);
+});
+
+$(document).on('click', writeButtonSelector, function (e) {
+ var $form;
+ e.preventDefault();
+ $form = $(this).closest('form');
+ $(document).triggerHandler('markdown-preview:hide', [$form]);
+});
+
+export default MarkdownPreview;
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index bc907a390d8..d1b3754d4ef 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -28,7 +28,9 @@
.dropdown-menu,
.dropdown-menu-nav {
@include set-visible;
- min-height: 40px;
+ min-height: $dropdown-min-height;
+ max-height: $dropdown-max-height;
+ overflow: auto;
@media (max-width: $screen-xs-max) {
width: 100%;
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index 2d7465401f1..621a4adc0cb 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -260,7 +260,7 @@
}
.filtered-search-input-dropdown-menu {
- max-height: 260px;
+ max-height: $dropdown-max-height;
max-width: 280px;
overflow: auto;
diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss
index 3f0268541a4..fab3270b9f5 100644
--- a/app/assets/stylesheets/framework/layout.scss
+++ b/app/assets/stylesheets/framework/layout.scss
@@ -106,10 +106,6 @@ body {
}
}
-.layout-page > .content-wrapper {
- min-height: calc(100vh - #{$header-height});
-}
-
.with-performance-bar .layout-page {
margin-top: $header-height + $performance-bar-height;
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index f7853909f56..ef1520f1f63 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -334,7 +334,8 @@ $regular_font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-San
* Dropdowns
*/
$dropdown-width: 300px;
-$dropdown-max-height: 215px;
+$dropdown-min-height: 40px;
+$dropdown-max-height: 312px;
$dropdown-vertical-offset: 4px;
$dropdown-link-color: #555;
$dropdown-link-hover-bg: $row-hover;
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 60b07537799..1d081b58f62 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -651,15 +651,13 @@
min-width: 0;
}
- .diff-changed-file-name,
- .diff-changed-file-path {
+ .diff-changed-file-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.diff-changed-file-path {
- direction: rtl;
color: $gl-text-color-tertiary;
}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index e1637618ab2..ae9a8b0182c 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -303,7 +303,6 @@
.gutter-toggle {
margin-top: 7px;
border-left: 1px solid $border-gray-normal;
- padding-left: 0;
text-align: center;
}
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 05c1033c5f7..a35ebd48887 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -48,7 +48,7 @@
}
.dropdown-menu {
- max-height: 250px;
+ max-height: $dropdown-max-height;
overflow-y: auto;
}
@@ -993,3 +993,11 @@ button.mini-pipeline-graph-dropdown-toggle {
font-weight: $gl-font-weight-normal;
line-height: 1.5;
}
+
+.legend-all {
+ color: $gl-text-color-secondary;
+}
+
+.legend-success {
+ color: $green-500;
+}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 6f4c678c4b8..61a76d0387a 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -322,13 +322,6 @@
}
}
-.project-repo-buttons {
- .project-action-button .dropdown-menu {
- max-height: 250px;
- overflow-y: auto;
- }
-}
-
.split-one {
display: inline-table;
margin-right: 12px;
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index b12ea760668..45f7d29eb05 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -146,6 +146,7 @@ module ApplicationSettingsHelper
:after_sign_up_text,
:akismet_api_key,
:akismet_enabled,
+ :authorized_keys_enabled,
:auto_devops_enabled,
:circuitbreaker_access_retries,
:circuitbreaker_check_interval,
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index 1ce487e6592..0f5fc2823a3 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -226,4 +226,12 @@ module DiffHelper
diffs.overflow?
end
+
+ def diff_file_path_text(diff_file, max: 60)
+ path = diff_file.new_path
+
+ return path unless path.size > max && max > 3
+
+ "...#{path[-(max - 3)..-1]}"
+ end
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 253e213af81..8ab338d873d 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -261,6 +261,7 @@ class ApplicationSetting < ActiveRecord::Base
{
after_sign_up_text: nil,
akismet_enabled: false,
+ authorized_keys_enabled: true, # TODO default to false if the instance is configured to use AuthorizedKeysCommand
container_registry_token_expire_delay: 5,
default_artifacts_expire_in: '30 days',
default_branch_protection: Settings.gitlab['default_branch_protection'],
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 9c879e2006b..b36e756c07c 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -103,6 +103,10 @@ class Repository
"#<#{self.class.name}:#{@disk_path}>"
end
+ def create_hooks
+ Gitlab::Git::Repository.create_hooks(path_to_repo, Gitlab.config.gitlab_shell.hooks_path)
+ end
+
def commit(ref = 'HEAD')
return nil unless exists?
return ref if ref.is_a?(::Commit)
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 3e2dbb07a6c..ba4ca88a8a9 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -775,6 +775,22 @@
= link_to icon('question-circle'), help_page_path('administration/polling')
%fieldset
+ %legend Performance optimization
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :authorized_keys_enabled do
+ = f.check_box :authorized_keys_enabled
+ Write to "authorized_keys" file
+ .help-block
+ By default, we write to the "authorized_keys" file to support Git
+ over SSH without additional configuration. GitLab can be optimized
+ to authenticate SSH keys via the database file. Only uncheck this
+ if you have configured your OpenSSH server to use the
+ AuthorizedKeysCommand. Click on the help icon for more details.
+ = link_to icon('question-circle'), help_page_path('administration/operations/fast_ssh_key_lookup')
+
+ %fieldset
%legend User and IP Rate Limits
.form-group
.col-sm-offset-2.col-sm-10
diff --git a/app/views/dashboard/projects/_nav.html.haml b/app/views/dashboard/projects/_nav.html.haml
index 3701e1c0578..c18077bc66f 100644
--- a/app/views/dashboard/projects/_nav.html.haml
+++ b/app/views/dashboard/projects/_nav.html.haml
@@ -1,4 +1,4 @@
-.top-area
+.nav-block
%ul.nav-links
= nav_link(html_options: { class: ("active" unless params[:personal].present?) }) do
= link_to s_('DashboardProjects|All'), dashboard_projects_path
diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml
index 691d2528022..4e9ea33e675 100644
--- a/app/views/layouts/devise.html.haml
+++ b/app/views/layouts/devise.html.haml
@@ -1,7 +1,7 @@
!!! 5
%html.devise-layout-html
= render "layouts/head"
- %body.ui_charcoal.login-page.application.navless{ data: { page: body_data_page } }
+ %body.ui_indigo.login-page.application.navless{ data: { page: body_data_page } }
.page-wrap
= render "layouts/header/empty"
.login-page-broadcast
diff --git a/app/views/layouts/devise_empty.html.haml b/app/views/layouts/devise_empty.html.haml
index ed6731bde95..8718bb3db1a 100644
--- a/app/views/layouts/devise_empty.html.haml
+++ b/app/views/layouts/devise_empty.html.haml
@@ -1,7 +1,7 @@
!!! 5
%html{ lang: "en" }
= render "layouts/head"
- %body.ui_charcoal.login-page.application.navless
+ %body.ui_indigo.login-page.application.navless
= render "layouts/header/empty"
= render "layouts/broadcast"
.container.navless-container
diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml
index dd473ebe580..325159dd9a7 100644
--- a/app/views/projects/diffs/_stats.html.haml
+++ b/app/views/projects/diffs/_stats.html.haml
@@ -25,7 +25,7 @@
= sprite_icon(diff_file_changed_icon(diff_file), size: 16, css_class: "#{diff_file_changed_icon_color(diff_file)} diff-file-changed-icon append-right-8")
%span.diff-changed-file-content.append-right-8
%strong.diff-changed-file-name= diff_file.blob.name
- %span.diff-changed-file-path.prepend-top-5= diff_file.new_path
+ %span.diff-changed-file-path.prepend-top-5= diff_file_path_text(diff_file)
%span.diff-changed-stats
%span.cgreen<
+#{diff_file.added_lines}
diff --git a/app/views/projects/pipelines/charts/_pipelines.haml b/app/views/projects/pipelines/charts/_pipelines.haml
index 7a100843f5e..41dc2f6cf9d 100644
--- a/app/views/projects/pipelines/charts/_pipelines.haml
+++ b/app/views/projects/pipelines/charts/_pipelines.haml
@@ -4,11 +4,11 @@
%h4= _("Pipelines charts")
%p
&nbsp;
- %span.cgreen
+ %span.legend-success
= icon("circle")
= s_("Pipeline|success")
&nbsp;
- %span.cgray
+ %span.legend-all
= icon("circle")
= s_("Pipeline|all")
diff --git a/changelogs/unreleased/36906-reordering-issues-to-the-bottom.yml b/changelogs/unreleased/36906-reordering-issues-to-the-bottom.yml
new file mode 100644
index 00000000000..0ab765a43b7
--- /dev/null
+++ b/changelogs/unreleased/36906-reordering-issues-to-the-bottom.yml
@@ -0,0 +1,5 @@
+---
+title: "Issue board: fix for dragging an issue to the very bottom in long lists"
+merge_request: 16250
+author: David Kuri
+type: fixed \ No newline at end of file
diff --git a/changelogs/unreleased/41744-substitute-ui-charcoal-with-ui-indigo.yml b/changelogs/unreleased/41744-substitute-ui-charcoal-with-ui-indigo.yml
new file mode 100644
index 00000000000..593d3741a09
--- /dev/null
+++ b/changelogs/unreleased/41744-substitute-ui-charcoal-with-ui-indigo.yml
@@ -0,0 +1,5 @@
+---
+title: Substitute deprecated ui_charcoal with new default ui_indigo
+merge_request: 16271
+author: Takuya Noguchi
+type: fixed
diff --git a/changelogs/unreleased/changes-dropdown-ellipsis.yml b/changelogs/unreleased/changes-dropdown-ellipsis.yml
new file mode 100644
index 00000000000..7e3f378cc33
--- /dev/null
+++ b/changelogs/unreleased/changes-dropdown-ellipsis.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed chanages dropdown ellipsis positioning
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-dashboard-projects-nav-links-height.yml b/changelogs/unreleased/fix-dashboard-projects-nav-links-height.yml
new file mode 100644
index 00000000000..2f6a07bb234
--- /dev/null
+++ b/changelogs/unreleased/fix-dashboard-projects-nav-links-height.yml
@@ -0,0 +1,5 @@
+---
+title: Fix dashboard projects nav links height
+merge_request: 16204
+author: George Tsiolis
+type: fixed
diff --git a/changelogs/unreleased/fj-41477-fix-bug-wiki-last-version.yml b/changelogs/unreleased/fj-41477-fix-bug-wiki-last-version.yml
new file mode 100644
index 00000000000..e4b1343876a
--- /dev/null
+++ b/changelogs/unreleased/fj-41477-fix-bug-wiki-last-version.yml
@@ -0,0 +1,5 @@
+---
+title: Fixing bug when wiki last version
+merge_request: 16197
+author:
+type: fixed
diff --git a/changelogs/unreleased/fj-41681-add-param-disable-commit-stats-api.yml b/changelogs/unreleased/fj-41681-add-param-disable-commit-stats-api.yml
new file mode 100644
index 00000000000..dca4dec224c
--- /dev/null
+++ b/changelogs/unreleased/fj-41681-add-param-disable-commit-stats-api.yml
@@ -0,0 +1,5 @@
+---
+title: Added option to disable commits stats in the commit endpoint
+merge_request: 16309
+author:
+type: added
diff --git a/changelogs/unreleased/jej-backport-authorized-keys-to-ce.yml b/changelogs/unreleased/jej-backport-authorized-keys-to-ce.yml
new file mode 100644
index 00000000000..4386c631f59
--- /dev/null
+++ b/changelogs/unreleased/jej-backport-authorized-keys-to-ce.yml
@@ -0,0 +1,5 @@
+---
+title: Backport fast database lookup of SSH authorized_keys from EE
+merge_request: 16014
+author:
+type: added
diff --git a/changelogs/unreleased/sh-fix-bare-import-hooks.yml b/changelogs/unreleased/sh-fix-bare-import-hooks.yml
new file mode 100644
index 00000000000..deb6c62f738
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-bare-import-hooks.yml
@@ -0,0 +1,5 @@
+---
+title: Fix hooks not being set up properly for bare import Rake task
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-store-user-in-api-logs.yml b/changelogs/unreleased/sh-store-user-in-api-logs.yml
new file mode 100644
index 00000000000..d904dcaf6d3
--- /dev/null
+++ b/changelogs/unreleased/sh-store-user-in-api-logs.yml
@@ -0,0 +1,5 @@
+---
+title: Save user ID and username in Grape API log (api_json.log)
+merge_request:
+author:
+type: changed
diff --git a/config/initializers/gollum.rb b/config/initializers/gollum.rb
index f1066f83dd9..0b86cac51a7 100644
--- a/config/initializers/gollum.rb
+++ b/config/initializers/gollum.rb
@@ -36,6 +36,26 @@ module Gollum
end
end
end
+
+ module Git
+ class Git
+ def tree_entry(commit, path)
+ pathname = Pathname.new(path)
+ tmp_entry = nil
+
+ pathname.each_filename do |dir|
+ tmp_entry = if tmp_entry.nil?
+ commit.tree[dir]
+ else
+ @repo.lookup(tmp_entry[:oid])[dir]
+ end
+
+ return nil unless tmp_entry
+ end
+ tmp_entry
+ end
+ end
+ end
end
Rails.application.configure do
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 5f95255334c..95fa79990e2 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -1,5 +1,6 @@
'use strict';
+var crypto = require('crypto');
var fs = require('fs');
var path = require('path');
var webpack = require('webpack');
@@ -179,15 +180,34 @@ var config = {
if (chunk.name) {
return chunk.name;
}
- return chunk.mapModules((m) => {
+
+ const moduleNames = [];
+
+ function collectModuleNames(m) {
+ // handle ConcatenatedModule which does not have resource nor context set
+ if (m.modules) {
+ m.modules.forEach(collectModuleNames);
+ return;
+ }
+
const pagesBase = path.join(ROOT_PATH, 'app/assets/javascripts/pages');
+
if (m.resource.indexOf(pagesBase) === 0) {
- return path.relative(pagesBase, m.resource)
+ moduleNames.push(path.relative(pagesBase, m.resource)
.replace(/\/index\.[a-z]+$/, '')
- .replace(/\//g, '__');
+ .replace(/\//g, '__'));
+ } else {
+ moduleNames.push(path.relative(m.context, m.resource));
}
- return path.relative(m.context, m.resource);
- }).join('_');
+ }
+
+ chunk.forEachModule(collectModuleNames);
+
+ const hash = crypto.createHash('sha256')
+ .update(moduleNames.join('_'))
+ .digest('hex');
+
+ return `${moduleNames[0]}-${hash.substr(0, 6)}`;
}),
// create cacheable common library bundle for all vue chunks
diff --git a/db/migrate/20160301174731_add_fingerprint_index.rb b/db/migrate/20160301174731_add_fingerprint_index.rb
new file mode 100644
index 00000000000..f2c3d1ba1ea
--- /dev/null
+++ b/db/migrate/20160301174731_add_fingerprint_index.rb
@@ -0,0 +1,17 @@
+# rubocop:disable all
+class AddFingerprintIndex < ActiveRecord::Migration
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+
+ # https://gitlab.com/gitlab-org/gitlab-ee/issues/764
+ def change
+ args = [:keys, :fingerprint]
+
+ if Gitlab::Database.postgresql?
+ args << { algorithm: :concurrently }
+ end
+
+ add_index(*args) unless index_exists?(:keys, :fingerprint)
+ end
+end
diff --git a/db/migrate/20170531180233_add_authorized_keys_enabled_to_application_settings.rb b/db/migrate/20170531180233_add_authorized_keys_enabled_to_application_settings.rb
new file mode 100644
index 00000000000..1d86a531eb3
--- /dev/null
+++ b/db/migrate/20170531180233_add_authorized_keys_enabled_to_application_settings.rb
@@ -0,0 +1,19 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddAuthorizedKeysEnabledToApplicationSettings < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default :application_settings, :authorized_keys_enabled, :boolean, default: true, allow_null: false
+ end
+
+ def down
+ remove_column :application_settings, :authorized_keys_enabled
+ end
+end
diff --git a/db/post_migrate/20171128214150_schedule_populate_merge_request_metrics_with_events_data.rb b/db/post_migrate/20171128214150_schedule_populate_merge_request_metrics_with_events_data.rb
index 547cc68e10e..fce1829c982 100644
--- a/db/post_migrate/20171128214150_schedule_populate_merge_request_metrics_with_events_data.rb
+++ b/db/post_migrate/20171128214150_schedule_populate_merge_request_metrics_with_events_data.rb
@@ -15,8 +15,6 @@ class SchedulePopulateMergeRequestMetricsWithEventsData < ActiveRecord::Migratio
end
def up
- merge_requests = MergeRequest.where("id IN (#{updatable_merge_requests_union_sql})").reorder(:id)
-
say 'Scheduling `PopulateMergeRequestMetricsWithEventsData` jobs'
# It will update around 4_000_000 records in batches of 10_000 merge
# requests (running between 10 minutes) and should take around 66 hours to complete.
@@ -25,7 +23,7 @@ class SchedulePopulateMergeRequestMetricsWithEventsData < ActiveRecord::Migratio
#
# More information about the updates in `PopulateMergeRequestMetricsWithEventsData` class.
#
- merge_requests.each_batch(of: BATCH_SIZE) do |relation, index|
+ MergeRequest.all.each_batch(of: BATCH_SIZE) do |relation, index|
range = relation.pluck('MIN(id)', 'MAX(id)').first
BackgroundMigrationWorker.perform_in(index * 10.minutes, MIGRATION, range)
@@ -37,32 +35,4 @@ class SchedulePopulateMergeRequestMetricsWithEventsData < ActiveRecord::Migratio
execute "update merge_request_metrics set latest_closed_by_id = null"
execute "update merge_request_metrics set merged_by_id = null"
end
-
- private
-
- # On staging:
- # Planning time: 0.682 ms
- # Execution time: 22033.158 ms
- #
- def updatable_merge_requests_union_sql
- metrics_not_exists_clause =
- 'NOT EXISTS (SELECT 1 FROM merge_request_metrics WHERE merge_request_metrics.merge_request_id = merge_requests.id)'
-
- without_metrics_data = <<-SQL.strip_heredoc
- merge_request_metrics.merged_by_id IS NULL OR
- merge_request_metrics.latest_closed_by_id IS NULL OR
- merge_request_metrics.latest_closed_at IS NULL
- SQL
-
- mrs_without_metrics_record = MergeRequest
- .where(metrics_not_exists_clause)
- .select(:id)
-
- mrs_without_events_data = MergeRequest
- .joins('INNER JOIN merge_request_metrics ON merge_requests.id = merge_request_metrics.merge_request_id')
- .where(without_metrics_data)
- .select(:id)
-
- Gitlab::SQL::Union.new([mrs_without_metrics_record, mrs_without_events_data]).to_sql
- end
end
diff --git a/db/schema.rb b/db/schema.rb
index e6a2ea4c862..a16f756ccfb 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -154,6 +154,7 @@ ActiveRecord::Schema.define(version: 20171230123729) do
t.integer "gitaly_timeout_default", default: 55, null: false
t.integer "gitaly_timeout_medium", default: 30, null: false
t.integer "gitaly_timeout_fast", default: 10, null: false
+ t.boolean "authorized_keys_enabled", default: true, null: false
end
create_table "audit_events", force: :cascade do |t|
diff --git a/doc/administration/operations/fast_ssh_key_lookup.md b/doc/administration/operations/fast_ssh_key_lookup.md
new file mode 100644
index 00000000000..835ed8c8006
--- /dev/null
+++ b/doc/administration/operations/fast_ssh_key_lookup.md
@@ -0,0 +1,170 @@
+# Fast lookup of authorized SSH keys in the database
+
+Regular SSH operations become slow as the number of users grows because OpenSSH
+searches for a key to authorize a user via a linear search. In the worst case,
+such as when the user is not authorized to access GitLab, OpenSSH will scan the
+entire file to search for a key. This can take significant time and disk I/O,
+which will delay users attempting to push or pull to a repository. Making
+matters worse, if users add or remove keys frequently, the operating system may
+not be able to cache the `authorized_keys` file, which causes the disk to be
+accessed repeatedly.
+
+GitLab Shell solves this by providing a way to authorize SSH users via a fast,
+indexed lookup in the GitLab database. This page describes how to enable the fast
+lookup of authorized SSH keys.
+
+> **Warning:** OpenSSH version 6.9+ is required because
+`AuthorizedKeysCommand` must be able to accept a fingerprint. These
+instructions will break installations using older versions of OpenSSH, such as
+those included with CentOS 6 as of September 2017. If you want to use this
+feature for CentOS 6, follow [the instructions on how to build and install a custom OpenSSH package](#compiling-a-custom-version-of-openssh-for-centos-6) before continuing.
+
+## Setting up fast lookup via GitLab Shell
+
+GitLab Shell provides a way to authorize SSH users via a fast, indexed lookup
+to the GitLab database. GitLab Shell uses the fingerprint of the SSH key to
+check whether the user is authorized to access GitLab.
+
+Add the following to your `sshd_config` file. This is usuaully located at
+`/etc/ssh/sshd_config`, but it will be `/assets/sshd_config` if you're using
+Omnibus Docker:
+
+```
+AuthorizedKeysCommand /opt/embedded/gitlab-shell/bin/gitlab-shell-authorized-keys-check git %u %k
+AuthorizedKeysCommandUser git
+```
+
+Reload OpenSSH:
+
+```bash
+# Debian or Ubuntu installations
+sudo service ssh reload
+
+# CentOS installations
+sudo service sshd reload
+```
+
+Confirm that SSH is working by removing your user's SSH key in the UI, adding a
+new one, and attempting to pull a repo.
+
+> **Warning:** Do not disable writes until SSH is confirmed to be working
+perfectly, because the file will quickly become out-of-date.
+
+In the case of lookup failures (which are not uncommon), the `authorized_keys`
+file will still be scanned. So git SSH performance will still be slow for many
+users as long as a large file exists.
+
+You can disable any more writes to the `authorized_keys` file by unchecking
+`Write to "authorized_keys" file` in the Application Settings of your GitLab
+installation.
+
+![Write to authorized keys setting](img/write_to_authorized_keys_setting.png)
+
+Again, confirm that SSH is working by removing your user's SSH key in the UI,
+adding a new one, and attempting to pull a repo.
+
+Then you can backup and delete your `authorized_keys` file for best performance.
+
+## How to go back to using the `authorized_keys` file
+
+This is a brief overview. Please refer to the above instructions for more context.
+
+1. [Rebuild the `authorized_keys` file](../raketasks/maintenance.md#rebuild-authorized_keys-file)
+1. Enable writes to the `authorized_keys` file in Application Settings
+1. Remove the `AuthorizedKeysCommand` lines from `/etc/ssh/sshd_config` or from `/assets/sshd_config` if you are using Omnibus Docker.
+1. Reload sshd: `sudo service sshd reload`
+1. Remove the `/opt/gitlab-shell/authorized_keys` file
+
+## Compiling a custom version of OpenSSH for CentOS 6
+
+Building a custom version of OpenSSH is not necessary for Ubuntu 16.04 users,
+since Ubuntu 16.04 ships with OpenSSH 7.2.
+
+It is also unnecessary for CentOS 7.4 users, as that version ships with
+OpenSSH 7.4. If you are using CentOS 7.0 - 7.3, we strongly recommend that you
+upgrade to CentOS 7.4 instead of following this procedure. This should be as
+simple as running `yum update`.
+
+CentOS 6 users must build their own OpenSSH package to enable SSH lookups via
+the database. The following instructions can be used to build OpenSSH 7.5:
+
+1. First, download the package and install the required packages:
+
+ ```
+ sudo su -
+ cd /tmp
+ curl --remote-name https://mirrors.evowise.com/pub/OpenBSD/OpenSSH/portable/openssh-7.5p1.tar.gz
+ tar xzvf openssh-7.5p1.tar.gz
+ yum install rpm-build gcc make wget openssl-devel krb5-devel pam-devel libX11-devel xmkmf libXt-devel
+ ```
+
+3. Prepare the build by copying files to the right place:
+
+ ```
+ mkdir -p /root/rpmbuild/{SOURCES,SPECS}
+ cp ./openssh-7.5p1/contrib/redhat/openssh.spec /root/rpmbuild/SPECS/
+ cp openssh-7.5p1.tar.gz /root/rpmbuild/SOURCES/
+ cd /root/rpmbuild/SPECS
+ ```
+
+3. Next, set the spec settings properly:
+
+ ```
+ sed -i -e "s/%define no_gnome_askpass 0/%define no_gnome_askpass 1/g" openssh.spec
+ sed -i -e "s/%define no_x11_askpass 0/%define no_x11_askpass 1/g" openssh.spec
+ sed -i -e "s/BuildPreReq/BuildRequires/g" openssh.spec
+ ```
+
+3. Build the RPMs:
+
+ ```
+ rpmbuild -bb openssh.spec
+ ```
+
+4. Ensure the RPMs were built:
+
+ ```
+ ls -al /root/rpmbuild/RPMS/x86_64/
+ ```
+
+ You should see something as the following:
+
+ ```
+ total 1324
+ drwxr-xr-x. 2 root root 4096 Jun 20 19:37 .
+ drwxr-xr-x. 3 root root 19 Jun 20 19:37 ..
+ -rw-r--r--. 1 root root 470828 Jun 20 19:37 openssh-7.5p1-1.x86_64.rpm
+ -rw-r--r--. 1 root root 490716 Jun 20 19:37 openssh-clients-7.5p1-1.x86_64.rpm
+ -rw-r--r--. 1 root root 17020 Jun 20 19:37 openssh-debuginfo-7.5p1-1.x86_64.rpm
+ -rw-r--r--. 1 root root 367516 Jun 20 19:37 openssh-server-7.5p1-1.x86_64.rpm
+ ```
+
+5. Install the packages. OpenSSH packages will replace `/etc/pam.d/sshd`
+ with its own version, which may prevent users from logging in, so be sure
+ that the file is backed up and restored after installation:
+
+ ```
+ timestamp=$(date +%s)
+ cp /etc/pam.d/sshd pam-ssh-conf-$timestamp
+ rpm -Uvh /root/rpmbuild/RPMS/x86_64/*.rpm
+ yes | cp pam-ssh-conf-$timestamp /etc/pam.d/sshd
+ ```
+
+6. Verify the installed version. In another window, attempt to login to the server:
+
+ ```
+ ssh -v <your-centos-machine>
+ ```
+
+ You should see a line that reads: "debug1: Remote protocol version 2.0, remote software version OpenSSH_7.5"
+
+ If not, you may need to restart sshd (e.g. `systemctl restart sshd.service`).
+
+7. *IMPORTANT!* Open a new SSH session to your server before exiting to make
+ sure everything is working! If you need to downgrade, simple install the
+ older package:
+
+ ```
+ # Only run this if you run into a problem logging in
+ yum downgrade openssh-server openssh openssh-clients
+ ```
diff --git a/doc/administration/operations/img/write_to_authorized_keys_setting.png b/doc/administration/operations/img/write_to_authorized_keys_setting.png
new file mode 100644
index 00000000000..232765f1917
--- /dev/null
+++ b/doc/administration/operations/img/write_to_authorized_keys_setting.png
Binary files differ
diff --git a/doc/administration/operations/index.md b/doc/administration/operations/index.md
index 320d71a9527..5655b7efec6 100644
--- a/doc/administration/operations/index.md
+++ b/doc/administration/operations/index.md
@@ -13,4 +13,5 @@ by GitLab to another file system or another server.
that to prioritize important jobs.
- [Sidekiq MemoryKiller](sidekiq_memory_killer.md): Configure Sidekiq MemoryKiller
to restart Sidekiq.
-- [Unicorn](unicorn.md): Understand Unicorn and unicorn-worker-killer. \ No newline at end of file
+- [Unicorn](unicorn.md): Understand Unicorn and unicorn-worker-killer.
+- [Speed up SSH operations](fast_ssh_key_lookup.md): Authorize SSH users via a fast, indexed lookup to the GitLab database.
diff --git a/doc/administration/operations/speed_up_ssh.md b/doc/administration/operations/speed_up_ssh.md
new file mode 100644
index 00000000000..89265b3018b
--- /dev/null
+++ b/doc/administration/operations/speed_up_ssh.md
@@ -0,0 +1 @@
+This document was moved to [another location](fast_ssh_key_lookup.md).
diff --git a/doc/api/commits.md b/doc/api/commits.md
index c9b72d4a1dd..63554c63057 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -159,6 +159,7 @@ Parameters:
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
| `sha` | string | yes | The commit hash or name of a repository branch or tag |
+| `stats` | boolean | no | Include commit stats. Default is true |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/repository/commits/master
diff --git a/doc/api/repositories.md b/doc/api/repositories.md
index 03b32577872..5fb25e40ed7 100644
--- a/doc/api/repositories.md
+++ b/doc/api/repositories.md
@@ -113,7 +113,7 @@ GET /projects/:id/repository/archive
Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
-- `sha` (optional) - The commit SHA to download defaults to the tip of the default branch
+- `sha` (optional) - The commit SHA to download. A tag, branch reference or sha can be used. This defaults to the tip of the default branch if not specified
## Compare branches, tags or commits
diff --git a/doc/ci/examples/code_climate.md b/doc/ci/examples/code_climate.md
index 6a5821762cc..f919ed3c797 100644
--- a/doc/ci/examples/code_climate.md
+++ b/doc/ci/examples/code_climate.md
@@ -16,7 +16,8 @@ codequality:
- docker:dind
script:
- docker pull codeclimate/codeclimate
- - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate analyze -f json > codeclimate.json || true
+ - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate:0.69.0 init
+ - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate:0.69.0 analyze -f json > codeclimate.json || true
artifacts:
paths: [codeclimate.json]
```
diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md
index df0e1521150..b8df0bfba20 100644
--- a/doc/ci/ssh_keys/README.md
+++ b/doc/ci/ssh_keys/README.md
@@ -181,7 +181,7 @@ before_script:
## Assuming you created the SSH_KNOWN_HOSTS variable, uncomment the
## following two lines.
##
- - echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts'
+ - echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
##
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
index 54029e00507..d1ba7d3dfc3 100644
--- a/doc/development/architecture.md
+++ b/doc/development/architecture.md
@@ -133,8 +133,6 @@ Usage: /etc/init.d/postgresql {start|stop|restart|reload|force-reload|status} [v
### Log locations of the services
-Note: `/home/git/` is shorthand for `/home/git`.
-
gitlabhq (includes Unicorn and Sidekiq logs)
- `/home/git/gitlab/log/` contains `application.log`, `production.log`, `sidekiq.log`, `unicorn.stdout.log`, `githost.log` and `unicorn.stderr.log` normally.
diff --git a/doc/development/changelog.md b/doc/development/changelog.md
index 48cffc0dd18..18f4177a5e5 100644
--- a/doc/development/changelog.md
+++ b/doc/development/changelog.md
@@ -127,7 +127,7 @@ type:
If you're working on the GitLab EE repository, the entry will be added to
`changelogs/unreleased-ee/` instead.
-#### Arguments
+### Arguments
| Argument | Shorthand | Purpose |
| ----------------- | --------- | ---------------------------------------------------------------------------------------------------------- |
diff --git a/doc/development/fe_guide/style_guide_scss.md b/doc/development/fe_guide/style_guide_scss.md
index 77b308c4a43..86a8b4135af 100644
--- a/doc/development/fe_guide/style_guide_scss.md
+++ b/doc/development/fe_guide/style_guide_scss.md
@@ -216,7 +216,7 @@ If you want a line or set of lines to be ignored by the linter, you can use
```scss
// This lint rule is disabled because the class name comes from a gem.
// scss-lint:disable SelectorFormat
-.ui_charcoal {
+.ui_indigo {
background-color: #333;
}
// scss-lint:enable SelectorFormat
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index d5619c7b563..5f14d232cb1 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -2,9 +2,6 @@
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/35954) in 10.1.
-CAUTION: **Warning:**
-The Cluster integration is currently in **Beta**.
-
With a cluster associated to your project, you can use Review Apps, deploy your
applications, run your pipelines, and much more, in an easy way.
diff --git a/doc/user/project/integrations/irker.md b/doc/user/project/integrations/irker.md
index c63ea1316fe..ecdd83ce8f0 100644
--- a/doc/user/project/integrations/irker.md
+++ b/doc/user/project/integrations/irker.md
@@ -47,4 +47,8 @@ Irker accepts channel names of the form `chan` and `#chan`, both for the
case, `Aorimn` is treated as a nick and no more as a channel name.
Irker can also join password-protected channels. Users need to append
-`?key=thesecretpassword` to the chan name.
+`?key=thesecretpassword` to the chan name. When using this feature remember to
+**not** put the `#` sign in front of the channel name; failing to do so will
+result on irker joining a channel literally named `#chan?key=password` henceforth
+leaking the channel key through the `/whois` IRC command (depending on IRC server
+configuration). This is due to a long standing irker bug.
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index eafdd28071d..82175c70e49 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -54,6 +54,12 @@ Below are described the supported events.
Triggered when you push to the repository except when pushing tags.
+> **Note:** When more than 20 commits are pushed at once, the `commits` web hook
+ attribute will only contain the first 20 for performance reasons. Loading
+ detailed commit data is expensive. Note that despite only 20 commits being
+ present in the `commits` attribute, the `total_commits_count` attribute will
+ contain the actual total.
+
**Request header**:
```
diff --git a/lib/api/api.rb b/lib/api/api.rb
index e0d14281c96..ae161efb358 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -13,7 +13,8 @@ module API
formatter: Gitlab::GrapeLogging::Formatters::LogrageWithTimestamp.new,
include: [
GrapeLogging::Loggers::FilterParameters.new,
- GrapeLogging::Loggers::ClientEnv.new
+ GrapeLogging::Loggers::ClientEnv.new,
+ Gitlab::GrapeLogging::Loggers::UserLogger.new
]
allow_access_with_scope :api
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 38e05074353..d8fd6a6eb06 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -82,13 +82,14 @@ module API
end
params do
requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag'
+ optional :stats, type: Boolean, default: true, desc: 'Include commit stats'
end
get ':id/repository/commits/:sha', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
commit = user_project.commit(params[:sha])
not_found! 'Commit' unless commit
- present commit, with: Entities::CommitDetail
+ present commit, with: Entities::CommitDetail, stats: params[:stats]
end
desc 'Get the diff for a specific commit of a project' do
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index bd0c54a1b04..f574858be02 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -278,7 +278,7 @@ module API
end
class CommitDetail < Commit
- expose :stats, using: Entities::CommitStats
+ expose :stats, using: Entities::CommitStats, if: :stats
expose :status
expose :last_pipeline, using: 'API::Entities::PipelineBasic'
end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index bf388163ec8..d6ce368efd5 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -5,6 +5,7 @@ module API
SUDO_HEADER = "HTTP_SUDO".freeze
SUDO_PARAM = :sudo
+ API_USER_ENV = 'gitlab.api.user'.freeze
def declared_params(options = {})
options = { include_parent_namespaces: false }.merge(options)
@@ -48,10 +49,16 @@ module API
validate_access_token!(scopes: scopes_registered_for_endpoint) unless sudo?
+ save_current_user_in_env(@current_user) if @current_user
+
@current_user
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
+ def save_current_user_in_env(user)
+ env[API_USER_ENV] = { user_id: user.id, username: user.username }
+ end
+
def sudo?
initial_current_user != current_user
end
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 79b302aae70..8bf53939751 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -82,6 +82,18 @@ module API
end
#
+ # Get a ssh key using the fingerprint
+ #
+ get "/authorized_keys" do
+ fingerprint = params.fetch(:fingerprint) do
+ Gitlab::InsecureKeyFingerprint.new(params.fetch(:key)).fingerprint
+ end
+ key = Key.find_by(fingerprint: fingerprint)
+ not_found!("Key") if key.nil?
+ present key, with: Entities::SSHKey
+ end
+
+ #
# Discover user by ssh key or user id
#
get "/discover" do
diff --git a/lib/api/v3/commits.rb b/lib/api/v3/commits.rb
index 0ef26aa696a..4f6ea8f502e 100644
--- a/lib/api/v3/commits.rb
+++ b/lib/api/v3/commits.rb
@@ -71,13 +71,14 @@ module API
end
params do
requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag'
+ optional :stats, type: Boolean, default: true, desc: 'Include commit stats'
end
get ":id/repository/commits/:sha", requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
commit = user_project.commit(params[:sha])
not_found! "Commit" unless commit
- present commit, with: ::API::Entities::CommitDetail
+ present commit, with: ::API::Entities::CommitDetail, stats: params[:stats]
end
desc 'Get the diff for a specific commit of a project' do
diff --git a/lib/gitlab/bare_repository_import/importer.rb b/lib/gitlab/bare_repository_import/importer.rb
index 709a901aa77..884a3de8f62 100644
--- a/lib/gitlab/bare_repository_import/importer.rb
+++ b/lib/gitlab/bare_repository_import/importer.rb
@@ -63,6 +63,7 @@ module Gitlab
log " * Created #{project.name} (#{project_full_path})".color(:green)
project.write_repository_config
+ project.repository.create_hooks
ProjectCacheWorker.perform_async(project.id)
else
diff --git a/lib/gitlab/git/gitlab_projects.rb b/lib/gitlab/git/gitlab_projects.rb
index cba638c06db..976fa1ddfe6 100644
--- a/lib/gitlab/git/gitlab_projects.rb
+++ b/lib/gitlab/git/gitlab_projects.rb
@@ -41,36 +41,6 @@ module Gitlab
io.read
end
- def rm_project
- logger.info "Removing repository <#{repository_absolute_path}>."
- FileUtils.rm_rf(repository_absolute_path)
- end
-
- # Move repository from one directory to another
- #
- # Example: gitlab/gitlab-ci.git -> randx/six.git
- #
- # Won't work if target namespace directory does not exist
- #
- def mv_project(new_path)
- new_absolute_path = File.join(shard_path, new_path)
-
- # verify that the source repo exists
- unless File.exist?(repository_absolute_path)
- logger.error "mv-project failed: source path <#{repository_absolute_path}> does not exist."
- return false
- end
-
- # ...and that the target repo does not exist
- if File.exist?(new_absolute_path)
- logger.error "mv-project failed: destination path <#{new_absolute_path}> already exists."
- return false
- end
-
- logger.info "Moving repository from <#{repository_absolute_path}> to <#{new_absolute_path}>."
- FileUtils.mv(repository_absolute_path, new_absolute_path)
- end
-
# Import project via git clone --bare
# URL must be publicly cloneable
def import_project(source, timeout)
diff --git a/lib/gitlab/gitaly_client/remote_service.rb b/lib/gitlab/gitaly_client/remote_service.rb
index 559a901b9a3..e58f641d69a 100644
--- a/lib/gitlab/gitaly_client/remote_service.rb
+++ b/lib/gitlab/gitaly_client/remote_service.rb
@@ -7,10 +7,12 @@ module Gitlab
@storage = repository.storage
end
- def add_remote(name, url, mirror_refmap)
+ def add_remote(name, url, mirror_refmaps)
request = Gitaly::AddRemoteRequest.new(
- repository: @gitaly_repo, name: name, url: url,
- mirror_refmap: mirror_refmap.to_s
+ repository: @gitaly_repo,
+ name: name,
+ url: url,
+ mirror_refmaps: Array.wrap(mirror_refmaps).map(&:to_s)
)
GitalyClient.call(@storage, :remote_service, :add_remote, request)
diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb
index d43d80da960..66006f5dc5b 100644
--- a/lib/gitlab/gitaly_client/repository_service.rb
+++ b/lib/gitlab/gitaly_client/repository_service.rb
@@ -43,8 +43,11 @@ module Gitlab
GitalyClient.call(@storage, :repository_service, :apply_gitattributes, request)
end
- def fetch_remote(remote, ssh_auth: nil, forced: false, no_tags: false)
- request = Gitaly::FetchRemoteRequest.new(repository: @gitaly_repo, remote: remote, force: forced, no_tags: no_tags)
+ def fetch_remote(remote, ssh_auth:, forced:, no_tags:, timeout:)
+ request = Gitaly::FetchRemoteRequest.new(
+ repository: @gitaly_repo, remote: remote, force: forced,
+ no_tags: no_tags, timeout: timeout
+ )
if ssh_auth&.ssh_import?
if ssh_auth.ssh_key_auth? && ssh_auth.ssh_private_key.present?
diff --git a/lib/gitlab/grape_logging/loggers/user_logger.rb b/lib/gitlab/grape_logging/loggers/user_logger.rb
new file mode 100644
index 00000000000..fa172861967
--- /dev/null
+++ b/lib/gitlab/grape_logging/loggers/user_logger.rb
@@ -0,0 +1,18 @@
+# This grape_logging module (https://github.com/aserafin/grape_logging) makes it
+# possible to log the user who performed the Grape API action by retrieving
+# the user context from the request environment.
+module Gitlab
+ module GrapeLogging
+ module Loggers
+ class UserLogger < ::GrapeLogging::Loggers::Base
+ def parameters(request, _)
+ params = request.env[::API::Helpers::API_USER_ENV]
+
+ return {} unless params
+
+ params.slice(:user_id, :username)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/insecure_key_fingerprint.rb b/lib/gitlab/insecure_key_fingerprint.rb
new file mode 100644
index 00000000000..f85b6e9197f
--- /dev/null
+++ b/lib/gitlab/insecure_key_fingerprint.rb
@@ -0,0 +1,23 @@
+module Gitlab
+ #
+ # Calculates the fingerprint of a given key without using
+ # openssh key validations. For this reason, only use
+ # for calculating the fingerprint to find the key with it.
+ #
+ # DO NOT use it for checking the validity of a ssh key.
+ #
+ class InsecureKeyFingerprint
+ attr_accessor :key
+
+ #
+ # Gets the base64 encoded string representing a rsa or dsa key
+ #
+ def initialize(key_base64)
+ @key = key_base64
+ end
+
+ def fingerprint
+ OpenSSL::Digest::MD5.hexdigest(Base64.decode64(@key)).scan(/../).join(':')
+ end
+ end
+end
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index 2c7b8af83f2..0002c7da8f1 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -37,7 +37,7 @@ module Gitlab
end
def environment_name_regex_chars
- 'a-zA-Z0-9_/\\$\\{\\}\\. -'
+ 'a-zA-Z0-9_/\\$\\{\\}\\. \\-'
end
def environment_name_regex
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index 564047bbd34..f4a41dc3eda 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -128,7 +128,7 @@ module Gitlab
def fetch_remote(repository, remote, ssh_auth: nil, forced: false, no_tags: false)
gitaly_migrate(:fetch_remote) do |is_enabled|
if is_enabled
- repository.gitaly_repository_client.fetch_remote(remote, ssh_auth: ssh_auth, forced: forced, no_tags: no_tags)
+ repository.gitaly_repository_client.fetch_remote(remote, ssh_auth: ssh_auth, forced: forced, no_tags: no_tags, timeout: git_timeout)
else
storage_path = Gitlab.config.repositories.storages[repository.storage]["path"]
local_fetch_remote(storage_path, repository.relative_path, remote, ssh_auth: ssh_auth, forced: forced, no_tags: no_tags)
@@ -136,7 +136,10 @@ module Gitlab
end
end
- # Move repository
+ # Move repository reroutes to mv_directory which is an alias for
+ # mv_namespace. Given the underlying implementation is a move action,
+ # indescriminate of what the folders might be.
+ #
# storage - project's storage path
# path - project disk path
# new_path - new project disk path
@@ -146,7 +149,9 @@ module Gitlab
#
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/873
def mv_repository(storage, path, new_path)
- gitlab_projects(storage, "#{path}.git").mv_project("#{new_path}.git")
+ return false if path.empty? || new_path.empty?
+
+ !!mv_directory(storage, "#{path}.git", "#{new_path}.git")
end
# Fork repository to new path
@@ -164,7 +169,9 @@ module Gitlab
.fork_repository(forked_to_storage, "#{forked_to_disk_path}.git")
end
- # Remove repository from file system
+ # Removes a repository from file system, using rm_diretory which is an alias
+ # for rm_namespace. Given the underlying implementation removes the name
+ # passed as second argument on the passed storage.
#
# storage - project's storage path
# name - project disk path
@@ -174,7 +181,12 @@ module Gitlab
#
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/873
def remove_repository(storage, name)
- gitlab_projects(storage, "#{name}.git").rm_project
+ return false if name.empty?
+
+ !!rm_directory(storage, "#{name}.git")
+ rescue ArgumentError => e
+ Rails.logger.warn("Repository does not exist: #{e} at: #{name}.git")
+ false
end
# Add new key to gitlab-shell
@@ -183,6 +195,8 @@ module Gitlab
# add_key("key-42", "sha-rsa ...")
#
def add_key(key_id, key_content)
+ return unless self.authorized_keys_enabled?
+
gitlab_shell_fast_execute([gitlab_shell_keys_path,
'add-key', key_id, self.class.strip_key(key_content)])
end
@@ -192,6 +206,8 @@ module Gitlab
# Ex.
# batch_add_keys { |adder| adder.add_key("key-42", "sha-rsa ...") }
def batch_add_keys(&block)
+ return unless self.authorized_keys_enabled?
+
IO.popen(%W(#{gitlab_shell_path}/bin/gitlab-keys batch-add-keys), 'w') do |io|
yield(KeyAdder.new(io))
end
@@ -202,10 +218,11 @@ module Gitlab
# Ex.
# remove_key("key-342", "sha-rsa ...")
#
- def remove_key(key_id, key_content)
+ def remove_key(key_id, key_content = nil)
+ return unless self.authorized_keys_enabled?
+
args = [gitlab_shell_keys_path, 'rm-key', key_id]
args << key_content if key_content
-
gitlab_shell_fast_execute(args)
end
@@ -215,9 +232,62 @@ module Gitlab
# remove_all_keys
#
def remove_all_keys
+ return unless self.authorized_keys_enabled?
+
gitlab_shell_fast_execute([gitlab_shell_keys_path, 'clear'])
end
+ # Remove ssh keys from gitlab shell that are not in the DB
+ #
+ # Ex.
+ # remove_keys_not_found_in_db
+ #
+ def remove_keys_not_found_in_db
+ return unless self.authorized_keys_enabled?
+
+ Rails.logger.info("Removing keys not found in DB")
+
+ batch_read_key_ids do |ids_in_file|
+ ids_in_file.uniq!
+ keys_in_db = Key.where(id: ids_in_file)
+
+ next unless ids_in_file.size > keys_in_db.count # optimization
+
+ ids_to_remove = ids_in_file - keys_in_db.pluck(:id)
+ ids_to_remove.each do |id|
+ Rails.logger.info("Removing key-#{id} not found in DB")
+ remove_key("key-#{id}")
+ end
+ end
+ end
+
+ # Iterate over all ssh key IDs from gitlab shell, in batches
+ #
+ # Ex.
+ # batch_read_key_ids { |batch| keys = Key.where(id: batch) }
+ #
+ def batch_read_key_ids(batch_size: 100, &block)
+ return unless self.authorized_keys_enabled?
+
+ list_key_ids do |key_id_stream|
+ key_id_stream.lazy.each_slice(batch_size) do |lines|
+ key_ids = lines.map { |l| l.chomp.to_i }
+ yield(key_ids)
+ end
+ end
+ end
+
+ # Stream all ssh key IDs from gitlab shell, separated by newlines
+ #
+ # Ex.
+ # list_key_ids
+ #
+ def list_key_ids(&block)
+ return unless self.authorized_keys_enabled?
+
+ IO.popen(%W(#{gitlab_shell_path}/bin/gitlab-keys list-key-ids), &block)
+ end
+
# Add empty directory for storing repositories
#
# Ex.
@@ -255,6 +325,7 @@ module Gitlab
rescue GRPC::InvalidArgument => e
raise ArgumentError, e.message
end
+ alias_method :rm_directory, :rm_namespace
# Move namespace directory inside repositories storage
#
@@ -274,6 +345,7 @@ module Gitlab
rescue GRPC::InvalidArgument
false
end
+ alias_method :mv_directory, :mv_namespace
def url_to_repo(path)
Gitlab.config.gitlab_shell.ssh_path_prefix + "#{path}.git"
@@ -333,6 +405,14 @@ module Gitlab
File.join(gitlab_shell_path, 'bin', 'gitlab-keys')
end
+ def authorized_keys_enabled?
+ # Return true if nil to ensure the authorized_keys methods work while
+ # fixing the authorized_keys file during migration.
+ return true if Gitlab::CurrentSettings.current_application_settings.authorized_keys_enabled.nil?
+
+ Gitlab::CurrentSettings.current_application_settings.authorized_keys_enabled
+ end
+
private
def gitlab_projects(shard_path, disk_path)
diff --git a/package.json b/package.json
index 78bc8e847c1..4759ae76817 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,6 @@
"dependencies": {
"autosize": "^4.0.0",
"axios": "^0.17.1",
- "axios-mock-adapter": "^1.10.0",
"babel-core": "^6.26.0",
"babel-eslint": "^8.0.2",
"babel-loader": "^7.1.2",
@@ -88,6 +87,7 @@
},
"devDependencies": {
"@gitlab-org/gitlab-svgs": "^1.5.0",
+ "axios-mock-adapter": "^1.10.0",
"babel-plugin-istanbul": "^4.1.5",
"eslint": "^3.18.0",
"eslint-config-airbnb-base": "^10.0.1",
diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index f9c31ac61d8..15cbe36ae76 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -266,4 +266,14 @@ describe DiffHelper do
end
end
end
+
+ context '#diff_file_path_text' do
+ it 'returns full path by default' do
+ expect(diff_file_path_text(diff_file)).to eq(diff_file.new_path)
+ end
+
+ it 'returns truncated path' do
+ expect(diff_file_path_text(diff_file, max: 10)).to eq("...open.rb")
+ end
+ end
end
diff --git a/spec/initializers/gollum_spec.rb b/spec/initializers/gollum_spec.rb
new file mode 100644
index 00000000000..adf824a8947
--- /dev/null
+++ b/spec/initializers/gollum_spec.rb
@@ -0,0 +1,62 @@
+require 'spec_helper'
+
+describe 'gollum' do
+ let(:project) { create(:project) }
+ let(:user) { project.owner }
+ let(:wiki) { ProjectWiki.new(project, user) }
+ let(:gollum_wiki) { Gollum::Wiki.new(wiki.repository.path) }
+
+ before do
+ create_page(page_name, 'content1')
+ end
+
+ after do
+ destroy_page(page_name)
+ end
+
+ context 'with simple paths' do
+ let(:page_name) { 'page1' }
+
+ it 'returns the entry hash if it matches the file name' do
+ expect(tree_entry(page_name)).not_to be_nil
+ end
+
+ it 'returns nil if the path does not fit completely' do
+ expect(tree_entry("foo/#{page_name}")).to be_nil
+ end
+ end
+
+ context 'with complex paths' do
+ let(:page_name) { '/foo/bar/page2' }
+
+ it 'returns the entry hash if it matches the file name' do
+ expect(tree_entry(page_name)).not_to be_nil
+ end
+
+ it 'returns nil if the path does not fit completely' do
+ expect(tree_entry("foo1/bar/page2")).to be_nil
+ expect(tree_entry("foo/bar1/page2")).to be_nil
+ end
+ end
+
+ def tree_entry(name)
+ gollum_wiki.repo.git.tree_entry(wiki_commits[0].commit, name + '.md')
+ end
+
+ def wiki_commits
+ gollum_wiki.repo.commits
+ end
+
+ def commit_details
+ Gitlab::Git::Wiki::CommitDetails.new(user.name, user.email, "test commit")
+ end
+
+ def create_page(name, content)
+ wiki.wiki.write_page(name, :markdown, content, commit_details)
+ end
+
+ def destroy_page(name)
+ page = wiki.find_page(name).page
+ wiki.delete_page(page, "test commit")
+ end
+end
diff --git a/spec/javascripts/boards/board_list_spec.js b/spec/javascripts/boards/board_list_spec.js
index 7c5888b6d82..b7cc3a8813e 100644
--- a/spec/javascripts/boards/board_list_spec.js
+++ b/spec/javascripts/boards/board_list_spec.js
@@ -154,6 +154,18 @@ describe('Board list component', () => {
});
});
+ it('sets data attribute with invalid id', (done) => {
+ component.showCount = true;
+
+ Vue.nextTick(() => {
+ expect(
+ component.$el.querySelector('.board-list-count').getAttribute('data-issue-id'),
+ ).toBe('-1');
+
+ done();
+ });
+ });
+
it('shows how many more issues to load', (done) => {
component.showCount = true;
component.list.issuesSize = 20;
diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js
index 2f02c11482f..9d6ea3781bc 100644
--- a/spec/javascripts/merge_request_spec.js
+++ b/spec/javascripts/merge_request_spec.js
@@ -19,17 +19,24 @@ import IssuablesHelper from '~/helpers/issuables_helper';
$('input[type=checkbox]').attr('checked', true)[0].dispatchEvent(changeEvent);
return expect($('.js-task-list-field').val()).toBe('- [x] Task List Item');
});
- return it('submits an ajax request on tasklist:changed', function() {
- spyOn(jQuery, 'ajax').and.callFake(function(req) {
+
+ it('submits an ajax request on tasklist:changed', (done) => {
+ spyOn(jQuery, 'ajax').and.callFake((req) => {
expect(req.type).toBe('PATCH');
expect(req.url).toBe(`${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/merge_requests/1.json`);
- return expect(req.data.merge_request.description).not.toBe(null);
+ expect(req.data.merge_request.description).not.toBe(null);
+ done();
});
- return $('.js-task-list-field').trigger('tasklist:changed');
+
+ $('.js-task-list-field').trigger('tasklist:changed');
});
});
describe('class constructor', () => {
+ beforeEach(() => {
+ spyOn(jQuery, 'ajax').and.stub();
+ });
+
it('calls .initCloseReopenReport', () => {
spyOn(IssuablesHelper, 'initCloseReopenReport');
diff --git a/spec/javascripts/todos_spec.js b/spec/javascripts/todos_spec.js
index 59e16f0786e..35871dddf89 100644
--- a/spec/javascripts/todos_spec.js
+++ b/spec/javascripts/todos_spec.js
@@ -1,5 +1,5 @@
import * as urlUtils from '~/lib/utils/url_utility';
-import Todos from '~/todos';
+import Todos from '~/pages/dashboard/todos/index/todos';
import '~/lib/utils/common_utils';
describe('Todos', () => {
diff --git a/spec/javascripts/vue_shared/components/table_pagination_spec.js b/spec/javascripts/vue_shared/components/table_pagination_spec.js
index 153b5c51cbf..c63f15e5880 100644
--- a/spec/javascripts/vue_shared/components/table_pagination_spec.js
+++ b/spec/javascripts/vue_shared/components/table_pagination_spec.js
@@ -32,7 +32,7 @@ describe('Pagination component', () => {
change: spy,
});
- expect(component.$el.innerHTML).not.toBeDefined();
+ expect(component.$el.childNodes.length).toEqual(0);
});
describe('prev button', () => {
diff --git a/spec/lib/gitlab/bare_repository_import/importer_spec.rb b/spec/lib/gitlab/bare_repository_import/importer_spec.rb
index b5d86df09d2..f302e412a6e 100644
--- a/spec/lib/gitlab/bare_repository_import/importer_spec.rb
+++ b/spec/lib/gitlab/bare_repository_import/importer_spec.rb
@@ -74,14 +74,18 @@ describe Gitlab::BareRepositoryImport::Importer, repository: true do
importer.create_project_if_needed
end
- it 'creates the Git repo in disk' do
+ it 'creates the Git repo on disk with the proper symlink for hooks' do
create_bare_repository("#{project_path}.git")
importer.create_project_if_needed
project = Project.find_by_full_path(project_path)
+ repo_path = File.join(project.repository_storage_path, project.disk_path + '.git')
+ hook_path = File.join(repo_path, 'hooks')
- expect(File).to exist(File.join(project.repository_storage_path, project.disk_path + '.git'))
+ expect(File).to exist(repo_path)
+ expect(File.symlink?(hook_path)).to be true
+ expect(File.readlink(hook_path)).to eq(Gitlab.config.gitlab_shell.hooks_path)
end
context 'hashed storage enabled' do
diff --git a/spec/lib/gitlab/git/gitlab_projects_spec.rb b/spec/lib/gitlab/git/gitlab_projects_spec.rb
index a798b188a0d..beef843537d 100644
--- a/spec/lib/gitlab/git/gitlab_projects_spec.rb
+++ b/spec/lib/gitlab/git/gitlab_projects_spec.rb
@@ -25,51 +25,6 @@ describe Gitlab::Git::GitlabProjects do
it { expect(gl_projects.logger).to eq(logger) }
end
- describe '#mv_project' do
- let(:new_repo_path) { File.join(tmp_repos_path, 'repo.git') }
-
- it 'moves a repo directory' do
- expect(File.exist?(tmp_repo_path)).to be_truthy
-
- message = "Moving repository from <#{tmp_repo_path}> to <#{new_repo_path}>."
- expect(logger).to receive(:info).with(message)
-
- expect(gl_projects.mv_project('repo.git')).to be_truthy
-
- expect(File.exist?(tmp_repo_path)).to be_falsy
- expect(File.exist?(new_repo_path)).to be_truthy
- end
-
- it "fails if the source path doesn't exist" do
- expected_source_path = File.join(tmp_repos_path, 'bad-src.git')
- expect(logger).to receive(:error).with("mv-project failed: source path <#{expected_source_path}> does not exist.")
-
- result = build_gitlab_projects(tmp_repos_path, 'bad-src.git').mv_project('repo.git')
- expect(result).to be_falsy
- end
-
- it 'fails if the destination path already exists' do
- FileUtils.mkdir_p(File.join(tmp_repos_path, 'already-exists.git'))
-
- expected_distination_path = File.join(tmp_repos_path, 'already-exists.git')
- message = "mv-project failed: destination path <#{expected_distination_path}> already exists."
- expect(logger).to receive(:error).with(message)
-
- expect(gl_projects.mv_project('already-exists.git')).to be_falsy
- end
- end
-
- describe '#rm_project' do
- it 'removes a repo directory' do
- expect(File.exist?(tmp_repo_path)).to be_truthy
- expect(logger).to receive(:info).with("Removing repository <#{tmp_repo_path}>.")
-
- expect(gl_projects.rm_project).to be_truthy
-
- expect(File.exist?(tmp_repo_path)).to be_falsy
- end
- end
-
describe '#push_branches' do
let(:remote_name) { 'remote-name' }
let(:branch_name) { 'master' }
diff --git a/spec/lib/gitlab/insecure_key_fingerprint_spec.rb b/spec/lib/gitlab/insecure_key_fingerprint_spec.rb
new file mode 100644
index 00000000000..6532579b1c9
--- /dev/null
+++ b/spec/lib/gitlab/insecure_key_fingerprint_spec.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+
+describe Gitlab::InsecureKeyFingerprint do
+ let(:key) do
+ 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn' \
+ '1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qk' \
+ 'r8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMg' \
+ 'Jw0='
+ end
+
+ let(:fingerprint) { "3f:a2:ee:de:b5:de:53:c3:aa:2f:9c:45:24:4c:47:7b" }
+
+ describe "#fingerprint" do
+ it "generates the key's fingerprint" do
+ expect(described_class.new(key.split[1]).fingerprint).to eq(fingerprint)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index 68a57826647..8b54d72d6f7 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -14,7 +14,7 @@ describe Gitlab::Regex do
it { is_expected.not_to match('?gitlab') }
end
- describe '.environment_slug_regex' do
+ describe '.environment_name_regex' do
subject { described_class.environment_name_regex }
it { is_expected.to match('foo') }
@@ -24,6 +24,7 @@ describe Gitlab::Regex do
it { is_expected.to match('foo.1') }
it { is_expected.not_to match('9&foo') }
it { is_expected.not_to match('foo-^') }
+ it { is_expected.not_to match('!!()()') }
end
describe '.environment_slug_regex' do
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index 81d9e6a8f82..2b61ce38418 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -4,6 +4,7 @@ require 'stringio'
describe Gitlab::Shell do
set(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
let(:gitlab_shell) { described_class.new }
let(:popen_vars) { { 'GIT_TERMINAL_PROMPT' => ENV['GIT_TERMINAL_PROMPT'] } }
let(:gitlab_projects) { double('gitlab_projects') }
@@ -51,6 +52,311 @@ describe Gitlab::Shell do
end
end
+ describe '#add_key' do
+ context 'when authorized_keys_enabled is true' do
+ it 'removes trailing garbage' do
+ allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
+ expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
+ [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar']
+ )
+
+ gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
+ end
+ end
+
+ context 'when authorized_keys_enabled is false' do
+ before do
+ stub_application_setting(authorized_keys_enabled: false)
+ end
+
+ it 'does nothing' do
+ expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute)
+
+ gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
+ end
+ end
+
+ context 'when authorized_keys_enabled is nil' do
+ before do
+ stub_application_setting(authorized_keys_enabled: nil)
+ end
+
+ it 'removes trailing garbage' do
+ allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
+ expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
+ [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar']
+ )
+
+ gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
+ end
+ end
+ end
+
+ describe '#batch_add_keys' do
+ context 'when authorized_keys_enabled is true' do
+ it 'instantiates KeyAdder' do
+ expect_any_instance_of(Gitlab::Shell::KeyAdder).to receive(:add_key).with('key-123', 'ssh-rsa foobar')
+
+ gitlab_shell.batch_add_keys do |adder|
+ adder.add_key('key-123', 'ssh-rsa foobar')
+ end
+ end
+ end
+
+ context 'when authorized_keys_enabled is false' do
+ before do
+ stub_application_setting(authorized_keys_enabled: false)
+ end
+
+ it 'does nothing' do
+ expect_any_instance_of(Gitlab::Shell::KeyAdder).not_to receive(:add_key)
+
+ gitlab_shell.batch_add_keys do |adder|
+ adder.add_key('key-123', 'ssh-rsa foobar')
+ end
+ end
+ end
+
+ context 'when authorized_keys_enabled is nil' do
+ before do
+ stub_application_setting(authorized_keys_enabled: nil)
+ end
+
+ it 'instantiates KeyAdder' do
+ expect_any_instance_of(Gitlab::Shell::KeyAdder).to receive(:add_key).with('key-123', 'ssh-rsa foobar')
+
+ gitlab_shell.batch_add_keys do |adder|
+ adder.add_key('key-123', 'ssh-rsa foobar')
+ end
+ end
+ end
+ end
+
+ describe '#remove_key' do
+ context 'when authorized_keys_enabled is true' do
+ it 'removes trailing garbage' do
+ allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
+ expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
+ [:gitlab_shell_keys_path, 'rm-key', 'key-123', 'ssh-rsa foobar']
+ )
+
+ gitlab_shell.remove_key('key-123', 'ssh-rsa foobar')
+ end
+ end
+
+ context 'when authorized_keys_enabled is false' do
+ before do
+ stub_application_setting(authorized_keys_enabled: false)
+ end
+
+ it 'does nothing' do
+ expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute)
+
+ gitlab_shell.remove_key('key-123', 'ssh-rsa foobar')
+ end
+ end
+
+ context 'when authorized_keys_enabled is nil' do
+ before do
+ stub_application_setting(authorized_keys_enabled: nil)
+ end
+
+ it 'removes trailing garbage' do
+ allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
+ expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
+ [:gitlab_shell_keys_path, 'rm-key', 'key-123', 'ssh-rsa foobar']
+ )
+
+ gitlab_shell.remove_key('key-123', 'ssh-rsa foobar')
+ end
+ end
+
+ context 'when key content is not given' do
+ it 'calls rm-key with only one argument' do
+ allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
+ expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
+ [:gitlab_shell_keys_path, 'rm-key', 'key-123']
+ )
+
+ gitlab_shell.remove_key('key-123')
+ end
+ end
+ end
+
+ describe '#remove_all_keys' do
+ context 'when authorized_keys_enabled is true' do
+ it 'removes trailing garbage' do
+ allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
+ expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with([:gitlab_shell_keys_path, 'clear'])
+
+ gitlab_shell.remove_all_keys
+ end
+ end
+
+ context 'when authorized_keys_enabled is false' do
+ before do
+ stub_application_setting(authorized_keys_enabled: false)
+ end
+
+ it 'does nothing' do
+ expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute)
+
+ gitlab_shell.remove_all_keys
+ end
+ end
+
+ context 'when authorized_keys_enabled is nil' do
+ before do
+ stub_application_setting(authorized_keys_enabled: nil)
+ end
+
+ it 'removes trailing garbage' do
+ allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
+ expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
+ [:gitlab_shell_keys_path, 'clear']
+ )
+
+ gitlab_shell.remove_all_keys
+ end
+ end
+ end
+
+ describe '#remove_keys_not_found_in_db' do
+ context 'when keys are in the file that are not in the DB' do
+ before do
+ gitlab_shell.remove_all_keys
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ gitlab_shell.add_key('key-9876', 'ssh-rsa ASDFASDF')
+ @another_key = create(:key) # this one IS in the DB
+ end
+
+ it 'removes the keys' do
+ expect(find_in_authorized_keys_file(1234)).to be_truthy
+ expect(find_in_authorized_keys_file(9876)).to be_truthy
+ expect(find_in_authorized_keys_file(@another_key.id)).to be_truthy
+ gitlab_shell.remove_keys_not_found_in_db
+ expect(find_in_authorized_keys_file(1234)).to be_falsey
+ expect(find_in_authorized_keys_file(9876)).to be_falsey
+ expect(find_in_authorized_keys_file(@another_key.id)).to be_truthy
+ end
+ end
+
+ context 'when keys there are duplicate keys in the file that are not in the DB' do
+ before do
+ gitlab_shell.remove_all_keys
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ end
+
+ it 'removes the keys' do
+ expect(find_in_authorized_keys_file(1234)).to be_truthy
+ gitlab_shell.remove_keys_not_found_in_db
+ expect(find_in_authorized_keys_file(1234)).to be_falsey
+ end
+
+ it 'does not run remove more than once per key (in a batch)' do
+ expect(gitlab_shell).to receive(:remove_key).with('key-1234').once
+ gitlab_shell.remove_keys_not_found_in_db
+ end
+ end
+
+ context 'when keys there are duplicate keys in the file that ARE in the DB' do
+ before do
+ gitlab_shell.remove_all_keys
+ @key = create(:key)
+ gitlab_shell.add_key(@key.shell_id, @key.key)
+ end
+
+ it 'does not remove the key' do
+ gitlab_shell.remove_keys_not_found_in_db
+ expect(find_in_authorized_keys_file(@key.id)).to be_truthy
+ end
+
+ it 'does not need to run a SELECT query for that batch, on account of that key' do
+ expect_any_instance_of(ActiveRecord::Relation).not_to receive(:pluck)
+ gitlab_shell.remove_keys_not_found_in_db
+ end
+ end
+
+ unless ENV['CI'] # Skip in CI, it takes 1 minute
+ context 'when the first batch can be skipped, but the next batch has keys that are not in the DB' do
+ before do
+ gitlab_shell.remove_all_keys
+ 100.times { |i| create(:key) } # first batch is all in the DB
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ end
+
+ it 'removes the keys not in the DB' do
+ expect(find_in_authorized_keys_file(1234)).to be_truthy
+ gitlab_shell.remove_keys_not_found_in_db
+ expect(find_in_authorized_keys_file(1234)).to be_falsey
+ end
+ end
+ end
+ end
+
+ describe '#batch_read_key_ids' do
+ context 'when there are keys in the authorized_keys file' do
+ before do
+ gitlab_shell.remove_all_keys
+ (1..4).each do |i|
+ gitlab_shell.add_key("key-#{i}", "ssh-rsa ASDFASDF#{i}")
+ end
+ end
+
+ it 'iterates over the key IDs in the file, in batches' do
+ loop_count = 0
+ first_batch = [1, 2]
+ second_batch = [3, 4]
+
+ gitlab_shell.batch_read_key_ids(batch_size: 2) do |batch|
+ expected = (loop_count == 0 ? first_batch : second_batch)
+ expect(batch).to eq(expected)
+ loop_count += 1
+ end
+ end
+ end
+ end
+
+ describe '#list_key_ids' do
+ context 'when there are keys in the authorized_keys file' do
+ before do
+ gitlab_shell.remove_all_keys
+ (1..4).each do |i|
+ gitlab_shell.add_key("key-#{i}", "ssh-rsa ASDFASDF#{i}")
+ end
+ end
+
+ it 'outputs the key IDs in the file, separated by newlines' do
+ ids = []
+ gitlab_shell.list_key_ids do |io|
+ io.each do |line|
+ ids << line
+ end
+ end
+
+ expect(ids).to eq(%W{1\n 2\n 3\n 4\n})
+ end
+ end
+
+ context 'when there are no keys in the authorized_keys file' do
+ before do
+ gitlab_shell.remove_all_keys
+ end
+
+ it 'outputs nothing, not even an empty string' do
+ ids = []
+ gitlab_shell.list_key_ids do |io|
+ io.each do |line|
+ ids << line
+ end
+ end
+
+ expect(ids).to eq([])
+ end
+ end
+ end
+
describe Gitlab::Shell::KeyAdder do
describe '#add_key' do
it 'removes trailing garbage' do
@@ -96,17 +402,6 @@ describe Gitlab::Shell do
allow(Gitlab.config.gitlab_shell).to receive(:git_timeout).and_return(800)
end
- describe '#add_key' do
- it 'removes trailing garbage' do
- allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
- expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
- [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar']
- )
-
- gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
- end
- end
-
describe '#add_repository' do
shared_examples '#add_repository' do
let(:repository_storage) { 'default' }
@@ -148,32 +443,44 @@ describe Gitlab::Shell do
end
describe '#remove_repository' do
- subject { gitlab_shell.remove_repository(project.repository_storage_path, project.disk_path) }
+ let!(:project) { create(:project, :repository) }
+ let(:disk_path) { "#{project.disk_path}.git" }
it 'returns true when the command succeeds' do
- expect(gitlab_projects).to receive(:rm_project) { true }
+ expect(gitlab_shell.exists?(project.repository_storage_path, disk_path)).to be(true)
- is_expected.to be_truthy
+ expect(gitlab_shell.remove_repository(project.repository_storage_path, project.disk_path)).to be(true)
+
+ expect(gitlab_shell.exists?(project.repository_storage_path, disk_path)).to be(false)
end
- it 'returns false when the command fails' do
- expect(gitlab_projects).to receive(:rm_project) { false }
+ it 'keeps the namespace directory' do
+ gitlab_shell.remove_repository(project.repository_storage_path, project.disk_path)
- is_expected.to be_falsy
+ expect(gitlab_shell.exists?(project.repository_storage_path, disk_path)).to be(false)
+ expect(gitlab_shell.exists?(project.repository_storage_path, project.disk_path.gsub(project.name, ''))).to be(true)
end
end
describe '#mv_repository' do
+ let!(:project2) { create(:project, :repository) }
+
it 'returns true when the command succeeds' do
- expect(gitlab_projects).to receive(:mv_project).with('project/newpath.git') { true }
+ old_path = project2.disk_path
+ new_path = "project/new_path"
+
+ expect(gitlab_shell.exists?(project2.repository_storage_path, "#{old_path}.git")).to be(true)
+ expect(gitlab_shell.exists?(project2.repository_storage_path, "#{new_path}.git")).to be(false)
- expect(gitlab_shell.mv_repository(project.repository_storage_path, project.disk_path, 'project/newpath')).to be_truthy
+ expect(gitlab_shell.mv_repository(project2.repository_storage_path, old_path, new_path)).to be_truthy
+
+ expect(gitlab_shell.exists?(project2.repository_storage_path, "#{old_path}.git")).to be(false)
+ expect(gitlab_shell.exists?(project2.repository_storage_path, "#{new_path}.git")).to be(true)
end
it 'returns false when the command fails' do
- expect(gitlab_projects).to receive(:mv_project).with('project/newpath.git') { false }
-
- expect(gitlab_shell.mv_repository(project.repository_storage_path, project.disk_path, 'project/newpath')).to be_falsy
+ expect(gitlab_shell.mv_repository(project2.repository_storage_path, project2.disk_path, '')).to be_falsy
+ expect(gitlab_shell.exists?(project2.repository_storage_path, "#{project2.disk_path}.git")).to be(true)
end
end
@@ -201,8 +508,6 @@ describe Gitlab::Shell do
end
shared_examples 'fetch_remote' do |gitaly_on|
- let(:repository) { project.repository }
-
def fetch_remote(ssh_auth = nil)
gitlab_shell.fetch_remote(repository.raw_repository, 'remote-name', ssh_auth: ssh_auth)
end
@@ -325,6 +630,23 @@ describe Gitlab::Shell do
describe '#fetch_remote gitaly' do
it_should_behave_like 'fetch_remote', true
+
+ context 'gitaly call' do
+ let(:remote_name) { 'remote-name' }
+ let(:ssh_auth) { double(:ssh_auth) }
+
+ subject do
+ gitlab_shell.fetch_remote(repository.raw_repository, remote_name,
+ forced: true, no_tags: true, ssh_auth: ssh_auth)
+ end
+
+ it 'passes the correct params to the gitaly service' do
+ expect(repository.gitaly_repository_client).to receive(:fetch_remote)
+ .with(remote_name, ssh_auth: ssh_auth, forced: true, no_tags: true, timeout: timeout)
+
+ subject
+ end
+ end
end
describe '#import_repository' do
@@ -396,4 +718,12 @@ describe Gitlab::Shell do
end
end
end
+
+ def find_in_authorized_keys_file(key_id)
+ gitlab_shell.batch_read_key_ids do |ids|
+ return true if ids.include?(key_id)
+ end
+
+ false
+ end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index c0db2c1b386..edd981752d9 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -412,6 +412,28 @@ describe Repository do
end
end
+ describe '#create_hooks' do
+ let(:hook_path) { File.join(repository.path_to_repo, 'hooks') }
+
+ it 'symlinks the global hooks directory' do
+ repository.create_hooks
+
+ expect(File.symlink?(hook_path)).to be true
+ expect(File.readlink(hook_path)).to eq(Gitlab.config.gitlab_shell.hooks_path)
+ end
+
+ it 'replaces existing symlink with the right directory' do
+ FileUtils.mkdir_p(hook_path)
+
+ expect(File.symlink?(hook_path)).to be false
+
+ repository.create_hooks
+
+ expect(File.symlink?(hook_path)).to be true
+ expect(File.readlink(hook_path)).to eq(Gitlab.config.gitlab_shell.hooks_path)
+ end
+ end
+
describe "#create_dir" do
it "commits a change that creates a new directory" do
expect do
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 0d2bd3207c0..34db50dc082 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -512,6 +512,31 @@ describe API::Commits do
end
end
+ context 'when stat param' do
+ let(:route) { "/projects/#{project_id}/repository/commits/#{commit_id}" }
+
+ it 'is not present return stats by default' do
+ get api(route, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to include 'stats'
+ end
+
+ it "is false it does not include stats" do
+ get api(route, user), stats: false
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).not_to include 'stats'
+ end
+
+ it "is true it includes stats" do
+ get api(route, user), stats: true
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to include 'stats'
+ end
+ end
+
context 'when unauthenticated', 'and project is public' do
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb
index 0462f494e15..837389451e8 100644
--- a/spec/requests/api/helpers_spec.rb
+++ b/spec/requests/api/helpers_spec.rb
@@ -68,6 +68,12 @@ describe API::Helpers do
end
it { is_expected.to eq(user) }
+
+ it 'sets the environment with data of the current user' do
+ subject
+
+ expect(env[API::Helpers::API_USER_ENV]).to eq({ user_id: subject.id, username: subject.username })
+ end
end
context "HEAD request" do
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 7b25047ea8f..2783c51b8df 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -192,6 +192,54 @@ describe API::Internal do
end
end
+ describe "GET /internal/authorized_keys" do
+ context "using an existing key's fingerprint" do
+ it "finds the key" do
+ get(api('/internal/authorized_keys'), fingerprint: key.fingerprint, secret_token: secret_token)
+
+ expect(response.status).to eq(200)
+ expect(json_response["key"]).to eq(key.key)
+ end
+ end
+
+ context "non existing key's fingerprint" do
+ it "returns 404" do
+ get(api('/internal/authorized_keys'), fingerprint: "no:t-:va:li:d0", secret_token: secret_token)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context "using a partial fingerprint" do
+ it "returns 404" do
+ get(api('/internal/authorized_keys'), fingerprint: "#{key.fingerprint[0..5]}%", secret_token: secret_token)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context "sending the key" do
+ it "finds the key" do
+ get(api('/internal/authorized_keys'), key: key.key.split[1], secret_token: secret_token)
+
+ expect(response.status).to eq(200)
+ expect(json_response["key"]).to eq(key.key)
+ end
+
+ it "returns 404 with a partial key" do
+ get(api('/internal/authorized_keys'), key: key.key.split[1][0...-3], secret_token: secret_token)
+
+ expect(response.status).to eq(404)
+ end
+
+ it "returns 404 with an not valid base64 string" do
+ get(api('/internal/authorized_keys'), key: "whatever!", secret_token: secret_token)
+
+ expect(response.status).to eq(404)
+ end
+ end
+ end
+
describe "POST /internal/allowed", :clean_gitlab_redis_shared_state do
context "access granted" do
around do |example|
diff --git a/spec/requests/api/v3/commits_spec.rb b/spec/requests/api/v3/commits_spec.rb
index 8b115e01f47..34c543bffe8 100644
--- a/spec/requests/api/v3/commits_spec.rb
+++ b/spec/requests/api/v3/commits_spec.rb
@@ -403,6 +403,33 @@ describe API::V3::Commits do
expect(response).to have_gitlab_http_status(200)
expect(json_response['status']).to eq("created")
end
+
+ context 'when stat param' do
+ let(:project_id) { project.id }
+ let(:commit_id) { project.repository.commit.id }
+ let(:route) { "/projects/#{project_id}/repository/commits/#{commit_id}" }
+
+ it 'is not present return stats by default' do
+ get v3_api(route, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to include 'stats'
+ end
+
+ it "is false it does not include stats" do
+ get v3_api(route, user), stats: false
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).not_to include 'stats'
+ end
+
+ it "is true it includes stats" do
+ get v3_api(route, user), stats: true
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to include 'stats'
+ end
+ end
end
context "unauthorized user" do
diff --git a/spec/workers/gitlab_shell_worker_spec.rb b/spec/workers/gitlab_shell_worker_spec.rb
new file mode 100644
index 00000000000..6b222af454d
--- /dev/null
+++ b/spec/workers/gitlab_shell_worker_spec.rb
@@ -0,0 +1,12 @@
+require 'spec_helper'
+
+describe GitlabShellWorker do
+ let(:worker) { described_class.new }
+
+ describe '#perform with add_key' do
+ it 'calls add_key on Gitlab::Shell' do
+ expect_any_instance_of(Gitlab::Shell).to receive(:add_key).with('foo', 'bar')
+ worker.perform(:add_key, 'foo', 'bar')
+ end
+ end
+end