Commit 179ed967 authored by Regis's avatar Regis

Merge branch 'master' into auto-pipelines-vue

parents d8c73e3e a3cc2f1e
...@@ -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,
......
Please view this file on the master branch, on stable branches it's out of date. **Note:** This file is automatically generated. Please see the [developer
documentation](doc/development/changelog.md) for instructions on adding your own
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)
...@@ -14,6 +18,7 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -14,6 +18,7 @@ Please view this file on the master branch, on stable branches it's out of date.
- 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)
...@@ -23,6 +28,7 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -23,6 +28,7 @@ Please view this file on the master branch, on stable branches it's out of date.
- 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)
...@@ -42,6 +48,8 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -42,6 +48,8 @@ Please view this file on the master branch, on stable branches it's out of date.
- 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
...@@ -54,16 +62,21 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -54,16 +62,21 @@ Please view this file on the master branch, on stable branches it's out of date.
- Initialize Sidekiq with the list of queues used by GitLab - Initialize Sidekiq with the list of queues used by GitLab
- Refactor email, use setter method instead AR callbacks for email attribute (Semyon Pupkov) - Refactor email, use setter method instead AR callbacks for email attribute (Semyon Pupkov)
- Shortened merge request modal to let clipboard button not overlap - Shortened merge request modal to let clipboard button not overlap
- Adds JavaScript validation for group path editing field
- In all filterable drop downs, put input field in focus only after load is complete (Ido @leibo) - In all filterable drop downs, put input field in focus only after load is complete (Ido @leibo)
- 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 ## 8.13.3 (2016-11-02)
- Removes any symlinks before importing a project export file. CVE-2016-9086
- Fixed Import/Export foreign key issue to do with project members.
- Fix relative links in Markdown wiki when displayed in "Project" tab !7218 - 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 - Reduce the overhead to calculate number of open/closed issues and merge requests within the group or project
- Fix project features default values - 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)
...@@ -253,6 +266,11 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -253,6 +266,11 @@ Please view this file on the master branch, on stable branches it's out of date.
- Fix broken Project API docs (Takuya Noguchi) - Fix broken Project API docs (Takuya Noguchi)
- Migrate invalid project members (owner -> master) - Migrate invalid project members (owner -> master)
## 8.12.8 (2016-11-02)
- Removes any symlinks before importing a project export file. CVE-2016-9086
- Fixed Import/Export foreign key issue to do with project members.
## 8.12.7 ## 8.12.7
- Prevent running `GfmAutocomplete` setup for each diff note. !6569 - Prevent running `GfmAutocomplete` setup for each diff note. !6569
...@@ -512,6 +530,10 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -512,6 +530,10 @@ Please view this file on the master branch, on stable branches it's out of date.
- Fix non-master branch readme display in tree view - Fix non-master branch readme display in tree view
- Add UX improvements for merge request version diffs - Add UX improvements for merge request version diffs
## 8.11.10 (2016-11-02)
- Removes any symlinks before importing a project export file. CVE-2016-9086
## 8.11.9 ## 8.11.9
- Don't send Private-Token (API authentication) headers to Sentry - Don't send Private-Token (API authentication) headers to Sentry
...@@ -752,6 +774,10 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -752,6 +774,10 @@ Please view this file on the master branch, on stable branches it's out of date.
- Update gitlab_git gem to 10.4.7 - Update gitlab_git gem to 10.4.7
- Simplify SQL queries of marking a todo as done - Simplify SQL queries of marking a todo as done
## 8.10.13 (2016-11-02)
- Removes any symlinks before importing a project export file. CVE-2016-9086
## 8.10.12 ## 8.10.12
- Don't send Private-Token (API authentication) headers to Sentry - Don't send Private-Token (API authentication) headers to Sentry
......
...@@ -104,7 +104,7 @@ gem 'deckar01-task_list', '1.0.5', require: 'task_list/railtie' ...@@ -104,7 +104,7 @@ gem 'deckar01-task_list', '1.0.5', 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
......
...@@ -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)
...@@ -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();
......
...@@ -299,7 +299,7 @@ ...@@ -299,7 +299,7 @@
}; };
Dispatcher.prototype.initFieldErrors = function() { Dispatcher.prototype.initFieldErrors = function() {
$('.show-gl-field-errors').each((i, form) => { $('.gl-show-field-errors').each((i, form) => {
new gl.GlFieldErrors(form); new gl.GlFieldErrors(form);
}); });
}; };
......
...@@ -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) {
......
/* eslint-disable no-param-reassign */
((global) => {
/*
* This class overrides the browser's validation error bubbles, displaying custom
* error messages for invalid fields instead. To begin validating any form, add the
* class `gl-show-field-errors` to the form element, and ensure error messages are
* declared in each inputs' `title` attribute. If no title is declared for an invalid
* field the user attempts to submit, "This field is required." will be shown by default.
*
* Opt not to validate certain fields by adding the class `gl-field-error-ignore` to the input.
*
* Set a custom error anchor for error message to be injected after with the
* class `gl-field-error-anchor`
*
* Examples:
*
* Basic:
*
* <form class='gl-show-field-errors'>
* <input type='text' name='username' title='Username is required.'/>
* </form>
*
* Ignore specific inputs (e.g. UsernameValidator):
*
* <form class='gl-show-field-errors'>
* <div class="form-group>
* <input type='text' class='gl-field-errors-ignore' pattern='[a-zA-Z0-9-_]+'/>
* </div>
* <div class="form-group">
* <input type='text' name='username' title='Username is required.'/>
* </div>
* </form>
*
* Custom Error Anchor (allows error message to be injected after specified element):
*
* <form class='gl-show-field-errors'>
* <div class="form-group gl-field-error-anchor">
* <input type='text' name='username' title='Username is required.'/>
* // Error message typically injected here
* </div>
* // Error message now injected here
* </form>
*
* */
/*
* Regex Patterns in use:
*
* Only alphanumeric: : "[a-zA-Z0-9]+"
* No special characters : "[a-zA-Z0-9-_]+",
*
* */
const errorMessageClass = 'gl-field-error';
const inputErrorClass = 'gl-field-error-outline';
const errorAnchorSelector = '.gl-field-error-anchor';
const ignoreInputSelector = '.gl-field-error-ignore';
class GlFieldError {
constructor({ input, formErrors }) {
this.inputElement = $(input);
this.inputDomElement = this.inputElement.get(0);
this.form = formErrors;
this.errorMessage = this.inputElement.attr('title') || 'This field is required.';
this.fieldErrorElement = $(`<p class='${errorMessageClass} hide'>${this.errorMessage}</p>`);
this.state = {
valid: false,
empty: true,
};
this.initFieldValidation();
}
initFieldValidation() {
const customErrorAnchor = this.inputElement.parents(errorAnchorSelector);
const errorAnchor = customErrorAnchor.length ? customErrorAnchor : this.inputElement;
// hidden when injected into DOM
errorAnchor.after(this.fieldErrorElement);
this.inputElement.off('invalid').on('invalid', this.handleInvalidSubmit.bind(this));
this.scopedSiblings = this.safelySelectSiblings();
}
safelySelectSiblings() {
// Apply `ignoreSelector` in markup to siblings whose visibility should not be toggled
const unignoredSiblings = this.inputElement.siblings(`p:not(${ignoreInputSelector})`);
const parentContainer = this.inputElement.parent('.form-group');
// Only select siblings when they're scoped within a form-group with one input
const safelyScoped = parentContainer.length && parentContainer.find('input').length === 1;
return safelyScoped ? unignoredSiblings : this.fieldErrorElement;
}
renderValidity() {
this.renderClear();
if (this.state.valid) {
this.renderValid();
} else if (this.state.empty) {
this.renderEmpty();
} else if (!this.state.valid) {
this.renderInvalid();
}
}
handleInvalidSubmit(event) {
event.preventDefault();
const currentValue = this.accessCurrentValue();
this.state.valid = false;
this.state.empty = currentValue === '';
this.renderValidity();
this.form.focusOnFirstInvalid.apply(this.form);
// For UX, wait til after first invalid submission to check each keyup
this.inputElement.off('keyup.fieldValidator')
.on('keyup.fieldValidator', this.updateValidity.bind(this));
}
/* Get or set current input value */
accessCurrentValue(newVal) {
return newVal ? this.inputElement.val(newVal) : this.inputElement.val();
}
getInputValidity() {
return this.inputDomElement.validity.valid;
}
updateValidity() {
const inputVal = this.accessCurrentValue();
this.state.empty = !inputVal.length;
this.state.valid = this.getInputValidity();
this.renderValidity();
}
renderValid() {
return this.renderClear();
}
renderEmpty() {
return this.renderInvalid();
}
renderInvalid() {
this.inputElement.addClass(inputErrorClass);
this.scopedSiblings.hide();
return this.fieldErrorElement.show();
}
renderClear() {
const inputVal = this.accessCurrentValue();
if (!inputVal.split(' ').length) {
const trimmedInput = inputVal.trim();
this.accessCurrentValue(trimmedInput);
}
this.inputElement.removeClass(inputErrorClass);
this.scopedSiblings.hide();
this.fieldErrorElement.hide();
}
}
global.GlFieldError = GlFieldError;
})(window.gl || (window.gl = {}));
/* eslint-disable */ /* eslint-disable */
((global) => {
/*
* This class overrides the browser's validation error bubbles, displaying custom
* error messages for invalid fields instead. To begin validating any form, add the
* class `show-gl-field-errors` to the form element, and ensure error messages are
* declared in each inputs' title attribute.
*
* Example:
*
* <form class='show-gl-field-errors'>
* <input type='text' name='username' title='Username is required.'/>
*</form>
*
* */
const errorMessageClass = 'gl-field-error';
const inputErrorClass = 'gl-field-error-outline';
class GlFieldError {
constructor({ input, formErrors }) {
this.inputElement = $(input);
this.inputDomElement = this.inputElement.get(0);
this.form = formErrors;
this.errorMessage = this.inputElement.attr('title') || 'This field is required.';
this.fieldErrorElement = $(`<p class='${errorMessageClass} hide'>${ this.errorMessage }</p>`);
this.state = {
valid: false,
empty: true
};
this.initFieldValidation();
}
initFieldValidation() {
// hidden when injected into DOM
this.inputElement.after(this.fieldErrorElement);
this.inputElement.off('invalid').on('invalid', this.handleInvalidSubmit.bind(this));
this.scopedSiblings = this.safelySelectSiblings();
}
safelySelectSiblings() {
// Apply `ignoreSelector` in markup to siblings whose visibility should not be toggled with input validity
const ignoreSelector = '.validation-ignore';
const unignoredSiblings = this.inputElement.siblings(`p:not(${ignoreSelector})`);
const parentContainer = this.inputElement.parent('.form-group');
// Only select siblings when they're scoped within a form-group with one input
const safelyScoped = parentContainer.length && parentContainer.find('input').length === 1;
return safelyScoped ? unignoredSiblings : this.fieldErrorElement;
}
renderValidity() {
this.renderClear();
if (this.state.valid) {
return this.renderValid();
}
if (this.state.empty) {
return this.renderEmpty();
}
if (!this.state.valid) {
return this.renderInvalid();
}
}
handleInvalidSubmit(event) {
event.preventDefault();
const currentValue = this.accessCurrentValue();
this.state.valid = false;
this.state.empty = currentValue === '';
this.renderValidity();
this.form.focusOnFirstInvalid.apply(this.form);
// For UX, wait til after first invalid submission to check each keyup
this.inputElement.off('keyup.field_validator')
.on('keyup.field_validator', this.updateValidity.bind(this));
}
/* Get or set current input value */
accessCurrentValue(newVal) {
return newVal ? this.inputElement.val(newVal) : this.inputElement.val();
}
getInputValidity() {
return this.inputDomElement.validity.valid;
}
updateValidity() {
const inputVal = this.accessCurrentValue();
this.state.empty = !inputVal.length;
this.state.valid = this.getInputValidity();
this.renderValidity();
}
renderValid() {
return this.renderClear();
}
renderEmpty() { //= require gl_field_error
return this.renderInvalid();
}
renderInvalid() {
this.inputElement.addClass(inputErrorClass);
this.scopedSiblings.hide();
return this.fieldErrorElement.show();
}
renderClear() { ((global) => {
const inputVal = this.accessCurrentValue(); const customValidationFlag = 'gl-field-error-ignore';
if (!inputVal.split(' ').length) {
const trimmedInput = inputVal.trim();
this.accessCurrentValue(trimmedInput);
}
this.inputElement.removeClass(inputErrorClass);
this.scopedSiblings.hide();
this.fieldErrorElement.hide();
}
}
const customValidationFlag = 'no-gl-field-errors';
class GlFieldErrors { class GlFieldErrors {
constructor(form) { constructor(form) {
...@@ -144,7 +22,7 @@ ...@@ -144,7 +22,7 @@
this.state.inputs = this.form.find(validateSelectors).toArray() this.state.inputs = this.form.find(validateSelectors).toArray()
.filter((input) => !input.classList.contains(customValidationFlag)) .filter((input) => !input.classList.contains(customValidationFlag))
.map((input) => new GlFieldError({ input, formErrors: this })); .map((input) => new global.GlFieldError({ input, formErrors: this }));
this.form.on('submit', this.catchInvalidFormSubmit); this.form.on('submit', this.catchInvalidFormSubmit);
} }
......
...@@ -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'
}); });
......
...@@ -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;
......
...@@ -136,3 +136,35 @@ label { ...@@ -136,3 +136,35 @@ label {
color: $red-normal; color: $red-normal;
} }
.gl-show-field-errors {
.gl-field-success-outline {
border: 1px solid $green-normal;
&:focus {
box-shadow: 0 0 0 1px $green-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 $green-normal;
border: 0 none;
}
}
.gl-field-error-outline {
border: 1px solid $red-normal;
&:focus {
box-shadow: 0 0 0 1px $red-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 rgba(210, 40, 82, 0.6);
border: 0 none;
}
}
.gl-field-success-message {
color: $green-normal;
}
.gl-field-error-message {
color: $red-normal;
}
.gl-field-hint {
color: $gl-text-color;
}
}
...@@ -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;
}
}
...@@ -75,43 +75,17 @@ ...@@ -75,43 +75,17 @@
.login-body { .login-body {
font-size: 13px; font-size: 13px;
input + p { input + p {
margin-top: 5px; margin-top: 5px;
} }
.gl-field-success-outline { .username .validation-success {
border: 1px solid $green-normal;
&:focus {
box-shadow: 0 0 0 1px $green-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 $green-normal;
border: 0 none;
}
}
.gl-field-error-outline {
border: 1px solid $red-normal;
&:focus {
box-shadow: 0 0 0 1px $red-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 rgba(210, 40, 82, 0.6);
border: 0 none;
}
}
.username .validation-success,
.gl-field-success-message {
color: $green-normal; color: $green-normal;
} }
.username .validation-error, .username .validation-error {
.gl-field-error-message {
color: $red-normal; color: $red-normal;
} }
.gl-field-hint {
color: $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: []
......
...@@ -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
......
...@@ -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,8 +47,10 @@ module CiStatusHelper ...@@ -47,8 +47,10 @@ 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)
......
...@@ -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
...@@ -441,11 +441,11 @@ class MergeRequest < ActiveRecord::Base ...@@ -441,11 +441,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?
......
...@@ -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
......
= render 'devise/shared/tab_single', tab_title: 'Sign in preview' = render 'devise/shared/tab_single', tab_title: 'Sign in preview'
.login-box .login-box
%form.show-gl-field-errors %form.gl-show-field-errors
.form-group .form-group
= label_tag :login = label_tag :login
= text_field_tag :login, nil, class: "form-control top", title: 'Please provide your username or email address.' = text_field_tag :login, nil, class: "form-control top", title: 'Please provide your username or email address.'
......
...@@ -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
......
= render 'devise/shared/tab_single', tab_title: 'Resend confirmation instructions' = render 'devise/shared/tab_single', tab_title: 'Resend confirmation instructions'
.login-box .login-box
.login-body .login-body
= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f| = form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: 'gl-show-field-errors' }) do |f|
.devise-errors .devise-errors
= devise_error_messages! = devise_error_messages!
.form-group .form-group
......
= render 'devise/shared/tab_single', tab_title:'Change your password' = render 'devise/shared/tab_single', tab_title:'Change your password'
.login-box .login-box
.login-body .login-body
= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: 'show-gl-field-errors' }) do |f| = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: 'gl-show-field-errors' }) do |f|
.devise-errors .devise-errors
= devise_error_messages! = devise_error_messages!
= f.hidden_field :reset_password_token = f.hidden_field :reset_password_token
......
= render 'devise/shared/tab_single', tab_title: 'Reset Password' = render 'devise/shared/tab_single', tab_title: 'Reset Password'
.login-box .login-box
.login-body .login-body
= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f| = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: 'gl-show-field-errors' }) do |f|
.devise-errors .devise-errors
= devise_error_messages! = devise_error_messages!
.form-group .form-group
......
= form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: 'new_user show-gl-field-errors', 'aria-live' => 'assertive'}) do |f| = form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: 'new_user gl-show-field-errors', 'aria-live' => 'assertive'}) do |f|
%div.form-group %div.form-group
= f.label "Username or email", for: :login = f.label "Username or email", for: :login
= f.text_field :login, class: "form-control top", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off", required: true, title: "This field is required." = f.text_field :login, class: "form-control top", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off", required: true, title: "This field is required."
......
= form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user', class: 'show-gl-field-errors') do = form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user', class: 'gl-show-field-errors') do
.form-group .form-group
= label_tag :username, 'Username or email' = label_tag :username, 'Username or email'
= text_field_tag :username, nil, {class: "form-control top", title: "This field is required", autofocus: "autofocus", required: true } = text_field_tag :username, nil, {class: "form-control top", title: "This field is required", autofocus: "autofocus", required: true }
......
= form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user', class: "show-gl-field-errors") do = form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user', class: "gl-show-field-errors") do
.form-group .form-group
= label_tag :username, "#{server['label']} Username" = label_tag :username, "#{server['label']} Username"
= text_field_tag :username, nil, {class: "form-control top", title: "This field is required.", autofocus: "autofocus", required: true } = text_field_tag :username, nil, {class: "form-control top", title: "This field is required.", autofocus: "autofocus", required: true }
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
.login-box .login-box
.login-body .login-body
- if @user.two_factor_otp_enabled? - if @user.two_factor_otp_enabled?
= form_for(resource, as: resource_name, url: session_path(resource_name), method: :post, html: { class: 'edit_user show-gl-field-errors' }) do |f| = form_for(resource, as: resource_name, url: session_path(resource_name), method: :post, html: { class: 'edit_user gl-show-field-errors' }) do |f|
- resource_params = params[resource_name].presence || params - resource_params = params[resource_name].presence || params
= f.hidden_field :remember_me, value: resource_params.fetch(:remember_me, 0) = f.hidden_field :remember_me, value: resource_params.fetch(:remember_me, 0)
%div %div
......
#register-pane.login-box{ role: 'tabpanel', class: 'tab-pane' } #register-pane.login-box{ role: 'tabpanel', class: 'tab-pane' }
.login-body .login-body
= form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user show-gl-field-errors", "aria-live" => "assertive" }) do |f| = form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user gl-show-field-errors", "aria-live" => "assertive" }) do |f|
.devise-errors .devise-errors
= devise_error_messages! = devise_error_messages!
%div.form-group %div.form-group
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
= f.text_field :name, class: "form-control top", required: true, title: "This field is required." = f.text_field :name, class: "form-control top", required: true, title: "This field is required."
%div.username.form-group %div.username.form-group
= f.label :username = f.label :username
= f.text_field :username, class: "form-control middle no-gl-field-error", pattern: "[a-zA-Z0-9]+", required: true, title: 'Please create a username with only alphanumeric characters.' = f.text_field :username, class: "form-control middle", pattern: "[a-zA-Z0-9]+", required: true, title: 'Please create a username with only alphanumeric characters.'
%p.validation-error.hide Username is already taken. %p.validation-error.hide Username is already taken.
%p.validation-success.hide Username is available. %p.validation-success.hide Username is available.
%p.validation-pending.hide Checking username availability... %p.validation-pending.hide Checking username availability...
......
= render 'devise/shared/tab_single', tab_title: 'Resend unlock instructions' = render 'devise/shared/tab_single', tab_title: 'Resend unlock instructions'
.login-box .login-box
.login-body .login-body
= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f| = form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post, class: 'gl-show-field-errors' }) do |f|
.devise-errors .devise-errors
= devise_error_messages! = devise_error_messages!
.form-group.append-bottom-20 .form-group.append-bottom-20
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
.panel-heading .panel-heading
Group settings Group settings
.panel-body .panel-body
= form_for @group, html: { multipart: true, class: "form-horizontal" }, authenticity_token: true do |f| = form_for @group, html: { multipart: true, class: "form-horizontal gl-show-field-errors" }, authenticity_token: true do |f|
= form_errors(@group) = form_errors(@group)
= render 'shared/group_form', f: f = render 'shared/group_form', f: f
......
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
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
New Group New Group
%hr %hr
= form_for @group, html: { class: 'group-form form-horizontal' } do |f| = form_for @group, html: { class: 'group-form form-horizontal gl-show-field-errors' } do |f|
= form_errors(@group) = form_errors(@group)
= render 'shared/group_form', f: f, autofocus: true = render 'shared/group_form', f: f, autofocus: true
......
...@@ -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]
......
...@@ -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
......
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
......
...@@ -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
......
...@@ -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)}");
}); });
...@@ -9,11 +9,13 @@ ...@@ -9,11 +9,13 @@
= f.label :path, class: 'control-label' do = f.label :path, class: 'control-label' do
Group path Group path
.col-sm-10 .col-sm-10
.input-group .input-group.gl-field-error-anchor
.input-group-addon .input-group-addon
= root_url = root_url
= f.text_field :path, placeholder: 'open-source', class: 'form-control', = f.text_field :path, placeholder: 'open-source', class: 'form-control',
autofocus: local_assigns[:autofocus] || false autofocus: local_assigns[:autofocus] || false, pattern: "[a-zA-Z0-9-_]+",
required: true, title: 'Please choose a group name with no special characters.'
- if @group.persisted? - if @group.persisted?
.alert.alert-warning.prepend-top-10 .alert.alert-warning.prepend-top-10
%ul %ul
......
<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)
......
...@@ -12,6 +12,7 @@ Options = Struct.new( ...@@ -12,6 +12,7 @@ Options = Struct.new(
:amend, :amend,
:author, :author,
:dry_run, :dry_run,
:force,
:merge_request, :merge_request,
:title :title
) )
...@@ -21,7 +22,7 @@ class ChangelogOptionParser ...@@ -21,7 +22,7 @@ class ChangelogOptionParser
options = Options.new options = Options.new
parser = OptionParser.new do |opts| parser = OptionParser.new do |opts|
opts.banner = "Usage: #{__FILE__} [options]" opts.banner = "Usage: #{__FILE__} [options] [title]\n\n"
# Note: We do not provide a shorthand for this in order to match the `git # Note: We do not provide a shorthand for this in order to match the `git
# commit` interface # commit` interface
...@@ -29,6 +30,10 @@ class ChangelogOptionParser ...@@ -29,6 +30,10 @@ class ChangelogOptionParser
options.amend = value options.amend = value
end end
opts.on('-f', '--force', 'Overwrite an existing entry') do |value|
options.force = value
end
opts.on('-m', '--merge-request [integer]', Integer, 'Merge Request ID') do |value| opts.on('-m', '--merge-request [integer]', Integer, 'Merge Request ID') do |value|
options.merge_request = value options.merge_request = value
end end
...@@ -111,8 +116,9 @@ class ChangelogEntry ...@@ -111,8 +116,9 @@ class ChangelogEntry
def assert_new_file! def assert_new_file!
return unless File.exist?(file_path) return unless File.exist?(file_path)
return if options.force
fail_with "#{file_path} already exists!" fail_with "#{file_path} already exists! Use `--force` to overwrite."
end end
def assert_title! def assert_title!
......
---
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:
# 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"
......
...@@ -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.
......
...@@ -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. |
......
...@@ -28,9 +28,12 @@ Example response: ...@@ -28,9 +28,12 @@ Example response:
```json ```json
[ [
{ {
"id" : 1, "id":1,
"url" : "https://gitlab.example.com/hook", "url":"https://gitlab.example.com/hook",
"created_at" : "2015-11-04T20:07:35.874Z" "created_at":"2016-10-31T12:32:15.192Z",
"push_events":true,
"tag_push_events":false,
"enable_ssl_verification":true
} }
] ]
``` ```
...@@ -48,6 +51,10 @@ POST /hooks ...@@ -48,6 +51,10 @@ POST /hooks
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `url` | string | yes | The hook URL | | `url` | string | yes | The hook URL |
| `token` | string | no | Secret token to validate received payloads; this will not be returned in the response |
| `push_events` | boolean | no | When true, the hook will fire on push events |
| `tag_push_events` | boolean | no | When true, the hook will fire on new tags being pushed |
| `enable_ssl_verification` | boolean | no | Do SSL verification when triggering the hook |
Example request: Example request:
...@@ -60,9 +67,12 @@ Example response: ...@@ -60,9 +67,12 @@ Example response:
```json ```json
[ [
{ {
"id" : 2, "id":1,
"url" : "https://gitlab.example.com/hook", "url":"https://gitlab.example.com/hook",
"created_at" : "2015-11-04T20:07:35.874Z" "created_at":"2016-10-31T12:32:15.192Z",
"push_events":true,
"tag_push_events":false,
"enable_ssl_verification":true
} }
] ]
``` ```
......
...@@ -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:
......
...@@ -21,6 +21,9 @@ The `merge_request` value is a reference to a merge request that adds this ...@@ -21,6 +21,9 @@ The `merge_request` value is a reference to a merge request that adds this
entry, and the `author` key is used to give attribution to community entry, and the `author` key is used to give attribution to community
contributors. Both are optional. contributors. Both are optional.
Community contributors and core team members are encouraged to add their name to
the `author` field. GitLab team members should not.
If you're working on the GitLab EE repository, the entry will be added to If you're working on the GitLab EE repository, the entry will be added to
`changelogs/unreleased-ee/` instead. `changelogs/unreleased-ee/` instead.
...@@ -46,13 +49,14 @@ author: ...@@ -46,13 +49,14 @@ author:
The entry filename is based on the name of the current Git branch. If you run The entry filename is based on the name of the current Git branch. If you run
the command above on a branch called `feature/hey-dz`, it will generate a the command above on a branch called `feature/hey-dz`, it will generate a
`changelogs/unreleased/feature-hey-dz` file. `changelogs/unreleased/feature-hey-dz.yml` file.
### Arguments ### Arguments
| Argument | Shorthand | Purpose | | Argument | Shorthand | Purpose |
| ----------------- | --------- | --------------------------------------------- | | ----------------- | --------- | --------------------------------------------- |
| `--amend` | | Amend the previous commit | | `--amend` | | Amend the previous commit |
| `--force` | `-f` | Overwrite an existing entry |
| `--merge-request` | `-m` | Merge Request ID | | `--merge-request` | `-m` | Merge Request ID |
| `--dry-run` | `-n` | Don't actually write anything, just print | | `--dry-run` | `-n` | Don't actually write anything, just print |
| `--git-username` | `-u` | Use Git user.name configuration as the author | | `--git-username` | `-u` | Use Git user.name configuration as the author |
...@@ -79,6 +83,23 @@ merge_request: ...@@ -79,6 +83,23 @@ merge_request:
author: author:
``` ```
#### `--force` or `-f`
Use **`--force`** or **`-f`** to overwrite an existing changelog entry if it
already exists.
```text
$ bin/changelog 'Hey DZ, I added a feature to GitLab!'
error changelogs/unreleased/feature-hey-dz.yml already exists! Use `--force` to overwrite.
$ bin/changelog 'Hey DZ, I added a feature to GitLab!' --force
create changelogs/unreleased/feature-hey-dz.yml
---
title: Hey DZ, I added a feature to GitLab!
merge_request: 1983
author:
```
#### `--merge-request` or `-m` #### `--merge-request` or `-m`
Use the **`--merge-request`** or **`-m`** argument to provide the Use the **`--merge-request`** or **`-m`** argument to provide the
......
...@@ -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)
``` ```
......
...@@ -271,9 +271,9 @@ sudo usermod -aG redis git ...@@ -271,9 +271,9 @@ sudo usermod -aG redis git
### Clone the Source ### Clone the Source
# Clone GitLab repository # Clone GitLab repository
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-13-stable gitlab sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-14-stable gitlab
**Note:** You can change `8-13-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! **Note:** You can change `8-14-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It ### Configure It
...@@ -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)
......
# From 8.13 to 8.14
Make sure you view this update guide from the tag (version) of GitLab you would
like to install. In most cases this should be the highest numbered production
tag (without rc in it). You can select the tag in the version dropdown at the
top left corner of GitLab (below the menu bar).
If the highest number stable branch is unclear please check the
[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
guide links by version.
### 1. Stop server
sudo service gitlab stop
### 2. Backup
```bash
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```
### 3. Update Ruby
We will continue supporting Ruby < 2.3 for the time being but we recommend you
upgrade to Ruby 2.3 if you're running a source installation, as this is the same
version that ships with our Omnibus package.
You can check which version you are running with `ruby -v`.
Download and compile Ruby:
```bash
mkdir /tmp/ruby && cd /tmp/ruby
curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.1.tar.gz
echo 'c39b4001f7acb4e334cb60a0f4df72d434bef711 ruby-2.3.1.tar.gz' | shasum --check - && tar xzf ruby-2.3.1.tar.gz
cd ruby-2.3.1
./configure --disable-install-rdoc
make
sudo make install
```
Install Bundler:
```bash
sudo gem install bundler --no-ri --no-rdoc
```
### 4. Get latest code
```bash
sudo -u git -H git fetch --all
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
```
For GitLab Community Edition:
```bash
sudo -u git -H git checkout 8-14-stable
```
OR
For GitLab Enterprise Edition:
```bash
sudo -u git -H git checkout 8-14-stable-ee
```
### 5. Update gitlab-shell
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch --all --tags
sudo -u git -H git checkout v4.0.0
```
### 6. Update gitlab-workhorse
Install and compile gitlab-workhorse. This requires
[Go 1.5](https://golang.org/dl) which should already be on your system from
GitLab 8.1.
```bash
cd /home/git/gitlab-workhorse
sudo -u git -H git fetch --all
sudo -u git -H git checkout v0.8.5
sudo -u git -H make
```
### 7. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
# MySQL installations (note: the line below states '--without postgres')
sudo -u git -H bundle install --without postgres development test --deployment
# PostgreSQL installations (note: the line below states '--without mysql')
sudo -u git -H bundle install --without mysql development test --deployment
# Optional: clean up old gems
sudo -u git -H bundle clean
# Run database migrations
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
# Clean up assets and cache
sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
```
### 8. Update configuration files
#### New configuration options for `gitlab.yml`
There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
```sh
git diff origin/8-13-stable:config/gitlab.yml.example origin/8-14-stable:config/gitlab.yml.example
```
#### Git configuration
Configure Git to generate packfile bitmaps (introduced in Git 2.0) on
the GitLab server during `git gc`.
```sh
sudo -u git -H git config --global repack.writeBitmaps true
```
#### Nginx configuration
Ensure you're still up-to-date with the latest NGINX configuration changes:
```sh
# For HTTPS configurations
git diff origin/8-13-stable:lib/support/nginx/gitlab-ssl origin/8-14-stable:lib/support/nginx/gitlab-ssl
# For HTTP configurations
git diff origin/8-13-stable:lib/support/nginx/gitlab origin/8-14-stable:lib/support/nginx/gitlab
```
If you are using Apache instead of NGINX please see the updated [Apache templates].
Also note that because Apache does not support upstreams behind Unix sockets you
will need to let gitlab-workhorse listen on a TCP port. You can do this
via [/etc/default/gitlab].
[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-14-stable/lib/support/init.d/gitlab.default.example#L38
#### SMTP configuration
If you're installing from source and use SMTP to deliver mail, you will need to add the following line
to config/initializers/smtp_settings.rb:
```ruby
ActionMailer::Base.delivery_method = :smtp
```
See [smtp_settings.rb.sample] as an example.
[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-14-stable/config/initializers/smtp_settings.rb.sample#L13
#### Init script
Ensure you're still up-to-date with the latest init script changes:
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload
### 9. Start application
sudo service gitlab start
sudo service nginx restart
### 10. Check application status
Check if GitLab and its environment are configured correctly:
sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
To make sure you didn't miss anything run a more thorough check:
sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
If all items are green, then congratulations, the upgrade is complete!
## Things went south? Revert to previous version (8.13)
### 1. Revert the code to the previous version
Follow the [upgrade guide from 8.12 to 8.13](8.12-to-8.13.md), except for the
database migration (the backup is already migrated to the previous version).
### 2. Restore from the backup
```bash
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
```
If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above.
...@@ -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
......
...@@ -43,14 +43,13 @@ module API ...@@ -43,14 +43,13 @@ module API
end end
class Hook < Grape::Entity class Hook < Grape::Entity
expose :id, :url, :created_at expose :id, :url, :created_at, :push_events, :tag_push_events
expose :enable_ssl_verification
end end
class ProjectHook < Hook class ProjectHook < Hook
expose :project_id, :push_events expose :project_id, :issues_events, :merge_requests_events
expose :issues_events, :merge_requests_events, :tag_push_events
expose :note_events, :build_events, :pipeline_events, :wiki_page_events expose :note_events, :build_events, :pipeline_events, :wiki_page_events
expose :enable_ssl_verification
end end
class BasicProjectDetails < Grape::Entity class BasicProjectDetails < Grape::Entity
...@@ -510,6 +509,7 @@ module API ...@@ -510,6 +509,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
......
...@@ -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)
......
...@@ -12,6 +12,7 @@ module API ...@@ -12,6 +12,7 @@ module API
end end
get do get do
hooks = SystemHook.all hooks = SystemHook.all
present hooks, with: Entities::Hook present hooks, with: Entities::Hook
end end
...@@ -19,10 +20,14 @@ module API ...@@ -19,10 +20,14 @@ module API
success Entities::Hook success Entities::Hook
end end
params do params do
requires :url, type: String, desc: 'The URL for the system hook' requires :url, type: String, desc: "The URL to send the request to"
optional :token, type: String, desc: 'The token used to validate payloads'
optional :push_events, type: Boolean, desc: "Trigger hook on push events"
optional :tag_push_events, type: Boolean, desc: "Trigger hook on tag push events"
optional :enable_ssl_verification, type: Boolean, desc: "Do SSL verification when triggering the hook"
end end
post do post do
hook = SystemHook.new declared(params).to_h hook = SystemHook.new declared(params, include_missing: false).to_h
if hook.save if hook.save
present hook, with: Entities::Hook present hook, with: Entities::Hook
......
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
...@@ -3,10 +3,25 @@ module Gitlab ...@@ -3,10 +3,25 @@ module Gitlab
class AttributeCleaner class AttributeCleaner
ALLOWED_REFERENCES = RelationFactory::PROJECT_REFERENCES + RelationFactory::USER_REFERENCES + ['group_id'] ALLOWED_REFERENCES = RelationFactory::PROJECT_REFERENCES + RelationFactory::USER_REFERENCES + ['group_id']
def self.clean!(relation_hash:) def self.clean(*args)
relation_hash.reject! do |key, _value| new(*args).clean
key.end_with?('_id') && !ALLOWED_REFERENCES.include?(key) end
def initialize(relation_hash:, relation_class:)
@relation_hash = relation_hash
@relation_class = relation_class
end
def clean
@relation_hash.reject do |key, _value|
prohibited_key?(key) || !@relation_class.attribute_method?(key)
end.except('id')
end end
private
def prohibited_key?(key)
key.end_with?('_id') && !ALLOWED_REFERENCES.include?(key)
end end
end end
end end
......
...@@ -43,6 +43,14 @@ module Gitlab ...@@ -43,6 +43,14 @@ module Gitlab
raise Projects::ImportService::Error.new("Unable to decompress #{@archive_file} into #{@shared.export_path}") unless result raise Projects::ImportService::Error.new("Unable to decompress #{@archive_file} into #{@shared.export_path}") unless result
remove_symlinks!
end
def remove_symlinks!
Dir["#{@shared.export_path}/**/*"].each do |path|
FileUtils.rm(path) if File.lstat(path).symlink?
end
true true
end end
end end
......
...@@ -55,7 +55,12 @@ module Gitlab ...@@ -55,7 +55,12 @@ module Gitlab
end end
def member_hash(member) def member_hash(member)
member.except('id').merge(source_id: @project.id, importing: true) parsed_hash(member).merge('source_id' => @project.id, 'importing' => true)
end
def parsed_hash(member)
Gitlab::ImportExport::AttributeCleaner.clean(relation_hash: member.deep_stringify_keys,
relation_class: ProjectMember)
end end
def find_project_user_query(member) def find_project_user_query(member)
......
...@@ -9,8 +9,14 @@ module Gitlab ...@@ -9,8 +9,14 @@ module Gitlab
end end
def restore def restore
begin
json = IO.read(@path) json = IO.read(@path)
@tree_hash = ActiveSupport::JSON.decode(json) @tree_hash = ActiveSupport::JSON.decode(json)
rescue => e
Rails.logger.error("Import/Export error: #{e.message}")
raise Gitlab::ImportExport::Error.new('Incorrect JSON format')
end
@project_members = @tree_hash.delete('project_members') @project_members = @tree_hash.delete('project_members')
ActiveRecord::Base.no_touching do ActiveRecord::Base.no_touching do
......
...@@ -14,7 +14,7 @@ module Gitlab ...@@ -14,7 +14,7 @@ module Gitlab
priorities: :label_priorities, priorities: :label_priorities,
label: :project_label }.freeze label: :project_label }.freeze
USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id].freeze USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id created_by_id].freeze
PROJECT_REFERENCES = %w[project_id source_project_id gl_project_id target_project_id].freeze PROJECT_REFERENCES = %w[project_id source_project_id gl_project_id target_project_id].freeze
...@@ -30,7 +30,7 @@ module Gitlab ...@@ -30,7 +30,7 @@ module Gitlab
def initialize(relation_sym:, relation_hash:, members_mapper:, user:, project_id:) def initialize(relation_sym:, relation_hash:, members_mapper:, user:, project_id:)
@relation_name = OVERRIDES[relation_sym] || relation_sym @relation_name = OVERRIDES[relation_sym] || relation_sym
@relation_hash = relation_hash.except('id', 'noteable_id').merge('project_id' => project_id) @relation_hash = relation_hash.except('noteable_id').merge('project_id' => project_id)
@members_mapper = members_mapper @members_mapper = members_mapper
@user = user @user = user
@imported_object_retries = 0 @imported_object_retries = 0
...@@ -172,11 +172,8 @@ module Gitlab ...@@ -172,11 +172,8 @@ module Gitlab
end end
def parsed_relation_hash def parsed_relation_hash
@parsed_relation_hash ||= begin @parsed_relation_hash ||= Gitlab::ImportExport::AttributeCleaner.clean(relation_hash: @relation_hash,
Gitlab::ImportExport::AttributeCleaner.clean!(relation_hash: @relation_hash) relation_class: relation_class)
@relation_hash.reject { |k, _v| !relation_class.attribute_method?(k) }
end
end end
def set_st_diffs def set_st_diffs
......
...@@ -24,12 +24,19 @@ module Gitlab ...@@ -24,12 +24,19 @@ module Gitlab
end end
def verify_version!(version) def verify_version!(version)
if Gem::Version.new(version) != Gem::Version.new(Gitlab::ImportExport.version) if different_version?(version)
raise Gitlab::ImportExport::Error.new("Import version mismatch: Required #{Gitlab::ImportExport.version} but was #{version}") raise Gitlab::ImportExport::Error.new("Import version mismatch: Required #{Gitlab::ImportExport.version} but was #{version}")
else else
true true
end end
end end
def different_version?(version)
Gem::Version.new(version) != Gem::Version.new(Gitlab::ImportExport.version)
rescue => e
Rails.logger.error("Import/Export error: #{e.message}")
raise Gitlab::ImportExport::Error.new('Incorrect VERSION format')
end
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,42 +10,38 @@ describe 'bin/changelog' do ...@@ -10,42 +10,38 @@ describe 'bin/changelog' do
expect(options.amend).to eq true expect(options.amend).to eq true
end end
it 'parses --merge-request' do it 'parses --force and -f' do
options = described_class.parse(%w[foo --merge-request 1234 bar]) %w[--force -f].each do |flag|
options = described_class.parse(%W[foo #{flag} bar])
expect(options.merge_request).to eq 1234 expect(options.force).to eq true
end end
it 'parses -m' do
options = described_class.parse(%w[foo -m 4321 bar])
expect(options.merge_request).to eq 4321
end end
it 'parses --dry-run' do it 'parses --merge-request and -m' do
options = described_class.parse(%w[foo --dry-run bar]) %w[--merge-request -m].each do |flag|
options = described_class.parse(%W[foo #{flag} 1234 bar])
expect(options.dry_run).to eq true expect(options.merge_request).to eq 1234
end
end end
it 'parses -n' do it 'parses --dry-run and -n' do
options = described_class.parse(%w[foo -n bar]) %w[--dry-run -n].each do |flag|
options = described_class.parse(%W[foo #{flag} bar])
expect(options.dry_run).to eq true expect(options.dry_run).to eq true
end end
end
it 'parses --git-username' do it 'parses --git-username and -u' 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])
%w[--git-username -u].each do |flag|
options = described_class.parse(%W[foo #{flag} bar])
expect(options.author).to eq 'Jane Doe' expect(options.author).to eq 'Jane Doe'
end end
it 'parses -u' do
allow(described_class).to receive(:git_user_name).and_return('John Smith')
options = described_class.parse(%w[foo -u bar])
expect(options.author).to eq 'John Smith'
end end
it 'parses -h' do it 'parses -h' 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
...@@ -64,9 +64,11 @@ describe 'Commits' do ...@@ -64,9 +64,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
...@@ -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
...@@ -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
%form.show-gl-field-errors{action: 'submit', method: 'post'} %form.gl-show-field-errors{action: 'submit', method: 'post'}
.form-group .form-group
%input.required-text{required: true, type: 'text'} Text %input.required-text{required: true, type: 'text'} Text
.form-group .form-group
...@@ -10,6 +10,6 @@ ...@@ -10,6 +10,6 @@
.form-group .form-group
%input.hidden{ type:'hidden' } %input.hidden{ type:'hidden' }
.form-group .form-group
%input.custom.no-gl-field-errors{ type:'text' } Custom, do not validate %input.custom.gl-field-error-ignore{ type:'text' } Custom, do not validate
.form-group .form-group
%input.submit{type: 'submit'} Submit %input.submit{type: 'submit'} Submit
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
describe('GL Style Field Errors', function() { describe('GL Style Field Errors', function() {
beforeEach(function() { beforeEach(function() {
fixture.load('gl_field_errors.html'); fixture.load('gl_field_errors.html');
const $form = this.$form = $('form.show-gl-field-errors'); const $form = this.$form = $('form.gl-show-field-errors');
this.fieldErrors = new global.GlFieldErrors($form); this.fieldErrors = new global.GlFieldErrors($form);
}); });
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
}); });
it('should ignore elements with custom error handling', function() { it('should ignore elements with custom error handling', function() {
const customErrorFlag = 'no-gl-field-errors'; const customErrorFlag = 'gl-field-error-ignore';
const customErrorElem = $(`.${customErrorFlag}`); const customErrorElem = $(`.${customErrorFlag}`);
expect(customErrorElem.length).toBe(1); expect(customErrorElem.length).toBe(1);
......
/* 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() {
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::ImportExport::AttributeCleaner, lib: true do describe Gitlab::ImportExport::AttributeCleaner, lib: true do
let(:relation_class){ double('relation_class').as_null_object }
let(:unsafe_hash) do let(:unsafe_hash) do
{ {
'id' => 101,
'service_id' => 99, 'service_id' => 99,
'moved_to_id' => 99, 'moved_to_id' => 99,
'namespace_id' => 99, 'namespace_id' => 99,
...@@ -27,8 +29,9 @@ describe Gitlab::ImportExport::AttributeCleaner, lib: true do ...@@ -27,8 +29,9 @@ describe Gitlab::ImportExport::AttributeCleaner, lib: true do
end end
it 'removes unwanted attributes from the hash' do it 'removes unwanted attributes from the hash' do
described_class.clean!(relation_hash: unsafe_hash) # allow(relation_class).to receive(:attribute_method?).and_return(true)
parsed_hash = described_class.clean(relation_hash: unsafe_hash, relation_class: relation_class)
expect(unsafe_hash).to eq(post_safe_hash) expect(parsed_hash).to eq(post_safe_hash)
end end
end end
require 'spec_helper'
describe Gitlab::ImportExport::FileImporter, lib: true do
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') }
let(:export_path) { "#{Dir::tmpdir}/file_importer_spec" }
let(:valid_file) { "#{shared.export_path}/valid.json" }
let(:symlink_file) { "#{shared.export_path}/invalid.json" }
let(:subfolder_symlink_file) { "#{shared.export_path}/subfolder/invalid.json" }
before do
stub_const('Gitlab::ImportExport::FileImporter::MAX_RETRIES', 0)
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
allow_any_instance_of(Gitlab::ImportExport::CommandLineUtil).to receive(:untar_zxf).and_return(true)
setup_files
described_class.import(archive_file: '', shared: shared)
end
after do
FileUtils.rm_rf(export_path)
end
it 'removes symlinks in root folder' do
expect(File.exist?(symlink_file)).to be false
end
it 'removes symlinks in subfolders' do
expect(File.exist?(subfolder_symlink_file)).to be false
end
it 'does not remove a valid file' do
expect(File.exist?(valid_file)).to be true
end
def setup_files
FileUtils.mkdir_p("#{shared.export_path}/subfolder/")
FileUtils.touch(valid_file)
FileUtils.ln_s(valid_file, symlink_file)
FileUtils.ln_s(valid_file, subfolder_symlink_file)
end
end
require 'spec_helper' require 'spec_helper'
include ImportExport::CommonUtil
describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
describe 'restore project tree' do describe 'restore project tree' do
...@@ -175,6 +176,19 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do ...@@ -175,6 +176,19 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
expect(MergeRequest.find_by_title('MR2').source_project_id).to eq(-1) expect(MergeRequest.find_by_title('MR2').source_project_id).to eq(-1)
end end
end end
context 'project.json file access check' do
it 'does not read a symlink' do
Dir.mktmpdir do |tmpdir|
setup_symlink(tmpdir, 'project.json')
allow(shared).to receive(:export_path).and_call_original
restored_project_json
expect(shared.errors.first).not_to include('test')
end
end
end
end end
end end
end end
require 'spec_helper' require 'spec_helper'
include ImportExport::CommonUtil
describe Gitlab::ImportExport::VersionChecker, services: true do describe Gitlab::ImportExport::VersionChecker, services: true do
describe 'bundle a project Git repo' do
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: '') } let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: '') }
describe 'bundle a project Git repo' do
let(:version) { Gitlab::ImportExport.version } let(:version) { Gitlab::ImportExport.version }
before do before do
...@@ -27,4 +29,16 @@ describe Gitlab::ImportExport::VersionChecker, services: true do ...@@ -27,4 +29,16 @@ describe Gitlab::ImportExport::VersionChecker, services: true do
end end
end end
end end
describe 'version file access check' do
it 'does not read a symlink' do
Dir.mktmpdir do |tmpdir|
setup_symlink(tmpdir, 'VERSION')
described_class.check!(shared: shared)
expect(shared.errors.first).not_to include('test')
end
end
end
end end
...@@ -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
......
require 'spec_helper' require 'spec_helper'
include Gitlab::Routing.url_helpers
describe JiraService, models: true do describe JiraService, models: true do
include Gitlab::Routing.url_helpers
describe "Associations" do describe "Associations" do
it { is_expected.to belong_to :project } it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook } it { is_expected.to have_one :service_hook }
...@@ -79,7 +80,9 @@ describe JiraService, models: true do ...@@ -79,7 +80,9 @@ describe JiraService, models: true do
stub_config_setting(relative_url_root: '/gitlab') stub_config_setting(relative_url_root: '/gitlab')
stub_config_setting(url: Settings.send(:build_gitlab_url)) stub_config_setting(url: Settings.send(:build_gitlab_url))
Project.default_url_options[:script_name] = "/gitlab" allow(JiraService).to receive(:default_url_options) do
{ script_name: '/gitlab' }
end
@jira_service.execute(merge_request, ExternalIssue.new("JIRA-123", project)) @jira_service.execute(merge_request, ExternalIssue.new("JIRA-123", 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
......
...@@ -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
......
...@@ -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
......
...@@ -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
......
...@@ -13,6 +13,7 @@ describe API::API, api: true do ...@@ -13,6 +13,7 @@ describe API::API, api: true do
context "when no user" do context "when no user" do
it "returns authentication error" do it "returns authentication error" do
get api("/hooks") get api("/hooks")
expect(response).to have_http_status(401) expect(response).to have_http_status(401)
end end
end end
...@@ -20,6 +21,7 @@ describe API::API, api: true do ...@@ -20,6 +21,7 @@ describe API::API, api: true do
context "when not an admin" do context "when not an admin" do
it "returns forbidden error" do it "returns forbidden error" do
get api("/hooks", user) get api("/hooks", user)
expect(response).to have_http_status(403) expect(response).to have_http_status(403)
end end
end end
...@@ -27,9 +29,12 @@ describe API::API, api: true do ...@@ -27,9 +29,12 @@ describe API::API, api: true do
context "when authenticated as admin" do context "when authenticated as admin" do
it "returns an array of hooks" do it "returns an array of hooks" do
get api("/hooks", admin) get api("/hooks", admin)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.first['url']).to eq(hook.url) expect(json_response.first['url']).to eq(hook.url)
expect(json_response.first['push_events']).to be true
expect(json_response.first['tag_push_events']).to be false
end end
end end
end end
...@@ -43,6 +48,7 @@ describe API::API, api: true do ...@@ -43,6 +48,7 @@ describe API::API, api: true do
it "responds with 400 if url not given" do it "responds with 400 if url not given" do
post api("/hooks", admin) post api("/hooks", admin)
expect(response).to have_http_status(400) expect(response).to have_http_status(400)
end end
...@@ -51,6 +57,14 @@ describe API::API, api: true do ...@@ -51,6 +57,14 @@ describe API::API, api: true do
post api("/hooks", admin) post api("/hooks", admin)
end.not_to change { SystemHook.count } end.not_to change { SystemHook.count }
end end
it 'sets default values for events' do
post api('/hooks', admin), url: 'http://mep.mep', enable_ssl_verification: true
expect(response).to have_http_status(201)
expect(json_response['enable_ssl_verification']).to be true
expect(json_response['tag_push_events']).to be false
end
end end
describe "GET /hooks/:id" do describe "GET /hooks/:id" do
......
...@@ -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
module ImportExport
module CommonUtil
def setup_symlink(tmpdir, symlink_name)
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(tmpdir)
File.open("#{tmpdir}/test", 'w') { |file| file.write("test") }
FileUtils.ln_s("#{tmpdir}/test", "#{tmpdir}/#{symlink_name}")
end
end
end
...@@ -39,3 +39,6 @@ captures/ ...@@ -39,3 +39,6 @@ captures/
# Keystore files # Keystore files
*.jks *.jks
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
...@@ -7,6 +7,11 @@ ...@@ -7,6 +7,11 @@
*.obj *.obj
*.elf *.elf
# Linker output
*.ilk
*.map
*.exp
# Precompiled Headers # Precompiled Headers
*.gch *.gch
*.pch *.pch
...@@ -34,3 +39,13 @@ ...@@ -34,3 +39,13 @@
# Debug files # Debug files
*.dSYM/ *.dSYM/
*.su *.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
modules.order
Module.symvers
Mkfile.old
dkms.conf
.architect .architect
bootstrap.css
bootstrap.js
bootstrap.json bootstrap.json
bootstrap.jsonp
build/ build/
classic.json
classic.jsonp
ext/ ext/
modern.json
modern.jsonp
resources/sass/.sass-cache/
...@@ -4,9 +4,6 @@ ...@@ -4,9 +4,6 @@
# User-specific stuff: # User-specific stuff:
.idea/workspace.xml .idea/workspace.xml
.idea/tasks.xml .idea/tasks.xml
.idea/dictionaries
.idea/vcs.xml
.idea/jsLibraryMappings.xml
# Sensitive or high-churn files: # Sensitive or high-churn files:
.idea/dataSources.ids .idea/dataSources.ids
......
Creative Commons Legal Code
CC0 1.0 Universal CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator exclusive Copyright and Related Rights (defined below) upon the creator and
and subsequent owner(s) (each and all, an "owner") of an original work of subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work"). authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for Certain owners wish to permanently relinquish those rights to a Work for the
the purpose of contributing to a commons of creative, cultural and purpose of contributing to a commons of creative, cultural and scientific
scientific works ("Commons") that the public can reliably and without fear works ("Commons") that the public can reliably and without fear of later
of later claims of infringement build upon, modify, incorporate in other claims of infringement build upon, modify, incorporate in other works, reuse
works, reuse and redistribute as freely as possible in any form whatsoever and redistribute as freely as possible in any form whatsoever and for any
and for any purposes, including without limitation commercial purposes. purposes, including without limitation commercial purposes. These owners may
These owners may contribute to the Commons to promote the ideal of a free contribute to the Commons to promote the ideal of a free culture and the
culture and the further production of creative, cultural and scientific further production of creative, cultural and scientific works, or to gain
works, or to gain reputation or greater distribution for their Work in reputation or greater distribution for their Work in part through the use and
part through the use and efforts of others. efforts of others.
For these and/or other purposes and motivations, and without any For these and/or other purposes and motivations, and without any expectation
expectation of additional consideration or compensation, the person of additional consideration or compensation, the person associating CC0 with a
associating CC0 with a Work (the "Affirmer"), to the extent that he or she Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
is an owner of Copyright and Related Rights in the Work, voluntarily and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
elects to apply CC0 to the Work and publicly distribute the Work under its and publicly distribute the Work under its terms, with knowledge of his or her
terms, with knowledge of his or her Copyright and Related Rights in the Copyright and Related Rights in the Work and the meaning and intended legal
Work and the meaning and intended legal effect of CC0 on those rights. effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be 1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not Related Rights"). Copyright and Related Rights include, but are not limited
limited to, the following: to, the following:
i. the right to reproduce, adapt, distribute, perform, display, communicate,
and translate a Work;
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s); ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work; iii. publicity and privacy rights pertaining to a person's image or likeness
depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work, iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below; subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work; v. rights protecting the extraction, dissemination, use and reuse of data in
a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation protection of databases, and under any national implementation thereof,
thereof, including any amended or successor version of such including any amended or successor version of such directive); and
directive); and
vii. other similar, equivalent or corresponding rights throughout the vii. other similar, equivalent or corresponding rights throughout the world
world based on applicable law or treaty, and any national based on applicable law or treaty, and any national implementations thereof.
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention of,
2. Waiver. To the greatest extent permitted by, but not in contravention applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
of, applicable law, Affirmer hereby overtly, fully, permanently, unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
irrevocably and unconditionally waives, abandons, and surrenders all of and Related Rights and associated claims and causes of action, whether now
Affirmer's Copyright and Related Rights and associated claims and causes known or unknown (including existing as well as future claims and causes of
of action, whether now known or unknown (including existing as well as action), in the Work (i) in all territories worldwide, (ii) for the maximum
future claims and causes of action), in the Work (i) in all territories duration provided by applicable law or treaty (including future time
worldwide, (ii) for the maximum duration provided by applicable law or extensions), (iii) in any current or future medium and for any number of
treaty (including future time extensions), (iii) in any current or future copies, and (iv) for any purpose whatsoever, including without limitation
medium and for any number of copies, and (iv) for any purpose whatsoever, commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
including without limitation commercial, advertising or promotional the Waiver for the benefit of each member of the public at large and to the
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each detriment of Affirmer's heirs and successors, fully intending that such Waiver
member of the public at large and to the detriment of Affirmer's heirs and shall not be subject to revocation, rescission, cancellation, termination, or
successors, fully intending that such Waiver shall not be subject to any other legal or equitable action to disrupt the quiet enjoyment of the Work
revocation, rescission, cancellation, termination, or any other legal or by the public as contemplated by Affirmer's express Statement of Purpose.
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose. 3. Public License Fallback. Should any part of the Waiver for any reason be
judged legally invalid or ineffective under applicable law, then the Waiver
3. Public License Fallback. Should any part of the Waiver for any reason shall be preserved to the maximum extent permitted taking into account
be judged legally invalid or ineffective under applicable law, then the Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
Waiver shall be preserved to the maximum extent permitted taking into is so judged Affirmer hereby grants to each affected person a royalty-free,
account Affirmer's express Statement of Purpose. In addition, to the non transferable, non sublicensable, non exclusive, irrevocable and
extent the Waiver is so judged Affirmer hereby grants to each affected unconditional license to exercise Affirmer's Copyright and Related Rights in
person a royalty-free, non transferable, non sublicensable, non exclusive, the Work (i) in all territories worldwide, (ii) for the maximum duration
irrevocable and unconditional license to exercise Affirmer's Copyright and provided by applicable law or treaty (including future time extensions), (iii)
Related Rights in the Work (i) in all territories worldwide, (ii) for the in any current or future medium and for any number of copies, and (iv) for any
maximum duration provided by applicable law or treaty (including future purpose whatsoever, including without limitation commercial, advertising or
time extensions), (iii) in any current or future medium and for any number promotional purposes (the "License"). The License shall be deemed effective as
of copies, and (iv) for any purpose whatsoever, including without of the date CC0 was applied by Affirmer to the Work. Should any part of the
limitation commercial, advertising or promotional purposes (the License for any reason be judged legally invalid or ineffective under
"License"). The License shall be deemed effective as of the date CC0 was applicable law, such partial invalidity or ineffectiveness shall not
applied by Affirmer to the Work. Should any part of the License for any invalidate the remainder of the License, and in such case Affirmer hereby
reason be judged legally invalid or ineffective under applicable law, such affirms that he or she will not (i) exercise any of his or her remaining
partial invalidity or ineffectiveness shall not invalidate the remainder Copyright and Related Rights in the Work or (ii) assert any associated claims
of the License, and in such case Affirmer hereby affirms that he or she and causes of action with respect to the Work, in either case contrary to
will not (i) exercise any of his or her remaining Copyright and Related Affirmer's express Statement of Purpose.
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers. 4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned, a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document. surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied, b. Affirmer offers the Work as-is and makes no representations or warranties
statutory or otherwise, including without limitation warranties of of any kind concerning the Work, express, implied, statutory or otherwise,
title, merchantability, fitness for a particular purpose, non including without limitation warranties of title, merchantability, fitness
infringement, or the absence of latent or other defects, accuracy, or for a particular purpose, non infringement, or the absence of latent or
the present or absence of errors, whether or not discoverable, all to other defects, accuracy, or the present or absence of errors, whether or not
the greatest extent permissible under applicable law. discoverable, all to the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without that may apply to the Work or any use thereof, including without limitation
limitation any person's Copyright and Related Rights in the Work. any person's Copyright and Related Rights in the Work. Further, Affirmer
Further, Affirmer disclaims responsibility for obtaining any necessary disclaims responsibility for obtaining any necessary consents, permissions
consents, permissions or other rights required for any use of the or other rights required for any use of the Work.
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to party to this document and has no duty or obligation with respect to this
this CC0 or use of the Work. CC0 or use of the Work.
For more information, please see
<http://creativecommons.org/publicdomain/zero/1.0/>
...@@ -7,6 +7,7 @@ app/storage/ ...@@ -7,6 +7,7 @@ app/storage/
# Laravel 5 & Lumen specific # Laravel 5 & Lumen specific
bootstrap/cache/ bootstrap/cache/
public/storage
.env.*.php .env.*.php
.env.php .env.php
.env .env
......
# For projects using nanoc (http://nanoc.ws/) # For projects using Nanoc (http://nanoc.ws/)
# Default location for output, needs to match output_dir's value found in config.yaml # Default location for output (needs to match output_dir's value found in nanoc.yaml)
output/ output/
# Temporary file directory # Temporary file directory
......
...@@ -11,3 +11,10 @@ system/cache/ ...@@ -11,3 +11,10 @@ system/cache/
system/logs/ system/logs/
system/storage/ system/storage/
# vQmod log files
vqmod/logs/*
# vQmod cache files
vqmod/vqcache/*
vqmod/checked.cache
vqmod/mods.cache
...@@ -66,7 +66,7 @@ docs/_build/ ...@@ -66,7 +66,7 @@ docs/_build/
# PyBuilder # PyBuilder
target/ target/
# IPython Notebook # Jupyter Notebook
.ipynb_checkpoints .ipynb_checkpoints
# pyenv # pyenv
......
...@@ -5,3 +5,6 @@ ...@@ -5,3 +5,6 @@
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
Cargo.lock Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
...@@ -61,6 +61,15 @@ acs-*.bib ...@@ -61,6 +61,15 @@ acs-*.bib
# fixme # fixme
*.lox *.lox
# feynmf/feynmp
*.mf
*.mp
*.t[1-9]
*.t[1-9][0-9]
*.tfm
*.[1-9]
*.[1-9][0-9]
#(r)(e)ledmac/(r)(e)ledpar #(r)(e)ledmac/(r)(e)ledpar
*.end *.end
*.?end *.?end
......
# Visual Studio 2015 user specific files # Visual Studio 2015 user specific files
.vs/ .vs/
# Visual Studio 2015 database file
*.VC.db
# Compiled Object files # Compiled Object files
*.slo *.slo
*.lo *.lo
......
## Ignore Visual Studio temporary files, build results, and ## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons. ## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files # User-specific files
*.suo *.suo
*.user *.user
*.userosscache *.userosscache
*.sln.docstates *.sln.docstates
*.vcxproj.filters
# User-specific files (MonoDevelop/Xamarin Studio) # User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs *.userprefs
...@@ -44,6 +47,7 @@ dlldata.c ...@@ -44,6 +47,7 @@ dlldata.c
project.lock.json project.lock.json
project.fragment.lock.json project.fragment.lock.json
artifacts/ artifacts/
Properties/launchSettings.json
*_i.c *_i.c
*_p.c *_p.c
...@@ -238,6 +242,9 @@ FakesAssemblies/ ...@@ -238,6 +242,9 @@ FakesAssemblies/
# Visual Studio 6 workspace options file # Visual Studio 6 workspace options file
*.opt *.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output # Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts **/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts
......
# Based on openjdk:8, already includes lein
image: clojure:lein-2.7.0
# If you need to configure a database, add a `services` section here
# See https://docs.gitlab.com/ce/ci/services/postgres.html
# Make sure you configure the connection as well
before_script:
# If you need to install any external applications, like a
# postgres client, you may want to uncomment the line below:
#
#- apt-get update -y
#
# Retrieve project dependencies
# Do this on before_script since it'll be shared between both test and
# any production sections a user adds
- lein deps
test:
script:
# If you need to run any migrations or configure the database, this
# would be the point to do it.
- lein test
# This file is a template, and might need editing before it works on your project.
# Official language image. Look for the different tagged releases at:
# https://hub.docker.com/r/crystallang/crystal/
image: "crystallang/crystal:latest"
# Pick zero or more services to be used on all builds.
# Only needed when using a docker container to run your tests in.
# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service
# services:
# - mysql:latest
# - redis:latest
# - postgres:latest
# variables:
# POSTGRES_DB: database_name
# Cache shards in between builds
cache:
paths:
- libs
# This is a basic example for a shard or script which doesn't use
# services such as redis or postgres
before_script:
- apt-get update -qq && apt-get install -y -qq libxml2-dev
- crystal -v # Print out Crystal version for debugging
- shards
# If you are using built-in Crystal Spec.
spec:
script:
- crystal spec
# If you are using minitest.cr
minitest:
script:
- crystal test/spec_test.cr # change to the file(s) you execute for tests
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