Commit b64752c2 authored by Joshua Lambert's avatar Joshua Lambert

Fix merge conflicts

parents 4dff5dbc 64aec37d
...@@ -180,6 +180,7 @@ update-knapsack: ...@@ -180,6 +180,7 @@ update-knapsack:
<<: *only-canonical-masters <<: *only-canonical-masters
stage: post-test stage: post-test
script: script:
- retry gem install fog-aws mime-types
- scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec-pg_node_*.json - scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec-pg_node_*.json
- scripts/merge-reports ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/spinach-pg_node_*.json - scripts/merge-reports ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/spinach-pg_node_*.json
- '[[ -z ${KNAPSACK_S3_BUCKET} ]] || scripts/sync-reports put $KNAPSACK_S3_BUCKET $KNAPSACK_RSPEC_SUITE_REPORT_PATH $KNAPSACK_SPINACH_SUITE_REPORT_PATH' - '[[ -z ${KNAPSACK_S3_BUCKET} ]] || scripts/sync-reports put $KNAPSACK_S3_BUCKET $KNAPSACK_RSPEC_SUITE_REPORT_PATH $KNAPSACK_SPINACH_SUITE_REPORT_PATH'
......
...@@ -91,7 +91,7 @@ gem 'carrierwave', '~> 1.1' ...@@ -91,7 +91,7 @@ gem 'carrierwave', '~> 1.1'
gem 'dropzonejs-rails', '~> 0.7.1' gem 'dropzonejs-rails', '~> 0.7.1'
# for backups # for backups
gem 'fog-aws', '~> 0.9' gem 'fog-aws', '~> 1.4'
gem 'fog-core', '~> 1.44' gem 'fog-core', '~> 1.44'
gem 'fog-google', '~> 0.5' gem 'fog-google', '~> 0.5'
gem 'fog-local', '~> 0.3' gem 'fog-local', '~> 0.3'
...@@ -237,7 +237,6 @@ gem 'webpack-rails', '~> 0.9.10' ...@@ -237,7 +237,6 @@ gem 'webpack-rails', '~> 0.9.10'
gem 'rack-proxy', '~> 0.6.0' gem 'rack-proxy', '~> 0.6.0'
gem 'sass-rails', '~> 5.0.6' gem 'sass-rails', '~> 5.0.6'
gem 'coffee-rails', '~> 4.1.0'
gem 'uglifier', '~> 2.7.2' gem 'uglifier', '~> 2.7.2'
gem 'addressable', '~> 2.3.8' gem 'addressable', '~> 2.3.8'
......
...@@ -122,13 +122,6 @@ GEM ...@@ -122,13 +122,6 @@ GEM
coderay (1.1.1) coderay (1.1.1)
coercible (1.0.0) coercible (1.0.0)
descendants_tracker (~> 0.0.1) descendants_tracker (~> 0.0.1)
coffee-rails (4.1.1)
coffee-script (>= 2.2.0)
railties (>= 4.0.0, < 5.1.x)
coffee-script (2.4.1)
coffee-script-source
execjs
coffee-script-source (1.10.0)
colorize (0.7.7) colorize (0.7.7)
concurrent-ruby (1.0.5) concurrent-ruby (1.0.5)
concurrent-ruby-ext (1.0.5) concurrent-ruby-ext (1.0.5)
...@@ -186,7 +179,7 @@ GEM ...@@ -186,7 +179,7 @@ GEM
et-orbi (1.0.3) et-orbi (1.0.3)
tzinfo tzinfo
eventmachine (1.0.8) eventmachine (1.0.8)
excon (0.55.0) excon (0.57.1)
execjs (2.6.0) execjs (2.6.0)
expression_parser (0.9.0) expression_parser (0.9.0)
extlib (0.9.16) extlib (0.9.16)
...@@ -222,26 +215,26 @@ GEM ...@@ -222,26 +215,26 @@ GEM
fog-json (~> 1.0) fog-json (~> 1.0)
ipaddress (~> 0.8) ipaddress (~> 0.8)
xml-simple (~> 1.1) xml-simple (~> 1.1)
fog-aws (0.13.0) fog-aws (1.4.0)
fog-core (~> 1.38) fog-core (~> 1.38)
fog-json (~> 1.0) fog-json (~> 1.0)
fog-xml (~> 0.1) fog-xml (~> 0.1)
ipaddress (~> 0.8) ipaddress (~> 0.8)
fog-core (1.44.1) fog-core (1.44.3)
builder builder
excon (~> 0.49) excon (~> 0.49)
formatador (~> 0.2) formatador (~> 0.2)
fog-google (0.5.0) fog-google (0.5.3)
fog-core fog-core
fog-json fog-json
fog-xml fog-xml
fog-json (1.0.2) fog-json (1.0.2)
fog-core (~> 1.0) fog-core (~> 1.0)
multi_json (~> 1.10) multi_json (~> 1.10)
fog-local (0.3.0) fog-local (0.3.1)
fog-core (~> 1.27) fog-core (~> 1.27)
fog-openstack (0.1.6) fog-openstack (0.1.21)
fog-core (>= 1.39) fog-core (>= 1.40)
fog-json (>= 1.0) fog-json (>= 1.0)
ipaddress (>= 0.8) ipaddress (>= 0.8)
fog-rackspace (0.1.1) fog-rackspace (0.1.1)
...@@ -938,7 +931,6 @@ DEPENDENCIES ...@@ -938,7 +931,6 @@ DEPENDENCIES
charlock_holmes (~> 0.7.3) charlock_holmes (~> 0.7.3)
chronic (~> 0.10.2) chronic (~> 0.10.2)
chronic_duration (~> 0.10.6) chronic_duration (~> 0.10.6)
coffee-rails (~> 4.1.0)
concurrent-ruby (~> 1.0.5) concurrent-ruby (~> 1.0.5)
connection_pool (~> 2.0) connection_pool (~> 2.0)
creole (~> 0.5.0) creole (~> 0.5.0)
...@@ -961,7 +953,7 @@ DEPENDENCIES ...@@ -961,7 +953,7 @@ DEPENDENCIES
flipper (~> 0.10.2) flipper (~> 0.10.2)
flipper-active_record (~> 0.10.2) flipper-active_record (~> 0.10.2)
fog-aliyun (~> 0.1.0) fog-aliyun (~> 0.1.0)
fog-aws (~> 0.9) fog-aws (~> 1.4)
fog-core (~> 1.44) fog-core (~> 1.44)
fog-google (~> 0.5) fog-google (~> 0.5)
fog-local (~> 0.3) fog-local (~> 0.3)
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
## Test coverage ## Test coverage
- [![Ruby coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby) Ruby - [![Ruby coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby) Ruby
- [![JavaScript coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=rake+karma)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-javascript) JavaScript - [![JavaScript coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=karma)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-javascript) JavaScript
## Canonical source ## Canonical source
......
...@@ -5,21 +5,28 @@ import './preview_markdown'; ...@@ -5,21 +5,28 @@ import './preview_markdown';
window.DropzoneInput = (function() { window.DropzoneInput = (function() {
function DropzoneInput(form) { function DropzoneInput(form) {
var updateAttachingMessage, $attachingFileMessage, $mdArea, $attachButton, $cancelButton, $retryLink, $uploadingErrorContainer, $uploadingErrorMessage, $uploadProgress, $uploadingProgressContainer, appendToTextArea, btnAlert, child, closeAlertMessage, closeSpinner, divHover, divSpinner, dropzone, $formDropzone, formTextarea, getFilename, handlePaste, iconPaperclip, iconSpinner, insertToTextArea, isImage, maxFileSize, pasteText, uploadsPath, showError, showSpinner, uploadFile, addFileToForm;
Dropzone.autoDiscover = false; Dropzone.autoDiscover = false;
divHover = '<div class="div-dropzone-hover"></div>'; const divHover = '<div class="div-dropzone-hover"></div>';
iconPaperclip = '<i class="fa fa-paperclip div-dropzone-icon"></i>'; const iconPaperclip = '<i class="fa fa-paperclip div-dropzone-icon"></i>';
$attachButton = form.find('.button-attach-file'); const $attachButton = form.find('.button-attach-file');
$attachingFileMessage = form.find('.attaching-file-message'); const $attachingFileMessage = form.find('.attaching-file-message');
$cancelButton = form.find('.button-cancel-uploading-files'); const $cancelButton = form.find('.button-cancel-uploading-files');
$retryLink = form.find('.retry-uploading-link'); const $retryLink = form.find('.retry-uploading-link');
$uploadProgress = form.find('.uploading-progress'); const $uploadProgress = form.find('.uploading-progress');
$uploadingErrorContainer = form.find('.uploading-error-container'); const $uploadingErrorContainer = form.find('.uploading-error-container');
$uploadingErrorMessage = form.find('.uploading-error-message'); const $uploadingErrorMessage = form.find('.uploading-error-message');
$uploadingProgressContainer = form.find('.uploading-progress-container'); const $uploadingProgressContainer = form.find('.uploading-progress-container');
uploadsPath = window.uploads_path || null; const uploadsPath = window.uploads_path || null;
maxFileSize = gon.max_file_size || 10; const maxFileSize = gon.max_file_size || 10;
formTextarea = form.find('.js-gfm-input'); const formTextarea = form.find('.js-gfm-input');
let handlePaste;
let pasteText;
let addFileToForm;
let updateAttachingMessage;
let isImage;
let getFilename;
let uploadFile;
formTextarea.wrap('<div class="div-dropzone"></div>'); formTextarea.wrap('<div class="div-dropzone"></div>');
formTextarea.on('paste', (function(_this) { formTextarea.on('paste', (function(_this) {
return function(event) { return function(event) {
...@@ -28,16 +35,16 @@ window.DropzoneInput = (function() { ...@@ -28,16 +35,16 @@ window.DropzoneInput = (function() {
})(this)); })(this));
// Add dropzone area to the form. // Add dropzone area to the form.
$mdArea = formTextarea.closest('.md-area'); const $mdArea = formTextarea.closest('.md-area');
form.setupMarkdownPreview(); form.setupMarkdownPreview();
$formDropzone = form.find('.div-dropzone'); const $formDropzone = form.find('.div-dropzone');
$formDropzone.parent().addClass('div-dropzone-wrapper'); $formDropzone.parent().addClass('div-dropzone-wrapper');
$formDropzone.append(divHover); $formDropzone.append(divHover);
$formDropzone.find('.div-dropzone-hover').append(iconPaperclip); $formDropzone.find('.div-dropzone-hover').append(iconPaperclip);
if (!uploadsPath) return; if (!uploadsPath) return;
dropzone = $formDropzone.dropzone({ const dropzone = $formDropzone.dropzone({
url: uploadsPath, url: uploadsPath,
dictDefaultMessage: '', dictDefaultMessage: '',
clickable: true, clickable: true,
...@@ -117,7 +124,7 @@ window.DropzoneInput = (function() { ...@@ -117,7 +124,7 @@ window.DropzoneInput = (function() {
} }
}); });
child = $(dropzone[0]).children('textarea'); const child = $(dropzone[0]).children('textarea');
// removeAllFiles(true) stops uploading files (if any) // removeAllFiles(true) stops uploading files (if any)
// and remove them from dropzone files queue. // and remove them from dropzone files queue.
...@@ -214,6 +221,35 @@ window.DropzoneInput = (function() { ...@@ -214,6 +221,35 @@ window.DropzoneInput = (function() {
return value.first(); return value.first();
}; };
const showSpinner = function(e) {
return $uploadingProgressContainer.removeClass('hide');
};
const closeSpinner = function() {
return $uploadingProgressContainer.addClass('hide');
};
const showError = function(message) {
$uploadingErrorContainer.removeClass('hide');
$uploadingErrorMessage.html(message);
};
const closeAlertMessage = function() {
return form.find('.div-dropzone-alert').alert('close');
};
const insertToTextArea = function(filename, url) {
return $(child).val(function(index, val) {
return val.replace(`{{${filename}}}`, url);
});
};
const appendToTextArea = function(url) {
return $(child).val(function(index, val) {
return val + url + "\n";
});
};
uploadFile = function(item, filename) { uploadFile = function(item, filename) {
var formData; var formData;
formData = new FormData(); formData = new FormData();
...@@ -262,35 +298,6 @@ window.DropzoneInput = (function() { ...@@ -262,35 +298,6 @@ window.DropzoneInput = (function() {
messageContainer.text(attachingMessage); messageContainer.text(attachingMessage);
}; };
insertToTextArea = function(filename, url) {
return $(child).val(function(index, val) {
return val.replace(`{{${filename}}}`, url);
});
};
appendToTextArea = function(url) {
return $(child).val(function(index, val) {
return val + url + "\n";
});
};
showSpinner = function(e) {
return $uploadingProgressContainer.removeClass('hide');
};
closeSpinner = function() {
return $uploadingProgressContainer.addClass('hide');
};
showError = function(message) {
$uploadingErrorContainer.removeClass('hide');
$uploadingErrorMessage.html(message);
};
closeAlertMessage = function() {
return form.find('.div-dropzone-alert').alert('close');
};
form.find('.markdown-selector').click(function(e) { form.find('.markdown-selector').click(function(e) {
e.preventDefault(); e.preventDefault();
$(this).closest('.gfm-form').find('.div-dropzone').click(); $(this).closest('.gfm-form').find('.div-dropzone').click();
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
*/ */
export default { export default {
ABORTED: 0,
NO_CONTENT: 204, NO_CONTENT: 204,
OK: 200, OK: 200,
}; };
...@@ -81,6 +81,9 @@ export default class Poll { ...@@ -81,6 +81,9 @@ export default class Poll {
}) })
.catch((error) => { .catch((error) => {
notificationCallback(false); notificationCallback(false);
if (error.status === httpStatusCodes.ABORTED) {
return;
}
errorCallback(error); errorCallback(error);
}); });
} }
......
...@@ -297,18 +297,12 @@ ...@@ -297,18 +297,12 @@
} }
.droplab-dropdown { .droplab-dropdown {
.description {
display: inline-block;
white-space: normal;
margin-left: 5px;
}
.dropdown-toggle > i { .dropdown-toggle > i {
pointer-events: none; pointer-events: none;
} }
li { .dropdown-menu li {
padding: $gl-btn-padding $gl-btn-padding 2px; padding: $gl-btn-padding;
cursor: pointer; cursor: pointer;
> a, > a,
...@@ -344,9 +338,25 @@ ...@@ -344,9 +338,25 @@
visibility: visible; visibility: visible;
} }
&.divider {
margin: 0 8px;
padding: 0;
border-top: $gray-darkest;
}
.icon { .icon {
visibility: hidden; visibility: hidden;
} }
.description {
display: inline-block;
white-space: normal;
margin-left: 5px;
p {
margin-bottom: 0;
}
}
} }
.icon { .icon {
...@@ -354,12 +364,6 @@ ...@@ -354,12 +364,6 @@
vertical-align: top; vertical-align: top;
padding-top: 2px; padding-top: 2px;
} }
.divider {
margin: 0 8px;
padding: 0;
border-top: $gray-darkest;
}
} }
.droplab-dropdown .dropdown-menu, .droplab-dropdown .dropdown-menu,
......
...@@ -275,7 +275,7 @@ ...@@ -275,7 +275,7 @@
} }
.filtered-search-input-dropdown-menu { .filtered-search-input-dropdown-menu {
max-height: 215px; max-height: 225px;
max-width: 280px; max-width: 280px;
overflow: auto; overflow: auto;
...@@ -382,10 +382,6 @@ ...@@ -382,10 +382,6 @@
} }
} }
.dropdown-menu .filter-dropdown-item {
padding: 0;
}
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
.issues-details-filters { .issues-details-filters {
padding: 0 0 10px; padding: 0 0 10px;
...@@ -435,6 +431,7 @@ ...@@ -435,6 +431,7 @@
.fa { .fa {
width: 15px; width: 15px;
line-height: $line-height-base;
} }
.dropdown-label-box { .dropdown-label-box {
......
...@@ -2,6 +2,10 @@ ...@@ -2,6 +2,10 @@
color: $gl-text-color; color: $gl-text-color;
word-wrap: break-word; word-wrap: break-word;
[dir="auto"] {
text-align: initial;
}
a { a {
color: $md-link-color; color: $md-link-color;
} }
......
...@@ -110,10 +110,10 @@ $well-light-text-color: #5b6169; ...@@ -110,10 +110,10 @@ $well-light-text-color: #5b6169;
* Text * Text
*/ */
$gl-font-size: 14px; $gl-font-size: 14px;
$gl-text-color: rgba(0, 0, 0, .85); $gl-text-color: #2e2e2e;
$gl-text-color-light: rgba(0, 0, 0, .7); $gl-text-color-secondary: #707070;
$gl-text-color-secondary: rgba(0, 0, 0, .55); $gl-text-color-tertiary: #949494;
$gl-text-color-disabled: rgba(0, 0, 0, .35); $gl-text-color-quaternary: #d6d6d6;
$gl-text-color-inverted: rgba(255, 255, 255, 1.0); $gl-text-color-inverted: rgba(255, 255, 255, 1.0);
$gl-text-color-secondary-inverted: rgba(255, 255, 255, .85); $gl-text-color-secondary-inverted: rgba(255, 255, 255, .85);
$gl-text-green: $green-600; $gl-text-green: $green-600;
...@@ -127,7 +127,7 @@ $gl-gray-dark: #313236; ...@@ -127,7 +127,7 @@ $gl-gray-dark: #313236;
$gl-gray-light: #5c5c5c; $gl-gray-light: #5c5c5c;
$gl-header-color: #4c4e54; $gl-header-color: #4c4e54;
$gl-header-nav-hover-color: #434343; $gl-header-nav-hover-color: #434343;
$placeholder-text-color: rgba(0, 0, 0, .42); $placeholder-text-color: $gl-text-color-tertiary;
/* /*
* Lists * Lists
...@@ -135,7 +135,7 @@ $placeholder-text-color: rgba(0, 0, 0, .42); ...@@ -135,7 +135,7 @@ $placeholder-text-color: rgba(0, 0, 0, .42);
$list-font-size: $gl-font-size; $list-font-size: $gl-font-size;
$list-title-color: $gl-text-color; $list-title-color: $gl-text-color;
$list-text-color: $gl-text-color; $list-text-color: $gl-text-color;
$list-text-disabled-color: $gl-text-color-disabled; $list-text-disabled-color: $gl-text-color-tertiary;
$list-border-light: #eee; $list-border-light: #eee;
$list-border: rgba(0, 0, 0, 0.05); $list-border: rgba(0, 0, 0, 0.05);
$list-text-height: 42px; $list-text-height: 42px;
......
...@@ -284,7 +284,7 @@ header.navbar-gitlab-new { ...@@ -284,7 +284,7 @@ header.navbar-gitlab-new {
position: relative; position: relative;
top: -1px; top: -1px;
padding: 0 5px; padding: 0 5px;
color: rgba($black, .65); color: $gl-text-color-secondary;
font-size: 10px; font-size: 10px;
line-height: 1; line-height: 1;
background: none; background: none;
...@@ -310,10 +310,10 @@ header.navbar-gitlab-new { ...@@ -310,10 +310,10 @@ header.navbar-gitlab-new {
.breadcrumbs-links { .breadcrumbs-links {
flex: 1; flex: 1;
align-self: center; align-self: center;
color: $black-transparent; color: $gl-text-color-quaternary;
a { a {
color: rgba($black, .65); color: $gl-text-color-secondary;
&:not(:first-child), &:not(:first-child),
&.group-path { &.group-path {
...@@ -368,9 +368,10 @@ header.navbar-gitlab-new { ...@@ -368,9 +368,10 @@ header.navbar-gitlab-new {
} }
.breadcrumbs-sub-title { .breadcrumbs-sub-title {
margin: 2px 0 0; margin: 2px 0;
font-size: 16px; font-size: 16px;
font-weight: normal; font-weight: normal;
line-height: 1;
ul { ul {
margin: 0; margin: 0;
......
...@@ -35,6 +35,7 @@ $new-sidebar-width: 220px; ...@@ -35,6 +35,7 @@ $new-sidebar-width: 220px;
.avatar-container { .avatar-container {
flex: 0 0 40px; flex: 0 0 40px;
background-color: $white-light;
} }
&:hover { &:hover {
......
...@@ -813,8 +813,6 @@ ...@@ -813,8 +813,6 @@
} }
.description { .description {
margin-bottom: 10px;
.text { .text {
margin: 0; margin: 0;
} }
......
class Commit class Commit
extend ActiveModel::Naming extend ActiveModel::Naming
extend Gitlab::Cache::RequestCache
include ActiveModel::Conversion include ActiveModel::Conversion
include Noteable include Noteable
...@@ -169,19 +170,9 @@ class Commit ...@@ -169,19 +170,9 @@ class Commit
end end
def author def author
if RequestStore.active? User.find_by_any_email(author_email.downcase)
key = "commit_author:#{author_email.downcase}"
# nil is a valid value since no author may exist in the system
if RequestStore.store.key?(key)
@author = RequestStore.store[key]
else
@author = find_author_by_any_email
RequestStore.store[key] = @author
end
else
@author ||= find_author_by_any_email
end
end end
request_cache(:author) { author_email.downcase }
def committer def committer
@committer ||= User.find_by_any_email(committer_email.downcase) @committer ||= User.find_by_any_email(committer_email.downcase)
...@@ -322,7 +313,7 @@ class Commit ...@@ -322,7 +313,7 @@ class Commit
def raw_diffs(*args) def raw_diffs(*args)
if Gitlab::GitalyClient.feature_enabled?(:commit_raw_diffs) if Gitlab::GitalyClient.feature_enabled?(:commit_raw_diffs)
Gitlab::GitalyClient::Commit.new(project.repository).diff_from_parent(self, *args) Gitlab::GitalyClient::CommitService.new(project.repository).diff_from_parent(self, *args)
else else
raw.diffs(*args) raw.diffs(*args)
end end
...@@ -331,7 +322,7 @@ class Commit ...@@ -331,7 +322,7 @@ class Commit
def raw_deltas def raw_deltas
@deltas ||= Gitlab::GitalyClient.migrate(:commit_deltas) do |is_enabled| @deltas ||= Gitlab::GitalyClient.migrate(:commit_deltas) do |is_enabled|
if is_enabled if is_enabled
Gitlab::GitalyClient::Commit.new(project.repository).commit_deltas(self) Gitlab::GitalyClient::CommitService.new(project.repository).commit_deltas(self)
else else
raw.deltas raw.deltas
end end
...@@ -368,10 +359,6 @@ class Commit ...@@ -368,10 +359,6 @@ class Commit
end end
end end
def find_author_by_any_email
User.find_by_any_email(author_email.downcase)
end
def repo_changes def repo_changes
changes = { added: [], modified: [], removed: [] } changes = { added: [], modified: [], removed: [] }
......
...@@ -2,7 +2,6 @@ require 'carrierwave/orm/activerecord' ...@@ -2,7 +2,6 @@ require 'carrierwave/orm/activerecord'
class Group < Namespace class Group < Namespace
include Gitlab::ConfigHelper include Gitlab::ConfigHelper
include Gitlab::VisibilityLevel
include AccessRequestable include AccessRequestable
include Avatarable include Avatarable
include Referable include Referable
...@@ -103,10 +102,6 @@ class Group < Namespace ...@@ -103,10 +102,6 @@ class Group < Namespace
full_name full_name
end end
def visibility_level_field
:visibility_level
end
def visibility_level_allowed_by_projects def visibility_level_allowed_by_projects
allowed_by_projects = self.projects.where('visibility_level > ?', self.visibility_level).none? allowed_by_projects = self.projects.where('visibility_level > ?', self.visibility_level).none?
......
...@@ -5,6 +5,7 @@ class Namespace < ActiveRecord::Base ...@@ -5,6 +5,7 @@ class Namespace < ActiveRecord::Base
include Sortable include Sortable
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
include Gitlab::CurrentSettings include Gitlab::CurrentSettings
include Gitlab::VisibilityLevel
include Routable include Routable
include AfterCommitQueue include AfterCommitQueue
...@@ -105,6 +106,10 @@ class Namespace < ActiveRecord::Base ...@@ -105,6 +106,10 @@ class Namespace < ActiveRecord::Base
end end
end end
def visibility_level_field
:visibility_level
end
def to_param def to_param
full_path full_path
end end
......
...@@ -190,7 +190,7 @@ class Note < ActiveRecord::Base ...@@ -190,7 +190,7 @@ class Note < ActiveRecord::Base
# override to return commits, which are not active record # override to return commits, which are not active record
def noteable def noteable
if for_commit? if for_commit?
project.commit(commit_id) @commit ||= project.commit(commit_id)
else else
super super
end end
......
...@@ -3,9 +3,13 @@ module Ci ...@@ -3,9 +3,13 @@ module Ci
condition(:protected_action) do condition(:protected_action) do
next false unless @subject.action? next false unless @subject.action?
!::Gitlab::UserAccess access = ::Gitlab::UserAccess.new(@user, project: @subject.project)
.new(@user, project: @subject.project)
.can_merge_to_branch?(@subject.ref) if @subject.tag?
!access.can_create_tag?(@subject.ref)
else
!access.can_merge_to_branch?(@subject.ref)
end
end end
rule { protected_action }.prevent :update_build rule { protected_action }.prevent :update_build
......
...@@ -55,7 +55,22 @@ ...@@ -55,7 +55,22 @@
%span %span
Members Members
- if current_user && can?(current_user, :admin_group, @group) - if current_user && can?(current_user, :admin_group, @group)
= nav_link(path: %w[groups#projects groups#edit]) do = nav_link(path: %w[groups#projects groups#edit ci_cd#show]) do
= link_to edit_group_path(@group), title: 'Settings' do = link_to edit_group_path(@group), title: 'Settings' do
%span %span
Settings Settings
%ul.sidebar-sub-level-items
= nav_link(path: 'groups#edit') do
= link_to edit_group_path(@group), title: 'General' do
%span
General
= nav_link(path: 'groups#projects') do
= link_to projects_group_path(@group), title: 'Projects' do
%span
Projects
= nav_link(controller: :ci_cd) do
= link_to group_settings_ci_cd_path(@group), title: 'Pipelines' do
%span
Pipelines
...@@ -165,7 +165,7 @@ ...@@ -165,7 +165,7 @@
Snippets Snippets
- if project_nav_tab? :settings - if project_nav_tab? :settings
= nav_link(path: %w[projects#edit members#show integrations#show services#edit repository#show ci_cd#show pages#show]) do = nav_link(path: %w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show pages#show]) do
= link_to edit_project_path(@project), title: 'Settings', class: 'shortcuts-tree' do = link_to edit_project_path(@project), title: 'Settings', class: 'shortcuts-tree' do
%span %span
Settings Settings
...@@ -177,8 +177,8 @@ ...@@ -177,8 +177,8 @@
= link_to edit_project_path(@project), title: 'General' do = link_to edit_project_path(@project), title: 'General' do
%span %span
General General
= nav_link(controller: :members) do = nav_link(controller: :project_members) do
= link_to project_settings_members_path(@project), title: 'Members' do = link_to project_project_members_path(@project), title: 'Members' do
%span %span
Members Members
- if can_edit - if can_edit
......
...@@ -9,8 +9,8 @@ ...@@ -9,8 +9,8 @@
#cycle-analytics{ class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project) } } #cycle-analytics{ class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project) } }
- if @cycle_analytics_no_data - if @cycle_analytics_no_data
.landing.content-block{ "v-if" => "!isOverviewDialogDismissed" } .landing.content-block{ "v-if" => "!isOverviewDialogDismissed" }
%button.dismiss-button{ type: 'button', 'aria-label': 'Dismiss Cycle Analytics introduction box' } %button.dismiss-button{ type: 'button', 'aria-label': 'Dismiss Cycle Analytics introduction box', "@click" => "dismissOverviewDialog()" }
= icon("times", "@click" => "dismissOverviewDialog()") = icon("times")
.svg-container .svg-container
= custom_icon('icon_cycle_analytics_splash') = custom_icon('icon_cycle_analytics_splash')
.inner-content .inner-content
......
...@@ -98,7 +98,7 @@ ...@@ -98,7 +98,7 @@
- if type == :boards - if type == :boards
- if can?(current_user, :admin_list, @project) - if can?(current_user, :admin_list, @project)
.dropdown.prepend-left-10#js-add-list .dropdown.prepend-left-10#js-add-list
%button.btn.btn-create.btn-inverted.js-new-board-list{ type: "button", data: { toggle: "dropdown", labels: labels_filter_path, namespace_path: @project.try(:namespace).try(:path), project_path: @project.try(:path) } } %button.btn.btn-create.btn-inverted.js-new-board-list{ type: "button", data: { toggle: "dropdown", labels: labels_filter_path, namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path) } }
Add list Add list
.dropdown-menu.dropdown-menu-paging.dropdown-menu-align-right.dropdown-menu-issues-board-new.dropdown-menu-selectable .dropdown-menu.dropdown-menu-paging.dropdown-menu-align-right.dropdown-menu-issues-board-new.dropdown-menu-selectable
= render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Add list" } = render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Add list" }
......
...@@ -2,6 +2,8 @@ class ProjectServiceWorker ...@@ -2,6 +2,8 @@ class ProjectServiceWorker
include Sidekiq::Worker include Sidekiq::Worker
include DedicatedSidekiqQueue include DedicatedSidekiqQueue
sidekiq_options dead: false
def perform(hook_id, data) def perform(hook_id, data)
data = data.with_indifferent_access data = data.with_indifferent_access
Service.find(hook_id).execute(data) Service.find(hook_id).execute(data)
......
...@@ -2,7 +2,7 @@ class WebHookWorker ...@@ -2,7 +2,7 @@ class WebHookWorker
include Sidekiq::Worker include Sidekiq::Worker
include DedicatedSidekiqQueue include DedicatedSidekiqQueue
sidekiq_options retry: 4 sidekiq_options retry: 4, dead: false
def perform(hook_id, data, hook_name) def perform(hook_id, data, hook_name)
hook = WebHook.find(hook_id) hook = WebHook.find(hook_id)
......
---
title: "reset text-align to initial to let elements with dir="auto" align texts to right in RTL languages ( default is left )"
merge_request: 12892
author: goshhob
---
title: refactor initializations in dropzone_input.js
merge_request: 12768
author: Brandon Everett
---
title: Prevent web hook and project service background jobs from going to the dead
jobs queue
merge_request:
author:
---
title: Remove coffee-rails gem
merge_request:
author: Takuya Noguchi
---
title: Protect manual actions against protected tag too
merge_request: 12908
author:
---
title: Bump fog-core to 1.44.3 and fog providers' plugins to latest
merge_request: 12897
author: Takuya Noguchi
---
title: allow closing Cycle Analytics intro box in firefox
merge_request:
author:
---
title: Fix label creation from new list for subgroup projects
merge_request:
author:
---
title: fix transient js error in rspec tests
merge_request:
author:
---
title: Add RequestCache which makes caching with RequestStore easier
merge_request: 12920
author:
...@@ -33,7 +33,6 @@ module GettextI18nRailsJs ...@@ -33,7 +33,6 @@ module GettextI18nRailsJs
[ [
".js", ".js",
".jsx", ".jsx",
".coffee",
".vue" ".vue"
].include? ::File.extname(file) ].include? ::File.extname(file)
end end
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
- aws_elb_request_count_sum - aws_elb_request_count_sum
weight: 1 weight: 1
queries: queries:
- query_range: 'sum(aws_elb_request_count_sum{%{environment_filter}}) * 60' - query_range: 'sum(aws_elb_request_count_sum{%{environment_filter}}) / 60'
label: Total label: Total
unit: req / sec unit: req / sec
- title: "Latency" - title: "Latency"
......
class AddForeignKeyToMergeRequests < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
class MergeRequest < ActiveRecord::Base
self.table_name = 'merge_requests'
include ::EachBatch
end
def up
scope = <<-SQL.strip_heredoc
head_pipeline_id IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM ci_pipelines
WHERE ci_pipelines.id = merge_requests.head_pipeline_id
)
SQL
MergeRequest.where(scope).each_batch(of: 1000) do |merge_requests|
merge_requests.update_all(head_pipeline_id: nil)
end
unless foreign_key_exists?(:merge_requests, :head_pipeline_id)
add_concurrent_foreign_key(:merge_requests, :ci_pipelines,
column: :head_pipeline_id, on_delete: :nullify)
end
end
def down
if foreign_key_exists?(:merge_requests, :head_pipeline_id)
remove_foreign_key(:merge_requests, column: :head_pipeline_id)
end
end
private
def foreign_key_exists?(table, column)
foreign_keys(table).any? do |key|
key.options[:column] == column.to_s
end
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170707184244) do ActiveRecord::Schema.define(version: 20170713104829) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -1615,6 +1615,7 @@ ActiveRecord::Schema.define(version: 20170707184244) do ...@@ -1615,6 +1615,7 @@ ActiveRecord::Schema.define(version: 20170707184244) do
add_foreign_key "merge_request_diffs", "merge_requests", name: "fk_8483f3258f", on_delete: :cascade add_foreign_key "merge_request_diffs", "merge_requests", name: "fk_8483f3258f", on_delete: :cascade
add_foreign_key "merge_request_metrics", "ci_pipelines", column: "pipeline_id", on_delete: :cascade add_foreign_key "merge_request_metrics", "ci_pipelines", column: "pipeline_id", on_delete: :cascade
add_foreign_key "merge_request_metrics", "merge_requests", on_delete: :cascade add_foreign_key "merge_request_metrics", "merge_requests", on_delete: :cascade
add_foreign_key "merge_requests", "ci_pipelines", column: "head_pipeline_id", name: "fk_fd82eae0b9", on_delete: :nullify
add_foreign_key "merge_requests", "projects", column: "target_project_id", name: "fk_a6963e8447", on_delete: :cascade add_foreign_key "merge_requests", "projects", column: "target_project_id", name: "fk_a6963e8447", on_delete: :cascade
add_foreign_key "merge_requests_closing_issues", "issues", on_delete: :cascade add_foreign_key "merge_requests_closing_issues", "issues", on_delete: :cascade
add_foreign_key "merge_requests_closing_issues", "merge_requests", on_delete: :cascade add_foreign_key "merge_requests_closing_issues", "merge_requests", on_delete: :cascade
......
...@@ -50,8 +50,7 @@ Shortcuts to GitLab's most visited docs: ...@@ -50,8 +50,7 @@ Shortcuts to GitLab's most visited docs:
- [Fork a project](gitlab-basics/fork-project.md) - [Fork a project](gitlab-basics/fork-project.md)
- [Importing and exporting projects between instances](user/project/settings/import_export.md). - [Importing and exporting projects between instances](user/project/settings/import_export.md).
- [Project access](public_access/public_access.md): Setting up your project's visibility to public, internal, or private. - [Project access](public_access/public_access.md): Setting up your project's visibility to public, internal, or private.
- [Groups](workflow/groups.md): Organize your projects in groups. - [Groups](user/group/index.md): Organize your projects in groups.
- [Create a group](gitlab-basics/create-group.md)
- [GitLab Subgroups](user/group/subgroups/index.md) - [GitLab Subgroups](user/group/subgroups/index.md)
- [Search through GitLab](user/search/index.md): Search for issues, merge requests, projects, groups, todos, and issues in Issue Boards. - [Search through GitLab](user/search/index.md): Search for issues, merge requests, projects, groups, todos, and issues in Issue Boards.
- [Snippets](user/snippets.md): Snippets allow you to create little bits of code. - [Snippets](user/snippets.md): Snippets allow you to create little bits of code.
......
...@@ -228,9 +228,14 @@ Tip: If you want to limit access to the nested members of an Active Directory ...@@ -228,9 +228,14 @@ Tip: If you want to limit access to the nested members of an Active Directory
group you can use the following syntax: group you can use the following syntax:
``` ```
(memberOf=CN=My Group,DC=Example,DC=com) (memberOf:1.2.840.113556.1.4.1941=CN=My Group,DC=Example,DC=com)
``` ```
Find more information about this "LDAP_MATCHING_RULE_IN_CHAIN" filter at
https://msdn.microsoft.com/en-us/library/aa746475(v=vs.85).aspx. Support for
nested members in the user filter should not be confused with
[group sync nested groups support (EE only)](https://docs.gitlab.com/ee/administration/auth/ldap-ee.html#supported-ldap-group-types-attributes).
Please note that GitLab does not support the custom filter syntax used by Please note that GitLab does not support the custom filter syntax used by
omniauth-ldap. omniauth-ldap.
......
# IP whitelist
> Introduced in GitLab 9.4.
GitLab provides some [monitoring endpoints] that provide health check information
when probed.
To control access to those endpoints via IP whitelisting, you can add single
hosts or use IP ranges:
**For Omnibus installations**
1. Open `/etc/gitlab/gitlab.rb` and add or uncomment the following:
```ruby
gitlab_rails['monitoring_whitelist'] = ['127.0.0.0/8', '192.168.0.1']
```
1. Save the file and [reconfigure] GitLab for the changes to take effect.
---
**For installations from source**
1. Edit `config/gitlab.yml`:
```yaml
monitoring:
# by default only local IPs are allowed to access monitoring resources
ip_whitelist:
- 127.0.0.0/8
- 192.168.0.1
```
1. Save the file and [restart] GitLab for the changes to take effect.
[reconfigure]: ../restart_gitlab.md#omnibus-gitlab-reconfigure
[restart]: ../restart_gitlab.md#installations-from-source
[monitoring endpoints]: ../../user/admin_area/monitoring/health_check.md
# GitLab Prometheus metrics # GitLab Prometheus metrics
>**Note:** >**Note:**
Available since [Omnibus GitLab 9.3][29118]. Currently experimental. For installations from source Available since [Omnibus GitLab 9.3][29118]. Currently experimental. For
you'll have to configure it yourself. installations from source you'll have to configure it yourself.
GitLab monitors its own internal service metrics, and makes them available at the `/-/metrics` endpoint. Unlike other [Prometheus] exporters, this endpoint requires authentication as it is available on the same URL and port as user traffic.
To enable the GitLab Prometheus metrics: To enable the GitLab Prometheus metrics:
...@@ -15,9 +13,14 @@ To enable the GitLab Prometheus metrics: ...@@ -15,9 +13,14 @@ To enable the GitLab Prometheus metrics:
## Collecting the metrics ## Collecting the metrics
Since the metrics endpoint is available on the same host and port as other traffic, it requires authentication. The token and URL to access is displayed on the [Health Check][health-check] page. GitLab monitors its own internal service metrics, and makes them available at the
`/-/metrics` endpoint. Unlike other [Prometheus] exporters, in order to access
it, the client IP needs to be [included in a whitelist][whitelist].
Currently the embedded Prometheus server is not automatically configured to collect metrics from this endpoint. We recommend setting up another Prometheus server, because the embedded server configuration is overwritten one every reconfigure of GitLab. In the future this will not be required. Currently the embedded Prometheus server is not automatically configured to
collect metrics from this endpoint. We recommend setting up another Prometheus
server, because the embedded server configuration is overwritten once every
[reconfigure of GitLab][reconfigure]. In the future this will not be required.
## Metrics available ## Metrics available
...@@ -47,4 +50,5 @@ In this experimental phase, only a few metrics are available: ...@@ -47,4 +50,5 @@ In this experimental phase, only a few metrics are available:
[29118]: https://gitlab.com/gitlab-org/gitlab-ce/issues/29118 [29118]: https://gitlab.com/gitlab-org/gitlab-ce/issues/29118
[Prometheus]: https://prometheus.io [Prometheus]: https://prometheus.io
[restart]: ../../restart_gitlab.md#omnibus-gitlab-restart [restart]: ../../restart_gitlab.md#omnibus-gitlab-restart
[health-check]: ../../../user/admin_area/monitoring/health_check.md [whitelist]: ../ip_whitelist.md
[reconfigure]: ../../restart_gitlab.md#omnibus-gitlab-reconfigure
...@@ -95,8 +95,9 @@ Sample Prometheus queries: ...@@ -95,8 +95,9 @@ Sample Prometheus queries:
## Configuring Prometheus to monitor Kubernetes ## Configuring Prometheus to monitor Kubernetes
> Introduced in GitLab 9.0. > Introduced in GitLab 9.0.
> Pod monitoring introduced in GitLab 9.4.
If your GitLab server is running within Kubernetes, Prometheus will collect metrics from the Nodes in the cluster including performance data on each container. This is particularly helpful if your CI/CD environments run in the same cluster, as you can use the [Prometheus project integration][] to monitor them. If your GitLab server is running within Kubernetes, Prometheus will collect metrics from the Nodes and [annotated Pods](https://prometheus.io/docs/operating/configuration/#<kubernetes_sd_config>) in the cluster, including performance data on each container. This is particularly helpful if your CI/CD environments run in the same cluster, as you can use the [Prometheus project integration][] to monitor them.
To disable the monitoring of Kubernetes: To disable the monitoring of Kubernetes:
......
...@@ -388,8 +388,8 @@ the style below as a guide: ...@@ -388,8 +388,8 @@ the style below as a guide:
1. Save the file and [restart] GitLab for the changes to take effect. 1. Save the file and [restart] GitLab for the changes to take effect.
[reconfigure]: path/to/administration/gitlab_restart.md#omnibus-gitlab-reconfigure [reconfigure]: path/to/administration/restart_gitlab.md#omnibus-gitlab-reconfigure
[restart]: path/to/administration/gitlab_restart.md#installations-from-source [restart]: path/to/administration/restart_gitlab.md#installations-from-source
```` ````
In this case: In this case:
......
...@@ -6,7 +6,7 @@ Step-by-step guides on the basics of working with Git and GitLab. ...@@ -6,7 +6,7 @@ Step-by-step guides on the basics of working with Git and GitLab.
- [Start using Git on the command line](start-using-git.md) - [Start using Git on the command line](start-using-git.md)
- [Create and add your SSH Keys](create-your-ssh-keys.md) - [Create and add your SSH Keys](create-your-ssh-keys.md)
- [Create a project](create-project.md) - [Create a project](create-project.md)
- [Create a group](create-group.md) - [Create a group](../user/group/index.md#create-a-new-group)
- [Create a branch](create-branch.md) - [Create a branch](create-branch.md)
- [Fork a project](fork-project.md) - [Fork a project](fork-project.md)
- [Add a file](add-file.md) - [Add a file](add-file.md)
......
# How to create a group in GitLab
Your projects in GitLab can be organized in 2 different ways: This document was moved to [another location](../user/group/index.md#create-a-new-group).
under your own namespace for single projects, such as `your-name/project-1` or
under groups.
If you organize your projects under a group, it works like a folder. You can
manage your group members' permissions and access to the projects.
---
To create a group:
1. Expand the left sidebar by clicking the three bars at the upper left corner
and then navigate to **Groups**.
![Go to groups](img/create_new_group_sidebar.png)
1. Once in your groups dashboard, click on **New group**.
![Create new group information](img/create_new_group_info.png)
1. Fill out the needed information:
1. Set the "Group path" which will be the namespace under which your projects
will be hosted (path can contain only letters, digits, underscores, dashes
and dots; it cannot start with dashes or end in dot).
1. The "Group name" will populate with the path. Optionally, you can change
it. This is the name that will display in the group views.
1. Optionally, you can add a description so that others can briefly understand
what this group is about.
1. Optionally, choose and avatar for your project.
1. Choose the [visibility level](../public_access/public_access.md).
1. Finally, click the **Create group** button.
## Add a new project to a group
There are 2 different ways to add a new project to a group:
- Select a group and then click on the **New project** button.
![New project](img/create_new_project_from_group.png)
You can then continue on [creating a project](create-project.md).
- While you are [creating a project](create-project.md), select a group namespace
you've already created from the dropdown menu.
![Select group](img/select_group_dropdown.png)
...@@ -2,13 +2,13 @@ ...@@ -2,13 +2,13 @@
![GCP landing page](img/gcp_landing.png) ![GCP landing page](img/gcp_landing.png)
>**Important note:**
GitLab has no official images in Google Cloud Platform yet. This guide serves
as a template for when the GitLab VM will be available.
The fastest way to get started on [Google Cloud Platform (GCP)][gcp] is through The fastest way to get started on [Google Cloud Platform (GCP)][gcp] is through
the [Google Cloud Launcher][launcher] program. the [Google Cloud Launcher][launcher] program.
GitLab's official Google Launcher apps:
1. [GitLab Community Edition](https://console.cloud.google.com/launcher/details/gitlab-public/gitlab-community-edition?project=gitlab-public)
2. [GitLab Enterprise Edition](https://console.cloud.google.com/launcher/details/gitlab-public/gitlab-enterprise-edition?project=gitlab-public)
## Prerequisites ## Prerequisites
There are only two prerequisites in order to install GitLab on GCP: There are only two prerequisites in order to install GitLab on GCP:
......
...@@ -157,8 +157,7 @@ configuration file may contain syntax errors. The block name ...@@ -157,8 +157,7 @@ configuration file may contain syntax errors. The block name
file, should be `[[storage]]` instead. file, should be `[[storage]]` instead.
```shell ```shell
cd /home/git/gitaly sudo -u git -H sed -i.pre-9.4 's/\[\[storages\]\]/[[storage]]/' /home/git/gitaly/config.toml
sudo -u git -H editor config.toml
``` ```
#### Compile Gitaly #### Compile Gitaly
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
be deprecated in GitLab 9.1. Read more in the [old behavior](#old-behavior) be deprecated in GitLab 9.1. Read more in the [old behavior](#old-behavior)
section. section.
- [Access token](#access-token) has been deprecated in GitLab 9.4 - [Access token](#access-token) has been deprecated in GitLab 9.4
in favor of [IP Whitelist](#ip-whitelist) in favor of [IP whitelist](#ip-whitelist)
GitLab provides liveness and readiness probes to indicate service health and GitLab provides liveness and readiness probes to indicate service health and
reachability to required services. These probes report on the status of the reachability to required services. These probes report on the status of the
...@@ -14,109 +14,101 @@ database connection, Redis connection, and access to the filesystem. These ...@@ -14,109 +14,101 @@ database connection, Redis connection, and access to the filesystem. These
endpoints [can be provided to schedulers like Kubernetes][kubernetes] to hold endpoints [can be provided to schedulers like Kubernetes][kubernetes] to hold
traffic until the system is ready or restart the container as needed. traffic until the system is ready or restart the container as needed.
## IP Whitelist ## IP whitelist
To access monitoring resources the client IP needs to be included in the whitelist. To access monitoring resources, the client IP needs to be included in a whitelist.
To add or remove hosts or IP ranges from the list you can edit `gitlab.rb` or `gitlab.yml`.
Example whitelist configuration: [Read how to add IPs to a whitelist for the monitoring endpoints.][admin].
```yaml
monitoring:
ip_whitelist:
- 127.0.0.0/8 # by default only local IPs are allowed to access monitoring resources
```
## Access Token (Deprecated) ## Using the endpoint
An access token needs to be provided while accessing the probe endpoints. The current With default whitelist settings, the probes can be accessed from localhost:
accepted token can be found under the **Admin area ➔ Monitoring ➔ Health check**
(`admin/health_check`) page of your GitLab instance.
![access token](img/health_check_token.png) - `http://localhost/-/readiness`
- `http://localhost/-/liveness`
The access token can be passed as a URL parameter: which will then provide a report of system health in JSON format.
Readiness example output:
``` ```
https://gitlab.example.com/-/readiness?token=ACCESS_TOKEN {
"queues_check" : {
"status" : "ok"
},
"redis_check" : {
"status" : "ok"
},
"shared_state_check" : {
"status" : "ok"
},
"fs_shards_check" : {
"labels" : {
"shard" : "default"
},
"status" : "ok"
},
"db_check" : {
"status" : "ok"
},
"cache_check" : {
"status" : "ok"
}
}
``` ```
which will then provide a report of system health in JSON format: Liveness example output:
``` ```
{ {
"db_check": { "fs_shards_check" : {
"status": "ok" "status" : "ok"
}, },
"redis_check": { "cache_check" : {
"status": "ok" "status" : "ok"
}, },
"fs_shards_check": { "db_check" : {
"status": "ok", "status" : "ok"
"labels": { },
"shard": "default" "redis_check" : {
} "status" : "ok"
},
"queues_check" : {
"status" : "ok"
},
"shared_state_check" : {
"status" : "ok"
} }
} }
``` ```
## Using the Endpoint
With default whitelist settings, the probes can be accessed from localhost:
- `http://localhost/-/readiness`
- `http://localhost/-/liveness`
## Status ## Status
On failure, the endpoint will return a `500` HTTP status code. On success, the endpoint On failure, the endpoint will return a `500` HTTP status code. On success, the endpoint
will return a valid successful HTTP status code, and a `success` message. will return a valid successful HTTP status code, and a `success` message.
## Old behavior ## Access token (Deprecated)
>**Notes:**
- Liveness and readiness probes were [introduced][ce-10416] in GitLab 9.1.
- The `health_check` endpoint was [introduced][ce-3888] in GitLab 8.8 and will
be deprecated in GitLab 9.1. Read more in the [old behavior](#old-behavior)
section.
GitLab provides a health check endpoint for uptime monitoring on the `health_check` web
endpoint. The health check reports on the overall system status based on the status of
the database connection, the state of the database migrations, and the ability to write
and access the cache. This endpoint can be provided to uptime monitoring services like
[Pingdom][pingdom], [Nagios][nagios-health], and [NewRelic][newrelic-health].
Once you have the [access token](#access-token) or your client IP is [whitelisted](#ip-whitelist), >**Note:**
health information can be retrieved as plain text, JSON, or XML using the `health_check` endpoint: Access token has been deprecated in GitLab 9.4
in favor of [IP whitelist](#ip-whitelist)
- `https://gitlab.example.com/health_check?token=ACCESS_TOKEN` An access token needs to be provided while accessing the probe endpoints. The current
- `https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN` accepted token can be found under the **Admin area ➔ Monitoring ➔ Health check**
- `https://gitlab.example.com/health_check.xml?token=ACCESS_TOKEN` (`admin/health_check`) page of your GitLab instance.
You can also ask for the status of specific services:
- `https://gitlab.example.com/health_check/cache.json?token=ACCESS_TOKEN`
- `https://gitlab.example.com/health_check/database.json?token=ACCESS_TOKEN`
- `https://gitlab.example.com/health_check/migrations.json?token=ACCESS_TOKEN`
For example, the JSON output of the following health check:
```bash ![access token](img/health_check_token.png)
curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json
```
would be like: The access token can be passed as a URL parameter:
``` ```
{"healthy":true,"message":"success"} https://gitlab.example.com/-/readiness?token=ACCESS_TOKEN
``` ```
On failure, the endpoint will return a `500` HTTP status code. On success, the endpoint
will return a valid successful HTTP status code, and a `success` message. Ideally your
uptime monitoring should look for the success message.
[ce-10416]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10416 [ce-10416]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10416
[ce-3888]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3888 [ce-3888]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3888
[pingdom]: https://www.pingdom.com [pingdom]: https://www.pingdom.com
[nagios-health]: https://nagios-plugins.org/doc/man/check_http.html [nagios-health]: https://nagios-plugins.org/doc/man/check_http.html
[newrelic-health]: https://docs.newrelic.com/docs/alerts/alert-policies/downtime-alerts/availability-monitoring [newrelic-health]: https://docs.newrelic.com/docs/alerts/alert-policies/downtime-alerts/availability-monitoring
[kubernetes]: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/ [kubernetes]: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/
[admin]: ../../../administration/monitoring/ip_whitelist.md
# Groups
With GitLab Groups you can assemble related projects together
and grant members access to several projects at once.
Groups can also be nested in [subgroups](subgroups/index.md).
Find your groups by expanding the left menu and clicking **Groups**:
![GitLab Groups](img/groups.png)
The Groups page displays all groups you are a member of, how many projects it holds,
how many members it has, the group visibility, and, if you have enough permissions,
a link to the group settings. By clicking the last button you can leave that group.
## Use cases
You can create groups for numerous reasons. To name a few:
- Organize related projects under the same [namespace](#namespaces), add members to that
group and grant access to all their projects at once
- Create a group, include members of your team, and make it easier to
`@mention` all the team at once in issues and merge requests
- Create a group for your company members, and create [subgroups](subgroups/index.md)
for each individual team. Let's say you create a group called `company-team`, and among others,
you created subgroups in this group for each individual team `backend-team`,
`frontend-team`, and `production-team`:
1. When you start a new implementation from an issue, you add a comment:
_"`@company-team`, let's do it! `@company-team/backend-team` you're good to go!"_
1. When your backend team needs help from frontend, they add a comment:
_"`@company-team/frontend-team` could you help us here please?"_
1. When the frontend team completes their implementation, they comment:
_"`@company-team/backend-team`, it's done! Let's ship it `@company-team/production-team`!"_
## Namespaces
In GitLab, a namespace is a unique name to be used as a user name, a group name, or a subgroup name.
- `http://gitlab.example.com/username`
- `http://gitlab.example.com/groupname`
- `http://gitlab.example.com/groupname/subgroup_name`
For example, consider a user called John:
1. John creates his account on GitLab.com with the username `jonh`;
his profile will be accessed under `https://gitlab.example.com/john`
1. John creates a group for his team with the groupname `john-team`;
his group and its projects will be accessed under `https://gitlab.example.com/john-team`
1. John creates a subgroup of `john-team` with the subgroup name `marketing`;
his subgroup and its projects will be accessed under `https://gitlab.example.com/john-team/marketing`
By doing so:
- Any team member mentions John with `@john`
- John mentions everyone from his team with `@john-team`
- John mentions only his marketing team with `@john-team/marketing`
## Create a new group
You can create a group in GitLab from:
1. The Groups page: expand the left menu, click **Groups**, and click the green button **New group**:
![new group from groups page](img/new_group_from_groups.png)
1. Elsewhere: expand the `plus` sign button on the top navbar and choose **New group**:
![new group from elsewhere](img/new_group_from_other_pages.png)
Add the following information:
![new group info](img/create_new_group_info.png)
1. Set the **Group path** which will be the **namespace** under which your projects
will be hosted (path can contain only letters, digits, underscores, dashes
and dots; it cannot start with dashes or end in dot).
1. The **Group name** will populate with the path. Optionally, you can change
it. This is the name that will display in the group views.
1. Optionally, you can add a description so that others can briefly understand
what this group is about.
1. Optionally, choose an avatar for your project.
1. Choose the [visibility level](../../public_access/public_access.md).
## Add users to a group
Add members to a group by navigating to the group's dashboard, and clicking **Members**:
![add members to group](img/add_new_members.png)
Select the [permission level][permissions] and add the new member. You can also set the expiring
date for that user, from which they will no longer have access to your group.
One of the benefits of putting multiple projects in one group is that you can
give a user to access to all projects in the group with one action.
Consider we have a group with two projects:
- On the **Group Members** page we can now add a new user to the group.
- Now because this user is a **Developer** member of the group, he automatically
gets **Developer** access to **all projects** within that group.
If necessary, you can increase the access level of an individual user for a specific project,
by adding them again as a new member to the project with the new permission levels.
## Request access to a group
As a group owner you can enable or disable non members to request access to
your group. Go to the group settings and click on **Allow users to request access**.
As a user, you can request to be a member of a group. Go to the group you'd
like to be a member of, and click the **Request Access** button on the right
side of your screen.
![Request access button](img/request_access_button.png)
---
Group owners and masters will be notified of your request and will be able to approve or
decline it on the members page.
![Manage access requests](img/access_requests_management.png)
---
If you change your mind before your request is approved, just click the
**Withdraw Access Request** button.
![Withdraw access request button](img/withdraw_access_request_button.png)
## Add projects to a group
There are two different ways to add a new project to a group:
- Select a group and then click on the **New project** button.
![New project](img/create_new_project_from_group.png)
You can then continue on [creating a project](../../gitlab-basics/create-project.md).
- While you are creating a project, select a group namespace
you've already created from the dropdown menu.
![Select group](img/select_group_dropdown.png)
## Transfer an existing project into a group
You can transfer an existing project into a group as long as you have at least **Master** [permissions][permissions] to that group
and if you are an **Owner** of the project.
![Transfer a project to a new namespace](img/transfer_project_to_other_group.png)
Find this option under your project's settings.
GitLab administrators can use the admin interface to move any project to any namespace if needed.
## Manage group memberships via LDAP
In GitLab Enterprise Edition it is possible to manage GitLab group memberships using LDAP groups.
See [the GitLab Enterprise Edition documentation](../../integration/ldap.md) for more information.
## Group settings
Once you have created a group, you can manage its settings by navigating to
the group's dashboard, and clicking **Settings**.
![group settings](img/group_settings.png)
### General settings
Besides giving you the option to edit any settings you've previously
set when [creating the group](#create-a-new-group), you can also
access further configurations for your group.
#### Enforce 2FA to group members
Add a secury layer to your group by
[enforcing two-factor authentication (2FA)](../../security/two_factor_authentication.md#enforcing-2fa-for-all-users-in-a-group)
to all group members.
#### Member Lock (EES/EEP)
Available in [GitLab Enterprise Edition Starter](https://about.gitlab.com/gitlab-ee/),
with **Member Lock** it is possible to lock membership in project to the
level of members in group.
Learn more about [Member Lock](https://docs.gitlab.com/ee/user/group/index.html#member-lock-ees-eep).
#### Share with group lock (EES/EEP)
In [GitLab Enterprise Edition Starter](https://about.gitlab.com/gitlab-ee/)
it is possible to prevent projects in a group from [sharing
a project with another group](../../workflow/share_projects_with_other_groups.md).
This allows for tighter control over project access.
Learn more about [Share with group lock](https://docs.gitlab.com/ee/user/group/index.html#share-with-group-lock-ees-eep).
### Advanced settings
- **Projects**: view all projects within that group, add members to each project,
access each project's settings, and remove any project from the same screen.
- **Webhooks**: configure [webhooks](../project/integrations/webhooks.md)
and [push rules](https://docs.gitlab.com/ee/push_rules/push_rules.html#push-rules) to your group (Push Rules is available in [GitLab Enteprise Edition Starter][ee].)
- **Audit Events**: view [Audit Events](https://docs.gitlab.com/ee/administration/audit_events.html#audit-events)
for the group (GitLab admins only, available in [GitLab Enterprise Edition Starter][ee]).
- **Pipelines quota**: keep track of the [pipeline quota](../admin_area/settings/continuous_integration.md) for the group
[permissions]: ../permissions.md#permissions
[ee]: https://about.gitlab.com/products/
\ No newline at end of file
...@@ -40,7 +40,7 @@ Installing and configuring Prometheus to monitor applications is fairly straight ...@@ -40,7 +40,7 @@ Installing and configuring Prometheus to monitor applications is fairly straight
### Configuring Omnibus GitLab Prometheus to monitor Kubernetes deployments ### Configuring Omnibus GitLab Prometheus to monitor Kubernetes deployments
With Omnibus GitLab running inside of Kubernetes, you can leverage the bundled With Omnibus GitLab running inside of Kubernetes, you can leverage the bundled
version of Prometheus to collect the required metrics. Once enabled, Prometheus will version of Prometheus to collect the supported metrics. Once enabled, Prometheus will automatically begin monitoring Kubernetes Nodes and any [annotated Pods](https://prometheus.io/docs/operating/configuration/#<kubernetes_sd_config>).
1. Read how to configure the bundled Prometheus server in the 1. Read how to configure the bundled Prometheus server in the
[Administration guide][gitlab-prometheus-k8s-monitor]. [Administration guide][gitlab-prometheus-k8s-monitor].
......
...@@ -5,13 +5,13 @@ for tracking the evolution of a new idea or the process ...@@ -5,13 +5,13 @@ for tracking the evolution of a new idea or the process
of solving a problem. of solving a problem.
It allows you, your team, and your collaborators to share It allows you, your team, and your collaborators to share
and discuss proposals, before and while implementing them. and discuss proposals before and while implementing them.
Issues and the GitLab Issue Tracker are available in all Issues and the GitLab Issue Tracker are available in all
[GitLab Products](https://about.gitlab.com/products/) as [GitLab Products](https://about.gitlab.com/products/) as
part of the [GitLab Workflow](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/). part of the [GitLab Workflow](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/).
## Use-Cases ## Use cases
Issues can have endless applications. Just to exemplify, these are Issues can have endless applications. Just to exemplify, these are
some cases for which creating issues are most used: some cases for which creating issues are most used:
...@@ -23,7 +23,28 @@ some cases for which creating issues are most used: ...@@ -23,7 +23,28 @@ some cases for which creating issues are most used:
- Obtaining support - Obtaining support
- Elaborating new code implementations - Elaborating new code implementations
See also the blog post [Always start a discussion with an issue](https://about.gitlab.com/2016/03/03/start-with-an-issue/). See also the blog post "[Always start a discussion with an issue](https://about.gitlab.com/2016/03/03/start-with-an-issue/)".
### Keep private things private
For instance, let's assume you have a public project but want to start a discussion on something
you don't want to be public. With [Confidential Issues](#confidential-issues),
you can discuss private matters among the project members, and still keep
your project public, open to collaboration.
### Streamline collaboration
With [Multiple Assignees for Issues](https://docs.gitlab.com/ee/user/project/issues/multiple_assignees_for_issues.html),
available in [GitLab Enterprise Edition Starter](https://about.gitlab.com/gitlab-ee/)
you can streamline collaboration and allow shared responsibilities to be clearly displayed.
All assignees are shown across your workflows and receive notifications (as they
would as single assignees), simplifying communication and ownership.
### Consistent collaboration
Create [issue templates](#issue-templates) to make collaboration consistent and
containing all information you need. For example, you can create a template
for feature proposals and another one for bug reports.
## Issue Tracker ## Issue Tracker
...@@ -96,8 +117,8 @@ Find GitLab Issue Boards by navigating to your **Project's Dashboard** > **Issue ...@@ -96,8 +117,8 @@ Find GitLab Issue Boards by navigating to your **Project's Dashboard** > **Issue
Read through the documentation for [Issue Boards](../issue_board.md) Read through the documentation for [Issue Boards](../issue_board.md)
to find out more about this feature. to find out more about this feature.
[Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards) With [GitLab Enterprise Edition Starter](https://about.gitlab.com/gitlab-ee/), you can also
are available only in [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/). create various boards per project with [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards).
### Issue's API ### Issue's API
......
...@@ -43,7 +43,7 @@ assigned to them if they created the issue themselves. ...@@ -43,7 +43,7 @@ assigned to them if they created the issue themselves.
##### 3.1. Multiple Assignees (EES/EEP) ##### 3.1. Multiple Assignees (EES/EEP)
Issue Weights are only available in [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/). Multiple Assignees are only available in [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/).
Often multiple people likely work on the same issue together, Often multiple people likely work on the same issue together,
which can especially be difficult to track in large teams which can especially be difficult to track in large teams
...@@ -52,9 +52,7 @@ where there is shared ownership of an issue. ...@@ -52,9 +52,7 @@ where there is shared ownership of an issue.
In GitLab Enterprise Edition, you can also select multiple assignees In GitLab Enterprise Edition, you can also select multiple assignees
to an issue. to an issue.
> **Note:** Learn more on the [Multiple Assignees documentation](https://docs.gitlab.com/ee/user/project/issues/multiple_assignees_for_issues.html).
Multiple Assignees was [introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/1904)
in [GitLab Enterprise Edition 9.2](https://about.gitlab.com/2017/05/22/gitlab-9-2-released/#multiple-assignees-for-issues).
#### 4. Milestone #### 4. Milestone
......
...@@ -3,6 +3,59 @@ ...@@ -3,6 +3,59 @@
Merge requests allow you to exchange changes you made to source code and Merge requests allow you to exchange changes you made to source code and
collaborate with other people on the same project. collaborate with other people on the same project.
## Overview
A Merge Request (**MR**) is the basis of GitLab as a code collaboration
and version control platform.
Is it simple as the name implies: a _request_ to _merge_ one branch into another.
With GitLab merge requests, you can:
- Compare the changes between two [branches](https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell#_git_branching)
- [Review and discuss](../../discussions/index.md#discussions) the proposed modifications inline
- Live preview the changes when [Review Apps](../../../ci/review_apps/index.md) is configured for your project
- Build, test, and deploy your code in a per-branch basis with built-in [GitLab CI/CD](../../../ci/README.md)
- Prevent the merge request from being merged before it's ready with [WIP MRs](#work-in-progress-merge-requests)
- View the deployment process through [Pipeline Graphs](../../../ci/pipelines.md#pipeline-graphs)
- [Automatically close the issue(s)](../../project/issues/closing_issues.md#via-merge-request) that originated the implementation proposed in the merge request
- Assign it to any registered user, and change the assignee how many times you need
- Assign a [milestone](../../project/milestones/index.md) and track the development of a broader implementation
- Organize your issues and merge requests consistently throughout the project with [labels](../../project/labels.md)
- Add a time estimation and the time spent with that merge request with [Time Tracking](../../../workflow/time_tracking.html#time-tracking)
- [Resolve merge conflicts from the UI](#resolve-conflicts)
With **[GitLab Enterprise Edition][ee]**, you can also:
- View the deployment process across projects with [Multi-Project Pipeline Graphs](https://docs.gitlab.com/ee/ci/multi_project_pipeline_graphs.html#multi-project-pipeline-graphs) (available only in GitLab Enterprise Edition Premium)
- Request [approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your managers (available in GitLab Enterprise Edition Starter)
- Enable [fast-forward merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/fast_forward_merge.html) (available in GitLab Enterprise Edition Starter)
- [Squash and merge](https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html) for a cleaner commit history (available in GitLab Enterprise Edition Starter)
- Enable [semi-linear history merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/index.html#semi-linear-history-merge-requests) as another security layer to guarantee the pipeline is passing in the target branch (available in GitLab Enterprise Edition Starter)
- Analise the impact of your changes with [Code Quality reports](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html) (available in GitLab Enterprise Edition Starter)
## Use cases
A. Consider you are a software developer working in a team:
1. You checkout a new branch, and submit your changes through a merge request
1. You gather feedback from your team
1. You work on the implementation optimizing code with [Code Quality reports](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html) (available in GitLab Enterprise Edition Starter)
1. You build and test your changes with GitLab CI/CD
1. You request the approval from your manager
1. Your manager pushes a commit with his final review, [approves the merge request](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html), and set it to [merge when pipeline succeeds](#merge-when-pipeline-succeeds) (Merge Request Approvals are available in GitLab Enterprise Edition Starter)
1. Your changes get deployed to production with [manual actions](../../../ci/yaml/README.md#manual-actions) for GitLab CI/CD
1. Your implementations were successfully shipped to your customer
B. Consider you're a web developer writing a webpage for your company's:
1. You checkout a new branch, and submit a new page through a merge request
1. You gather feedback from your reviewers
1. Your changes are previewed with [Review Apps](../../../ci/review_apps/index.md)
1. You request your web designers for their implementation
1. You request the [approval](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your manager (available in GitLab Enterprise Edition Starter)
1. Once approved, your merge request is [squashed and merged](https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html), and [deployed to staging with GitLab Pages](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/) (Squash and Merge is available in GitLab Enterprise Edition Starter)
1. Your production team [cherry picks](#cherry-pick-changes) the merge commit into production
## Authorization for merge requests ## Authorization for merge requests
There are two main ways to have a merge request flow with GitLab: There are two main ways to have a merge request flow with GitLab:
...@@ -79,6 +132,16 @@ specific commit page. ...@@ -79,6 +132,16 @@ specific commit page.
You can append `?w=1` while on the diffs page of a merge request to ignore any You can append `?w=1` while on the diffs page of a merge request to ignore any
whitespace changes. whitespace changes.
## Live preview with Review Apps
If you configured [Review Apps](https://about.gitlab.com/features/review-apps/) for your project,
you can preview the changes submitted to a feature-branch through a merge request
in a per-branch basis. No need to checkout the branch, install and preview locally;
all your changes will be available to preview by anyone with the Review Apps link.
[Read more about Review Apps.](../../../ci/review_apps/index.md)
## Tips ## Tips
Here are some tips that will help you be more efficient with merge requests in Here are some tips that will help you be more efficient with merge requests in
...@@ -167,3 +230,4 @@ git checkout origin/merge-requests/1 ...@@ -167,3 +230,4 @@ git checkout origin/merge-requests/1
``` ```
[protected branches]: ../protected_branches.md [protected branches]: ../protected_branches.md
[ee]: https://about.gitlab.com/gitlab-ee/ "GitLab Enterprise Edition"
...@@ -41,7 +41,7 @@ server up and running for your GitLab instance. ...@@ -41,7 +41,7 @@ server up and running for your GitLab instance.
Before we begin, let's understand a few concepts first. Before we begin, let's understand a few concepts first.
### Static sites ## Static sites
GitLab Pages only supports static websites, meaning, GitLab Pages only supports static websites, meaning,
your output files must be HTML, CSS, and JavaScript only. your output files must be HTML, CSS, and JavaScript only.
...@@ -51,14 +51,14 @@ CSS, and JS, or use a [Static Site Generator (SSG)](https://www.staticgen.com/) ...@@ -51,14 +51,14 @@ CSS, and JS, or use a [Static Site Generator (SSG)](https://www.staticgen.com/)
to simplify your code and build the static site for you, to simplify your code and build the static site for you,
which is highly recommendable and much faster than hardcoding. which is highly recommendable and much faster than hardcoding.
#### Further Reading ### Further reading
- Read through this technical overview on [Static versus Dynamic Websites](https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/) - Read through this technical overview on [Static versus Dynamic Websites](https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/)
- Understand [how modern Static Site Generators work](https://about.gitlab.com/2016/06/10/ssg-overview-gitlab-pages-part-2/) and what you can add to your static site - Understand [how modern Static Site Generators work](https://about.gitlab.com/2016/06/10/ssg-overview-gitlab-pages-part-2/) and what you can add to your static site
- You can use [any SSG with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/) - You can use [any SSG with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/)
- Fork an [example project](https://gitlab.com/pages) to build your website based upon - Fork an [example project](https://gitlab.com/pages) to build your website based upon
### GitLab Pages domain ## GitLab Pages domain
If you set up a GitLab Pages project on GitLab.com, If you set up a GitLab Pages project on GitLab.com,
it will automatically be accessible under a it will automatically be accessible under a
...@@ -73,9 +73,9 @@ Pages wildcard domain. This guide is valid for any GitLab instance, ...@@ -73,9 +73,9 @@ Pages wildcard domain. This guide is valid for any GitLab instance,
you just need to replace Pages wildcard domain on GitLab.com you just need to replace Pages wildcard domain on GitLab.com
(`*.gitlab.io`) with your own. (`*.gitlab.io`) with your own.
#### Practical examples ### Practical examples
**Project Websites:** #### Project Websites
- You created a project called `blog` under your username `john`, - You created a project called `blog` under your username `john`,
therefore your project URL is `https://gitlab.com/john/blog/`. therefore your project URL is `https://gitlab.com/john/blog/`.
...@@ -87,16 +87,21 @@ URL is `https://gitlab.com/websites/blog/`. Once you enable ...@@ -87,16 +87,21 @@ URL is `https://gitlab.com/websites/blog/`. Once you enable
GitLab Pages for this project, the site will live under GitLab Pages for this project, the site will live under
`https://websites.gitlab.io/blog/`. `https://websites.gitlab.io/blog/`.
**User and Group Websites:** #### User and Group Websites
- Under your username, `john`, you created a project called - Under your username, `john`, you created a project called
`john.gitlab.io`. Your project URL will be `https://gitlab.com/john/john.gitlab.io`. `john.gitlab.io`. Your project URL will be `https://gitlab.com/john/john.gitlab.io`.
Once you enable GitLab Pages for your project, your website Once you enable GitLab Pages for your project, your website
will be published under `https://john.gitlab.io`. will be published under `https://john.gitlab.io`.
- Under your group `websites`, you created a project called - Under your group `websites`, you created a project called
`websites.gitlab.io`. your project's URL will be `https://gitlab.com/websites/websites.gitlab.io`. Once you enable GitLab Pages for your project, `websites.gitlab.io`. your project's URL will be `https://gitlab.com/websites/websites.gitlab.io`.
Once you enable GitLab Pages for your project,
your website will be published under `https://websites.gitlab.io`. your website will be published under `https://websites.gitlab.io`.
>**Note:**
GitLab Pages [does **not** support subgroups](../../group/subgroups/index.md#limitations).
You can only create the highest level group website.
**General example:** **General example:**
- On GitLab.com, a project site will always be available under - On GitLab.com, a project site will always be available under
......
...@@ -398,6 +398,9 @@ don't redirect HTTP to HTTPS. ...@@ -398,6 +398,9 @@ don't redirect HTTP to HTTPS.
[rfc]: https://tools.ietf.org/html/rfc2818#section-3.1 "HTTP Over TLS RFC" [rfc]: https://tools.ietf.org/html/rfc2818#section-3.1 "HTTP Over TLS RFC"
GitLab Pages [does **not** support subgroups](../../group/subgroups/index.md#limitations).
You can only create the highest level group website.
## Redirects in GitLab Pages ## Redirects in GitLab Pages
Since you cannot use any custom server configuration files, like `.htaccess` or Since you cannot use any custom server configuration files, like `.htaccess` or
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
- [Description templates](../user/project/description_templates.md) - [Description templates](../user/project/description_templates.md)
- [Feature branch workflow](workflow.md) - [Feature branch workflow](workflow.md)
- [GitLab Flow](gitlab_flow.md) - [GitLab Flow](gitlab_flow.md)
- [Groups](groups.md) - [Groups](../user/group/index.md)
- Issues - The GitLab Issue Tracker is an advanced and complete tool for - Issues - The GitLab Issue Tracker is an advanced and complete tool for
tracking the evolution of a new idea or the process of solving a problem. tracking the evolution of a new idea or the process of solving a problem.
- [Confidential issues](../user/project/issues/confidential_issues.md) - [Confidential issues](../user/project/issues/confidential_issues.md)
......
# GitLab Groups
GitLab groups allow you to group projects into directories and give users access to several projects at once. This document was moved to [another location](../user/group/index.md).
When you create a new project in GitLab, the default namespace for the project is the personal namespace associated with your GitLab user.
In this document we will see how to create groups, put projects in groups and manage who can access the projects in a group.
## Creating groups
You can create a group by going to the 'Groups' tab of the GitLab dashboard and clicking the 'New group' button.
![Click the 'New group' button in the 'Groups' tab](groups/new_group_button.png)
Next, enter the path and name (required) and the optional description and group avatar.
![Fill in the path for your new group](groups/new_group_form.png)
When your group has been created you are presented with the group dashboard feed, which will be empty.
![Group dashboard](groups/group_dashboard.png)
You can use the 'New project' button to add a project to the new group.
## Transferring an existing project into a group
You can transfer an existing project into a group you have at least Master access in from the project settings page.
The option to transfer a project is only available if you are the Owner of the project.
First scroll down to the 'Dangerous settings' and click 'Show them to me'.
Now you can pick any of the groups you have at least Master access in as the new namespace for the group.
![Transfer a project to a new namespace](groups/transfer_project.png)
GitLab administrators can use the admin interface to move any project to any namespace if needed.
## Adding users to a group
One of the benefits of putting multiple projects in one group is that you can give a user to access to all projects in the group with one action.
Suppose we have a group with two projects.
![Group with two projects](groups/group_with_two_projects.png)
On the 'Group Members' page we can now add a new user Barry to the group.
![Add user Barry to the group](groups/add_member_to_group.png)
Now because Barry is a 'Developer' member of the 'Open Source' group, he automatically gets 'Developer' access to all projects in the 'Open Source' group.
![Barry has 'Developer' access to GitLab CI](groups/project_members_via_group.png)
If necessary, you can increase the access level of an individual user for a specific project, by adding them as a Member to the project.
![Barry effectively has 'Master' access to GitLab CI now](groups/override_access_level.png)
## Requesting access to a group
As a group owner you can enable or disable non members to request access to
your group. Go to the group settings and click on **Allow users to request access**.
As a user, you can request to be a member of a group. Go to the group you'd
like to be a member of, and click the **Request Access** button on the right
side of your screen.
![Request access button](groups/request_access_button.png)
---
Group owners & masters will be notified of your request and will be able to approve or
decline it on the members page.
![Manage access requests](groups/access_requests_management.png)
---
If you change your mind before your request is approved, just click the
**Withdraw Access Request** button.
![Withdraw access request button](groups/withdraw_access_request_button.png)
## Managing group memberships via LDAP
In GitLab Enterprise Edition it is possible to manage GitLab group memberships using LDAP groups.
See [the GitLab Enterprise Edition documentation](http://docs.gitlab.com/ee/integration/ldap.html) for more information.
## Allowing only admins to create groups
By default, any GitLab user can create new groups.
This ability can be disabled for individual users from the admin panel.
It is also possible to configure GitLab so that new users default to not being able to create groups:
```
# For omnibus-gitlab, put the following in /etc/gitlab/gitlab.rb
gitlab_rails['gitlab_default_can_create_group'] = false
# For installations from source, uncomment the 'default_can_create_group'
# line in /home/git/gitlab/config/gitlab.yml
```
...@@ -5,7 +5,7 @@ to a project with a single action. ...@@ -5,7 +5,7 @@ to a project with a single action.
## Groups as collections of users ## Groups as collections of users
Groups are used primarily to [create collections of projects](groups.md), but you can also Groups are used primarily to [create collections of projects](../user/group/index.md), but you can also
take advantage of the fact that groups define collections of _users_, namely the group take advantage of the fact that groups define collections of _users_, namely the group
members. members.
......
...@@ -150,7 +150,7 @@ module API ...@@ -150,7 +150,7 @@ module API
# #
# begin # begin
# repository = wiki? ? project.wiki.repository : project.repository # repository = wiki? ? project.wiki.repository : project.repository
# Gitlab::GitalyClient::Notifications.new(repository.raw_repository).post_receive # Gitlab::GitalyClient::NotificationService.new(repository.raw_repository).post_receive
# rescue GRPC::Unavailable => e # rescue GRPC::Unavailable => e
# render_api_error!(e, 500) # render_api_error!(e, 500)
# end # end
......
...@@ -8,7 +8,12 @@ require_dependency 'declarative_policy/step' ...@@ -8,7 +8,12 @@ require_dependency 'declarative_policy/step'
require_dependency 'declarative_policy/base' require_dependency 'declarative_policy/base'
require 'thread'
module DeclarativePolicy module DeclarativePolicy
CLASS_CACHE_MUTEX = Mutex.new
CLASS_CACHE_IVAR = :@__DeclarativePolicy_CLASS_CACHE
class << self class << self
def policy_for(user, subject, opts = {}) def policy_for(user, subject, opts = {})
cache = opts[:cache] || {} cache = opts[:cache] || {}
...@@ -23,7 +28,36 @@ module DeclarativePolicy ...@@ -23,7 +28,36 @@ module DeclarativePolicy
subject = find_delegate(subject) subject = find_delegate(subject)
subject.class.ancestors.each do |klass| class_for_class(subject.class)
end
private
# This method is heavily cached because there are a lot of anonymous
# modules in play in a typical rails app, and #name performs quite
# slowly for anonymous classes and modules.
#
# See https://bugs.ruby-lang.org/issues/11119
#
# if the above bug is resolved, this caching could likely be removed.
def class_for_class(subject_class)
unless subject_class.instance_variable_defined?(CLASS_CACHE_IVAR)
CLASS_CACHE_MUTEX.synchronize do
# re-check in case of a race
break if subject_class.instance_variable_defined?(CLASS_CACHE_IVAR)
policy_class = compute_class_for_class(subject_class)
subject_class.instance_variable_set(CLASS_CACHE_IVAR, policy_class)
end
end
policy_class = subject_class.instance_variable_get(CLASS_CACHE_IVAR)
raise "no policy for #{subject.class.name}" if policy_class.nil?
policy_class
end
def compute_class_for_class(subject_class)
subject_class.ancestors.each do |klass|
next unless klass.name next unless klass.name
begin begin
...@@ -37,12 +71,8 @@ module DeclarativePolicy ...@@ -37,12 +71,8 @@ module DeclarativePolicy
nil nil
end end
end end
raise "no policy for #{subject.class.name}"
end end
private
def find_delegate(subject) def find_delegate(subject)
seen = Set.new seen = Set.new
......
...@@ -21,11 +21,14 @@ module DeclarativePolicy ...@@ -21,11 +21,14 @@ module DeclarativePolicy
private private
def id_for(obj) def id_for(obj)
if obj.respond_to?(:id) && obj.id id =
obj.id.to_s begin
else obj.id
"##{obj.object_id}" rescue NoMethodError
nil
end end
id || "##{obj.object_id}"
end end
end end
end end
......
...@@ -82,6 +82,7 @@ module DeclarativePolicy ...@@ -82,6 +82,7 @@ module DeclarativePolicy
# depending on the scope, we may cache only by the user or only by # depending on the scope, we may cache only by the user or only by
# the subject, resulting in sharing across different policy objects. # the subject, resulting in sharing across different policy objects.
def cache_key def cache_key
@cache_key ||=
case @condition.scope case @condition.scope
when :normal then "/dp/condition/#{@condition.key}/#{user_key},#{subject_key}" when :normal then "/dp/condition/#{@condition.key}/#{user_key},#{subject_key}"
when :user then "/dp/condition/#{@condition.key}/#{user_key}" when :user then "/dp/condition/#{@condition.key}/#{user_key}"
......
module Gitlab module Gitlab
module BackgroundMigration module BackgroundMigration
def self.queue
@queue ||= BackgroundMigrationWorker.sidekiq_options['queue']
end
# Begins stealing jobs from the background migrations queue, blocking the # Begins stealing jobs from the background migrations queue, blocking the
# caller until all jobs have been completed. # caller until all jobs have been completed.
# #
# When a migration raises a StandardError is is going to be retries up to
# three times, for example, to recover from a deadlock.
#
# When Exception is being raised, it enqueues the migration again, and
# re-raises the exception.
#
# steal_class - The name of the class for which to steal jobs. # steal_class - The name of the class for which to steal jobs.
def self.steal(steal_class) def self.steal(steal_class)
queue = Sidekiq::Queue enqueued = Sidekiq::Queue.new(self.queue)
.new(BackgroundMigrationWorker.sidekiq_options['queue']) scheduled = Sidekiq::ScheduledSet.new
[scheduled, enqueued].each do |queue|
queue.each do |job| queue.each do |job|
migration_class, migration_args = job.args migration_class, migration_args = job.args
next unless job.queue == self.queue
next unless migration_class == steal_class next unless migration_class == steal_class
perform(migration_class, migration_args) begin
perform(migration_class, migration_args, retries: 3) if job.delete
rescue Exception # rubocop:disable Lint/RescueException
BackgroundMigrationWorker # enqueue this migration again
.perform_async(migration_class, migration_args)
job.delete raise
end
end
end end
end end
##
# Performs a background migration.
#
# class_name - The name of the background migration class as defined in the # class_name - The name of the background migration class as defined in the
# Gitlab::BackgroundMigration namespace. # Gitlab::BackgroundMigration namespace.
# #
......
module Gitlab
module Cache
# This module provides a simple way to cache values in RequestStore,
# and the cache key would be based on the class name, method name,
# optionally customized instance level values, optionally customized
# method level values, and optional method arguments.
#
# A simple example:
#
# class UserAccess
# extend Gitlab::Cache::RequestCache
#
# request_cache_key do
# [user&.id, project&.id]
# end
#
# request_cache def can_push_to_branch?(ref)
# # ...
# end
# end
#
# This way, the result of `can_push_to_branch?` would be cached in
# `RequestStore.store` based on the cache key. If RequestStore is not
# currently active, then it would be stored in a hash saved in an
# instance variable, so the cache logic would be the same.
# Here's another example using customized method level values:
#
# class Commit
# extend Gitlab::Cache::RequestCache
#
# def author
# User.find_by_any_email(author_email.downcase)
# end
# request_cache(:author) { author_email.downcase }
# end
#
# So that we could have different strategies for different methods
#
module RequestCache
def self.extended(klass)
return if klass < self
extension = Module.new
klass.const_set(:RequestCacheExtension, extension)
klass.prepend(extension)
end
def request_cache_key(&block)
if block_given?
@request_cache_key = block
else
@request_cache_key
end
end
def request_cache(method_name, &method_key_block)
const_get(:RequestCacheExtension).module_eval do
cache_key_method_name = "#{method_name}_cache_key"
define_method(method_name) do |*args|
store =
if RequestStore.active?
RequestStore.store
else
ivar_name = # ! and ? cannot be used as ivar name
"@cache_#{method_name.to_s.tr('!?', "\u2605\u2606")}"
instance_variable_get(ivar_name) ||
instance_variable_set(ivar_name, {})
end
key = __send__(cache_key_method_name, args)
store.fetch(key) { store[key] = super(*args) }
end
define_method(cache_key_method_name) do |args|
klass = self.class
instance_key = instance_exec(&klass.request_cache_key) if
klass.request_cache_key
method_key = instance_exec(&method_key_block) if method_key_block
[klass.name, method_name, *instance_key, *method_key, *args]
.join(':')
end
private cache_key_method_name
end
end
end
end
end
...@@ -140,6 +140,8 @@ module Gitlab ...@@ -140,6 +140,8 @@ module Gitlab
return add_foreign_key(source, target, return add_foreign_key(source, target,
column: column, column: column,
on_delete: on_delete) on_delete: on_delete)
else
on_delete = 'SET NULL' if on_delete == :nullify
end end
disable_statement_timeout disable_statement_timeout
...@@ -155,7 +157,7 @@ module Gitlab ...@@ -155,7 +157,7 @@ module Gitlab
ADD CONSTRAINT #{key_name} ADD CONSTRAINT #{key_name}
FOREIGN KEY (#{column}) FOREIGN KEY (#{column})
REFERENCES #{target} (id) REFERENCES #{target} (id)
#{on_delete ? "ON DELETE #{on_delete}" : ''} #{on_delete ? "ON DELETE #{on_delete.upcase}" : ''}
NOT VALID; NOT VALID;
EOF EOF
......
...@@ -29,7 +29,7 @@ module Gitlab ...@@ -29,7 +29,7 @@ module Gitlab
path = path.sub(/\A\/*/, '') path = path.sub(/\A\/*/, '')
path = '/' if path.empty? path = '/' if path.empty?
name = File.basename(path) name = File.basename(path)
entry = Gitlab::GitalyClient::Commit.new(repository).tree_entry(sha, path, MAX_DATA_DISPLAY_SIZE) entry = Gitlab::GitalyClient::CommitService.new(repository).tree_entry(sha, path, MAX_DATA_DISPLAY_SIZE)
return unless entry return unless entry
case entry.type case entry.type
...@@ -87,7 +87,7 @@ module Gitlab ...@@ -87,7 +87,7 @@ module Gitlab
def raw(repository, sha) def raw(repository, sha)
Gitlab::GitalyClient.migrate(:git_blob_raw) do |is_enabled| Gitlab::GitalyClient.migrate(:git_blob_raw) do |is_enabled|
if is_enabled if is_enabled
Gitlab::GitalyClient::Blob.new(repository).get_blob(oid: sha, limit: MAX_DATA_DISPLAY_SIZE) Gitlab::GitalyClient::BlobService.new(repository).get_blob(oid: sha, limit: MAX_DATA_DISPLAY_SIZE)
else else
blob = repository.lookup(sha) blob = repository.lookup(sha)
...@@ -182,7 +182,7 @@ module Gitlab ...@@ -182,7 +182,7 @@ module Gitlab
Gitlab::GitalyClient.migrate(:git_blob_load_all_data) do |is_enabled| Gitlab::GitalyClient.migrate(:git_blob_load_all_data) do |is_enabled|
@data = begin @data = begin
if is_enabled if is_enabled
Gitlab::GitalyClient::Blob.new(repository).get_blob(oid: id, limit: -1).data Gitlab::GitalyClient::BlobService.new(repository).get_blob(oid: id, limit: -1).data
else else
repository.lookup(id).content repository.lookup(id).content
end end
......
...@@ -3,39 +3,8 @@ ...@@ -3,39 +3,8 @@
module Gitlab module Gitlab
module Git module Git
class Branch < Ref class Branch < Ref
def initialize(repository, name, target) def initialize(repository, name, target, target_commit)
if target.is_a?(Gitaly::FindLocalBranchResponse) super(repository, name, target, target_commit)
target = target_from_gitaly_local_branches_response(target)
end
super(repository, name, target)
end
def target_from_gitaly_local_branches_response(response)
# Git messages have no encoding enforcements. However, in the UI we only
# handle UTF-8, so basically we cross our fingers that the message force
# encoded to UTF-8 is readable.
message = response.commit_subject.dup.force_encoding('UTF-8')
# NOTE: For ease of parsing in Gitaly, we have only the subject of
# the commit and not the full message. This is ok, since all the
# code that uses `local_branches` only cares at most about the
# commit message.
# TODO: Once gitaly "takes over" Rugged consider separating the
# subject from the message to make it clearer when there's one
# available but not the other.
hash = {
id: response.commit_id,
message: message,
authored_date: Time.at(response.commit_author.date.seconds),
author_name: response.commit_author.name,
author_email: response.commit_author.email,
committed_date: Time.at(response.commit_committer.date.seconds),
committer_name: response.commit_committer.name,
committer_email: response.commit_committer.email
}
Gitlab::Git::Commit.decorate(hash)
end end
end end
end end
......
...@@ -33,10 +33,9 @@ module Gitlab ...@@ -33,10 +33,9 @@ module Gitlab
object object
end end
def initialize(repository, name, target) def initialize(repository, name, target, derefenced_target)
encode! name @name = Gitlab::Git.ref_name(name)
@name = name.gsub(/\Arefs\/(tags|heads)\//, '') @dereferenced_target = derefenced_target
@dereferenced_target = Gitlab::Git::Commit.find(repository, target)
@target = if target.respond_to?(:oid) @target = if target.respond_to?(:oid)
target.oid target.oid
elsif target.respond_to?(:name) elsif target.respond_to?(:name)
......
...@@ -80,16 +80,10 @@ module Gitlab ...@@ -80,16 +80,10 @@ module Gitlab
end end
# Returns an Array of Branches # Returns an Array of Branches
def branches(filter: nil, sort_by: nil) #
branches = rugged.branches.each(filter).map do |rugged_ref| # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/389
begin def branches(sort_by: nil)
Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target) branches_filter(sort_by: sort_by)
rescue Rugged::ReferenceError
# Omit invalid branch
end
end.compact
sort_branches(branches, sort_by)
end end
def reload_rugged def reload_rugged
...@@ -107,7 +101,10 @@ module Gitlab ...@@ -107,7 +101,10 @@ module Gitlab
reload_rugged if force_reload reload_rugged if force_reload
rugged_ref = rugged.branches[name] rugged_ref = rugged.branches[name]
Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target) if rugged_ref if rugged_ref
target_commit = Gitlab::Git::Commit.find(self, rugged_ref.target)
Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target, target_commit)
end
end end
def local_branches(sort_by: nil) def local_branches(sort_by: nil)
...@@ -115,7 +112,7 @@ module Gitlab ...@@ -115,7 +112,7 @@ module Gitlab
if is_enabled if is_enabled
gitaly_ref_client.local_branches(sort_by: sort_by) gitaly_ref_client.local_branches(sort_by: sort_by)
else else
branches(filter: :local, sort_by: sort_by) branches_filter(filter: :local, sort_by: sort_by)
end end
end end
end end
...@@ -162,6 +159,8 @@ module Gitlab ...@@ -162,6 +159,8 @@ module Gitlab
end end
# Returns an Array of Tags # Returns an Array of Tags
#
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/390
def tags def tags
rugged.references.each("refs/tags/*").map do |ref| rugged.references.each("refs/tags/*").map do |ref|
message = nil message = nil
...@@ -174,7 +173,8 @@ module Gitlab ...@@ -174,7 +173,8 @@ module Gitlab
end end
end end
Gitlab::Git::Tag.new(self, ref.name, ref.target, message) target_commit = Gitlab::Git::Commit.find(self, ref.target)
Gitlab::Git::Tag.new(self, ref.name, ref.target, target_commit, message)
end.sort_by(&:name) end.sort_by(&:name)
end end
...@@ -204,13 +204,6 @@ module Gitlab ...@@ -204,13 +204,6 @@ module Gitlab
branch_names + tag_names branch_names + tag_names
end end
# Deprecated. Will be removed in 5.2
def heads
rugged.references.each("refs/heads/*").map do |head|
Gitlab::Git::Ref.new(self, head.name, head.target)
end.sort_by(&:name)
end
def has_commits? def has_commits?
!empty? !empty?
end end
...@@ -297,28 +290,6 @@ module Gitlab ...@@ -297,28 +290,6 @@ module Gitlab
(size.to_f / 1024).round(2) (size.to_f / 1024).round(2)
end end
# Returns an array of BlobSnippets for files at the specified +ref+ that
# contain the +query+ string.
def search_files(query, ref = nil)
greps = []
ref ||= root_ref
populated_index(ref).each do |entry|
# Discard submodules
next if submodule?(entry)
blob = Gitlab::Git::Blob.raw(self, entry[:oid])
# Skip binary files
next if blob.data.encoding == Encoding::ASCII_8BIT
blob.load_all_data!(self)
greps += build_greps(blob.data, query, ref, entry[:path])
end
greps
end
# Use the Rugged Walker API to build an array of commits. # Use the Rugged Walker API to build an array of commits.
# #
# Usage. # Usage.
...@@ -707,7 +678,8 @@ module Gitlab ...@@ -707,7 +678,8 @@ module Gitlab
# create_branch("other-feature", "master") # create_branch("other-feature", "master")
def create_branch(ref, start_point = "HEAD") def create_branch(ref, start_point = "HEAD")
rugged_ref = rugged.branches.create(ref, start_point) rugged_ref = rugged.branches.create(ref, start_point)
Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target) target_commit = Gitlab::Git::Commit.find(self, rugged_ref.target)
Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target, target_commit)
rescue Rugged::ReferenceError => e rescue Rugged::ReferenceError => e
raise InvalidRef.new("Branch #{ref} already exists") if e.to_s =~ /'refs\/heads\/#{ref}'/ raise InvalidRef.new("Branch #{ref} already exists") if e.to_s =~ /'refs\/heads\/#{ref}'/
raise InvalidRef.new("Invalid reference #{start_point}") raise InvalidRef.new("Invalid reference #{start_point}")
...@@ -837,6 +809,20 @@ module Gitlab ...@@ -837,6 +809,20 @@ module Gitlab
private private
# Gitaly note: JV: Trying to get rid of the 'filter' option so we can implement this with 'git'.
def branches_filter(filter: nil, sort_by: nil)
branches = rugged.branches.each(filter).map do |rugged_ref|
begin
target_commit = Gitlab::Git::Commit.find(self, rugged_ref.target)
Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target, target_commit)
rescue Rugged::ReferenceError
# Omit invalid branch
end
end.compact
sort_branches(branches, sort_by)
end
def raw_log(options) def raw_log(options)
default_options = { default_options = {
limit: 10, limit: 10,
...@@ -1091,73 +1077,6 @@ module Gitlab ...@@ -1091,73 +1077,6 @@ module Gitlab
index index
end end
# Return an array of BlobSnippets for lines in +file_contents+ that match
# +query+
def build_greps(file_contents, query, ref, filename)
# The file_contents string is potentially huge so we make sure to loop
# through it one line at a time. This gives Ruby the chance to GC lines
# we are not interested in.
#
# We need to do a little extra work because we are not looking for just
# the lines that matches the query, but also for the context
# (surrounding lines). We will use Enumerable#each_cons to efficiently
# loop through the lines while keeping surrounding lines on hand.
#
# First, we turn "foo\nbar\nbaz" into
# [
# [nil, -3], [nil, -2], [nil, -1],
# ['foo', 0], ['bar', 1], ['baz', 3],
# [nil, 4], [nil, 5], [nil, 6]
# ]
lines_with_index = Enumerator.new do |yielder|
# Yield fake 'before' lines for the first line of file_contents
(-SEARCH_CONTEXT_LINES..-1).each do |i|
yielder.yield [nil, i]
end
# Yield the actual file contents
count = 0
file_contents.each_line do |line|
line.chomp!
yielder.yield [line, count]
count += 1
end
# Yield fake 'after' lines for the last line of file_contents
(count + 1..count + SEARCH_CONTEXT_LINES).each do |i|
yielder.yield [nil, i]
end
end
greps = []
# Loop through consecutive blocks of lines with indexes
lines_with_index.each_cons(2 * SEARCH_CONTEXT_LINES + 1) do |line_block|
# Get the 'middle' line and index from the block
line, _ = line_block[SEARCH_CONTEXT_LINES]
next unless line && line.match(/#{Regexp.escape(query)}/i)
# Yay, 'line' contains a match!
# Get an array with just the context lines (no indexes)
match_with_context = line_block.map(&:first)
# Remove 'nil' lines in case we are close to the first or last line
match_with_context.compact!
# Get the line number (1-indexed) of the first context line
first_context_line_number = line_block[0][1] + 1
greps << Gitlab::Git::BlobSnippet.new(
ref,
match_with_context,
first_context_line_number,
filename
)
end
greps
end
# Return the Rugged patches for the diff between +from+ and +to+. # Return the Rugged patches for the diff between +from+ and +to+.
def diff_patches(from, to, options = {}, *paths) def diff_patches(from, to, options = {}, *paths)
options ||= {} options ||= {}
...@@ -1187,11 +1106,11 @@ module Gitlab ...@@ -1187,11 +1106,11 @@ module Gitlab
end end
def gitaly_ref_client def gitaly_ref_client
@gitaly_ref_client ||= Gitlab::GitalyClient::Ref.new(self) @gitaly_ref_client ||= Gitlab::GitalyClient::RefService.new(self)
end end
def gitaly_commit_client def gitaly_commit_client
@gitaly_commit_client ||= Gitlab::GitalyClient::Commit.new(self) @gitaly_commit_client ||= Gitlab::GitalyClient::CommitService.new(self)
end end
def gitaly_migrate(method, &block) def gitaly_migrate(method, &block)
......
...@@ -5,8 +5,8 @@ module Gitlab ...@@ -5,8 +5,8 @@ module Gitlab
class Tag < Ref class Tag < Ref
attr_reader :object_sha attr_reader :object_sha
def initialize(repository, name, target, message = nil) def initialize(repository, name, target, target_commit, message = nil)
super(repository, name, target) super(repository, name, target, target_commit)
@message = message @message = message
end end
......
module Gitlab module Gitlab
module GitalyClient module GitalyClient
class Blob class BlobService
def initialize(repository) def initialize(repository)
@gitaly_repo = repository.gitaly_repository @gitaly_repo = repository.gitaly_repository
end end
......
module Gitlab module Gitlab
module GitalyClient module GitalyClient
class Commit class CommitService
# The ID of empty tree. # The ID of empty tree.
# See http://stackoverflow.com/a/40884093/1856239 and https://github.com/git/git/blob/3ad8b5bf26362ac67c9020bf8c30eee54a84f56d/cache.h#L1011-L1012 # See http://stackoverflow.com/a/40884093/1856239 and https://github.com/git/git/blob/3ad8b5bf26362ac67c9020bf8c30eee54a84f56d/cache.h#L1011-L1012
EMPTY_TREE_ID = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'.freeze EMPTY_TREE_ID = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'.freeze
...@@ -17,20 +17,20 @@ module Gitlab ...@@ -17,20 +17,20 @@ module Gitlab
child_id: child_id child_id: child_id
) )
GitalyClient.call(@repository.storage, :commit, :commit_is_ancestor, request).value GitalyClient.call(@repository.storage, :commit_service, :commit_is_ancestor, request).value
end end
def diff_from_parent(commit, options = {}) def diff_from_parent(commit, options = {})
request_params = commit_diff_request_params(commit, options) request_params = commit_diff_request_params(commit, options)
request_params[:ignore_whitespace_change] = options.fetch(:ignore_whitespace_change, false) request_params[:ignore_whitespace_change] = options.fetch(:ignore_whitespace_change, false)
request = Gitaly::CommitDiffRequest.new(request_params) request = Gitaly::CommitDiffRequest.new(request_params)
response = GitalyClient.call(@repository.storage, :diff, :commit_diff, request) response = GitalyClient.call(@repository.storage, :diff_service, :commit_diff, request)
Gitlab::Git::DiffCollection.new(GitalyClient::DiffStitcher.new(response), options) Gitlab::Git::DiffCollection.new(GitalyClient::DiffStitcher.new(response), options)
end end
def commit_deltas(commit) def commit_deltas(commit)
request = Gitaly::CommitDeltaRequest.new(commit_diff_request_params(commit)) request = Gitaly::CommitDeltaRequest.new(commit_diff_request_params(commit))
response = GitalyClient.call(@repository.storage, :diff, :commit_delta, request) response = GitalyClient.call(@repository.storage, :diff_service, :commit_delta, request)
response.flat_map do |msg| response.flat_map do |msg|
msg.deltas.map { |d| Gitlab::Git::Diff.new(d) } msg.deltas.map { |d| Gitlab::Git::Diff.new(d) }
end end
...@@ -44,7 +44,7 @@ module Gitlab ...@@ -44,7 +44,7 @@ module Gitlab
limit: limit.to_i limit: limit.to_i
) )
response = GitalyClient.call(@repository.storage, :commit, :tree_entry, request) response = GitalyClient.call(@repository.storage, :commit_service, :tree_entry, request)
entry = response.first entry = response.first
return unless entry.oid.present? return unless entry.oid.present?
......
module Gitlab module Gitlab
module GitalyClient module GitalyClient
class Notifications class NotificationService
# 'repository' is a Gitlab::Git::Repository # 'repository' is a Gitlab::Git::Repository
def initialize(repository) def initialize(repository)
@gitaly_repo = repository.gitaly_repository @gitaly_repo = repository.gitaly_repository
...@@ -10,7 +10,7 @@ module Gitlab ...@@ -10,7 +10,7 @@ module Gitlab
def post_receive def post_receive
GitalyClient.call( GitalyClient.call(
@storage, @storage,
:notifications, :notification_service,
:post_receive, :post_receive,
Gitaly::PostReceiveRequest.new(repository: @gitaly_repo) Gitaly::PostReceiveRequest.new(repository: @gitaly_repo)
) )
......
module Gitlab module Gitlab
module GitalyClient module GitalyClient
class Ref class RefService
include Gitlab::EncodingHelper include Gitlab::EncodingHelper
# 'repository' is a Gitlab::Git::Repository # 'repository' is a Gitlab::Git::Repository
...@@ -12,19 +12,19 @@ module Gitlab ...@@ -12,19 +12,19 @@ module Gitlab
def default_branch_name def default_branch_name
request = Gitaly::FindDefaultBranchNameRequest.new(repository: @gitaly_repo) request = Gitaly::FindDefaultBranchNameRequest.new(repository: @gitaly_repo)
response = GitalyClient.call(@storage, :ref, :find_default_branch_name, request) response = GitalyClient.call(@storage, :ref_service, :find_default_branch_name, request)
Gitlab::Git.branch_name(response.name) Gitlab::Git.branch_name(response.name)
end end
def branch_names def branch_names
request = Gitaly::FindAllBranchNamesRequest.new(repository: @gitaly_repo) request = Gitaly::FindAllBranchNamesRequest.new(repository: @gitaly_repo)
response = GitalyClient.call(@storage, :ref, :find_all_branch_names, request) response = GitalyClient.call(@storage, :ref_service, :find_all_branch_names, request)
consume_refs_response(response) { |name| Gitlab::Git.branch_name(name) } consume_refs_response(response) { |name| Gitlab::Git.branch_name(name) }
end end
def tag_names def tag_names
request = Gitaly::FindAllTagNamesRequest.new(repository: @gitaly_repo) request = Gitaly::FindAllTagNamesRequest.new(repository: @gitaly_repo)
response = GitalyClient.call(@storage, :ref, :find_all_tag_names, request) response = GitalyClient.call(@storage, :ref_service, :find_all_tag_names, request)
consume_refs_response(response) { |name| Gitlab::Git.tag_name(name) } consume_refs_response(response) { |name| Gitlab::Git.tag_name(name) }
end end
...@@ -34,7 +34,7 @@ module Gitlab ...@@ -34,7 +34,7 @@ module Gitlab
commit_id: commit_id, commit_id: commit_id,
prefix: ref_prefix prefix: ref_prefix
) )
encode!(GitalyClient.call(@storage, :ref, :find_ref_name, request).name.dup) encode!(GitalyClient.call(@storage, :ref_service, :find_ref_name, request).name.dup)
end end
def count_tag_names def count_tag_names
...@@ -48,7 +48,7 @@ module Gitlab ...@@ -48,7 +48,7 @@ module Gitlab
def local_branches(sort_by: nil) def local_branches(sort_by: nil)
request = Gitaly::FindLocalBranchesRequest.new(repository: @gitaly_repo) request = Gitaly::FindLocalBranchesRequest.new(repository: @gitaly_repo)
request.sort_by = sort_by_param(sort_by) if sort_by request.sort_by = sort_by_param(sort_by) if sort_by
response = GitalyClient.call(@storage, :ref, :find_local_branches, request) response = GitalyClient.call(@storage, :ref_service, :find_local_branches, request)
consume_branches_response(response) consume_branches_response(response)
end end
...@@ -72,11 +72,39 @@ module Gitlab ...@@ -72,11 +72,39 @@ module Gitlab
Gitlab::Git::Branch.new( Gitlab::Git::Branch.new(
@repository, @repository,
encode!(gitaly_branch.name.dup), encode!(gitaly_branch.name.dup),
gitaly_branch.commit_id gitaly_branch.commit_id,
commit_from_local_branches_response(gitaly_branch)
) )
end end
end end
end end
def commit_from_local_branches_response(response)
# Git messages have no encoding enforcements. However, in the UI we only
# handle UTF-8, so basically we cross our fingers that the message force
# encoded to UTF-8 is readable.
message = response.commit_subject.dup.force_encoding('UTF-8')
# NOTE: For ease of parsing in Gitaly, we have only the subject of
# the commit and not the full message. This is ok, since all the
# code that uses `local_branches` only cares at most about the
# commit message.
# TODO: Once gitaly "takes over" Rugged consider separating the
# subject from the message to make it clearer when there's one
# available but not the other.
hash = {
id: response.commit_id,
message: message,
authored_date: Time.at(response.commit_author.date.seconds),
author_name: response.commit_author.name,
author_email: response.commit_author.email,
committed_date: Time.at(response.commit_committer.date.seconds),
committer_name: response.commit_committer.name,
committer_email: response.commit_committer.email
}
Gitlab::Git::Commit.decorate(hash)
end
end end
end end
end end
...@@ -2,7 +2,8 @@ module Gitlab ...@@ -2,7 +2,8 @@ module Gitlab
module PerformanceBar module PerformanceBar
include Gitlab::CurrentSettings include Gitlab::CurrentSettings
ALLOWED_USER_IDS_KEY = 'performance_bar_allowed_user_ids'.freeze ALLOWED_USER_IDS_KEY = 'performance_bar_allowed_user_ids:v2'.freeze
EXPIRY_TIME = 5.minutes
def self.enabled?(user = nil) def self.enabled?(user = nil)
return false unless user && allowed_group_id return false unless user && allowed_group_id
...@@ -15,7 +16,7 @@ module Gitlab ...@@ -15,7 +16,7 @@ module Gitlab
end end
def self.allowed_user_ids def self.allowed_user_ids
Rails.cache.fetch(ALLOWED_USER_IDS_KEY) do Rails.cache.fetch(ALLOWED_USER_IDS_KEY, expires_in: EXPIRY_TIME) do
group = Group.find_by_id(allowed_group_id) group = Group.find_by_id(allowed_group_id)
if group if group
......
# Inspired by https://github.com/peek/peek-pg/blob/master/lib/peek/views/pg.rb # Inspired by https://github.com/peek/peek-pg/blob/master/lib/peek/views/pg.rb
# PEEK_DB_CLIENT is a constant set in config/initializers/peek.rb
module Gitlab module Gitlab
module PerformanceBar module PerformanceBar
module PeekQueryTracker module PeekQueryTracker
...@@ -23,10 +24,16 @@ module Gitlab ...@@ -23,10 +24,16 @@ module Gitlab
subscribe('sql.active_record') do |_, start, finish, _, data| subscribe('sql.active_record') do |_, start, finish, _, data|
if RequestStore.active? && RequestStore.store[:peek_enabled] if RequestStore.active? && RequestStore.store[:peek_enabled]
# data[:cached] is only available starting from Rails 5.1.0
# https://github.com/rails/rails/blob/v5.1.0/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb#L113
# Before that, data[:name] was set to 'CACHE'
# https://github.com/rails/rails/blob/v4.2.9/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb#L80
unless data.fetch(:cached, data[:name] == 'CACHE')
track_query(data[:sql].strip, data[:binds], start, finish) track_query(data[:sql].strip, data[:binds], start, finish)
end end
end end
end end
end
def track_query(raw_query, bindings, start, finish) def track_query(raw_query, bindings, start, finish)
query = Gitlab::Sherlock::Query.new(raw_query, start, finish) query = Gitlab::Sherlock::Query.new(raw_query, start, finish)
......
module Gitlab module Gitlab
class UserAccess class UserAccess
extend Gitlab::Cache::RequestCache
request_cache_key do
[user&.id, project&.id]
end
attr_reader :user, :project attr_reader :user, :project
def initialize(user, project: nil) def initialize(user, project: nil)
...@@ -28,7 +34,7 @@ module Gitlab ...@@ -28,7 +34,7 @@ module Gitlab
true true
end end
def can_create_tag?(ref) request_cache def can_create_tag?(ref)
return false unless can_access_git? return false unless can_access_git?
if ProtectedTag.protected?(project, ref) if ProtectedTag.protected?(project, ref)
...@@ -38,7 +44,7 @@ module Gitlab ...@@ -38,7 +44,7 @@ module Gitlab
end end
end end
def can_delete_branch?(ref) request_cache def can_delete_branch?(ref)
return false unless can_access_git? return false unless can_access_git?
if ProtectedBranch.protected?(project, ref) if ProtectedBranch.protected?(project, ref)
...@@ -48,7 +54,7 @@ module Gitlab ...@@ -48,7 +54,7 @@ module Gitlab
end end
end end
def can_push_to_branch?(ref) request_cache def can_push_to_branch?(ref)
return false unless can_access_git? return false unless can_access_git?
if ProtectedBranch.protected?(project, ref) if ProtectedBranch.protected?(project, ref)
...@@ -60,7 +66,7 @@ module Gitlab ...@@ -60,7 +66,7 @@ module Gitlab
end end
end end
def can_merge_to_branch?(ref) request_cache def can_merge_to_branch?(ref)
return false unless can_access_git? return false unless can_access_git?
if ProtectedBranch.protected?(project, ref) if ProtectedBranch.protected?(project, ref)
......
...@@ -5,7 +5,7 @@ namespace :gettext do ...@@ -5,7 +5,7 @@ namespace :gettext do
# See: https://github.com/grosser/gettext_i18n_rails#customizing-list-of-translatable-files # See: https://github.com/grosser/gettext_i18n_rails#customizing-list-of-translatable-files
def files_to_translate def files_to_translate
folders = %W(app lib config #{locale_path}).join(',') folders = %W(app lib config #{locale_path}).join(',')
exts = %w(rb erb haml slim rhtml js jsx vue coffee handlebars hbs mustache).join(',') exts = %w(rb erb haml slim rhtml js jsx vue handlebars hbs mustache).join(',')
Dir.glob( Dir.glob(
"{#{folders}}/**/*.{#{exts}}" "{#{folders}}/**/*.{#{exts}}"
......
require 'set'
require_relative '../../migration_helpers'
module RuboCop
module Cop
module Migration
# Cop that prevents the use of hash indexes in database migrations
class HashIndex < RuboCop::Cop::Cop
include MigrationHelpers
MSG = 'hash indexes should be avoided at all costs since they are not ' \
'recorded in the PostgreSQL WAL, you should use a btree index instead'.freeze
NAMES = Set.new([:add_index, :index, :add_concurrent_index]).freeze
def on_send(node)
return unless in_migration?(node)
name = node.children[1]
return unless NAMES.include?(name)
opts = node.children.last
return unless opts && opts.type == :hash
opts.each_node(:pair) do |pair|
next unless hash_key_type(pair) == :sym &&
hash_key_name(pair) == :using
if hash_key_value(pair).to_s == 'hash'
add_offense(pair, :expression)
end
end
end
def hash_key_type(pair)
pair.children[0].type
end
def hash_key_name(pair)
pair.children[0].children[0]
end
def hash_key_value(pair)
pair.children[1].children[0]
end
end
end
end
end
...@@ -13,6 +13,7 @@ require_relative 'cop/migration/add_concurrent_index' ...@@ -13,6 +13,7 @@ require_relative 'cop/migration/add_concurrent_index'
require_relative 'cop/migration/add_index' require_relative 'cop/migration/add_index'
require_relative 'cop/migration/add_timestamps' require_relative 'cop/migration/add_timestamps'
require_relative 'cop/migration/datetime' require_relative 'cop/migration/datetime'
require_relative 'cop/migration/hash_index'
require_relative 'cop/migration/remove_concurrent_index' require_relative 'cop/migration/remove_concurrent_index'
require_relative 'cop/migration/remove_index' require_relative 'cop/migration/remove_index'
require_relative 'cop/migration/reversible_add_column_with_default' require_relative 'cop/migration/reversible_add_column_with_default'
......
...@@ -10,7 +10,7 @@ fi ...@@ -10,7 +10,7 @@ fi
# Only install knapsack after bundle install! Otherwise oddly some native # Only install knapsack after bundle install! Otherwise oddly some native
# gems could not be found under some circumstance. No idea why, hours wasted. # gems could not be found under some circumstance. No idea why, hours wasted.
retry gem install knapsack fog-aws mime-types retry gem install knapsack
cp config/gitlab.yml.example config/gitlab.yml cp config/gitlab.yml.example config/gitlab.yml
......
...@@ -4,14 +4,19 @@ FactoryGirl.define do ...@@ -4,14 +4,19 @@ FactoryGirl.define do
factory :commit do factory :commit do
git_commit RepoHelpers.sample_commit git_commit RepoHelpers.sample_commit
project factory: :empty_project project factory: :empty_project
author { build(:author) }
initialize_with do initialize_with do
new(git_commit, project) new(git_commit, project)
end end
after(:build) do |commit|
allow(commit).to receive(:author).and_return build(:author)
end
trait :without_author do trait :without_author do
author nil after(:build) do |commit|
allow(commit).to receive(:author).and_return nil
end
end end
end end
end end
...@@ -3,7 +3,8 @@ require 'rails_helper' ...@@ -3,7 +3,8 @@ require 'rails_helper'
describe 'Issue Boards', feature: true, js: true do describe 'Issue Boards', feature: true, js: true do
include DragTo include DragTo
let(:project) { create(:empty_project, :public) } let(:group) { create(:group, :nested) }
let(:project) { create(:empty_project, :public, namespace: group) }
let(:board) { create(:board, project: project) } let(:board) { create(:board, project: project) }
let(:user) { create(:user) } let(:user) { create(:user) }
let!(:user2) { create(:user) } let!(:user2) { create(:user) }
......
...@@ -54,7 +54,8 @@ feature 'Member autocomplete', :js do ...@@ -54,7 +54,8 @@ feature 'Member autocomplete', :js do
let(:note) { create(:note_on_commit, project: project, commit_id: project.commit.id) } let(:note) { create(:note_on_commit, project: project, commit_id: project.commit.id) }
before do before do
allow_any_instance_of(Commit).to receive(:author).and_return(author) allow(User).to receive(:find_by_any_email)
.with(noteable.author_email.downcase).and_return(author)
visit project_commit_path(project, noteable) visit project_commit_path(project, noteable)
end end
......
...@@ -25,23 +25,28 @@ function mockServiceCall(service, response, shouldFail = false) { ...@@ -25,23 +25,28 @@ function mockServiceCall(service, response, shouldFail = false) {
describe('Poll', () => { describe('Poll', () => {
const service = jasmine.createSpyObj('service', ['fetch']); const service = jasmine.createSpyObj('service', ['fetch']);
const callbacks = jasmine.createSpyObj('callbacks', ['success', 'error']); const callbacks = jasmine.createSpyObj('callbacks', ['success', 'error', 'notification']);
function setup() {
return new Poll({
resource: service,
method: 'fetch',
successCallback: callbacks.success,
errorCallback: callbacks.error,
notificationCallback: callbacks.notification,
}).makeRequest();
}
afterEach(() => { afterEach(() => {
callbacks.success.calls.reset(); callbacks.success.calls.reset();
callbacks.error.calls.reset(); callbacks.error.calls.reset();
callbacks.notification.calls.reset();
service.fetch.calls.reset(); service.fetch.calls.reset();
}); });
it('calls the success callback when no header for interval is provided', (done) => { it('calls the success callback when no header for interval is provided', (done) => {
mockServiceCall(service, { status: 200 }); mockServiceCall(service, { status: 200 });
setup();
new Poll({
resource: service,
method: 'fetch',
successCallback: callbacks.success,
errorCallback: callbacks.error,
}).makeRequest();
waitForAllCallsToFinish(service, 1, () => { waitForAllCallsToFinish(service, 1, () => {
expect(callbacks.success).toHaveBeenCalled(); expect(callbacks.success).toHaveBeenCalled();
...@@ -51,15 +56,9 @@ describe('Poll', () => { ...@@ -51,15 +56,9 @@ describe('Poll', () => {
}); });
}); });
it('calls the error callback whe the http request returns an error', (done) => { it('calls the error callback when the http request returns an error', (done) => {
mockServiceCall(service, { status: 500 }, true); mockServiceCall(service, { status: 500 }, true);
setup();
new Poll({
resource: service,
method: 'fetch',
successCallback: callbacks.success,
errorCallback: callbacks.error,
}).makeRequest();
waitForAllCallsToFinish(service, 1, () => { waitForAllCallsToFinish(service, 1, () => {
expect(callbacks.success).not.toHaveBeenCalled(); expect(callbacks.success).not.toHaveBeenCalled();
...@@ -69,15 +68,22 @@ describe('Poll', () => { ...@@ -69,15 +68,22 @@ describe('Poll', () => {
}); });
}); });
it('skips the error callback when request is aborted', (done) => {
mockServiceCall(service, { status: 0 }, true);
setup();
waitForAllCallsToFinish(service, 1, () => {
expect(callbacks.success).not.toHaveBeenCalled();
expect(callbacks.error).not.toHaveBeenCalled();
expect(callbacks.notification).toHaveBeenCalled();
done();
});
});
it('should call the success callback when the interval header is -1', (done) => { it('should call the success callback when the interval header is -1', (done) => {
mockServiceCall(service, { status: 200, headers: { 'poll-interval': -1 } }); mockServiceCall(service, { status: 200, headers: { 'poll-interval': -1 } });
setup().then(() => {
new Poll({
resource: service,
method: 'fetch',
successCallback: callbacks.success,
errorCallback: callbacks.error,
}).makeRequest().then(() => {
expect(callbacks.success).toHaveBeenCalled(); expect(callbacks.success).toHaveBeenCalled();
expect(callbacks.error).not.toHaveBeenCalled(); expect(callbacks.error).not.toHaveBeenCalled();
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::BackgroundMigration do describe Gitlab::BackgroundMigration do
describe '.queue' do
it 'returns background migration worker queue' do
expect(described_class.queue)
.to eq BackgroundMigrationWorker.sidekiq_options['queue']
end
end
describe '.steal' do describe '.steal' do
it 'steals jobs from a queue' do context 'when there are enqueued jobs present' do
queue = [double(:job, args: ['Foo', [10, 20]])] let(:queue) do
[double(args: ['Foo', [10, 20]], queue: described_class.queue)]
end
before do
allow(Sidekiq::Queue).to receive(:new) allow(Sidekiq::Queue).to receive(:new)
.with(BackgroundMigrationWorker.sidekiq_options['queue']) .with(described_class.queue)
.and_return(queue) .and_return(queue)
end
expect(queue[0]).to receive(:delete) context 'when queue contains unprocessed jobs' do
it 'steals jobs from a queue' do
expect(queue[0]).to receive(:delete).and_return(true)
expect(described_class).to receive(:perform).with('Foo', [10, 20]) expect(described_class).to receive(:perform)
.with('Foo', [10, 20], anything)
described_class.steal('Foo') described_class.steal('Foo')
end end
it 'does not steal jobs for a different migration' do it 'does not steal job that has already been taken' do
queue = [double(:job, args: ['Foo', [10, 20]])] expect(queue[0]).to receive(:delete).and_return(false)
allow(Sidekiq::Queue).to receive(:new) expect(described_class).not_to receive(:perform)
.with(BackgroundMigrationWorker.sidekiq_options['queue'])
.and_return(queue)
described_class.steal('Foo')
end
it 'does not steal jobs for a different migration' do
expect(described_class).not_to receive(:perform) expect(described_class).not_to receive(:perform)
expect(queue[0]).not_to receive(:delete) expect(queue[0]).not_to receive(:delete)
...@@ -31,16 +47,74 @@ describe Gitlab::BackgroundMigration do ...@@ -31,16 +47,74 @@ describe Gitlab::BackgroundMigration do
end end
end end
context 'when one of the jobs raises an error' do
let(:migration) { spy(:migration) }
let(:queue) do
[double(args: ['Foo', [10, 20]], queue: described_class.queue),
double(args: ['Foo', [20, 30]], queue: described_class.queue)]
end
before do
stub_const("#{described_class}::Foo", migration)
allow(queue[0]).to receive(:delete).and_return(true)
allow(queue[1]).to receive(:delete).and_return(true)
end
it 'enqueues the migration again and re-raises the error' do
allow(migration).to receive(:perform).with(10, 20)
.and_raise(Exception, 'Migration error').once
expect(BackgroundMigrationWorker).to receive(:perform_async)
.with('Foo', [10, 20]).once
expect { described_class.steal('Foo') }.to raise_error(Exception)
end
end
end
context 'when there are scheduled jobs present', :sidekiq, :redis do
it 'steals all jobs from the scheduled sets' do
Sidekiq::Testing.disable! do
BackgroundMigrationWorker.perform_in(10.minutes, 'Object')
expect(Sidekiq::ScheduledSet.new).to be_one
expect(described_class).to receive(:perform).with('Object', any_args)
described_class.steal('Object')
expect(Sidekiq::ScheduledSet.new).to be_none
end
end
end
context 'when there are enqueued and scheduled jobs present', :sidekiq, :redis do
it 'steals from the scheduled sets queue first' do
Sidekiq::Testing.disable! do
expect(described_class).to receive(:perform)
.with('Object', [1], anything).ordered
expect(described_class).to receive(:perform)
.with('Object', [2], anything).ordered
BackgroundMigrationWorker.perform_async('Object', [2])
BackgroundMigrationWorker.perform_in(10.minutes, 'Object', [1])
described_class.steal('Object')
end
end
end
end
describe '.perform' do describe '.perform' do
it 'performs a background migration' do let(:migration) { spy(:migration) }
instance = double(:instance)
klass = double(:klass, new: instance)
expect(described_class).to receive(:const_get) before do
.with('Foo') stub_const("#{described_class.name}::Foo", migration)
.and_return(klass) end
expect(instance).to receive(:perform).with(10, 20) it 'performs a background migration' do
expect(migration).to receive(:perform).with(10, 20).once
described_class.perform('Foo', [10, 20]) described_class.perform('Foo', [10, 20])
end end
......
require 'spec_helper'
describe Gitlab::Cache::RequestCache do
let(:klass) do
Class.new do
extend Gitlab::Cache::RequestCache
attr_accessor :id, :name, :result, :extra
def self.name
'ExpensiveAlgorithm'
end
def initialize(id, name, result, extra = nil)
self.id = id
self.name = name
self.result = result
self.extra = nil
end
request_cache def compute(arg)
result << arg
end
request_cache def repute(arg)
result << arg
end
def dispute(arg)
result << arg
end
request_cache(:dispute) { extra }
end
end
let(:algorithm) { klass.new('id', 'name', []) }
shared_examples 'cache for the same instance' do
it 'does not compute twice for the same argument' do
algorithm.compute(true)
result = algorithm.compute(true)
expect(result).to eq([true])
end
it 'computes twice for the different argument' do
algorithm.compute(true)
result = algorithm.compute(false)
expect(result).to eq([true, false])
end
it 'computes twice for the different class name' do
algorithm.compute(true)
allow(klass).to receive(:name).and_return('CheapAlgo')
result = algorithm.compute(true)
expect(result).to eq([true, true])
end
it 'computes twice for the different method' do
algorithm.compute(true)
result = algorithm.repute(true)
expect(result).to eq([true, true])
end
context 'when request_cache_key is provided' do
before do
klass.request_cache_key do
[id, name]
end
end
it 'computes twice for the different keys, id' do
algorithm.compute(true)
algorithm.id = 'ad'
result = algorithm.compute(true)
expect(result).to eq([true, true])
end
it 'computes twice for the different keys, name' do
algorithm.compute(true)
algorithm.name = 'same'
result = algorithm.compute(true)
expect(result).to eq([true, true])
end
it 'uses extra method cache key if provided' do
algorithm.dispute(true) # miss
algorithm.extra = true
algorithm.dispute(true) # miss
result = algorithm.dispute(true) # hit
expect(result).to eq([true, true])
end
end
end
context 'when RequestStore is active', :request_store do
it_behaves_like 'cache for the same instance'
it 'computes once for different instances when keys are the same' do
algorithm.compute(true)
result = klass.new('id', 'name', algorithm.result).compute(true)
expect(result).to eq([true])
end
it 'computes twice if RequestStore starts over' do
algorithm.compute(true)
RequestStore.end!
RequestStore.clear!
RequestStore.begin!
result = algorithm.compute(true)
expect(result).to eq([true, true])
end
end
context 'when RequestStore is inactive' do
it_behaves_like 'cache for the same instance'
it 'computes twice for different instances even if keys are the same' do
algorithm.compute(true)
result = klass.new('id', 'name', algorithm.result).compute(true)
expect(result).to eq([true, true])
end
end
end
...@@ -174,13 +174,23 @@ describe Gitlab::Database::MigrationHelpers, lib: true do ...@@ -174,13 +174,23 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
allow(Gitlab::Database).to receive(:mysql?).and_return(false) allow(Gitlab::Database).to receive(:mysql?).and_return(false)
end end
it 'creates a concurrent foreign key' do it 'creates a concurrent foreign key and validates it' do
expect(model).to receive(:disable_statement_timeout) expect(model).to receive(:disable_statement_timeout)
expect(model).to receive(:execute).ordered.with(/NOT VALID/) expect(model).to receive(:execute).ordered.with(/NOT VALID/)
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/) expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
model.add_concurrent_foreign_key(:projects, :users, column: :user_id) model.add_concurrent_foreign_key(:projects, :users, column: :user_id)
end end
it 'appends a valid ON DELETE statement' do
expect(model).to receive(:disable_statement_timeout)
expect(model).to receive(:execute).with(/ON DELETE SET NULL/)
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
model.add_concurrent_foreign_key(:projects, :users,
column: :user_id,
on_delete: :nullify)
end
end end
end end
end end
......
...@@ -7,51 +7,6 @@ describe Gitlab::Git::Branch, seed_helper: true do ...@@ -7,51 +7,6 @@ describe Gitlab::Git::Branch, seed_helper: true do
it { is_expected.to be_kind_of Array } it { is_expected.to be_kind_of Array }
describe 'initialize' do
let(:commit_id) { 'f00' }
let(:commit_subject) { "My commit".force_encoding('ASCII-8BIT') }
let(:committer) do
Gitaly::FindLocalBranchCommitAuthor.new(
name: generate(:name),
email: generate(:email),
date: Google::Protobuf::Timestamp.new(seconds: 123)
)
end
let(:author) do
Gitaly::FindLocalBranchCommitAuthor.new(
name: generate(:name),
email: generate(:email),
date: Google::Protobuf::Timestamp.new(seconds: 456)
)
end
let(:gitaly_branch) do
Gitaly::FindLocalBranchResponse.new(
name: 'foo', commit_id: commit_id, commit_subject: commit_subject,
commit_author: author, commit_committer: committer
)
end
let(:attributes) do
{
id: commit_id,
message: commit_subject,
authored_date: Time.at(author.date.seconds),
author_name: author.name,
author_email: author.email,
committed_date: Time.at(committer.date.seconds),
committer_name: committer.name,
committer_email: committer.email
}
end
let(:branch) { described_class.new(repository, 'foo', gitaly_branch) }
it 'parses Gitaly::FindLocalBranchResponse correctly' do
expect(Gitlab::Git::Commit).to receive(:decorate)
.with(hash_including(attributes)).and_call_original
expect(branch.dereferenced_target.message).to be_utf8
end
end
describe '#size' do describe '#size' do
subject { super().size } subject { super().size }
it { is_expected.to eq(SeedRepo::Repo::BRANCHES.size) } it { is_expected.to eq(SeedRepo::Repo::BRANCHES.size) }
......
...@@ -45,11 +45,11 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -45,11 +45,11 @@ describe Gitlab::Git::Repository, seed_helper: true do
end end
it 'gets the branch name from GitalyClient' do it 'gets the branch name from GitalyClient' do
expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name) expect_any_instance_of(Gitlab::GitalyClient::RefService).to receive(:default_branch_name)
repository.root_ref repository.root_ref
end end
it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::Ref, :default_branch_name do it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RefService, :default_branch_name do
subject { repository.root_ref } subject { repository.root_ref }
end end
end end
...@@ -132,11 +132,11 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -132,11 +132,11 @@ describe Gitlab::Git::Repository, seed_helper: true do
it { is_expected.not_to include("branch-from-space") } it { is_expected.not_to include("branch-from-space") }
it 'gets the branch names from GitalyClient' do it 'gets the branch names from GitalyClient' do
expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names) expect_any_instance_of(Gitlab::GitalyClient::RefService).to receive(:branch_names)
subject subject
end end
it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::Ref, :branch_names it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RefService, :branch_names
end end
describe '#tag_names' do describe '#tag_names' do
...@@ -160,11 +160,11 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -160,11 +160,11 @@ describe Gitlab::Git::Repository, seed_helper: true do
it { is_expected.not_to include("v5.0.0") } it { is_expected.not_to include("v5.0.0") }
it 'gets the tag names from GitalyClient' do it 'gets the tag names from GitalyClient' do
expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names) expect_any_instance_of(Gitlab::GitalyClient::RefService).to receive(:tag_names)
subject subject
end end
it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::Ref, :tag_names it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RefService, :tag_names
end end
shared_examples 'archive check' do |extenstion| shared_examples 'archive check' do |extenstion|
...@@ -234,33 +234,6 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -234,33 +234,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
it { expect(repository.bare?).to be_truthy } it { expect(repository.bare?).to be_truthy }
end end
describe '#heads' do
let(:heads) { repository.heads }
subject { heads }
it { is_expected.to be_kind_of Array }
describe '#size' do
subject { super().size }
it { is_expected.to eq(SeedRepo::Repo::BRANCHES.size) }
end
context :head do
subject { heads.first }
describe '#name' do
subject { super().name }
it { is_expected.to eq("feature") }
end
context :commit do
subject { heads.first.dereferenced_target.sha }
it { is_expected.to eq("0b4bc9a49b562e85de7cc9e834518ea6828729b9") }
end
end
end
describe '#ref_names' do describe '#ref_names' do
let(:ref_names) { repository.ref_names } let(:ref_names) { repository.ref_names }
subject { ref_names } subject { ref_names }
...@@ -278,42 +251,6 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -278,42 +251,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
end end
end end
describe '#search_files' do
let(:results) { repository.search_files('rails', 'master') }
subject { results }
it { is_expected.to be_kind_of Array }
describe '#first' do
subject { super().first }
it { is_expected.to be_kind_of Gitlab::Git::BlobSnippet }
end
context 'blob result' do
subject { results.first }
describe '#ref' do
subject { super().ref }
it { is_expected.to eq('master') }
end
describe '#filename' do
subject { super().filename }
it { is_expected.to eq('CHANGELOG') }
end
describe '#startline' do
subject { super().startline }
it { is_expected.to eq(35) }
end
describe '#data' do
subject { super().data }
it { is_expected.to include "Ability to filter by multiple labels" }
end
end
end
describe '#submodule_url_for' do describe '#submodule_url_for' do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) } let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) }
let(:ref) { 'master' } let(:ref) { 'master' }
...@@ -431,7 +368,7 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -431,7 +368,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
context 'when Gitaly commit_count feature is enabled' do context 'when Gitaly commit_count feature is enabled' do
it_behaves_like 'counting commits' it_behaves_like 'counting commits'
it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::Commit, :commit_count do it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::CommitService, :commit_count do
subject { repository.commit_count('master') } subject { repository.commit_count('master') }
end end
end end
...@@ -521,7 +458,7 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -521,7 +458,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
end end
it "should refresh the repo's #heads collection" do it "should refresh the repo's #heads collection" do
head_names = @normal_repo.heads.map { |h| h.name } head_names = @normal_repo.branches.map { |h| h.name }
expect(head_names).to include(new_branch) expect(head_names).to include(new_branch)
end end
...@@ -542,7 +479,7 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -542,7 +479,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
eq(normal_repo.rugged.branches["master"].target.oid) eq(normal_repo.rugged.branches["master"].target.oid)
) )
head_names = normal_repo.heads.map { |h| h.name } head_names = normal_repo.branches.map { |h| h.name }
expect(head_names).not_to include(new_branch) expect(head_names).not_to include(new_branch)
end end
...@@ -589,10 +526,6 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -589,10 +526,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
expect(@repo.rugged.branches["feature"]).to be_nil expect(@repo.rugged.branches["feature"]).to be_nil
end end
it "should update the repo's #heads collection" do
expect(@repo.heads).not_to include("feature")
end
after(:all) do after(:all) do
FileUtils.rm_rf(TEST_MUTABLE_REPO_PATH) FileUtils.rm_rf(TEST_MUTABLE_REPO_PATH)
ensure_seeds ensure_seeds
...@@ -1292,12 +1225,12 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -1292,12 +1225,12 @@ describe Gitlab::Git::Repository, seed_helper: true do
end end
it 'gets the branches from GitalyClient' do it 'gets the branches from GitalyClient' do
expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches) expect_any_instance_of(Gitlab::GitalyClient::RefService).to receive(:local_branches)
.and_return([]) .and_return([])
@repo.local_branches @repo.local_branches
end end
it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::Ref, :local_branches do it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RefService, :local_branches do
subject { @repo.local_branches } subject { @repo.local_branches }
end end
end end
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::GitalyClient::Commit do describe Gitlab::GitalyClient::CommitService do
let(:diff_stub) { double('Gitaly::Diff::Stub') } let(:diff_stub) { double('Gitaly::DiffService::Stub') }
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:repository) { project.repository } let(:repository) { project.repository }
let(:repository_message) { repository.gitaly_repository } let(:repository_message) { repository.gitaly_repository }
...@@ -16,7 +16,7 @@ describe Gitlab::GitalyClient::Commit do ...@@ -16,7 +16,7 @@ describe Gitlab::GitalyClient::Commit do
right_commit_id: commit.id right_commit_id: commit.id
) )
expect_any_instance_of(Gitaly::Diff::Stub).to receive(:commit_diff).with(request, kind_of(Hash)) expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_diff).with(request, kind_of(Hash))
described_class.new(repository).diff_from_parent(commit) described_class.new(repository).diff_from_parent(commit)
end end
...@@ -31,7 +31,7 @@ describe Gitlab::GitalyClient::Commit do ...@@ -31,7 +31,7 @@ describe Gitlab::GitalyClient::Commit do
right_commit_id: initial_commit.id right_commit_id: initial_commit.id
) )
expect_any_instance_of(Gitaly::Diff::Stub).to receive(:commit_diff).with(request, kind_of(Hash)) expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_diff).with(request, kind_of(Hash))
described_class.new(repository).diff_from_parent(initial_commit) described_class.new(repository).diff_from_parent(initial_commit)
end end
...@@ -61,7 +61,7 @@ describe Gitlab::GitalyClient::Commit do ...@@ -61,7 +61,7 @@ describe Gitlab::GitalyClient::Commit do
right_commit_id: commit.id right_commit_id: commit.id
) )
expect_any_instance_of(Gitaly::Diff::Stub).to receive(:commit_delta).with(request, kind_of(Hash)).and_return([]) expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_delta).with(request, kind_of(Hash)).and_return([])
described_class.new(repository).commit_deltas(commit) described_class.new(repository).commit_deltas(commit)
end end
...@@ -76,7 +76,7 @@ describe Gitlab::GitalyClient::Commit do ...@@ -76,7 +76,7 @@ describe Gitlab::GitalyClient::Commit do
right_commit_id: initial_commit.id right_commit_id: initial_commit.id
) )
expect_any_instance_of(Gitaly::Diff::Stub).to receive(:commit_delta).with(request, kind_of(Hash)).and_return([]) expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_delta).with(request, kind_of(Hash)).and_return([])
described_class.new(repository).commit_deltas(initial_commit) described_class.new(repository).commit_deltas(initial_commit)
end end
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::GitalyClient::Notifications do describe Gitlab::GitalyClient::NotificationService do
describe '#post_receive' do describe '#post_receive' do
let(:project) { create(:empty_project) } let(:project) { create(:empty_project) }
let(:storage_name) { project.repository_storage } let(:storage_name) { project.repository_storage }
...@@ -8,7 +8,7 @@ describe Gitlab::GitalyClient::Notifications do ...@@ -8,7 +8,7 @@ describe Gitlab::GitalyClient::Notifications do
subject { described_class.new(project.repository) } subject { described_class.new(project.repository) }
it 'sends a post_receive message' do it 'sends a post_receive message' do
expect_any_instance_of(Gitaly::Notifications::Stub) expect_any_instance_of(Gitaly::NotificationService::Stub)
.to receive(:post_receive).with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash)) .to receive(:post_receive).with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
subject.post_receive subject.post_receive
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::GitalyClient::Ref do describe Gitlab::GitalyClient::RefService do
let(:project) { create(:empty_project) } let(:project) { create(:empty_project) }
let(:storage_name) { project.repository_storage } let(:storage_name) { project.repository_storage }
let(:relative_path) { project.path_with_namespace + '.git' } let(:relative_path) { project.path_with_namespace + '.git' }
...@@ -8,7 +8,7 @@ describe Gitlab::GitalyClient::Ref do ...@@ -8,7 +8,7 @@ describe Gitlab::GitalyClient::Ref do
describe '#branch_names' do describe '#branch_names' do
it 'sends a find_all_branch_names message' do it 'sends a find_all_branch_names message' do
expect_any_instance_of(Gitaly::Ref::Stub) expect_any_instance_of(Gitaly::RefService::Stub)
.to receive(:find_all_branch_names) .to receive(:find_all_branch_names)
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash)) .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
.and_return([]) .and_return([])
...@@ -19,7 +19,7 @@ describe Gitlab::GitalyClient::Ref do ...@@ -19,7 +19,7 @@ describe Gitlab::GitalyClient::Ref do
describe '#tag_names' do describe '#tag_names' do
it 'sends a find_all_tag_names message' do it 'sends a find_all_tag_names message' do
expect_any_instance_of(Gitaly::Ref::Stub) expect_any_instance_of(Gitaly::RefService::Stub)
.to receive(:find_all_tag_names) .to receive(:find_all_tag_names)
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash)) .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
.and_return([]) .and_return([])
...@@ -30,7 +30,7 @@ describe Gitlab::GitalyClient::Ref do ...@@ -30,7 +30,7 @@ describe Gitlab::GitalyClient::Ref do
describe '#default_branch_name' do describe '#default_branch_name' do
it 'sends a find_default_branch_name message' do it 'sends a find_default_branch_name message' do
expect_any_instance_of(Gitaly::Ref::Stub) expect_any_instance_of(Gitaly::RefService::Stub)
.to receive(:find_default_branch_name) .to receive(:find_default_branch_name)
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash)) .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
.and_return(double(name: 'foo')) .and_return(double(name: 'foo'))
...@@ -41,7 +41,7 @@ describe Gitlab::GitalyClient::Ref do ...@@ -41,7 +41,7 @@ describe Gitlab::GitalyClient::Ref do
describe '#local_branches' do describe '#local_branches' do
it 'sends a find_local_branches message' do it 'sends a find_local_branches message' do
expect_any_instance_of(Gitaly::Ref::Stub) expect_any_instance_of(Gitaly::RefService::Stub)
.to receive(:find_local_branches) .to receive(:find_local_branches)
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash)) .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
.and_return([]) .and_return([])
...@@ -50,7 +50,7 @@ describe Gitlab::GitalyClient::Ref do ...@@ -50,7 +50,7 @@ describe Gitlab::GitalyClient::Ref do
end end
it 'parses and sends the sort parameter' do it 'parses and sends the sort parameter' do
expect_any_instance_of(Gitaly::Ref::Stub) expect_any_instance_of(Gitaly::RefService::Stub)
.to receive(:find_local_branches) .to receive(:find_local_branches)
.with(gitaly_request_with_params(sort_by: :UPDATED_DESC), kind_of(Hash)) .with(gitaly_request_with_params(sort_by: :UPDATED_DESC), kind_of(Hash))
.and_return([]) .and_return([])
...@@ -59,7 +59,7 @@ describe Gitlab::GitalyClient::Ref do ...@@ -59,7 +59,7 @@ describe Gitlab::GitalyClient::Ref do
end end
it 'translates known mismatches on sort param values' do it 'translates known mismatches on sort param values' do
expect_any_instance_of(Gitaly::Ref::Stub) expect_any_instance_of(Gitaly::RefService::Stub)
.to receive(:find_local_branches) .to receive(:find_local_branches)
.with(gitaly_request_with_params(sort_by: :NAME), kind_of(Hash)) .with(gitaly_request_with_params(sort_by: :NAME), kind_of(Hash))
.and_return([]) .and_return([])
......
...@@ -16,9 +16,9 @@ describe Gitlab::GitalyClient, lib: true, skip_gitaly_mock: true do ...@@ -16,9 +16,9 @@ describe Gitlab::GitalyClient, lib: true, skip_gitaly_mock: true do
'default' => { 'gitaly_address' => address } 'default' => { 'gitaly_address' => address }
}) })
expect(Gitaly::Commit::Stub).to receive(:new).with(address, any_args) expect(Gitaly::CommitService::Stub).to receive(:new).with(address, any_args)
described_class.stub(:commit, 'default') described_class.stub(:commit_service, 'default')
end end
end end
...@@ -31,9 +31,9 @@ describe Gitlab::GitalyClient, lib: true, skip_gitaly_mock: true do ...@@ -31,9 +31,9 @@ describe Gitlab::GitalyClient, lib: true, skip_gitaly_mock: true do
'default' => { 'gitaly_address' => prefixed_address } 'default' => { 'gitaly_address' => prefixed_address }
}) })
expect(Gitaly::Commit::Stub).to receive(:new).with(address, any_args) expect(Gitaly::CommitService::Stub).to receive(:new).with(address, any_args)
described_class.stub(:commit, 'default') described_class.stub(:commit_service, 'default')
end end
end end
end end
......
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20170713104829_add_foreign_key_to_merge_requests.rb')
describe AddForeignKeyToMergeRequests, :migration do
let(:projects) { table(:projects) }
let(:merge_requests) { table(:merge_requests) }
let(:pipelines) { table(:ci_pipelines) }
before do
projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce')
pipelines.create!(project_id: projects.first.id,
ref: 'some-branch',
sha: 'abc12345')
# merge request without a pipeline
create_merge_request(head_pipeline_id: nil)
# merge request with non-existent pipeline
create_merge_request(head_pipeline_id: 1234)
# merge reqeust with existing pipeline assigned
create_merge_request(head_pipeline_id: pipelines.first.id)
end
it 'correctly adds a foreign key to head_pipeline_id' do
migrate!
expect(merge_requests.first.head_pipeline_id).to be_nil
expect(merge_requests.second.head_pipeline_id).to be_nil
expect(merge_requests.third.head_pipeline_id).to eq pipelines.first.id
end
def create_merge_request(**opts)
merge_requests.create!(source_project_id: projects.first.id,
target_project_id: projects.first.id,
source_branch: 'some-branch',
target_branch: 'master', **opts)
end
end
...@@ -19,17 +19,15 @@ describe Commit, models: true do ...@@ -19,17 +19,15 @@ describe Commit, models: true do
expect(commit.author).to eq(user) expect(commit.author).to eq(user)
end end
it 'caches the author' do it 'caches the author', :request_store do
allow(RequestStore).to receive(:active?).and_return(true)
user = create(:user, email: commit.author_email) user = create(:user, email: commit.author_email)
expect_any_instance_of(Commit).to receive(:find_author_by_any_email).and_call_original expect(User).to receive(:find_by_any_email).and_call_original
expect(commit.author).to eq(user) expect(commit.author).to eq(user)
key = "commit_author:#{commit.author_email}" key = "Commit:author:#{commit.author_email.downcase}"
expect(RequestStore.store[key]).to eq(user) expect(RequestStore.store[key]).to eq(user)
expect(commit.author).to eq(user) expect(commit.author).to eq(user)
RequestStore.store.clear
end end
end end
......
...@@ -63,6 +63,14 @@ describe Namespace, models: true do ...@@ -63,6 +63,14 @@ describe Namespace, models: true do
it { is_expected.to respond_to(:has_parent?) } it { is_expected.to respond_to(:has_parent?) }
end end
describe 'inclusions' do
it { is_expected.to include_module(Gitlab::VisibilityLevel) }
end
describe '#visibility_level_field' do
it { expect(namespace.visibility_level_field).to eq(:visibility_level) }
end
describe '#to_param' do describe '#to_param' do
it { expect(namespace.to_param).to eq(namespace.full_path) } it { expect(namespace.to_param).to eq(namespace.full_path) }
end end
......
...@@ -103,12 +103,7 @@ describe Ci::BuildPolicy, :models do ...@@ -103,12 +103,7 @@ describe Ci::BuildPolicy, :models do
project.add_developer(user) project.add_developer(user)
end end
context 'when branch build is assigned to is protected' do shared_examples 'protected ref' do
before do
create(:protected_branch, :no_one_can_push,
name: 'some-ref', project: project)
end
context 'when build is a manual action' do context 'when build is a manual action' do
let(:build) do let(:build) do
create(:ci_build, :manual, ref: 'some-ref', pipeline: pipeline) create(:ci_build, :manual, ref: 'some-ref', pipeline: pipeline)
...@@ -130,6 +125,43 @@ describe Ci::BuildPolicy, :models do ...@@ -130,6 +125,43 @@ describe Ci::BuildPolicy, :models do
end end
end end
context 'when build is against a protected branch' do
before do
create(:protected_branch, :no_one_can_push,
name: 'some-ref', project: project)
end
it_behaves_like 'protected ref'
end
context 'when build is against a protected tag' do
before do
create(:protected_tag, :no_one_can_create,
name: 'some-ref', project: project)
build.update(tag: true)
end
it_behaves_like 'protected ref'
end
context 'when build is against a protected tag but it is not a tag' do
before do
create(:protected_tag, :no_one_can_create,
name: 'some-ref', project: project)
end
context 'when build is a manual action' do
let(:build) do
create(:ci_build, :manual, ref: 'some-ref', pipeline: pipeline)
end
it 'includes ability to update build' do
expect(policy).to be_allowed :update_build
end
end
end
context 'when branch build is assigned to is not protected' do context 'when branch build is assigned to is not protected' do
context 'when build is a manual action' do context 'when build is a manual action' do
let(:build) { create(:ci_build, :manual, pipeline: pipeline) } let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
......
...@@ -594,10 +594,10 @@ describe API::Internal do ...@@ -594,10 +594,10 @@ describe API::Internal do
# end # end
# #
# it "calls the Gitaly client with the project's repository" do # it "calls the Gitaly client with the project's repository" do
# expect(Gitlab::GitalyClient::Notifications). # expect(Gitlab::GitalyClient::NotificationService).
# to receive(:new).with(gitlab_git_repository_with(path: project.repository.path)). # to receive(:new).with(gitlab_git_repository_with(path: project.repository.path)).
# and_call_original # and_call_original
# expect_any_instance_of(Gitlab::GitalyClient::Notifications). # expect_any_instance_of(Gitlab::GitalyClient::NotificationService).
# to receive(:post_receive) # to receive(:post_receive)
# #
# post api("/internal/notify_post_receive"), valid_params # post api("/internal/notify_post_receive"), valid_params
...@@ -606,10 +606,10 @@ describe API::Internal do ...@@ -606,10 +606,10 @@ describe API::Internal do
# end # end
# #
# it "calls the Gitaly client with the wiki's repository if it's a wiki" do # it "calls the Gitaly client with the wiki's repository if it's a wiki" do
# expect(Gitlab::GitalyClient::Notifications). # expect(Gitlab::GitalyClient::NotificationService).
# to receive(:new).with(gitlab_git_repository_with(path: project.wiki.repository.path)). # to receive(:new).with(gitlab_git_repository_with(path: project.wiki.repository.path)).
# and_call_original # and_call_original
# expect_any_instance_of(Gitlab::GitalyClient::Notifications). # expect_any_instance_of(Gitlab::GitalyClient::NotificationService).
# to receive(:post_receive) # to receive(:post_receive)
# #
# post api("/internal/notify_post_receive"), valid_wiki_params # post api("/internal/notify_post_receive"), valid_wiki_params
...@@ -618,7 +618,7 @@ describe API::Internal do ...@@ -618,7 +618,7 @@ describe API::Internal do
# end # end
# #
# it "returns 500 if the gitaly call fails" do # it "returns 500 if the gitaly call fails" do
# expect_any_instance_of(Gitlab::GitalyClient::Notifications). # expect_any_instance_of(Gitlab::GitalyClient::NotificationService).
# to receive(:post_receive).and_raise(GRPC::Unavailable) # to receive(:post_receive).and_raise(GRPC::Unavailable)
# #
# post api("/internal/notify_post_receive"), valid_params # post api("/internal/notify_post_receive"), valid_params
...@@ -636,10 +636,10 @@ describe API::Internal do ...@@ -636,10 +636,10 @@ describe API::Internal do
# end # end
# #
# it "calls the Gitaly client with the project's repository" do # it "calls the Gitaly client with the project's repository" do
# expect(Gitlab::GitalyClient::Notifications). # expect(Gitlab::GitalyClient::NotificationService).
# to receive(:new).with(gitlab_git_repository_with(path: project.repository.path)). # to receive(:new).with(gitlab_git_repository_with(path: project.repository.path)).
# and_call_original # and_call_original
# expect_any_instance_of(Gitlab::GitalyClient::Notifications). # expect_any_instance_of(Gitlab::GitalyClient::NotificationService).
# to receive(:post_receive) # to receive(:post_receive)
# #
# post api("/internal/notify_post_receive"), valid_params # post api("/internal/notify_post_receive"), valid_params
...@@ -648,10 +648,10 @@ describe API::Internal do ...@@ -648,10 +648,10 @@ describe API::Internal do
# end # end
# #
# it "calls the Gitaly client with the wiki's repository if it's a wiki" do # it "calls the Gitaly client with the wiki's repository if it's a wiki" do
# expect(Gitlab::GitalyClient::Notifications). # expect(Gitlab::GitalyClient::NotificationService).
# to receive(:new).with(gitlab_git_repository_with(path: project.wiki.repository.path)). # to receive(:new).with(gitlab_git_repository_with(path: project.wiki.repository.path)).
# and_call_original # and_call_original
# expect_any_instance_of(Gitlab::GitalyClient::Notifications). # expect_any_instance_of(Gitlab::GitalyClient::NotificationService).
# to receive(:post_receive) # to receive(:post_receive)
# #
# post api("/internal/notify_post_receive"), valid_wiki_params # post api("/internal/notify_post_receive"), valid_wiki_params
......
...@@ -6,7 +6,7 @@ describe API::Version do ...@@ -6,7 +6,7 @@ describe API::Version do
it 'returns authentication error' do it 'returns authentication error' do
get api('/version') get api('/version')
expect(response).to have_http_status(401) expect(response).to have_gitlab_http_status(401)
end end
end end
...@@ -16,7 +16,7 @@ describe API::Version do ...@@ -16,7 +16,7 @@ describe API::Version do
it 'returns the version information' do it 'returns the version information' do
get api('/version', user) get api('/version', user)
expect(response).to have_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(json_response['version']).to eq(Gitlab::VERSION) expect(json_response['version']).to eq(Gitlab::VERSION)
expect(json_response['revision']).to eq(Gitlab::REVISION) expect(json_response['revision']).to eq(Gitlab::REVISION)
end end
......
require 'spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/migration/hash_index'
describe RuboCop::Cop::Migration::HashIndex do
include CopHelper
subject(:cop) { described_class.new }
context 'in migration' do
before do
allow(cop).to receive(:in_migration?).and_return(true)
end
it 'registers an offense when creating a hash index' do
inspect_source(cop, 'def change; add_index :table, :column, using: :hash; end')
aggregate_failures do
expect(cop.offenses.size).to eq(1)
expect(cop.offenses.map(&:line)).to eq([1])
end
end
it 'registers an offense when creating a concurrent hash index' do
inspect_source(cop, 'def change; add_concurrent_index :table, :column, using: :hash; end')
aggregate_failures do
expect(cop.offenses.size).to eq(1)
expect(cop.offenses.map(&:line)).to eq([1])
end
end
it 'registers an offense when creating a hash index using t.index' do
inspect_source(cop, 'def change; t.index :table, :column, using: :hash; end')
aggregate_failures do
expect(cop.offenses.size).to eq(1)
expect(cop.offenses.map(&:line)).to eq([1])
end
end
end
context 'outside of migration' do
it 'registers no offense' do
inspect_source(cop, 'def change; index :table, :column, using: :hash; end')
expect(cop.offenses.size).to eq(0)
end
end
end
...@@ -383,7 +383,7 @@ describe NotificationService, services: true do ...@@ -383,7 +383,7 @@ describe NotificationService, services: true do
before do before do
build_team(note.project) build_team(note.project)
reset_delivered_emails! reset_delivered_emails!
allow_any_instance_of(Commit).to receive(:author).and_return(@u_committer) allow(note.noteable).to receive(:author).and_return(@u_committer)
update_custom_notification(:new_note, @u_guest_custom, resource: project) update_custom_notification(:new_note, @u_guest_custom, resource: project)
update_custom_notification(:new_note, @u_custom_global) update_custom_notification(:new_note, @u_custom_global)
end end
......
RSpec::Matchers.define :have_gitlab_http_status do |expected|
match do |actual|
expect(actual).to have_http_status(expected)
end
description do
"respond with numeric status code #{expected}"
end
failure_message do |actual|
"expected the response to have status code #{expected.inspect}" \
" but it was #{actual.response_code}. The response was: #{actual.body}"
end
end
...@@ -8,4 +8,8 @@ RSpec.configure do |config| ...@@ -8,4 +8,8 @@ RSpec.configure do |config|
config.after(:each, :sidekiq) do config.after(:each, :sidekiq) do
Sidekiq::Worker.clear_all Sidekiq::Worker.clear_all
end end
config.after(:each, :sidekiq, :redis) do
Sidekiq.redis { |redis| redis.flushdb }
end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment