summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md30
-rw-r--r--app/assets/javascripts/boards/components/modal/list.js2
-rw-r--r--app/assets/javascripts/breakpoints.js77
-rw-r--r--app/assets/javascripts/build.js7
-rw-r--r--app/assets/javascripts/fly_out_nav.js3
-rw-r--r--app/assets/javascripts/issuable_context.js3
-rw-r--r--app/assets/javascripts/main.js3
-rw-r--r--app/assets/javascripts/merge_request_tabs.js9
-rw-r--r--app/assets/javascripts/monitoring/components/monitoring_column.vue5
-rw-r--r--app/assets/javascripts/new_sidebar.js3
-rw-r--r--app/assets/javascripts/wikis.js11
-rw-r--r--app/assets/stylesheets/new_sidebar.scss1
-rw-r--r--app/services/ci/register_job_service.rb6
-rw-r--r--app/services/projects/update_pages_service.rb4
-rw-r--r--app/views/dashboard/projects/_blank_state_admin_welcome.html.haml2
-rw-r--r--app/views/import/fogbugz/new_user_map.html.haml2
-rw-r--r--app/views/projects/edit.html.haml4
-rwxr-xr-xbin/changelog134
-rw-r--r--changelogs/unreleased/21949-add-type-to-changelog.yml4
-rw-r--r--changelogs/unreleased/fix-import-symbolink-links.yml4
-rw-r--r--changelogs/unreleased/rs-alphanumeric-ssh-params.yml5
-rw-r--r--doc/README.md8
-rw-r--r--doc/administration/high_availability/nfs.md4
-rw-r--r--doc/development/changelog.md54
-rw-r--r--doc/install/kubernetes/gitlab_chart.md6
-rw-r--r--doc/install/kubernetes/gitlab_omnibus.md171
-rw-r--r--doc/install/kubernetes/gitlab_runner_chart.md4
-rw-r--r--doc/install/kubernetes/index.md13
-rw-r--r--doc/user/index.md88
-rw-r--r--lib/gitlab/git/repository.rb33
-rw-r--r--lib/gitlab/import_export/file_importer.rb6
-rw-r--r--lib/gitlab/url_blocker.rb8
-rw-r--r--lib/support/nginx/gitlab35
-rw-r--r--lib/support/nginx/gitlab-ssl39
-rw-r--r--locale/fr/gitlab.po2
-rw-r--r--spec/bin/changelog_spec.rb98
-rw-r--r--spec/controllers/autocomplete_controller_spec.rb133
-rw-r--r--spec/factories/projects.rb99
-rw-r--r--spec/factories/users.rb6
-rw-r--r--spec/javascripts/breakpoints_spec.js15
-rw-r--r--spec/javascripts/fly_out_nav_spec.js2
-rw-r--r--spec/lib/gitlab/import_export/file_importer_spec.rb5
-rw-r--r--spec/lib/gitlab/template/issue_template_spec.rb44
-rw-r--r--spec/lib/gitlab/template/merge_request_template_spec.rb44
-rw-r--r--spec/lib/gitlab/url_blocker_spec.rb34
-rw-r--r--spec/requests/api/projects_spec.rb4
46 files changed, 810 insertions, 464 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7493f2562e8..6a9c751937e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,11 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 9.4.4 (2017-08-09)
+
+- Remove hidden symlinks from project import files.
+- Disallow Git URLs that include a username or hostname beginning with a non-alphanumeric character.
+
## 9.4.3 (2017-07-31)
- Fix Prometheus client PID reuse bug. !13130
@@ -226,6 +231,11 @@ entry.
- Log rescued exceptions to Sentry.
- Remove remaining N+1 queries in merge requests API with emojis and labels.
+## 9.3.10 (2017-08-09)
+
+- Remove hidden symlinks from project import files.
+- Disallow Git URLs that include a username or hostname beginning with a non-alphanumeric character.
+
## 9.3.9 (2017-07-20)
- Fix an infinite loop when handling user-supplied regular expressions.
@@ -498,6 +508,11 @@ entry.
- Remove foreigh key on ci_trigger_schedules only if it exists.
- Allow translation of Pipeline Schedules.
+## 9.2.10 (2017-08-09)
+
+- Remove hidden symlinks from project import files.
+- Disallow Git URLs that include a username or hostname beginning with a non-alphanumeric character.
+
## 9.2.9 (2017-07-20)
- Fix an infinite loop when handling user-supplied regular expressions.
@@ -753,6 +768,11 @@ entry.
- Fix preemptive scroll bar on user activity calendar.
- Pipeline chat notifications convert seconds to minutes and hours.
+## 9.1.10 (2017-08-09)
+
+- Remove hidden symlinks from project import files.
+- Disallow Git URLs that include a username or hostname beginning with a non-alphanumeric character.
+
## 9.1.9 (2017-07-20)
- Fix an infinite loop when handling user-supplied regular expressions.
@@ -1076,6 +1096,11 @@ entry.
- Only send chat notifications for the default branch.
- Don't fill in the default kubernetes namespace.
+## 9.0.13 (2017-08-09)
+
+- Remove hidden symlinks from project import files.
+- Disallow Git URLs that include a username or hostname beginning with a non-alphanumeric character.
+
## 9.0.12 (2017-07-20)
- Fix an infinite loop when handling user-supplied regular expressions.
@@ -1456,6 +1481,11 @@ entry.
- Change development tanuki favicon colors to match logo color order.
- API issues - support filtering by iids.
+## 8.17.8 (2017-08-09)
+
+- Remove hidden symlinks from project import files.
+- Disallow Git URLs that include a username or hostname beginning with a non-alphanumeric character.
+
## 8.17.7 (2017-07-19)
- Renders 404 if given project is not readable by the user on Todos dashboard.
diff --git a/app/assets/javascripts/boards/components/modal/list.js b/app/assets/javascripts/boards/components/modal/list.js
index 363269c0d5d..b4a45feee4d 100644
--- a/app/assets/javascripts/boards/components/modal/list.js
+++ b/app/assets/javascripts/boards/components/modal/list.js
@@ -1,7 +1,7 @@
/* global ListIssue */
-/* global bp */
import Vue from 'vue';
+import bp from '../../../breakpoints';
const ModalStore = gl.issueBoards.ModalStore;
diff --git a/app/assets/javascripts/breakpoints.js b/app/assets/javascripts/breakpoints.js
index 2c1f988d987..7951348d8b2 100644
--- a/app/assets/javascripts/breakpoints.js
+++ b/app/assets/javascripts/breakpoints.js
@@ -1,66 +1,19 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, quotes, no-shadow, prefer-arrow-callback, prefer-template, consistent-return, no-return-assign, new-parens, no-param-reassign, max-len */
+export const breakpoints = {
+ lg: 1200,
+ md: 992,
+ sm: 768,
+ xs: 0,
+};
-var Breakpoints = (function() {
- var BreakpointInstance, instance;
+const BreakpointInstance = {
+ windowWidth: () => window.innerWidth,
+ getBreakpointSize() {
+ const windowWidth = this.windowWidth();
- function Breakpoints() {}
+ const breakpoint = Object.keys(breakpoints).find(key => windowWidth > breakpoints[key]);
- instance = null;
+ return breakpoint;
+ },
+};
- BreakpointInstance = (function() {
- var BREAKPOINTS;
-
- BREAKPOINTS = ["xs", "sm", "md", "lg"];
-
- function BreakpointInstance() {
- this.setup();
- }
-
- BreakpointInstance.prototype.setup = function() {
- var allDeviceSelector, els;
- allDeviceSelector = BREAKPOINTS.map(function(breakpoint) {
- return ".device-" + breakpoint;
- });
- if ($(allDeviceSelector.join(",")).length) {
- return;
- }
- // Create all the elements
- els = $.map(BREAKPOINTS, function(breakpoint) {
- return "<div class='device-" + breakpoint + " visible-" + breakpoint + "'></div>";
- });
- return $("body").append(els.join(''));
- };
-
- BreakpointInstance.prototype.visibleDevice = function() {
- var allDeviceSelector;
- allDeviceSelector = BREAKPOINTS.map(function(breakpoint) {
- return ".device-" + breakpoint;
- });
- return $(allDeviceSelector.join(",")).filter(":visible");
- };
-
- BreakpointInstance.prototype.getBreakpointSize = function() {
- var $visibleDevice;
- $visibleDevice = this.visibleDevice;
- // TODO: Consider refactoring in light of turbolinks removal.
- // the page refreshed via turbolinks
- if (!$visibleDevice().length) {
- this.setup();
- }
- $visibleDevice = this.visibleDevice();
- return $visibleDevice.attr("class").split("visible-")[1];
- };
-
- return BreakpointInstance;
- })();
-
- Breakpoints.get = function() {
- return instance != null ? instance : instance = new BreakpointInstance;
- };
-
- return Breakpoints;
-})();
-
-$(() => { window.bp = Breakpoints.get(); });
-
-window.Breakpoints = Breakpoints;
+export default BreakpointInstance;
diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js
index 940326dcd33..ae1a23132a7 100644
--- a/app/assets/javascripts/build.js
+++ b/app/assets/javascripts/build.js
@@ -1,8 +1,7 @@
/* eslint-disable func-names, wrap-iife, no-use-before-define,
consistent-return, prefer-rest-params */
-/* global Breakpoints */
-
import _ from 'underscore';
+import bp from './breakpoints';
import { bytesToKiB } from './lib/utils/number_utils';
window.Build = (function () {
@@ -34,8 +33,6 @@ window.Build = (function () {
this.$scrollBottomBtn = $('.js-scroll-down');
clearTimeout(Build.timeout);
- // Init breakpoint checker
- this.bp = Breakpoints.get();
this.initSidebar();
this.populateJobs(this.buildStage);
@@ -230,7 +227,7 @@ window.Build = (function () {
};
Build.prototype.shouldHideSidebarForViewport = function () {
- const bootstrapBreakpoint = this.bp.getBreakpointSize();
+ const bootstrapBreakpoint = bp.getBreakpointSize();
return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
};
diff --git a/app/assets/javascripts/fly_out_nav.js b/app/assets/javascripts/fly_out_nav.js
index ad957f132b8..56744a440e7 100644
--- a/app/assets/javascripts/fly_out_nav.js
+++ b/app/assets/javascripts/fly_out_nav.js
@@ -1,6 +1,5 @@
-/* global bp */
import Cookies from 'js-cookie';
-import './breakpoints';
+import bp from './breakpoints';
export const canShowActiveSubItems = (el) => {
const isHiddenByMedia = bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md';
diff --git a/app/assets/javascripts/issuable_context.js b/app/assets/javascripts/issuable_context.js
index 26392db4b5b..70c364e51fe 100644
--- a/app/assets/javascripts/issuable_context.js
+++ b/app/assets/javascripts/issuable_context.js
@@ -1,7 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-new, comma-dangle, quotes, prefer-arrow-callback, consistent-return, one-var, no-var, one-var-declaration-per-line, no-underscore-dangle, max-len */
-/* global bp */
-
import Cookies from 'js-cookie';
+import bp from './breakpoints';
import UsersSelect from './users_select';
const PARTICIPANTS_ROW_COUNT = 7;
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index e0c61a474c6..37f531c78f4 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -1,5 +1,4 @@
/* eslint-disable func-names, space-before-function-paren, no-var, quotes, consistent-return, prefer-arrow-callback, comma-dangle, object-shorthand, no-new, max-len, no-multi-spaces, import/newline-after-import, import/first */
-/* global bp */
/* global Flash */
/* global ConfirmDangerModal */
/* global Aside */
@@ -66,7 +65,7 @@ import './api';
import './aside';
import './autosave';
import loadAwardsHandler from './awards_handler';
-import './breakpoints';
+import bp from './breakpoints';
import './broadcast_message';
import './build';
import './build_artifacts';
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 0294da3f20d..5a9b3d19f84 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -1,13 +1,12 @@
/* eslint-disable no-new, class-methods-use-this */
-/* global Breakpoints */
/* global Flash */
/* global notes */
import Cookies from 'js-cookie';
-import './breakpoints';
import './flash';
import BlobForkSuggestion from './blob/blob_fork_suggestion';
import initChangesDropdown from './init_changes_dropdown';
+import bp from './breakpoints';
/* eslint-disable max-len */
// MergeRequestTabs
@@ -134,7 +133,7 @@ import initChangesDropdown from './init_changes_dropdown';
this.destroyPipelinesView();
} else if (this.isDiffAction(action)) {
this.loadDiff($target.attr('href'));
- if (Breakpoints.get().getBreakpointSize() !== 'lg') {
+ if (bp.getBreakpointSize() !== 'lg') {
this.shrinkView();
}
if (this.diffViewType() === 'parallel') {
@@ -145,7 +144,7 @@ import initChangesDropdown from './init_changes_dropdown';
this.resetViewContainer();
this.mountPipelinesView();
} else {
- if (Breakpoints.get().getBreakpointSize() !== 'xs') {
+ if (bp.getBreakpointSize() !== 'xs') {
this.expandView();
}
this.resetViewContainer();
@@ -392,7 +391,7 @@ import initChangesDropdown from './init_changes_dropdown';
// Screen space on small screens is usually very sparse
// So we dont affix the tabs on these
- if (Breakpoints.get().getBreakpointSize() === 'xs' || !$tabs.length) return;
+ if (bp.getBreakpointSize() === 'xs' || !$tabs.length) return;
/**
If the browser does not support position sticky, it returns the position as static.
diff --git a/app/assets/javascripts/monitoring/components/monitoring_column.vue b/app/assets/javascripts/monitoring/components/monitoring_column.vue
index c376baea79c..407af51cb7a 100644
--- a/app/assets/javascripts/monitoring/components/monitoring_column.vue
+++ b/app/assets/javascripts/monitoring/components/monitoring_column.vue
@@ -1,5 +1,4 @@
<script>
- /* global Breakpoints */
import d3 from 'd3';
import monitoringLegends from './monitoring_legends.vue';
import monitoringFlag from './monitoring_flag.vue';
@@ -8,6 +7,7 @@
import eventHub from '../event_hub';
import measurements from '../utils/measurements';
import { formatRelevantDigits } from '../../lib/utils/number_utils';
+ import bp from '../../breakpoints';
const bisectDate = d3.bisector(d => d.time).left;
@@ -42,7 +42,6 @@
yScale: {},
margin: {},
data: [],
- breakpointHandler: Breakpoints.get(),
unitOfDisplay: '',
areaColorRgb: '#8fbce8',
lineColorRgb: '#1f78d1',
@@ -96,7 +95,7 @@
methods: {
draw() {
- const breakpointSize = this.breakpointHandler.getBreakpointSize();
+ const breakpointSize = bp.getBreakpointSize();
const query = this.columnData.queries[0];
this.margin = measurements.large.margin;
if (breakpointSize === 'xs' || breakpointSize === 'sm') {
diff --git a/app/assets/javascripts/new_sidebar.js b/app/assets/javascripts/new_sidebar.js
index 930218dd1f5..b10b074f5ac 100644
--- a/app/assets/javascripts/new_sidebar.js
+++ b/app/assets/javascripts/new_sidebar.js
@@ -1,7 +1,6 @@
import Cookies from 'js-cookie';
import _ from 'underscore';
-/* global bp */
-import './breakpoints';
+import bp from './breakpoints';
export default class NewNavSidebar {
constructor() {
diff --git a/app/assets/javascripts/wikis.js b/app/assets/javascripts/wikis.js
index 51ed2b4fd15..a0025ddb598 100644
--- a/app/assets/javascripts/wikis.js
+++ b/app/assets/javascripts/wikis.js
@@ -1,10 +1,7 @@
-/* global Breakpoints */
-
-import './breakpoints';
+import bp from './breakpoints';
export default class Wikis {
constructor() {
- this.bp = Breakpoints.get();
this.sidebarEl = document.querySelector('.js-wiki-sidebar');
this.sidebarExpanded = false;
@@ -41,15 +38,15 @@ export default class Wikis {
this.renderSidebar();
}
- sidebarCanCollapse() {
- const bootstrapBreakpoint = this.bp.getBreakpointSize();
+ static sidebarCanCollapse() {
+ const bootstrapBreakpoint = bp.getBreakpointSize();
return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
}
renderSidebar() {
if (!this.sidebarEl) return;
const { classList } = this.sidebarEl;
- if (this.sidebarExpanded || !this.sidebarCanCollapse()) {
+ if (this.sidebarExpanded || !Wikis.sidebarCanCollapse()) {
if (!classList.contains('right-sidebar-expanded')) {
classList.remove('right-sidebar-collapsed');
classList.add('right-sidebar-expanded');
diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss
index 4367b8c1a15..d49f23b4f5a 100644
--- a/app/assets/stylesheets/new_sidebar.scss
+++ b/app/assets/stylesheets/new_sidebar.scss
@@ -103,6 +103,7 @@ $new-sidebar-collapsed-width: 50px;
&.sidebar-icons-only {
width: $new-sidebar-collapsed-width;
+ overflow-x: hidden;
.badge,
.project-title {
diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb
index fc87bd6a659..414f672cc6a 100644
--- a/app/services/ci/register_job_service.rb
+++ b/app/services/ci/register_job_service.rb
@@ -85,13 +85,13 @@ module Ci
end
def register_failure
- failed_attempt_counter.increase
- attempt_counter.increase
+ failed_attempt_counter.increment
+ attempt_counter.increment
end
def register_success(job)
job_queue_duration_seconds.observe({ shared_runner: @runner.shared? }, Time.now - job.created_at)
- attempt_counter.increase
+ attempt_counter.increment
end
def failed_attempt_counter
diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb
index 5038155ca31..394b336a638 100644
--- a/app/services/projects/update_pages_service.rb
+++ b/app/services/projects/update_pages_service.rb
@@ -172,11 +172,11 @@ module Projects
end
def register_attempt
- pages_deployments_total_counter.increase
+ pages_deployments_total_counter.increment
end
def register_failure
- pages_deployments_failed_total_counter.increase
+ pages_deployments_failed_total_counter.increment
end
def pages_deployments_total_counter
diff --git a/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml b/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml
index 209afd4aab4..57544559824 100644
--- a/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml
+++ b/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml
@@ -28,6 +28,6 @@
%h3.blank-state-title
Create a group
%p.blank-state-text
- Groups are a great way to organise projects and people.
+ Groups are a great way to organize projects and people.
= link_to new_group_path, class: "btn btn-new" do
New group
diff --git a/app/views/import/fogbugz/new_user_map.html.haml b/app/views/import/fogbugz/new_user_map.html.haml
index c52a515226e..84e0009487f 100644
--- a/app/views/import/fogbugz/new_user_map.html.haml
+++ b/app/views/import/fogbugz/new_user_map.html.haml
@@ -10,7 +10,7 @@
Customize how FogBugz email addresses and usernames are imported into GitLab.
In the next step, you'll be able to select the projects you want to import.
%p
- The user map is a mapping of the FogBugz users that participated on your projects to the way their email address and usernames wil be imported into GitLab. You can change this by populating the table below.
+ The user map is a mapping of the FogBugz users that participated on your projects to the way their email address and usernames will be imported into GitLab. You can change this by populating the table below.
%ul
%li
%strong Default: Map a FogBugz account ID to a full name
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 20fceda26dc..c2794f8aaa8 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -205,7 +205,7 @@
%button.btn.js-settings-toggle
= expanded ? 'Collapse' : 'Expand'
%p
- Perform advanced options such as housekeeping, exporting, archiveing, renameing, transfering, or removeing your project.
+ Perform advanced options such as housekeeping, exporting, archiving, renaming, transferring, or removing your project.
.settings-content.no-animate{ class: ('expanded' if expanded) }
.sub-section
%h4 Housekeeping
@@ -274,7 +274,7 @@
%li Be careful. Changing the project's namespace can have unintended side effects.
%li You can only transfer the project to namespaces you manage.
%li You will need to update your local repositories to point to the new location.
- %li Project visibility level will be changed to match namespace rules when transfering to a group.
+ %li Project visibility level will be changed to match namespace rules when transferring to a group.
= f.submit 'Transfer project', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) }
- if @project.forked? && can?(current_user, :remove_fork_project, @project)
.sub-section
diff --git a/bin/changelog b/bin/changelog
index 4c894f8ff5b..61d4de06e90 100755
--- a/bin/changelog
+++ b/bin/changelog
@@ -14,54 +14,107 @@ Options = Struct.new(
:dry_run,
:force,
:merge_request,
- :title
+ :title,
+ :type
)
+INVALID_TYPE = -1
class ChangelogOptionParser
- def self.parse(argv)
- options = Options.new
+ Type = Struct.new(:name, :description)
+ TYPES = [
+ Type.new('added', 'New feature'),
+ Type.new('fixed', 'Bug fix'),
+ Type.new('changed', 'Feature change'),
+ Type.new('deprecated', 'New deprecation'),
+ Type.new('removed', 'Feature removal'),
+ Type.new('security', 'Security fix'),
+ Type.new('other', 'Other')
+ ].freeze
+ TYPES_OFFSET = 1
+
+ class << self
+ def parse(argv)
+ options = Options.new
+
+ parser = OptionParser.new do |opts|
+ opts.banner = "Usage: #{__FILE__} [options] [title]\n\n"
+
+ # Note: We do not provide a shorthand for this in order to match the `git
+ # commit` interface
+ opts.on('--amend', 'Amend the previous commit') do |value|
+ options.amend = value
+ 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|
+ options.merge_request = value
+ end
+
+ opts.on('-n', '--dry-run', "Don't actually write anything, just print") do |value|
+ options.dry_run = value
+ end
+
+ opts.on('-u', '--git-username', 'Use Git user.name configuration as the author') do |value|
+ options.author = git_user_name if value
+ end
+
+ opts.on('-t', '--type [string]', String, "The category of the change, valid options are: #{TYPES.map(&:name).join(', ')}") do |value|
+ options.type = parse_type(value)
+ end
+
+ opts.on('-h', '--help', 'Print help message') do
+ $stdout.puts opts
+ exit
+ end
+ end
- parser = OptionParser.new do |opts|
- opts.banner = "Usage: #{__FILE__} [options] [title]\n\n"
+ parser.parse!(argv)
- # Note: We do not provide a shorthand for this in order to match the `git
- # commit` interface
- opts.on('--amend', 'Amend the previous commit') do |value|
- options.amend = value
- end
+ # Title is everything that remains, but let's clean it up a bit
+ options.title = argv.join(' ').strip.squeeze(' ').tr("\r\n", '')
- opts.on('-f', '--force', 'Overwrite an existing entry') do |value|
- options.force = value
- end
+ options
+ end
- opts.on('-m', '--merge-request [integer]', Integer, 'Merge Request ID') do |value|
- options.merge_request = value
- end
+ def read_type
+ read_type_message
- opts.on('-n', '--dry-run', "Don't actually write anything, just print") do |value|
- options.dry_run = value
- end
+ type = TYPES[$stdin.getc.to_i - TYPES_OFFSET]
+ assert_valid_type!(type)
- opts.on('-u', '--git-username', 'Use Git user.name configuration as the author') do |value|
- options.author = git_user_name if value
- end
+ type.name
+ end
+
+ private
- opts.on('-h', '--help', 'Print help message') do
- $stdout.puts opts
- exit
+ def parse_type(name)
+ type_found = TYPES.find do |type|
+ type.name == name
end
+ type_found ? type_found.name : INVALID_TYPE
end
- parser.parse!(argv)
-
- # Title is everything that remains, but let's clean it up a bit
- options.title = argv.join(' ').strip.squeeze(' ').tr("\r\n", '')
+ def read_type_message
+ $stdout.puts "\n>> Please specify the index for the category of your change:"
+ TYPES.each_with_index do |type, index|
+ $stdout.puts "#{index + TYPES_OFFSET}. #{type.description}"
+ end
+ $stdout.print "\n?> "
+ end
- options
- end
+ def assert_valid_type!(type)
+ unless type
+ $stderr.puts "Invalid category index, please select an index between 1 and #{TYPES.length}"
+ exit 1
+ end
+ end
- def self.git_user_name
- %x{git config user.name}.strip
+ def git_user_name
+ %x{git config user.name}.strip
+ end
end
end
@@ -72,8 +125,12 @@ class ChangelogEntry
@options = options
assert_feature_branch!
- assert_new_file!
assert_title!
+ assert_new_file!
+
+ # Read type from $stdin unless is already set
+ options.type ||= ChangelogOptionParser.read_type
+ assert_valid_type!
$stdout.puts "\e[32mcreate\e[0m #{file_path}"
$stdout.puts contents
@@ -90,7 +147,8 @@ class ChangelogEntry
yaml_content = YAML.dump(
'title' => title,
'merge_request' => options.merge_request,
- 'author' => options.author
+ 'author' => options.author,
+ 'type' => options.type
)
remove_trailing_whitespace(yaml_content)
end
@@ -129,6 +187,12 @@ class ChangelogEntry
" to use the title from the previous commit."
end
+ def assert_valid_type!
+ return unless options.type && options.type == INVALID_TYPE
+
+ fail_with 'Invalid category given!'
+ end
+
def title
if options.title.empty?
last_commit_subject
diff --git a/changelogs/unreleased/21949-add-type-to-changelog.yml b/changelogs/unreleased/21949-add-type-to-changelog.yml
new file mode 100644
index 00000000000..a20f6b7ad4e
--- /dev/null
+++ b/changelogs/unreleased/21949-add-type-to-changelog.yml
@@ -0,0 +1,4 @@
+---
+title: Added type to CHANGELOG entries
+merge_request:
+author: Jacopo Beschi @jacopo-beschi
diff --git a/changelogs/unreleased/fix-import-symbolink-links.yml b/changelogs/unreleased/fix-import-symbolink-links.yml
new file mode 100644
index 00000000000..36e73821bdc
--- /dev/null
+++ b/changelogs/unreleased/fix-import-symbolink-links.yml
@@ -0,0 +1,4 @@
+---
+title: Remove hidden symlinks from project import files
+merge_request:
+author:
diff --git a/changelogs/unreleased/rs-alphanumeric-ssh-params.yml b/changelogs/unreleased/rs-alphanumeric-ssh-params.yml
new file mode 100644
index 00000000000..426b01cafad
--- /dev/null
+++ b/changelogs/unreleased/rs-alphanumeric-ssh-params.yml
@@ -0,0 +1,5 @@
+---
+title: Disallow Git URLs that include a username or hostname beginning with a non-alphanumeric
+ character
+merge_request:
+author:
diff --git a/doc/README.md b/doc/README.md
index ca4790ceda0..4175750d497 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -1,3 +1,7 @@
+---
+toc: false
+---
+
# GitLab Documentation
Welcome to [GitLab](https://about.gitlab.com/), a Git-based fully featured
@@ -26,6 +30,9 @@ Shortcuts to GitLab's most visited docs:
| [Configuring `.gitlab-ci.yml`](ci/yaml/README.md) | [SSH authentication](ssh/README.md) |
| [Using Docker images](ci/docker/using_docker_images.md) | [GitLab Pages](user/project/pages/index.md) |
+- [User documentation](user/index.md)
+- [Administrator documentation](#administrator-documentation)
+
## Getting started with GitLab
- [GitLab Basics](gitlab-basics/README.md): Start working on your command line and on GitLab.
@@ -36,7 +43,6 @@ Shortcuts to GitLab's most visited docs:
### User account
-- [User documentation](user/index.md): Learn how to use GitLab and explore its features
- [User account](user/profile/index.md): Manage your account
- [Authentication](topics/authentication/index.md): Account security with two-factor authentication, setup your ssh keys and deploy keys for secure access to your projects.
- [Profile settings](user/profile/index.md#profile-settings): Manage your profile settings, two factor authentication and more.
diff --git a/doc/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md
index 90a2e9298bf..e09ccaba08c 100644
--- a/doc/administration/high_availability/nfs.md
+++ b/doc/administration/high_availability/nfs.md
@@ -42,6 +42,10 @@ GitLab does not recommend using EFS with GitLab.
are allocated. For smaller volumes, users may experience decent performance
for a period of time due to 'Burst Credits'. Over a period of weeks to months
credits may run out and performance will bottom out.
+- To keep "Burst Credits" available, it may be necessary to provision more space
+ with 'dummy data'. However, this may get expensive.
+- Another option to maintain "Burst Credits" is to use FS Cache on the server so
+ that AWS doesn't always have to go into EFS to access files.
- For larger volumes, allocated IOPS may not be the problem. Workloads where
many small files are written in a serialized manner are not well-suited for EFS.
EBS with an NFS server on top will perform much better.
diff --git a/doc/development/changelog.md b/doc/development/changelog.md
index ce39a379a0e..f869938fe11 100644
--- a/doc/development/changelog.md
+++ b/doc/development/changelog.md
@@ -15,11 +15,14 @@ following format:
title: "Going through change[log]s"
merge_request: 1972
author: Ozzy Osbourne
+type: added
```
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
contributors. **Both are optional**.
+The `type` field maps the category of the change,
+valid options are: added, fixed, changed, deprecated, removed, security, other. **Type field is mandatory**.
Community contributors and core team members are encouraged to add their name to
the `author` field. GitLab team members **should not**.
@@ -94,6 +97,19 @@ Its simplest usage is to provide the value for `title`:
$ bin/changelog 'Hey DZ, I added a feature to GitLab!'
```
+At this point the script would ask you to select the category of the change (mapped to the `type` field in the entry):
+
+```text
+>> Please specify the category of your change:
+1. New feature
+2. Bug fix
+3. Feature change
+4. New deprecation
+5. Feature removal
+6. Security fix
+7. Other
+```
+
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
`changelogs/unreleased/feature-hey-dz.yml` file.
@@ -106,26 +122,29 @@ create changelogs/unreleased/my-feature.yml
title: Hey DZ, I added a feature to GitLab!
merge_request:
author:
+type:
```
If you're working on the GitLab EE repository, the entry will be added to
`changelogs/unreleased-ee/` instead.
#### Arguments
-| Argument | Shorthand | Purpose |
-| ----------------- | --------- | --------------------------------------------- |
-| [`--amend`] | | Amend the previous commit |
-| [`--force`] | `-f` | Overwrite an existing entry |
-| [`--merge-request`] | `-m` | Set merge request ID |
-| [`--dry-run`] | `-n` | Don't actually write anything, just print |
-| [`--git-username`] | `-u` | Use Git user.name configuration as the author |
-| [`--help`] | `-h` | Print help message |
+| Argument | Shorthand | Purpose |
+| ----------------- | --------- | ---------------------------------------------------------------------------------------------------------- |
+| [`--amend`] | | Amend the previous commit |
+| [`--force`] | `-f` | Overwrite an existing entry |
+| [`--merge-request`] | `-m` | Set merge request ID |
+| [`--dry-run`] | `-n` | Don't actually write anything, just print |
+| [`--git-username`] | `-u` | Use Git user.name configuration as the author |
+| [`--type`] | `-t` | The category of the change, valid options are: added, fixed, changed, deprecated, removed, security, other |
+| [`--help`] | `-h` | Print help message |
[`--amend`]: #-amend
[`--force`]: #-force-or-f
[`--merge-request`]: #-merge-request-or-m
[`--dry-run`]: #-dry-run-or-n
[`--git-username`]: #-git-username-or-u
+[`--type`]: #-type-or-t
[`--help`]: #-help
##### `--amend`
@@ -147,6 +166,7 @@ create changelogs/unreleased/feature-hey-dz.yml
title: Added an awesome new feature to GitLab
merge_request:
author:
+type:
```
##### `--force` or `-f`
@@ -164,6 +184,7 @@ create changelogs/unreleased/feature-hey-dz.yml
title: Hey DZ, I added a feature to GitLab!
merge_request: 1983
author:
+type:
```
##### `--merge-request` or `-m`
@@ -178,6 +199,7 @@ create changelogs/unreleased/feature-hey-dz.yml
title: Hey DZ, I added a feature to GitLab!
merge_request: 1983
author:
+type:
```
##### `--dry-run` or `-n`
@@ -192,6 +214,7 @@ create changelogs/unreleased/feature-hey-dz.yml
title: Added an awesome new feature to GitLab
merge_request:
author:
+type:
$ ls changelogs/unreleased/
```
@@ -211,6 +234,21 @@ create changelogs/unreleased/feature-hey-dz.yml
title: Hey DZ, I added a feature to GitLab!
merge_request:
author: Jane Doe
+type:
+```
+
+##### `--type` or `-t`
+
+Use the **`--type`** or **`-t`** argument to provide the `type` value:
+
+```text
+$ bin/changelog 'Hey DZ, I added a feature to GitLab!' -t added
+create changelogs/unreleased/feature-hey-dz.yml
+---
+title: Hey DZ, I added a feature to GitLab!
+merge_request:
+author:
+type: added
```
### History and Reasoning
diff --git a/doc/install/kubernetes/gitlab_chart.md b/doc/install/kubernetes/gitlab_chart.md
index d2442a4fbde..0fad181f59e 100644
--- a/doc/install/kubernetes/gitlab_chart.md
+++ b/doc/install/kubernetes/gitlab_chart.md
@@ -1,7 +1,7 @@
# GitLab Helm Chart
-> Officially supported cloud providers are Google Container Service and Azure Container Service.
+> These Helm charts are in beta. GitLab is working on a [cloud-native](http://docs.gitlab.com/omnibus/package-information/cloud_native.html) set of [Charts](https://gitlab.com/charts/helm.gitlab.io) which will replace these.
-> Officially supported schedulers are Kubernetes and Terraform.
+> Officially supported cloud providers are Google Container Service and Azure Container Service.
The `gitlab` Helm chart deploys GitLab into your Kubernetes cluster.
@@ -207,7 +207,7 @@ its class in an annotation.
>**Note:**
The Ingress alone doesn't expose GitLab externally. You need to have a Ingress controller setup to do that.
Setting up an Ingress controller can be done by installing the `nginx-ingress` helm chart. But be sure
-to read the [documentation](https://github.com/kubernetes/charts/blob/master/stable/nginx-ingress/README.md).
+to read the [documentation](https://github.com/kubernetes/charts/blob/master/stable/nginx-ingress/README.md).
>**Note:**
If you would like to use the Registry, you will also need to ensure your Ingress supports a [sufficiently large request size](http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size).
diff --git a/doc/install/kubernetes/gitlab_omnibus.md b/doc/install/kubernetes/gitlab_omnibus.md
new file mode 100644
index 00000000000..b7e86ea7c81
--- /dev/null
+++ b/doc/install/kubernetes/gitlab_omnibus.md
@@ -0,0 +1,171 @@
+# GitLab-Omnibus Helm Chart
+> These Helm charts are in beta. GitLab is working on a [cloud-native](http://docs.gitlab.com/omnibus/package-information/cloud_native.html) set of [Charts](https://gitlab.com/charts/helm.gitlab.io) which will replace these.
+
+> Officially supported cloud providers are Google Container Service and Azure Container Service.
+
+This work is based partially on: https://github.com/lwolf/kubernetes-gitlab/. GitLab would like to thank Sergey Nuzhdin for his work.
+
+## Introduction
+
+This chart provides an easy way to get started with GitLab, provisioning an installation with nearly all functionality enabled. SSL is automatically provisioned as well via [Let's Encrypt](https://letsencrypt.org/).
+
+The deployment includes:
+
+- A [GitLab Omnibus](https://docs.gitlab.com/omnibus/) Pod, including Mattermost, Container Registry, and Prometheus
+- An auto-scaling [GitLab Runner](https://docs.gitlab.com/runner/) using the Kubernetes executor
+- [Redis](https://github.com/kubernetes/charts/tree/master/stable/redis)
+- [PostgreSQL](https://github.com/kubernetes/charts/tree/master/stable/postgresql)
+- [NGINX Ingress](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress)
+- Persistent Volume Claims for Data, Registry, Postgres, and Redis
+
+A video demonstration of GitLab utilizing this chart [is available](https://about.gitlab.com/handbook/sales/demo/).
+
+Terms:
+
+- Google Cloud Platform (**GCP**)
+- Google Container Engine (**GKE**)
+- Azure Container Service (**ACS**)
+- Kubernetes (**k8s**)
+
+## Prerequisites
+
+- _At least_ 4 GB of RAM available on your cluster, in chunks of 1 GB. 41GB of storage and 2 CPU are also required.
+- Kubernetes 1.4+ with Beta APIs enabled
+- [Persistent Volume](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) provisioner support in the underlying infrastructure
+- An [external IP address](#networking-prerequisites)
+- A [wildcard DNS entry](#networking-prerequisites), which resolves to the external IP address
+- The `kubectl` CLI installed locally and authenticated for the cluster
+- The Helm Client installed locally
+- The Helm Server (Tiller) already installed and running in the cluster, by running `helm init`
+- The GitLab Helm Repo [added to your Helm Client](index.md#add-the-gitlab-helm-repository)
+
+### Networking Prerequisites
+
+This chart configures a GitLab server and Kubernetes cluster which can support dynamic [Review Apps](https://docs.gitlab.com/ee/ci/review_apps/index.html), as well as services like the integrated [Container Registry](https://docs.gitlab.com/ee/user/project/container_registry.html) and [Mattermost](https://docs.gitlab.com/omnibus/gitlab-mattermost/).
+
+To support the GitLab services and dynamic environments, a wildcard DNS entry is required which resolves to the external Load Balancer IP.
+
+To provision an external IP on GCP and Azure, simply request a new address from the Networking section. Ensure that the region matches the region your container cluster is created in. Note, it is important that the IP is not assigned at this point in time. It will be automatically assigned once the Helm chart is installed, and assigned to the Load Balancer.
+
+Now that an external IP address has been allocated, ensure that the wildcard DNS entry you would like to use resolves to this IP. Please consult the documentation for your DNS service for more information on creating DNS records.
+
+## Configuring and Installing GitLab
+
+For most installations, only two parameters are required:
+- `baseIP`: the desired [external IP address](#networking-prerequisites)
+- `baseDomain`: the [base domain](#networking-prerequisites) with the wildcard host entry resolving to the `baseIP`. For example, `mycompany.io`.
+
+Other common configuration options:
+- `gitlab`: Choose the [desired edition](https://about.gitlab.com/products), either `ee` or `ce`. `ce` is the default.
+- `gitlabEELicense`: For Enterprise Edition, the [license](https://docs.gitlab.com/ee/user/admin_area/license.html) can be installed directly via the Chart
+- `provider`: Optimizes the deployment for a cloud provider. The default is `gke` for GCP, with `acs` also supported for Azure.
+- `legoEmail`: Email address to use when requesting new SSL certificates from Let's Encrypt
+
+For additional configuration options, consult the [values.yaml](https://gitlab.com/charts/charts.gitlab.io/blob/master/charts/gitlab-omnibus/values.yaml).
+
+These settings can either be passed directly on the command line:
+```bash
+helm install --name gitlab --set baseDomain=gitlab.io,baseIP=1.1.1.1,gitlab=ee,gitlabEELicense=$LICENSE,legoEmail=email@gitlab.com gitlab/gitlab-omnibus
+```
+
+or within a YAML file:
+```bash
+helm install --name gitlab -f values.yaml gitlab/gitlab-omnibus
+```
+
+> **Note:**
+If you are using a machine type with support for less than 4 attached disks, like an Azure trial, you should disable dedicated storage for [Postgres and Redis](#persistent-storage).
+
+### Choosing a different GitLab release version
+
+The version of GitLab installed is based on the `gitlab` setting (see [section](#choosing-gitlab-edition) above), and
+the value of the corresponding helm setting: `gitlabCEImage` or `gitabEEImage`.
+
+```yaml
+gitlab: CE
+gitlabCEImage: gitlab/gitlab-ce:9.1.2-ce.0
+gitlabEEImage: gitlab/gitlab-ee:9.1.2-ee.0
+```
+
+The different images can be found in the [gitlab-ce](https://hub.docker.com/r/gitlab/gitlab-ce/tags/) and [gitlab-ee](https://hub.docker.com/r/gitlab/gitlab-ee/tags/)
+repositories on Docker Hub.
+
+> **Note:**
+There is no guarantee that other release versions of GitLab, other than what are
+used by default in the chart, will be supported by a chart install.
+
+### Persistent storage
+
+By default, persistent storage is enabled for GitLab and the charts it depends
+on (Redis and PostgreSQL).
+
+Components can have their claim size set from your `values.yaml`, along with whether to provision separate storage for Postgres and Redis.
+
+Basic configuration:
+
+```yaml
+redisImage: redis:3.2.10
+redisDedicatedStorage: true
+redisStorageSize: 5Gi
+postgresImage: postgres:9.6.3
+# If you disable postgresDedicatedStorage, you should consider bumping up gitlabRailsStorageSize
+postgresDedicatedStorage: true
+postgresStorageSize: 30Gi
+gitlabRailsStorageSize: 30Gi
+gitlabRegistryStorageSize: 30Gi
+gitlabConfigStorageSize: 1Gi
+```
+
+### Routing and SSL
+
+Ingress routing and SSL are automatically configured within this Chart. An NGINX ingress is provisioned and configured, and will route traffic to any service. SSL certificates are automatically created and configured by [kube-lego](https://github.com/kubernetes/charts/tree/master/stable/kube-lego).
+
+> **Note:**
+Let's Encrypt limits a single TLD to five certificate requests within a single week. This means that common DNS wildcard services like [xip.io](http://xip.io) and [nip.io](http://nip.io) are unlikely to work.
+
+## Installing GitLab using the Helm Chart
+> You may see a temporary error message `SchedulerPredicates failed due to PersistentVolumeClaim is not bound` while storage provisions. Once the storage provisions, the pods will automatically restart. This may take a couple minutes depending on your cloud provider. If the error persists, please review the [prerequisites](#prerequisites) to ensure you have enough RAM, CPU, and storage.
+
+Once you have reviewed the [configuration settings](#configuring-and-installing-gitlab), you can install the chart. We recommending saving your configuration options in a `values.yaml` file for easier upgrades in the future.
+
+For example:
+```bash
+helm install --name gitlab -f values.yaml gitlab/gitlab-omnibus
+```
+
+or passing them on the command line:
+```bash
+helm install --name gitlab --set baseDomain=gitlab.io,baseIP=1.1.1.1,gitlab=ee,gitlabEELicense=$LICENSE,legoEmail=email@gitlab.com gitlab/gitlab-omnibus
+```
+
+## Updating GitLab using the Helm Chart
+
+Once your GitLab Chart is installed, configuration changes and chart updates
+should we done using `helm upgrade`
+
+```bash
+helm upgrade -f <CONFIG_VALUES_FILE> <RELEASE-NAME> gitlab/gitlab
+```
+
+where:
+
+- `<CONFIG_VALUES_FILE>` is the path to values file containing your custom
+ [configuration] (#configuring-and-installing-gitlab).
+- `<RELEASE-NAME>` is the name you gave the chart when installing it.
+ In the [Install section](#installing-gitlab-using-the-helm-chart) we called it `gitlab`.
+
+## Uninstalling GitLab using the Helm Chart
+
+To uninstall the GitLab Chart, run the following:
+
+```bash
+helm delete <RELEASE-NAME>
+```
+
+where:
+
+- `<RELEASE-NAME>` is the name you gave the chart when installing it.
+ In the [Install section](#installing) we called it `gitlab`.
+
+[kube-srv]: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services---service-types
+[storageclass]: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#storageclasses
diff --git a/doc/install/kubernetes/gitlab_runner_chart.md b/doc/install/kubernetes/gitlab_runner_chart.md
index 515b2841d08..b0fe91d6337 100644
--- a/doc/install/kubernetes/gitlab_runner_chart.md
+++ b/doc/install/kubernetes/gitlab_runner_chart.md
@@ -1,7 +1,7 @@
# GitLab Runner Helm Chart
-> Officially supported cloud providers are Google Container Service and Azure Container Service.
+> These Helm charts are in beta. GitLab is working on a [cloud-native](http://docs.gitlab.com/omnibus/package-information/cloud_native.html) set of [Charts](https://gitlab.com/charts/helm.gitlab.io) which will replace these.
-> Officially supported schedulers are Kubernetes and Terraform.
+> Officially supported cloud providers are Google Container Service and Azure Container Service.
The `gitlab-runner` Helm chart deploys a GitLab Runner instance into your
Kubernetes cluster.
diff --git a/doc/install/kubernetes/index.md b/doc/install/kubernetes/index.md
index 5ea08869a9b..3608aa6b2d6 100644
--- a/doc/install/kubernetes/index.md
+++ b/doc/install/kubernetes/index.md
@@ -1,7 +1,7 @@
# Installing GitLab on Kubernetes
-> Officially supported cloud providers are Google Container Service and Azure Container Service.
+> These Helm charts are in beta. GitLab is working on a [cloud-native](http://docs.gitlab.com/omnibus/package-information/cloud_native.html) set of [Charts](https://gitlab.com/charts/helm.gitlab.io) which will replace these.
-> Officially supported schedulers are Kubernetes, Terraform and Tectonic.
+> Officially supported cloud providers are Google Container Service and Azure Container Service.
The easiest method to deploy GitLab in [Kubernetes](https://kubernetes.io/) is
to take advantage of the official GitLab Helm charts. [Helm] is a package
@@ -35,12 +35,11 @@ helm init
## Using the GitLab Helm Charts
-GitLab makes available two Helm Charts, one for the GitLab server and another
-for the Runner. More detailed information on installing and configuring each
-Chart can be found below:
+GitLab makes available three Helm Charts: an easy to use bundled chart, and a specific chart for GitLab itself and the Runner.
-- [Install GitLab](gitlab_chart.md)
-- [Install GitLab Runner](gitlab_runner_chart.md)
+- [gitlab-omnibus](gitlab_omnibus.md): The easiest way to get started. Includes everything needed to run GitLab, including: a Runner, Container Registry, automatic SSL, and an Ingress.
+- [gitlab](gitlab_chart.md): Just the GitLab service, with optional Postgres and Redis.
+- [gitlab-runner](gitlab_runner_chart.md): GitLab Runner, to process CI jobs.
[chart]: https://github.com/kubernetes/charts
[helm-quick]: https://github.com/kubernetes/helm/blob/master/docs/quickstart.md
diff --git a/doc/user/index.md b/doc/user/index.md
index 1281cc6e4f0..d664fd62754 100644
--- a/doc/user/index.md
+++ b/doc/user/index.md
@@ -71,80 +71,44 @@ your code, use it as an issue tracker, collaborate on code, and continuously
build, test, and deploy your app with built-in GitLab CI/CD. Or, you can do
it all at once, from one single project.
-### Repository
-
-Host your codebase in [GitLab repositories](project/repository/index.md) with version control
-and as part of a fully integrated platform.
-
-### Issues
-
-Explore the best of GitLab [Issues](project/issues/index.md).
-
-### Merge Requests
-
-Collanorate on code, gather reviews, live preview changes per branch, and
-request approvals with [Merge Requests](project/merge_requests/index.md).
-
-### Milestones
-
-Work on multiple issues and merge requests towards the same target date
-with [Milestones](project/milestones/index.md).
-
-### GitLab Pages
-
-Publish your static site directly from GitLab with [GitLab Pages](project/pages/index.md). You
-can [build, test, and deploy any Static Site Generator](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/) with Pages.
-
-### Container Registry
-
-Build and deploy Docker images with [GitLab Container Registry](project/container_registry.md).
+- [Repositories](project/repository/index.md): Host your codebase in
+repositories with version control and as part of a fully integrated platform.
+- [Issues](project/issues/index.md): Explore the best of GitLab Issues' features.
+- [Merge Requests](project/merge_requests/index.md): Collaborate on code,
+reviews, live preview changes per branch, and request approvals with Merge Requests.
+- [Milestones](project/milestones/index.md): Work on multiple issues and merge
+requests towards the same target date with Milestones.
## GitLab CI/CD
Use built-in [GitLab CI/CD](../ci/README.md) to test, build, and deploy your applications
directly from GitLab. No third-party integrations needed.
-### Auto Deploy
-
-Deploy your application out-of-the-box with [GitLab Auto Deploy](../ci/autodeploy/index.md).
-
-### Review Apps
-
-Live-preview the changes introduced by a merge request with [Review Apps](../ci/review_apps/index.md).
-
-## Groups
-
-With GitLab [Groups](group/index.md) you can assemble related projects together
-and grant members access to several projects at once.
-
-### Subgroups
-
-Groups can also be nested in [subgroups](group/subgroups/index.md).
+- [GitLab Auto Deploy](../ci/autodeploy/index.md): Deploy your application out-of-the-box with GitLab Auto Deploy.
+- [Review Apps](../ci/review_apps/index.md): Live-preview the changes introduced by a merge request with Review Apps.
+- [GitLab Pages](project/pages/index.md): Publish your static site directly from
+GitLab with Gitlab Pages. You can build, test, and deploy any Static Site Generator with Pages.
+- [GitLab Container Registry](project/container_registry.md): Build and deploy Docker
+images with Container Registry.
## Account
There is a lot you can customize and configure
to enjoy the best of GitLab.
-[Manage your user settings](profile/index.md) to change your personal info,
+- [Settings](profile/index.md): Manage your user settings to change your personal info,
personal access tokens, authorized applications, etc.
+- [Authentication](../topics/authentication/index.md): Read through the authentication
+methods available in GitLab.
+- [Permissions](permissions.md): Learn the different set of permissions levels for each
+user type (guest, reporter, developer, master, owner).
-### Authentication
-
-Read through the [authentication](../topics/authentication/index.md) methods available in GitLab.
-
-### Permissions
-
-Learn the different set of [permissions](permissions.md) for user type (guest, reporter, developer, master, owner).
-
-## Integrations
-
-[Integrate GitLab](../integration/README.md) with your preferred tool,
-such as Trello, JIRA, etc.
+## Groups
-## Git and GitLab
+With GitLab [Groups](group/index.md) you can assemble related projects together
+and grant members access to several projects at once.
-Learn what is [Git](../topics/git/index.md) and its best practices.
+Groups can also be nested in [subgroups](group/subgroups/index.md).
## Discussions
@@ -168,6 +132,11 @@ requests you're assigned to.
you have quick access to. You can also gather feedback on them through
[discussions](#discussions).
+## Integrations
+
+[Integrate GitLab](../integration/README.md) with your preferred tool,
+such as Trello, JIRA, etc.
+
## Webhooks
Configure [webhooks](project/integrations/webhooks.html) to listen for
@@ -178,3 +147,6 @@ POST request with data to the webhook URL.
Automate GitLab via [API](../api/README.html).
+## Git and GitLab
+
+Learn what is [Git](../topics/git/index.md) and its best practices.
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 371f8797ff2..7000b173075 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -324,6 +324,23 @@ module Gitlab
raw_log(options).map { |c| Commit.decorate(self, c) }
end
+ # Used in gitaly-ruby
+ def raw_log(options)
+ actual_ref = options[:ref] || root_ref
+ begin
+ sha = sha_from_ref(actual_ref)
+ rescue Rugged::OdbError, Rugged::InvalidError, Rugged::ReferenceError
+ # Return an empty array if the ref wasn't found
+ return []
+ end
+
+ if log_using_shell?(options)
+ log_by_shell(sha, options)
+ else
+ log_by_walk(sha, options)
+ end
+ end
+
def count_commits(options)
gitaly_migrate(:count_commits) do |is_enabled|
if is_enabled
@@ -733,22 +750,6 @@ module Gitlab
sort_branches(branches, sort_by)
end
- def raw_log(options)
- actual_ref = options[:ref] || root_ref
- begin
- sha = sha_from_ref(actual_ref)
- rescue Rugged::OdbError, Rugged::InvalidError, Rugged::ReferenceError
- # Return an empty array if the ref wasn't found
- return []
- end
-
- if log_using_shell?(options)
- log_by_shell(sha, options)
- else
- log_by_walk(sha, options)
- end
- end
-
def log_using_shell?(options)
options[:path].present? ||
options[:disable_walk] ||
diff --git a/lib/gitlab/import_export/file_importer.rb b/lib/gitlab/import_export/file_importer.rb
index ffd17118c91..989342389bc 100644
--- a/lib/gitlab/import_export/file_importer.rb
+++ b/lib/gitlab/import_export/file_importer.rb
@@ -47,12 +47,16 @@ module Gitlab
end
def remove_symlinks!
- Dir["#{@shared.export_path}/**/*"].each do |path|
+ extracted_files.each do |path|
FileUtils.rm(path) if File.lstat(path).symlink?
end
true
end
+
+ def extracted_files
+ Dir.glob("#{@shared.export_path}/**/*", File::FNM_DOTMATCH).reject { |f| f =~ /.*\/\.{1,2}$/ }
+ end
end
end
end
diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb
index 7e14a566696..fee1a127fd7 100644
--- a/lib/gitlab/url_blocker.rb
+++ b/lib/gitlab/url_blocker.rb
@@ -19,6 +19,8 @@ module Gitlab
return false if internal?(uri)
return true if blocked_port?(uri.port)
+ return true if blocked_user_or_hostname?(uri.user)
+ return true if blocked_user_or_hostname?(uri.hostname)
server_ips = Resolv.getaddresses(uri.hostname)
return true if (blocked_ips & server_ips).any?
@@ -37,6 +39,12 @@ module Gitlab
port < 1024 && !VALID_PORTS.include?(port)
end
+ def blocked_user_or_hostname?(value)
+ return false if value.blank?
+
+ value !~ /\A\p{Alnum}/
+ end
+
def internal?(uri)
internal_web?(uri) || internal_shell?(uri)
end
diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab
index f25e66d54c8..54f51d9d633 100644
--- a/lib/support/nginx/gitlab
+++ b/lib/support/nginx/gitlab
@@ -25,6 +25,39 @@ map $http_upgrade $connection_upgrade_gitlab {
'' close;
}
+## NGINX 'combined' log format with filtered query strings
+log_format gitlab_access $remote_addr - $remote_user [$time_local] "$request_method $gitlab_filtered_request_uri $server_protocol" $status $body_bytes_sent "$gitlab_filtered_http_referer" "$http_user_agent";
+
+## Remove private_token from the request URI
+# In: /foo?private_token=unfiltered&authenticity_token=unfiltered&rss_token=unfiltered&...
+# Out: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&rss_token=unfiltered&...
+map $request_uri $gitlab_temp_request_uri_1 {
+ default $request_uri;
+ ~(?i)^(?<start>.*)(?<temp>[\?&]private[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
+}
+
+## Remove authenticity_token from the request URI
+# In: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&rss_token=unfiltered&...
+# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=unfiltered&...
+map $gitlab_temp_request_uri_1 $gitlab_temp_request_uri_2 {
+ default $gitlab_temp_request_uri_1;
+ ~(?i)^(?<start>.*)(?<temp>[\?&]authenticity[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
+}
+
+## Remove rss_token from the request URI
+# In: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=unfiltered&...
+# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=[FILTERED]&...
+map $gitlab_temp_request_uri_2 $gitlab_filtered_request_uri {
+ default $gitlab_temp_request_uri_2;
+ ~(?i)^(?<start>.*)(?<temp>[\?&]rss[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
+}
+
+## A version of the referer without the query string
+map $http_referer $gitlab_filtered_http_referer {
+ default $http_referer;
+ ~^(?<temp>.*)\? $temp;
+}
+
## Normal HTTP host
server {
## Either remove "default_server" from the listen line below,
@@ -46,7 +79,7 @@ server {
# set_real_ip_from YOUR_TRUSTED_ADDRESS; ## Replace this with something like 192.168.1.0/24
## Individual nginx logs for this GitLab vhost
- access_log /var/log/nginx/gitlab_access.log;
+ access_log /var/log/nginx/gitlab_access.log gitlab_access;
error_log /var/log/nginx/gitlab_error.log;
location / {
diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl
index 2b40da18bab..ed8131ef24f 100644
--- a/lib/support/nginx/gitlab-ssl
+++ b/lib/support/nginx/gitlab-ssl
@@ -29,6 +29,41 @@ map $http_upgrade $connection_upgrade_gitlab_ssl {
'' close;
}
+
+## NGINX 'combined' log format with filtered query strings
+log_format gitlab_ssl_access $remote_addr - $remote_user [$time_local] "$request_method $gitlab_ssl_filtered_request_uri $server_protocol" $status $body_bytes_sent "$gitlab_ssl_filtered_http_referer" "$http_user_agent";
+
+## Remove private_token from the request URI
+# In: /foo?private_token=unfiltered&authenticity_token=unfiltered&rss_token=unfiltered&...
+# Out: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&rss_token=unfiltered&...
+map $request_uri $gitlab_ssl_temp_request_uri_1 {
+ default $request_uri;
+ ~(?i)^(?<start>.*)(?<temp>[\?&]private[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
+}
+
+## Remove authenticity_token from the request URI
+# In: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&rss_token=unfiltered&...
+# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=unfiltered&...
+map $gitlab_ssl_temp_request_uri_1 $gitlab_ssl_temp_request_uri_2 {
+ default $gitlab_ssl_temp_request_uri_1;
+ ~(?i)^(?<start>.*)(?<temp>[\?&]authenticity[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
+}
+
+## Remove rss_token from the request URI
+# In: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=unfiltered&...
+# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=[FILTERED]&...
+map $gitlab_ssl_temp_request_uri_2 $gitlab_ssl_filtered_request_uri {
+ default $gitlab_ssl_temp_request_uri_2;
+ ~(?i)^(?<start>.*)(?<temp>[\?&]rss[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
+}
+
+## A version of the referer without the query string
+map $http_referer $gitlab_ssl_filtered_http_referer {
+ default $http_referer;
+ ~^(?<temp>.*)\? $temp;
+}
+
+
## Redirects all HTTP traffic to the HTTPS host
server {
## Either remove "default_server" from the listen line below,
@@ -40,7 +75,7 @@ server {
server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com
server_tokens off; ## Don't show the nginx version number, a security best practice
return 301 https://$http_host$request_uri;
- access_log /var/log/nginx/gitlab_access.log;
+ access_log /var/log/nginx/gitlab_access.log gitlab_ssl_access;
error_log /var/log/nginx/gitlab_error.log;
}
@@ -93,7 +128,7 @@ server {
# set_real_ip_from YOUR_TRUSTED_ADDRESS; ## Replace this with something like 192.168.1.0/24
## Individual nginx logs for this GitLab vhost
- access_log /var/log/nginx/gitlab_access.log;
+ access_log /var/log/nginx/gitlab_access.log gitlab_ssl_access;
error_log /var/log/nginx/gitlab_error.log;
location / {
diff --git a/locale/fr/gitlab.po b/locale/fr/gitlab.po
index c490933c6d4..90e2462039c 100644
--- a/locale/fr/gitlab.po
+++ b/locale/fr/gitlab.po
@@ -417,7 +417,7 @@ msgstr[0] "Fourche"
msgstr[1] "Fourches"
msgid "ForkedFromProjectPath|Forked from"
-msgstr "Fouché depuis"
+msgstr "Fourché depuis"
msgid "From issue creation until deploy to production"
msgstr "Depuis la création de l'incident jusqu'au déploiement en production"
diff --git a/spec/bin/changelog_spec.rb b/spec/bin/changelog_spec.rb
index 91aff0db7cc..6d8b9865dcb 100644
--- a/spec/bin/changelog_spec.rb
+++ b/spec/bin/changelog_spec.rb
@@ -4,56 +4,90 @@ load File.expand_path('../../bin/changelog', __dir__)
describe 'bin/changelog' do
describe ChangelogOptionParser do
- it 'parses --ammend' do
- options = described_class.parse(%w[foo bar --amend])
+ describe '.parse' do
+ it 'parses --amend' do
+ options = described_class.parse(%w[foo bar --amend])
- expect(options.amend).to eq true
- end
+ expect(options.amend).to eq true
+ end
- it 'parses --force and -f' do
- %w[--force -f].each do |flag|
- options = described_class.parse(%W[foo #{flag} bar])
+ it 'parses --force and -f' do
+ %w[--force -f].each do |flag|
+ options = described_class.parse(%W[foo #{flag} bar])
- expect(options.force).to eq true
+ expect(options.force).to eq true
+ end
end
- end
- it 'parses --merge-request and -m' do
- %w[--merge-request -m].each do |flag|
- options = described_class.parse(%W[foo #{flag} 1234 bar])
+ it 'parses --merge-request and -m' do
+ %w[--merge-request -m].each do |flag|
+ options = described_class.parse(%W[foo #{flag} 1234 bar])
- expect(options.merge_request).to eq 1234
+ expect(options.merge_request).to eq 1234
+ end
end
- end
- it 'parses --dry-run and -n' do
- %w[--dry-run -n].each do |flag|
- options = described_class.parse(%W[foo #{flag} bar])
+ it 'parses --dry-run and -n' do
+ %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 and -u' do
- allow(described_class).to receive(:git_user_name).and_return('Jane Doe')
+ it 'parses --git-username and -u' do
+ allow(described_class).to receive(:git_user_name).and_return('Jane Doe')
- %w[--git-username -u].each do |flag|
- options = described_class.parse(%W[foo #{flag} 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 --type and -t' do
+ %w[--type -t].each do |flag|
+ options = described_class.parse(%W[foo #{flag} security])
+
+ expect(options.type).to eq 'security'
+ end
end
- end
- it 'parses -h' do
- expect do
- expect { described_class.parse(%w[foo -h bar]) }.to output.to_stdout
- end.to raise_error(SystemExit)
+ it 'parses -h' do
+ expect do
+ expect { described_class.parse(%w[foo -h bar]) }.to output.to_stdout
+ end.to raise_error(SystemExit)
+ end
+
+ it 'assigns title' do
+ options = described_class.parse(%W[foo -m 1 bar\n -u baz\r\n --amend])
+
+ expect(options.title).to eq 'foo bar baz'
+ end
end
- it 'assigns title' do
- options = described_class.parse(%W[foo -m 1 bar\n -u baz\r\n --amend])
+ describe '.read_type' do
+ let(:type) { '1' }
- expect(options.title).to eq 'foo bar baz'
+ it 'reads type from $stdin' do
+ expect($stdin).to receive(:getc).and_return(type)
+ expect do
+ expect(described_class.read_type).to eq('added')
+ end.to output.to_stdout
+ end
+
+ context 'invalid type given' do
+ let(:type) { '99' }
+
+ it 'shows error message and exits the program' do
+ allow($stdin).to receive(:getc).and_return(type)
+ expect do
+ expect do
+ expect { described_class.read_type }.to raise_error(SystemExit)
+ end.to output("Invalid category index, please select an index between 1 and 7\n").to_stderr
+ end.to output.to_stdout
+ end
+ end
end
end
end
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index 3c396e36b24..379e3ce690f 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -1,8 +1,8 @@
require 'spec_helper'
describe AutocompleteController do
- let!(:project) { create(:project) }
- let!(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:user) { project.owner }
context 'GET users' do
let!(:user2) { create(:user) }
@@ -11,7 +11,6 @@ describe AutocompleteController do
context 'project members' do
before do
sign_in(user)
- project.add_master(user)
end
describe 'GET #users with project ID' do
@@ -19,11 +18,11 @@ describe AutocompleteController do
get(:users, project_id: project.id)
end
- let(:body) { JSON.parse(response.body) }
-
- it { expect(body).to be_kind_of(Array) }
- it { expect(body.size).to eq 2 }
- it { expect(body.map { |u| u["username"] }).to include(user.username) }
+ it 'returns the project members' do
+ expect(json_response).to be_kind_of(Array)
+ expect(json_response.size).to eq(1)
+ expect(json_response.map { |u| u["username"] }).to include(user.username)
+ end
end
describe 'GET #users with unknown project' do
@@ -39,20 +38,20 @@ describe AutocompleteController do
let(:group) { create(:group) }
before do
- sign_in(user)
group.add_owner(user)
+ sign_in(user)
end
- let(:body) { JSON.parse(response.body) }
-
describe 'GET #users with group ID' do
before do
get(:users, group_id: group.id)
end
- it { expect(body).to be_kind_of(Array) }
- it { expect(body.size).to eq 1 }
- it { expect(body.first["username"]).to eq user.username }
+ it 'returns the group members' do
+ expect(json_response).to be_kind_of(Array)
+ expect(json_response.size).to eq(1)
+ expect(json_response.first["username"]).to eq user.username
+ end
end
describe 'GET #users with unknown group ID' do
@@ -65,23 +64,22 @@ describe AutocompleteController do
end
context 'non-member login for public project' do
- let!(:project) { create(:project, :public) }
+ let(:project) { create(:project, :public) }
before do
sign_in(non_member)
- project.add_master(user)
end
- let(:body) { JSON.parse(response.body) }
-
describe 'GET #users with project ID' do
before do
get(:users, project_id: project.id, current_user: true)
end
- it { expect(body).to be_kind_of(Array) }
- it { expect(body.size).to eq 3 }
- it { expect(body.map { |u| u['username'] }).to include(user.username, non_member.username) }
+ it 'returns the project members and non-members' do
+ expect(json_response).to be_kind_of(Array)
+ expect(json_response.size).to eq(2)
+ expect(json_response.map { |u| u['username'] }).to include(user.username, non_member.username)
+ end
end
end
@@ -91,10 +89,8 @@ describe AutocompleteController do
get(:users)
end
- let(:body) { JSON.parse(response.body) }
-
- it { expect(body).to be_kind_of(Array) }
- it { expect(body.size).to eq User.count }
+ it { expect(json_response).to be_kind_of(Array) }
+ it { expect(json_response.size).to eq User.count }
end
context 'user order' do
@@ -106,7 +102,7 @@ describe AutocompleteController do
sign_in(user)
get(:users, search: 'user')
- response_usernames = JSON.parse(response.body).map { |user| user['username'] }
+ response_usernames = json_response.map { |user| user['username'] }
expect(response_usernames.take(3)).to match_array([user.username, reported_user.username, user1.username])
end
@@ -120,15 +116,12 @@ describe AutocompleteController do
get(:users, per_page: per_page)
end
- let(:body) { JSON.parse(response.body) }
-
- it { expect(body).to be_kind_of(Array) }
- it { expect(body.size).to eq per_page }
+ it { expect(json_response).to be_kind_of(Array) }
+ it { expect(json_response.size).to eq(per_page) }
end
context 'unauthenticated user' do
let(:public_project) { create(:project, :public) }
- let(:body) { JSON.parse(response.body) }
describe 'GET #users with public project' do
before do
@@ -136,8 +129,8 @@ describe AutocompleteController do
get(:users, project_id: public_project.id)
end
- it { expect(body).to be_kind_of(Array) }
- it { expect(body.size).to eq 2 }
+ it { expect(json_response).to be_kind_of(Array) }
+ it { expect(json_response.size).to eq 2 }
end
describe 'GET #users with project' do
@@ -170,8 +163,8 @@ describe AutocompleteController do
get(:users)
end
- it { expect(body).to be_kind_of(Array) }
- it { expect(body.size).to eq 0 }
+ it { expect(json_response).to be_kind_of(Array) }
+ it { expect(json_response).to be_empty }
end
describe 'GET #users with todo filter' do
@@ -179,14 +172,12 @@ describe AutocompleteController do
get :users, todo_filter: true
expect(response.status).to eq 200
- expect(body).to be_kind_of(Array)
+ expect(json_response).to be_kind_of(Array)
end
end
end
context 'author of issuable included' do
- let(:body) { JSON.parse(response.body) }
-
context 'authenticated' do
before do
sign_in(user)
@@ -195,13 +186,13 @@ describe AutocompleteController do
it 'includes the author' do
get(:users, author_id: non_member.id)
- expect(body.first["username"]).to eq non_member.username
+ expect(json_response.first["username"]).to eq non_member.username
end
it 'rejects non existent user ids' do
get(:users, author_id: 99999)
- expect(body.collect { |u| u['id'] }).not_to include(99999)
+ expect(json_response.collect { |u| u['id'] }).not_to include(99999)
end
end
@@ -209,7 +200,7 @@ describe AutocompleteController do
it 'returns empty result' do
get(:users, author_id: non_member.id)
- expect(body).to be_empty
+ expect(json_response).to be_empty
end
end
end
@@ -222,10 +213,9 @@ describe AutocompleteController do
it 'skips the user IDs passed' do
get(:users, skip_users: [user, user2].map(&:id))
- other_user_ids = [non_member, project.owner, project.creator].map(&:id)
- response_user_ids = JSON.parse(response.body).map { |user| user['id'] }
+ response_user_ids = json_response.map { |user| user['id'] }
- expect(response_user_ids).to contain_exactly(*other_user_ids)
+ expect(response_user_ids).to contain_exactly(non_member.id)
end
end
end
@@ -249,17 +239,15 @@ describe AutocompleteController do
get(:projects, project_id: project.id)
end
- let(:body) { JSON.parse(response.body) }
+ it 'returns projects' do
+ expect(json_response).to be_kind_of(Array)
+ expect(json_response.size).to eq(2)
- it do
- expect(body).to be_kind_of(Array)
- expect(body.size).to eq 2
+ expect(json_response.first['id']).to eq(0)
+ expect(json_response.first['name_with_namespace']).to eq 'No project'
- expect(body.first['id']).to eq 0
- expect(body.first['name_with_namespace']).to eq 'No project'
-
- expect(body.last['id']).to eq authorized_project.id
- expect(body.last['name_with_namespace']).to eq authorized_project.name_with_namespace
+ expect(json_response.last['id']).to eq authorized_project.id
+ expect(json_response.last['name_with_namespace']).to eq authorized_project.name_with_namespace
end
end
end
@@ -275,14 +263,12 @@ describe AutocompleteController do
get(:projects, project_id: project.id, search: 'rugged')
end
- let(:body) { JSON.parse(response.body) }
-
- it do
- expect(body).to be_kind_of(Array)
- expect(body.size).to eq 2
+ it 'returns projects' do
+ expect(json_response).to be_kind_of(Array)
+ expect(json_response.size).to eq(2)
- expect(body.last['id']).to eq authorized_search_project.id
- expect(body.last['name_with_namespace']).to eq authorized_search_project.name_with_namespace
+ expect(json_response.last['id']).to eq authorized_search_project.id
+ expect(json_response.last['name_with_namespace']).to eq authorized_search_project.name_with_namespace
end
end
end
@@ -304,11 +290,9 @@ describe AutocompleteController do
get(:projects, project_id: project.id)
end
- let(:body) { JSON.parse(response.body) }
-
- it do
- expect(body).to be_kind_of(Array)
- expect(body.size).to eq 3 # Of a total of 4
+ it 'returns projects' do
+ expect(json_response).to be_kind_of(Array)
+ expect(json_response.size).to eq 3 # Of a total of 4
end
end
end
@@ -328,11 +312,9 @@ describe AutocompleteController do
get(:projects, project_id: project.id, offset_id: authorized_project.id)
end
- let(:body) { JSON.parse(response.body) }
-
- it do
- expect(body.detect { |item| item['id'] == 0 }).to be_nil # 'No project' is not there
- expect(body.detect { |item| item['id'] == authorized_project.id }).to be_nil # Offset project is not there either
+ it 'returns "No project"' do
+ expect(json_response.detect { |item| item['id'] == 0 }).to be_nil # 'No project' is not there
+ expect(json_response.detect { |item| item['id'] == authorized_project.id }).to be_nil # Offset project is not there either
end
end
end
@@ -349,13 +331,10 @@ describe AutocompleteController do
get(:projects, project_id: project.id)
end
- let(:body) { JSON.parse(response.body) }
-
- it do
- expect(body).to be_kind_of(Array)
- expect(body.size).to eq 1 # 'No project'
-
- expect(body.first['id']).to eq 0
+ it 'returns a single "No project"' do
+ expect(json_response).to be_kind_of(Array)
+ expect(json_response.size).to eq(1) # 'No project'
+ expect(json_response.first['id']).to eq 0
end
end
end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 3f8e7030b1c..4a2034b31b3 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -8,12 +8,47 @@ FactoryGirl.define do
factory :project, class: 'Project' do
sequence(:name) { |n| "project#{n}" }
path { name.downcase.gsub(/\s/, '_') }
- namespace
- creator
-
# Behaves differently to nil due to cache_has_external_issue_tracker
has_external_issue_tracker false
+ # Associations
+ namespace
+ creator { group ? create(:user) : namespace&.owner }
+
+ # Nest Project Feature attributes
+ transient do
+ wiki_access_level ProjectFeature::ENABLED
+ builds_access_level ProjectFeature::ENABLED
+ snippets_access_level ProjectFeature::ENABLED
+ issues_access_level ProjectFeature::ENABLED
+ merge_requests_access_level ProjectFeature::ENABLED
+ repository_access_level ProjectFeature::ENABLED
+ end
+
+ after(:create) do |project, evaluator|
+ # Builds and MRs can't have higher visibility level than repository access level.
+ builds_access_level = [evaluator.builds_access_level, evaluator.repository_access_level].min
+ merge_requests_access_level = [evaluator.merge_requests_access_level, evaluator.repository_access_level].min
+
+ project.project_feature.update_columns(
+ wiki_access_level: evaluator.wiki_access_level,
+ builds_access_level: builds_access_level,
+ snippets_access_level: evaluator.snippets_access_level,
+ issues_access_level: evaluator.issues_access_level,
+ merge_requests_access_level: merge_requests_access_level,
+ repository_access_level: evaluator.repository_access_level)
+
+ # Normally the class Projects::CreateService is used for creating
+ # projects, and this class takes care of making sure the owner and current
+ # user have access to the project. Our specs don't use said service class,
+ # thus we must manually refresh things here.
+ unless project.group || project.pending_delete
+ project.add_master(project.owner)
+ end
+
+ project.group&.refresh_members_authorized_projects
+ end
+
trait :public do
visibility_level Gitlab::VisibilityLevel::PUBLIC
end
@@ -67,30 +102,28 @@ FactoryGirl.define do
test_repo
transient do
- create_template nil
+ create_templates nil
end
after :create do |project, evaluator|
- if evaluator.create_template
- args = evaluator.create_template
-
- project.add_user(args[:user], args[:access])
+ if evaluator.create_templates
+ templates_path = "#{evaluator.create_templates}_templates"
project.repository.create_file(
- args[:user],
- ".gitlab/#{args[:path]}/bug.md",
+ project.creator,
+ ".gitlab/#{templates_path}/bug.md",
'something valid',
message: 'test 3',
branch_name: 'master')
project.repository.create_file(
- args[:user],
- ".gitlab/#{args[:path]}/template_test.md",
+ project.creator,
+ ".gitlab/#{templates_path}/template_test.md",
'template_test',
message: 'test 1',
branch_name: 'master')
project.repository.create_file(
- args[:user],
- ".gitlab/#{args[:path]}/feature_proposal.md",
+ project.creator,
+ ".gitlab/#{templates_path}/feature_proposal.md",
'feature_proposal',
message: 'test 2',
branch_name: 'master')
@@ -142,44 +175,6 @@ FactoryGirl.define do
trait(:repository_enabled) { repository_access_level ProjectFeature::ENABLED }
trait(:repository_disabled) { repository_access_level ProjectFeature::DISABLED }
trait(:repository_private) { repository_access_level ProjectFeature::PRIVATE }
-
- # Nest Project Feature attributes
- transient do
- wiki_access_level ProjectFeature::ENABLED
- builds_access_level ProjectFeature::ENABLED
- snippets_access_level ProjectFeature::ENABLED
- issues_access_level ProjectFeature::ENABLED
- merge_requests_access_level ProjectFeature::ENABLED
- repository_access_level ProjectFeature::ENABLED
- end
-
- after(:create) do |project, evaluator|
- # Builds and MRs can't have higher visibility level than repository access level.
- builds_access_level = [evaluator.builds_access_level, evaluator.repository_access_level].min
- merge_requests_access_level = [evaluator.merge_requests_access_level, evaluator.repository_access_level].min
-
- project.project_feature
- .update_attributes!(
- wiki_access_level: evaluator.wiki_access_level,
- builds_access_level: builds_access_level,
- snippets_access_level: evaluator.snippets_access_level,
- issues_access_level: evaluator.issues_access_level,
- merge_requests_access_level: merge_requests_access_level,
- repository_access_level: evaluator.repository_access_level
- )
-
- # Normally the class Projects::CreateService is used for creating
- # projects, and this class takes care of making sure the owner and current
- # user have access to the project. Our specs don't use said service class,
- # thus we must manually refresh things here.
- owner = project.owner
-
- if owner && owner.is_a?(User) && !project.pending_delete
- project.members.create!(user: owner, access_level: Gitlab::Access::MASTER)
- end
-
- project.group&.refresh_members_authorized_projects
- end
end
# Project with empty repository
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
index e60fe713bc3..4000cd085b7 100644
--- a/spec/factories/users.rb
+++ b/spec/factories/users.rb
@@ -1,5 +1,5 @@
FactoryGirl.define do
- factory :user, aliases: [:author, :assignee, :recipient, :owner, :creator, :resource_owner] do
+ factory :user, aliases: [:author, :assignee, :recipient, :owner, :resource_owner] do
email { generate(:email) }
name { generate(:name) }
username { generate(:username) }
@@ -8,6 +8,10 @@ FactoryGirl.define do
confirmation_token { nil }
can_create_group true
+ after(:stub) do |user|
+ user.notification_email = user.email
+ end
+
before(:create) do |user|
user.ensure_rss_token
end
diff --git a/spec/javascripts/breakpoints_spec.js b/spec/javascripts/breakpoints_spec.js
new file mode 100644
index 00000000000..b1b5d36c1fb
--- /dev/null
+++ b/spec/javascripts/breakpoints_spec.js
@@ -0,0 +1,15 @@
+import bp, {
+ breakpoints,
+} from '~/breakpoints';
+
+describe('breakpoints', () => {
+ Object.keys(breakpoints).forEach((key) => {
+ const size = breakpoints[key];
+
+ it(`returns ${key} when larger than ${size}`, () => {
+ spyOn(bp, 'windowWidth').and.returnValue(size + 10);
+
+ expect(bp.getBreakpointSize()).toBe(key);
+ });
+ });
+});
diff --git a/spec/javascripts/fly_out_nav_spec.js b/spec/javascripts/fly_out_nav_spec.js
index d7b7acaa3f4..e44d874ad2b 100644
--- a/spec/javascripts/fly_out_nav_spec.js
+++ b/spec/javascripts/fly_out_nav_spec.js
@@ -1,4 +1,3 @@
-/* global bp */
import Cookies from 'js-cookie';
import {
calculateTop,
@@ -7,6 +6,7 @@ import {
canShowSubItems,
canShowActiveSubItems,
} from '~/fly_out_nav';
+import bp from '~/breakpoints';
describe('Fly out sidebar navigation', () => {
let el;
diff --git a/spec/lib/gitlab/import_export/file_importer_spec.rb b/spec/lib/gitlab/import_export/file_importer_spec.rb
index 690c7625c52..162b776e107 100644
--- a/spec/lib/gitlab/import_export/file_importer_spec.rb
+++ b/spec/lib/gitlab/import_export/file_importer_spec.rb
@@ -5,6 +5,7 @@ describe Gitlab::ImportExport::FileImporter do
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(:hidden_symlink_file) { "#{shared.export_path}/.hidden" }
let(:subfolder_symlink_file) { "#{shared.export_path}/subfolder/invalid.json" }
before do
@@ -25,6 +26,10 @@ describe Gitlab::ImportExport::FileImporter do
expect(File.exist?(symlink_file)).to be false
end
+ it 'removes hidden symlinks in root folder' do
+ expect(File.exist?(hidden_symlink_file)).to be false
+ end
+
it 'removes symlinks in subfolders' do
expect(File.exist?(subfolder_symlink_file)).to be false
end
diff --git a/spec/lib/gitlab/template/issue_template_spec.rb b/spec/lib/gitlab/template/issue_template_spec.rb
index 6e0b1075a89..7098499f996 100644
--- a/spec/lib/gitlab/template/issue_template_spec.rb
+++ b/spec/lib/gitlab/template/issue_template_spec.rb
@@ -1,41 +1,28 @@
require 'spec_helper'
describe Gitlab::Template::IssueTemplate do
- subject { described_class }
-
- let(:user) { create(:user) }
-
- let(:project) do
- create(:project,
- :repository,
- create_template: {
- user: user,
- access: Gitlab::Access::MASTER,
- path: 'issue_templates'
- })
- end
+ let(:project) { create(:project, :repository, create_templates: :issue) }
describe '.all' do
it 'strips the md suffix' do
- expect(subject.all(project).first.name).not_to end_with('.issue_template')
+ expect(described_class.all(project).first.name).not_to end_with('.issue_template')
end
it 'combines the globals and rest' do
- all = subject.all(project).map(&:name)
+ all = described_class.all(project).map(&:name)
expect(all).to include('bug')
expect(all).to include('feature_proposal')
- expect(all).to include('template_test')
end
end
describe '.find' do
it 'returns nil if the file does not exist' do
- expect { subject.find('mepmep-yadida', project) }.to raise_error(Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError)
+ expect { described_class.find('mepmep-yadida', project) }.to raise_error(Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError)
end
it 'returns the issue object of a valid file' do
- ruby = subject.find('bug', project)
+ ruby = described_class.find('bug', project)
expect(ruby).to be_a described_class
expect(ruby.name).to eq('bug')
@@ -44,21 +31,17 @@ describe Gitlab::Template::IssueTemplate do
describe '.by_category' do
it 'return array of templates' do
- all = subject.by_category('', project).map(&:name)
+ all = described_class.by_category('', project).map(&:name)
expect(all).to include('bug')
expect(all).to include('feature_proposal')
- expect(all).to include('template_test')
end
context 'when repo is bare or empty' do
let(:empty_project) { create(:project) }
- before do
- empty_project.add_user(user, Gitlab::Access::MASTER)
- end
-
it "returns empty array" do
- templates = subject.by_category('', empty_project)
+ templates = described_class.by_category('', empty_project)
+
expect(templates).to be_empty
end
end
@@ -66,26 +49,23 @@ describe Gitlab::Template::IssueTemplate do
describe '#content' do
it 'loads the full file' do
- issue_template = subject.new('.gitlab/issue_templates/bug.md', project)
+ issue_template = described_class.new('.gitlab/issue_templates/bug.md', project)
expect(issue_template.name).to eq 'bug'
expect(issue_template.content).to eq('something valid')
end
it 'raises error when file is not found' do
- issue_template = subject.new('.gitlab/issue_templates/bugnot.md', project)
+ issue_template = described_class.new('.gitlab/issue_templates/bugnot.md', project)
expect { issue_template.content }.to raise_error(Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError)
end
context "when repo is empty" do
let(:empty_project) { create(:project) }
- before do
- empty_project.add_user(user, Gitlab::Access::MASTER)
- end
-
it "raises file not found" do
- issue_template = subject.new('.gitlab/issue_templates/not_existent.md', empty_project)
+ issue_template = described_class.new('.gitlab/issue_templates/not_existent.md', empty_project)
+
expect { issue_template.content }.to raise_error(Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError)
end
end
diff --git a/spec/lib/gitlab/template/merge_request_template_spec.rb b/spec/lib/gitlab/template/merge_request_template_spec.rb
index b952274cd24..bd7ff64aa8a 100644
--- a/spec/lib/gitlab/template/merge_request_template_spec.rb
+++ b/spec/lib/gitlab/template/merge_request_template_spec.rb
@@ -1,41 +1,28 @@
require 'spec_helper'
describe Gitlab::Template::MergeRequestTemplate do
- subject { described_class }
-
- let(:user) { create(:user) }
-
- let(:project) do
- create(:project,
- :repository,
- create_template: {
- user: user,
- access: Gitlab::Access::MASTER,
- path: 'merge_request_templates'
- })
- end
+ let(:project) { create(:project, :repository, create_templates: :merge_request) }
describe '.all' do
it 'strips the md suffix' do
- expect(subject.all(project).first.name).not_to end_with('.issue_template')
+ expect(described_class.all(project).first.name).not_to end_with('.issue_template')
end
it 'combines the globals and rest' do
- all = subject.all(project).map(&:name)
+ all = described_class.all(project).map(&:name)
expect(all).to include('bug')
expect(all).to include('feature_proposal')
- expect(all).to include('template_test')
end
end
describe '.find' do
it 'returns nil if the file does not exist' do
- expect { subject.find('mepmep-yadida', project) }.to raise_error(Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError)
+ expect { described_class.find('mepmep-yadida', project) }.to raise_error(Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError)
end
it 'returns the merge request object of a valid file' do
- ruby = subject.find('bug', project)
+ ruby = described_class.find('bug', project)
expect(ruby).to be_a described_class
expect(ruby.name).to eq('bug')
@@ -44,21 +31,17 @@ describe Gitlab::Template::MergeRequestTemplate do
describe '.by_category' do
it 'return array of templates' do
- all = subject.by_category('', project).map(&:name)
+ all = described_class.by_category('', project).map(&:name)
expect(all).to include('bug')
expect(all).to include('feature_proposal')
- expect(all).to include('template_test')
end
context 'when repo is bare or empty' do
let(:empty_project) { create(:project) }
- before do
- empty_project.add_user(user, Gitlab::Access::MASTER)
- end
-
it "returns empty array" do
- templates = subject.by_category('', empty_project)
+ templates = described_class.by_category('', empty_project)
+
expect(templates).to be_empty
end
end
@@ -66,26 +49,23 @@ describe Gitlab::Template::MergeRequestTemplate do
describe '#content' do
it 'loads the full file' do
- issue_template = subject.new('.gitlab/merge_request_templates/bug.md', project)
+ issue_template = described_class.new('.gitlab/merge_request_templates/bug.md', project)
expect(issue_template.name).to eq 'bug'
expect(issue_template.content).to eq('something valid')
end
it 'raises error when file is not found' do
- issue_template = subject.new('.gitlab/merge_request_templates/bugnot.md', project)
+ issue_template = described_class.new('.gitlab/merge_request_templates/bugnot.md', project)
expect { issue_template.content }.to raise_error(Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError)
end
context "when repo is empty" do
let(:empty_project) { create(:project) }
- before do
- empty_project.add_user(user, Gitlab::Access::MASTER)
- end
-
it "raises file not found" do
- issue_template = subject.new('.gitlab/merge_request_templates/not_existent.md', empty_project)
+ issue_template = described_class.new('.gitlab/merge_request_templates/not_existent.md', empty_project)
+
expect { issue_template.content }.to raise_error(Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError)
end
end
diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb
index f5b4882815f..f18823b61ef 100644
--- a/spec/lib/gitlab/url_blocker_spec.rb
+++ b/spec/lib/gitlab/url_blocker_spec.rb
@@ -20,6 +20,34 @@ describe Gitlab::UrlBlocker do
expect(described_class.blocked_url?('https://gitlab.com:25/foo/foo.git')).to be true
end
+ it 'returns true for a non-alphanumeric hostname' do
+ stub_resolv
+
+ aggregate_failures do
+ expect(described_class).to be_blocked_url('ssh://-oProxyCommand=whoami/a')
+
+ # The leading character here is a Unicode "soft hyphen"
+ expect(described_class).to be_blocked_url('ssh://­oProxyCommand=whoami/a')
+
+ # Unicode alphanumerics are allowed
+ expect(described_class).not_to be_blocked_url('ssh://ğitlab.com/a')
+ end
+ end
+
+ it 'returns true for a non-alphanumeric username' do
+ stub_resolv
+
+ aggregate_failures do
+ expect(described_class).to be_blocked_url('ssh://-oProxyCommand=whoami@example.com/a')
+
+ # The leading character here is a Unicode "soft hyphen"
+ expect(described_class).to be_blocked_url('ssh://­oProxyCommand=whoami@example.com/a')
+
+ # Unicode alphanumerics are allowed
+ expect(described_class).not_to be_blocked_url('ssh://ğitlab@example.com/a')
+ end
+ end
+
it 'returns true for invalid URL' do
expect(described_class.blocked_url?('http://:8080')).to be true
end
@@ -28,4 +56,10 @@ describe Gitlab::UrlBlocker do
expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git')).to be false
end
end
+
+ # Resolv does not support resolving UTF-8 domain names
+ # See https://bugs.ruby-lang.org/issues/4270
+ def stub_resolv
+ allow(Resolv).to receive(:getaddresses).and_return([])
+ end
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 9baac12821f..6cb27d16fe5 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -8,8 +8,8 @@ describe API::Projects do
let(:user2) { create(:user) }
let(:user3) { create(:user) }
let(:admin) { create(:admin) }
- let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
- let(:project2) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace) }
+ let(:project) { create(:project, namespace: user.namespace) }
+ let(:project2) { create(:project, path: 'project2', namespace: user.namespace) }
let(:snippet) { create(:project_snippet, :public, author: user, project: project, title: 'example') }
let(:project_member) { create(:project_member, :developer, user: user3, project: project) }
let(:user4) { create(:user) }