Commit 988c4510 authored by Nick Thomas's avatar Nick Thomas

Merge remote-tracking branch 'ce/master' into ce-to-ee

parents 388488fd 18188136
...@@ -244,7 +244,6 @@ gem 'gemojione', '~> 3.0' ...@@ -244,7 +244,6 @@ gem 'gemojione', '~> 3.0'
gem 'gon', '~> 6.1.0' gem 'gon', '~> 6.1.0'
gem 'jquery-atwho-rails', '~> 1.3.2' gem 'jquery-atwho-rails', '~> 1.3.2'
gem 'jquery-rails', '~> 4.1.0' gem 'jquery-rails', '~> 4.1.0'
gem 'jquery-ui-rails', '~> 5.0.0'
gem 'request_store', '~> 1.3' gem 'request_store', '~> 1.3'
gem 'select2-rails', '~> 3.5.9' gem 'select2-rails', '~> 3.5.9'
gem 'virtus', '~> 1.0.1' gem 'virtus', '~> 1.0.1'
......
...@@ -346,8 +346,11 @@ GEM ...@@ -346,8 +346,11 @@ GEM
grpc (1.1.2) grpc (1.1.2)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
googleauth (~> 0.5.1) googleauth (~> 0.5.1)
<<<<<<< HEAD
gssapi (1.2.0) gssapi (1.2.0)
ffi (>= 1.0.1) ffi (>= 1.0.1)
=======
>>>>>>> ce/master
haml (4.0.7) haml (4.0.7)
tilt tilt
haml_lint (0.21.0) haml_lint (0.21.0)
...@@ -398,8 +401,6 @@ GEM ...@@ -398,8 +401,6 @@ GEM
rails-dom-testing (>= 1, < 3) rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0) railties (>= 4.2.0)
thor (>= 0.14, < 2.0) thor (>= 0.14, < 2.0)
jquery-ui-rails (5.0.5)
railties (>= 3.2.16)
json (1.8.6) json (1.8.6)
json-schema (2.6.2) json-schema (2.6.2)
addressable (~> 2.3.8) addressable (~> 2.3.8)
...@@ -936,7 +937,6 @@ DEPENDENCIES ...@@ -936,7 +937,6 @@ DEPENDENCIES
jira-ruby (~> 1.1.2) jira-ruby (~> 1.1.2)
jquery-atwho-rails (~> 1.3.2) jquery-atwho-rails (~> 1.3.2)
jquery-rails (~> 4.1.0) jquery-rails (~> 4.1.0)
jquery-ui-rails (~> 5.0.0)
json-schema (~> 2.6.2) json-schema (~> 2.6.2)
jwt (~> 1.5.6) jwt (~> 1.5.6)
kaminari (~> 0.17.0) kaminari (~> 0.17.0)
......
...@@ -3,7 +3,7 @@ require('./issue_card_inner'); ...@@ -3,7 +3,7 @@ require('./issue_card_inner');
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
module.exports = { export default {
name: 'BoardsIssueCard', name: 'BoardsIssueCard',
template: ` template: `
<li class="card" <li class="card"
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
/* global Vue */ /* global Vue */
/* global Sortable */ /* global Sortable */
const boardCard = require('./board_card'); import boardNewIssue from './board_new_issue';
require('./board_new_issue'); import boardCard from './board_card';
(() => { (() => {
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
...@@ -15,7 +15,7 @@ require('./board_new_issue'); ...@@ -15,7 +15,7 @@ require('./board_new_issue');
template: '#js-board-list-template', template: '#js-board-list-template',
components: { components: {
boardCard, boardCard,
'board-new-issue': gl.issueBoards.BoardNewIssue boardNewIssue,
}, },
props: { props: {
disabled: Boolean, disabled: Boolean,
...@@ -81,6 +81,12 @@ require('./board_new_issue'); ...@@ -81,6 +81,12 @@ require('./board_new_issue');
}); });
} }
}, },
toggleForm() {
this.showIssueForm = !this.showIssueForm;
},
},
created() {
gl.IssueBoardsApp.$on(`hide-issue-form-${this.list.id}`, this.toggleForm);
}, },
mounted () { mounted () {
const options = gl.issueBoards.getBoardSortableDefaultOptions({ const options = gl.issueBoards.getBoardSortableDefaultOptions({
...@@ -115,6 +121,9 @@ require('./board_new_issue'); ...@@ -115,6 +121,9 @@ require('./board_new_issue');
this.loadNextPage(); this.loadNextPage();
} }
}; };
} },
beforeDestroy() {
gl.IssueBoardsApp.$off(`hide-issue-form-${this.list.id}`, this.toggleForm);
},
}); });
})(); })();
/* global ListIssue */
const Store = gl.issueBoards.BoardsStore;
export default {
name: 'BoardNewIssue',
props: {
list: Object,
},
data() {
return {
title: '',
error: false,
};
},
methods: {
submit(e) {
e.preventDefault();
if (this.title.trim() === '') return;
this.error = false;
const labels = this.list.label ? [this.list.label] : [];
const issue = new ListIssue({
title: this.title,
labels,
subscribed: true,
});
this.list.newIssue(issue)
.then(() => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions
$(this.$refs.submitButton).enable();
Store.detail.issue = issue;
Store.detail.list = this.list;
})
.catch(() => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions
$(this.$refs.submitButton).enable();
// Remove the issue
this.list.removeIssue(issue);
// Show error message
this.error = true;
});
this.cancel();
},
cancel() {
this.title = '';
gl.IssueBoardsApp.$emit(`hide-issue-form-${this.list.id}`);
},
},
mounted() {
this.$refs.input.focus();
},
template: `
<div class="card board-new-issue-form">
<form @submit="submit($event)">
<div class="flash-container"
v-if="error">
<div class="flash-alert">
An error occured. Please try again.
</div>
</div>
<label class="label-light"
:for="list.id + '-title'">
Title
</label>
<input class="form-control"
type="text"
v-model="title"
ref="input"
:id="list.id + '-title'" />
<div class="clearfix prepend-top-10">
<button class="btn btn-success pull-left"
type="submit"
:disabled="title === ''"
ref="submit-button">
Submit issue
</button>
<button class="btn btn-default pull-right"
type="button"
@click="cancel">
Cancel
</button>
</div>
</form>
</div>
`,
};
/* eslint-disable comma-dangle, no-unused-vars */
/* global Vue */
/* global ListIssue */
(() => {
const Store = gl.issueBoards.BoardsStore;
window.gl = window.gl || {};
gl.issueBoards.BoardNewIssue = Vue.extend({
props: {
list: Object,
},
data() {
return {
title: '',
error: false
};
},
methods: {
submit(e) {
e.preventDefault();
if (this.title.trim() === '') return;
this.error = false;
const labels = this.list.label ? [this.list.label] : [];
const issue = new ListIssue({
title: this.title,
labels,
subscribed: true
});
this.list.newIssue(issue)
.then((data) => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions
$(this.$refs.submitButton).enable();
Store.detail.issue = issue;
Store.detail.list = this.list;
})
.catch(() => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions
$(this.$refs.submitButton).enable();
// Remove the issue
this.list.removeIssue(issue);
// Show error message
this.error = true;
});
this.cancel();
},
cancel() {
this.title = '';
this.$parent.showIssueForm = false;
}
},
mounted() {
this.$refs.input.focus();
},
});
})();
...@@ -69,7 +69,9 @@ const PipelineStore = require('./pipelines_store'); ...@@ -69,7 +69,9 @@ const PipelineStore = require('./pipelines_store');
return pipelinesService.all() return pipelinesService.all()
.then(response => response.json()) .then(response => response.json())
.then((json) => { .then((json) => {
this.store.storePipelines(json); // depending of the endpoint the response can either bring a `pipelines` key or not.
const pipelines = json.pipelines || json;
this.store.storePipelines(pipelines);
this.isLoading = false; this.isLoading = false;
}) })
.catch(() => { .catch(() => {
......
...@@ -79,12 +79,12 @@ require('./comparison_pane'); ...@@ -79,12 +79,12 @@ require('./comparison_pane');
<div class='help-button pull-right' <div class='help-button pull-right'
v-if='!showHelpState' v-if='!showHelpState'
@click='toggleHelpState(true)'> @click='toggleHelpState(true)'>
<i class='fa fa-question-circle'></i> <i class='fa fa-question-circle' aria-hidden='true'></i>
</div> </div>
<div class='close-help-button pull-right' <div class='close-help-button pull-right'
v-if='showHelpState' v-if='showHelpState'
@click='toggleHelpState(false)'> @click='toggleHelpState(false)'>
<i class='fa fa-close'></i> <i class='fa fa-close' aria-hidden='true'></i>
</div> </div>
</div> </div>
<div class='time-tracking-content hide-collapsed'> <div class='time-tracking-content hide-collapsed'>
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
this.onPickImageClick = this.onPickImageClick.bind(this); this.onPickImageClick = this.onPickImageClick.bind(this);
this.fileInput = $(input); this.fileInput = $(input);
this.modalCropImg = _.isString(this.modalCropImg) ? $(this.modalCropImg) : this.modalCropImg; this.modalCropImg = _.isString(this.modalCropImg) ? $(this.modalCropImg) : this.modalCropImg;
this.fileInput.attr('name', `${this.fileInput.attr('name')}-trigger`).attr('id', `this.fileInput.attr('id')-trigger`); this.fileInput.attr('name', `${this.fileInput.attr('name')}-trigger`).attr('id', `${this.fileInput.attr('id')}-trigger`);
this.exportWidth = exportWidth; this.exportWidth = exportWidth;
this.exportHeight = exportHeight; this.exportHeight = exportHeight;
this.cropBoxWidth = cropBoxWidth; this.cropBoxWidth = cropBoxWidth;
......
.calender-block { .calender-block {
padding-left: 0; padding-left: 0;
padding-right: 0; padding-right: 0;
border-top: 0;
direction: rtl; direction: rtl;
@media (min-width: $screen-sm-min) and (max-width: $screen-md-max) { @media (min-width: $screen-sm-min) and (max-width: $screen-md-max) {
......
...@@ -271,6 +271,7 @@ span.idiff { ...@@ -271,6 +271,7 @@ span.idiff {
font-size: 13px; font-size: 13px;
line-height: 28px; line-height: 28px;
display: inline-block; display: inline-block;
float: none;
} }
} }
} }
...@@ -21,6 +21,7 @@ $dark-highlight-color: $black; ...@@ -21,6 +21,7 @@ $dark-highlight-color: $black;
$dark-pre-hll-bg: #373b41; $dark-pre-hll-bg: #373b41;
$dark-hll-bg: #373b41; $dark-hll-bg: #373b41;
$dark-over-bg: #9f9ab5; $dark-over-bg: #9f9ab5;
$dark-expanded-bg: #3e3e3e;
$dark-c: #969896; $dark-c: #969896;
$dark-err: #c66; $dark-err: #c66;
$dark-k: #b294bb; $dark-k: #b294bb;
...@@ -155,6 +156,22 @@ $dark-il: #de935f; ...@@ -155,6 +156,22 @@ $dark-il: #de935f;
.line_content.match { .line_content.match {
@include dark-diff-match-line; @include dark-diff-match-line;
} }
&:not(.diff-expanded) + .diff-expanded,
&.diff-expanded + .line_holder:not(.diff-expanded) {
> .diff-line-num,
> .line_content {
border-top: 1px solid $black;
}
}
&.diff-expanded {
> .diff-line-num,
> .line_content {
background: $dark-expanded-bg;
border-color: $dark-expanded-bg;
}
}
} }
// highlight line via anchor // highlight line via anchor
......
...@@ -14,6 +14,7 @@ $monokai-line-empty-border: darken($monokai-line-empty-bg, 15%); ...@@ -14,6 +14,7 @@ $monokai-line-empty-border: darken($monokai-line-empty-bg, 15%);
$monokai-diff-border: #808080; $monokai-diff-border: #808080;
$monokai-highlight-bg: #ffe792; $monokai-highlight-bg: #ffe792;
$monokai-over-bg: #9f9ab5; $monokai-over-bg: #9f9ab5;
$monokai-expanded-bg: #3e3e3e;
$monokai-new-bg: rgba(166, 226, 46, 0.1); $monokai-new-bg: rgba(166, 226, 46, 0.1);
$monokai-new-idiff: rgba(166, 226, 46, 0.15); $monokai-new-idiff: rgba(166, 226, 46, 0.15);
...@@ -155,6 +156,22 @@ $monokai-gi: #a6e22e; ...@@ -155,6 +156,22 @@ $monokai-gi: #a6e22e;
.line_content.match { .line_content.match {
@include dark-diff-match-line; @include dark-diff-match-line;
} }
&:not(.diff-expanded) + .diff-expanded,
&.diff-expanded + .line_holder:not(.diff-expanded) {
> .diff-line-num,
> .line_content {
border-top: 1px solid $black;
}
}
&.diff-expanded {
> .diff-line-num,
> .line_content {
background: $monokai-expanded-bg;
border-color: $monokai-expanded-bg;
}
}
} }
// highlight line via anchor // highlight line via anchor
......
...@@ -18,6 +18,7 @@ $solarized-dark-line-color-old: #7a6c71; ...@@ -18,6 +18,7 @@ $solarized-dark-line-color-old: #7a6c71;
$solarized-dark-highlight: #094554; $solarized-dark-highlight: #094554;
$solarized-dark-hll-bg: #174652; $solarized-dark-hll-bg: #174652;
$solarized-dark-over-bg: #9f9ab5; $solarized-dark-over-bg: #9f9ab5;
$solarized-dark-expanded-bg: #010d10;
$solarized-dark-c: #586e75; $solarized-dark-c: #586e75;
$solarized-dark-err: #93a1a1; $solarized-dark-err: #93a1a1;
$solarized-dark-g: #93a1a1; $solarized-dark-g: #93a1a1;
...@@ -159,6 +160,22 @@ $solarized-dark-il: #2aa198; ...@@ -159,6 +160,22 @@ $solarized-dark-il: #2aa198;
.line_content.match { .line_content.match {
@include dark-diff-match-line; @include dark-diff-match-line;
} }
&:not(.diff-expanded) + .diff-expanded,
&.diff-expanded + .line_holder:not(.diff-expanded) {
> .diff-line-num,
> .line_content {
border-top: 1px solid $black;
}
}
&.diff-expanded {
> .diff-line-num,
> .line_content {
background: $solarized-dark-expanded-bg;
border-color: $solarized-dark-expanded-bg;
}
}
} }
// highlight line via anchor // highlight line via anchor
......
...@@ -19,6 +19,8 @@ $solarized-light-line-color-old: #ad9186; ...@@ -19,6 +19,8 @@ $solarized-light-line-color-old: #ad9186;
$solarized-light-highlight: #eee8d5; $solarized-light-highlight: #eee8d5;
$solarized-light-hll-bg: #ddd8c5; $solarized-light-hll-bg: #ddd8c5;
$solarized-light-over-bg: #ded7fc; $solarized-light-over-bg: #ded7fc;
$solarized-light-expanded-border: #d2cdbd;
$solarized-light-expanded-bg: #ece6d4;
$solarized-light-c: #93a1a1; $solarized-light-c: #93a1a1;
$solarized-light-err: #586e75; $solarized-light-err: #586e75;
$solarized-light-g: #586e75; $solarized-light-g: #586e75;
...@@ -166,6 +168,22 @@ $solarized-light-il: #2aa198; ...@@ -166,6 +168,22 @@ $solarized-light-il: #2aa198;
.line_content.match { .line_content.match {
@include matchLine; @include matchLine;
} }
&:not(.diff-expanded) + .diff-expanded,
&.diff-expanded + .line_holder:not(.diff-expanded) {
> .diff-line-num,
> .line_content {
border-top: 1px solid $solarized-light-expanded-border;
}
}
&.diff-expanded {
> .diff-line-num,
> .line_content {
background: $solarized-light-expanded-bg;
border-color: $solarized-light-expanded-bg;
}
}
} }
// highlight line via anchor // highlight line via anchor
......
...@@ -8,6 +8,8 @@ $white-highlight: #fafe3d; ...@@ -8,6 +8,8 @@ $white-highlight: #fafe3d;
$white-pre-hll-bg: #f8eec7; $white-pre-hll-bg: #f8eec7;
$white-hll-bg: #f8f8f8; $white-hll-bg: #f8f8f8;
$white-over-bg: #ded7fc; $white-over-bg: #ded7fc;
$white-expanded-border: #e0e0e0;
$white-expanded-bg: #f7f7f7;
$white-c: #998; $white-c: #998;
$white-err: #a61717; $white-err: #a61717;
$white-err-bg: #e3d2d2; $white-err-bg: #e3d2d2;
...@@ -140,6 +142,22 @@ $white-gc-bg: #eaf2f5; ...@@ -140,6 +142,22 @@ $white-gc-bg: #eaf2f5;
} }
} }
&:not(.diff-expanded) + .diff-expanded,
&.diff-expanded + .line_holder:not(.diff-expanded) {
> .diff-line-num,
> .line_content {
border-top: 1px solid $white-expanded-border;
}
}
&.diff-expanded {
> .diff-line-num,
> .line_content {
background: $white-expanded-bg;
border-color: $white-expanded-bg;
}
}
.line_content { .line_content {
&.old { &.old {
background-color: $line-removed; background-color: $line-removed;
......
...@@ -133,8 +133,13 @@ ...@@ -133,8 +133,13 @@
width: 35px; width: 35px;
font-weight: normal; font-weight: normal;
&:hover { &[disabled] {
text-decoration: underline; cursor: default;
&:hover,
&:active {
text-decoration: none;
}
} }
} }
} }
......
...@@ -72,14 +72,6 @@ class ApplicationController < ActionController::Base ...@@ -72,14 +72,6 @@ class ApplicationController < ActionController::Base
end end
end end
def authenticate_user!(*args)
if redirect_to_home_page_url?
return redirect_to current_application_settings.home_page_url
end
super(*args)
end
def log_exception(exception) def log_exception(exception)
application_trace = ActionDispatch::ExceptionWrapper.new(env, exception).application_trace application_trace = ActionDispatch::ExceptionWrapper.new(env, exception).application_trace
application_trace.map!{ |t| " #{t}\n" } application_trace.map!{ |t| " #{t}\n" }
...@@ -291,19 +283,6 @@ class ApplicationController < ActionController::Base ...@@ -291,19 +283,6 @@ class ApplicationController < ActionController::Base
session[:skip_tfa] && session[:skip_tfa] > Time.current session[:skip_tfa] && session[:skip_tfa] > Time.current
end end
def redirect_to_home_page_url?
# If user is not signed-in and tries to access root_path - redirect him to landing page
# Don't redirect to the default URL to prevent endless redirections
return false unless current_application_settings.home_page_url.present?
home_page_url = current_application_settings.home_page_url.chomp('/')
root_urls = [Gitlab.config.gitlab['url'].chomp('/'), root_url.chomp('/')]
return false if root_urls.include?(home_page_url)
current_user.nil? && root_path == request.path
end
# U2F (universal 2nd factor) devices need a unique identifier for the application # U2F (universal 2nd factor) devices need a unique identifier for the application
# to perform authentication. # to perform authentication.
# https://developers.yubico.com/U2F/App_ID.html # https://developers.yubico.com/U2F/App_ID.html
......
...@@ -10,9 +10,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -10,9 +10,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_action :module_enabled before_action :module_enabled
before_action :merge_request, only: [ before_action :merge_request, only: [
:edit, :update, :show, :diffs, :commits, :conflicts, :conflict_for_path, :pipelines, :merge, :merge_check, :edit, :update, :show, :diffs, :commits, :conflicts, :conflict_for_path, :pipelines, :merge, :merge_check,
<<<<<<< HEAD
:ci_status, :ci_environments_status, :toggle_subscription, :cancel_merge_when_pipeline_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues, :ci_status, :ci_environments_status, :toggle_subscription, :cancel_merge_when_pipeline_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues,
# EE # EE
:approve, :approvals, :unapprove, :rebase :approve, :approvals, :unapprove, :rebase
=======
:ci_status, :ci_environments_status, :toggle_subscription, :cancel_merge_when_pipeline_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues
>>>>>>> ce/master
] ]
before_action :validates_merge_request, only: [:show, :diffs, :commits, :pipelines] before_action :validates_merge_request, only: [:show, :diffs, :commits, :pipelines]
before_action :define_show_vars, only: [:show, :diffs, :commits, :conflicts, :conflict_for_path, :builds, :pipelines] before_action :define_show_vars, only: [:show, :diffs, :commits, :conflicts, :conflict_for_path, :builds, :pipelines]
...@@ -248,9 +252,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -248,9 +252,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
format.json do format.json do
define_pipelines_vars define_pipelines_vars
render json: PipelineSerializer render json: {
pipelines: PipelineSerializer
.new(project: @project, user: @current_user) .new(project: @project, user: @current_user)
.represent(@pipelines) .represent(@pipelines)
}
end end
end end
end end
......
...@@ -8,7 +8,9 @@ ...@@ -8,7 +8,9 @@
# `DashboardController#show`, which is the default. # `DashboardController#show`, which is the default.
class RootController < Dashboard::ProjectsController class RootController < Dashboard::ProjectsController
skip_before_action :authenticate_user!, only: [:index] skip_before_action :authenticate_user!, only: [:index]
before_action :redirect_to_custom_dashboard, only: [:index]
before_action :redirect_unlogged_user, if: -> { current_user.nil? }
before_action :redirect_logged_user, if: -> { current_user.present? }
def index def index
super super
...@@ -16,23 +18,38 @@ class RootController < Dashboard::ProjectsController ...@@ -16,23 +18,38 @@ class RootController < Dashboard::ProjectsController
private private
def redirect_to_custom_dashboard def redirect_unlogged_user
return redirect_to new_user_session_path unless current_user if redirect_to_home_page_url?
redirect_to(current_application_settings.home_page_url)
else
redirect_to(new_user_session_path)
end
end
def redirect_logged_user
case current_user.dashboard case current_user.dashboard
when 'stars' when 'stars'
flash.keep flash.keep
redirect_to starred_dashboard_projects_path redirect_to(starred_dashboard_projects_path)
when 'project_activity' when 'project_activity'
redirect_to activity_dashboard_path redirect_to(activity_dashboard_path)
when 'starred_project_activity' when 'starred_project_activity'
redirect_to activity_dashboard_path(filter: 'starred') redirect_to(activity_dashboard_path(filter: 'starred'))
when 'groups' when 'groups'
redirect_to dashboard_groups_path redirect_to(dashboard_groups_path)
when 'todos' when 'todos'
redirect_to dashboard_todos_path redirect_to(dashboard_todos_path)
else
return
end end
end end
def redirect_to_home_page_url?
# If user is not signed-in and tries to access root_path - redirect him to landing page
# Don't redirect to the default URL to prevent endless redirections
return false unless current_application_settings.home_page_url.present?
home_page_url = current_application_settings.home_page_url.chomp('/')
root_urls = [Gitlab.config.gitlab['url'].chomp('/'), root_url.chomp('/')]
root_urls.exclude?(home_page_url)
end
end end
...@@ -19,7 +19,7 @@ module ButtonHelper ...@@ -19,7 +19,7 @@ module ButtonHelper
title = data[:title] || 'Copy to clipboard' title = data[:title] || 'Copy to clipboard'
data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data) data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data)
content_tag :button, content_tag :button,
icon('clipboard'), icon('clipboard', 'aria-hidden': 'true'),
class: "btn #{css_class}", class: "btn #{css_class}",
data: data, data: data,
type: :button, type: :button,
......
module IssuablesHelper module IssuablesHelper
def sidebar_gutter_toggle_icon def sidebar_gutter_toggle_icon
sidebar_gutter_collapsed? ? icon('angle-double-left') : icon('angle-double-right') sidebar_gutter_collapsed? ? icon('angle-double-left', { 'aria-hidden': 'true' }) : icon('angle-double-right', { 'aria-hidden': 'true' })
end end
def sidebar_gutter_collapsed_class def sidebar_gutter_collapsed_class
......
...@@ -56,15 +56,6 @@ module Ci ...@@ -56,15 +56,6 @@ module Ci
pending.unstarted.order('created_at ASC').first pending.unstarted.order('created_at ASC').first
end end
def create_from(build)
new_build = build.dup
new_build.status = 'pending'
new_build.runner_id = nil
new_build.trigger_request_id = nil
new_build.token = nil
new_build.save
end
def retry(build, current_user) def retry(build, current_user)
Ci::RetryBuildService Ci::RetryBuildService
.new(build.project, current_user) .new(build.project, current_user)
......
...@@ -22,6 +22,12 @@ class Project < ActiveRecord::Base ...@@ -22,6 +22,12 @@ class Project < ActiveRecord::Base
extend Gitlab::ConfigHelper extend Gitlab::ConfigHelper
<<<<<<< HEAD
=======
BoardLimitExceeded = Class.new(StandardError)
NUMBER_OF_PERMITTED_BOARDS = 1
>>>>>>> ce/master
UNKNOWN_IMPORT_URL = 'http://unknown.git'.freeze UNKNOWN_IMPORT_URL = 'http://unknown.git'.freeze
cache_markdown_field :description, pipeline: :description cache_markdown_field :description, pipeline: :description
......
...@@ -371,7 +371,11 @@ class User < ActiveRecord::Base ...@@ -371,7 +371,11 @@ class User < ActiveRecord::Base
# Return (create if necessary) the ghost user. The ghost user # Return (create if necessary) the ghost user. The ghost user
# owns records previously belonging to deleted users. # owns records previously belonging to deleted users.
def ghost def ghost
User.find_by_ghost(true) || create_ghost_user unique_internal(where(ghost: true), 'ghost', 'ghost%s@example.com') do |u|
u.bio = 'This is a "Ghost User", created to hold all issues authored by users that have since been deleted. This user cannot be removed.'
u.state = :blocked
u.name = 'Ghost User'
end
end end
end end
...@@ -1053,10 +1057,14 @@ class User < ActiveRecord::Base ...@@ -1053,10 +1057,14 @@ class User < ActiveRecord::Base
end end
end end
def self.create_ghost_user def self.unique_internal(scope, username, email_pattern, &b)
# Since we only want a single ghost user in an instance, we use an scope.first || create_unique_internal(scope, username, email_pattern, &b)
end
def self.create_unique_internal(scope, username, email_pattern, &creation_block)
# Since we only want a single one of these in an instance, we use an
# exclusive lease to ensure than this block is never run concurrently. # exclusive lease to ensure than this block is never run concurrently.
lease_key = "ghost_user_creation" lease_key = "user:unique_internal:#{username}"
lease = Gitlab::ExclusiveLease.new(lease_key, timeout: 1.minute.to_i) lease = Gitlab::ExclusiveLease.new(lease_key, timeout: 1.minute.to_i)
until uuid = lease.try_obtain until uuid = lease.try_obtain
...@@ -1065,25 +1073,25 @@ class User < ActiveRecord::Base ...@@ -1065,25 +1073,25 @@ class User < ActiveRecord::Base
sleep(1) sleep(1)
end end
# Recheck if a ghost user is already present. One might have been # Recheck if the user is already present. One might have been
# added between the time we last checked (first line of this method) # added between the time we last checked (first line of this method)
# and the time we acquired the lock. # and the time we acquired the lock.
ghost_user = User.find_by_ghost(true) existing_user = uncached { scope.first }
return ghost_user if ghost_user.present? return existing_user if existing_user.present?
uniquify = Uniquify.new uniquify = Uniquify.new
username = uniquify.string("ghost") { |s| User.find_by_username(s) } username = uniquify.string(username) { |s| User.find_by_username(s) }
email = uniquify.string(-> (n) { "ghost#{n}@example.com" }) do |s| email = uniquify.string(-> (n) { Kernel.sprintf(email_pattern, n) }) do |s|
User.find_by_email(s) User.find_by_email(s)
end end
bio = 'This is a "Ghost User", created to hold all issues authored by users that have since been deleted. This user cannot be removed.' scope.create(
username: username,
User.create( password: Devise.friendly_token,
username: username, password: Devise.friendly_token, bio: bio, email: email,
email: email, name: "Ghost User", state: :blocked, ghost: true &creation_block
) )
ensure ensure
Gitlab::ExclusiveLease.cancel(lease_key, uuid) Gitlab::ExclusiveLease.cancel(lease_key, uuid)
......
...@@ -8,8 +8,11 @@ class MergeRequestEntity < IssuableEntity ...@@ -8,8 +8,11 @@ class MergeRequestEntity < IssuableEntity
expose :merge_status expose :merge_status
expose :merge_user_id expose :merge_user_id
expose :merge_when_pipeline_succeeds expose :merge_when_pipeline_succeeds
<<<<<<< HEAD
expose :rebase_commit_sha expose :rebase_commit_sha
expose :rebase_in_progress?, if: { type: :full } expose :rebase_in_progress?, if: { type: :full }
=======
>>>>>>> ce/master
expose :source_branch expose :source_branch
expose :source_project_id expose :source_project_id
expose :target_branch expose :target_branch
......
...@@ -79,6 +79,7 @@ module MergeRequests ...@@ -79,6 +79,7 @@ module MergeRequests
end end
end end
<<<<<<< HEAD
# Note: Closed merge requests also need approvals reset. # Note: Closed merge requests also need approvals reset.
def reset_approvals_for_merge_requests def reset_approvals_for_merge_requests
merge_requests = merge_requests_for(@branch_name, mr_states: [:opened, :reopened, :closed]) merge_requests = merge_requests_for(@branch_name, mr_states: [:opened, :reopened, :closed])
...@@ -95,6 +96,8 @@ module MergeRequests ...@@ -95,6 +96,8 @@ module MergeRequests
end end
end end
=======
>>>>>>> ce/master
def reset_merge_when_pipeline_succeeds def reset_merge_when_pipeline_succeeds
merge_requests_for_source_branch.each(&:reset_merge_when_pipeline_succeeds) merge_requests_for_source_branch.each(&:reset_merge_when_pipeline_succeeds)
end end
......
...@@ -104,6 +104,7 @@ class TodoService ...@@ -104,6 +104,7 @@ class TodoService
def merge_request_build_failed(merge_request) def merge_request_build_failed(merge_request)
create_build_failed_todo(merge_request, merge_request.author) create_build_failed_todo(merge_request, merge_request.author)
create_build_failed_todo(merge_request, merge_request.merge_user) if merge_request.merge_when_pipeline_succeeds? create_build_failed_todo(merge_request, merge_request.merge_user) if merge_request.merge_when_pipeline_succeeds?
<<<<<<< HEAD
end end
# When new approvers are added for a merge request: # When new approvers are added for a merge request:
...@@ -112,6 +113,8 @@ class TodoService ...@@ -112,6 +113,8 @@ class TodoService
# #
def add_merge_request_approvers(merge_request, approvers) def add_merge_request_approvers(merge_request, approvers)
create_approval_required_todos(merge_request, approvers, merge_request.author) create_approval_required_todos(merge_request, approvers, merge_request.author)
=======
>>>>>>> ce/master
end end
# When a new commit is pushed to a merge request we should: # When a new commit is pushed to a merge request we should:
......
.btn-group - if @environment
= view_on_environment_button(@commit.sha, @path, @environment) if @environment .btn-group<
= view_on_environment_button(@commit.sha, @path, @environment)
.btn-group.tree-btn-group .btn-group{ role: "group" }<
= link_to 'Raw', namespace_project_raw_path(@project.namespace, @project, @id), = link_to 'Raw', namespace_project_raw_path(@project.namespace, @project, @id),
class: 'btn btn-sm', target: '_blank' class: 'btn btn-sm', target: '_blank'
-# only show normal/blame view links for text files -# only show normal/blame view links for text files
...@@ -18,8 +19,12 @@ ...@@ -18,8 +19,12 @@
tree_join(@commit.sha, @path)), class: 'btn btn-sm js-data-file-blob-permalink-url' tree_join(@commit.sha, @path)), class: 'btn btn-sm js-data-file-blob-permalink-url'
- if current_user - if current_user
<<<<<<< HEAD
.btn-group{ role: "group" } .btn-group{ role: "group" }
= lock_file_link(html_options: {class: 'btn btn-sm path-lock'}) = lock_file_link(html_options: {class: 'btn btn-sm path-lock'})
=======
.btn-group{ role: "group" }<
>>>>>>> ce/master
- if blob_text_viewable?(@blob) - if blob_text_viewable?(@blob)
= edit_blob_link = edit_blob_link
= replace_blob_link = replace_blob_link
......
...@@ -25,9 +25,10 @@ ...@@ -25,9 +25,10 @@
#blob-content-holder.blob-content-holder #blob-content-holder.blob-content-holder
%article.file-holder %article.file-holder
.js-file-title.file-title .js-file-title.file-title-flex-parent
.file-header-content
= blob_icon blob.mode, blob.name = blob_icon blob.mode, blob.name
%strong %strong.file-title-name
= blob.name = blob.name
%small %small
= number_to_human_size(blob_size(blob)) = number_to_human_size(blob_size(blob))
......
...@@ -9,20 +9,20 @@ ...@@ -9,20 +9,20 @@
- line_old = line_new - @form.offset - line_old = line_new - @form.offset
- line_content = capture do - line_content = capture do
%td.line_content.noteable_line{ class: line_class }==#{' ' * @form.indent}#{line} %td.line_content.noteable_line{ class: line_class }==#{' ' * @form.indent}#{line}
%tr.line_holder{ id: line_old, class: line_class } %tr.line_holder.diff-expanded{ id: line_old, class: line_class }
- case diff_view - case diff_view
- when :inline - when :inline
%td.old_line.diff-line-num{ data: { linenumber: line_old } } %td.old_line.diff-line-num{ data: { linenumber: line_old } }
%a{ href: "##{line_old}", data: { linenumber: line_old } } %a{ href: "#", data: { linenumber: line_old }, disabled: true }
%td.new_line.diff-line-num{ data: { linenumber: line_new } } %td.new_line.diff-line-num{ data: { linenumber: line_new } }
%a{ href: "##{line_new}", data: { linenumber: line_new } } %a{ href: "#", data: { linenumber: line_new }, disabled: true }
= line_content = line_content
- when :parallel - when :parallel
%td.old_line.diff-line-num{ data: { linenumber: line_old } } %td.old_line.diff-line-num{ data: { linenumber: line_old } }
%a{ href: "##{line_old}", data: { linenumber: line_old } } %a{ href: "##{line_old}", data: { linenumber: line_old }, disabled: true }
= line_content = line_content
%td.new_line.diff-line-num{ data: { linenumber: line_new } } %td.new_line.diff-line-num{ data: { linenumber: line_new } }
%a{ href: "##{line_new}", data: { linenumber: line_new } } %a{ href: "##{line_new}", data: { linenumber: line_new }, disabled: true }
= line_content = line_content
- if @form.unfold? && @form.bottom? && @form.to < @blob.lines.size - if @form.unfold? && @form.bottom? && @form.to < @blob.lines.size
......
...@@ -2,28 +2,8 @@ ...@@ -2,28 +2,8 @@
.board-list-loading.text-center{ "v-if" => "loading" } .board-list-loading.text-center{ "v-if" => "loading" }
= icon("spinner spin") = icon("spinner spin")
- if can? current_user, :create_issue, @project - if can? current_user, :create_issue, @project
%board-new-issue{ "inline-template" => true, %board-new-issue{ ":list" => "list",
":list" => "list",
"v-if" => 'list.type !== "done" && showIssueForm' } "v-if" => 'list.type !== "done" && showIssueForm' }
.card.board-new-issue-form
%form{ "@submit" => "submit($event)" }
.flash-container{ "v-if" => "error" }
.flash-alert
An error occured. Please try again.
%label.label-light{ ":for" => 'list.id + "-title"' }
Title
%input.form-control{ type: "text",
"v-model" => "title",
"ref" => "input",
":id" => 'list.id + "-title"' }
.clearfix.prepend-top-10
%button.btn.btn-success.pull-left{ type: "submit",
":disabled" => 'title === ""',
"ref" => "submit-button" }
Submit issue
%button.btn.btn-default.pull-right{ type: "button",
"@click" => "cancel" }
Cancel
%ul.board-list{ "ref" => "list", %ul.board-list{ "ref" => "list",
"v-show" => "!loading", "v-show" => "!loading",
":data-board" => "list.id", ":data-board" => "list.id",
......
...@@ -5,9 +5,12 @@ ...@@ -5,9 +5,12 @@
- when :merge_when_pipeline_succeeds - when :merge_when_pipeline_succeeds
:plain :plain
$('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/merge_when_pipeline_succeeds'))}"); $('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/merge_when_pipeline_succeeds'))}");
<<<<<<< HEAD
- when :hook_validation_error - when :hook_validation_error
:plain :plain
$('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/error'))}"); $('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/error'))}");
=======
>>>>>>> ce/master
- when :sha_mismatch - when :sha_mismatch
:plain :plain
$('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/sha_mismatch'))}"); $('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/sha_mismatch'))}");
......
...@@ -11,10 +11,17 @@ ...@@ -11,10 +11,17 @@
.accept-action .accept-action
- if @pipeline && @pipeline.active? - if @pipeline && @pipeline.active?
%span.btn-group %span.btn-group
<<<<<<< HEAD
= button_tag class: "btn btn-create js-merge-button merge_when_pipeline_succeeds", disabled: !@merge_request.approved?, ":disabled" => "disableAcceptance" do = button_tag class: "btn btn-create js-merge-button merge_when_pipeline_succeeds", disabled: !@merge_request.approved?, ":disabled" => "disableAcceptance" do
Merge When Pipeline Succeeds Merge When Pipeline Succeeds
- unless @project.only_allow_merge_if_pipeline_succeeds? - unless @project.only_allow_merge_if_pipeline_succeeds?
= button_tag class: "btn btn-success dropdown-toggle", 'data-toggle' => 'dropdown', disabled: !@merge_request.approved?, ":disabled" => "disableAcceptance" do = button_tag class: "btn btn-success dropdown-toggle", 'data-toggle' => 'dropdown', disabled: !@merge_request.approved?, ":disabled" => "disableAcceptance" do
=======
= button_tag class: "btn btn-create js-merge-button merge_when_pipeline_succeeds" do
Merge When Pipeline Succeeds
- unless @project.only_allow_merge_if_pipeline_succeeds?
= button_tag class: "btn btn-success dropdown-toggle", 'data-toggle' => 'dropdown' do
>>>>>>> ce/master
= icon('caret-down') = icon('caret-down')
%span.sr-only %span.sr-only
Select Merge Moment Select Merge Moment
......
...@@ -9,16 +9,16 @@ ...@@ -9,16 +9,16 @@
- if current_user - if current_user
%span.issuable-header-text.hide-collapsed.pull-left %span.issuable-header-text.hide-collapsed.pull-left
Todo Todo
%a.gutter-toggle.pull-right.js-sidebar-toggle{ role: "button", href: "#", aria: { label: "Toggle sidebar" } } %a.gutter-toggle.pull-right.js-sidebar-toggle{ role: "button", href: "#", "aria-label" => "Toggle sidebar" }
= sidebar_gutter_toggle_icon = sidebar_gutter_toggle_icon
- if current_user - if current_user
%button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", aria: { label: (todo.nil? ? "Add todo" : "Mark done") }, data: { todo_text: "Add todo", mark_text: "Mark done", issuable_id: issuable.id, issuable_type: issuable.class.name.underscore, url: namespace_project_todos_path(@project.namespace, @project), delete_path: (dashboard_todo_path(todo) if todo) } } %button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", "aria-label" => (todo.nil? ? "Add todo" : "Mark done"), data: { todo_text: "Add todo", mark_text: "Mark done", issuable_id: issuable.id, issuable_type: issuable.class.name.underscore, url: namespace_project_todos_path(@project.namespace, @project), delete_path: (dashboard_todo_path(todo) if todo) } }
%span.js-issuable-todo-text %span.js-issuable-todo-text
- if todo - if todo
Mark done Mark done
- else - else
Add todo Add todo
= icon('spin spinner', class: 'hidden js-issuable-todo-loading') = icon('spin spinner', class: 'hidden js-issuable-todo-loading', 'aria-hidden': 'true')
= form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, format: :json, html: { class: 'issuable-context-form inline-update js-issuable-update' } do |f| = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, format: :json, html: { class: 'issuable-context-form inline-update js-issuable-update' } do |f|
.block.assignee .block.assignee
...@@ -26,10 +26,10 @@ ...@@ -26,10 +26,10 @@
- if issuable.assignee - if issuable.assignee
= link_to_member(@project, issuable.assignee, size: 24) = link_to_member(@project, issuable.assignee, size: 24)
- else - else
= icon('user') = icon('user', 'aria-hidden': 'true')
.title.hide-collapsed .title.hide-collapsed
Assignee Assignee
= icon('spinner spin', class: 'block-loading') = icon('spinner spin', class: 'block-loading', 'aria-hidden': 'true')
- if can_edit_issuable - if can_edit_issuable
= link_to 'Edit', '#', class: 'edit-link pull-right' = link_to 'Edit', '#', class: 'edit-link pull-right'
.value.hide-collapsed .value.hide-collapsed
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
= link_to_member(@project, issuable.assignee, size: 32, extra_class: 'bold') do = link_to_member(@project, issuable.assignee, size: 32, extra_class: 'bold') do
- if issuable.instance_of?(MergeRequest) && !issuable.can_be_merged_by?(issuable.assignee) - if issuable.instance_of?(MergeRequest) && !issuable.can_be_merged_by?(issuable.assignee)
%span.pull-right.cannot-be-merged{ data: { toggle: 'tooltip', placement: 'left' }, title: 'Not allowed to merge' } %span.pull-right.cannot-be-merged{ data: { toggle: 'tooltip', placement: 'left' }, title: 'Not allowed to merge' }
= icon('exclamation-triangle') = icon('exclamation-triangle', 'aria-hidden': 'true')
%span.username %span.username
= issuable.assignee.to_reference = issuable.assignee.to_reference
- else - else
...@@ -54,7 +54,7 @@ ...@@ -54,7 +54,7 @@
.block.milestone .block.milestone
.sidebar-collapsed-icon .sidebar-collapsed-icon
= icon('clock-o') = icon('clock-o', 'aria-hidden': 'true')
%span %span
- if issuable.milestone - if issuable.milestone
%span.has-tooltip{ title: milestone_remaining_days(issuable.milestone), data: { container: 'body', html: 1, placement: 'left' } } %span.has-tooltip{ title: milestone_remaining_days(issuable.milestone), data: { container: 'body', html: 1, placement: 'left' } }
...@@ -63,7 +63,7 @@ ...@@ -63,7 +63,7 @@
None None
.title.hide-collapsed .title.hide-collapsed
Milestone Milestone
= icon('spinner spin', class: 'block-loading') = icon('spinner spin', class: 'block-loading', 'aria-hidden': 'true')
- if can_edit_issuable - if can_edit_issuable
= link_to 'Edit', '#', class: 'edit-link pull-right' = link_to 'Edit', '#', class: 'edit-link pull-right'
.value.hide-collapsed .value.hide-collapsed
...@@ -81,16 +81,16 @@ ...@@ -81,16 +81,16 @@
// Fallback while content is loading // Fallback while content is loading
.title.hide-collapsed .title.hide-collapsed
Time tracking Time tracking
= icon('spinner spin') = icon('spinner spin', 'aria-hidden': 'true')
- if issuable.has_attribute?(:due_date) - if issuable.has_attribute?(:due_date)
.block.due_date .block.due_date
.sidebar-collapsed-icon .sidebar-collapsed-icon
= icon('calendar') = icon('calendar', 'aria-hidden': 'true')
%span.js-due-date-sidebar-value %span.js-due-date-sidebar-value
= issuable.due_date.try(:to_s, :medium) || 'None' = issuable.due_date.try(:to_s, :medium) || 'None'
.title.hide-collapsed .title.hide-collapsed
Due date Due date
= icon('spinner spin', class: 'block-loading') = icon('spinner spin', class: 'block-loading', 'aria-hidden': 'true')
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
= link_to 'Edit', '#', class: 'edit-link pull-right' = link_to 'Edit', '#', class: 'edit-link pull-right'
.value.hide-collapsed .value.hide-collapsed
...@@ -110,7 +110,7 @@ ...@@ -110,7 +110,7 @@
.dropdown .dropdown
%button.dropdown-menu-toggle.js-due-date-select{ type: 'button', data: { toggle: 'dropdown', field_name: "#{issuable.to_ability_name}[due_date]", ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable) } } %button.dropdown-menu-toggle.js-due-date-select{ type: 'button', data: { toggle: 'dropdown', field_name: "#{issuable.to_ability_name}[due_date]", ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable) } }
%span.dropdown-toggle-text Due date %span.dropdown-toggle-text Due date
= icon('chevron-down') = icon('chevron-down', 'aria-hidden': 'true')
.dropdown-menu.dropdown-menu-due-date .dropdown-menu.dropdown-menu-due-date
= dropdown_title('Due date') = dropdown_title('Due date')
= dropdown_content do = dropdown_content do
...@@ -120,12 +120,12 @@ ...@@ -120,12 +120,12 @@
- selected_labels = issuable.labels - selected_labels = issuable.labels
.block.labels .block.labels
.sidebar-collapsed-icon.js-sidebar-labels-tooltip{ title: issuable_labels_tooltip(issuable.labels_array), data: { placement: "left", container: "body" } } .sidebar-collapsed-icon.js-sidebar-labels-tooltip{ title: issuable_labels_tooltip(issuable.labels_array), data: { placement: "left", container: "body" } }
= icon('tags') = icon('tags', 'aria-hidden': 'true')
%span %span
= selected_labels.size = selected_labels.size
.title.hide-collapsed .title.hide-collapsed
Labels Labels
= icon('spinner spin', class: 'block-loading') = icon('spinner spin', class: 'block-loading', 'aria-hidden': 'true')
- if can_edit_issuable - if can_edit_issuable
= link_to 'Edit', '#', class: 'edit-link pull-right' = link_to 'Edit', '#', class: 'edit-link pull-right'
.value.issuable-show-labels.hide-collapsed{ class: ("has-labels" if selected_labels.any?) } .value.issuable-show-labels.hide-collapsed{ class: ("has-labels" if selected_labels.any?) }
...@@ -141,7 +141,7 @@ ...@@ -141,7 +141,7 @@
%button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:path), project_path: @project.try(:path), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project) } } %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:path), project_path: @project.try(:path), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project) } }
%span.dropdown-toggle-text{ class: ("is-default" if selected_labels.empty?) } %span.dropdown-toggle-text{ class: ("is-default" if selected_labels.empty?) }
= multi_label_name(selected_labels, "Labels") = multi_label_name(selected_labels, "Labels")
= icon('chevron-down') = icon('chevron-down', 'aria-hidden': 'true')
.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" = render partial: "shared/issuable/label_page_default"
- if can? current_user, :admin_label, @project and @project - if can? current_user, :admin_label, @project and @project
...@@ -179,7 +179,7 @@ ...@@ -179,7 +179,7 @@
- subscribed = issuable.subscribed?(current_user, @project) - subscribed = issuable.subscribed?(current_user, @project)
.block.light.subscription{ data: { url: toggle_subscription_path(issuable) } } .block.light.subscription{ data: { url: toggle_subscription_path(issuable) } }
.sidebar-collapsed-icon .sidebar-collapsed-icon
= icon('rss') = icon('rss', 'aria-hidden': 'true')
%span.issuable-header-text.hide-collapsed.pull-left %span.issuable-header-text.hide-collapsed.pull-left
Notifications Notifications
- subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed' - subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed'
......
---
title: Make documentation of list repository tree API call more detailed
merge_request: 9532
author: Marius Kleiner
---
title: API: Add environment stop action
merge_request: 8808
author:
---
title: Improves a11y in sidebar by adding aria-hidden attributes in i tags and by
fixing two broken aria-hidden attributes
merge_request:
author:
---
title: API issues - support filtering by iids
merge_request:
author:
---
title: Fix the redirect to custom home page URL
merge_request: 9518
author:
---
title: Ensure archive download is only one directory deep
merge_request: 9616
author:
---
title: Enable filtering milestones by search criteria in the API
merge_request: 9606
author:
---
title: Fix broken migration when upgrading straight to 8.17.1
merge_request: 9613
author:
---
title: Visually show expanded diff lines cant have comments
merge_request:
author:
---
title: 'CORS: Whitelist pagination headers'
merge_request: 9651
author: Robert Schilling
---
title: Fix updaing commit status when using optional attributes
merge_request: 9618
author:
---
title: Fixed long file names overflowing under action buttons
merge_request:
author:
--- ---
title: Remove the newrelic gem title: Remove the newrelic gem
<<<<<<< HEAD
merge_request: 1335 merge_request: 1335
=======
merge_request: 9622
>>>>>>> ce/master
author: Robert Schilling author: Robert Schilling
---
title: Make projects dropdown only show projects you are a member of
merge_request: 9614
author:
---
title: Removed top border from user contribution calendar
merge_request:
author:
...@@ -123,7 +123,7 @@ module Gitlab ...@@ -123,7 +123,7 @@ module Gitlab
credentials: true, credentials: true,
headers: :any, headers: :any,
methods: :any, methods: :any,
expose: ['Link'] expose: ['Link', 'X-Total', 'X-Total-Pages', 'X-Per-Page', 'X-Page', 'X-Next-Page', 'X-Prev-Page']
end end
# Cross-origin requests must not have the session cookie available # Cross-origin requests must not have the session cookie available
...@@ -133,7 +133,7 @@ module Gitlab ...@@ -133,7 +133,7 @@ module Gitlab
credentials: false, credentials: false,
headers: :any, headers: :any,
methods: :any, methods: :any,
expose: ['Link'] expose: ['Link', 'X-Total', 'X-Total-Pages', 'X-Per-Page', 'X-Page', 'X-Next-Page', 'X-Prev-Page']
end end
end end
......
class DisableInvalidServiceTemplates < ActiveRecord::Migration class DisableInvalidServiceTemplates < ActiveRecord::Migration
DOWNTIME = false DOWNTIME = false
unless defined?(Service)
class Service < ActiveRecord::Base class Service < ActiveRecord::Base
self.inheritance_column = nil self.inheritance_column = nil
end end
end
def up def up
Service.where(template: true, active: true).each do |template| Service.where(template: true, active: true).each do |template|
......
...@@ -160,7 +160,7 @@ The following table shows the possible return codes for API requests. ...@@ -160,7 +160,7 @@ The following table shows the possible return codes for API requests.
| Return values | Description | | Return values | Description |
| ------------- | ----------- | | ------------- | ----------- |
| `200 OK` | The `GET`, `PUT` or `DELETE` request was successful, the resource(s) itself is returned as JSON. | | `200 OK` | The `GET`, `PUT` or `DELETE` request was successful, the resource(s) itself is returned as JSON. |
| `204 OK` | The server has successfully fulfilled the request and that there is no additional content to send in the response payload body. | | `204 No Content` | The server has successfully fulfilled the request and that there is no additional content to send in the response payload body. |
| `201 Created` | The `POST` request was successful and the resource is returned as JSON. | | `201 Created` | The `POST` request was successful and the resource is returned as JSON. |
| `304 Not Modified` | Indicates that the resource has not been modified since the last request. | | `304 Not Modified` | Indicates that the resource has not been modified since the last request. |
| `400 Bad Request` | A required attribute of the API request is missing, e.g., the title of an issue is not given. | | `400 Bad Request` | A required attribute of the API request is missing, e.g., the title of an issue is not given. |
......
...@@ -33,7 +33,7 @@ Example response: ...@@ -33,7 +33,7 @@ Example response:
Creates a new environment with the given name and external_url. Creates a new environment with the given name and external_url.
It returns 201 if the environment was successfully created, 400 for wrong parameters. It returns `201` if the environment was successfully created, `400` for wrong parameters.
``` ```
POST /projects/:id/environment POST /projects/:id/environment
...@@ -64,7 +64,7 @@ Example response: ...@@ -64,7 +64,7 @@ Example response:
Updates an existing environment's name and/or external_url. Updates an existing environment's name and/or external_url.
It returns 200 if the environment was successfully updated. In case of an error, a status code 400 is returned. It returns `200` if the environment was successfully updated. In case of an error, a status code `400` is returned.
``` ```
PUT /projects/:id/environments/:environments_id PUT /projects/:id/environments/:environments_id
...@@ -94,7 +94,7 @@ Example response: ...@@ -94,7 +94,7 @@ Example response:
## Delete an environment ## Delete an environment
It returns 200 if the environment was successfully deleted, and 404 if the environment does not exist. It returns `200` if the environment was successfully deleted, and `404` if the environment does not exist.
``` ```
DELETE /projects/:id/environments/:environment_id DELETE /projects/:id/environments/:environment_id
...@@ -107,4 +107,35 @@ DELETE /projects/:id/environments/:environment_id ...@@ -107,4 +107,35 @@ DELETE /projects/:id/environments/:environment_id
```bash ```bash
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/environments/1" curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/environments/1"
<<<<<<< HEAD
=======
```
## Stop an environment
It returns `200` if the environment was successfully stopped, and `404` if the environment does not exist.
```
POST /projects/:id/environments/:environment_id/stop
```
| Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- |
| `id` | integer | yes | The ID of the project |
| `environment_id` | integer | yes | The ID of the environment |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environments/1/stop"
```
Example response:
```json
{
"id": 1,
"name": "deploy",
"slug": "deploy",
"external_url": "https://deploy.example.gitlab.com"
}
>>>>>>> ce/master
``` ```
...@@ -25,6 +25,7 @@ GET /issues?labels=foo,bar ...@@ -25,6 +25,7 @@ GET /issues?labels=foo,bar
GET /issues?labels=foo,bar&state=opened GET /issues?labels=foo,bar&state=opened
GET /issues?milestone=1.0.0 GET /issues?milestone=1.0.0
GET /issues?milestone=1.0.0&state=opened GET /issues?milestone=1.0.0&state=opened
GET /issues?iids[]=42&iids[]=43
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
...@@ -32,6 +33,7 @@ GET /issues?milestone=1.0.0&state=opened ...@@ -32,6 +33,7 @@ GET /issues?milestone=1.0.0&state=opened
| `state` | string | no | Return all issues or just those that are `opened` or `closed`| | `state` | string | no | Return all issues or just those that are `opened` or `closed`|
| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned | | `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned |
| `milestone` | string| no | The milestone title | | `milestone` | string| no | The milestone title |
| `iids` | Array[integer] | no | Return only the issues having the given `iid` |
| `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` | | `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` | | `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
...@@ -103,6 +105,7 @@ GET /groups/:id/issues?labels=foo,bar ...@@ -103,6 +105,7 @@ GET /groups/:id/issues?labels=foo,bar
GET /groups/:id/issues?labels=foo,bar&state=opened GET /groups/:id/issues?labels=foo,bar&state=opened
GET /groups/:id/issues?milestone=1.0.0 GET /groups/:id/issues?milestone=1.0.0
GET /groups/:id/issues?milestone=1.0.0&state=opened GET /groups/:id/issues?milestone=1.0.0&state=opened
GET /groups/:id/issues?iids[]=42&iids[]=43
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
...@@ -110,6 +113,7 @@ GET /groups/:id/issues?milestone=1.0.0&state=opened ...@@ -110,6 +113,7 @@ GET /groups/:id/issues?milestone=1.0.0&state=opened
| `id` | integer | yes | The ID of a group | | `id` | integer | yes | The ID of a group |
| `state` | string | no | Return all issues or just those that are `opened` or `closed`| | `state` | string | no | Return all issues or just those that are `opened` or `closed`|
| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned | | `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned |
| `iids` | Array[integer] | no | Return only the issues having the given `iid` |
| `milestone` | string| no | The milestone title | | `milestone` | string| no | The milestone title |
| `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` | | `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` | | `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
...@@ -183,12 +187,13 @@ GET /projects/:id/issues?labels=foo,bar ...@@ -183,12 +187,13 @@ GET /projects/:id/issues?labels=foo,bar
GET /projects/:id/issues?labels=foo,bar&state=opened GET /projects/:id/issues?labels=foo,bar&state=opened
GET /projects/:id/issues?milestone=1.0.0 GET /projects/:id/issues?milestone=1.0.0
GET /projects/:id/issues?milestone=1.0.0&state=opened GET /projects/:id/issues?milestone=1.0.0&state=opened
GET /projects/:id/issues?iids[]=42&iids[]=43
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `iid` | integer | no | Return the issue having the given `iid` | | `iids` | Array[integer] | no | Return only the milestone having the given `iid` |
| `state` | string | no | Return all issues or just those that are `opened` or `closed`| | `state` | string | no | Return all issues or just those that are `opened` or `closed`|
| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned | | `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned |
| `milestone` | string| no | The milestone title | | `milestone` | string| no | The milestone title |
......
...@@ -480,6 +480,7 @@ Parameters: ...@@ -480,6 +480,7 @@ Parameters:
- `merge_commit_message` (optional) - Custom merge commit message - `merge_commit_message` (optional) - Custom merge commit message
- `should_remove_source_branch` (optional) - if `true` removes the source branch - `should_remove_source_branch` (optional) - if `true` removes the source branch
- `merge_when_pipeline_succeeds` (optional) - if `true` the MR is merged when the pipeline succeeds - `merge_when_pipeline_succeeds` (optional) - if `true` the MR is merged when the pipeline succeeds
<<<<<<< HEAD
- `sha` (optional) - if present, then this SHA must - `sha` (optional) - if present, then this SHA must
...@@ -493,6 +494,9 @@ Parameters: ...@@ -493,6 +494,9 @@ Parameters:
| `sha` | string | no | If present, then this SHA must match the HEAD of the source branch, otherwise the merge will fail | | `sha` | string | no | If present, then this SHA must match the HEAD of the source branch, otherwise the merge will fail |
| `squash` | boolean | no | Squash the merge request into a single commit | | `squash` | boolean | no | Squash the merge request into a single commit |
=======
- `sha` (optional) - if present, then this SHA must match the HEAD of the source branch, otherwise the merge will fail
>>>>>>> ce/master
```json ```json
{ {
......
...@@ -10,6 +10,7 @@ GET /projects/:id/milestones?iid=42 ...@@ -10,6 +10,7 @@ GET /projects/:id/milestones?iid=42
GET /projects/:id/milestones?iid[]=42&iid[]=43 GET /projects/:id/milestones?iid[]=42&iid[]=43
GET /projects/:id/milestones?state=active GET /projects/:id/milestones?state=active
GET /projects/:id/milestones?state=closed GET /projects/:id/milestones?state=closed
GET /projects/:id/milestones?search=version
``` ```
Parameters: Parameters:
...@@ -19,6 +20,7 @@ Parameters: ...@@ -19,6 +20,7 @@ Parameters:
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `iid` | Array[integer] | optional | Return only the milestone having the given `iid` | | `iid` | Array[integer] | optional | Return only the milestone having the given `iid` |
| `state` | string | optional | Return only `active` or `closed` milestones` | | `state` | string | optional | Return only `active` or `closed` milestones` |
| `search` | string | optional | Return only milestones with a title or description matching the provided string |
```bash ```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/milestones curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/milestones
......
...@@ -241,7 +241,10 @@ Parameters: ...@@ -241,7 +241,10 @@ Parameters:
"group_access_level": 10 "group_access_level": 10
} }
], ],
<<<<<<< HEAD
"repository_storage": "default", "repository_storage": "default",
=======
>>>>>>> ce/master
"only_allow_merge_if_pipeline_succeeds": false, "only_allow_merge_if_pipeline_succeeds": false,
"only_allow_merge_if_all_discussions_are_resolved": false, "only_allow_merge_if_all_discussions_are_resolved": false,
"request_access_enabled": false "request_access_enabled": false
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
Get a list of repository files and directories in a project. This endpoint can Get a list of repository files and directories in a project. This endpoint can
be accessed without authentication if the repository is publicly accessible. be accessed without authentication if the repository is publicly accessible.
This command provides essentially the same functionality as the `git ls-tree` command. For more information, see the section _Tree Objects_ in the [Git internals documentation](https://git-scm.com/book/en/v2/Git-Internals-Git-Objects/#_tree_objects).
``` ```
GET /projects/:id/repository/tree GET /projects/:id/repository/tree
``` ```
......
...@@ -26,7 +26,7 @@ Parameters: ...@@ -26,7 +26,7 @@ Parameters:
"committer_email": "jack@example.com", "committer_email": "jack@example.com",
"id": "2695effb5807a22ff3d138d593fd856244e155e7", "id": "2695effb5807a22ff3d138d593fd856244e155e7",
"message": "Initial commit", "message": "Initial commit",
"parents_ids": [ "parent_ids": [
"2a4b78934375d7f53875269ffd4f45fd83a84ebe" "2a4b78934375d7f53875269ffd4f45fd83a84ebe"
] ]
}, },
...@@ -110,7 +110,7 @@ Parameters: ...@@ -110,7 +110,7 @@ Parameters:
"committer_email": "jack@example.com", "committer_email": "jack@example.com",
"id": "2695effb5807a22ff3d138d593fd856244e155e7", "id": "2695effb5807a22ff3d138d593fd856244e155e7",
"message": "Initial commit", "message": "Initial commit",
"parents_ids": [ "parent_ids": [
"2a4b78934375d7f53875269ffd4f45fd83a84ebe" "2a4b78934375d7f53875269ffd4f45fd83a84ebe"
] ]
}, },
......
...@@ -30,6 +30,7 @@ changes are in V4: ...@@ -30,6 +30,7 @@ changes are in V4:
- Moved `DELETE /todos` to `POST /todos/mark_as_done` and `DELETE /todos/:todo_id` to `POST /todos/:todo_id/mark_as_done` [!9410](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9410) - Moved `DELETE /todos` to `POST /todos/mark_as_done` and `DELETE /todos/:todo_id` to `POST /todos/:todo_id/mark_as_done` [!9410](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9410)
- Endpoints `/projects/owned`, `/projects/visible`, `/projects/starred` & `/projects/all` are consolidated into `/projects` using query parameters [!8962](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8962) - Endpoints `/projects/owned`, `/projects/visible`, `/projects/starred` & `/projects/all` are consolidated into `/projects` using query parameters [!8962](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8962)
- Return pagination headers for all endpoints that return an array [!8606](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8606) - Return pagination headers for all endpoints that return an array [!8606](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8606)
- Added `POST /environments/:environment_id/stop` to stop an environment [!8808](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8808)
- Removed `DELETE projects/:id/deploy_keys/:key_id/disable`. Use `DELETE projects/:id/deploy_keys/:key_id` instead [!9366](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9366) - Removed `DELETE projects/:id/deploy_keys/:key_id/disable`. Use `DELETE projects/:id/deploy_keys/:key_id` instead [!9366](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9366)
- Moved `PUT /users/:id/(block|unblock)` to `POST /users/:id/(block|unblock)` [!9371](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9371) - Moved `PUT /users/:id/(block|unblock)` to `POST /users/:id/(block|unblock)` [!9371](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9371)
- Make subscription API more RESTful. Use `post ":project_id/:subscribable_type/:subscribable_id/subscribe"` to subscribe and `post ":project_id/:subscribable_type/:subscribable_id/unsubscribe"` to unsubscribe from a resource. [!9325](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9325) - Make subscription API more RESTful. Use `post ":project_id/:subscribable_type/:subscribable_id/subscribe"` to subscribe and `post ":project_id/:subscribable_type/:subscribable_id/unsubscribe"` to unsubscribe from a resource. [!9325](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9325)
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
### Nazim Ramesh ### Nazim Ramesh
- Small to medium size organisations using GitLab CE - Small to medium size organisations using GitLab CE
<img src="img/steven-lyons.png" width="300px"> <img src="img/nazim-ramesh.png" width="300px">
#### Demographics #### Demographics
...@@ -131,7 +131,7 @@ James and his team use CI quite heavily for several projects. Whilst they’ve w ...@@ -131,7 +131,7 @@ James and his team use CI quite heavily for several projects. Whilst they’ve w
- Would like to use GitLab at work - Would like to use GitLab at work
- Working for a medium to large size organisation - Working for a medium to large size organisation
<img src="img/harry-robison.png" width="300px"> <img src="img/karolina-plaskaty.png" width="300px">
#### Demographics #### Demographics
......
...@@ -155,10 +155,9 @@ page](https://golang.org/dl). ...@@ -155,10 +155,9 @@ page](https://golang.org/dl).
## 4. Node ## 4. Node
Since GitLab 8.17, GitLab requires the use of node >= v4.3.0 to compile Since GitLab 8.17, GitLab requires the use of node >= v4.3.0 to compile
javascript assets, and starting in GitLab 9.0, yarn >= v0.17.0 is required to javascript assets, and yarn >= v0.17.0 to manage javascript dependencies.
manage javascript dependencies. In many distros the versions provided by the In many distros the versions provided by the official package repositories
official package repositories are out of date, so we'll need to install through are out of date, so we'll need to install through the following commands:
the following commands:
# install node v7.x # install node v7.x
curl --location https://deb.nodesource.com/setup_7.x | bash - curl --location https://deb.nodesource.com/setup_7.x | bash -
......
...@@ -38,23 +38,6 @@ If you are running GitLab within a Docker container, you can run the backup from ...@@ -38,23 +38,6 @@ If you are running GitLab within a Docker container, you can run the backup from
docker exec -t <container name> gitlab-rake gitlab:backup:create docker exec -t <container name> gitlab-rake gitlab:backup:create
``` ```
You can specify that portions of the application data be skipped using the
environment variable `SKIP`. You can skip:
- `db` (database)
- `uploads` (attachments)
- `repositories` (Git repositories data)
- `builds` (CI job output logs)
- `artifacts` (CI job artifacts)
- `lfs` (LFS objects)
- `registry` (Container Registry images)
Separate multiple data types to skip using a comma. For example:
```
sudo gitlab-rake gitlab:backup:create SKIP=db,uploads
```
Example output: Example output:
``` ```
...@@ -111,13 +94,14 @@ To use the `copy` strategy instead of the default streaming strategy, specify ...@@ -111,13 +94,14 @@ To use the `copy` strategy instead of the default streaming strategy, specify
You can choose what should be backed up by adding the environment variable `SKIP`. You can choose what should be backed up by adding the environment variable `SKIP`.
The available options are: The available options are:
* `db` - `db` (database)
* `uploads` (attachments) - `uploads` (attachments)
* `repositories` - `repositories` (Git repositories data)
* `builds` (CI build output logs) - `builds` (CI job output logs)
* `artifacts` (CI build artifacts) - `artifacts` (CI job artifacts)
* `lfs` (LFS objects) - `lfs` (LFS objects)
* `pages` (pages content) - `registry` (Container Registry images)
- `pages` (Pages content)
Use a comma to specify several options at the same time: Use a comma to specify several options at the same time:
...@@ -416,7 +400,7 @@ sudo gitlab-rake gitlab:check SANITIZE=true ...@@ -416,7 +400,7 @@ sudo gitlab-rake gitlab:check SANITIZE=true
If there is a GitLab version mismatch between your backup tar file and the installed If there is a GitLab version mismatch between your backup tar file and the installed
version of GitLab, the restore command will abort with an error. Install the version of GitLab, the restore command will abort with an error. Install the
[correct GitLab version](https://www.gitlab.com/downloads/archives/) and try again. [correct GitLab version](https://about.gitlab.com/downloads/archives/) and try again.
## Configure cron to make daily backups ## Configure cron to make daily backups
......
...@@ -13,7 +13,7 @@ read [this nice tutorial by DigitalOcean](https://www.digitalocean.com/community ...@@ -13,7 +13,7 @@ read [this nice tutorial by DigitalOcean](https://www.digitalocean.com/community
## Locating an existing SSH key pair ## Locating an existing SSH key pair
Before generating a new SSH key check if your system already has one Before generating a new SSH key pair check if your system already has one
at the default location by opening a shell, or Command Prompt on Windows, at the default location by opening a shell, or Command Prompt on Windows,
and running the following command: and running the following command:
...@@ -23,43 +23,49 @@ and running the following command: ...@@ -23,43 +23,49 @@ and running the following command:
type %userprofile%\.ssh\id_rsa.pub type %userprofile%\.ssh\id_rsa.pub
``` ```
**GNU/Linux / macOS / PowerShell:** **Git Bash on Windows / GNU/Linux / macOS / PowerShell:**
```bash ```bash
cat ~/.ssh/id_rsa.pub cat ~/.ssh/id_rsa.pub
``` ```
If you see a string starting with `ssh-rsa` you already have an SSH key pair If you see a string starting with `ssh-rsa` you already have an SSH key pair
and you can skip the next step **Generating a new SSH key pair** and you can skip the generate portion of the next section and skip to the copy
and continue onto **Copying your public SSH key to the clipboard**. to clipboard step.
If you don't see the string or would like to generate a SSH key pair with a If you don't see the string or would like to generate a SSH key pair with a
custom name continue onto the next step. custom name continue onto the next step.
>
**Note:** Public SSH key may also be named as follows:
- `id_dsa.pub`
- `id_ecdsa.pub`
- `id_ed25519.pub`
## Generating a new SSH key pair ## Generating a new SSH key pair
1. To generate a new SSH key, use the following command: 1. To generate a new SSH key pair, use the following command:
**GNU/Linux / macOS:** **Git Bash on Windows / GNU/Linux / macOS:**
```bash ```bash
ssh-keygen -t rsa -C "GitLab" -b 4096 ssh-keygen -t rsa -C "your.email@example.com" -b 4096
``` ```
**Windows:** **Windows:**
On Windows you will need to download Alternatively on Windows you can download
[PuttyGen](http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html) [PuttyGen](http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html)
and follow this [documentation article][winputty] to generate a SSH key pair. and follow [this documentation article][winputty] to generate a SSH key pair.
1. Next, you will be prompted to input a file path to save your key pair to. 1. Next, you will be prompted to input a file path to save your SSH key pair to.
If you don't already have an SSH key pair use the suggested path by pressing If you don't already have an SSH key pair use the suggested path by pressing
enter. Using the suggested path will allow your SSH client enter. Using the suggested path will normally allow your SSH client
to automatically use the key pair with no additional configuration. to automatically use the SSH key pair with no additional configuration.
If you already have a key pair with the suggested file path, you will need If you already have a SSH key pair with the suggested file path, you will need
to input a new file path and declare what host this key pair will be used to input a new file path and declare what host this SSH key pair will be used
for in your `.ssh/config` file, see **Working with non-default SSH key pair paths** for in your `.ssh/config` file, see [**Working with non-default SSH key pair paths**](#working-with-non-default-ssh-key-pair-paths)
for more information. for more information.
1. Once you have input a file path you will be prompted to input a password to 1. Once you have input a file path you will be prompted to input a password to
...@@ -68,12 +74,12 @@ custom name continue onto the next step. ...@@ -68,12 +74,12 @@ custom name continue onto the next step.
pressing enter. pressing enter.
>**Note:** >**Note:**
If you want to change the password of your key, you can use `ssh-keygen -p <keyname>`. If you want to change the password of your SSH key pair, you can use
`ssh-keygen -p <keyname>`.
1. The next step is to copy the public key as we will need it afterwards. 1. The next step is to copy the public SSH key as we will need it afterwards.
To copy your public key to the clipboard, use the appropriate code for your To copy your public SSH key to the clipboard, use the appropriate code below:
operating system below:
**macOS:** **macOS:**
...@@ -93,7 +99,7 @@ custom name continue onto the next step. ...@@ -93,7 +99,7 @@ custom name continue onto the next step.
type %userprofile%\.ssh\id_rsa.pub | clip type %userprofile%\.ssh\id_rsa.pub | clip
``` ```
**Windows PowerShell:** **Git Bash on Windows / Windows PowerShell:**
```bash ```bash
cat ~/.ssh/id_rsa.pub | clip cat ~/.ssh/id_rsa.pub | clip
...@@ -101,7 +107,7 @@ custom name continue onto the next step. ...@@ -101,7 +107,7 @@ custom name continue onto the next step.
1. The final step is to add your public SSH key to GitLab. 1. The final step is to add your public SSH key to GitLab.
Navigate to the 'SSH Keys' tab in you 'Profile Settings'. Navigate to the 'SSH Keys' tab in your 'Profile Settings'.
Paste your key in the 'Key' section and give it a relevant 'Title'. Paste your key in the 'Key' section and give it a relevant 'Title'.
Use an identifiable title like 'Work Laptop - Windows 7' or Use an identifiable title like 'Work Laptop - Windows 7' or
'Home MacBook Pro 15'. 'Home MacBook Pro 15'.
...@@ -109,14 +115,30 @@ custom name continue onto the next step. ...@@ -109,14 +115,30 @@ custom name continue onto the next step.
If you manually copied your public SSH key make sure you copied the entire If you manually copied your public SSH key make sure you copied the entire
key starting with `ssh-rsa` and ending with your email. key starting with `ssh-rsa` and ending with your email.
1. Optionally you can test your setup by running `ssh -T git@example.com`
(replacing `example.com` with your GitLab domain) and verifying that you
receive a `Welcome to GitLab` message.
## Working with non-default SSH key pair paths ## Working with non-default SSH key pair paths
If you used a non-default file path for your GitLab SSH key pair, If you used a non-default file path for your GitLab SSH key pair,
you must configure your SSH client to find your GitLab SSH private key you must configure your SSH client to find your GitLab private SSH key
for connections to your GitLab server (perhaps gitlab.com). for connections to your GitLab server (perhaps `gitlab.com`).
For your current terminal session you can do so using the following commands
(replacing `other_id_rsa` with your private SSH key):
**Git Bash on Windows / GNU/Linux / macOS:**
```bash
eval $(ssh-agent -s)
ssh-add ~/.ssh/other_id_rsa
```
For OpenSSH clients this is configured in the `~/.ssh/config` file. To retain these settings you'll need to save them to a configuration file.
Below are two example host configurations using their own key: For OpenSSH clients this is configured in the `~/.ssh/config` file for some
operating systems.
Below are two example host configurations using their own SSH key:
``` ```
# GitLab.com server # GitLab.com server
...@@ -140,8 +162,8 @@ That's why it needs to uniquely map to a single user. ...@@ -140,8 +162,8 @@ That's why it needs to uniquely map to a single user.
## Deploy keys ## Deploy keys
Deploy keys allow read-only access to multiple projects with a single SSH Deploy keys allow read-only or read-write (if enabled) access to one or
key. multiple projects with a single SSH key pair.
This is really useful for cloning repositories to your Continuous This is really useful for cloning repositories to your Continuous
Integration (CI) server. By using deploy keys, you don't have to setup a Integration (CI) server. By using deploy keys, you don't have to setup a
...@@ -150,7 +172,8 @@ dummy user account. ...@@ -150,7 +172,8 @@ dummy user account.
If you are a project master or owner, you can add a deploy key in the If you are a project master or owner, you can add a deploy key in the
project settings under the section 'Deploy Keys'. Press the 'New Deploy project settings under the section 'Deploy Keys'. Press the 'New Deploy
Key' button and upload a public SSH key. After this, the machine that uses Key' button and upload a public SSH key. After this, the machine that uses
the corresponding private key has read-only access to the project. the corresponding private SSH key has read-only or read-write (if enabled)
access to the project.
You can't add the same deploy key twice with the 'New Deploy Key' option. You can't add the same deploy key twice with the 'New Deploy Key' option.
If you want to add the same key to another project, please enable it in the If you want to add the same key to another project, please enable it in the
...@@ -166,6 +189,18 @@ project. ...@@ -166,6 +189,18 @@ project.
### Eclipse ### Eclipse
How to add your ssh key to Eclipse: https://wiki.eclipse.org/EGit/User_Guide#Eclipse_SSH_Configuration How to add your SSH key to Eclipse: https://wiki.eclipse.org/EGit/User_Guide#Eclipse_SSH_Configuration
[winputty]: https://the.earth.li/~sgtatham/putty/0.67/htmldoc/Chapter8.html#pubkey-puttygen [winputty]: https://the.earth.li/~sgtatham/putty/0.67/htmldoc/Chapter8.html#pubkey-puttygen
## Troubleshooting
If on Git clone you are prompted for a password like `git@gitlab.com's password:`
something is wrong with your SSH setup.
- Ensure that you generated your SSH key pair correctly and added the public SSH
key to your GitLab profile
- Try manually registering your private SSH key using `ssh-agent` as documented
earlier in this document
- Try to debug the connection by running `ssh -Tv git@example.com`
(replacing `example.com` with your GitLab domain)
...@@ -72,14 +72,15 @@ module API ...@@ -72,14 +72,15 @@ module API
status = GenericCommitStatus.running_or_pending.find_or_initialize_by( status = GenericCommitStatus.running_or_pending.find_or_initialize_by(
project: @project, project: @project,
pipeline: pipeline, pipeline: pipeline,
user: current_user,
name: name, name: name,
ref: ref, ref: ref,
target_url: params[:target_url], user: current_user
description: params[:description],
coverage: params[:coverage]
) )
optional_attributes =
attributes_for_keys(%w[target_url description coverage])
status.update(optional_attributes) if optional_attributes.any?
render_validation_error!(status) if status.invalid? render_validation_error!(status) if status.invalid?
begin begin
......
...@@ -111,7 +111,10 @@ module API ...@@ -111,7 +111,10 @@ module API
SharedGroup.represent(project.project_group_links.all, options) SharedGroup.represent(project.project_group_links.all, options)
end end
expose :only_allow_merge_if_pipeline_succeeds expose :only_allow_merge_if_pipeline_succeeds
<<<<<<< HEAD
expose :repository_storage, if: lambda { |_project, options| options[:current_user].try(:admin?) } expose :repository_storage, if: lambda { |_project, options| options[:current_user].try(:admin?) }
=======
>>>>>>> ce/master
expose :request_access_enabled expose :request_access_enabled
expose :only_allow_merge_if_all_discussions_are_resolved expose :only_allow_merge_if_all_discussions_are_resolved
expose :approvals_before_merge expose :approvals_before_merge
......
...@@ -81,6 +81,23 @@ module API ...@@ -81,6 +81,23 @@ module API
environment.destroy environment.destroy
end end
desc 'Stops an existing environment' do
success Entities::Environment
end
params do
requires :environment_id, type: Integer, desc: 'The environment ID'
end
post ':id/environments/:environment_id/stop' do
authorize! :create_deployment, user_project
environment = user_project.environments.find(params[:environment_id])
environment.stop_with_action!(current_user)
status 200
present environment, with: Entities::Environment
end
end end
end end
end end
...@@ -171,6 +171,10 @@ module API ...@@ -171,6 +171,10 @@ module API
items.where(iid: iid) items.where(iid: iid)
end end
def filter_by_search(items, text)
items.search(text)
end
# error helpers # error helpers
def forbidden!(reason = nil) def forbidden!(reason = nil)
......
...@@ -25,6 +25,7 @@ module API ...@@ -25,6 +25,7 @@ module API
optional :sort, type: String, values: %w[asc desc], default: 'desc', optional :sort, type: String, values: %w[asc desc], default: 'desc',
desc: 'Return issues sorted in `asc` or `desc` order.' desc: 'Return issues sorted in `asc` or `desc` order.'
optional :milestone, type: String, desc: 'Return issues for a specific milestone' optional :milestone, type: String, desc: 'Return issues for a specific milestone'
optional :iids, type: Array[Integer], desc: 'The IID array of issues'
use :pagination use :pagination
end end
......
...@@ -31,6 +31,7 @@ module API ...@@ -31,6 +31,7 @@ module API
optional :state, type: String, values: %w[active closed all], default: 'all', optional :state, type: String, values: %w[active closed all], default: 'all',
desc: 'Return "active", "closed", or "all" milestones' desc: 'Return "active", "closed", or "all" milestones'
optional :iid, type: Array[Integer], desc: 'The IID of the milestone' optional :iid, type: Array[Integer], desc: 'The IID of the milestone'
optional :search, type: String, desc: 'The search criteria for the title or description of the milestone'
use :pagination use :pagination
end end
get ":id/milestones" do get ":id/milestones" do
...@@ -39,6 +40,7 @@ module API ...@@ -39,6 +40,7 @@ module API
milestones = user_project.milestones milestones = user_project.milestones
milestones = filter_milestones_state(milestones, params[:state]) milestones = filter_milestones_state(milestones, params[:state])
milestones = filter_by_iid(milestones, params[:iid]) if params[:iid].present? milestones = filter_by_iid(milestones, params[:iid]) if params[:iid].present?
milestones = filter_by_search(milestones, params[:search]) if params[:search]
present paginate(milestones), with: Entities::Milestone present paginate(milestones), with: Entities::Milestone
end end
......
...@@ -46,6 +46,7 @@ module API ...@@ -46,6 +46,7 @@ module API
expose :awardable_id, :awardable_type expose :awardable_id, :awardable_type
end end
<<<<<<< HEAD
class ApplicationSetting < Grape::Entity class ApplicationSetting < Grape::Entity
expose :id expose :id
expose :default_projects_limit expose :default_projects_limit
...@@ -79,6 +80,8 @@ module API ...@@ -79,6 +80,8 @@ module API
expose :terminal_max_session_time expose :terminal_max_session_time
end end
=======
>>>>>>> ce/master
class Project < Grape::Entity class Project < Grape::Entity
expose :id, :description, :default_branch, :tag_list expose :id, :description, :default_branch, :tag_list
expose :public?, as: :public expose :public?, as: :public
...@@ -111,10 +114,15 @@ module API ...@@ -111,10 +114,15 @@ module API
::API::Entities::SharedGroup.represent(project.project_group_links.all, options) ::API::Entities::SharedGroup.represent(project.project_group_links.all, options)
end end
expose :only_allow_merge_if_pipeline_succeeds, as: :only_allow_merge_if_build_succeeds expose :only_allow_merge_if_pipeline_succeeds, as: :only_allow_merge_if_build_succeeds
<<<<<<< HEAD
expose :repository_storage, if: lambda { |_project, options| options[:current_user].try(:admin?) } expose :repository_storage, if: lambda { |_project, options| options[:current_user].try(:admin?) }
expose :request_access_enabled expose :request_access_enabled
expose :only_allow_merge_if_all_discussions_are_resolved expose :only_allow_merge_if_all_discussions_are_resolved
expose :approvals_before_merge expose :approvals_before_merge
=======
expose :request_access_enabled
expose :only_allow_merge_if_all_discussions_are_resolved
>>>>>>> ce/master
expose :statistics, using: 'API::Entities::ProjectStatistics', if: :statistics expose :statistics, using: 'API::Entities::ProjectStatistics', if: :statistics
end end
...@@ -149,6 +157,7 @@ module API ...@@ -149,6 +157,7 @@ module API
expose :merge_status expose :merge_status
expose :diff_head_sha, as: :sha expose :diff_head_sha, as: :sha
expose :merge_commit_sha expose :merge_commit_sha
<<<<<<< HEAD
expose :subscribed do |merge_request, options| expose :subscribed do |merge_request, options|
merge_request.subscribed?(options[:current_user], options[:project]) merge_request.subscribed?(options[:current_user], options[:project])
...@@ -159,6 +168,14 @@ module API ...@@ -159,6 +168,14 @@ module API
expose :should_remove_source_branch?, as: :should_remove_source_branch expose :should_remove_source_branch?, as: :should_remove_source_branch
expose :force_remove_source_branch?, as: :force_remove_source_branch expose :force_remove_source_branch?, as: :force_remove_source_branch
expose :squash expose :squash
=======
expose :subscribed do |merge_request, options|
merge_request.subscribed?(options[:current_user], options[:project])
end
expose :user_notes_count
expose :should_remove_source_branch?, as: :should_remove_source_branch
expose :force_remove_source_branch?, as: :force_remove_source_branch
>>>>>>> ce/master
expose :web_url do |merge_request, options| expose :web_url do |merge_request, options|
Gitlab::UrlBuilder.build(merge_request) Gitlab::UrlBuilder.build(merge_request)
......
...@@ -68,8 +68,12 @@ module API ...@@ -68,8 +68,12 @@ module API
end end
merge_requests = merge_requests.reorder(params[:order_by] => params[:sort]) merge_requests = merge_requests.reorder(params[:order_by] => params[:sort])
<<<<<<< HEAD
present paginate(merge_requests), with: ::API::Entities::MergeRequest, current_user: current_user, project: user_project present paginate(merge_requests), with: ::API::Entities::MergeRequest, current_user: current_user, project: user_project
=======
present paginate(merge_requests), with: ::API::V3::Entities::MergeRequest, current_user: current_user, project: user_project
>>>>>>> ce/master
end end
desc 'Create a merge request' do desc 'Create a merge request' do
...@@ -181,7 +185,7 @@ module API ...@@ -181,7 +185,7 @@ module API
optional :should_remove_source_branch, type: Boolean, optional :should_remove_source_branch, type: Boolean,
desc: 'When true, the source branch will be deleted if possible' desc: 'When true, the source branch will be deleted if possible'
optional :merge_when_build_succeeds, type: Boolean, optional :merge_when_build_succeeds, type: Boolean,
desc: 'When true, this merge request will be merged when the pipeline succeeds' desc: 'When true, this merge request will be merged when the build succeeds'
optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch' optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch'
optional :squash, type: Boolean, desc: 'When true, the commits will be squashed into a single commit on merge' optional :squash, type: Boolean, desc: 'When true, the commits will be squashed into a single commit on merge'
end end
...@@ -222,7 +226,11 @@ module API ...@@ -222,7 +226,11 @@ module API
present merge_request, with: ::API::V3::Entities::MergeRequest, current_user: current_user, project: user_project present merge_request, with: ::API::V3::Entities::MergeRequest, current_user: current_user, project: user_project
end end
<<<<<<< HEAD
desc 'Cancel merge if "Merge When Pipeline Succeeds" is enabled' do desc 'Cancel merge if "Merge When Pipeline Succeeds" is enabled' do
=======
desc 'Cancel merge if "Merge When Build succeeds" is enabled' do
>>>>>>> ce/master
success ::API::V3::Entities::MergeRequest success ::API::V3::Entities::MergeRequest
end end
post "#{path}/cancel_merge_when_build_succeeds" do post "#{path}/cancel_merge_when_build_succeeds" do
......
...@@ -199,13 +199,17 @@ module Gitlab ...@@ -199,13 +199,17 @@ module Gitlab
nil nil
end end
def archive_prefix(ref, sha)
project_name = self.name.chomp('.git')
"#{project_name}-#{ref.parameterize}-#{sha}"
end
def archive_metadata(ref, storage_path, format = "tar.gz") def archive_metadata(ref, storage_path, format = "tar.gz")
ref ||= root_ref ref ||= root_ref
commit = Gitlab::Git::Commit.find(self, ref) commit = Gitlab::Git::Commit.find(self, ref)
return {} if commit.nil? return {} if commit.nil?
project_name = self.name.chomp('.git') prefix = archive_prefix(ref, commit.id)
prefix = "#{project_name}-#{ref}-#{commit.id}"
{ {
'RepoPath' => path, 'RepoPath' => path,
......
...@@ -21,7 +21,6 @@ ...@@ -21,7 +21,6 @@
"dropzone": "^4.2.0", "dropzone": "^4.2.0",
"es6-promise": "^4.0.5", "es6-promise": "^4.0.5",
"jquery": "^2.2.1", "jquery": "^2.2.1",
"jquery-ui": "git+https://github.com/jquery/jquery-ui#1.11.4",
"jquery-ujs": "^1.2.1", "jquery-ujs": "^1.2.1",
"js-cookie": "^2.1.3", "js-cookie": "^2.1.3",
"mousetrap": "^1.4.6", "mousetrap": "^1.4.6",
......
...@@ -43,7 +43,8 @@ describe Projects::MergeRequestsController do ...@@ -43,7 +43,8 @@ describe Projects::MergeRequestsController do
submit_new_merge_request(format: :json) submit_new_merge_request(format: :json)
expect(response).to be_ok expect(response).to be_ok
expect(json_response).not_to be_empty expect(json_response).to have_key 'pipelines'
expect(json_response['pipelines']).not_to be_empty
end end
end end
end end
......
...@@ -2,6 +2,26 @@ require 'spec_helper' ...@@ -2,6 +2,26 @@ require 'spec_helper'
describe RootController do describe RootController do
describe 'GET index' do describe 'GET index' do
context 'when user is not logged in' do
it 'redirects to the sign-in page' do
get :index
expect(response).to redirect_to(new_user_session_path)
end
context 'when a custom home page URL is defined' do
before do
stub_application_setting(home_page_url: 'https://gitlab.com')
end
it 'redirects the user to the custom home page URL' do
get :index
expect(response).to redirect_to('https://gitlab.com')
end
end
end
context 'with a user' do context 'with a user' do
let(:user) { create(:user) } let(:user) { create(:user) }
...@@ -12,55 +32,60 @@ describe RootController do ...@@ -12,55 +32,60 @@ describe RootController do
context 'who has customized their dashboard setting for starred projects' do context 'who has customized their dashboard setting for starred projects' do
before do before do
user.update_attribute(:dashboard, 'stars') user.dashboard = 'stars'
end end
it 'redirects to their specified dashboard' do it 'redirects to their specified dashboard' do
get :index get :index
expect(response).to redirect_to starred_dashboard_projects_path expect(response).to redirect_to starred_dashboard_projects_path
end end
end end
context 'who has customized their dashboard setting for project activities' do context 'who has customized their dashboard setting for project activities' do
before do before do
user.update_attribute(:dashboard, 'project_activity') user.dashboard = 'project_activity'
end end
it 'redirects to the activity list' do it 'redirects to the activity list' do
get :index get :index
expect(response).to redirect_to activity_dashboard_path expect(response).to redirect_to activity_dashboard_path
end end
end end
context 'who has customized their dashboard setting for starred project activities' do context 'who has customized their dashboard setting for starred project activities' do
before do before do
user.update_attribute(:dashboard, 'starred_project_activity') user.dashboard = 'starred_project_activity'
end end
it 'redirects to the activity list' do it 'redirects to the activity list' do
get :index get :index
expect(response).to redirect_to activity_dashboard_path(filter: 'starred') expect(response).to redirect_to activity_dashboard_path(filter: 'starred')
end end
end end
context 'who has customized their dashboard setting for groups' do context 'who has customized their dashboard setting for groups' do
before do before do
user.update_attribute(:dashboard, 'groups') user.dashboard = 'groups'
end end
it 'redirects to their group list' do it 'redirects to their group list' do
get :index get :index
expect(response).to redirect_to dashboard_groups_path expect(response).to redirect_to dashboard_groups_path
end end
end end
context 'who has customized their dashboard setting for todos' do context 'who has customized their dashboard setting for todos' do
before do before do
user.update_attribute(:dashboard, 'todos') user.dashboard = 'todos'
end end
it 'redirects to their todo list' do it 'redirects to their todo list' do
get :index get :index
expect(response).to redirect_to dashboard_todos_path expect(response).to redirect_to dashboard_todos_path
end end
end end
...@@ -68,6 +93,7 @@ describe RootController do ...@@ -68,6 +93,7 @@ describe RootController do
context 'who uses the default dashboard setting' do context 'who uses the default dashboard setting' do
it 'renders the default dashboard' do it 'renders the default dashboard' do
get :index get :index
expect(response).to render_template 'dashboard/projects/index' expect(response).to render_template 'dashboard/projects/index'
end end
end end
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
require('~/boards/models/list'); require('~/boards/models/list');
require('~/boards/models/label'); require('~/boards/models/label');
require('~/boards/stores/boards_store'); require('~/boards/stores/boards_store');
const boardCard = require('~/boards/components/board_card'); const boardCard = require('~/boards/components/board_card').default;
require('./mock_data'); require('./mock_data');
describe('Issue card', () => { describe('Issue card', () => {
......
/* global boardsMockInterceptor */
/* global BoardService */
/* global List */
/* global listObj */
import Vue from 'vue';
import boardNewIssue from '~/boards/components/board_new_issue';
require('~/boards/models/list');
require('./mock_data');
require('es6-promise').polyfill();
describe('Issue boards new issue form', () => {
let vm;
let list;
const promiseReturn = {
json() {
return {
iid: 100,
};
},
};
const submitIssue = () => {
vm.$el.querySelector('.btn-success').click();
};
beforeEach((done) => {
const BoardNewIssueComp = Vue.extend(boardNewIssue);
Vue.http.interceptors.push(boardsMockInterceptor);
gl.boardService = new BoardService('/test/issue-boards/board', '', '1');
gl.issueBoards.BoardsStore.create();
gl.IssueBoardsApp = new Vue();
setTimeout(() => {
list = new List(listObj);
spyOn(gl.boardService, 'newIssue').and.callFake(() => new Promise((resolve, reject) => {
if (vm.title === 'error') {
reject();
} else {
resolve(promiseReturn);
}
}));
vm = new BoardNewIssueComp({
propsData: {
list,
},
}).$mount();
done();
}, 0);
});
afterEach(() => {
Vue.http.interceptors = _.without(Vue.http.interceptors, boardsMockInterceptor);
});
it('disables submit button if title is empty', () => {
expect(vm.$el.querySelector('.btn-success').disabled).toBe(true);
});
it('enables submit button if title is not empty', (done) => {
vm.title = 'Testing Title';
setTimeout(() => {
expect(vm.$el.querySelector('.form-control').value).toBe('Testing Title');
expect(vm.$el.querySelector('.btn-success').disabled).not.toBe(true);
done();
}, 0);
});
it('clears title after clicking cancel', (done) => {
vm.$el.querySelector('.btn-default').click();
setTimeout(() => {
expect(vm.title).toBe('');
done();
}, 0);
});
it('does not create new issue if title is empty', (done) => {
submitIssue();
setTimeout(() => {
expect(gl.boardService.newIssue).not.toHaveBeenCalled();
done();
}, 0);
});
describe('submit success', () => {
it('creates new issue', (done) => {
vm.title = 'submit title';
setTimeout(() => {
submitIssue();
expect(gl.boardService.newIssue).toHaveBeenCalled();
done();
}, 0);
});
it('enables button after submit', (done) => {
vm.title = 'submit issue';
setTimeout(() => {
submitIssue();
expect(vm.$el.querySelector('.btn-success').disbled).not.toBe(true);
done();
}, 0);
});
it('clears title after submit', (done) => {
vm.title = 'submit issue';
setTimeout(() => {
submitIssue();
expect(vm.title).toBe('');
done();
}, 0);
});
it('adds new issue to list after submit', (done) => {
vm.title = 'submit issue';
setTimeout(() => {
submitIssue();
expect(list.issues.length).toBe(2);
expect(list.issues[1].title).toBe('submit issue');
expect(list.issues[1].subscribed).toBe(true);
done();
}, 0);
});
it('sets detail issue after submit', (done) => {
vm.title = 'submit issue';
setTimeout(() => {
submitIssue();
expect(gl.issueBoards.BoardsStore.detail.issue.title).toBe('submit issue');
done();
});
});
it('sets detail list after submit', (done) => {
vm.title = 'submit issue';
setTimeout(() => {
submitIssue();
expect(gl.issueBoards.BoardsStore.detail.list.id).toBe(list.id);
done();
}, 0);
});
});
describe('submit error', () => {
it('removes issue', (done) => {
vm.title = 'error';
setTimeout(() => {
submitIssue();
setTimeout(() => {
expect(list.issues.length).toBe(1);
done();
}, 500);
}, 0);
});
it('shows error', (done) => {
vm.title = 'error';
submitIssue();
setTimeout(() => {
submitIssue();
setTimeout(() => {
expect(vm.error).toBe(true);
done();
}, 500);
}, 0);
});
});
});
/* eslint-disable space-before-function-paren, one-var, no-var, one-var-declaration-per-line, no-return-assign, quotes, max-len */ /* eslint-disable space-before-function-paren, one-var, no-var, one-var-declaration-per-line, no-return-assign, quotes, max-len */
/* global NewBranchForm */ /* global NewBranchForm */
require('jquery-ui/ui/autocomplete');
require('~/new_branch_form'); require('~/new_branch_form');
(function() { (function() {
......
...@@ -47,7 +47,7 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -47,7 +47,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
end end
end end
describe :branch_names do describe '#branch_names' do
subject { repository.branch_names } subject { repository.branch_names }
it 'has SeedRepo::Repo::BRANCHES.size elements' do it 'has SeedRepo::Repo::BRANCHES.size elements' do
...@@ -57,7 +57,7 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -57,7 +57,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
it { is_expected.not_to include("branch-from-space") } it { is_expected.not_to include("branch-from-space") }
end end
describe :tag_names do describe '#tag_names' do
subject { repository.tag_names } subject { repository.tag_names }
it { is_expected.to be_kind_of Array } it { is_expected.to be_kind_of Array }
...@@ -78,49 +78,63 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -78,49 +78,63 @@ describe Gitlab::Git::Repository, seed_helper: true do
it { expect(metadata['ArchivePath']).to end_with extenstion } it { expect(metadata['ArchivePath']).to end_with extenstion }
end end
describe :archive do describe '#archive_prefix' do
let(:project_name) { 'project-name'}
before do
expect(repository).to receive(:name).once.and_return(project_name)
end
it 'returns parameterised string for a ref containing slashes' do
prefix = repository.archive_prefix('test/branch', 'SHA')
expect(prefix).to eq("#{project_name}-test-branch-SHA")
end
end
describe '#archive' do
let(:metadata) { repository.archive_metadata('master', '/tmp') } let(:metadata) { repository.archive_metadata('master', '/tmp') }
it_should_behave_like 'archive check', '.tar.gz' it_should_behave_like 'archive check', '.tar.gz'
end end
describe :archive_zip do describe '#archive_zip' do
let(:metadata) { repository.archive_metadata('master', '/tmp', 'zip') } let(:metadata) { repository.archive_metadata('master', '/tmp', 'zip') }
it_should_behave_like 'archive check', '.zip' it_should_behave_like 'archive check', '.zip'
end end
describe :archive_bz2 do describe '#archive_bz2' do
let(:metadata) { repository.archive_metadata('master', '/tmp', 'tbz2') } let(:metadata) { repository.archive_metadata('master', '/tmp', 'tbz2') }
it_should_behave_like 'archive check', '.tar.bz2' it_should_behave_like 'archive check', '.tar.bz2'
end end
describe :archive_fallback do describe '#archive_fallback' do
let(:metadata) { repository.archive_metadata('master', '/tmp', 'madeup') } let(:metadata) { repository.archive_metadata('master', '/tmp', 'madeup') }
it_should_behave_like 'archive check', '.tar.gz' it_should_behave_like 'archive check', '.tar.gz'
end end
describe :size do describe '#size' do
subject { repository.size } subject { repository.size }
it { is_expected.to be < 2 } it { is_expected.to be < 2 }
end end
describe :has_commits? do describe '#has_commits?' do
it { expect(repository.has_commits?).to be_truthy } it { expect(repository.has_commits?).to be_truthy }
end end
describe :empty? do describe '#empty?' do
it { expect(repository.empty?).to be_falsey } it { expect(repository.empty?).to be_falsey }
end end
describe :bare? do describe '#bare?' do
it { expect(repository.bare?).to be_truthy } it { expect(repository.bare?).to be_truthy }
end end
describe :heads do describe '#heads' do
let(:heads) { repository.heads } let(:heads) { repository.heads }
subject { heads } subject { heads }
...@@ -147,7 +161,7 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -147,7 +161,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
end end
end end
describe :ref_names do describe '#ref_names' do
let(:ref_names) { repository.ref_names } let(:ref_names) { repository.ref_names }
subject { ref_names } subject { ref_names }
...@@ -164,7 +178,7 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -164,7 +178,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
end end
end end
describe :search_files do describe '#search_files' do
let(:results) { repository.search_files('rails', 'master') } let(:results) { repository.search_files('rails', 'master') }
subject { results } subject { results }
...@@ -200,7 +214,7 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -200,7 +214,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
end end
end end
context :submodules do context '#submodules' do
let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) } let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) }
context 'where repo has submodules' do context 'where repo has submodules' do
...@@ -264,7 +278,7 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -264,7 +278,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
end end
end end
describe :commit_count do describe '#commit_count' do
it { expect(repository.commit_count("master")).to eq(25) } it { expect(repository.commit_count("master")).to eq(25) }
it { expect(repository.commit_count("feature")).to eq(9) } it { expect(repository.commit_count("feature")).to eq(9) }
end end
......
...@@ -181,20 +181,6 @@ describe Ci::Build, :models do ...@@ -181,20 +181,6 @@ describe Ci::Build, :models do
end end
end end
describe '#create_from' do
before do
build.status = 'success'
build.save
end
let(:create_from_build) { Ci::Build.create_from build }
it 'exists a pending task' do
expect(Ci::Build.pending.count(:all)).to eq 0
create_from_build
expect(Ci::Build.pending.count(:all)).to be > 0
end
end
describe '#depends_on_builds' do describe '#depends_on_builds' do
let!(:build) { create(:ci_build, pipeline: pipeline, name: 'build', stage_idx: 0, stage: 'build') } let!(:build) { create(:ci_build, pipeline: pipeline, name: 'build', stage_idx: 0, stage: 'build') }
let!(:rspec_test) { create(:ci_build, pipeline: pipeline, name: 'rspec', stage_idx: 1, stage: 'test') } let!(:rspec_test) { create(:ci_build, pipeline: pipeline, name: 'rspec', stage_idx: 1, stage: 'test') }
......
...@@ -151,18 +151,17 @@ describe API::CommitStatuses, api: true do ...@@ -151,18 +151,17 @@ describe API::CommitStatuses, api: true do
end end
context 'with all optional parameters' do context 'with all optional parameters' do
before do context 'when creating a commit status' do
optional_params = { state: 'success', it 'creates commit status' do
post api(post_url, developer), {
state: 'success',
context: 'coverage', context: 'coverage',
ref: 'develop', ref: 'develop',
description: 'test', description: 'test',
coverage: 80.0, coverage: 80.0,
target_url: 'http://gitlab.com/status' } target_url: 'http://gitlab.com/status'
}
post api(post_url, developer), optional_params
end
it 'creates commit status' do
expect(response).to have_http_status(201) expect(response).to have_http_status(201)
expect(json_response['sha']).to eq(commit.id) expect(json_response['sha']).to eq(commit.id)
expect(json_response['status']).to eq('success') expect(json_response['status']).to eq('success')
...@@ -174,6 +173,43 @@ describe API::CommitStatuses, api: true do ...@@ -174,6 +173,43 @@ describe API::CommitStatuses, api: true do
end end
end end
context 'when updatig a commit status' do
before do
post api(post_url, developer), {
state: 'running',
context: 'coverage',
ref: 'develop',
description: 'coverage test',
coverage: 0.0,
target_url: 'http://gitlab.com/status'
}
post api(post_url, developer), {
state: 'success',
name: 'coverage',
ref: 'develop',
description: 'new description',
coverage: 90.0
}
end
it 'updates a commit status' do
expect(response).to have_http_status(201)
expect(json_response['sha']).to eq(commit.id)
expect(json_response['status']).to eq('success')
expect(json_response['name']).to eq('coverage')
expect(json_response['ref']).to eq('develop')
expect(json_response['coverage']).to eq(90.0)
expect(json_response['description']).to eq('new description')
expect(json_response['target_url']).to eq('http://gitlab.com/status')
end
it 'does not create a new commit status' do
expect(CommitStatus.count).to eq 1
end
end
end
context 'when status is invalid' do context 'when status is invalid' do
before { post api(post_url, developer), state: 'invalid' } before { post api(post_url, developer), state: 'invalid' }
......
...@@ -141,4 +141,39 @@ describe API::Environments, api: true do ...@@ -141,4 +141,39 @@ describe API::Environments, api: true do
end end
end end
end end
describe 'POST /projects/:id/environments/:environment_id/stop' do
context 'as a master' do
context 'with a stoppable environment' do
before do
environment.update(state: :available)
post api("/projects/#{project.id}/environments/#{environment.id}/stop", user)
end
it 'returns a 200' do
expect(response).to have_http_status(200)
end
it 'actually stops the environment' do
expect(environment.reload).to be_stopped
end
end
it 'returns a 404 for non existing id' do
post api("/projects/#{project.id}/environments/12345/stop", user)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 Not found')
end
end
context 'a non member' do
it 'rejects the request' do
post api("/projects/#{project.id}/environments/#{environment.id}/stop", non_member)
expect(response).to have_http_status(404)
end
end
end
end end
...@@ -212,6 +212,25 @@ describe API::Issues, api: true do ...@@ -212,6 +212,25 @@ describe API::Issues, api: true do
expect(json_response.first['id']).to eq(confidential_issue.id) expect(json_response.first['id']).to eq(confidential_issue.id)
end end
it 'returns an array of issues found by iids' do
get api('/issues', user), iids: [closed_issue.iid]
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(closed_issue.id)
end
it 'returns an empty array if iid does not exist' do
get api("/issues", user), iids: [99999]
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
it 'sorts by created_at descending by default' do it 'sorts by created_at descending by default' do
get api('/issues', user) get api('/issues', user)
...@@ -377,6 +396,25 @@ describe API::Issues, api: true do ...@@ -377,6 +396,25 @@ describe API::Issues, api: true do
expect(json_response.first['labels']).to eq([label_c.title, label_b.title, group_label.title]) expect(json_response.first['labels']).to eq([label_c.title, label_b.title, group_label.title])
end end
it 'returns an array of issues found by iids' do
get api(base_url, user), iids: [group_issue.iid]
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(group_issue.id)
end
it 'returns an empty array if iid does not exist' do
get api(base_url, user), iids: [99999]
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
it 'returns an empty array if no group issue matches labels' do it 'returns an empty array if no group issue matches labels' do
get api("#{base_url}?labels=foo,bar", user) get api("#{base_url}?labels=foo,bar", user)
...@@ -586,6 +624,25 @@ describe API::Issues, api: true do ...@@ -586,6 +624,25 @@ describe API::Issues, api: true do
expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title]) expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title])
end end
it 'returns an array of issues found by iids' do
get api("#{base_url}/issues", user), iids: [issue.iid]
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(issue.id)
end
it 'returns an empty array if iid does not exist' do
get api("#{base_url}/issues", user), iids: [99999]
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
it 'returns an empty array if not all labels matches' do it 'returns an empty array if not all labels matches' do
get api("#{base_url}/issues?labels=#{label.title},foo", user) get api("#{base_url}/issues?labels=#{label.title},foo", user)
......
...@@ -4,8 +4,8 @@ describe API::Milestones, api: true do ...@@ -4,8 +4,8 @@ describe API::Milestones, api: true do
include ApiHelpers include ApiHelpers
let(:user) { create(:user) } let(:user) { create(:user) }
let!(:project) { create(:empty_project, namespace: user.namespace ) } let!(:project) { create(:empty_project, namespace: user.namespace ) }
let!(:closed_milestone) { create(:closed_milestone, project: project) } let!(:closed_milestone) { create(:closed_milestone, project: project, title: 'version1', description: 'closed milestone') }
let!(:milestone) { create(:milestone, project: project) } let!(:milestone) { create(:milestone, project: project, title: 'version2', description: 'open milestone') }
before { project.team << [user, :developer] } before { project.team << [user, :developer] }
...@@ -60,17 +60,28 @@ describe API::Milestones, api: true do ...@@ -60,17 +60,28 @@ describe API::Milestones, api: true do
get api("/projects/#{project.id}/milestones", user), iid: [milestone.iid, closed_milestone.iid] get api("/projects/#{project.id}/milestones", user), iid: [milestone.iid, closed_milestone.iid]
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response.size).to eq(2) expect(json_response.size).to eq(2)
expect(json_response.first['title']).to eq milestone.title expect(json_response.first['title']).to eq milestone.title
expect(json_response.first['id']).to eq milestone.id expect(json_response.first['id']).to eq milestone.id
end end
it 'returns a project milestone by iid array' do it 'returns a project milestone by searching for title' do
get api("/projects/#{project.id}/milestones", user), iid: [milestone.iid, closed_milestone.iid] get api("/projects/#{project.id}/milestones", user), search: 'version2'
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(response).to include_pagination_headers expect(response).to include_pagination_headers
expect(json_response.size).to eq(2) expect(json_response.size).to eq(1)
expect(json_response.first['title']).to eq milestone.title
expect(json_response.first['id']).to eq milestone.id
end
it 'returns a project milestones by searching for description' do
get api("/projects/#{project.id}/milestones", user), search: 'open'
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response.size).to eq(1)
expect(json_response.first['title']).to eq milestone.title expect(json_response.first['title']).to eq milestone.title
expect(json_response.first['id']).to eq milestone.id expect(json_response.first['id']).to eq milestone.id
end end
......
...@@ -70,7 +70,10 @@ describe MergeRequests::RefreshService, services: true do ...@@ -70,7 +70,10 @@ describe MergeRequests::RefreshService, services: true do
it { expect(@merge_request.notes).not_to be_empty } it { expect(@merge_request.notes).not_to be_empty }
it { expect(@merge_request).to be_open } it { expect(@merge_request).to be_open }
<<<<<<< HEAD
it { expect(@merge_request.approvals).to be_empty } it { expect(@merge_request.approvals).to be_empty }
=======
>>>>>>> ce/master
it { expect(@merge_request.merge_when_pipeline_succeeds).to be_falsey } it { expect(@merge_request.merge_when_pipeline_succeeds).to be_falsey }
it { expect(@merge_request.diff_head_sha).to eq(@newrev) } it { expect(@merge_request.diff_head_sha).to eq(@newrev) }
it { expect(@fork_merge_request).to be_open } it { expect(@fork_merge_request).to be_open }
......
require 'spec_helper'
describe 'ci/status/_badge', :view do
let(:user) { create(:user) }
let(:project) { create(:empty_project, :private) }
let(:pipeline) { create(:ci_pipeline, project: project) }
context 'when rendering status for build' do
let(:build) do
create(:ci_build, :success, pipeline: pipeline)
end
context 'when user has ability to see details' do
before do
project.add_developer(user)
end
it 'has link to build details page' do
details_path = namespace_project_build_path(
project.namespace, project, build)
render_status(build)
expect(rendered).to have_link 'passed', href: details_path
end
end
context 'when user do not have ability to see build details' do
before do
render_status(build)
end
it 'contains build status text' do
expect(rendered).to have_content 'passed'
end
it 'does not contain links' do
expect(rendered).not_to have_link 'passed'
end
end
end
context 'when rendering status for external job' do
context 'when user has ability to see commit status details' do
before do
project.add_developer(user)
end
context 'status has external target url' do
before do
external_job = create(:generic_commit_status,
status: :running,
pipeline: pipeline,
target_url: 'http://gitlab.com')
render_status(external_job)
end
it 'contains valid commit status text' do
expect(rendered).to have_content 'running'
end
it 'has link to external status page' do
expect(rendered).to have_link 'running', href: 'http://gitlab.com'
end
end
context 'status do not have external target url' do
before do
external_job = create(:generic_commit_status, status: :canceled)
render_status(external_job)
end
it 'contains valid commit status text' do
expect(rendered).to have_content 'canceled'
end
it 'has link to external status page' do
expect(rendered).not_to have_link 'canceled'
end
end
end
end
def render_status(resource)
render 'ci/status/badge', status: resource.detailed_status(user)
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment