Commit 64e8b7ca authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'upstream/master' into pipeline-notifications

* upstream/master: (74 commits)
  Fetch locals to avoid undefined method/local error
  Remove author according to the document
  Simplify implementation of entity serializers
  Add documentation for the "Only allow merge requests to be merged if all discussions are resolved" feature
  Complete and improve specs
  Add setting to only allow merge requests to be merged when all discussions are resolved
  Apply `*_params_ce + *_params_ee` pattern to MergeRequestsController
  Add tests for deployment and environment entitites
  Add tests for serialization entities, add user entity
  Only skip group when it's actually a group in the "Share with group" select
  Fix: Todos Filter Shows All Users
  Fix: Guest sees some repository details and gets 404
  Move shared params to a helper
  GrapeDSL for project hooks
  Update commits.scss
  updated styling commit SHA on branches page + added to changelog
  change build list height to show 6,5 builds + improve padding of list, with first/last child selectors
  Refine build entity tests a little
  Expose commit author if author exists
  Ignore builds directory from eslint
  ...
parents f16dca30 86b8fb4e
...@@ -2,4 +2,4 @@ ...@@ -2,4 +2,4 @@
/public/ /public/
/tmp/ /tmp/
/vendor/ /vendor/
/builds/
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
"filenames" "filenames"
], ],
"rules": { "rules": {
"filenames/match-regex": [2, "^[a-z_]+$"] "filenames/match-regex": [2, "^[a-z0-9_]+(.js)?$"]
}, },
"globals": { "globals": {
"$": false, "$": false,
......
...@@ -4,6 +4,7 @@ entry. ...@@ -4,6 +4,7 @@ entry.
## 8.14.0 (2016-11-22) ## 8.14.0 (2016-11-22)
- Show correct environment log in admin/logs (@duk3luk3 !7191)
- Fix Milestone dropdown not stay selected for `Upcoming` and `No Milestone` option !7117 - Fix Milestone dropdown not stay selected for `Upcoming` and `No Milestone` option !7117
- Backups do not fail anymore when using tar on annex and custom_hooks only. !5814 - Backups do not fail anymore when using tar on annex and custom_hooks only. !5814
- Adds user project membership expired event to clarify why user was removed (Callum Dryden) - Adds user project membership expired event to clarify why user was removed (Callum Dryden)
...@@ -17,6 +18,7 @@ entry. ...@@ -17,6 +18,7 @@ entry.
- Fix mobile layout issues in admin user overview page !7087 - Fix mobile layout issues in admin user overview page !7087
- Fix HipChat notifications rendering (airatshigapov, eisnerd) - Fix HipChat notifications rendering (airatshigapov, eisnerd)
- Remove 'Edit' button from wiki edit view !7143 (Hiroyuki Sato) - Remove 'Edit' button from wiki edit view !7143 (Hiroyuki Sato)
- Cleaned up global namespace JS !19661 (Jose Ivan Vargas)
- Refactor Jira service to use jira-ruby gem - Refactor Jira service to use jira-ruby gem
- Improved todos empty state - Improved todos empty state
- Add hover to trash icon in notes !7008 (blackst0ne) - Add hover to trash icon in notes !7008 (blackst0ne)
...@@ -26,6 +28,7 @@ entry. ...@@ -26,6 +28,7 @@ entry.
- Fix sidekiq stats in admin area (blackst0ne) - Fix sidekiq stats in admin area (blackst0ne)
- Added label description as tooltip to issue board list title - Added label description as tooltip to issue board list title
- Created cycle analytics bundle JavaScript file - Created cycle analytics bundle JavaScript file
- Hides container registry when repository is disabled
- API: Fix booleans not recognized as such when using the `to_boolean` helper - API: Fix booleans not recognized as such when using the `to_boolean` helper
- Removed delete branch tooltip !6954 - Removed delete branch tooltip !6954
- Stop unauthorized users dragging on milestone page (blackst0ne) - Stop unauthorized users dragging on milestone page (blackst0ne)
...@@ -45,6 +48,8 @@ entry. ...@@ -45,6 +48,8 @@ entry.
- New issue board list dropdown stays open after adding a new list - New issue board list dropdown stays open after adding a new list
- Fix: Backup restore doesn't clear cache - Fix: Backup restore doesn't clear cache
- Optimize Event queries by removing default order - Optimize Event queries by removing default order
- Add new icon for skipped builds
- Show created icon in pipeline mini-graph
- Remove duplicate links from sidebar - Remove duplicate links from sidebar
- API: Fix project deploy keys 400 and 500 errors when adding an existing key. !6784 (Joshua Welsh) - API: Fix project deploy keys 400 and 500 errors when adding an existing key. !6784 (Joshua Welsh)
- Add Rake task to create/repair GitLab Shell hooks symlinks !5634 - Add Rake task to create/repair GitLab Shell hooks symlinks !5634
...@@ -62,11 +67,16 @@ entry. ...@@ -62,11 +67,16 @@ entry.
- Improve search query parameter naming in /admin/users !7115 (YarNayar) - Improve search query parameter naming in /admin/users !7115 (YarNayar)
- Fix table pagination to be responsive - Fix table pagination to be responsive
- Allow to search for user by secondary email address in the admin interface(/admin/users) !7115 (YarNayar) - Allow to search for user by secondary email address in the admin interface(/admin/users) !7115 (YarNayar)
- Updated commit SHA styling on the branches page.
## 8.13.3 (2016-11-02) ## 8.13.3 (2016-11-02)
- Removes any symlinks before importing a project export file. CVE-2016-9086 - Removes any symlinks before importing a project export file. CVE-2016-9086
- Fixed Import/Export foreign key issue to do with project members. - Fixed Import/Export foreign key issue to do with project members.
- Fix relative links in Markdown wiki when displayed in "Project" tab !7218
- Reduce the overhead to calculate number of open/closed issues and merge requests within the group or project
- Fix project features default values
- Changed build dropdown list length to be 6,5 builds long in the pipeline graph
## 8.13.2 (2016-10-31) ## 8.13.2 (2016-10-31)
......
...@@ -100,11 +100,11 @@ gem 'seed-fu', '~> 2.3.5' ...@@ -100,11 +100,11 @@ gem 'seed-fu', '~> 2.3.5'
# Markdown and HTML processing # Markdown and HTML processing
gem 'html-pipeline', '~> 1.11.0' gem 'html-pipeline', '~> 1.11.0'
gem 'deckar01-task_list', '1.0.5', require: 'task_list/railtie' gem 'deckar01-task_list', '1.0.6', require: 'task_list/railtie'
gem 'gitlab-markup', '~> 1.5.0' gem 'gitlab-markup', '~> 1.5.0'
gem 'redcarpet', '~> 3.3.3' gem 'redcarpet', '~> 3.3.3'
gem 'RedCloth', '~> 4.3.2' gem 'RedCloth', '~> 4.3.2'
gem 'rdoc', '~>3.6' gem 'rdoc', '~> 4.2'
gem 'org-ruby', '~> 0.9.12' gem 'org-ruby', '~> 0.9.12'
gem 'creole', '~> 0.5.0' gem 'creole', '~> 0.5.0'
gem 'wikicloth', '0.8.1' gem 'wikicloth', '0.8.1'
...@@ -260,9 +260,6 @@ group :development do ...@@ -260,9 +260,6 @@ group :development do
gem 'better_errors', '~> 1.0.1' gem 'better_errors', '~> 1.0.1'
gem 'binding_of_caller', '~> 0.7.2' gem 'binding_of_caller', '~> 0.7.2'
# Docs generator
gem 'sdoc', '~> 0.3.20'
# thin instead webrick # thin instead webrick
gem 'thin', '~> 1.7.0' gem 'thin', '~> 1.7.0'
end end
......
...@@ -159,7 +159,7 @@ GEM ...@@ -159,7 +159,7 @@ GEM
database_cleaner (1.5.3) database_cleaner (1.5.3)
debug_inspector (0.0.2) debug_inspector (0.0.2)
debugger-ruby_core_source (1.3.8) debugger-ruby_core_source (1.3.8)
deckar01-task_list (1.0.5) deckar01-task_list (1.0.6)
activesupport (~> 4.0) activesupport (~> 4.0)
html-pipeline html-pipeline
rack (~> 1.0) rack (~> 1.0)
...@@ -567,7 +567,7 @@ GEM ...@@ -567,7 +567,7 @@ GEM
ffi (>= 0.5.0) ffi (>= 0.5.0)
rblineprof (0.3.6) rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3) debugger-ruby_core_source (~> 1.3)
rdoc (3.12.2) rdoc (4.2.2)
json (~> 1.4) json (~> 1.4)
recaptcha (3.0.0) recaptcha (3.0.0)
json json
...@@ -663,9 +663,6 @@ GEM ...@@ -663,9 +663,6 @@ GEM
scss_lint (0.47.1) scss_lint (0.47.1)
rake (>= 0.9, < 11) rake (>= 0.9, < 11)
sass (~> 3.4.15) sass (~> 3.4.15)
sdoc (0.3.20)
json (>= 1.1.3)
rdoc (~> 3.10)
seed-fu (2.3.6) seed-fu (2.3.6)
activerecord (>= 3.1) activerecord (>= 3.1)
activesupport (>= 3.1) activesupport (>= 3.1)
...@@ -843,7 +840,7 @@ DEPENDENCIES ...@@ -843,7 +840,7 @@ DEPENDENCIES
creole (~> 0.5.0) creole (~> 0.5.0)
d3_rails (~> 3.5.0) d3_rails (~> 3.5.0)
database_cleaner (~> 1.5.0) database_cleaner (~> 1.5.0)
deckar01-task_list (= 1.0.5) deckar01-task_list (= 1.0.6)
default_value_for (~> 3.0.0) default_value_for (~> 3.0.0)
devise (~> 4.2) devise (~> 4.2)
devise-two-factor (~> 3.0.0) devise-two-factor (~> 3.0.0)
...@@ -936,7 +933,7 @@ DEPENDENCIES ...@@ -936,7 +933,7 @@ DEPENDENCIES
rails-deprecated_sanitizer (~> 1.0.3) rails-deprecated_sanitizer (~> 1.0.3)
rainbow (~> 2.1.0) rainbow (~> 2.1.0)
rblineprof (~> 0.3.6) rblineprof (~> 0.3.6)
rdoc (~> 3.6) rdoc (~> 4.2)
recaptcha (~> 3.0) recaptcha (~> 3.0)
redcarpet (~> 3.3.3) redcarpet (~> 3.3.3)
redis (~> 3.2) redis (~> 3.2)
...@@ -956,7 +953,6 @@ DEPENDENCIES ...@@ -956,7 +953,6 @@ DEPENDENCIES
sanitize (~> 2.0) sanitize (~> 2.0)
sass-rails (~> 5.0.6) sass-rails (~> 5.0.6)
scss_lint (~> 0.47.0) scss_lint (~> 0.47.0)
sdoc (~> 0.3.20)
seed-fu (~> 2.3.5) seed-fu (~> 2.3.5)
select2-rails (~> 3.5.9) select2-rails (~> 3.5.9)
sentry-raven (~> 2.0.0) sentry-raven (~> 2.0.0)
......
...@@ -80,7 +80,7 @@ GitLab is a Ruby on Rails application that runs on the following software: ...@@ -80,7 +80,7 @@ GitLab is a Ruby on Rails application that runs on the following software:
- Redis 2.8+ - Redis 2.8+
- MySQL or PostgreSQL - MySQL or PostgreSQL
For more information please see the [architecture documentation](http://doc.gitlab.com/ce/development/architecture.html). For more information please see the [architecture documentation](https://docs.gitlab.com/ce/development/architecture.html).
## Third-party applications ## Third-party applications
...@@ -96,7 +96,7 @@ For upgrading information please see our [update page](https://about.gitlab.com/ ...@@ -96,7 +96,7 @@ For upgrading information please see our [update page](https://about.gitlab.com/
## Documentation ## Documentation
All documentation can be found on [doc.gitlab.com/ce/](http://doc.gitlab.com/ce/). All documentation can be found on [docs.gitlab.com/ce/](https://docs.gitlab.com/ce/).
## Getting help ## Getting help
......
...@@ -22,16 +22,14 @@ ...@@ -22,16 +22,14 @@
}); });
}, },
// Return groups list. Filtered by query // Return groups list. Filtered by query
// Only active groups retrieved groups: function(query, options, callback) {
groups: function(query, skip_ldap, skip_groups, callback) {
var url = Api.buildUrl(Api.groupsPath); var url = Api.buildUrl(Api.groupsPath);
return $.ajax({ return $.ajax({
url: url, url: url,
data: { data: $.extend({
search: query, search: query,
skip_groups: skip_groups, per_page: 20
per_page: 20 }, options),
},
dataType: "json" dataType: "json"
}).done(function(groups) { }).done(function(groups) {
return callback(groups); return callback(groups);
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
/*= require jquery-ui/sortable */ /*= require jquery-ui/sortable */
/*= require jquery_ujs */ /*= require jquery_ujs */
/*= require jquery.endless-scroll */ /*= require jquery.endless-scroll */
/*= require jquery.timeago */
/*= require jquery.highlight */ /*= require jquery.highlight */
/*= require jquery.waitforimages */ /*= require jquery.waitforimages */
/*= require jquery.atwho */ /*= require jquery.atwho */
...@@ -54,125 +55,53 @@ ...@@ -54,125 +55,53 @@
/*= require_directory . */ /*= require_directory . */
/*= require fuzzaldrin-plus */ /*= require fuzzaldrin-plus */
(function() { (function () {
window.slugify = function(text) { document.addEventListener('page:fetch', gl.utils.cleanupBeforeFetch);
return text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase(); window.addEventListener('hashchange', gl.utils.shiftWindow);
};
window.ajaxGet = function(url) {
return $.ajax({
type: "GET",
url: url,
dataType: "script"
});
};
window.split = function(val) {
return val.split(/,\s*/);
};
window.extractLast = function(term) {
return split(term).pop();
};
window.rstrip = function(val) {
if (val) {
return val.replace(/\s+$/, '');
} else {
return val;
}
};
// Disable button if text field is empty
window.disableButtonIfEmptyField = function(field_selector, button_selector, event_name) {
event_name = event_name || 'input';
var closest_submit, field;
field = $(field_selector);
closest_submit = field.closest('form').find(button_selector);
if (rstrip(field.val()) === "") {
closest_submit.disable();
}
return field.on(event_name, function() {
if (rstrip($(this).val()) === "") {
return closest_submit.disable();
} else {
return closest_submit.enable();
}
});
};
// Disable button if any input field with given selector is empty window.onload = function () {
window.disableButtonIfAnyEmptyField = function(form, form_selector, button_selector) {
var closest_submit, updateButtons;
closest_submit = form.find(button_selector);
updateButtons = function() {
var filled;
filled = true;
form.find('input').filter(form_selector).each(function() {
return filled = rstrip($(this).val()) !== "" || !$(this).attr('required');
});
if (filled) {
return closest_submit.enable();
} else {
return closest_submit.disable();
}
};
updateButtons();
return form.keyup(updateButtons);
};
window.sanitize = function(str) {
return str.replace(/<(?:.|\n)*?>/gm, '');
};
window.shiftWindow = function() {
return scrollBy(0, -100);
};
document.addEventListener("page:fetch", gl.utils.cleanupBeforeFetch);
window.addEventListener("hashchange", shiftWindow);
window.onload = function() {
// Scroll the window to avoid the topnav bar // Scroll the window to avoid the topnav bar
// https://github.com/twitter/bootstrap/issues/1768 // https://github.com/twitter/bootstrap/issues/1768
if (location.hash) { if (location.hash) {
return setTimeout(shiftWindow, 100); return setTimeout(gl.utils.shiftWindow, 100);
} }
}; };
$(function() { $(function () {
var $body, $document, $sidebarGutterToggle, $window, bootstrapBreakpoint, checkInitialSidebarSize, fitSidebarForSize, flash; var $body = $('body');
$document = $(document); var $document = $(document);
$window = $(window); var $window = $(window);
$body = $('body'); var $sidebarGutterToggle = $('.js-sidebar-toggle');
var $flash = $('.flash-container');
var bootstrapBreakpoint = bp.getBreakpointSize();
var checkInitialSidebarSize;
var fitSidebarForSize;
// Set the default path for all cookies to GitLab's root directory // Set the default path for all cookies to GitLab's root directory
Cookies.defaults.path = gon.relative_url_root || '/'; Cookies.defaults.path = gon.relative_url_root || '/';
gl.utils.preventDisabledButtons(); gl.utils.preventDisabledButtons();
bootstrapBreakpoint = bp.getBreakpointSize(); $('.nav-sidebar').niceScroll({
$(".nav-sidebar").niceScroll({
cursoropacitymax: '0.4', cursoropacitymax: '0.4',
cursorcolor: '#FFF', cursorcolor: '#FFF',
cursorborder: "1px solid #FFF" cursorborder: '1px solid #FFF'
}); });
$(".js-select-on-focus").on("focusin", function() { $('.js-select-on-focus').on('focusin', function () {
return $(this).select().one('mouseup', function(e) { return $(this).select().one('mouseup', function (e) {
return e.preventDefault(); return e.preventDefault();
}); });
// Click a .js-select-on-focus field, select the contents // Click a .js-select-on-focus field, select the contents
// Prevent a mouseup event from deselecting the input // Prevent a mouseup event from deselecting the input
}); });
$('.remove-row').bind('ajax:success', function() { $('.remove-row').bind('ajax:success', function () {
$(this).tooltip('destroy') $(this).tooltip('destroy')
.closest('li') .closest('li')
.fadeOut(); .fadeOut();
}); });
$('.js-remove-tr').bind('ajax:before', function() { $('.js-remove-tr').bind('ajax:before', function () {
return $(this).hide(); return $(this).hide();
}); });
$('.js-remove-tr').bind('ajax:success', function() { $('.js-remove-tr').bind('ajax:success', function () {
return $(this).closest('tr').fadeOut(); return $(this).closest('tr').fadeOut();
}); });
$('select.select2').select2({ $('select.select2').select2({
...@@ -180,8 +109,8 @@ ...@@ -180,8 +109,8 @@
// Initialize select2 selects // Initialize select2 selects
dropdownAutoWidth: true dropdownAutoWidth: true
}); });
$('.js-select2').bind('select2-close', function() { $('.js-select2').bind('select2-close', function () {
return setTimeout((function() { return setTimeout((function () {
$('.select2-container-active').removeClass('select2-container-active'); $('.select2-container-active').removeClass('select2-container-active');
return $(':focus').blur(); return $(':focus').blur();
}), 1); }), 1);
...@@ -191,24 +120,24 @@ ...@@ -191,24 +120,24 @@
$.fn.tooltip.Constructor.DEFAULTS.trigger = 'hover'; $.fn.tooltip.Constructor.DEFAULTS.trigger = 'hover';
$body.tooltip({ $body.tooltip({
selector: '.has-tooltip, [data-toggle="tooltip"]', selector: '.has-tooltip, [data-toggle="tooltip"]',
placement: function(_, el) { placement: function (_, el) {
return $(el).data('placement') || 'bottom'; return $(el).data('placement') || 'bottom';
} }
}); });
$('.trigger-submit').on('change', function() { $('.trigger-submit').on('change', function () {
return $(this).parents('form').submit(); return $(this).parents('form').submit();
// Form submitter // Form submitter
}); });
gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true); gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true);
// Flash // Flash
if ((flash = $(".flash-container")).length > 0) { if ($flash.length > 0) {
flash.click(function() { $flash.click(function () {
return $(this).fadeOut(); return $(this).fadeOut();
}); });
flash.show(); $flash.show();
} }
// Disable form buttons while a form is submitting // Disable form buttons while a form is submitting
$body.on('ajax:complete, ajax:beforeSend, submit', 'form', function(e) { $body.on('ajax:complete, ajax:beforeSend, submit', 'form', function (e) {
var buttons; var buttons;
buttons = $('[type="submit"]', this); buttons = $('[type="submit"]', this);
switch (e.type) { switch (e.type) {
...@@ -219,36 +148,36 @@ ...@@ -219,36 +148,36 @@
return buttons.enable(); return buttons.enable();
} }
}); });
$(document).ajaxError(function(e, xhrObj, xhrSetting, xhrErrorText) { $(document).ajaxError(function (e, xhrObj) {
var ref; var ref = xhrObj.status;
if (xhrObj.status === 401) { if (xhrObj.status === 401) {
return new Flash('You need to be logged in.', 'alert'); return new Flash('You need to be logged in.', 'alert');
} else if ((ref = xhrObj.status) === 404 || ref === 500) { } else if (ref === 404 || ref === 500) {
return new Flash('Something went wrong on our end.', 'alert'); return new Flash('Something went wrong on our end.', 'alert');
} }
}); });
$('.account-box').hover(function() { $('.account-box').hover(function () {
// Show/Hide the profile menu when hovering the account box // Show/Hide the profile menu when hovering the account box
return $(this).toggleClass('hover'); return $(this).toggleClass('hover');
}); });
$document.on('click', '.diff-content .js-show-suppressed-diff', function() { $document.on('click', '.diff-content .js-show-suppressed-diff', function () {
var $container; var $container;
$container = $(this).parent(); $container = $(this).parent();
$container.next('table').show(); $container.next('table').show();
return $container.remove(); return $container.remove();
// Commit show suppressed diff // Commit show suppressed diff
}); });
$('.navbar-toggle').on('click', function() { $('.navbar-toggle').on('click', function () {
$('.header-content .title').toggle(); $('.header-content .title').toggle();
$('.header-content .header-logo').toggle(); $('.header-content .header-logo').toggle();
$('.header-content .navbar-collapse').toggle(); $('.header-content .navbar-collapse').toggle();
return $('.navbar-toggle').toggleClass('active'); return $('.navbar-toggle').toggleClass('active');
}); });
// Show/hide comments on diff // Show/hide comments on diff
$body.on("click", ".js-toggle-diff-comments", function(e) { $body.on('click', '.js-toggle-diff-comments', function (e) {
var $this = $(this); var $this = $(this);
$this.toggleClass('active');
var notesHolders = $this.closest('.diff-file').find('.notes_holder'); var notesHolders = $this.closest('.diff-file').find('.notes_holder');
$this.toggleClass('active');
if ($this.hasClass('active')) { if ($this.hasClass('active')) {
notesHolders.show().find('.hide').show(); notesHolders.show().find('.hide').show();
} else { } else {
...@@ -257,30 +186,27 @@ ...@@ -257,30 +186,27 @@
$this.trigger('blur'); $this.trigger('blur');
return e.preventDefault(); return e.preventDefault();
}); });
$document.off("click", '.js-confirm-danger'); $document.off('click', '.js-confirm-danger');
$document.on("click", '.js-confirm-danger', function(e) { $document.on('click', '.js-confirm-danger', function (e) {
var btn, form, text; var btn = $(e.target);
var form = btn.closest('form');
var text = btn.data('confirm-danger-message');
e.preventDefault(); e.preventDefault();
btn = $(e.target);
text = btn.data("confirm-danger-message");
form = btn.closest("form");
return new ConfirmDangerModal(form, text); return new ConfirmDangerModal(form, text);
}); });
$document.on('click', 'button', function() { $document.on('click', 'button', function () {
return $(this).blur(); return $(this).blur();
}); });
$('input[type="search"]').each(function() { $('input[type="search"]').each(function () {
var $this; var $this = $(this);
$this = $(this);
$this.attr('value', $this.val()); $this.attr('value', $this.val());
}); });
$document.off('keyup', 'input[type="search"]').on('keyup', 'input[type="search"]', function(e) { $document.off('keyup', 'input[type="search"]').on('keyup', 'input[type="search"]', function () {
var $this; var $this;
$this = $(this); $this = $(this);
return $this.attr('value', $this.val()); return $this.attr('value', $this.val());
}); });
$sidebarGutterToggle = $('.js-sidebar-toggle'); $document.off('breakpoint:change').on('breakpoint:change', function (e, breakpoint) {
$document.off('breakpoint:change').on('breakpoint:change', function(e, breakpoint) {
var $gutterIcon; var $gutterIcon;
if (breakpoint === 'sm' || breakpoint === 'xs') { if (breakpoint === 'sm' || breakpoint === 'xs') {
$gutterIcon = $sidebarGutterToggle.find('i'); $gutterIcon = $sidebarGutterToggle.find('i');
...@@ -289,7 +215,7 @@ ...@@ -289,7 +215,7 @@
} }
} }
}); });
fitSidebarForSize = function() { fitSidebarForSize = function () {
var oldBootstrapBreakpoint; var oldBootstrapBreakpoint;
oldBootstrapBreakpoint = bootstrapBreakpoint; oldBootstrapBreakpoint = bootstrapBreakpoint;
bootstrapBreakpoint = bp.getBreakpointSize(); bootstrapBreakpoint = bp.getBreakpointSize();
...@@ -297,13 +223,13 @@ ...@@ -297,13 +223,13 @@
return $document.trigger('breakpoint:change', [bootstrapBreakpoint]); return $document.trigger('breakpoint:change', [bootstrapBreakpoint]);
} }
}; };
checkInitialSidebarSize = function() { checkInitialSidebarSize = function () {
bootstrapBreakpoint = bp.getBreakpointSize(); bootstrapBreakpoint = bp.getBreakpointSize();
if (bootstrapBreakpoint === "xs" || "sm") { if (bootstrapBreakpoint === 'xs' || 'sm') {
return $document.trigger('breakpoint:change', [bootstrapBreakpoint]); return $document.trigger('breakpoint:change', [bootstrapBreakpoint]);
} }
}; };
$window.off("resize.app").on("resize.app", function(e) { $window.off('resize.app').on('resize.app', function () {
return fitSidebarForSize(); return fitSidebarForSize();
}); });
gl.awardsHandler = new AwardsHandler(); gl.awardsHandler = new AwardsHandler();
......
/* eslint-disable */ /* eslint-disable */
(function() { (function() {
this.AwardsHandler = (function() { this.AwardsHandler = (function() {
const FROM_SENTENCE_REGEX = /(?:, and | and |, )/; //For separating lists produced by ruby's Array#toSentence var FROM_SENTENCE_REGEX = /(?:, and | and |, )/; //For separating lists produced by ruby's Array#toSentence
function AwardsHandler() { function AwardsHandler() {
this.aliases = gl.emojiAliases(); this.aliases = gl.emojiAliases();
$(document).off('click', '.js-add-award').on('click', '.js-add-award', (function(_this) { $(document).off('click', '.js-add-award').on('click', '.js-add-award', (function(_this) {
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
submit.disable(); submit.disable();
$('.js-confirm-danger-input').off('input'); $('.js-confirm-danger-input').off('input');
$('.js-confirm-danger-input').on('input', function() { $('.js-confirm-danger-input').on('input', function() {
if (rstrip($(this).val()) === project_path) { if (gl.utils.rstrip($(this).val()) === project_path) {
return submit.enable(); return submit.enable();
} else { } else {
return submit.disable(); return submit.disable();
......
...@@ -126,8 +126,8 @@ ...@@ -126,8 +126,8 @@
} }
return { return {
username: m.username, username: m.username,
title: sanitize(title), title: gl.utils.sanitize(title),
search: sanitize(m.username + " " + m.name) search: gl.utils.sanitize(m.username + " " + m.name)
}; };
}); });
} }
...@@ -159,7 +159,7 @@ ...@@ -159,7 +159,7 @@
} }
return { return {
id: i.iid, id: i.iid,
title: sanitize(i.title), title: gl.utils.sanitize(i.title),
search: i.iid + " " + i.title search: i.iid + " " + i.title
}; };
}); });
...@@ -189,7 +189,7 @@ ...@@ -189,7 +189,7 @@
} }
return { return {
id: m.iid, id: m.iid,
title: sanitize(m.title), title: gl.utils.sanitize(m.title),
search: "" + m.title search: "" + m.title
}; };
}); });
...@@ -222,7 +222,7 @@ ...@@ -222,7 +222,7 @@
} }
return { return {
id: m.iid, id: m.iid,
title: sanitize(m.title), title: gl.utils.sanitize(m.title),
search: m.iid + " " + m.title search: m.iid + " " + m.title
}; };
}); });
...@@ -240,9 +240,9 @@ ...@@ -240,9 +240,9 @@
var sanitizeLabelTitle; var sanitizeLabelTitle;
sanitizeLabelTitle = function(title) { sanitizeLabelTitle = function(title) {
if (/[\w\?&]+\s+[\w\?&]+/g.test(title)) { if (/[\w\?&]+\s+[\w\?&]+/g.test(title)) {
return "\"" + (sanitize(title)) + "\""; return "\"" + (gl.utils.sanitize(title)) + "\"";
} else { } else {
return sanitize(title); return gl.utils.sanitize(title);
} }
}; };
return $.map(merges, function(m) { return $.map(merges, function(m) {
......
...@@ -24,8 +24,8 @@ ...@@ -24,8 +24,8 @@
if (isNewForm) { if (isNewForm) {
this.form.find('.div-dropzone').remove(); this.form.find('.div-dropzone').remove();
this.form.addClass('gfm-form'); this.form.addClass('gfm-form');
disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button'));
// remove notify commit author checkbox for non-commit notes // remove notify commit author checkbox for non-commit notes
gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button'));
GitLab.GfmAutoComplete.setup(this.form.find('.js-gfm-input')); GitLab.GfmAutoComplete.setup(this.form.find('.js-gfm-input'));
new DropzoneInput(this.form); new DropzoneInput(this.form);
autosize(this.textarea); autosize(this.textarea);
......
...@@ -6,15 +6,16 @@ ...@@ -6,15 +6,16 @@
function GroupsSelect() { function GroupsSelect() {
$('.ajax-groups-select').each((function(_this) { $('.ajax-groups-select').each((function(_this) {
return function(i, select) { return function(i, select) {
var skip_ldap, skip_groups; var all_available, skip_groups;
skip_ldap = $(select).hasClass('skip_ldap'); all_available = $(select).data('all-available');
skip_groups = $(select).data('skip-groups') || []; skip_groups = $(select).data('skip-groups') || [];
return $(select).select2({ return $(select).select2({
placeholder: "Search for a group", placeholder: "Search for a group",
multiple: $(select).hasClass('multiselect'), multiple: $(select).hasClass('multiselect'),
minimumInputLength: 0, minimumInputLength: 0,
query: function(query) { query: function(query) {
return Api.groups(query.term, skip_ldap, skip_groups, function(groups) { options = { all_available: all_available, skip_groups: skip_groups };
return Api.groups(query.term, options, function(groups) {
var data; var data;
data = { data = {
results: groups results: groups
......
...@@ -24,6 +24,81 @@ ...@@ -24,6 +24,81 @@
return null; return null;
} }
}; };
w.gl.utils.ajaxGet = function(url) {
return $.ajax({
type: "GET",
url: url,
dataType: "script"
});
};
w.gl.utils.split = function(val) {
return val.split(/,\s*/);
};
w.gl.utils.extractLast = function(term) {
return this.split(term).pop();
};
w.gl.utils.rstrip = function rstrip(val) {
if (val) {
return val.replace(/\s+$/, '');
} else {
return val;
}
};
w.gl.utils.disableButtonIfEmptyField = function(field_selector, button_selector, event_name) {
event_name = event_name || 'input';
var closest_submit, field, that;
that = this;
field = $(field_selector);
closest_submit = field.closest('form').find(button_selector);
if (this.rstrip(field.val()) === "") {
closest_submit.disable();
}
return field.on(event_name, function() {
if (that.rstrip($(this).val()) === "") {
return closest_submit.disable();
} else {
return closest_submit.enable();
}
});
};
w.gl.utils.disableButtonIfAnyEmptyField = function(form, form_selector, button_selector) {
var closest_submit, updateButtons;
closest_submit = form.find(button_selector);
updateButtons = function() {
var filled;
filled = true;
form.find('input').filter(form_selector).each(function() {
return filled = this.rstrip($(this).val()) !== "" || !$(this).attr('required');
});
if (filled) {
return closest_submit.enable();
} else {
return closest_submit.disable();
}
};
updateButtons();
return form.keyup(updateButtons);
};
w.gl.utils.sanitize = function(str) {
return str.replace(/<(?:.|\n)*?>/gm, '');
};
w.gl.utils.unbindEvents = function() {
return $(document).off('scroll');
};
w.gl.utils.shiftWindow = function() {
return w.scrollBy(0, -100);
};
gl.utils.updateTooltipTitle = function($tooltipEl, newTitle) { gl.utils.updateTooltipTitle = function($tooltipEl, newTitle) {
return $tooltipEl.tooltip('destroy').attr('title', newTitle).tooltip('fixTitle'); return $tooltipEl.tooltip('destroy').attr('title', newTitle).tooltip('fixTitle');
}; };
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
$('.project_member, .group_member').off('ajax:success').on('ajax:success', this.removeRow); $('.project_member, .group_member').off('ajax:success').on('ajax:success', this.removeRow);
$('.js-member-update-control').off('change').on('change', this.formSubmit); $('.js-member-update-control').off('change').on('change', this.formSubmit);
$('.js-edit-member-form').off('ajax:success').on('ajax:success', this.formSuccess); $('.js-edit-member-form').off('ajax:success').on('ajax:success', this.formSuccess);
disableButtonIfEmptyField('#user_ids', 'input[name=commit]', 'change'); gl.utils.disableButtonIfEmptyField('#user_ids', 'input[name=commit]', 'change');
} }
removeRow(e) { removeRow(e) {
......
...@@ -45,7 +45,9 @@ ...@@ -45,7 +45,9 @@
}; };
ProjectNew.prototype.toggleRepoVisibility = function () { ProjectNew.prototype.toggleRepoVisibility = function () {
var $repoAccessLevel = $('.js-repo-access-level select'); var $repoAccessLevel = $('.js-repo-access-level select'),
containerRegistry = document.querySelectorAll('.js-container-registry')[0],
containerRegistryCheckbox = document.getElementById('project_container_registry_enabled');
this.$repoSelects.find("option[value='" + $repoAccessLevel.val() + "']") this.$repoSelects.find("option[value='" + $repoAccessLevel.val() + "']")
.nextAll() .nextAll()
...@@ -70,8 +72,17 @@ ...@@ -70,8 +72,17 @@
if (selectedVal) { if (selectedVal) {
this.$repoSelects.removeClass('disabled'); this.$repoSelects.removeClass('disabled');
if (containerRegistry) {
containerRegistry.style.display = '';
}
} else { } else {
this.$repoSelects.addClass('disabled'); this.$repoSelects.addClass('disabled');
if (containerRegistry) {
containerRegistry.style.display = 'none';
containerRegistryCheckbox.checked = false;
}
} }
}.bind(this)); }.bind(this));
}; };
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
data = groups.concat(projects); data = groups.concat(projects);
return finalCallback(data); return finalCallback(data);
}; };
return Api.groups(term, false, false, groupsCallback); return Api.groups(term, {}, groupsCallback);
}; };
} else { } else {
projectsCallback = finalCallback; projectsCallback = finalCallback;
...@@ -73,7 +73,7 @@ ...@@ -73,7 +73,7 @@
data = groups.concat(projects); data = groups.concat(projects);
return finalCallback(data); return finalCallback(data);
}; };
return Api.groups(query.term, false, false, groupsCallback); return Api.groups(query.term, {}, groupsCallback);
}; };
} else { } else {
projectsCallback = finalCallback; projectsCallback = finalCallback;
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
filterable: true, filterable: true,
fieldName: 'group_id', fieldName: 'group_id',
data: function(term, callback) { data: function(term, callback) {
return Api.groups(term, false, false, function(data) { return Api.groups(term, {}, function(data) {
data.unshift({ data.unshift({
name: 'Any' name: 'Any'
}); });
......
...@@ -23,6 +23,8 @@ ...@@ -23,6 +23,8 @@
$dropdown = $(dropdown); $dropdown = $(dropdown);
options.projectId = $dropdown.data('project-id'); options.projectId = $dropdown.data('project-id');
options.showCurrentUser = $dropdown.data('current-user'); options.showCurrentUser = $dropdown.data('current-user');
options.todoFilter = $dropdown.data('todo-filter');
options.todoStateFilter = $dropdown.data('todo-state-filter');
showNullUser = $dropdown.data('null-user'); showNullUser = $dropdown.data('null-user');
showMenuAbove = $dropdown.data('showMenuAbove'); showMenuAbove = $dropdown.data('showMenuAbove');
showAnyUser = $dropdown.data('any-user'); showAnyUser = $dropdown.data('any-user');
...@@ -394,6 +396,8 @@ ...@@ -394,6 +396,8 @@
project_id: options.projectId || null, project_id: options.projectId || null,
group_id: options.groupId || null, group_id: options.groupId || null,
skip_ldap: options.skipLdap || null, skip_ldap: options.skipLdap || null,
todo_filter: options.todoFilter || null,
todo_state_filter: options.todoStateFilter || null,
current_user: options.showCurrentUser || null, current_user: options.showCurrentUser || null,
push_code_to_protected_branches: options.pushCodeToProtectedBranches || null, push_code_to_protected_branches: options.pushCodeToProtectedBranches || null,
author_id: options.authorId || null, author_id: options.authorId || null,
......
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
color: $dropdown-toggle-color; color: $dropdown-toggle-color;
font-size: 15px; font-size: 15px;
text-align: left; text-align: left;
border: 1px solid $dropdown-toggle-border-color; border: 1px solid $border-color;
border-radius: $border-radius-base; border-radius: $border-radius-base;
outline: 0; outline: 0;
text-overflow: ellipsis; text-overflow: ellipsis;
...@@ -45,11 +45,9 @@ ...@@ -45,11 +45,9 @@
.fa { .fa {
position: absolute; position: absolute;
top: 50%; top: 10px;
right: 6px; right: 8px;
margin-top: -6px;
color: $dropdown-toggle-icon-color; color: $dropdown-toggle-icon-color;
font-size: 10px;
&.fa-spinner { &.fa-spinner {
font-size: 16px; font-size: 16px;
......
...@@ -27,9 +27,9 @@ ...@@ -27,9 +27,9 @@
height: 0; height: 0;
margin-left: 2px; margin-left: 2px;
vertical-align: middle; vertical-align: middle;
border-top: $caret-width-base dashed; border-top: 5px dashed;
border-right: $caret-width-base solid transparent; border-right: 5px solid transparent;
border-left: $caret-width-base solid transparent; border-left: 5px solid transparent;
color: $gray-darkest; color: $gray-darkest;
} }
} }
......
...@@ -164,7 +164,22 @@ ...@@ -164,7 +164,22 @@
.branch-commit { .branch-commit {
color: $gl-gray; color: $gl-gray;
.commit-id, .commit-icon {
text-align: center;
display: inline-block;
svg {
height: 14px;
width: 14px;
vertical-align: middle;
fill: $table-text-gray;
}
}
.commit-id {
color: $gl-link-color;
}
.commit-row-message { .commit-row-message {
color: $gl-gray; color: $gl-gray;
} }
......
...@@ -55,6 +55,10 @@ ...@@ -55,6 +55,10 @@
float: left; float: left;
} }
.file-buttons {
font-size: 0;
}
.select2 { .select2 {
float: right; float: right;
} }
......
// CI icon colors
.ci-status-icon {
&-created {
fill: $gray-darkest;
}
&-skipped,
&-canceled {
fill: $gl-text-color;
}
}
...@@ -85,6 +85,11 @@ ...@@ -85,6 +85,11 @@
} }
.commit-link { .commit-link {
a {
&:focus {
text-decoration: none;
}
}
.ci-status { .ci-status {
...@@ -439,7 +444,7 @@ ...@@ -439,7 +444,7 @@
} }
.grouped-pipeline-dropdown { .grouped-pipeline-dropdown {
padding: 8px 0; padding: 0;
width: 186px; width: 186px;
left: auto; left: auto;
right: -197px; right: -197px;
...@@ -448,6 +453,14 @@ ...@@ -448,6 +453,14 @@
ul { ul {
max-height: 245px; max-height: 245px;
overflow: auto; overflow: auto;
li:first-child {
padding-top: 8px;
}
li:last-child {
padding-bottom: 8px;
}
} }
a { a {
......
...@@ -6,7 +6,8 @@ ...@@ -6,7 +6,8 @@
white-space: nowrap; white-space: nowrap;
border-radius: 4px; border-radius: 4px;
&:hover { &:hover,
&:focus {
text-decoration: none; text-decoration: none;
} }
......
...@@ -116,8 +116,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -116,8 +116,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:metrics_packet_size, :metrics_packet_size,
:send_user_confirmation_email, :send_user_confirmation_email,
:container_registry_token_expire_delay, :container_registry_token_expire_delay,
:repository_storage,
:enabled_git_access_protocol, :enabled_git_access_protocol,
repository_storages: [],
restricted_visibility_levels: [], restricted_visibility_levels: [],
import_sources: [], import_sources: [],
disabled_oauth_sign_in_sources: [] disabled_oauth_sign_in_sources: []
......
...@@ -192,9 +192,10 @@ class ApplicationController < ActionController::Base ...@@ -192,9 +192,10 @@ class ApplicationController < ActionController::Base
end end
# JSON for infinite scroll via Pager object # JSON for infinite scroll via Pager object
def pager_json(partial, count) def pager_json(partial, count, locals = {})
html = render_to_string( html = render_to_string(
partial, partial,
locals: locals,
layout: false, layout: false,
formats: [:html] formats: [:html]
) )
......
...@@ -11,9 +11,13 @@ class AutocompleteController < ApplicationController ...@@ -11,9 +11,13 @@ class AutocompleteController < ApplicationController
@users = @users.reorder(:name) @users = @users.reorder(:name)
@users = @users.page(params[:page]) @users = @users.page(params[:page])
if params[:todo_filter].present?
@users = @users.todo_authors(current_user.id, params[:todo_state_filter])
end
if params[:search].blank? if params[:search].blank?
# Include current user if available to filter by "Me" # Include current user if available to filter by "Me"
if params[:current_user] && current_user if params[:current_user].present? && current_user
@users = [*@users, current_user] @users = [*@users, current_user]
end end
......
...@@ -26,8 +26,15 @@ class Projects::CommitsController < Projects::ApplicationController ...@@ -26,8 +26,15 @@ class Projects::CommitsController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.html format.html
format.json { pager_json("projects/commits/_commits", @commits.size) }
format.atom { render layout: false } format.atom { render layout: false }
format.json do
pager_json(
'projects/commits/_commits',
@commits.size,
project: @project,
ref: @ref)
end
end end
end end
end end
...@@ -7,7 +7,7 @@ class Projects::GroupLinksController < Projects::ApplicationController ...@@ -7,7 +7,7 @@ class Projects::GroupLinksController < Projects::ApplicationController
@group_links = project.project_group_links.all @group_links = project.project_group_links.all
@skip_groups = @group_links.pluck(:group_id) @skip_groups = @group_links.pluck(:group_id)
@skip_groups << project.group.try(:id) @skip_groups << project.namespace_id unless project.personal?
end end
def create def create
......
...@@ -352,13 +352,23 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -352,13 +352,23 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def branch_from def branch_from
# This is always source # This is always source
@source_project = @merge_request.nil? ? @project : @merge_request.source_project @source_project = @merge_request.nil? ? @project : @merge_request.source_project
@commit = @repository.commit(params[:ref]) if params[:ref].present?
if params[:ref].present?
@ref = params[:ref]
@commit = @repository.commit(@ref)
end
render layout: false render layout: false
end end
def branch_to def branch_to
@target_project = selected_target_project @target_project = selected_target_project
@commit = @target_project.commit(params[:ref]) if params[:ref].present?
if params[:ref].present?
@ref = params[:ref]
@commit = @target_project.commit(@ref)
end
render layout: false render layout: false
end end
...@@ -589,12 +599,27 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -589,12 +599,27 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def merge_request_params def merge_request_params
params.require(:merge_request).permit( params.require(:merge_request)
:title, :assignee_id, :source_project_id, :source_branch, .permit(merge_request_params_ce)
:target_project_id, :target_branch, :milestone_id, end
:state_event, :description, :task_num, :force_remove_source_branch,
:lock_version, label_ids: [] def merge_request_params_ce
) [
:assignee_id,
:description,
:force_remove_source_branch,
:lock_version,
:milestone_id,
:source_branch,
:source_project_id,
:state_event,
:target_branch,
:target_project_id,
:task_num,
:title,
label_ids: []
]
end end
def merge_params def merge_params
......
...@@ -335,6 +335,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -335,6 +335,7 @@ class ProjectsController < Projects::ApplicationController
:visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar,
:build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
:public_builds, :only_allow_merge_if_build_succeeds, :request_access_enabled, :public_builds, :only_allow_merge_if_build_succeeds, :request_access_enabled,
:only_allow_merge_if_all_discussions_are_resolved,
:lfs_enabled, project_feature_attributes :lfs_enabled, project_feature_attributes
) )
end end
......
...@@ -93,11 +93,11 @@ module ApplicationSettingsHelper ...@@ -93,11 +93,11 @@ module ApplicationSettingsHelper
end end
end end
def repository_storage_options_for_select def repository_storages_options_for_select
options = Gitlab.config.repositories.storages.map do |name, path| options = Gitlab.config.repositories.storages.map do |name, path|
["#{name} - #{path}", name] ["#{name} - #{path}", name]
end end
options_for_select(options, @application_setting.repository_storage) options_for_select(options, @application_setting.repository_storages)
end end
end end
...@@ -47,17 +47,27 @@ module CiStatusHelper ...@@ -47,17 +47,27 @@ module CiStatusHelper
'icon_play' 'icon_play'
when 'created' when 'created'
'icon_status_created' 'icon_status_created'
when 'skipped'
'icon_status_skipped'
else else
'icon_status_cancel' 'icon_status_canceled'
end end
custom_icon(icon_name) custom_icon(icon_name)
end end
def render_commit_status(commit, tooltip_placement: 'auto left') def render_commit_status(commit, ref: nil, tooltip_placement: 'auto left')
project = commit.project project = commit.project
path = pipelines_namespace_project_commit_path(project.namespace, project, commit) path = pipelines_namespace_project_commit_path(
render_status_with_link('commit', commit.status, path, tooltip_placement: tooltip_placement) project.namespace,
project,
commit)
render_status_with_link(
'commit',
commit.status(ref),
path,
tooltip_placement: tooltip_placement)
end end
def render_pipeline_status(pipeline, tooltip_placement: 'auto left') def render_pipeline_status(pipeline, tooltip_placement: 'auto left')
......
...@@ -25,9 +25,11 @@ module CommitsHelper ...@@ -25,9 +25,11 @@ module CommitsHelper
end end
end end
def commit_to_html(commit, project, inline = true) def commit_to_html(commit, ref, project)
template = inline ? "inline_commit" : "commit" render 'projects/commits/commit',
render "projects/commits/#{template}", commit: commit, project: project unless commit.nil? commit: commit,
ref: ref,
project: project
end end
# Breadcrumb links for a Project and, if applicable, a tree path # Breadcrumb links for a Project and, if applicable, a tree path
......
...@@ -43,7 +43,7 @@ module DropdownsHelper ...@@ -43,7 +43,7 @@ module DropdownsHelper
default_label = data_attr[:default_label] default_label = data_attr[:default_label]
content_tag(:button, class: "dropdown-menu-toggle #{options[:toggle_class] if options.has_key?(:toggle_class)}", id: (options[:id] if options.has_key?(:id)), type: "button", data: data_attr) do content_tag(:button, class: "dropdown-menu-toggle #{options[:toggle_class] if options.has_key?(:toggle_class)}", id: (options[:id] if options.has_key?(:id)), type: "button", data: data_attr) do
output = content_tag(:span, toggle_text, class: "dropdown-toggle-text #{'is-default' if toggle_text == default_label}") output = content_tag(:span, toggle_text, class: "dropdown-toggle-text #{'is-default' if toggle_text == default_label}")
output << icon('chevron-down') output << icon('caret-down')
output.html_safe output.html_safe
end end
end end
......
...@@ -18,6 +18,7 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -18,6 +18,7 @@ class ApplicationSetting < ActiveRecord::Base
serialize :disabled_oauth_sign_in_sources, Array serialize :disabled_oauth_sign_in_sources, Array
serialize :domain_whitelist, Array serialize :domain_whitelist, Array
serialize :domain_blacklist, Array serialize :domain_blacklist, Array
serialize :repository_storages
cache_markdown_field :sign_in_text cache_markdown_field :sign_in_text
cache_markdown_field :help_page_text cache_markdown_field :help_page_text
...@@ -74,9 +75,8 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -74,9 +75,8 @@ class ApplicationSetting < ActiveRecord::Base
presence: true, presence: true,
numericality: { only_integer: true, greater_than: 0 } numericality: { only_integer: true, greater_than: 0 }
validates :repository_storage, validates :repository_storages, presence: true
presence: true, validate :check_repository_storages
inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } }
validates :enabled_git_access_protocol, validates :enabled_git_access_protocol,
inclusion: { in: %w(ssh http), allow_blank: true, allow_nil: true } inclusion: { in: %w(ssh http), allow_blank: true, allow_nil: true }
...@@ -166,7 +166,7 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -166,7 +166,7 @@ class ApplicationSetting < ActiveRecord::Base
disabled_oauth_sign_in_sources: [], disabled_oauth_sign_in_sources: [],
send_user_confirmation_email: false, send_user_confirmation_email: false,
container_registry_token_expire_delay: 5, container_registry_token_expire_delay: 5,
repository_storage: 'default', repository_storages: ['default'],
user_default_external: false, user_default_external: false,
) )
end end
...@@ -201,6 +201,29 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -201,6 +201,29 @@ class ApplicationSetting < ActiveRecord::Base
self.domain_blacklist_raw = file.read self.domain_blacklist_raw = file.read
end end
def repository_storages
value = read_attribute(:repository_storages)
value = [value] if value.is_a?(String)
value = [] if value.nil?
value
end
# repository_storage is still required in the API. Remove in 9.0
def repository_storage
repository_storages.first
end
def repository_storage=(value)
self.repository_storages = [value]
end
# Choose one of the available repository storage options. Currently all have
# equal weighting.
def pick_repository_storage
repository_storages.sample
end
def runners_registration_token def runners_registration_token
ensure_runners_registration_token! ensure_runners_registration_token!
end end
...@@ -208,4 +231,12 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -208,4 +231,12 @@ class ApplicationSetting < ActiveRecord::Base
def health_check_access_token def health_check_access_token
ensure_health_check_access_token! ensure_health_check_access_token!
end end
private
def check_repository_storages
invalid = repository_storages - Gitlab.config.repositories.storages.keys
errors.add(:repository_storages, "can't include: #{invalid.join(", ")}") unless
invalid.empty?
end
end end
...@@ -226,12 +226,19 @@ class Commit ...@@ -226,12 +226,19 @@ class Commit
end end
def pipelines def pipelines
@pipeline ||= project.pipelines.where(sha: sha) project.pipelines.where(sha: sha)
end end
def status def status(ref = nil)
return @status if defined?(@status) @statuses ||= {}
@status ||= pipelines.status
if @statuses.key?(ref)
@statuses[ref]
elsif ref
@statuses[ref] = pipelines.where(ref: ref).status
else
@statuses[ref] = pipelines.status
end
end end
def revert_branch_name def revert_branch_name
......
...@@ -425,6 +425,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -425,6 +425,7 @@ class MergeRequest < ActiveRecord::Base
return false if work_in_progress? return false if work_in_progress?
return false if broken? return false if broken?
return false unless skip_ci_check || mergeable_ci_state? return false unless skip_ci_check || mergeable_ci_state?
return false unless mergeable_discussions_state?
true true
end end
...@@ -441,11 +442,11 @@ class MergeRequest < ActiveRecord::Base ...@@ -441,11 +442,11 @@ class MergeRequest < ActiveRecord::Base
end end
def should_remove_source_branch? def should_remove_source_branch?
merge_params['should_remove_source_branch'].present? Gitlab::Utils.to_boolean(merge_params['should_remove_source_branch'])
end end
def force_remove_source_branch? def force_remove_source_branch?
merge_params['force_remove_source_branch'].present? Gitlab::Utils.to_boolean(merge_params['force_remove_source_branch'])
end end
def remove_source_branch? def remove_source_branch?
...@@ -493,6 +494,12 @@ class MergeRequest < ActiveRecord::Base ...@@ -493,6 +494,12 @@ class MergeRequest < ActiveRecord::Base
discussions_resolvable? && diff_discussions.none?(&:to_be_resolved?) discussions_resolvable? && diff_discussions.none?(&:to_be_resolved?)
end end
def mergeable_discussions_state?
return true unless project.only_allow_merge_if_all_discussions_are_resolved?
discussions_resolved?
end
def hook_attrs def hook_attrs
attrs = { attrs = {
source: source_project.try(:hook_attrs), source: source_project.try(:hook_attrs),
......
...@@ -28,7 +28,7 @@ class Project < ActiveRecord::Base ...@@ -28,7 +28,7 @@ class Project < ActiveRecord::Base
default_value_for :archived, false default_value_for :archived, false
default_value_for :visibility_level, gitlab_config_features.visibility_level default_value_for :visibility_level, gitlab_config_features.visibility_level
default_value_for :container_registry_enabled, gitlab_config_features.container_registry default_value_for :container_registry_enabled, gitlab_config_features.container_registry
default_value_for(:repository_storage) { current_application_settings.repository_storage } default_value_for(:repository_storage) { current_application_settings.pick_repository_storage }
default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled } default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled }
default_value_for :issues_enabled, gitlab_config_features.issues default_value_for :issues_enabled, gitlab_config_features.issues
default_value_for :merge_requests_enabled, gitlab_config_features.merge_requests default_value_for :merge_requests_enabled, gitlab_config_features.merge_requests
...@@ -1067,10 +1067,6 @@ class Project < ActiveRecord::Base ...@@ -1067,10 +1067,6 @@ class Project < ActiveRecord::Base
forks.count forks.count
end end
def find_label(name)
labels.find_by(name: name)
end
def origin_merge_requests def origin_merge_requests
merge_requests.where(source_project_id: self.id) merge_requests.where(source_project_id: self.id)
end end
......
...@@ -173,6 +173,7 @@ class User < ActiveRecord::Base ...@@ -173,6 +173,7 @@ class User < ActiveRecord::Base
scope :active, -> { with_state(:active) } scope :active, -> { with_state(:active) }
scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all } scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') } scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') }
scope :todo_authors, ->(user_id, state) { where(id: Todo.where(user_id: user_id, state: state).select(:author_id)) }
def self.with_two_factor def self.with_two_factor
joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id"). joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id").
......
class BaseSerializer
def initialize(parameters = {})
@request = EntityRequest.new(parameters)
end
def represent(resource, opts = {})
self.class.entity_class
.represent(resource, opts.merge(request: @request))
end
def self.entity(entity_class)
@entity_class ||= entity_class
end
def self.entity_class
@entity_class
end
end
class BuildEntity < Grape::Entity
include RequestAwareEntity
expose :id
expose :name
expose :build_url do |build|
url_to(:namespace_project_build, build)
end
expose :retry_url do |build|
url_to(:retry_namespace_project_build, build)
end
expose :play_url, if: ->(build, _) { build.manual? } do |build|
url_to(:play_namespace_project_build, build)
end
private
def url_to(route, build)
send("#{route}_url", build.project.namespace, build.project, build)
end
end
class CommitEntity < API::Entities::RepoCommit
include RequestAwareEntity
expose :author, using: UserEntity
expose :commit_url do |commit|
namespace_project_tree_url(
request.project.namespace,
request.project,
id: commit.id)
end
end
class DeploymentEntity < Grape::Entity
include RequestAwareEntity
expose :id
expose :iid
expose :sha
expose :ref do
expose :name do |deployment|
deployment.ref
end
expose :ref_url do |deployment|
namespace_project_tree_url(
deployment.project.namespace,
deployment.project,
id: deployment.ref)
end
end
expose :tag
expose :last?
expose :user, using: UserEntity
expose :commit, using: CommitEntity
expose :deployable, using: BuildEntity
expose :manual_actions, using: BuildEntity
end
class EntityRequest
# We use EntityRequest object to collect parameters and variables
# from the controller. Because options that are being passed to the entity
# do appear in each entity object in the chain, we need a way to pass data
# that is present in the controller (see #20045).
#
def initialize(parameters)
parameters.each do |key, value|
define_singleton_method(key) { value }
end
end
end
class EnvironmentEntity < Grape::Entity
include RequestAwareEntity
expose :id
expose :name
expose :state
expose :external_url
expose :environment_type
expose :last_deployment, using: DeploymentEntity
expose :stoppable?
expose :environment_url do |environment|
namespace_project_environment_url(
environment.project.namespace,
environment.project,
environment)
end
expose :created_at, :updated_at
end
class EnvironmentSerializer < BaseSerializer
entity EnvironmentEntity
end
module RequestAwareEntity
extend ActiveSupport::Concern
included do
include Gitlab::Routing.url_helpers
end
def request
@options.fetch(:request)
end
end
class UserEntity < API::Entities::UserBasic
end
...@@ -353,9 +353,9 @@ ...@@ -353,9 +353,9 @@
%fieldset %fieldset
%legend Repository Storage %legend Repository Storage
.form-group .form-group
= f.label :repository_storage, 'Storage path for new projects', class: 'control-label col-sm-2' = f.label :repository_storages, 'Storage paths for new projects', class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
= f.select :repository_storage, repository_storage_options_for_select, {}, class: 'form-control' = f.select :repository_storages, repository_storages_options_for_select, {include_hidden: false}, multiple: true, class: 'form-control'
.help-block .help-block
Manage repository storage paths. Learn more in the Manage repository storage paths. Learn more in the
= succeed "." do = succeed "." do
......
- @no_container = true - @no_container = true
- page_title "Logs" - page_title "Logs"
- loggers = [Gitlab::GitLogger, Gitlab::AppLogger, - loggers = [Gitlab::GitLogger, Gitlab::AppLogger,
Gitlab::ProductionLogger, Gitlab::SidekiqLogger, Gitlab::EnvironmentLogger, Gitlab::SidekiqLogger,
Gitlab::RepositoryCheckLogger] Gitlab::RepositoryCheckLogger]
= render 'admin/background_jobs/head' = render 'admin/background_jobs/head'
......
xml.instruct! xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "#{current_user.name} issues" xml.title "#{current_user.name} issues"
xml.link href: issues_dashboard_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml" xml.link href: url_for(params), rel: "self", type: "application/atom+xml"
xml.link href: issues_dashboard_url, rel: "alternate", type: "text/html" xml.link href: issues_dashboard_url, rel: "alternate", type: "text/html"
xml.id issues_dashboard_url xml.id issues_dashboard_url
xml.updated @issues.first.created_at.xmlschema if @issues.reorder(nil).any? xml.updated @issues.first.created_at.xmlschema if @issues.reorder(nil).any?
......
...@@ -2,13 +2,13 @@ ...@@ -2,13 +2,13 @@
- header_title "Issues", issues_dashboard_path(assignee_id: current_user.id) - header_title "Issues", issues_dashboard_path(assignee_id: current_user.id)
= content_for :meta_tags do = content_for :meta_tags do
- if current_user - if current_user
= auto_discovery_link_tag(:atom, issues_dashboard_url(format: :atom, private_token: current_user.private_token), title: "#{current_user.name} issues") = auto_discovery_link_tag(:atom, url_for(params.merge(format: :atom, private_token: current_user.private_token)), title: "#{current_user.name} issues")
.top-area .top-area
= render 'shared/issuable/nav', type: :issues = render 'shared/issuable/nav', type: :issues
.nav-controls .nav-controls
- if current_user - if current_user
= link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: 'btn' do = link_to url_for(params.merge(format: :atom, private_token: current_user.private_token)), class: 'btn' do
= icon('rss') = icon('rss')
%span.icon-label %span.icon-label
Subscribe Subscribe
......
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
- if params[:author_id].present? - if params[:author_id].present?
= hidden_field_tag(:author_id, params[:author_id]) = hidden_field_tag(:author_id, params[:author_id])
= dropdown_tag(user_dropdown_label(params[:author_id], 'Author'), options: { toggle_class: 'js-user-search js-filter-submit js-author-search', title: 'Filter by author', filter: true, filterInput: 'input#author-search', dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit', = dropdown_tag(user_dropdown_label(params[:author_id], 'Author'), options: { toggle_class: 'js-user-search js-filter-submit js-author-search', title: 'Filter by author', filter: true, filterInput: 'input#author-search', dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit',
placeholder: 'Search authors', data: { any_user: 'Any Author', first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: 'author_id', default_label: 'Author' } }) placeholder: 'Search authors', data: { any_user: 'Any Author', first_user: (current_user.username if current_user), project_id: (@project.id if @project), selected: params[:author_id], field_name: 'author_id', default_label: 'Author', todo_filter: true, todo_state_filter: params[:state] || 'pending' } })
.filter-item.inline .filter-item.inline
- if params[:type].present? - if params[:type].present?
= hidden_field_tag(:type, params[:type]) = hidden_field_tag(:type, params[:type])
......
xml.instruct! xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "#{@group.name} issues" xml.title "#{@group.name} issues"
xml.link href: issues_group_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml" xml.link href: url_for(params), rel: "self", type: "application/atom+xml"
xml.link href: issues_group_url, rel: "alternate", type: "text/html" xml.link href: issues_group_url, rel: "alternate", type: "text/html"
xml.id issues_group_url xml.id issues_group_url
xml.updated @issues.first.created_at.xmlschema if @issues.reorder(nil).any? xml.updated @issues.first.created_at.xmlschema if @issues.reorder(nil).any?
......
- page_title "Issues" - page_title "Issues"
= content_for :meta_tags do = content_for :meta_tags do
- if current_user - if current_user
= auto_discovery_link_tag(:atom, issues_group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} issues") = auto_discovery_link_tag(:atom, url_for(params.merge(format: :atom, private_token: current_user.private_token)), title: "#{@group.name} issues")
.top-area .top-area
= render 'shared/issuable/nav', type: :issues = render 'shared/issuable/nav', type: :issues
.nav-controls .nav-controls
- if current_user - if current_user
= link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token), class: 'btn' do = link_to url_for(params.merge(format: :atom, private_token: current_user.private_token)), class: 'btn' do
= icon('rss') = icon('rss')
%span.icon-label %span.icon-label
Subscribe Subscribe
......
...@@ -26,8 +26,8 @@ ...@@ -26,8 +26,8 @@
Perform code reviews and enhance collaboration with merge requests. Perform code reviews and enhance collaboration with merge requests.
Each project can also have an issue tracker and a wiki. Each project can also have an issue tracker and a wiki.
- if current_application_settings.sign_in_text.present? - if current_application_settings.sign_in_text.present?
= markdown_field(current_application_settings, :sign_in_text) = markdown_field(current_application_settings, :sign_in_text)
%hr.footer-fixed %hr.footer-fixed
.container.footer-container .container.footer-container
......
- if commit.status - ref = local_assigns.fetch(:ref)
= link_to builds_namespace_project_commit_path(commit.project.namespace, commit.project, commit), class: "ci-status ci-#{commit.status}" do - status = commit.status(ref)
= ci_icon_for_status(commit.status) - if status
= ci_label_for_status(commit.status) = link_to builds_namespace_project_commit_path(commit.project.namespace, commit.project, commit), class: "ci-status ci-#{status}" do
= ci_icon_for_status(status)
= ci_label_for_status(status)
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message" = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message"
......
...@@ -12,3 +12,7 @@ ...@@ -12,3 +12,7 @@
%span.descr %span.descr
Builds need to be configured to enable this feature. Builds need to be configured to enable this feature.
= link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_build_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds') = link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_build_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds')
.checkbox
= f.label :only_allow_merge_if_all_discussions_are_resolved do
= f.check_box :only_allow_merge_if_all_discussions_are_resolved
%strong Only allow merge requests to be merged if all discussions are resolved
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
%ul.blob-commit-info.hidden-xs %ul.blob-commit-info.hidden-xs
- blob_commit = @repository.last_commit_for_path(@commit.id, blob.path) - blob_commit = @repository.last_commit_for_path(@commit.id, blob.path)
= render blob_commit, project: @project = render blob_commit, project: @project, ref: @ref
%div#blob-content-holder.blob-content-holder %div#blob-content-holder.blob-content-holder
%article.file-holder %article.file-holder
......
...@@ -14,13 +14,13 @@ ...@@ -14,13 +14,13 @@
= text_field_tag 'file_name', params[:file_name], placeholder: "File name", = text_field_tag 'file_name', params[:file_name], placeholder: "File name",
required: true, class: 'form-control new-file-name' required: true, class: 'form-control new-file-name'
.pull-right .pull-right.file-buttons
.license-selector.js-license-selector-wrap.hidden .license-selector.js-license-selector-wrap.hidden
= dropdown_tag("Choose a License template", options: { toggle_class: 'js-license-selector', title: "Choose a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } ) = dropdown_tag("Choose a License template", options: { toggle_class: 'btn js-license-selector', title: "Choose a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } )
.gitignore-selector.js-gitignore-selector-wrap.hidden .gitignore-selector.js-gitignore-selector-wrap.hidden
= dropdown_tag("Choose a .gitignore template", options: { toggle_class: 'js-gitignore-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } ) = dropdown_tag("Choose a .gitignore template", options: { toggle_class: 'btn js-gitignore-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } )
.gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.hidden .gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.hidden
= dropdown_tag("Choose a GitLab CI Yaml template", options: { toggle_class: 'js-gitlab-ci-yml-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } ) = dropdown_tag("Choose a GitLab CI Yaml template", options: { toggle_class: 'btn js-gitlab-ci-yml-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } )
= button_tag class: 'soft-wrap-toggle btn', type: 'button' do = button_tag class: 'soft-wrap-toggle btn', type: 'button' do
%span.no-wrap %span.no-wrap
= custom_icon('icon_no_wrap') = custom_icon('icon_no_wrap')
......
...@@ -26,6 +26,6 @@ ...@@ -26,6 +26,6 @@
:javascript :javascript
disableButtonIfEmptyField($('.js-upload-blob-form').find('.js-commit-message'), '.btn-upload-file'); gl.utils.disableButtonIfEmptyField($('.js-upload-blob-form').find('.js-commit-message'), '.btn-upload-file');
new BlobFileDropzone($('.js-upload-blob-form'), '#{method}'); new BlobFileDropzone($('.js-upload-blob-form'), '#{method}');
new NewCommitForm($('.js-upload-blob-form')) new NewCommitForm($('.js-upload-blob-form'))
.branch-commit .branch-commit
.icon-container.commit-icon
= custom_icon("icon_commit")
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-id monospace" = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-id monospace"
&middot; &middot;
%span.str-truncated %span.str-truncated
......
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
- else - else
Cant find HEAD commit for this branch Cant find HEAD commit for this branch
- stages_status = pipeline.statuses.relevant.latest.stages_status - stages_status = pipeline.statuses.latest.stages_status
%td.stage-cell %td.stage-cell
- stages.each do |stage| - stages.each do |stage|
- status = stages_status[stage] - status = stages_status[stage]
......
- ref = local_assigns.fetch(:ref)
- if @note_counts - if @note_counts
- note_count = @note_counts.fetch(commit.id, 0) - note_count = @note_counts.fetch(commit.id, 0)
- else - else
...@@ -18,15 +19,15 @@ ...@@ -18,15 +19,15 @@
%span.commit-row-message.visible-xs-inline %span.commit-row-message.visible-xs-inline
&middot; &middot;
= commit.short_id = commit.short_id
- if commit.status - if commit.status(ref)
.visible-xs-inline .visible-xs-inline
= render_commit_status(commit) = render_commit_status(commit, ref: ref)
- if commit.description? - if commit.description?
%a.text-expander.hidden-xs.js-toggle-button ... %a.text-expander.hidden-xs.js-toggle-button ...
.commit-actions.hidden-xs .commit-actions.hidden-xs
- if commit.status - if commit.status(ref)
= render_commit_status(commit) = render_commit_status(commit, ref: ref)
= clipboard_button(clipboard_text: commit.id) = clipboard_button(clipboard_text: commit.id)
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-short-id btn btn-transparent" = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-short-id btn btn-transparent"
= link_to_browse_code(project, commit) = link_to_browse_code(project, commit)
......
...@@ -11,4 +11,4 @@ ...@@ -11,4 +11,4 @@
%li.warning-row.unstyled %li.warning-row.unstyled
#{number_with_delimiter(hidden)} additional commits have been omitted to prevent performance issues. #{number_with_delimiter(hidden)} additional commits have been omitted to prevent performance issues.
- else - else
%ul.content-list= render commits, project: @project %ul.content-list= render commits, project: @project, ref: @ref
- unless defined?(project) - ref = local_assigns.fetch(:ref)
- project = @project
- commits, hidden = limited_commits(@commits) - commits, hidden = limited_commits(@commits)
- commits.chunk { |c| c.committed_date.in_time_zone.to_date }.each do |day, commits| - commits.chunk { |c| c.committed_date.in_time_zone.to_date }.each do |day, commits|
%li.commit-header= "#{day.strftime('%d %b, %Y')} #{pluralize(commits.count, 'commit')}" %li.commit-header= "#{day.strftime('%d %b, %Y')} #{pluralize(commits.count, 'commit')}"
%li.commits-row %li.commits-row
%ul.list-unstyled.commit-list %ul.list-unstyled.commit-list
= render commits, project: project = render commits, project: project, ref: ref
- if hidden > 0 - if hidden > 0
%li.alert.alert-warning %li.alert.alert-warning
......
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
%div{id: dom_id(@project)} %div{id: dom_id(@project)}
%ol#commits-list.list-unstyled.content_list %ol#commits-list.list-unstyled.content_list
= render "commits", project: @project = render 'commits', project: @project, ref: @ref
= spinner = spinner
:javascript :javascript
......
...@@ -102,7 +102,7 @@ ...@@ -102,7 +102,7 @@
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
- if Gitlab.config.registry.enabled - if Gitlab.config.registry.enabled
.form-group .form-group.js-container-registry{ style: ("display: none;" if @project.project_feature.send(:repository_access_level) == 0) }
.checkbox .checkbox
= f.label :container_registry_enabled do = f.label :container_registry_enabled do
= f.check_box :container_registry_enabled = f.check_box :container_registry_enabled
...@@ -290,4 +290,4 @@ ...@@ -290,4 +290,4 @@
Saving project. Saving project.
%p Please wait a moment, this page will automatically refresh when ready. %p Please wait a moment, this page will automatically refresh when ready.
= render 'shared/confirm_modal', phrase: @project.path = render 'shared/confirm_modal', phrase: @project.path
\ No newline at end of file
xml.instruct! xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "#{@project.name} issues" xml.title "#{@project.name} issues"
xml.link href: namespace_project_issues_url(@project.namespace, @project, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml" xml.link href: url_for(params), rel: "self", type: "application/atom+xml"
xml.link href: namespace_project_issues_url(@project.namespace, @project), rel: "alternate", type: "text/html" xml.link href: namespace_project_issues_url(@project.namespace, @project), rel: "alternate", type: "text/html"
xml.id namespace_project_issues_url(@project.namespace, @project) xml.id namespace_project_issues_url(@project.namespace, @project)
xml.updated @issues.first.created_at.xmlschema if @issues.reorder(nil).any? xml.updated @issues.first.created_at.xmlschema if @issues.reorder(nil).any?
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
= content_for :meta_tags do = content_for :meta_tags do
- if current_user - if current_user
= auto_discovery_link_tag(:atom, namespace_project_issues_url(@project.namespace, @project, :atom, private_token: current_user.private_token), title: "#{@project.name} issues") = auto_discovery_link_tag(:atom, url_for(params.merge(format: :atom, private_token: current_user.private_token)), title: "#{@project.name} issues")
%div{ class: (container_class) } %div{ class: (container_class) }
- if @project.issues.any? - if @project.issues.any?
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
= render 'shared/issuable/nav', type: :issues = render 'shared/issuable/nav', type: :issues
.nav-controls .nav-controls
- if current_user - if current_user
= link_to namespace_project_issues_path(@project.namespace, @project, :atom, { private_token: current_user.private_token }), class: 'btn append-right-10' do = link_to url_for(params.merge(format: :atom, private_token: current_user.private_token)), class: 'btn append-right-10' do
= icon('rss') = icon('rss')
%span.icon-label %span.icon-label
Subscribe Subscribe
......
= commit_to_html(@commit, @source_project, false) - if @commit
= commit_to_html(@commit, @ref, @source_project)
= commit_to_html(@commit, @target_project, false) - if @commit
= commit_to_html(@commit, @ref, @target_project)
...@@ -3,4 +3,4 @@ ...@@ -3,4 +3,4 @@
Most recent commits displayed first Most recent commits displayed first
%ol#commits-list.list-unstyled %ol#commits-list.list-unstyled
= render "projects/commits/commits", project: @merge_request.source_project = render "projects/commits/commits", project: @merge_request.source_project, ref: @merge_request.source_branch
...@@ -23,8 +23,10 @@ ...@@ -23,8 +23,10 @@
= render 'projects/merge_requests/widget/open/merge_when_build_succeeds' = render 'projects/merge_requests/widget/open/merge_when_build_succeeds'
- elsif !@merge_request.can_be_merged_by?(current_user) - elsif !@merge_request.can_be_merged_by?(current_user)
= render 'projects/merge_requests/widget/open/not_allowed' = render 'projects/merge_requests/widget/open/not_allowed'
- elsif !@merge_request.mergeable_ci_state? && @pipeline && @pipeline.failed? - elsif !@merge_request.mergeable_ci_state?
= render 'projects/merge_requests/widget/open/build_failed' = render 'projects/merge_requests/widget/open/build_failed'
- elsif !@merge_request.mergeable_discussions_state?
= render 'projects/merge_requests/widget/open/unresolved_discussions'
- elsif @merge_request.can_be_merged? || resolved_conflicts - elsif @merge_request.can_be_merged? || resolved_conflicts
= render 'projects/merge_requests/widget/open/accept' = render 'projects/merge_requests/widget/open/accept'
......
%h4
= icon('exclamation-triangle')
This merge request has unresolved discussions
%p
Please resolve these discussions to allow this merge request to be merged.
\ No newline at end of file
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
= render 'projects/last_push' = render 'projects/last_push'
= render "home_panel" = render "home_panel"
- if @project.feature_available?(:repository, current_user) - if current_user && can?(current_user, :download_code, @project)
%nav.project-stats{ class: container_class } %nav.project-stats{ class: container_class }
%ul.nav %ul.nav
%li %li
...@@ -79,7 +79,7 @@ ...@@ -79,7 +79,7 @@
= render 'shared/notifications/button', notification_setting: @notification_setting = render 'shared/notifications/button', notification_setting: @notification_setting
- if @repository.commit - if @repository.commit
.project-last-commit{ class: container_class } .project-last-commit{ class: container_class }
= render 'projects/last_commit', commit: @repository.commit, project: @project = render 'projects/last_commit', commit: @repository.commit, ref: current_ref, project: @project
%div{ class: container_class } %div{ class: container_class }
- if @project.archived? - if @project.archived?
......
...@@ -37,5 +37,5 @@ ...@@ -37,5 +37,5 @@
:javascript :javascript
// Load last commit log for each file in tree // Load last commit log for each file in tree
$('#tree-slider').waitForImages(function() { $('#tree-slider').waitForImages(function() {
ajaxGet("#{escape_javascript(@logs_path)}"); gl.utils.ajaxGet("#{escape_javascript(@logs_path)}");
}); });
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14"> <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" class="ci-status-icon-canceled" viewBox="0 0 14 14">
<g fill="#5C5C5C" fill-rule="evenodd"> <g fill="#5C5C5C" fill-rule="evenodd">
<path d="M12.5,7 C12.5,3.96243388 10.0375661,1.5 7,1.5 C3.96243388,1.5 1.5,3.96243388 1.5,7 C1.5,10.0375661 3.96243388,12.5 7,12.5 C10.0375661,12.5 12.5,10.0375661 12.5,7 Z M0,7 C0,3.13400675 3.13400675,0 7,0 C10.8659932,0 14,3.13400675 14,7 C14,10.8659932 10.8659932,14 7,14 C3.13400675,14 0,10.8659932 0,7 Z"/> <path d="M12.5,7 C12.5,3.96243388 10.0375661,1.5 7,1.5 C3.96243388,1.5 1.5,3.96243388 1.5,7 C1.5,10.0375661 3.96243388,12.5 7,12.5 C10.0375661,12.5 12.5,10.0375661 12.5,7 Z M0,7 C0,3.13400675 3.13400675,0 7,0 C10.8659932,0 14,3.13400675 14,7 C14,10.8659932 10.8659932,14 7,14 C3.13400675,14 0,10.8659932 0,7 Z"/>
<rect width="8" height="2" x="3" y="6" transform="rotate(45 7 7)" rx=".5"/> <rect width="8" height="2" x="3" y="6" transform="rotate(45 7 7)" rx=".5"/>
......
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" enable-background="new 0 0 14 14"><path d="M12.5,7 C12.5,4 10,1.5 7,1.5 C4,1.5 1.5,4 1.5,7 C1.5,10 4,12.5 7,12.5 C10,12.5 12.5,10 12.5,7 L12.5,7 Z M0,7 C0,3.1 3.1,0 7,0 C10.9,0 14,3.1 14,7 C14,10.9 10.9,14 7,14 C3.1,14 0,10.9 0,7 L0,7 Z" /><circle cx="7" cy="7" r="3.25"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" class="ci-status-icon-created" viewBox="0 0 14 14" enable-background="new 0 0 14 14"><path d="M12.5,7 C12.5,4 10,1.5 7,1.5 C4,1.5 1.5,4 1.5,7 C1.5,10 4,12.5 7,12.5 C10,12.5 12.5,10 12.5,7 L12.5,7 Z M0,7 C0,3.1 3.1,0 7,0 C10.9,0 14,3.1 14,7 C14,10.9 10.9,14 7,14 C3.1,14 0,10.9 0,7 L0,7 Z" /><circle cx="7" cy="7" r="3.25"/></svg>
<svg width="20" height="20" class="ci-status-icon-skipped" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><title>Group Copy 31</title><g fill="#5C5C5C" fill-rule="evenodd"><path d="M10 17.857c4.286 0 7.857-3.571 7.857-7.857S14.286 2.143 10 2.143 2.143 5.714 2.143 10 5.714 17.857 10 17.857M10 0c5.571 0 10 4.429 10 10s-4.429 10-10 10S0 15.571 0 10 4.429 0 10 0"/><path d="M10.986 11l-1.293 1.293a1 1 0 0 0 1.414 1.414l2.644-2.644a1.505 1.505 0 0 0 0-2.126l-2.644-2.644a1 1 0 0 0-1.414 1.414L10.986 9H6.4a1 1 0 0 0 0 2h4.586z"/></g></svg>
...@@ -142,6 +142,7 @@ ...@@ -142,6 +142,7 @@
.col-sm-10.col-sm-offset-2 .col-sm-10.col-sm-offset-2
.checkbox .checkbox
= label_tag 'merge_request[force_remove_source_branch]' do = label_tag 'merge_request[force_remove_source_branch]' do
= hidden_field_tag 'merge_request[force_remove_source_branch]', '0'
= check_box_tag 'merge_request[force_remove_source_branch]', '1', @merge_request.force_remove_source_branch? = check_box_tag 'merge_request[force_remove_source_branch]', '1', @merge_request.force_remove_source_branch?
Remove source branch when merge request is accepted. Remove source branch when merge request is accepted.
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
%button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data} %button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data}
%span.dropdown-toggle-text{ class: ("is-default" if selected.nil? || selected.empty?) } %span.dropdown-toggle-text{ class: ("is-default" if selected.nil? || selected.empty?) }
= multi_label_name(selected, "Labels") = multi_label_name(selected, "Labels")
= icon('chevron-down') = icon('caret-down')
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
= render partial: "shared/issuable/label_page_default", locals: { title: dropdown_title, show_footer: show_footer, show_create: show_create } = render partial: "shared/issuable/label_page_default", locals: { title: dropdown_title, show_footer: show_footer, show_create: show_create }
- if show_create && project && can?(current_user, :admin_label, project) - if show_create && project && can?(current_user, :admin_label, project)
......
---
title: Add setting to only allow merge requests to be merged when all discussions are resolved
merge_request: 7125
author: Rodolfo Arruda
---
title: 'Fix: Todos Filter Shows All Users'
merge_request:
author:
---
title: Issues atom feed url reflect filters on dashboard
merge_request: 7114
author: Lucas Deschamps
---
title: Only skip group when it's actually a group in the "Share with group" select
merge_request: 7262
author:
---
title: 'Fix: Guest sees some repository details and gets 404'
merge_request:
author:
---
title: Introduce round-robin project creation to spread load over multiple shards
merge_request: 7266
author:
---
title: Ensure merge request's "remove branch" accessors return booleans
merge_request: 7267
author:
---
title: Fix invalid filename validation on eslint
merge_request: 7281
author:
---
title: Fix showing pipeline status for a given commit from correct branch
merge_request: 7034
author:
class OnlyAllowMergeIfAllDiscussionsAreResolved < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_column_with_default(:projects,
:only_allow_merge_if_all_discussions_are_resolved,
:boolean,
default: false)
end
def down
remove_column(:projects, :only_allow_merge_if_all_discussions_are_resolved)
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class RenameRepositoryStorageColumn < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
# When a migration requires downtime you **must** uncomment the following
# constant and define a short and easy to understand explanation as to why the
# migration requires downtime.
# DOWNTIME_REASON = ''
# When using the methods "add_concurrent_index" or "add_column_with_default"
# you must disable the use of transactions as these methods can not run in an
# existing transaction. When using "add_concurrent_index" make sure that this
# method is the _only_ method called in the migration, any other changes
# should go in a separate migration. This ensures that upon failure _only_ the
# index creation fails and can be retried or reverted easily.
#
# To disable transactions uncomment the following line and remove these
# comments:
# disable_ddl_transaction!
def change
rename_column :application_settings, :repository_storage, :repository_storages
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: 20161025231710) do ActiveRecord::Schema.define(version: 20161103171205) 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"
...@@ -88,7 +88,7 @@ ActiveRecord::Schema.define(version: 20161025231710) do ...@@ -88,7 +88,7 @@ ActiveRecord::Schema.define(version: 20161025231710) do
t.integer "container_registry_token_expire_delay", default: 5 t.integer "container_registry_token_expire_delay", default: 5
t.text "after_sign_up_text" t.text "after_sign_up_text"
t.boolean "user_default_external", default: false, null: false t.boolean "user_default_external", default: false, null: false
t.string "repository_storage", default: "default" t.string "repository_storages", default: "default"
t.string "enabled_git_access_protocol" t.string "enabled_git_access_protocol"
t.boolean "domain_blacklist_enabled", default: false t.boolean "domain_blacklist_enabled", default: false
t.text "domain_blacklist" t.text "domain_blacklist"
...@@ -905,6 +905,7 @@ ActiveRecord::Schema.define(version: 20161025231710) do ...@@ -905,6 +905,7 @@ ActiveRecord::Schema.define(version: 20161025231710) do
t.boolean "has_external_wiki" t.boolean "has_external_wiki"
t.boolean "lfs_enabled" t.boolean "lfs_enabled"
t.text "description_html" t.text "description_html"
t.boolean "only_allow_merge_if_all_discussions_are_resolved", default: false, null: false
end end
add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree
......
...@@ -13,7 +13,8 @@ This guide talks about how to read and use these system log files. ...@@ -13,7 +13,8 @@ This guide talks about how to read and use these system log files.
This file lives in `/var/log/gitlab/gitlab-rails/production.log` for This file lives in `/var/log/gitlab/gitlab-rails/production.log` for
omnibus package or in `/home/git/gitlab/log/production.log` for omnibus package or in `/home/git/gitlab/log/production.log` for
installations from source. installations from source. (When Gitlab is running in an environment
other than production, the corresponding logfile is shown here.)
It contains information about all performed requests. You can see the It contains information about all performed requests. You can see the
URL and type of request, IP address and what exactly parts of code were URL and type of request, IP address and what exactly parts of code were
......
...@@ -91,6 +91,9 @@ be stored via the **Application Settings** in the Admin area. ...@@ -91,6 +91,9 @@ be stored via the **Application Settings** in the Admin area.
![Choose repository storage path in Admin area](img/repository_storages_admin_ui.png) ![Choose repository storage path in Admin area](img/repository_storages_admin_ui.png)
Beginning with GitLab 8.13.4, multiple paths can be chosen. New projects will be
randomly placed on one of the selected paths.
[ce-4578]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4578 [ce-4578]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4578
[restart gitlab]: restart_gitlab.md#installations-from-source [restart gitlab]: restart_gitlab.md#installations-from-source
[reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure [reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure
......
...@@ -2,7 +2,12 @@ ...@@ -2,7 +2,12 @@
## List groups ## List groups
Get a list of groups. (As user: my groups, as admin: all groups) Get a list of groups. (As user: my groups or all available, as admin: all groups).
Parameters:
- `all_available` (optional) - if passed, show all groups you have access to
- `skip_groups` (optional)(array of group IDs) - if passed, skip groups
``` ```
GET /groups GET /groups
...@@ -21,7 +26,6 @@ GET /groups ...@@ -21,7 +26,6 @@ GET /groups
You can search for groups by name or path, see below. You can search for groups by name or path, see below.
## List a group's projects ## List a group's projects
Get a list of projects in this group. Get a list of projects in this group.
......
...@@ -89,6 +89,7 @@ Parameters: ...@@ -89,6 +89,7 @@ Parameters:
"public_builds": true, "public_builds": true,
"shared_with_groups": [], "shared_with_groups": [],
"only_allow_merge_if_build_succeeds": false, "only_allow_merge_if_build_succeeds": false,
"only_allow_merge_if_all_discussions_are_resolved": false,
"request_access_enabled": false "request_access_enabled": false
}, },
{ {
...@@ -151,6 +152,7 @@ Parameters: ...@@ -151,6 +152,7 @@ Parameters:
"public_builds": true, "public_builds": true,
"shared_with_groups": [], "shared_with_groups": [],
"only_allow_merge_if_build_succeeds": false, "only_allow_merge_if_build_succeeds": false,
"only_allow_merge_if_all_discussions_are_resolved": false,
"request_access_enabled": false "request_access_enabled": false
} }
] ]
...@@ -429,6 +431,7 @@ Parameters: ...@@ -429,6 +431,7 @@ Parameters:
} }
], ],
"only_allow_merge_if_build_succeeds": false, "only_allow_merge_if_build_succeeds": false,
"only_allow_merge_if_all_discussions_are_resolved": false,
"request_access_enabled": false "request_access_enabled": false
} }
``` ```
...@@ -602,6 +605,7 @@ Parameters: ...@@ -602,6 +605,7 @@ Parameters:
| `import_url` | string | no | URL to import repository from | | `import_url` | string | no | URL to import repository from |
| `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members | | `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members |
| `only_allow_merge_if_build_succeeds` | boolean | no | Set whether merge requests can only be merged with successful builds | | `only_allow_merge_if_build_succeeds` | boolean | no | Set whether merge requests can only be merged with successful builds |
| `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved |
| `lfs_enabled` | boolean | no | Enable LFS | | `lfs_enabled` | boolean | no | Enable LFS |
| `request_access_enabled` | boolean | no | Allow users to request member access | | `request_access_enabled` | boolean | no | Allow users to request member access |
...@@ -634,6 +638,7 @@ Parameters: ...@@ -634,6 +638,7 @@ Parameters:
| `import_url` | string | no | URL to import repository from | | `import_url` | string | no | URL to import repository from |
| `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members | | `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members |
| `only_allow_merge_if_build_succeeds` | boolean | no | Set whether merge requests can only be merged with successful builds | | `only_allow_merge_if_build_succeeds` | boolean | no | Set whether merge requests can only be merged with successful builds |
| `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved |
| `lfs_enabled` | boolean | no | Enable LFS | | `lfs_enabled` | boolean | no | Enable LFS |
| `request_access_enabled` | boolean | no | Allow users to request member access | | `request_access_enabled` | boolean | no | Allow users to request member access |
...@@ -665,6 +670,7 @@ Parameters: ...@@ -665,6 +670,7 @@ Parameters:
| `import_url` | string | no | URL to import repository from | | `import_url` | string | no | URL to import repository from |
| `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members | | `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members |
| `only_allow_merge_if_build_succeeds` | boolean | no | Set whether merge requests can only be merged with successful builds | | `only_allow_merge_if_build_succeeds` | boolean | no | Set whether merge requests can only be merged with successful builds |
| `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved |
| `lfs_enabled` | boolean | no | Enable LFS | | `lfs_enabled` | boolean | no | Enable LFS |
| `request_access_enabled` | boolean | no | Allow users to request member access | | `request_access_enabled` | boolean | no | Allow users to request member access |
...@@ -752,6 +758,7 @@ Example response: ...@@ -752,6 +758,7 @@ Example response:
"public_builds": true, "public_builds": true,
"shared_with_groups": [], "shared_with_groups": [],
"only_allow_merge_if_build_succeeds": false, "only_allow_merge_if_build_succeeds": false,
"only_allow_merge_if_all_discussions_are_resolved": false,
"request_access_enabled": false "request_access_enabled": false
} }
``` ```
...@@ -820,6 +827,7 @@ Example response: ...@@ -820,6 +827,7 @@ Example response:
"public_builds": true, "public_builds": true,
"shared_with_groups": [], "shared_with_groups": [],
"only_allow_merge_if_build_succeeds": false, "only_allow_merge_if_build_succeeds": false,
"only_allow_merge_if_all_discussions_are_resolved": false,
"request_access_enabled": false "request_access_enabled": false
} }
``` ```
...@@ -908,6 +916,7 @@ Example response: ...@@ -908,6 +916,7 @@ Example response:
"public_builds": true, "public_builds": true,
"shared_with_groups": [], "shared_with_groups": [],
"only_allow_merge_if_build_succeeds": false, "only_allow_merge_if_build_succeeds": false,
"only_allow_merge_if_all_discussions_are_resolved": false,
"request_access_enabled": false "request_access_enabled": false
} }
``` ```
...@@ -996,6 +1005,7 @@ Example response: ...@@ -996,6 +1005,7 @@ Example response:
"public_builds": true, "public_builds": true,
"shared_with_groups": [], "shared_with_groups": [],
"only_allow_merge_if_build_succeeds": false, "only_allow_merge_if_build_succeeds": false,
"only_allow_merge_if_all_discussions_are_resolved": false,
"request_access_enabled": false "request_access_enabled": false
} }
``` ```
......
...@@ -42,6 +42,7 @@ Example response: ...@@ -42,6 +42,7 @@ Example response:
"sign_in_text" : null, "sign_in_text" : null,
"container_registry_token_expire_delay": 5, "container_registry_token_expire_delay": 5,
"repository_storage": "default", "repository_storage": "default",
"repository_storages": ["default"],
"koding_enabled": false, "koding_enabled": false,
"koding_url": null "koding_url": null
} }
...@@ -73,7 +74,8 @@ PUT /application/settings ...@@ -73,7 +74,8 @@ PUT /application/settings
| `user_oauth_applications` | boolean | no | Allow users to register any application to use GitLab as an OAuth provider | | `user_oauth_applications` | boolean | no | Allow users to register any application to use GitLab as an OAuth provider |
| `after_sign_out_path` | string | no | Where to redirect users after logout | | `after_sign_out_path` | string | no | Where to redirect users after logout |
| `container_registry_token_expire_delay` | integer | no | Container Registry token duration in minutes | | `container_registry_token_expire_delay` | integer | no | Container Registry token duration in minutes |
| `repository_storage` | string | no | Storage path for new projects. The value should be the name of one of the repository storage paths defined in your gitlab.yml | | `repository_storages` | array of strings | no | A list of names of enabled storage paths, taken from `gitlab.yml`. New projects will be created in one of these stores, chosen at random. |
| `repository_storage` | string | no | The first entry in `repository_storages`. Deprecated, but retained for compatibility reasons |
| `enabled_git_access_protocol` | string | no | Enabled protocols for Git access. Allowed values are: `ssh`, `http`, and `nil` to allow both protocols. | | `enabled_git_access_protocol` | string | no | Enabled protocols for Git access. Allowed values are: `ssh`, `http`, and `nil` to allow both protocols. |
| `koding_enabled` | boolean | no | Enable Koding integration. Default is `false`. | | `koding_enabled` | boolean | no | Enable Koding integration. Default is `false`. |
| `koding_url` | string | yes (if `koding_enabled` is `true`) | The Koding instance URL for integration. | | `koding_url` | string | yes (if `koding_enabled` is `true`) | The Koding instance URL for integration. |
......
...@@ -226,7 +226,7 @@ e.g. `docker run --rm -t -i -v $(pwd)/src:/home/app/src test-image:latest run_ap ...@@ -226,7 +226,7 @@ e.g. `docker run --rm -t -i -v $(pwd)/src:/home/app/src test-image:latest run_ap
> **Note:** > **Note:**
This feature requires GitLab 8.8 and GitLab Runner 1.2. This feature requires GitLab 8.8 and GitLab Runner 1.2.
Once you've built a Docker image, you can push it up to the built-in [GitLab Container Registry](../../container_registry/README.md). For example, if you're using Once you've built a Docker image, you can push it up to the built-in [GitLab Container Registry](../../user/project/container_registry.md). For example, if you're using
docker-in-docker on your runners, this is how your `.gitlab-ci.yml` could look: docker-in-docker on your runners, this is how your `.gitlab-ci.yml` could look:
......
...@@ -465,6 +465,7 @@ curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "domain ...@@ -465,6 +465,7 @@ curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "domain
[cURL]: http://curl.haxx.se/ "cURL website" [cURL]: http://curl.haxx.se/ "cURL website"
[single spaces]: http://www.slate.com/articles/technology/technology/2011/01/space_invaders.html [single spaces]: http://www.slate.com/articles/technology/technology/2011/01/space_invaders.html
[gfm]: http://docs.gitlab.com/ce/user/markdown.html#newlines "GitLab flavored markdown documentation" [gfm]: http://docs.gitlab.com/ce/user/markdown.html#newlines "GitLab flavored markdown documentation"
[ce-1242]: https://gitlab.com/gitlab-org/gitlab-ce/issues/1242
[doc-restart]: ../administration/restart_gitlab.md "GitLab restart documentation" [doc-restart]: ../administration/restart_gitlab.md "GitLab restart documentation"
[ce-3349]: https://gitlab.com/gitlab-org/gitlab-ce/issues/3349 "Documentation restructure" [ce-3349]: https://gitlab.com/gitlab-org/gitlab-ce/issues/3349 "Documentation restructure"
[graffle]: https://gitlab.com/gitlab-org/gitlab-design/blob/d8d39f4a87b90fb9ae89ca12dc565347b4900d5e/production/resources/gitlab-map.graffle [graffle]: https://gitlab.com/gitlab-org/gitlab-design/blob/d8d39f4a87b90fb9ae89ca12dc565347b4900d5e/production/resources/gitlab-map.graffle
......
...@@ -196,6 +196,12 @@ It consists of two subtasks: ...@@ -196,6 +196,12 @@ It consists of two subtasks:
As long as the fixtures don't change, `rake teaspoon:tests` is sufficient As long as the fixtures don't change, `rake teaspoon:tests` is sufficient
(and saves you some time). (and saves you some time).
If you need to debug your tests and/or application code while they're
running, navigate to [localhost:3000/teaspoon](http://localhost:3000/teaspoon)
in your browser, open DevTools, and run tests for individual files by clicking
on them. This is also much faster than setting up and running tests from the
command line.
Please note: Not all of the frontend fixtures are generated. Some are still static Please note: Not all of the frontend fixtures are generated. Some are still static
files. These will not be touched by `rake teaspoon:fixtures`. files. These will not be touched by `rake teaspoon:fixtures`.
......
...@@ -42,14 +42,6 @@ To run several tests inside one directory: ...@@ -42,14 +42,6 @@ To run several tests inside one directory:
If you want to use [Spring](https://github.com/rails/spring) set If you want to use [Spring](https://github.com/rails/spring) set
`ENABLE_SPRING=1` in your environment. `ENABLE_SPRING=1` in your environment.
## Generate searchable docs for source code
You can find results under the `doc/code` directory.
```
bundle exec rake gitlab:generate_docs
```
## Generate API documentation for project services (e.g. Slack) ## Generate API documentation for project services (e.g. Slack)
``` ```
......
...@@ -479,10 +479,14 @@ Copy the example site config: ...@@ -479,10 +479,14 @@ Copy the example site config:
sudo cp lib/support/nginx/gitlab /etc/nginx/sites-available/gitlab sudo cp lib/support/nginx/gitlab /etc/nginx/sites-available/gitlab
sudo ln -s /etc/nginx/sites-available/gitlab /etc/nginx/sites-enabled/gitlab sudo ln -s /etc/nginx/sites-available/gitlab /etc/nginx/sites-enabled/gitlab
Make sure to edit the config file to match your setup: Make sure to edit the config file to match your setup. Also, ensure that you match your paths to GitLab, especially if installing for a user other than the 'git' user:
# Change YOUR_SERVER_FQDN to the fully-qualified # Change YOUR_SERVER_FQDN to the fully-qualified
# domain name of your host serving GitLab. # domain name of your host serving GitLab.
#
# Remember to match your paths to GitLab, especially
# if installing for a user other than 'git'.
#
# If using Ubuntu default nginx install: # If using Ubuntu default nginx install:
# either remove the default_server from the listen line # either remove the default_server from the listen line
# or else sudo rm -f /etc/nginx/sites-enabled/default # or else sudo rm -f /etc/nginx/sites-enabled/default
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
GitLab University is the best place to learn about **Version Control with Git and GitLab**. GitLab University is the best place to learn about **Version Control with Git and GitLab**.
It doesn't replace, but accompanies our great [Documentation](http://docs.gitlab.com) It doesn't replace, but accompanies our great [Documentation](https://docs.gitlab.com)
and [Blog Articles](https://about.gitlab.com/blog/). and [Blog Articles](https://about.gitlab.com/blog/).
Would you like to contribute to GitLab University? Then please take a look at our contribution [process](/process) for more information. Would you like to contribute to GitLab University? Then please take a look at our contribution [process](/process) for more information.
...@@ -31,7 +31,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project ...@@ -31,7 +31,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
1. [An Overview of GitLab.com - Video](https://www.youtube.com/watch?v=WaiL5DGEMR4) 1. [An Overview of GitLab.com - Video](https://www.youtube.com/watch?v=WaiL5DGEMR4)
1. [Why Use Git and GitLab - Slides](https://docs.google.com/a/gitlab.com/presentation/d/1RcZhFmn5VPvoFu6UMxhMOy7lAsToeBZRjLRn0LIdaNc/edit?usp=drive_web) 1. [Why Use Git and GitLab - Slides](https://docs.google.com/a/gitlab.com/presentation/d/1RcZhFmn5VPvoFu6UMxhMOy7lAsToeBZRjLRn0LIdaNc/edit?usp=drive_web)
1. [GitLab Basics - Article](http://doc.gitlab.com/ce/gitlab-basics/README.html) 1. [GitLab Basics - Article](../gitlab-basics/README.md)
1. [Git and GitLab Basics - Video](https://www.youtube.com/watch?v=03wb9FvO4Ak&index=5&list=PLFGfElNsQthbQu_IWlNOxul0TbS_2JH-e) 1. [Git and GitLab Basics - Video](https://www.youtube.com/watch?v=03wb9FvO4Ak&index=5&list=PLFGfElNsQthbQu_IWlNOxul0TbS_2JH-e)
1. [Git and GitLab Basics - Online Course](https://courses.platzi.com/classes/git-gitlab/concepto/part-1/part-23370/material/) 1. [Git and GitLab Basics - Online Course](https://courses.platzi.com/classes/git-gitlab/concepto/part-1/part-23370/material/)
1. [Comparison of GitLab Versions](https://about.gitlab.com/features/#compare) 1. [Comparison of GitLab Versions](https://about.gitlab.com/features/#compare)
...@@ -51,10 +51,10 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project ...@@ -51,10 +51,10 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
#### 1.5. Migrating from other Source Control #### 1.5. Migrating from other Source Control
1. [Migrating from BitBucket/Stash](http://doc.gitlab.com/ee/workflow/importing/import_projects_from_bitbucket.html) 1. [Migrating from BitBucket/Stash](https://docs.gitlab.com/ee/workflow/importing/import_projects_from_bitbucket.html)
1. [Migrating from GitHub](http://doc.gitlab.com/ee/workflow/importing/import_projects_from_github.html) 1. [Migrating from GitHub](https://docs.gitlab.com/ee/workflow/importing/import_projects_from_github.html)
1. [Migrating from SVN](http://doc.gitlab.com/ee/workflow/importing/migrating_from_svn.html) 1. [Migrating from SVN](https://docs.gitlab.com/ee/workflow/importing/migrating_from_svn.html)
1. [Migrating from Fogbugz](http://doc.gitlab.com/ee/workflow/importing/import_projects_from_fogbugz.html) 1. [Migrating from Fogbugz](https://docs.gitlab.com/ee/workflow/importing/import_projects_from_fogbugz.html)
#### 1.6. GitLab Inc. #### 1.6. GitLab Inc.
...@@ -91,11 +91,11 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project ...@@ -91,11 +91,11 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
1. [Using any Static Site Generator with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/) 1. [Using any Static Site Generator with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/)
1. [Securing GitLab Pages with SSL](https://about.gitlab.com/2016/06/24/secure-gitlab-pages-with-startssl/) 1. [Securing GitLab Pages with SSL](https://about.gitlab.com/2016/06/24/secure-gitlab-pages-with-startssl/)
1. [GitLab Pages Documentation](http://doc.gitlab.com/ee/pages/README.html) 1. [GitLab Pages Documentation](https://docs.gitlab.com/ee/pages/README.html)
#### 2.2. GitLab Issues #### 2.2. GitLab Issues
1. [Markdown in GitLab](http://doc.gitlab.com/ce/markdown/markdown.html) 1. [Markdown in GitLab](../user/markdown.md)
1. [Issues and Merge Requests - Video](https://www.youtube.com/watch?v=raXvuwet78M) 1. [Issues and Merge Requests - Video](https://www.youtube.com/watch?v=raXvuwet78M)
1. [Due Dates and Milestones fro GitLab Issues](https://about.gitlab.com/2016/08/05/feature-highlight-set-dates-for-issues/) 1. [Due Dates and Milestones fro GitLab Issues](https://about.gitlab.com/2016/08/05/feature-highlight-set-dates-for-issues/)
1. [How to Use GitLab Labels](https://about.gitlab.com/2016/08/17/using-gitlab-labels/) 1. [How to Use GitLab Labels](https://about.gitlab.com/2016/08/17/using-gitlab-labels/)
...@@ -129,7 +129,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project ...@@ -129,7 +129,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
1. [GitLab Flow vs Forking in GitLab - Video](https://www.youtube.com/watch?v=UGotqAUACZA) 1. [GitLab Flow vs Forking in GitLab - Video](https://www.youtube.com/watch?v=UGotqAUACZA)
1. [GitLab Flow Overview](https://about.gitlab.com/2014/09/29/gitlab-flow/) 1. [GitLab Flow Overview](https://about.gitlab.com/2014/09/29/gitlab-flow/)
1. [Always Start with an Issue](https://about.gitlab.com/2016/03/03/start-with-an-issue/) 1. [Always Start with an Issue](https://about.gitlab.com/2016/03/03/start-with-an-issue/)
1. [GitLab Flow Documentation](http://doc.gitlab.com/ee/workflow/gitlab_flow.html) 1. [GitLab Flow Documentation](https://docs.gitlab.com/ee/workflow/gitlab_flow.html)
#### 2.5. GitLab Comparisons #### 2.5. GitLab Comparisons
...@@ -189,8 +189,8 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project ...@@ -189,8 +189,8 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
#### 3.9. <a name="integrations"></a> Integrations #### 3.9. <a name="integrations"></a> Integrations
1. [How to Integrate JIRA and Jenkins with GitLab - Video](https://gitlabmeetings.webex.com/gitlabmeetings/ldr.php?RCID=44b548147a67ab4d8a62274047146415) 1. [How to Integrate JIRA and Jenkins with GitLab - Video](https://gitlabmeetings.webex.com/gitlabmeetings/ldr.php?RCID=44b548147a67ab4d8a62274047146415)
1. [How to Integrate Jira with GitLab](http://doc.gitlab.com/ee/integration/jira.html) 1. [How to Integrate Jira with GitLab](https://docs.gitlab.com/ee/integration/jira.html)
1. [How to Integrate Jenkins with GitLab](http://doc.gitlab.com/ee/integration/jenkins.html) 1. [How to Integrate Jenkins with GitLab](https://docs.gitlab.com/ee/integration/jenkins.html)
1. [How to Integrate Bamboo with GitLab](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/project_services/bamboo.md) 1. [How to Integrate Bamboo with GitLab](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/project_services/bamboo.md)
1. [How to Integrate Slack with GitLab](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/slack.md) 1. [How to Integrate Slack with GitLab](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/slack.md)
1. [How to Integrate Convox with GitLab](https://about.gitlab.com/2016/06/09/continuous-delivery-with-gitlab-and-convox/) 1. [How to Integrate Convox with GitLab](https://about.gitlab.com/2016/06/09/continuous-delivery-with-gitlab-and-convox/)
......
...@@ -10,7 +10,7 @@ User authentication by combination of 2 different steps during login. This allow ...@@ -10,7 +10,7 @@ User authentication by combination of 2 different steps during login. This allow
### Access Levels ### Access Levels
Process of selective restriction to create, view, modify or delete a resource based on a set of assigned permissions. See [GitLab's Permission Guidelines](http://doc.gitlab.com/ce/permissions/permissions.html) Process of selective restriction to create, view, modify or delete a resource based on a set of assigned permissions. See [GitLab's Permission Guidelines](../../permissions/permissions.md
### Active Directory (AD) ### Active Directory (AD)
......
...@@ -58,28 +58,28 @@ Sometimes we need to upgrade customers from old versions of GitLab to latest, so ...@@ -58,28 +58,28 @@ Sometimes we need to upgrade customers from old versions of GitLab to latest, so
- Users - Users
- Groups - Groups
- Projects - Projects
- [Backup using our Backup rake task](http://docs.gitlab.com/ce/raketasks/backup_restore.html#create-a-backup-of-the-gitlab-system) - [Backup using our Backup rake task](https://docs.gitlab.com/ce/raketasks/backup_restore.html#create-a-backup-of-the-gitlab-system)
- [Upgrade to 5.0 source using our Upgrade documentation](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/update/4.2-to-5.0.md) - [Upgrade to 5.0 source using our Upgrade documentation](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/update/4.2-to-5.0.md)
- [Upgrade to 5.1 source](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/update/5.0-to-5.1.md) - [Upgrade to 5.1 source](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/update/5.0-to-5.1.md)
- [Upgrade to 6.0 source](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/update/5.1-to-6.0.md) - [Upgrade to 6.0 source](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/update/5.1-to-6.0.md)
- [Upgrade to 7.14 source](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/update/6.x-or-7.x-to-7.14.md) - [Upgrade to 7.14 source](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/update/6.x-or-7.x-to-7.14.md)
- [Backup using our Backup rake task](http://docs.gitlab.com/ce/raketasks/backup_restore.html#create-a-backup-of-the-gitlab-system) - [Backup using our Backup rake task](https://docs.gitlab.com/ce/raketasks/backup_restore.html#create-a-backup-of-the-gitlab-system)
- [Perform the MySQL to PostgreSQL migration to convert your backup](http://docs.gitlab.com/ce/update/mysql_to_postgresql.html#converting-a-gitlab-backup-file-from-mysql-to-postgres) - [Perform the MySQL to PostgreSQL migration to convert your backup](https://docs.gitlab.com/ce/update/mysql_to_postgresql.html#converting-a-gitlab-backup-file-from-mysql-to-postgres)
- [Upgrade to Omnibus 7.14](http://doc.gitlab.com/omnibus/update/README.html#upgrading-from-a-non-omnibus-installation-to-an-omnibus-installation) - [Upgrade to Omnibus 7.14](https://docs.gitlab.com/omnibus/update/README.html#upgrading-from-a-non-omnibus-installation-to-an-omnibus-installation)
- [Restore backup using our Restore rake task](http://docs.gitlab.com/ce/raketasks/backup_restore.html#restore-a-previously-created-backup) - [Restore backup using our Restore rake task](https://docs.gitlab.com/ce/raketasks/backup_restore.html#restore-a-previously-created-backup)
- [Upgrade to latest EE](https://about.gitlab.com/downloads-ee) - [Upgrade to latest EE](https://about.gitlab.com/downloads-ee)
- (GitLab inc. only) Acquire and apply a license for the Enterprise Edition product, ask in #support - (GitLab inc. only) Acquire and apply a license for the Enterprise Edition product, ask in #support
- Perform a downgrade from [EE to CE](http://doc.gitlab.com/ee/downgrade_ee_to_ce/README.html) - Perform a downgrade from [EE to CE](https://docs.gitlab.com/ee/downgrade_ee_to_ce/README.html)
#### Start to learn about some of the integrations that we support #### Start to learn about some of the integrations that we support
Our integrations add great value to GitLab. User questions often relate to integrating GitLab with existing external services and the configuration involved Our integrations add great value to GitLab. User questions often relate to integrating GitLab with existing external services and the configuration involved
- Learn about our Integrations (specially, not only): - Learn about our Integrations (specially, not only):
- [LDAP](http://doc.gitlab.com/ee/integration/ldap.html) - [LDAP](https://docs.gitlab.com/ee/integration/ldap.html)
- [JIRA](http://doc.gitlab.com/ee/project_services/jira.html) - [JIRA](https://docs.gitlab.com/ee/project_services/jira.html)
- [Jenkins](http://doc.gitlab.com/ee/integration/jenkins.html) - [Jenkins](https://docs.gitlab.com/ee/integration/jenkins.html)
- [SAML](http://doc.gitlab.com/ce/integration/saml.html) - [SAML](https://docs.gitlab.com/ce/integration/saml.html)
#### Goals #### Goals
...@@ -91,8 +91,8 @@ Our integrations add great value to GitLab. User questions often relate to integ ...@@ -91,8 +91,8 @@ Our integrations add great value to GitLab. User questions often relate to integ
#### Understand the gathering of diagnostics for GitLab instances #### Understand the gathering of diagnostics for GitLab instances
- Learn about the GitLab checks that are available - Learn about the GitLab checks that are available
- [Environment Information and maintenance checks](http://docs.gitlab.com/ce/raketasks/maintenance.html) - [Environment Information and maintenance checks](https://docs.gitlab.com/ce/raketasks/maintenance.html)
- [GitLab check](http://docs.gitlab.com/ce/raketasks/check.html) - [GitLab check](https://docs.gitlab.com/ce/raketasks/check.html)
- Omnibus commands - Omnibus commands
- [Status](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/maintenance/README.md#get-service-status) - [Status](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/maintenance/README.md#get-service-status)
- [Starting and stopping services](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/maintenance/README.md#starting-and-stopping) - [Starting and stopping services](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/maintenance/README.md#starting-and-stopping)
...@@ -167,12 +167,12 @@ Some tickets need specific knowledge or a deep understanding of a particular com ...@@ -167,12 +167,12 @@ Some tickets need specific knowledge or a deep understanding of a particular com
Move on to understanding some of GitLab's more advanced features. You can make use of GitLab.com to understand the features from an end-user perspective and then use your own instance to understand setup and configuration of the feature from an Administrative perspective Move on to understanding some of GitLab's more advanced features. You can make use of GitLab.com to understand the features from an end-user perspective and then use your own instance to understand setup and configuration of the feature from an Administrative perspective
- Set up and try [Git Annex](http://doc.gitlab.com/ee/workflow/git_annex.html) - Set up and try [Git Annex](https://docs.gitlab.com/ee/workflow/git_annex.html)
- Set up and try [Git LFS](http://doc.gitlab.com/ee/workflow/lfs/manage_large_binaries_with_git_lfs.html) - Set up and try [Git LFS](https://docs.gitlab.com/ee/workflow/lfs/manage_large_binaries_with_git_lfs.html)
- Get to know the [GitLab API](http://doc.gitlab.com/ee/api/README.html), its capabilities and shortcomings - Get to know the [GitLab API](https://docs.gitlab.com/ee/api/README.html), its capabilities and shortcomings
- Learn how to [migrate from SVN to Git](http://doc.gitlab.com/ee/workflow/importing/migrating_from_svn.html) - Learn how to [migrate from SVN to Git](https://docs.gitlab.com/ee/workflow/importing/migrating_from_svn.html)
- Set up [GitLab CI](http://doc.gitlab.com/ee/ci/quick_start/README.html) - Set up [GitLab CI](https://docs.gitlab.com/ee/ci/quick_start/README.html)
- Create your first [GitLab Page](http://doc.gitlab.com/ee/pages/administration.html) - Create your first [GitLab Page](https://docs.gitlab.com/ee/pages/administration.html)
- Get to know the GitLab Codebase by reading through the source code: - Get to know the GitLab Codebase by reading through the source code:
- Find the differences between the [EE codebase](https://gitlab.com/gitlab-org/gitlab-ce) - Find the differences between the [EE codebase](https://gitlab.com/gitlab-org/gitlab-ce)
and the [CE codebase](https://gitlab.com/gitlab-org/gitlab-ce) and the [CE codebase](https://gitlab.com/gitlab-org/gitlab-ce)
......
...@@ -33,7 +33,25 @@ resolved discussions tracker. ...@@ -33,7 +33,25 @@ resolved discussions tracker.
!["3/4 discussions resolved"][discussions-resolved] !["3/4 discussions resolved"][discussions-resolved]
## Only allow merge requests to be merged if all discussions are resolved
> [Introduced][ce-7125] in GitLab 8.14.
You can prevent merge requests from being merged until all discussions are resolved.
Navigate to your project's settings page, select the
**Only allow merge requests to be merged if all discussions are resolved** check
box and hit **Save** for the changes to take effect.
![Only allow merge if all the discussions are resolved settings](img/only_allow_merge_if_all_discussions_are_resolved.png)
From now on, you will not be able to merge from the UI until all discussions
are resolved.
![Only allow merge if all the discussions are resolved message](img/only_allow_merge_if_all_discussions_are_resolved_msg.png)
[ce-5022]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5022 [ce-5022]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5022
[ce-7125]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7125
[resolve-discussion-button]: img/resolve_discussion_button.png [resolve-discussion-button]: img/resolve_discussion_button.png
[resolve-comment-button]: img/resolve_comment_button.png [resolve-comment-button]: img/resolve_comment_button.png
[discussion-view]: img/discussion_view.png [discussion-view]: img/discussion_view.png
......
...@@ -40,7 +40,7 @@ hit **Save** for the changes to take effect. ...@@ -40,7 +40,7 @@ hit **Save** for the changes to take effect.
![Only allow merge if build succeeds settings](img/merge_when_build_succeeds_only_if_succeeds_settings.png) ![Only allow merge if build succeeds settings](img/merge_when_build_succeeds_only_if_succeeds_settings.png)
From now on, every time the pipelinefails you will not be able to merge the From now on, every time the pipeline fails you will not be able to merge the
merge request from the UI, until you make all relevant builds pass. merge request from the UI, until you make all relevant builds pass.
![Only allow merge if build succeeds msg](img/merge_when_build_succeeds_only_if_succeeds_msg.png) ![Only allow merge if build succeeds message](img/merge_when_build_succeeds_only_if_succeeds_msg.png)
...@@ -4,7 +4,7 @@ class Spinach::Features::AdminLogs < Spinach::FeatureSteps ...@@ -4,7 +4,7 @@ class Spinach::Features::AdminLogs < Spinach::FeatureSteps
include SharedAdmin include SharedAdmin
step 'I should see tabs with available logs' do step 'I should see tabs with available logs' do
expect(page).to have_content 'production.log' expect(page).to have_content 'test.log'
expect(page).to have_content 'githost.log' expect(page).to have_content 'githost.log'
expect(page).to have_content 'application.log' expect(page).to have_content 'application.log'
end end
......
...@@ -100,6 +100,7 @@ module API ...@@ -100,6 +100,7 @@ module API
end end
expose :only_allow_merge_if_build_succeeds expose :only_allow_merge_if_build_succeeds
expose :request_access_enabled expose :request_access_enabled
expose :only_allow_merge_if_all_discussions_are_resolved
end end
class Member < UserBasic class Member < UserBasic
...@@ -509,6 +510,7 @@ module API ...@@ -509,6 +510,7 @@ module API
expose :after_sign_out_path expose :after_sign_out_path
expose :container_registry_token_expire_delay expose :container_registry_token_expire_delay
expose :repository_storage expose :repository_storage
expose :repository_storages
expose :koding_enabled expose :koding_enabled
expose :koding_url expose :koding_url
end end
......
...@@ -8,11 +8,14 @@ module API ...@@ -8,11 +8,14 @@ module API
# #
# Parameters: # Parameters:
# skip_groups (optional) - Array of group ids to exclude from list # skip_groups (optional) - Array of group ids to exclude from list
# all_available (optional, boolean) - Show all group that you have access to
# Example Request: # Example Request:
# GET /groups # GET /groups
get do get do
@groups = if current_user.admin @groups = if current_user.admin
Group.all Group.all
elsif params[:all_available]
GroupsFinder.new.execute(current_user)
else else
current_user.groups current_user.groups
end end
......
...@@ -25,7 +25,7 @@ module API ...@@ -25,7 +25,7 @@ module API
post ':id/labels' do post ':id/labels' do
authorize! :admin_label, user_project authorize! :admin_label, user_project
label = user_project.find_label(params[:name]) label = available_labels.find_by(title: params[:name])
conflict!('Label already exists') if label conflict!('Label already exists') if label
label = user_project.labels.create(declared(params, include_parent_namespaces: false).to_h) label = user_project.labels.create(declared(params, include_parent_namespaces: false).to_h)
...@@ -46,7 +46,7 @@ module API ...@@ -46,7 +46,7 @@ module API
delete ':id/labels' do delete ':id/labels' do
authorize! :admin_label, user_project authorize! :admin_label, user_project
label = user_project.find_label(params[:name]) label = user_project.labels.find_by(title: params[:name])
not_found!('Label') unless label not_found!('Label') unless label
present label.destroy, with: Entities::Label, current_user: current_user present label.destroy, with: Entities::Label, current_user: current_user
...@@ -65,7 +65,7 @@ module API ...@@ -65,7 +65,7 @@ module API
put ':id/labels' do put ':id/labels' do
authorize! :admin_label, user_project authorize! :admin_label, user_project
label = user_project.find_label(params[:name]) label = user_project.labels.find_by(title: params[:name])
not_found!('Label not found') unless label not_found!('Label not found') unless label
update_params = declared(params, update_params = declared(params,
......
module API module API
# Projects API # Projects API
class ProjectHooks < Grape::API class ProjectHooks < Grape::API
helpers do
params :project_hook_properties do
requires :url, type: String, desc: "The URL to send the request to"
optional :push_events, type: Boolean, desc: "Trigger hook on push events"
optional :issues_events, type: Boolean, desc: "Trigger hook on issues events"
optional :merge_requests_events, type: Boolean, desc: "Trigger hook on merge request events"
optional :tag_push_events, type: Boolean, desc: "Trigger hook on tag push events"
optional :note_events, type: Boolean, desc: "Trigger hook on note(comment) events"
optional :build_events, type: Boolean, desc: "Trigger hook on build events"
optional :pipeline_events, type: Boolean, desc: "Trigger hook on pipeline events"
optional :wiki_events, type: Boolean, desc: "Trigger hook on wiki events"
optional :enable_ssl_verification, type: Boolean, desc: "Do SSL verification when triggering the hook"
optional :token, type: String, desc: "Secret token to validate received payloads; this will not be returned in the response"
end
end
before { authenticate! } before { authenticate! }
before { authorize_admin_project } before { authorize_admin_project }
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects do resource :projects do
# Get project hooks desc 'Get project hooks' do
# success Entities::ProjectHook
# Parameters: end
# id (required) - The ID of a project
# Example Request:
# GET /projects/:id/hooks
get ":id/hooks" do get ":id/hooks" do
@hooks = paginate user_project.hooks hooks = paginate user_project.hooks
present @hooks, with: Entities::ProjectHook
present hooks, with: Entities::ProjectHook
end end
# Get a project hook desc 'Get a project hook' do
# success Entities::ProjectHook
# Parameters: end
# id (required) - The ID of a project params do
# hook_id (required) - The ID of a project hook requires :hook_id, type: Integer, desc: 'The ID of a project hook'
# Example Request: end
# GET /projects/:id/hooks/:hook_id
get ":id/hooks/:hook_id" do get ":id/hooks/:hook_id" do
@hook = user_project.hooks.find(params[:hook_id]) hook = user_project.hooks.find(params[:hook_id])
present @hook, with: Entities::ProjectHook present hook, with: Entities::ProjectHook
end end
# Add hook to project desc 'Add hook to project' do
# success Entities::ProjectHook
# Parameters: end
# id (required) - The ID of a project params do
# url (required) - The hook URL use :project_hook_properties
# Example Request: end
# POST /projects/:id/hooks
post ":id/hooks" do post ":id/hooks" do
required_attributes! [:url] new_hook_params = declared(params, include_missing: false, include_parent_namespaces: false).to_h
attrs = attributes_for_keys [ hook = user_project.hooks.new(new_hook_params)
:url,
:push_events,
:issues_events,
:merge_requests_events,
:tag_push_events,
:note_events,
:build_events,
:pipeline_events,
:wiki_page_events,
:enable_ssl_verification,
:token
]
@hook = user_project.hooks.new(attrs)
if @hook.save if hook.save
present @hook, with: Entities::ProjectHook present hook, with: Entities::ProjectHook
else else
if @hook.errors[:url].present? error!("Invalid url given", 422) if hook.errors[:url].present?
error!("Invalid url given", 422)
end not_found!("Project hook #{hook.errors.messages}")
not_found!("Project hook #{@hook.errors.messages}")
end end
end end
# Update an existing project hook desc 'Update an existing project hook' do
# success Entities::ProjectHook
# Parameters: end
# id (required) - The ID of a project params do
# hook_id (required) - The ID of a project hook requires :hook_id, type: Integer, desc: "The ID of the hook to update"
# url (required) - The hook URL use :project_hook_properties
# Example Request: end
# PUT /projects/:id/hooks/:hook_id
put ":id/hooks/:hook_id" do put ":id/hooks/:hook_id" do
@hook = user_project.hooks.find(params[:hook_id]) hook = user_project.hooks.find(params[:hook_id])
required_attributes! [:url]
attrs = attributes_for_keys [ new_params = declared(params, include_missing: false, include_parent_namespaces: false).to_h
:url, new_params.delete('hook_id')
:push_events,
:issues_events,
:merge_requests_events,
:tag_push_events,
:note_events,
:build_events,
:pipeline_events,
:wiki_page_events,
:enable_ssl_verification,
:token
]
if @hook.update_attributes attrs if hook.update_attributes(new_params)
present @hook, with: Entities::ProjectHook present hook, with: Entities::ProjectHook
else else
if @hook.errors[:url].present? error!("Invalid url given", 422) if hook.errors[:url].present?
error!("Invalid url given", 422)
end not_found!("Project hook #{hook.errors.messages}")
not_found!("Project hook #{@hook.errors.messages}")
end end
end end
# Deletes project hook. This is an idempotent function. desc 'Deletes project hook' do
# success Entities::ProjectHook
# Parameters: end
# id (required) - The ID of a project params do
# hook_id (required) - The ID of hook to delete requires :hook_id, type: Integer, desc: 'The ID of the hook to delete'
# Example Request: end
# DELETE /projects/:id/hooks/:hook_id
delete ":id/hooks/:hook_id" do delete ":id/hooks/:hook_id" do
required_attributes! [:hook_id]
begin begin
@hook = user_project.hooks.destroy(params[:hook_id]) present user_project.hooks.destroy(params[:hook_id]), with: Entities::ProjectHook
rescue rescue
# ProjectHook can raise Error if hook_id not found # ProjectHook can raise Error if hook_id not found
not_found!("Error deleting hook #{params[:hook_id]}") not_found!("Error deleting hook #{params[:hook_id]}")
......
...@@ -139,7 +139,8 @@ module API ...@@ -139,7 +139,8 @@ module API
:shared_runners_enabled, :shared_runners_enabled,
:snippets_enabled, :snippets_enabled,
:visibility_level, :visibility_level,
:wiki_enabled] :wiki_enabled,
:only_allow_merge_if_all_discussions_are_resolved]
attrs = map_public_to_visibility_level(attrs) attrs = map_public_to_visibility_level(attrs)
@project = ::Projects::CreateService.new(current_user, attrs).execute @project = ::Projects::CreateService.new(current_user, attrs).execute
if @project.saved? if @project.saved?
...@@ -193,7 +194,8 @@ module API ...@@ -193,7 +194,8 @@ module API
:shared_runners_enabled, :shared_runners_enabled,
:snippets_enabled, :snippets_enabled,
:visibility_level, :visibility_level,
:wiki_enabled] :wiki_enabled,
:only_allow_merge_if_all_discussions_are_resolved]
attrs = map_public_to_visibility_level(attrs) attrs = map_public_to_visibility_level(attrs)
@project = ::Projects::CreateService.new(user, attrs).execute @project = ::Projects::CreateService.new(user, attrs).execute
if @project.saved? if @project.saved?
...@@ -275,7 +277,8 @@ module API ...@@ -275,7 +277,8 @@ module API
:shared_runners_enabled, :shared_runners_enabled,
:snippets_enabled, :snippets_enabled,
:visibility_level, :visibility_level,
:wiki_enabled] :wiki_enabled,
:only_allow_merge_if_all_discussions_are_resolved]
attrs = map_public_to_visibility_level(attrs) attrs = map_public_to_visibility_level(attrs)
authorize_admin_project authorize_admin_project
authorize! :rename_project, user_project if attrs[:name].present? authorize! :rename_project, user_project if attrs[:name].present?
......
...@@ -17,12 +17,12 @@ module API ...@@ -17,12 +17,12 @@ module API
present current_settings, with: Entities::ApplicationSetting present current_settings, with: Entities::ApplicationSetting
end end
# Modify applicaiton settings # Modify application settings
# #
# Example Request: # Example Request:
# PUT /application/settings # PUT /application/settings
put "application/settings" do put "application/settings" do
attributes = current_settings.attributes.keys - ["id"] attributes = ["repository_storage"] + current_settings.attributes.keys - ["id"]
attrs = attributes_for_keys(attributes) attrs = attributes_for_keys(attributes)
if current_settings.update_attributes(attrs) if current_settings.update_attributes(attrs)
......
module Gitlab module Gitlab
class ProductionLogger < Gitlab::Logger class EnvironmentLogger < Gitlab::Logger
def self.file_name_noext def self.file_name_noext
'production' Rails.env
end end
end end
end end
namespace :gitlab do
desc "GitLab | Generate sdocs for project"
task generate_docs: :environment do
system(*%W(bundle exec sdoc -o doc/code app lib))
end
end
...@@ -10,54 +10,38 @@ describe 'bin/changelog' do ...@@ -10,54 +10,38 @@ describe 'bin/changelog' do
expect(options.amend).to eq true expect(options.amend).to eq true
end end
it 'parses --force' do it 'parses --force and -f' do
options = described_class.parse(%w[foo --force bar]) %w[--force -f].each do |flag|
options = described_class.parse(%W[foo #{flag} bar])
expect(options.force).to eq true expect(options.force).to eq true
end
end end
it 'parses -f' do it 'parses --merge-request and -m' do
options = described_class.parse(%w[foo -f bar]) %w[--merge-request -m].each do |flag|
options = described_class.parse(%W[foo #{flag} 1234 bar])
expect(options.force).to eq true expect(options.merge_request).to eq 1234
end
end end
it 'parses --merge-request' do it 'parses --dry-run and -n' do
options = described_class.parse(%w[foo --merge-request 1234 bar]) %w[--dry-run -n].each do |flag|
options = described_class.parse(%W[foo #{flag} bar])
expect(options.merge_request).to eq 1234 expect(options.dry_run).to eq true
end
end end
it 'parses -m' do it 'parses --git-username and -u' do
options = described_class.parse(%w[foo -m 4321 bar])
expect(options.merge_request).to eq 4321
end
it 'parses --dry-run' do
options = described_class.parse(%w[foo --dry-run bar])
expect(options.dry_run).to eq true
end
it 'parses -n' do
options = described_class.parse(%w[foo -n bar])
expect(options.dry_run).to eq true
end
it 'parses --git-username' do
allow(described_class).to receive(:git_user_name).and_return('Jane Doe') allow(described_class).to receive(:git_user_name).and_return('Jane Doe')
options = described_class.parse(%w[foo --git-username bar])
expect(options.author).to eq 'Jane Doe'
end
it 'parses -u' do %w[--git-username -u].each do |flag|
allow(described_class).to receive(:git_user_name).and_return('John Smith') options = described_class.parse(%W[foo #{flag} bar])
options = described_class.parse(%w[foo -u bar])
expect(options.author).to eq 'John Smith' expect(options.author).to eq 'Jane Doe'
end
end end
it 'parses -h' do it 'parses -h' do
......
...@@ -297,6 +297,72 @@ describe Projects::MergeRequestsController do ...@@ -297,6 +297,72 @@ describe Projects::MergeRequestsController do
end end
end end
end end
describe 'only_allow_merge_if_all_discussions_are_resolved? setting' do
let(:merge_request) { create(:merge_request_with_diff_notes, source_project: project, author: user) }
context 'when enabled' do
before do
project.update_column(:only_allow_merge_if_all_discussions_are_resolved, true)
end
context 'with unresolved discussion' do
before do
expect(merge_request).not_to be_discussions_resolved
end
it 'returns :failed' do
merge_with_sha
expect(assigns(:status)).to eq(:failed)
end
end
context 'with all discussions resolved' do
before do
merge_request.discussions.each { |d| d.resolve!(user) }
expect(merge_request).to be_discussions_resolved
end
it 'returns :success' do
merge_with_sha
expect(assigns(:status)).to eq(:success)
end
end
end
context 'when disabled' do
before do
project.update_column(:only_allow_merge_if_all_discussions_are_resolved, false)
end
context 'with unresolved discussion' do
before do
expect(merge_request).not_to be_discussions_resolved
end
it 'returns :success' do
merge_with_sha
expect(assigns(:status)).to eq(:success)
end
end
context 'with all discussions resolved' do
before do
merge_request.discussions.each { |d| d.resolve!(user) }
expect(merge_request).to be_discussions_resolved
end
it 'returns :success' do
merge_with_sha
expect(assigns(:status)).to eq(:success)
end
end
end
end
end end
end end
......
...@@ -68,6 +68,11 @@ FactoryGirl.define do ...@@ -68,6 +68,11 @@ FactoryGirl.define do
factory :closed_merge_request, traits: [:closed] factory :closed_merge_request, traits: [:closed]
factory :reopened_merge_request, traits: [:reopened] factory :reopened_merge_request, traits: [:reopened]
factory :merge_request_with_diffs, traits: [:with_diffs] factory :merge_request_with_diffs, traits: [:with_diffs]
factory :merge_request_with_diff_notes do
after(:create) do |mr|
create(:diff_note_on_merge_request, noteable: mr, project: mr.source_project)
end
end
factory :labeled_merge_request do factory :labeled_merge_request do
transient do transient do
......
...@@ -7,15 +7,16 @@ describe "Admin Runners" do ...@@ -7,15 +7,16 @@ describe "Admin Runners" do
describe "Runners page" do describe "Runners page" do
before do before do
runner = FactoryGirl.create(:ci_runner) runner = FactoryGirl.create(:ci_runner, contacted_at: Time.now)
pipeline = FactoryGirl.create(:ci_pipeline) pipeline = FactoryGirl.create(:ci_pipeline)
FactoryGirl.create(:ci_build, pipeline: pipeline, runner_id: runner.id) FactoryGirl.create(:ci_build, pipeline: pipeline, runner_id: runner.id)
visit admin_runners_path visit admin_runners_path
end end
it { page.has_text? "Manage Runners" } it 'has all necessary texts' do
it { page.has_text? "To register a new runner" } expect(page).to have_text "To register a new Runner"
it { page.has_text? "Runners with last contact less than a minute ago: 1" } expect(page).to have_text "Runners with last contact less than a minute ago: 1"
end
describe 'search' do describe 'search' do
before do before do
...@@ -27,8 +28,10 @@ describe "Admin Runners" do ...@@ -27,8 +28,10 @@ describe "Admin Runners" do
search_form.click_button 'Search' search_form.click_button 'Search'
end end
it { expect(page).to have_content("runner-foo") } it 'shows correct runner' do
it { expect(page).not_to have_content("runner-bar") } expect(page).to have_content("runner-foo")
expect(page).not_to have_content("runner-bar")
end
end end
end end
...@@ -46,8 +49,10 @@ describe "Admin Runners" do ...@@ -46,8 +49,10 @@ describe "Admin Runners" do
end end
describe 'projects' do describe 'projects' do
it { expect(page).to have_content(@project1.name_with_namespace) } it 'contains project names' do
it { expect(page).to have_content(@project2.name_with_namespace) } expect(page).to have_content(@project1.name_with_namespace)
expect(page).to have_content(@project2.name_with_namespace)
end
end end
describe 'search' do describe 'search' do
...@@ -57,8 +62,10 @@ describe "Admin Runners" do ...@@ -57,8 +62,10 @@ describe "Admin Runners" do
search_form.click_button 'Search' search_form.click_button 'Search'
end end
it { expect(page).to have_content(@project1.name_with_namespace) } it 'contains name of correct project' do
it { expect(page).not_to have_content(@project2.name_with_namespace) } expect(page).to have_content(@project1.name_with_namespace)
expect(page).not_to have_content(@project2.name_with_namespace)
end
end end
describe 'enable/create' do describe 'enable/create' do
......
...@@ -19,6 +19,17 @@ describe "Dashboard Issues Feed", feature: true do ...@@ -19,6 +19,17 @@ describe "Dashboard Issues Feed", feature: true do
expect(body).to have_selector('title', text: "#{user.name} issues") expect(body).to have_selector('title', text: "#{user.name} issues")
end end
it "renders atom feed with url parameters" do
visit issues_dashboard_path(:atom, private_token: user.private_token, state: 'opened', assignee_id: user.id)
link = find('link[type="application/atom+xml"]')
params = CGI::parse(URI.parse(link[:href]).query)
expect(params).to include('private_token' => [user.private_token])
expect(params).to include('state' => ['opened'])
expect(params).to include('assignee_id' => [user.id.to_s])
end
context "issue with basic fields" do context "issue with basic fields" do
let!(:issue2) { create(:issue, author: user, assignee: user, project: project2, description: 'test desc') } let!(:issue2) { create(:issue, author: user, assignee: user, project: project2, description: 'test desc') }
......
...@@ -3,10 +3,14 @@ require 'spec_helper' ...@@ -3,10 +3,14 @@ require 'spec_helper'
describe 'Issues Feed', feature: true do describe 'Issues Feed', feature: true do
describe 'GET /issues' do describe 'GET /issues' do
let!(:user) { create(:user) } let!(:user) { create(:user) }
let!(:group) { create(:group) }
let!(:project) { create(:project) } let!(:project) { create(:project) }
let!(:issue) { create(:issue, author: user, project: project) } let!(:issue) { create(:issue, author: user, project: project) }
before { project.team << [user, :developer] } before do
project.team << [user, :developer]
group.add_developer(user)
end
context 'when authenticated' do context 'when authenticated' do
it 'renders atom feed' do it 'renders atom feed' do
...@@ -33,5 +37,28 @@ describe 'Issues Feed', feature: true do ...@@ -33,5 +37,28 @@ describe 'Issues Feed', feature: true do
expect(body).to have_selector('entry summary', text: issue.title) expect(body).to have_selector('entry summary', text: issue.title)
end end
end end
it "renders atom feed with url parameters for project issues" do
visit namespace_project_issues_path(project.namespace, project,
:atom, private_token: user.private_token, state: 'opened', assignee_id: user.id)
link = find('link[type="application/atom+xml"]')
params = CGI::parse(URI.parse(link[:href]).query)
expect(params).to include('private_token' => [user.private_token])
expect(params).to include('state' => ['opened'])
expect(params).to include('assignee_id' => [user.id.to_s])
end
it "renders atom feed with url parameters for group issues" do
visit issues_group_path(group, :atom, private_token: user.private_token, state: 'opened', assignee_id: user.id)
link = find('link[type="application/atom+xml"]')
params = CGI::parse(URI.parse(link[:href]).query)
expect(params).to include('private_token' => [user.private_token])
expect(params).to include('state' => ['opened'])
expect(params).to include('assignee_id' => [user.id.to_s])
end
end end
end end
...@@ -12,11 +12,15 @@ describe 'Commits' do ...@@ -12,11 +12,15 @@ describe 'Commits' do
end end
let!(:pipeline) do let!(:pipeline) do
FactoryGirl.create :ci_pipeline, project: project, sha: project.commit.sha create(:ci_pipeline,
project: project,
ref: project.default_branch,
sha: project.commit.sha,
status: :success)
end end
context 'commit status is Generic Commit Status' do context 'commit status is Generic Commit Status' do
let!(:status) { FactoryGirl.create :generic_commit_status, pipeline: pipeline } let!(:status) { create(:generic_commit_status, pipeline: pipeline) }
before do before do
project.team << [@user, :reporter] project.team << [@user, :reporter]
...@@ -39,7 +43,7 @@ describe 'Commits' do ...@@ -39,7 +43,7 @@ describe 'Commits' do
end end
context 'commit status is Ci Build' do context 'commit status is Ci Build' do
let!(:build) { FactoryGirl.create :ci_build, pipeline: pipeline } let!(:build) { create(:ci_build, pipeline: pipeline) }
let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') } let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
context 'when logged as developer' do context 'when logged as developer' do
...@@ -48,13 +52,22 @@ describe 'Commits' do ...@@ -48,13 +52,22 @@ describe 'Commits' do
end end
describe 'Project commits' do describe 'Project commits' do
let!(:pipeline_from_other_branch) do
create(:ci_pipeline,
project: project,
ref: 'fix',
sha: project.commit.sha,
status: :failed)
end
before do before do
visit namespace_project_commits_path(project.namespace, project, :master) visit namespace_project_commits_path(project.namespace, project, :master)
end end
it 'shows build status' do it 'shows correct build status from default branch' do
page.within("//li[@id='commit-#{pipeline.short_sha}']") do page.within("//li[@id='commit-#{pipeline.short_sha}']") do
expect(page).to have_css(".ci-status-link") expect(page).to have_css('.ci-status-link')
expect(page).to have_css('.ci-status-icon-success')
end end
end end
end end
...@@ -64,9 +77,11 @@ describe 'Commits' do ...@@ -64,9 +77,11 @@ describe 'Commits' do
visit ci_status_path(pipeline) visit ci_status_path(pipeline)
end end
it { expect(page).to have_content pipeline.sha[0..7] } it 'shows pipeline`s data' do
it { expect(page).to have_content pipeline.git_commit_message } expect(page).to have_content pipeline.sha[0..7]
it { expect(page).to have_content pipeline.git_author_name } expect(page).to have_content pipeline.git_commit_message
expect(page).to have_content pipeline.git_author_name
end
end end
context 'Download artifacts' do context 'Download artifacts' do
......
...@@ -44,6 +44,22 @@ describe "Dashboard Issues filtering", feature: true, js: true do ...@@ -44,6 +44,22 @@ describe "Dashboard Issues filtering", feature: true, js: true do
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
expect(page).to have_selector('.issue', count: 1) expect(page).to have_selector('.issue', count: 1)
end end
it 'updates atom feed link' do
visit_issues(milestone_title: '', assignee_id: user.id)
link = find('.nav-controls a', text: 'Subscribe')
params = CGI::parse(URI.parse(link[:href]).query)
auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
auto_discovery_params = CGI::parse(URI.parse(auto_discovery_link[:href]).query)
expect(params).to include('private_token' => [user.private_token])
expect(params).to include('milestone_title' => [''])
expect(params).to include('assignee_id' => [user.id.to_s])
expect(auto_discovery_params).to include('private_token' => [user.private_token])
expect(auto_discovery_params).to include('milestone_title' => [''])
expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s])
end
end end
def show_milestone_dropdown def show_milestone_dropdown
...@@ -51,7 +67,7 @@ describe "Dashboard Issues filtering", feature: true, js: true do ...@@ -51,7 +67,7 @@ describe "Dashboard Issues filtering", feature: true, js: true do
expect(page).to have_selector('.dropdown-content', visible: true) expect(page).to have_selector('.dropdown-content', visible: true)
end end
def visit_issues def visit_issues(*args)
visit issues_dashboard_path visit issues_dashboard_path(*args)
end end
end end
...@@ -4,6 +4,7 @@ describe 'Filter issues', feature: true do ...@@ -4,6 +4,7 @@ describe 'Filter issues', feature: true do
include WaitForAjax include WaitForAjax
let!(:project) { create(:project) } let!(:project) { create(:project) }
let!(:group) { create(:group) }
let!(:user) { create(:user)} let!(:user) { create(:user)}
let!(:milestone) { create(:milestone, project: project) } let!(:milestone) { create(:milestone, project: project) }
let!(:label) { create(:label, project: project) } let!(:label) { create(:label, project: project) }
...@@ -11,6 +12,7 @@ describe 'Filter issues', feature: true do ...@@ -11,6 +12,7 @@ describe 'Filter issues', feature: true do
before do before do
project.team << [user, :master] project.team << [user, :master]
group.add_developer(user)
login_as(user) login_as(user)
create(:issue, project: project) create(:issue, project: project)
end end
...@@ -347,4 +349,36 @@ describe 'Filter issues', feature: true do ...@@ -347,4 +349,36 @@ describe 'Filter issues', feature: true do
end end
end end
end end
it 'updates atom feed link for project issues' do
visit namespace_project_issues_path(project.namespace, project, milestone_title: '', assignee_id: user.id)
link = find('.nav-controls a', text: 'Subscribe')
params = CGI::parse(URI.parse(link[:href]).query)
auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
auto_discovery_params = CGI::parse(URI.parse(auto_discovery_link[:href]).query)
expect(params).to include('private_token' => [user.private_token])
expect(params).to include('milestone_title' => [''])
expect(params).to include('assignee_id' => [user.id.to_s])
expect(auto_discovery_params).to include('private_token' => [user.private_token])
expect(auto_discovery_params).to include('milestone_title' => [''])
expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s])
end
it 'updates atom feed link for group issues' do
visit issues_group_path(group, milestone_title: '', assignee_id: user.id)
link = find('.nav-controls a', text: 'Subscribe')
params = CGI::parse(URI.parse(link[:href]).query)
auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
auto_discovery_params = CGI::parse(URI.parse(auto_discovery_link[:href]).query)
expect(params).to include('private_token' => [user.private_token])
expect(params).to include('milestone_title' => [''])
expect(params).to include('assignee_id' => [user.id.to_s])
expect(auto_discovery_params).to include('private_token' => [user.private_token])
expect(auto_discovery_params).to include('milestone_title' => [''])
expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s])
end
end end
require 'spec_helper'
feature 'Check if mergeable with unresolved discussions', js: true, feature: true do
let(:user) { create(:user) }
let(:project) { create(:project) }
let!(:merge_request) { create(:merge_request_with_diff_notes, source_project: project, author: user) }
before do
login_as user
project.team << [user, :master]
end
context 'when project.only_allow_merge_if_all_discussions_are_resolved == true' do
before do
project.update_column(:only_allow_merge_if_all_discussions_are_resolved, true)
end
context 'with unresolved discussions' do
it 'does not allow to merge' do
visit_merge_request(merge_request)
expect(page).not_to have_button 'Accept Merge Request'
expect(page).to have_content('This merge request has unresolved discussions')
end
end
context 'with all discussions resolved' do
before do
merge_request.discussions.each { |d| d.resolve!(user) }
end
it 'allows MR to be merged' do
visit_merge_request(merge_request)
expect(page).to have_button 'Accept Merge Request'
end
end
end
context 'when project.only_allow_merge_if_all_discussions_are_resolved == false' do
before do
project.update_column(:only_allow_merge_if_all_discussions_are_resolved, false)
end
context 'with unresolved discussions' do
it 'does not allow to merge' do
visit_merge_request(merge_request)
expect(page).to have_button 'Accept Merge Request'
end
end
context 'with all discussions resolved' do
before do
merge_request.discussions.each { |d| d.resolve!(user) }
end
it 'allows MR to be merged' do
visit_merge_request(merge_request)
expect(page).to have_button 'Accept Merge Request'
end
end
end
def visit_merge_request(merge_request)
visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
end
end
...@@ -3,7 +3,7 @@ require 'spec_helper' ...@@ -3,7 +3,7 @@ require 'spec_helper'
feature 'Edit Merge Request', feature: true do feature 'Edit Merge Request', feature: true do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, :public) } let(:project) { create(:project, :public) }
let(:merge_request) { create(:merge_request, :with_diffs, source_project: project) } let(:merge_request) { create(:merge_request, :simple, source_project: project) }
before do before do
project.team << [user, :master] project.team << [user, :master]
...@@ -28,5 +28,17 @@ feature 'Edit Merge Request', feature: true do ...@@ -28,5 +28,17 @@ feature 'Edit Merge Request', feature: true do
expect(page).to have_content 'Someone edited the merge request the same time you did' expect(page).to have_content 'Someone edited the merge request the same time you did'
end end
it 'allows to unselect "Remove source branch"' do
merge_request.update(merge_params: { 'force_remove_source_branch' => '1' })
expect(merge_request.merge_params['force_remove_source_branch']).to be_truthy
visit edit_namespace_project_merge_request_path(project.namespace, project, merge_request)
uncheck 'Remove source branch when merge request is accepted'
click_button 'Save changes'
expect(page).to have_content 'Remove source branch'
end
end end
end end
...@@ -79,12 +79,14 @@ describe "Builds" do ...@@ -79,12 +79,14 @@ describe "Builds" do
click_link "Cancel running" click_link "Cancel running"
end end
it { expect(page).to have_selector('.nav-links li.active', text: 'All') } it 'shows all necessary content' do
it { expect(page).to have_content 'canceled' } expect(page).to have_selector('.nav-links li.active', text: 'All')
it { expect(page).to have_content @build.short_sha } expect(page).to have_content 'canceled'
it { expect(page).to have_content @build.ref } expect(page).to have_content @build.short_sha
it { expect(page).to have_content @build.name } expect(page).to have_content @build.ref
it { expect(page).not_to have_link 'Cancel running' } expect(page).to have_content @build.name
expect(page).not_to have_link 'Cancel running'
end
end end
describe "GET /:project/builds/:id" do describe "GET /:project/builds/:id" do
...@@ -93,10 +95,12 @@ describe "Builds" do ...@@ -93,10 +95,12 @@ describe "Builds" do
visit namespace_project_build_path(@project.namespace, @project, @build) visit namespace_project_build_path(@project.namespace, @project, @build)
end end
it { expect(page.status_code).to eq(200) } it 'shows commit`s data' do
it { expect(page).to have_content @commit.sha[0..7] } expect(page.status_code).to eq(200)
it { expect(page).to have_content @commit.git_commit_message } expect(page).to have_content @commit.sha[0..7]
it { expect(page).to have_content @commit.git_author_name } expect(page).to have_content @commit.git_commit_message
expect(page).to have_content @commit.git_author_name
end
end end
context "Build from other project" do context "Build from other project" do
...@@ -167,7 +171,7 @@ describe "Builds" do ...@@ -167,7 +171,7 @@ describe "Builds" do
describe 'Variables' do describe 'Variables' do
before do before do
@trigger_request = create :ci_trigger_request_with_variables @trigger_request = create :ci_trigger_request_with_variables
@build = create :ci_build, pipeline: @commit, trigger_request: @trigger_request @build = create :ci_build, pipeline: @commit, trigger_request: @trigger_request
visit namespace_project_build_path(@project.namespace, @project, @build) visit namespace_project_build_path(@project.namespace, @project, @build)
end end
...@@ -176,14 +180,14 @@ describe "Builds" do ...@@ -176,14 +180,14 @@ describe "Builds" do
expect(page).to have_css('.reveal-variables') expect(page).to have_css('.reveal-variables')
expect(page).not_to have_css('.js-build-variable') expect(page).not_to have_css('.js-build-variable')
expect(page).not_to have_css('.js-build-value') expect(page).not_to have_css('.js-build-value')
click_button 'Reveal Variables' click_button 'Reveal Variables'
expect(page).not_to have_css('.reveal-variables') expect(page).not_to have_css('.reveal-variables')
expect(page).to have_selector('.js-build-variable', text: 'TRIGGER_KEY_1') expect(page).to have_selector('.js-build-variable', text: 'TRIGGER_KEY_1')
expect(page).to have_selector('.js-build-value', text: 'TRIGGER_VALUE_1') expect(page).to have_selector('.js-build-value', text: 'TRIGGER_VALUE_1')
end end
end end
end end
describe "POST /:project/builds/:id/cancel" do describe "POST /:project/builds/:id/cancel" do
...@@ -194,9 +198,11 @@ describe "Builds" do ...@@ -194,9 +198,11 @@ describe "Builds" do
click_link "Cancel" click_link "Cancel"
end end
it { expect(page.status_code).to eq(200) } it 'loads the page and shows all needed controls' do
it { expect(page).to have_content 'canceled' } expect(page.status_code).to eq(200)
it { expect(page).to have_content 'Retry' } expect(page).to have_content 'canceled'
expect(page).to have_content 'Retry'
end
end end
context "Build from other project" do context "Build from other project" do
......
...@@ -183,4 +183,19 @@ describe 'Edit Project Settings', feature: true do ...@@ -183,4 +183,19 @@ describe 'Edit Project Settings', feature: true do
end end
end end
end end
# Regression spec for https://gitlab.com/gitlab-org/gitlab-ce/issues/24056
describe 'project statistic visibility' do
let!(:project) { create(:project, :private) }
before do
project.team << [member, :guest]
login_as(member)
visit namespace_project_path(project.namespace, project)
end
it "does not show project statistic for guest" do
expect(page).not_to have_selector('.project-stats')
end
end
end end
...@@ -36,17 +36,54 @@ describe 'Dashboard > User filters todos', feature: true, js: true do ...@@ -36,17 +36,54 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
expect(page).not_to have_content project_2.name_with_namespace expect(page).not_to have_content project_2.name_with_namespace
end end
it 'filters by author' do context "Author filter" do
click_button 'Author' it 'filters by author' do
within '.dropdown-menu-author' do click_button 'Author'
fill_in 'Search authors', with: user_1.name
click_link user_1.name within '.dropdown-menu-author' do
fill_in 'Search authors', with: user_1.name
click_link user_1.name
end
wait_for_ajax
expect(find('.todos-list')).to have_content user_1.name
expect(find('.todos-list')).not_to have_content user_2.name
end end
wait_for_ajax it "shows only authors of existing todos" do
click_button 'Author'
within '.dropdown-menu-author' do
# It should contain two users + "Any Author"
expect(page).to have_selector('.dropdown-menu-user-link', count: 3)
expect(page).to have_content(user_1.name)
expect(page).to have_content(user_2.name)
end
end
expect(find('.todos-list')).to have_content user_1.name it "shows only authors of existing done todos" do
expect(find('.todos-list')).not_to have_content user_2.name user_3 = create :user
user_4 = create :user
create(:todo, user: user_1, author: user_3, project: project_1, target: issue, action: 1, state: :done)
create(:todo, user: user_1, author: user_4, project: project_2, target: merge_request, action: 2, state: :done)
project_1.team << [user_3, :developer]
project_2.team << [user_4, :developer]
visit dashboard_todos_path(state: 'done')
click_button 'Author'
within '.dropdown-menu-author' do
# It should contain two users + "Any Author"
expect(page).to have_selector('.dropdown-menu-user-link', count: 3)
expect(page).to have_content(user_3.name)
expect(page).to have_content(user_4.name)
expect(page).not_to have_content(user_1.name)
expect(page).not_to have_content(user_2.name)
end
end
end end
it 'filters by type' do it 'filters by type' do
......
/* eslint-disable */ /* eslint-disable */
/*= require merge_request_widget */ /*= require merge_request_widget */
/*= require lib/utils/jquery.timeago.js */ /*= require jquery.timeago.js */
(function() { (function() {
describe('MergeRequestWidget', function() { describe('MergeRequestWidget', function() {
......
...@@ -41,14 +41,62 @@ describe ApplicationSetting, models: true do ...@@ -41,14 +41,62 @@ describe ApplicationSetting, models: true do
subject { setting } subject { setting }
end end
context 'repository storages inclussion' do # Upgraded databases will have this sort of content
context 'repository_storages is a String, not an Array' do
before { setting.__send__(:raw_write_attribute, :repository_storages, 'default') }
it { expect(setting.repository_storages_before_type_cast).to eq('default') }
it { expect(setting.repository_storages).to eq(['default']) }
end
context 'repository storages' do
before do before do
storages = { 'custom' => 'tmp/tests/custom_repositories' } storages = {
'custom1' => 'tmp/tests/custom_repositories_1',
'custom2' => 'tmp/tests/custom_repositories_2',
'custom3' => 'tmp/tests/custom_repositories_3',
}
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages) allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
end end
it { is_expected.to allow_value('custom').for(:repository_storage) } describe 'inclusion' do
it { is_expected.not_to allow_value('alternative').for(:repository_storage) } it { is_expected.to allow_value('custom1').for(:repository_storages) }
it { is_expected.to allow_value(['custom2', 'custom3']).for(:repository_storages) }
it { is_expected.not_to allow_value('alternative').for(:repository_storages) }
it { is_expected.not_to allow_value(['alternative', 'custom1']).for(:repository_storages) }
end
describe 'presence' do
it { is_expected.not_to allow_value([]).for(:repository_storages) }
it { is_expected.not_to allow_value("").for(:repository_storages) }
it { is_expected.not_to allow_value(nil).for(:repository_storages) }
end
describe '.pick_repository_storage' do
it 'uses Array#sample to pick a random storage' do
array = double('array', sample: 'random')
expect(setting).to receive(:repository_storages).and_return(array)
expect(setting.pick_repository_storage).to eq('random')
end
describe '#repository_storage' do
it 'returns the first storage' do
setting.repository_storages = ['good', 'bad']
expect(setting.repository_storage).to eq('good')
end
end
describe '#repository_storage=' do
it 'overwrites repository_storages' do
setting.repository_storage = 'overwritten'
expect(setting.repository_storages).to eq(['overwritten'])
end
end
end
end end
end end
......
...@@ -205,12 +205,53 @@ eos ...@@ -205,12 +205,53 @@ eos
end end
end end
describe '#ci_commits' do
# TODO: kamil
end
describe '#status' do describe '#status' do
# TODO: kamil context 'without arguments for compound status' do
shared_examples 'giving the status from pipeline' do
it do
expect(commit.status).to eq(Ci::Pipeline.status)
end
end
context 'with pipelines' do
let!(:pipeline) do
create(:ci_empty_pipeline, project: project, sha: commit.sha)
end
it_behaves_like 'giving the status from pipeline'
end
context 'without pipelines' do
it_behaves_like 'giving the status from pipeline'
end
end
context 'when a particular ref is specified' do
let!(:pipeline_from_master) do
create(:ci_empty_pipeline,
project: project,
sha: commit.sha,
ref: 'master',
status: 'failed')
end
let!(:pipeline_from_fix) do
create(:ci_empty_pipeline,
project: project,
sha: commit.sha,
ref: 'fix',
status: 'success')
end
it 'gives pipelines from a particular branch' do
expect(commit.status('master')).to eq(pipeline_from_master.status)
expect(commit.status('fix')).to eq(pipeline_from_fix.status)
end
it 'gives compound status if ref is nil' do
expect(commit.status(nil)).to eq(commit.status)
end
end
end end
describe '#participants' do describe '#participants' do
......
...@@ -825,11 +825,8 @@ describe MergeRequest, models: true do ...@@ -825,11 +825,8 @@ describe MergeRequest, models: true do
end end
context 'when failed' do context 'when failed' do
before { allow(subject).to receive(:broken?) { false } } context 'when #mergeable_ci_state? is false' do
context 'when project settings restrict to merge only if build succeeds and build failed' do
before do before do
project.only_allow_merge_if_build_succeeds = true
allow(subject).to receive(:mergeable_ci_state?) { false } allow(subject).to receive(:mergeable_ci_state?) { false }
end end
...@@ -837,6 +834,16 @@ describe MergeRequest, models: true do ...@@ -837,6 +834,16 @@ describe MergeRequest, models: true do
expect(subject.mergeable_state?).to be_falsey expect(subject.mergeable_state?).to be_falsey
end end
end end
context 'when #mergeable_discussions_state? is false' do
before do
allow(subject).to receive(:mergeable_discussions_state?) { false }
end
it 'returns false' do
expect(subject.mergeable_state?).to be_falsey
end
end
end end
end end
...@@ -887,7 +894,49 @@ describe MergeRequest, models: true do ...@@ -887,7 +894,49 @@ describe MergeRequest, models: true do
end end
end end
describe '#environments' do describe '#mergeable_discussions_state?' do
let(:merge_request) { create(:merge_request_with_diff_notes, source_project: project) }
context 'when project.only_allow_merge_if_all_discussions_are_resolved == true' do
let(:project) { create(:project, only_allow_merge_if_all_discussions_are_resolved: true) }
context 'with all discussions resolved' do
before do
merge_request.discussions.each { |d| d.resolve!(merge_request.author) }
end
it 'returns true' do
expect(merge_request.mergeable_discussions_state?).to be_truthy
end
end
context 'with unresolved discussions' do
before do
merge_request.discussions.each(&:unresolve!)
end
it 'returns false' do
expect(merge_request.mergeable_discussions_state?).to be_falsey
end
end
end
context 'when project.only_allow_merge_if_all_discussions_are_resolved == false' do
let(:project) { create(:project, only_allow_merge_if_all_discussions_are_resolved: false) }
context 'with unresolved discussions' do
before do
merge_request.discussions.each(&:unresolve!)
end
it 'returns true' do
expect(merge_request.mergeable_discussions_state?).to be_truthy
end
end
end
end
describe "#environments" do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:merge_request) { create(:merge_request, source_project: project) } let(:merge_request) { create(:merge_request, source_project: project) }
......
...@@ -837,16 +837,19 @@ describe Project, models: true do ...@@ -837,16 +837,19 @@ describe Project, models: true do
context 'repository storage by default' do context 'repository storage by default' do
let(:project) { create(:empty_project) } let(:project) { create(:empty_project) }
subject { project.repository_storage }
before do before do
storages = { 'alternative_storage' => '/some/path' } storages = {
'default' => 'tmp/tests/repositories',
'picked' => 'tmp/tests/repositories',
}
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages) allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
stub_application_setting(repository_storage: 'alternative_storage')
allow_any_instance_of(Project).to receive(:ensure_dir_exist).and_return(true)
end end
it { is_expected.to eq('alternative_storage') } it 'picks storage from ApplicationSetting' do
expect_any_instance_of(ApplicationSetting).to receive(:pick_repository_storage).and_return('picked')
expect(project.repository_storage).to eq('picked')
end
end end
context 'shared runners by default' do context 'shared runners by default' do
......
...@@ -256,6 +256,20 @@ describe User, models: true do ...@@ -256,6 +256,20 @@ describe User, models: true do
expect(users_without_two_factor).not_to include(user_with_2fa.id) expect(users_without_two_factor).not_to include(user_with_2fa.id)
end end
end end
describe '.todo_authors' do
it 'filters users' do
create :user
user_2 = create :user
user_3 = create :user
current_user = create :user
create(:todo, user: current_user, author: user_2, state: :done)
create(:todo, user: current_user, author: user_3, state: :pending)
expect(User.todo_authors(current_user.id, 'pending')).to eq [user_3]
expect(User.todo_authors(current_user.id, 'done')).to eq [user_2]
end
end
end end
describe "Respond to" do describe "Respond to" do
......
...@@ -37,7 +37,7 @@ describe API::API, api: true do ...@@ -37,7 +37,7 @@ describe API::API, api: true do
end end
end end
context "when authenticated as admin" do context "when authenticated as admin" do
it "admin: returns an array of all groups" do it "admin: returns an array of all groups" do
get api("/groups", admin) get api("/groups", admin)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
...@@ -55,6 +55,17 @@ describe API::API, api: true do ...@@ -55,6 +55,17 @@ describe API::API, api: true do
expect(json_response.length).to eq(1) expect(json_response.length).to eq(1)
end end
end end
context "when using all_available in request" do
it "returns all groups you have access to" do
public_group = create :group, :public
get api("/groups", user1), all_available: true
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.first['name']).to eq(public_group.name)
end
end
end end
describe "GET /groups/:id" do describe "GET /groups/:id" do
......
...@@ -82,7 +82,20 @@ describe API::API, api: true do ...@@ -82,7 +82,20 @@ describe API::API, api: true do
expect(json_response['message']['title']).to eq(['is invalid']) expect(json_response['message']['title']).to eq(['is invalid'])
end end
it 'returns 409 if label already exists' do it 'returns 409 if label already exists in group' do
group = create(:group)
group_label = create(:group_label, group: group)
project.update(group: group)
post api("/projects/#{project.id}/labels", user),
name: group_label.name,
color: '#FFAABB'
expect(response).to have_http_status(409)
expect(json_response['message']).to eq('Label already exists')
end
it 'returns 409 if label already exists in project' do
post api("/projects/#{project.id}/labels", user), post api("/projects/#{project.id}/labels", user),
name: 'label1', name: 'label1',
color: '#FFAABB' color: '#FFAABB'
......
...@@ -14,14 +14,14 @@ describe API::API, 'MergeRequestDiffs', api: true do ...@@ -14,14 +14,14 @@ describe API::API, 'MergeRequestDiffs', api: true do
end end
describe 'GET /projects/:id/merge_requests/:merge_request_id/versions' do describe 'GET /projects/:id/merge_requests/:merge_request_id/versions' do
context 'valid merge request' do it 'returns 200 for a valid merge request' do
before { get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions", user) } get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions", user)
let(:merge_request_diff) { merge_request.merge_request_diffs.first } merge_request_diff = merge_request.merge_request_diffs.first
it { expect(response.status).to eq 200 } expect(response.status).to eq 200
it { expect(json_response.size).to eq(merge_request.merge_request_diffs.size) } expect(json_response.size).to eq(merge_request.merge_request_diffs.size)
it { expect(json_response.first['id']).to eq(merge_request_diff.id) } expect(json_response.first['id']).to eq(merge_request_diff.id)
it { expect(json_response.first['head_commit_sha']).to eq(merge_request_diff.head_commit_sha) } expect(json_response.first['head_commit_sha']).to eq(merge_request_diff.head_commit_sha)
end end
it 'returns a 404 when merge_request_id not found' do it 'returns a 404 when merge_request_id not found' do
...@@ -31,14 +31,14 @@ describe API::API, 'MergeRequestDiffs', api: true do ...@@ -31,14 +31,14 @@ describe API::API, 'MergeRequestDiffs', api: true do
end end
describe 'GET /projects/:id/merge_requests/:merge_request_id/versions/:version_id' do describe 'GET /projects/:id/merge_requests/:merge_request_id/versions/:version_id' do
context 'valid merge request' do it 'returns a 200 for a valid merge request' do
before { get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/#{merge_request_diff.id}", user) } merge_request_diff = merge_request.merge_request_diffs.first
let(:merge_request_diff) { merge_request.merge_request_diffs.first } get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/#{merge_request_diff.id}", user)
it { expect(response.status).to eq 200 } expect(response.status).to eq 200
it { expect(json_response['id']).to eq(merge_request_diff.id) } expect(json_response['id']).to eq(merge_request_diff.id)
it { expect(json_response['head_commit_sha']).to eq(merge_request_diff.head_commit_sha) } expect(json_response['head_commit_sha']).to eq(merge_request_diff.head_commit_sha)
it { expect(json_response['diffs'].size).to eq(merge_request_diff.diffs.size) } expect(json_response['diffs'].size).to eq(merge_request_diff.diffs.size)
end end
it 'returns a 404 when merge_request_id not found' do it 'returns a 404 when merge_request_id not found' do
......
...@@ -186,14 +186,14 @@ describe API::API, api: true do ...@@ -186,14 +186,14 @@ describe API::API, api: true do
end end
describe 'GET /projects/:id/merge_requests/:merge_request_id/commits' do describe 'GET /projects/:id/merge_requests/:merge_request_id/commits' do
context 'valid merge request' do it 'returns a 200 when merge request is valid' do
before { get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/commits", user) } get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/commits", user)
let(:commit) { merge_request.commits.first } commit = merge_request.commits.first
it { expect(response.status).to eq 200 } expect(response.status).to eq 200
it { expect(json_response.size).to eq(merge_request.commits.size) } expect(json_response.size).to eq(merge_request.commits.size)
it { expect(json_response.first['id']).to eq(commit.id) } expect(json_response.first['id']).to eq(commit.id)
it { expect(json_response.first['title']).to eq(commit.title) } expect(json_response.first['title']).to eq(commit.title)
end end
it 'returns a 404 when merge_request_id not found' do it 'returns a 404 when merge_request_id not found' do
......
...@@ -256,7 +256,8 @@ describe API::API, api: true do ...@@ -256,7 +256,8 @@ describe API::API, api: true do
merge_requests_enabled: false, merge_requests_enabled: false,
wiki_enabled: false, wiki_enabled: false,
only_allow_merge_if_build_succeeds: false, only_allow_merge_if_build_succeeds: false,
request_access_enabled: true request_access_enabled: true,
only_allow_merge_if_all_discussions_are_resolved: false
}) })
post api('/projects', user), project post api('/projects', user), project
...@@ -327,6 +328,22 @@ describe API::API, api: true do ...@@ -327,6 +328,22 @@ describe API::API, api: true do
expect(json_response['only_allow_merge_if_build_succeeds']).to be_truthy expect(json_response['only_allow_merge_if_build_succeeds']).to be_truthy
end end
it 'sets a project as allowing merge even if discussions are unresolved' do
project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: false })
post api('/projects', user), project
expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_falsey
end
it 'sets a project as allowing merge only if all discussions are resolved' do
project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: true })
post api('/projects', user), project
expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_truthy
end
context 'when a visibility level is restricted' do context 'when a visibility level is restricted' do
before do before do
@project = attributes_for(:project, { public: true }) @project = attributes_for(:project, { public: true })
...@@ -448,6 +465,22 @@ describe API::API, api: true do ...@@ -448,6 +465,22 @@ describe API::API, api: true do
post api("/projects/user/#{user.id}", admin), project post api("/projects/user/#{user.id}", admin), project
expect(json_response['only_allow_merge_if_build_succeeds']).to be_truthy expect(json_response['only_allow_merge_if_build_succeeds']).to be_truthy
end end
it 'sets a project as allowing merge even if discussions are unresolved' do
project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: false })
post api("/projects/user/#{user.id}", admin), project
expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_falsey
end
it 'sets a project as allowing merge only if all discussions are resolved' do
project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: true })
post api("/projects/user/#{user.id}", admin), project
expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_truthy
end
end end
describe "POST /projects/:id/uploads" do describe "POST /projects/:id/uploads" do
...@@ -509,6 +542,7 @@ describe API::API, api: true do ...@@ -509,6 +542,7 @@ describe API::API, api: true do
expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name) expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name)
expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access) expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access)
expect(json_response['only_allow_merge_if_build_succeeds']).to eq(project.only_allow_merge_if_build_succeeds) expect(json_response['only_allow_merge_if_build_succeeds']).to eq(project.only_allow_merge_if_build_succeeds)
expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved)
end end
it 'returns a project by path name' do it 'returns a project by path name' do
......
...@@ -33,6 +33,7 @@ describe API::API, 'Settings', api: true do ...@@ -33,6 +33,7 @@ describe API::API, 'Settings', api: true do
expect(json_response['default_projects_limit']).to eq(3) expect(json_response['default_projects_limit']).to eq(3)
expect(json_response['signin_enabled']).to be_falsey expect(json_response['signin_enabled']).to be_falsey
expect(json_response['repository_storage']).to eq('custom') expect(json_response['repository_storage']).to eq('custom')
expect(json_response['repository_storages']).to eq(['custom'])
expect(json_response['koding_enabled']).to be_truthy expect(json_response['koding_enabled']).to be_truthy
expect(json_response['koding_url']).to eq('http://koding.example.com') expect(json_response['koding_url']).to eq('http://koding.example.com')
end end
......
...@@ -220,26 +220,33 @@ describe Ci::API::API do ...@@ -220,26 +220,33 @@ describe Ci::API::API do
end end
context 'when request is valid' do context 'when request is valid' do
it { expect(response.status).to eq 202 } it 'gets correct response' do
expect(response.status).to eq 202
expect(response.header).to have_key 'Range'
expect(response.header).to have_key 'Build-Status'
end
it { expect(build.reload.trace).to eq 'BUILD TRACE appended' } it { expect(build.reload.trace).to eq 'BUILD TRACE appended' }
it { expect(response.header).to have_key 'Range' }
it { expect(response.header).to have_key 'Build-Status' }
end end
context 'when content-range start is too big' do context 'when content-range start is too big' do
let(:headers_with_range) { headers.merge({ 'Content-Range' => '15-20' }) } let(:headers_with_range) { headers.merge({ 'Content-Range' => '15-20' }) }
it { expect(response.status).to eq 416 } it 'gets correct response' do
it { expect(response.header).to have_key 'Range' } expect(response.status).to eq 416
it { expect(response.header['Range']).to eq '0-11' } expect(response.header).to have_key 'Range'
expect(response.header['Range']).to eq '0-11'
end
end end
context 'when content-range start is too small' do context 'when content-range start is too small' do
let(:headers_with_range) { headers.merge({ 'Content-Range' => '8-20' }) } let(:headers_with_range) { headers.merge({ 'Content-Range' => '8-20' }) }
it { expect(response.status).to eq 416 } it 'gets correct response' do
it { expect(response.header).to have_key 'Range' } expect(response.status).to eq 416
it { expect(response.header['Range']).to eq '0-11' } expect(response.header).to have_key 'Range'
expect(response.header['Range']).to eq '0-11'
end
end end
context 'when Content-Range header is missing' do context 'when Content-Range header is missing' do
......
...@@ -109,10 +109,12 @@ describe Ci::API::API do ...@@ -109,10 +109,12 @@ describe Ci::API::API do
end end
describe "DELETE /runners/delete" do describe "DELETE /runners/delete" do
let!(:runner) { FactoryGirl.create(:ci_runner) } it 'returns 200' do
before { delete ci_api("/runners/delete"), token: runner.token } runner = FactoryGirl.create(:ci_runner)
delete ci_api("/runners/delete"), token: runner.token
it { expect(response).to have_http_status 200 } expect(response).to have_http_status 200
it { expect(Ci::Runner.count).to eq(0) } expect(Ci::Runner.count).to eq(0)
end
end end
end end
require 'spec_helper'
describe BuildEntity do
let(:entity) do
described_class.new(build, request: double)
end
subject { entity.as_json }
context 'when build is a regular job' do
let(:build) { create(:ci_build) }
it 'contains url to build page and retry action' do
expect(subject).to include(:build_url, :retry_url)
expect(subject).not_to include(:play_url)
end
it 'does not contain sensitive information' do
expect(subject).not_to include(/token/)
expect(subject).not_to include(/variables/)
end
end
context 'when build is a manual action' do
let(:build) { create(:ci_build, :manual) }
it 'contains url to play action' do
expect(subject).to include(:play_url)
end
end
end
require 'spec_helper'
describe CommitEntity do
let(:entity) do
described_class.new(commit, request: request)
end
let(:request) { double('request') }
let(:project) { create(:project) }
let(:commit) { project.commit }
subject { entity.as_json }
before do
allow(request).to receive(:project).and_return(project)
end
context 'when commit author is a user' do
before do
create(:user, email: commit.author_email)
end
it 'contains information about user' do
expect(subject.fetch(:author)).not_to be_nil
end
end
context 'when commit author is not a user' do
it 'does not contain author details' do
expect(subject.fetch(:author)).to be_nil
end
end
it 'contains commit URL' do
expect(subject).to include(:commit_url)
end
it 'needs to receive project in the request' do
expect(request).to receive(:project)
.and_return(project)
subject
end
end
require 'spec_helper'
describe DeploymentEntity do
let(:entity) do
described_class.new(deployment, request: double)
end
let(:deployment) { create(:deployment) }
subject { entity.as_json }
it 'exposes internal deployment id' do
expect(subject).to include(:iid)
end
it 'exposes nested information about branch' do
expect(subject[:ref][:name]).to eq 'master'
expect(subject[:ref][:ref_url]).not_to be_empty
end
end
require 'spec_helper'
describe EntityRequest do
subject do
described_class.new(user: 'user', project: 'some project')
end
describe 'methods created' do
it 'defines accessible attributes' do
expect(subject.user).to eq 'user'
expect(subject.project).to eq 'some project'
end
it 'raises error when attribute is not defined' do
expect { subject.some_method }.to raise_error NoMethodError
end
end
end
require 'spec_helper'
describe EnvironmentEntity do
let(:entity) do
described_class.new(environment, request: double)
end
let(:environment) { create(:environment) }
subject { entity.as_json }
it 'exposes latest deployment' do
expect(subject).to include(:last_deployment)
end
it 'exposes core elements of environment' do
expect(subject).to include(:id, :name, :state, :environment_url)
end
end
require 'spec_helper'
describe EnvironmentSerializer do
let(:serializer) do
described_class
.new(user: user, project: project)
.represent(resource)
end
let(:json) { serializer.as_json }
let(:user) { create(:user) }
let(:project) { create(:project) }
context 'when there is a single object provided' do
before do
create(:ci_build, :manual, name: 'manual1',
pipeline: deployable.pipeline)
end
let(:deployment) do
create(:deployment, deployable: deployable,
user: user,
project: project,
sha: project.commit.id)
end
let(:deployable) { create(:ci_build) }
let(:resource) { deployment.environment }
it 'it generates payload for single object' do
expect(json).to be_an_instance_of Hash
end
it 'contains important elements of environment' do
expect(json)
.to include(:name, :external_url, :environment_url, :last_deployment)
end
it 'contains relevant information about last deployment' do
last_deployment = json.fetch(:last_deployment)
expect(last_deployment)
.to include(:ref, :user, :commit, :deployable, :manual_actions)
end
end
context 'when there is a collection of objects provided' do
let(:project) { create(:empty_project) }
let(:resource) { create_list(:environment, 2) }
it 'contains important elements of environment' do
expect(json.first)
.to include(:last_deployment, :name, :external_url)
end
it 'generates payload for collection' do
expect(json).to be_an_instance_of Array
end
end
end
require 'spec_helper'
describe UserEntity do
let(:entity) { described_class.new(user) }
let(:user) { create(:user) }
subject { entity.as_json }
it 'exposes user name and login' do
expect(subject).to include(:username, :name)
end
it 'does not expose passwords' do
expect(subject).not_to include(/password/)
end
it 'does not expose tokens' do
expect(subject).not_to include(/token/)
end
it 'does not expose 2FA OTPs' do
expect(subject).not_to include(/otp/)
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