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'
gem 'gon', '~> 6.1.0'
gem 'jquery-atwho-rails', '~> 1.3.2'
gem 'jquery-rails', '~> 4.1.0'
gem 'jquery-ui-rails', '~> 5.0.0'
gem 'request_store', '~> 1.3'
gem 'select2-rails', '~> 3.5.9'
gem 'virtus', '~> 1.0.1'
......
......@@ -346,8 +346,11 @@ GEM
grpc (1.1.2)
google-protobuf (~> 3.1)
googleauth (~> 0.5.1)
<<<<<<< HEAD
gssapi (1.2.0)
ffi (>= 1.0.1)
=======
>>>>>>> ce/master
haml (4.0.7)
tilt
haml_lint (0.21.0)
......@@ -398,8 +401,6 @@ GEM
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
jquery-ui-rails (5.0.5)
railties (>= 3.2.16)
json (1.8.6)
json-schema (2.6.2)
addressable (~> 2.3.8)
......@@ -936,7 +937,6 @@ DEPENDENCIES
jira-ruby (~> 1.1.2)
jquery-atwho-rails (~> 1.3.2)
jquery-rails (~> 4.1.0)
jquery-ui-rails (~> 5.0.0)
json-schema (~> 2.6.2)
jwt (~> 1.5.6)
kaminari (~> 0.17.0)
......
......@@ -3,7 +3,7 @@ require('./issue_card_inner');
const Store = gl.issueBoards.BoardsStore;
module.exports = {
export default {
name: 'BoardsIssueCard',
template: `
<li class="card"
......
......@@ -2,8 +2,8 @@
/* global Vue */
/* global Sortable */
const boardCard = require('./board_card');
require('./board_new_issue');
import boardNewIssue from './board_new_issue';
import boardCard from './board_card';
(() => {
const Store = gl.issueBoards.BoardsStore;
......@@ -15,7 +15,7 @@ require('./board_new_issue');
template: '#js-board-list-template',
components: {
boardCard,
'board-new-issue': gl.issueBoards.BoardNewIssue
boardNewIssue,
},
props: {
disabled: Boolean,
......@@ -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 () {
const options = gl.issueBoards.getBoardSortableDefaultOptions({
......@@ -115,6 +121,9 @@ require('./board_new_issue');
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');
return pipelinesService.all()
.then(response => response.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;
})
.catch(() => {
......
......@@ -79,12 +79,12 @@ require('./comparison_pane');
<div class='help-button pull-right'
v-if='!showHelpState'
@click='toggleHelpState(true)'>
<i class='fa fa-question-circle'></i>
<i class='fa fa-question-circle' aria-hidden='true'></i>
</div>
<div class='close-help-button pull-right'
v-if='showHelpState'
@click='toggleHelpState(false)'>
<i class='fa fa-close'></i>
<i class='fa fa-close' aria-hidden='true'></i>
</div>
</div>
<div class='time-tracking-content hide-collapsed'>
......
......@@ -13,7 +13,7 @@
this.onPickImageClick = this.onPickImageClick.bind(this);
this.fileInput = $(input);
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.exportHeight = exportHeight;
this.cropBoxWidth = cropBoxWidth;
......
.calender-block {
padding-left: 0;
padding-right: 0;
border-top: 0;
direction: rtl;
@media (min-width: $screen-sm-min) and (max-width: $screen-md-max) {
......
......@@ -271,6 +271,7 @@ span.idiff {
font-size: 13px;
line-height: 28px;
display: inline-block;
float: none;
}
}
}
......@@ -21,6 +21,7 @@ $dark-highlight-color: $black;
$dark-pre-hll-bg: #373b41;
$dark-hll-bg: #373b41;
$dark-over-bg: #9f9ab5;
$dark-expanded-bg: #3e3e3e;
$dark-c: #969896;
$dark-err: #c66;
$dark-k: #b294bb;
......@@ -155,6 +156,22 @@ $dark-il: #de935f;
.line_content.match {
@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
......
......@@ -14,6 +14,7 @@ $monokai-line-empty-border: darken($monokai-line-empty-bg, 15%);
$monokai-diff-border: #808080;
$monokai-highlight-bg: #ffe792;
$monokai-over-bg: #9f9ab5;
$monokai-expanded-bg: #3e3e3e;
$monokai-new-bg: rgba(166, 226, 46, 0.1);
$monokai-new-idiff: rgba(166, 226, 46, 0.15);
......@@ -155,6 +156,22 @@ $monokai-gi: #a6e22e;
.line_content.match {
@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
......
......@@ -18,6 +18,7 @@ $solarized-dark-line-color-old: #7a6c71;
$solarized-dark-highlight: #094554;
$solarized-dark-hll-bg: #174652;
$solarized-dark-over-bg: #9f9ab5;
$solarized-dark-expanded-bg: #010d10;
$solarized-dark-c: #586e75;
$solarized-dark-err: #93a1a1;
$solarized-dark-g: #93a1a1;
......@@ -159,6 +160,22 @@ $solarized-dark-il: #2aa198;
.line_content.match {
@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
......
......@@ -19,6 +19,8 @@ $solarized-light-line-color-old: #ad9186;
$solarized-light-highlight: #eee8d5;
$solarized-light-hll-bg: #ddd8c5;
$solarized-light-over-bg: #ded7fc;
$solarized-light-expanded-border: #d2cdbd;
$solarized-light-expanded-bg: #ece6d4;
$solarized-light-c: #93a1a1;
$solarized-light-err: #586e75;
$solarized-light-g: #586e75;
......@@ -166,6 +168,22 @@ $solarized-light-il: #2aa198;
.line_content.match {
@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
......
......@@ -8,6 +8,8 @@ $white-highlight: #fafe3d;
$white-pre-hll-bg: #f8eec7;
$white-hll-bg: #f8f8f8;
$white-over-bg: #ded7fc;
$white-expanded-border: #e0e0e0;
$white-expanded-bg: #f7f7f7;
$white-c: #998;
$white-err: #a61717;
$white-err-bg: #e3d2d2;
......@@ -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 {
&.old {
background-color: $line-removed;
......
......@@ -133,8 +133,13 @@
width: 35px;
font-weight: normal;
&:hover {
text-decoration: underline;
&[disabled] {
cursor: default;
&:hover,
&:active {
text-decoration: none;
}
}
}
}
......
......@@ -72,14 +72,6 @@ class ApplicationController < ActionController::Base
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)
application_trace = ActionDispatch::ExceptionWrapper.new(env, exception).application_trace
application_trace.map!{ |t| " #{t}\n" }
......@@ -291,19 +283,6 @@ class ApplicationController < ActionController::Base
session[:skip_tfa] && session[:skip_tfa] > Time.current
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
# to perform authentication.
# https://developers.yubico.com/U2F/App_ID.html
......
......@@ -10,9 +10,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_action :module_enabled
before_action :merge_request, only: [
: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,
# EE
: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 :define_show_vars, only: [:show, :diffs, :commits, :conflicts, :conflict_for_path, :builds, :pipelines]
......@@ -248,9 +252,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
format.json do
define_pipelines_vars
render json: PipelineSerializer
render json: {
pipelines: PipelineSerializer
.new(project: @project, user: @current_user)
.represent(@pipelines)
}
end
end
end
......
......@@ -8,7 +8,9 @@
# `DashboardController#show`, which is the default.
class RootController < Dashboard::ProjectsController
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
super
......@@ -16,23 +18,38 @@ class RootController < Dashboard::ProjectsController
private
def redirect_to_custom_dashboard
return redirect_to new_user_session_path unless current_user
def redirect_unlogged_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
when 'stars'
flash.keep
redirect_to starred_dashboard_projects_path
redirect_to(starred_dashboard_projects_path)
when 'project_activity'
redirect_to activity_dashboard_path
redirect_to(activity_dashboard_path)
when 'starred_project_activity'
redirect_to activity_dashboard_path(filter: 'starred')
redirect_to(activity_dashboard_path(filter: 'starred'))
when 'groups'
redirect_to dashboard_groups_path
redirect_to(dashboard_groups_path)
when 'todos'
redirect_to dashboard_todos_path
else
return
redirect_to(dashboard_todos_path)
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
......@@ -19,7 +19,7 @@ module ButtonHelper
title = data[:title] || 'Copy to clipboard'
data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data)
content_tag :button,
icon('clipboard'),
icon('clipboard', 'aria-hidden': 'true'),
class: "btn #{css_class}",
data: data,
type: :button,
......
module IssuablesHelper
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
def sidebar_gutter_collapsed_class
......
......@@ -56,15 +56,6 @@ module Ci
pending.unstarted.order('created_at ASC').first
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)
Ci::RetryBuildService
.new(build.project, current_user)
......
......@@ -22,6 +22,12 @@ class Project < ActiveRecord::Base
extend Gitlab::ConfigHelper
<<<<<<< HEAD
=======
BoardLimitExceeded = Class.new(StandardError)
NUMBER_OF_PERMITTED_BOARDS = 1
>>>>>>> ce/master
UNKNOWN_IMPORT_URL = 'http://unknown.git'.freeze
cache_markdown_field :description, pipeline: :description
......
......@@ -371,7 +371,11 @@ class User < ActiveRecord::Base
# Return (create if necessary) the ghost user. The ghost user
# owns records previously belonging to deleted users.
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
......@@ -1053,10 +1057,14 @@ class User < ActiveRecord::Base
end
end
def self.create_ghost_user
# Since we only want a single ghost user in an instance, we use an
def self.unique_internal(scope, username, email_pattern, &b)
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.
lease_key = "ghost_user_creation"
lease_key = "user:unique_internal:#{username}"
lease = Gitlab::ExclusiveLease.new(lease_key, timeout: 1.minute.to_i)
until uuid = lease.try_obtain
......@@ -1065,25 +1073,25 @@ class User < ActiveRecord::Base
sleep(1)
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)
# and the time we acquired the lock.
ghost_user = User.find_by_ghost(true)
return ghost_user if ghost_user.present?
existing_user = uncached { scope.first }
return existing_user if existing_user.present?
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)
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.'
User.create(
username: username, password: Devise.friendly_token, bio: bio,
email: email, name: "Ghost User", state: :blocked, ghost: true
scope.create(
username: username,
password: Devise.friendly_token,
email: email,
&creation_block
)
ensure
Gitlab::ExclusiveLease.cancel(lease_key, uuid)
......
......@@ -8,8 +8,11 @@ class MergeRequestEntity < IssuableEntity
expose :merge_status
expose :merge_user_id
expose :merge_when_pipeline_succeeds
<<<<<<< HEAD
expose :rebase_commit_sha
expose :rebase_in_progress?, if: { type: :full }
=======
>>>>>>> ce/master
expose :source_branch
expose :source_project_id
expose :target_branch
......
......@@ -79,6 +79,7 @@ module MergeRequests
end
end
<<<<<<< HEAD
# Note: Closed merge requests also need approvals reset.
def reset_approvals_for_merge_requests
merge_requests = merge_requests_for(@branch_name, mr_states: [:opened, :reopened, :closed])
......@@ -95,6 +96,8 @@ module MergeRequests
end
end
=======
>>>>>>> ce/master
def reset_merge_when_pipeline_succeeds
merge_requests_for_source_branch.each(&:reset_merge_when_pipeline_succeeds)
end
......
......@@ -104,6 +104,7 @@ class TodoService
def merge_request_build_failed(merge_request)
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?
<<<<<<< HEAD
end
# When new approvers are added for a merge request:
......@@ -112,6 +113,8 @@ class TodoService
#
def add_merge_request_approvers(merge_request, approvers)
create_approval_required_todos(merge_request, approvers, merge_request.author)
=======
>>>>>>> ce/master
end
# When a new commit is pushed to a merge request we should:
......
.btn-group
= view_on_environment_button(@commit.sha, @path, @environment) if @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),
class: 'btn btn-sm', target: '_blank'
-# only show normal/blame view links for text files
......@@ -18,8 +19,12 @@
tree_join(@commit.sha, @path)), class: 'btn btn-sm js-data-file-blob-permalink-url'
- if current_user
<<<<<<< HEAD
.btn-group{ role: "group" }
= lock_file_link(html_options: {class: 'btn btn-sm path-lock'})
=======
.btn-group{ role: "group" }<
>>>>>>> ce/master
- if blob_text_viewable?(@blob)
= edit_blob_link
= replace_blob_link
......
......@@ -25,12 +25,13 @@
#blob-content-holder.blob-content-holder
%article.file-holder
.js-file-title.file-title
= blob_icon blob.mode, blob.name
%strong
= blob.name
%small
= number_to_human_size(blob_size(blob))
.js-file-title.file-title-flex-parent
.file-header-content
= blob_icon blob.mode, blob.name
%strong.file-title-name
= blob.name
%small
= number_to_human_size(blob_size(blob))
.file-actions.hidden-xs
= render "actions"
= render blob, blob: blob
......@@ -9,20 +9,20 @@
- line_old = line_new - @form.offset
- line_content = capture do
%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
- when :inline
%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 } }
%a{ href: "##{line_new}", data: { linenumber: line_new } }
%a{ href: "#", data: { linenumber: line_new }, disabled: true }
= line_content
- when :parallel
%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
%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
- if @form.unfold? && @form.bottom? && @form.to < @blob.lines.size
......
......@@ -2,28 +2,8 @@
.board-list-loading.text-center{ "v-if" => "loading" }
= icon("spinner spin")
- if can? current_user, :create_issue, @project
%board-new-issue{ "inline-template" => true,
":list" => "list",
%board-new-issue{ ":list" => "list",
"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",
"v-show" => "!loading",
":data-board" => "list.id",
......
......@@ -5,9 +5,12 @@
- when :merge_when_pipeline_succeeds
:plain
$('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/merge_when_pipeline_succeeds'))}");
<<<<<<< HEAD
- when :hook_validation_error
:plain
$('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/error'))}");
=======
>>>>>>> ce/master
- when :sha_mismatch
:plain
$('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/sha_mismatch'))}");
......
......@@ -11,10 +11,17 @@
.accept-action
- if @pipeline && @pipeline.active?
%span.btn-group
<<<<<<< HEAD
= button_tag class: "btn btn-create js-merge-button merge_when_pipeline_succeeds", disabled: !@merge_request.approved?, ":disabled" => "disableAcceptance" do
Merge When 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-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')
%span.sr-only
Select Merge Moment
......
......@@ -9,16 +9,16 @@
- if current_user
%span.issuable-header-text.hide-collapsed.pull-left
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
- 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
- if todo
Mark done
- else
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|
.block.assignee
......@@ -26,10 +26,10 @@
- if issuable.assignee
= link_to_member(@project, issuable.assignee, size: 24)
- else
= icon('user')
= icon('user', 'aria-hidden': 'true')
.title.hide-collapsed
Assignee
= icon('spinner spin', class: 'block-loading')
= icon('spinner spin', class: 'block-loading', 'aria-hidden': 'true')
- if can_edit_issuable
= link_to 'Edit', '#', class: 'edit-link pull-right'
.value.hide-collapsed
......@@ -37,7 +37,7 @@
= link_to_member(@project, issuable.assignee, size: 32, extra_class: 'bold') do
- 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' }
= icon('exclamation-triangle')
= icon('exclamation-triangle', 'aria-hidden': 'true')
%span.username
= issuable.assignee.to_reference
- else
......@@ -54,7 +54,7 @@
.block.milestone
.sidebar-collapsed-icon
= icon('clock-o')
= icon('clock-o', 'aria-hidden': 'true')
%span
- if issuable.milestone
%span.has-tooltip{ title: milestone_remaining_days(issuable.milestone), data: { container: 'body', html: 1, placement: 'left' } }
......@@ -63,7 +63,7 @@
None
.title.hide-collapsed
Milestone
= icon('spinner spin', class: 'block-loading')
= icon('spinner spin', class: 'block-loading', 'aria-hidden': 'true')
- if can_edit_issuable
= link_to 'Edit', '#', class: 'edit-link pull-right'
.value.hide-collapsed
......@@ -81,16 +81,16 @@
// Fallback while content is loading
.title.hide-collapsed
Time tracking
= icon('spinner spin')
= icon('spinner spin', 'aria-hidden': 'true')
- if issuable.has_attribute?(:due_date)
.block.due_date
.sidebar-collapsed-icon
= icon('calendar')
= icon('calendar', 'aria-hidden': 'true')
%span.js-due-date-sidebar-value
= issuable.due_date.try(:to_s, :medium) || 'None'
.title.hide-collapsed
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)
= link_to 'Edit', '#', class: 'edit-link pull-right'
.value.hide-collapsed
......@@ -110,7 +110,7 @@
.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) } }
%span.dropdown-toggle-text Due date
= icon('chevron-down')
= icon('chevron-down', 'aria-hidden': 'true')
.dropdown-menu.dropdown-menu-due-date
= dropdown_title('Due date')
= dropdown_content do
......@@ -120,12 +120,12 @@
- selected_labels = issuable.labels
.block.labels
.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
= selected_labels.size
.title.hide-collapsed
Labels
= icon('spinner spin', class: 'block-loading')
= icon('spinner spin', class: 'block-loading', 'aria-hidden': 'true')
- if can_edit_issuable
= link_to 'Edit', '#', class: 'edit-link pull-right'
.value.issuable-show-labels.hide-collapsed{ class: ("has-labels" if selected_labels.any?) }
......@@ -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) } }
%span.dropdown-toggle-text{ class: ("is-default" if selected_labels.empty?) }
= 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
= render partial: "shared/issuable/label_page_default"
- if can? current_user, :admin_label, @project and @project
......@@ -179,7 +179,7 @@
- subscribed = issuable.subscribed?(current_user, @project)
.block.light.subscription{ data: { url: toggle_subscription_path(issuable) } }
.sidebar-collapsed-icon
= icon('rss')
= icon('rss', 'aria-hidden': 'true')
%span.issuable-header-text.hide-collapsed.pull-left
Notifications
- 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
<<<<<<< HEAD
merge_request: 1335
=======
merge_request: 9622
>>>>>>> ce/master
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
credentials: true,
headers: :any,
methods: :any,
expose: ['Link']
expose: ['Link', 'X-Total', 'X-Total-Pages', 'X-Per-Page', 'X-Page', 'X-Next-Page', 'X-Prev-Page']
end
# Cross-origin requests must not have the session cookie available
......@@ -133,7 +133,7 @@ module Gitlab
credentials: false,
headers: :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
......
class DisableInvalidServiceTemplates < ActiveRecord::Migration
DOWNTIME = false
unless defined?(Service)
class Service < ActiveRecord::Base
self.inheritance_column = nil
end
class Service < ActiveRecord::Base
self.inheritance_column = nil
end
def up
......
......@@ -160,7 +160,7 @@ The following table shows the possible return codes for API requests.
| Return values | Description |
| ------------- | ----------- |
| `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. |
| `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. |
......
......@@ -33,7 +33,7 @@ Example response:
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
......@@ -64,7 +64,7 @@ Example response:
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
......@@ -94,7 +94,7 @@ Example response:
## 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
......@@ -107,4 +107,35 @@ DELETE /projects/:id/environments/:environment_id
```bash
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
GET /issues?labels=foo,bar&state=opened
GET /issues?milestone=1.0.0
GET /issues?milestone=1.0.0&state=opened
GET /issues?iids[]=42&iids[]=43
```
| Attribute | Type | Required | Description |
......@@ -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`|
| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned |
| `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` |
| `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
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&state=opened
GET /groups/:id/issues?iids[]=42&iids[]=43
```
| Attribute | Type | Required | Description |
......@@ -110,6 +113,7 @@ GET /groups/:id/issues?milestone=1.0.0&state=opened
| `id` | integer | yes | The ID of a group |
| `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 |
| `iids` | Array[integer] | no | Return only the issues having the given `iid` |
| `milestone` | string| no | The milestone title |
| `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` |
......@@ -183,12 +187,13 @@ GET /projects/:id/issues?labels=foo,bar
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&state=opened
GET /projects/:id/issues?iids[]=42&iids[]=43
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `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`|
| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned |
| `milestone` | string| no | The milestone title |
......
......@@ -480,6 +480,7 @@ Parameters:
- `merge_commit_message` (optional) - Custom merge commit message
- `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
<<<<<<< HEAD
- `sha` (optional) - if present, then this SHA must
......@@ -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 |
| `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
{
......
......@@ -10,6 +10,7 @@ GET /projects/:id/milestones?iid=42
GET /projects/:id/milestones?iid[]=42&iid[]=43
GET /projects/:id/milestones?state=active
GET /projects/:id/milestones?state=closed
GET /projects/:id/milestones?search=version
```
Parameters:
......@@ -18,7 +19,8 @@ Parameters:
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
| `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
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/milestones
......@@ -115,4 +117,4 @@ GET /projects/:id/milestones/:milestone_id/merge_requests
Parameters:
- `id` (required) - The ID of a project
- `milestone_id` (required) - The ID of a project milestone
\ No newline at end of file
- `milestone_id` (required) - The ID of a project milestone
......@@ -241,7 +241,10 @@ Parameters:
"group_access_level": 10
}
],
<<<<<<< HEAD
"repository_storage": "default",
=======
>>>>>>> ce/master
"only_allow_merge_if_pipeline_succeeds": false,
"only_allow_merge_if_all_discussions_are_resolved": false,
"request_access_enabled": false
......
......@@ -5,6 +5,8 @@
Get a list of repository files and directories in a project. This endpoint can
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
```
......
......@@ -26,7 +26,7 @@ Parameters:
"committer_email": "jack@example.com",
"id": "2695effb5807a22ff3d138d593fd856244e155e7",
"message": "Initial commit",
"parents_ids": [
"parent_ids": [
"2a4b78934375d7f53875269ffd4f45fd83a84ebe"
]
},
......@@ -110,7 +110,7 @@ Parameters:
"committer_email": "jack@example.com",
"id": "2695effb5807a22ff3d138d593fd856244e155e7",
"message": "Initial commit",
"parents_ids": [
"parent_ids": [
"2a4b78934375d7f53875269ffd4f45fd83a84ebe"
]
},
......
......@@ -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)
- 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)
- 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)
- 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)
......
......@@ -14,7 +14,7 @@
### Nazim Ramesh
- 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
......@@ -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
- 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
......
......@@ -155,10 +155,9 @@ page](https://golang.org/dl).
## 4. Node
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
manage javascript dependencies. In many distros the versions provided by the
official package repositories are out of date, so we'll need to install through
the following commands:
javascript assets, and yarn >= v0.17.0 to manage javascript dependencies.
In many distros the versions provided by the official package repositories
are out of date, so we'll need to install through the following commands:
# install node v7.x
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
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:
```
......@@ -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`.
The available options are:
* `db`
* `uploads` (attachments)
* `repositories`
* `builds` (CI build output logs)
* `artifacts` (CI build artifacts)
* `lfs` (LFS objects)
* `pages` (pages content)
- `db` (database)
- `uploads` (attachments)
- `repositories` (Git repositories data)
- `builds` (CI job output logs)
- `artifacts` (CI job artifacts)
- `lfs` (LFS objects)
- `registry` (Container Registry images)
- `pages` (Pages content)
Use a comma to specify several options at the same time:
......@@ -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
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
......
......@@ -13,7 +13,7 @@ read [this nice tutorial by DigitalOcean](https://www.digitalocean.com/community
## 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,
and running the following command:
......@@ -23,43 +23,49 @@ and running the following command:
type %userprofile%\.ssh\id_rsa.pub
```
**GNU/Linux / macOS / PowerShell:**
**Git Bash on Windows / GNU/Linux / macOS / PowerShell:**
```bash
cat ~/.ssh/id_rsa.pub
```
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 continue onto **Copying your public SSH key to the clipboard**.
and you can skip the generate portion of the next section and skip to the copy
to clipboard step.
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.
>
**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
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
ssh-keygen -t rsa -C "GitLab" -b 4096
ssh-keygen -t rsa -C "your.email@example.com" -b 4096
```
**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)
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
enter. Using the suggested path will allow your SSH client
to automatically use the key pair with no additional configuration.
enter. Using the suggested path will normally allow your SSH client
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
to input a new file path and declare what host this key pair will be used
for in your `.ssh/config` file, see **Working with non-default SSH key pair paths**
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 SSH key pair will be used
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.
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.
pressing enter.
>**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
operating system below:
To copy your public SSH key to the clipboard, use the appropriate code below:
**macOS:**
......@@ -93,7 +99,7 @@ custom name continue onto the next step.
type %userprofile%\.ssh\id_rsa.pub | clip
```
**Windows PowerShell:**
**Git Bash on Windows / Windows PowerShell:**
```bash
cat ~/.ssh/id_rsa.pub | clip
......@@ -101,22 +107,38 @@ custom name continue onto the next step.
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'.
Use an identifiable title like 'Work Laptop - Windows 7' or
'Home MacBook Pro 15'.
If you manually copied your public SSH key make sure you copied the entire
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
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
for connections to your GitLab server (perhaps gitlab.com).
you must configure your SSH client to find your GitLab private SSH key
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):
For OpenSSH clients this is configured in the `~/.ssh/config` file.
Below are two example host configurations using their own key:
**Git Bash on Windows / GNU/Linux / macOS:**
```bash
eval $(ssh-agent -s)
ssh-add ~/.ssh/other_id_rsa
```
To retain these settings you'll need to save them to a configuration file.
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
......@@ -140,8 +162,8 @@ That's why it needs to uniquely map to a single user.
## Deploy keys
Deploy keys allow read-only access to multiple projects with a single SSH
key.
Deploy keys allow read-only or read-write (if enabled) access to one or
multiple projects with a single SSH key pair.
This is really useful for cloning repositories to your Continuous
Integration (CI) server. By using deploy keys, you don't have to setup a
......@@ -150,7 +172,8 @@ dummy user account.
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
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.
If you want to add the same key to another project, please enable it in the
......@@ -166,6 +189,18 @@ project.
### 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
## 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
status = GenericCommitStatus.running_or_pending.find_or_initialize_by(
project: @project,
pipeline: pipeline,
user: current_user,
name: name,
ref: ref,
target_url: params[:target_url],
description: params[:description],
coverage: params[:coverage]
user: current_user
)
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?
begin
......
......@@ -111,7 +111,10 @@ module API
SharedGroup.represent(project.project_group_links.all, options)
end
expose :only_allow_merge_if_pipeline_succeeds
<<<<<<< HEAD
expose :repository_storage, if: lambda { |_project, options| options[:current_user].try(:admin?) }
=======
>>>>>>> ce/master
expose :request_access_enabled
expose :only_allow_merge_if_all_discussions_are_resolved
expose :approvals_before_merge
......
......@@ -81,6 +81,23 @@ module API
environment.destroy
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
......@@ -171,6 +171,10 @@ module API
items.where(iid: iid)
end
def filter_by_search(items, text)
items.search(text)
end
# error helpers
def forbidden!(reason = nil)
......
......@@ -25,6 +25,7 @@ module API
optional :sort, type: String, values: %w[asc desc], default: 'desc',
desc: 'Return issues sorted in `asc` or `desc` order.'
optional :milestone, type: String, desc: 'Return issues for a specific milestone'
optional :iids, type: Array[Integer], desc: 'The IID array of issues'
use :pagination
end
......
......@@ -31,6 +31,7 @@ module API
optional :state, type: String, values: %w[active closed all], default: 'all',
desc: 'Return "active", "closed", or "all" milestones'
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
end
get ":id/milestones" do
......@@ -39,6 +40,7 @@ module API
milestones = user_project.milestones
milestones = filter_milestones_state(milestones, params[:state])
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
end
......
......@@ -46,6 +46,7 @@ module API
expose :awardable_id, :awardable_type
end
<<<<<<< HEAD
class ApplicationSetting < Grape::Entity
expose :id
expose :default_projects_limit
......@@ -79,6 +80,8 @@ module API
expose :terminal_max_session_time
end
=======
>>>>>>> ce/master
class Project < Grape::Entity
expose :id, :description, :default_branch, :tag_list
expose :public?, as: :public
......@@ -111,10 +114,15 @@ module API
::API::Entities::SharedGroup.represent(project.project_group_links.all, options)
end
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 :request_access_enabled
expose :only_allow_merge_if_all_discussions_are_resolved
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
end
......@@ -149,6 +157,7 @@ module API
expose :merge_status
expose :diff_head_sha, as: :sha
expose :merge_commit_sha
<<<<<<< HEAD
expose :subscribed do |merge_request, options|
merge_request.subscribed?(options[:current_user], options[:project])
......@@ -159,6 +168,14 @@ module API
expose :should_remove_source_branch?, as: :should_remove_source_branch
expose :force_remove_source_branch?, as: :force_remove_source_branch
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|
Gitlab::UrlBuilder.build(merge_request)
......
......@@ -68,8 +68,12 @@ module API
end
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::V3::Entities::MergeRequest, current_user: current_user, project: user_project
>>>>>>> ce/master
end
desc 'Create a merge request' do
......@@ -181,7 +185,7 @@ module API
optional :should_remove_source_branch, type: Boolean,
desc: 'When true, the source branch will be deleted if possible'
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 :squash, type: Boolean, desc: 'When true, the commits will be squashed into a single commit on merge'
end
......@@ -222,7 +226,11 @@ module API
present merge_request, with: ::API::V3::Entities::MergeRequest, current_user: current_user, project: user_project
end
<<<<<<< HEAD
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
end
post "#{path}/cancel_merge_when_build_succeeds" do
......
......@@ -199,13 +199,17 @@ module Gitlab
nil
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")
ref ||= root_ref
commit = Gitlab::Git::Commit.find(self, ref)
return {} if commit.nil?
project_name = self.name.chomp('.git')
prefix = "#{project_name}-#{ref}-#{commit.id}"
prefix = archive_prefix(ref, commit.id)
{
'RepoPath' => path,
......
......@@ -21,7 +21,6 @@
"dropzone": "^4.2.0",
"es6-promise": "^4.0.5",
"jquery": "^2.2.1",
"jquery-ui": "git+https://github.com/jquery/jquery-ui#1.11.4",
"jquery-ujs": "^1.2.1",
"js-cookie": "^2.1.3",
"mousetrap": "^1.4.6",
......
......@@ -43,7 +43,8 @@ describe Projects::MergeRequestsController do
submit_new_merge_request(format: :json)
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
......
......@@ -2,6 +2,26 @@ require 'spec_helper'
describe RootController 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
let(:user) { create(:user) }
......@@ -12,55 +32,60 @@ describe RootController do
context 'who has customized their dashboard setting for starred projects' do
before do
user.update_attribute(:dashboard, 'stars')
user.dashboard = 'stars'
end
it 'redirects to their specified dashboard' do
get :index
expect(response).to redirect_to starred_dashboard_projects_path
end
end
context 'who has customized their dashboard setting for project activities' do
before do
user.update_attribute(:dashboard, 'project_activity')
user.dashboard = 'project_activity'
end
it 'redirects to the activity list' do
get :index
expect(response).to redirect_to activity_dashboard_path
end
end
context 'who has customized their dashboard setting for starred project activities' do
before do
user.update_attribute(:dashboard, 'starred_project_activity')
user.dashboard = 'starred_project_activity'
end
it 'redirects to the activity list' do
get :index
expect(response).to redirect_to activity_dashboard_path(filter: 'starred')
end
end
context 'who has customized their dashboard setting for groups' do
before do
user.update_attribute(:dashboard, 'groups')
user.dashboard = 'groups'
end
it 'redirects to their group list' do
get :index
expect(response).to redirect_to dashboard_groups_path
end
end
context 'who has customized their dashboard setting for todos' do
before do
user.update_attribute(:dashboard, 'todos')
user.dashboard = 'todos'
end
it 'redirects to their todo list' do
get :index
expect(response).to redirect_to dashboard_todos_path
end
end
......@@ -68,6 +93,7 @@ describe RootController do
context 'who uses the default dashboard setting' do
it 'renders the default dashboard' do
get :index
expect(response).to render_template 'dashboard/projects/index'
end
end
......
......@@ -8,7 +8,7 @@
require('~/boards/models/list');
require('~/boards/models/label');
require('~/boards/stores/boards_store');
const boardCard = require('~/boards/components/board_card');
const boardCard = require('~/boards/components/board_card').default;
require('./mock_data');
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 */
/* global NewBranchForm */
require('jquery-ui/ui/autocomplete');
require('~/new_branch_form');
(function() {
......
......@@ -47,7 +47,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
describe :branch_names do
describe '#branch_names' do
subject { repository.branch_names }
it 'has SeedRepo::Repo::BRANCHES.size elements' do
......@@ -57,7 +57,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
it { is_expected.not_to include("branch-from-space") }
end
describe :tag_names do
describe '#tag_names' do
subject { repository.tag_names }
it { is_expected.to be_kind_of Array }
......@@ -78,49 +78,63 @@ describe Gitlab::Git::Repository, seed_helper: true do
it { expect(metadata['ArchivePath']).to end_with extenstion }
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') }
it_should_behave_like 'archive check', '.tar.gz'
end
describe :archive_zip do
describe '#archive_zip' do
let(:metadata) { repository.archive_metadata('master', '/tmp', 'zip') }
it_should_behave_like 'archive check', '.zip'
end
describe :archive_bz2 do
describe '#archive_bz2' do
let(:metadata) { repository.archive_metadata('master', '/tmp', 'tbz2') }
it_should_behave_like 'archive check', '.tar.bz2'
end
describe :archive_fallback do
describe '#archive_fallback' do
let(:metadata) { repository.archive_metadata('master', '/tmp', 'madeup') }
it_should_behave_like 'archive check', '.tar.gz'
end
describe :size do
describe '#size' do
subject { repository.size }
it { is_expected.to be < 2 }
end
describe :has_commits? do
describe '#has_commits?' do
it { expect(repository.has_commits?).to be_truthy }
end
describe :empty? do
describe '#empty?' do
it { expect(repository.empty?).to be_falsey }
end
describe :bare? do
describe '#bare?' do
it { expect(repository.bare?).to be_truthy }
end
describe :heads do
describe '#heads' do
let(:heads) { repository.heads }
subject { heads }
......@@ -147,7 +161,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
describe :ref_names do
describe '#ref_names' do
let(:ref_names) { repository.ref_names }
subject { ref_names }
......@@ -164,7 +178,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
describe :search_files do
describe '#search_files' do
let(:results) { repository.search_files('rails', 'master') }
subject { results }
......@@ -200,7 +214,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
context :submodules do
context '#submodules' do
let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) }
context 'where repo has submodules' do
......@@ -264,7 +278,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
describe :commit_count do
describe '#commit_count' do
it { expect(repository.commit_count("master")).to eq(25) }
it { expect(repository.commit_count("feature")).to eq(9) }
end
......
......@@ -181,20 +181,6 @@ describe Ci::Build, :models do
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
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') }
......
......@@ -151,26 +151,62 @@ describe API::CommitStatuses, api: true do
end
context 'with all optional parameters' do
before do
optional_params = { state: 'success',
context: 'coverage',
ref: 'develop',
description: 'test',
coverage: 80.0,
target_url: 'http://gitlab.com/status' }
post api(post_url, developer), optional_params
context 'when creating a commit status' do
it 'creates commit status' do
post api(post_url, developer), {
state: 'success',
context: 'coverage',
ref: 'develop',
description: 'test',
coverage: 80.0,
target_url: 'http://gitlab.com/status'
}
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(80.0)
expect(json_response['description']).to eq('test')
expect(json_response['target_url']).to eq('http://gitlab.com/status')
end
end
it 'creates 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(80.0)
expect(json_response['description']).to eq('test')
expect(json_response['target_url']).to eq('http://gitlab.com/status')
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
......
......@@ -141,4 +141,39 @@ describe API::Environments, api: true do
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
......@@ -212,6 +212,25 @@ describe API::Issues, api: true do
expect(json_response.first['id']).to eq(confidential_issue.id)
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
get api('/issues', user)
......@@ -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])
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
get api("#{base_url}?labels=foo,bar", user)
......@@ -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])
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
get api("#{base_url}/issues?labels=#{label.title},foo", user)
......
......@@ -4,8 +4,8 @@ describe API::Milestones, api: true do
include ApiHelpers
let(:user) { create(:user) }
let!(:project) { create(:empty_project, namespace: user.namespace ) }
let!(:closed_milestone) { create(:closed_milestone, project: project) }
let!(:milestone) { create(:milestone, project: project) }
let!(:closed_milestone) { create(:closed_milestone, project: project, title: 'version1', description: 'closed milestone') }
let!(:milestone) { create(:milestone, project: project, title: 'version2', description: 'open milestone') }
before { project.team << [user, :developer] }
......@@ -60,17 +60,28 @@ describe API::Milestones, api: true do
get api("/projects/#{project.id}/milestones", user), iid: [milestone.iid, closed_milestone.iid]
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response.size).to eq(2)
expect(json_response.first['title']).to eq milestone.title
expect(json_response.first['id']).to eq milestone.id
end
it 'returns a project milestone by iid array' do
get api("/projects/#{project.id}/milestones", user), iid: [milestone.iid, closed_milestone.iid]
it 'returns a project milestone by searching for title' do
get api("/projects/#{project.id}/milestones", user), search: 'version2'
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(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['id']).to eq milestone.id
end
......
......@@ -70,7 +70,10 @@ describe MergeRequests::RefreshService, services: true do
it { expect(@merge_request.notes).not_to be_empty }
it { expect(@merge_request).to be_open }
<<<<<<< HEAD
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.diff_head_sha).to eq(@newrev) }
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