Commit 5c5d13c4 authored by barthc's avatar barthc

Merge branch 'master' into prevent_authored_awardable_votes

parents 29f818e6 51087cfa
### Summary
(Summarize the bug encountered concisely)
### Steps to reproduce
(How one can reproduce the issue - this is very important)
### Expected behavior
(What you should see instead)
### Actual behaviour
(What actually happens)
### Relevant logs and/or screenshots
(Paste any relevant logs - please use code blocks (```) to format console output,
logs, and code as it's very hard to read otherwise.)
### Output of checks
#### Results of GitLab application Check
(For installations with omnibus-gitlab package run and paste the output of:
`sudo gitlab-rake gitlab:check SANITIZE=true`)
(For installations from source run and paste the output of:
`sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true`)
(we will only investigate if the tests are passing)
#### Results of GitLab environment info
(For installations with omnibus-gitlab package run and paste the output of:
`sudo gitlab-rake gitlab:env:info`)
(For installations from source run and paste the output of:
`sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`)
### Possible fixes
(If you can, link to the line of code that might be responsible for the problem)
### Description
(Include problem, use cases, benefits, and/or goals)
### Proposal
### Links / references
Please view this file on the master branch, on stable branches it's out of date.
v 8.12.0 (unreleased)
- Make push events have equal vertical spacing.
- Add two-factor recovery endpoint to internal API !5510
- Remove vendor prefixes for linear-gradient CSS (ClemMakesApps)
- Add font color contrast to external label in admin area (ClemMakesApps)
- Change logo animation to CSS (ClemMakesApps)
- Change merge_error column from string to text type
- Reduce contributions calendar data payload (ClemMakesApps)
- Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel)
- Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling)
- Shorten task status phrase (ClemMakesApps)
- Add hover color to emoji icon (ClemMakesApps)
- Fix branches page dropdown sort alignment (ClemMakesApps)
- Add white background for no readme container (ClemMakesApps)
- Optimistic locking for Issues and Merge Requests (title and description overriding prevention)
- Add `wiki_page_events` to project hook APIs (Ben Boeckel)
- Remove Gitorious import
- Fix inconsistent background color for filter input field (ClemMakesApps)
- Add Sentry logging to API calls
- Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling)
- Remove unused mixins (ClemMakesApps)
- Fix groups sort dropdown alignment (ClemMakesApps)
- Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps)
- Fix markdown help references (ClemMakesApps)
- Added tests for diff notes
- Add a button to download latest successful artifacts for branches and tags !5142
- Add delimiter to project stars and forks count (ClemMakesApps)
- Fix badge count alignment (ClemMakesApps)
- Fix branch title trailing space on hover (ClemMakesApps)
......@@ -26,6 +35,7 @@ v 8.12.0 (unreleased)
- Update merge_requests.md with a simpler way to check out a merge request. !5944
- Fix button missing type (ClemMakesApps)
- Move to project dropdown with infinite scroll for better performance
- Fix leaking of submit buttons outside the width of a main container !18731 (originally by @pavelloz)
- Load branches asynchronously in Cherry Pick and Revert dialogs.
- Add merge request versions !5467
- Change using size to use count and caching it for number of group members. !5935
......@@ -34,11 +44,24 @@ v 8.12.0 (unreleased)
- Capitalize mentioned issue timeline notes (ClemMakesApps)
- Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger)
- Adds response mime type to transaction metric action when it's not HTML
- Fix hover leading space bug in pipeline graph !5980
- User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496
v 8.11.4 (unreleased)
- Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner)
v 8.11.4 (unreleased)
- Fix resolving conflicts on forks
v 8.11.4 (unreleased)
- Fix diff commenting on merge requests created prior to 8.10
v 8.11.3 (unreleased)
- Do not enforce using hash with hidden key in CI configuration. !6079
- Allow system info page to handle case where info is unavailable
- Label list shows all issues (opened or closed) with that label
- Don't show resolve conflicts link before MR status is updated
- Fix "Wiki" link not appearing in navigation for projects with external wiki
- Fix IE11 fork button bug !598
- Don't prevent viewing the MR when git refs for conflicts can't be found on disk
- Fix external issue tracker "Issues" link leading to 404s
......
......@@ -129,7 +129,7 @@ request that potentially fixes it.
### Feature proposals
To create a feature proposal for CE and CI, open an issue on the
To create a feature proposal for CE, open an issue on the
[issue tracker of CE][ce-tracker].
For feature proposals for EE, open an issue on the
......@@ -144,16 +144,7 @@ code snippet right after your description in a new line: `~"feature proposal"`.
Please keep feature proposals as small and simple as possible, complex ones
might be edited to make them small and simple.
You are encouraged to use the template below for feature proposals.
```
## Description
Include problem, use cases, benefits, and/or goals
## Proposal
## Links / references
```
Please submit Feature Proposals using the ['Feature Proposal' issue template](.gitlab/issue_templates/Feature Proposal.md) provided on the issue tracker.
For changes in the interface, it can be helpful to create a mockup first.
If you want to create something yourself, consider opening an issue first to
......@@ -166,55 +157,11 @@ submitting your own, there's a good chance somebody else had the same issue or
feature proposal. Show your support with an award emoji and/or join the
discussion.
Please submit bugs using the following template in the issue description area.
Please submit bugs using the ['Bug' issue template](.gitlab/issue_templates/Bug.md) provided on the issue tracker.
The text in the parenthesis is there to help you with what to include. Omit it
when submitting the actual issue. You can copy-paste it and then edit as you
see fit.
```
## Summary
(Summarize your issue in one sentence - what goes wrong, what did you expect to happen)
## Steps to reproduce
(How one can reproduce the issue - this is very important)
## Expected behavior
(What you should see instead)
## Relevant logs and/or screenshots
(Paste any relevant logs - please use code blocks (```) to format console output,
logs, and code as it's very hard to read otherwise.)
## Output of checks
### Results of GitLab Application Check
(For installations with omnibus-gitlab package run and paste the output of:
sudo gitlab-rake gitlab:check SANITIZE=true)
(For installations from source run and paste the output of:
sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true)
(we will only investigate if the tests are passing)
### Results of GitLab Environment Info
(For installations with omnibus-gitlab package run and paste the output of:
sudo gitlab-rake gitlab:env:info)
(For installations from source run and paste the output of:
sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production)
## Possible fixes
(If you can, link to the line of code that might be responsible for the problem)
```
### Issue weight
Issue weight allows us to get an idea of the amount of work required to solve
......
......@@ -117,7 +117,7 @@
}
});
} else {
return elements.show();
return elements.show().removeClass('option-hidden');
}
}
};
......@@ -190,9 +190,9 @@
currentIndex = -1;
NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link, .option-hidden';
NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link';
SELECTABLE_CLASSES = ".dropdown-content li:not(" + NON_SELECTABLE_CLASSES + ")";
SELECTABLE_CLASSES = ".dropdown-content li:not(" + NON_SELECTABLE_CLASSES + ", .option-hidden)";
CURSOR_SELECT_SCROLL_PADDING = 5
......@@ -565,10 +565,6 @@
} else {
field.remove();
}
if (this.options.toggleLabel) {
this.updateLabel(selectedObject, el, this);
}
return selectedObject;
} else if (el.hasClass(INDETERMINATE_CLASS)) {
el.addClass(ACTIVE_CLASS);
el.removeClass(INDETERMINATE_CLASS);
......@@ -578,7 +574,6 @@
if (!field.length && fieldName) {
this.addInput(fieldName, value, selectedObject);
}
return selectedObject;
} else {
if (!this.options.multiSelect || el.hasClass('dropdown-clear-active')) {
this.dropdown.find("." + ACTIVE_CLASS).removeClass(ACTIVE_CLASS);
......@@ -590,9 +585,6 @@
field.remove();
}
el.addClass(ACTIVE_CLASS);
if (this.options.toggleLabel) {
this.updateLabel(selectedObject, el, this);
}
if (value != null) {
if (!field.length && fieldName) {
this.addInput(fieldName, value, selectedObject);
......@@ -600,8 +592,14 @@
field.val(value).trigger('change');
}
}
return selectedObject;
}
// Update label right after input has been added
if (this.options.toggleLabel) {
this.updateLabel(selectedObject, el, this);
}
return selectedObject;
};
GitLabDropdown.prototype.addInput = function(fieldName, value, selectedObject) {
......
(function() {
var clearHighlights, currentTimer, defaultClass, delay, firstPiece, pieceIndex, pieces, start, stop, work;
Turbolinks.enableProgressBar();
defaultClass = 'tanuki-shape';
pieces = ['path#tanuki-right-cheek', 'path#tanuki-right-eye, path#tanuki-right-ear', 'path#tanuki-nose', 'path#tanuki-left-eye, path#tanuki-left-ear', 'path#tanuki-left-cheek'];
pieceIndex = 0;
firstPiece = pieces[0];
currentTimer = null;
delay = 150;
clearHighlights = function() {
return $("." + defaultClass + ".highlight").attr('class', defaultClass);
};
start = function() {
clearHighlights();
pieceIndex = 0;
if (pieces[0] !== firstPiece) {
pieces.reverse();
}
if (currentTimer) {
clearInterval(currentTimer);
}
return currentTimer = setInterval(work, delay);
$('.tanuki-logo').addClass('animate');
};
stop = function() {
clearInterval(currentTimer);
return clearHighlights();
};
work = function() {
clearHighlights();
$(pieces[pieceIndex]).attr('class', defaultClass + " highlight");
if (pieceIndex === pieces.length - 1) {
pieceIndex = 0;
return pieces.reverse();
} else {
return pieceIndex++;
}
$('.tanuki-logo').removeClass('animate');
};
$(document).on('page:fetch', start);
......
......@@ -75,10 +75,8 @@ class MergeConflictResolver {
window.location.href = data.redirect_to;
})
.error(() => {
new Flash('Something went wrong!');
})
.always(() => {
this.vue.isSubmitting = false;
new Flash('Something went wrong!');
});
}
......
......@@ -24,6 +24,7 @@
@import "framework/issue_box.scss";
@import "framework/jquery.scss";
@import "framework/lists.scss";
@import "framework/logo.scss";
@import "framework/markdown_area.scss";
@import "framework/mobile.scss";
@import "framework/modal.scss";
......
......@@ -195,6 +195,12 @@
.separator + .dropdown-header {
padding-top: 2px;
}
.unclickable {
cursor: not-allowed;
padding: 5px 8px;
color: $dropdown-header-color;
}
}
.dropdown-menu-large {
......
......@@ -19,7 +19,6 @@ input[type='text'].danger {
}
.form-actions {
margin: -$gl-padding;
margin-top: 0;
margin-bottom: -$gl-padding;
padding: $gl-padding;
......
......@@ -2,16 +2,6 @@
* Application Header
*
*/
@mixin tanuki-logo-colors($path-color) {
fill: $path-color;
transition: all 0.8s;
&:hover,
&.highlight {
fill: lighten($path-color, 25%);
transition: all 0.1s;
}
}
header {
transition: padding $sidebar-transition-duration;
......@@ -25,7 +15,7 @@ header {
margin: 8px 0;
text-align: center;
#tanuki-logo, img {
.tanuki-logo, img {
height: 36px;
}
}
......@@ -205,26 +195,6 @@ header {
}
}
#tanuki-logo {
#tanuki-left-ear,
#tanuki-right-ear,
#tanuki-nose {
@include tanuki-logo-colors($tanuki-red);
}
#tanuki-left-eye,
#tanuki-right-eye {
@include tanuki-logo-colors($tanuki-orange);
}
#tanuki-left-cheek,
#tanuki-right-cheek {
@include tanuki-logo-colors($tanuki-yellow);
}
}
@media (max-width: $screen-xs-max) {
header .container-fluid {
font-size: 18px;
......
@mixin unique-keyframes {
$animation-name: unique-id();
@include webkit-prefix(animation-name, $animation-name);
@-webkit-keyframes #{$animation-name} {
@content;
}
@keyframes #{$animation-name} {
@content;
}
}
@mixin tanuki-logo-colors($path-color) {
fill: $path-color;
transition: all 0.8s;
&:hover {
fill: lighten($path-color, 25%);
transition: all 0.1s;
}
}
@mixin tanuki-second-highlight-animations($tanuki-color) {
@include unique-keyframes {
10%, 80% {
fill: #{$tanuki-color}
}
20%, 90% {
fill: lighten($tanuki-color, 25%);
}
}
}
@mixin tanuki-forth-highlight-animations($tanuki-color) {
@include unique-keyframes {
30%, 60% {
fill: #{$tanuki-color};
}
40%, 70% {
fill: lighten($tanuki-color, 25%);
}
}
}
.tanuki-logo {
.tanuki-left-ear,
.tanuki-right-ear,
.tanuki-nose {
@include tanuki-logo-colors($tanuki-red);
}
.tanuki-left-eye,
.tanuki-right-eye {
@include tanuki-logo-colors($tanuki-orange);
}
.tanuki-left-cheek,
.tanuki-right-cheek {
@include tanuki-logo-colors($tanuki-yellow);
}
&.animate {
.tanuki-shape {
@include webkit-prefix(animation-duration, 1.5s);
@include webkit-prefix(animation-iteration-count, infinite);
}
.tanuki-left-cheek {
@include unique-keyframes {
0%, 10%, 100% {
fill: lighten($tanuki-yellow, 25%);
}
90% {
fill: $tanuki-yellow;
}
}
}
.tanuki-left-eye {
@include tanuki-second-highlight-animations($tanuki-orange);
}
.tanuki-left-ear {
@include tanuki-second-highlight-animations($tanuki-red);
}
.tanuki-nose {
@include unique-keyframes {
20%, 70% {
fill: $tanuki-red;
}
30%, 80% {
fill: lighten($tanuki-red, 25%);
}
}
}
.tanuki-right-eye {
@include tanuki-forth-highlight-animations($tanuki-orange);
}
.tanuki-right-ear {
@include tanuki-forth-highlight-animations($tanuki-red);
}
.tanuki-right-cheek {
@include unique-keyframes {
40% {
fill: $tanuki-yellow;
}
60% {
fill: lighten($tanuki-yellow, 25%);
}
}
}
}
}
\ No newline at end of file
......@@ -9,22 +9,6 @@
border-radius: $radius;
}
@mixin border-radius-left($radius) {
@include border-radius($radius 0 0 $radius)
}
@mixin border-radius-right($radius) {
@include border-radius(0 0 $radius $radius)
}
@mixin linear-gradient($from, $to) {
background-image: -webkit-gradient(linear, 0 0, 0 100%, from($from), to($to));
background-image: -webkit-linear-gradient($from, $to);
background-image: -moz-linear-gradient($from, $to);
background-image: -ms-linear-gradient($from, $to);
background-image: -o-linear-gradient($from, $to);
}
@mixin transition($transition) {
-webkit-transition: $transition;
-moz-transition: $transition;
......@@ -38,14 +22,6 @@
* Mixins with fixed values
*/
@mixin shade {
@include box-shadow(0 0 3px #ddd);
}
@mixin solid-shade {
@include box-shadow(0 0 0 3px #f1f1f1);
}
@mixin str-truncated($max_width: 82%) {
display: inline-block;
overflow: hidden;
......@@ -94,23 +70,6 @@
}
}
@mixin input-big {
height: 36px;
padding: 5px 10px;
font-size: 16px;
line-height: 24px;
color: #7f8fa4;
background-color: #fff;
border-color: #e7e9ed;
}
@mixin btn-big {
height: 36px;
padding: 5px 10px;
font-size: 16px;
line-height: 24px;
}
@mixin bulleted-list {
> ul {
list-style-type: disc;
......@@ -129,3 +88,8 @@
color: rgba(255, 255, 255, 0.3);
background: rgba(255, 255, 255, 0.1);
}
@mixin webkit-prefix($property, $value) {
#{'-webkit-' + $property}: $value;
#{$property}: $value;
}
......@@ -8,10 +8,7 @@
height: 30px;
transition-duration: .3s;
-webkit-transform: translateZ(0);
background: -webkit-linear-gradient($gradient-direction, rgba($gradient-color, 0.4), $gradient-color 45%);
background: -o-linear-gradient($gradient-direction, rgba($gradient-color, 0.4), $gradient-color 45%);
background: -moz-linear-gradient($gradient-direction, rgba($gradient-color, 0.4), $gradient-color 45%);
background: linear-gradient($gradient-direction, rgba($gradient-color, 0.4), $gradient-color 45%);
background: linear-gradient(to $gradient-direction, $gradient-color 45%, rgba($gradient-color, 0.4));
&.scrolling {
visibility: visible;
......@@ -161,6 +158,7 @@
> .dropdown {
margin-right: $gl-padding-top;
display: inline-block;
vertical-align: top;
&:last-child {
margin-right: 0;
......@@ -210,12 +208,6 @@
}
}
.project-filter-form {
input {
background-color: $background-color;
}
}
@media (max-width: $screen-xs-max) {
padding-bottom: 0;
width: 100%;
......
......@@ -115,11 +115,8 @@
}
&.commits-stat {
margin-top: 3px;
display: block;
padding: 3px;
padding-left: 0;
padding: 0 3px 0 0;
&:hover {
background: none;
}
......
......@@ -319,6 +319,14 @@
a {
color: $layout-link-gray;
text-decoration: none;
&:hover {
.ci-status-text {
text-decoration: underline;
}
}
}
}
......
......@@ -600,18 +600,25 @@ pre.light-well {
}
}
.project-show-readme .readme-holder {
padding: $gl-padding 0;
border-top: 0;
.edit-project-readme {
z-index: 2;
position: relative;
.project-show-readme {
.row-content-block {
background-color: inherit;
border: none;
}
.wiki h1 {
border-bottom: none;
padding: 0;
.readme-holder {
padding: $gl-padding 0;
border-top: 0;
.edit-project-readme {
z-index: 2;
position: relative;
}
.wiki h1 {
border-bottom: none;
padding: 0;
}
}
}
......
......@@ -43,6 +43,15 @@
border-color: $blue-normal;
}
&.ci-created {
color: $table-text-gray;
border-color: $table-text-gray;
svg {
fill: $table-text-gray;
}
}
svg {
height: 13px;
width: 13px;
......
class Projects::ArtifactsController < Projects::ApplicationController
include ExtractsPath
layout 'project'
before_action :authorize_read_build!
before_action :authorize_update_build!, only: [:keep]
before_action :extract_ref_name_and_path
before_action :validate_artifacts!
def download
unless artifacts_file.file_storage?
return redirect_to artifacts_file.url
if artifacts_file.file_storage?
send_file artifacts_file.path, disposition: 'attachment'
else
redirect_to artifacts_file.url
end
send_file artifacts_file.path, disposition: 'attachment'
end
def browse
directory = params[:path] ? "#{params[:path]}/" : ''
@entry = build.artifacts_metadata_entry(directory)
return render_404 unless @entry.exists?
render_404 unless @entry.exists?
end
def file
......@@ -34,14 +37,41 @@ class Projects::ArtifactsController < Projects::ApplicationController
redirect_to namespace_project_build_path(project.namespace, project, build)
end
def latest_succeeded
target_path = artifacts_action_path(@path, project, build)
if target_path
redirect_to(target_path)
else
render_404
end
end
private
def extract_ref_name_and_path
return unless params[:ref_name_and_path]
@ref_name, @path = extract_ref(params[:ref_name_and_path])
end
def validate_artifacts!
render_404 unless build.artifacts?
render_404 unless build && build.artifacts?
end
def build
@build ||= project.builds.find_by!(id: params[:build_id])
@build ||= build_from_id || build_from_ref
end
def build_from_id
project.builds.find_by(id: params[:build_id]) if params[:build_id]
end
def build_from_ref
return unless @ref_name
builds = project.latest_successful_builds_for(@ref_name)
builds.find_by(name: params[:job])
end
def artifacts_file
......
......@@ -25,6 +25,11 @@ module CiStatusHelper
end
end
def ci_status_for_statuseable(subject)
status = subject.try(:status) || 'not found'
status.humanize
end
def ci_icon_for_status(status)
icon_name =
case status
......@@ -41,7 +46,7 @@ module CiStatusHelper
when 'play'
'icon_play'
when 'created'
'icon_status_pending'
'icon_status_created'
else
'icon_status_cancel'
end
......@@ -66,10 +71,10 @@ module CiStatusHelper
Ci::Runner.shared.blank?
end
def render_status_with_link(type, status, path = nil, tooltip_placement: 'auto left', cssclass: '')
def render_status_with_link(type, status, path = nil, tooltip_placement: 'auto left', cssclass: '', container: 'body')
klass = "ci-status-link ci-status-icon-#{status.dasherize} #{cssclass}"
title = "#{type.titleize}: #{ci_label_for_status(status)}"
data = { toggle: 'tooltip', placement: tooltip_placement }
data = { toggle: 'tooltip', placement: tooltip_placement, container: container }
if path
link_to ci_icon_for_status(status), path,
......
......@@ -149,4 +149,20 @@ module GitlabRoutingHelper
def resend_invite_group_member_path(group_member, *args)
resend_invite_group_group_member_path(group_member.source, group_member)
end
# Artifacts
def artifacts_action_path(path, project, build)
action, path_params = path.split('/', 2)
args = [project.namespace, project, build, path_params]
case action
when 'download'
download_namespace_project_build_artifacts_path(*args)
when 'browse'
browse_namespace_project_build_artifacts_path(*args)
when 'file'
file_namespace_project_build_artifacts_path(*args)
end
end
end
......@@ -98,6 +98,6 @@ module MergeRequestsHelper
end
def merge_request_button_visibility(merge_request, closed)
return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?)
return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?) || merge_request.closed_without_fork?
end
end
......@@ -355,7 +355,7 @@ class Ability
rules += named_abilities('project_snippet')
end
unless project.wiki_enabled
unless project.has_wiki?
rules += named_abilities('wiki')
end
......
......@@ -65,8 +65,8 @@ module Ci
end
# ref can't be HEAD or SHA, can only be branch/tag name
scope :latest_successful_for, ->(ref = default_branch) do
where(ref: ref).success.order(id: :desc).limit(1)
def self.latest_successful_for(ref)
where(ref: ref).order(id: :desc).success.first
end
def self.truncate_sha(sha)
......
......@@ -28,4 +28,8 @@ module NoteOnDiff
def can_be_award_emoji?
false
end
def to_discussion
Discussion.new([self])
end
end
......@@ -52,11 +52,11 @@ module Taskable
end
# Return a string that describes the current state of this Taskable's task
# list items, e.g. "20 tasks (12 completed, 8 remaining)"
# list items, e.g. "12 of 20 tasks completed"
def task_status
return '' if description.blank?
sum = tasks.summary
"#{sum.item_count} tasks (#{sum.complete_count} completed, #{sum.incomplete_count} remaining)"
"#{sum.complete_count} of #{sum.item_count} #{'task'.pluralize(sum.item_count)} completed"
end
end
......@@ -107,10 +107,6 @@ class DiffNote < Note
self.noteable.find_diff_discussion(self.discussion_id)
end
def to_discussion
Discussion.new([self])
end
private
def supported?
......
......@@ -91,13 +91,13 @@ class MergeRequest < ActiveRecord::Base
end
end
validates :source_project, presence: true, unless: [:allow_broken, :importing?]
validates :source_project, presence: true, unless: [:allow_broken, :importing?, :closed_without_fork?]
validates :source_branch, presence: true
validates :target_project, presence: true
validates :target_branch, presence: true
validates :merge_user, presence: true, if: :merge_when_build_succeeds?
validate :validate_branches, unless: [:allow_broken, :importing?]
validate :validate_fork
validate :validate_branches, unless: [:allow_broken, :importing?, :closed_without_fork?]
validate :validate_fork, unless: :closed_without_fork?
scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) }
scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
......@@ -305,19 +305,22 @@ class MergeRequest < ActiveRecord::Base
def validate_fork
return true unless target_project && source_project
return true if target_project == source_project
return true unless forked_source_project_missing?
if target_project == source_project
true
else
# If source and target projects are different
# we should check if source project is actually a fork of target project
if source_project.forked_from?(target_project)
true
else
errors.add :validate_fork,
'Source project is not a fork of target project'
end
end
errors.add :validate_fork,
'Source project is not a fork of the target project'
end
def closed_without_fork?
closed? && forked_source_project_missing?
end
def forked_source_project_missing?
return false unless for_fork?
return true unless source_project
!source_project.forked_from?(target_project)
end
def ensure_merge_request_diff
......@@ -726,7 +729,9 @@ class MergeRequest < ActiveRecord::Base
end
def pipeline
@pipeline ||= source_project.pipeline(diff_head_sha, source_branch) if diff_head_sha && source_project
return unless diff_head_sha && source_project
@pipeline ||= source_project.pipeline_for(source_branch, diff_head_sha)
end
def all_pipelines
......
......@@ -436,7 +436,7 @@ class Project < ActiveRecord::Base
# ref can't be HEAD, can only be branch/tag name or SHA
def latest_successful_builds_for(ref = default_branch)
latest_pipeline = pipelines.latest_successful_for(ref).first
latest_pipeline = pipelines.latest_successful_for(ref)
if latest_pipeline
latest_pipeline.builds.latest.with_artifacts
......@@ -680,6 +680,10 @@ class Project < ActiveRecord::Base
update_column(:has_external_issue_tracker, services.external_issue_trackers.any?)
end
def has_wiki?
wiki_enabled? || has_external_wiki?
end
def external_wiki
if has_external_wiki.nil?
cache_has_external_wiki # Populate
......@@ -1096,12 +1100,17 @@ class Project < ActiveRecord::Base
!namespace.share_with_group_lock
end
def pipeline(sha, ref)
def pipeline_for(ref, sha = nil)
sha ||= commit(ref).try(:sha)
return unless sha
pipelines.order(id: :desc).find_by(sha: sha, ref: ref)
end
def ensure_pipeline(sha, ref, current_user = nil)
pipeline(sha, ref) || pipelines.create(sha: sha, ref: ref, user: current_user)
def ensure_pipeline(ref, sha, current_user = nil)
pipeline_for(ref, sha) ||
pipelines.create(sha: sha, ref: ref, user: current_user)
end
def enable_ci
......
module MergeRequests
class ResolveService < MergeRequests::BaseService
attr_accessor :conflicts, :rugged, :merge_index
attr_accessor :conflicts, :rugged, :merge_index, :merge_request
def execute(merge_request)
@conflicts = merge_request.conflicts
@rugged = project.repository.rugged
@merge_index = conflicts.merge_index
@merge_request = merge_request
fetch_their_commit!
conflicts.files.each do |file|
write_resolved_file_to_index(file, params[:sections])
......@@ -27,5 +30,21 @@ module MergeRequests
merge_index.add(path: our_path, oid: rugged.write(new_file, :blob), mode: file.our_mode)
merge_index.conflict_remove(our_path)
end
# If their commit (in the target project) doesn't exist in the source project, it
# can't be a parent for the merge commit we're about to create. If that's the case,
# fetch the target branch ref into the source project so the commit exists in both.
#
def fetch_their_commit!
return if rugged.include?(conflicts.their_commit.oid)
random_string = SecureRandom.hex
project.repository.fetch_ref(
merge_request.target_project.repository.path_to_repo,
"refs/heads/#{merge_request.target_branch}",
"refs/tmp/#{random_string}/head"
)
end
end
end
......@@ -11,6 +11,10 @@ module MergeRequests
params.except!(:target_project_id)
params.except!(:source_branch)
if merge_request.closed_without_fork?
params.except!(:target_branch, :force_remove_source_branch)
end
merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
update(merge_request)
......
- page_title "CI Lint"
- page_description "Validate your GitLab CI configuration file"
%h2 Check your .gitlab-ci.yml
%hr
......
......@@ -27,6 +27,8 @@
= link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: 'btn btn-default', method: :post, title: "Compare" do
Compare
= render 'projects/buttons/download', project: @project, ref: branch.name
- if can_remove_branch?(@project, branch.name)
= link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-remove remove-row has-tooltip', title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do
= icon("trash-o")
......
- unless @project.empty_repo?
- if can? current_user, :download_code, @project
= link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn has-tooltip', data: {container: "body"}, rel: 'nofollow', title: "Download ZIP" do
= icon('download')
- if !project.empty_repo? && can?(current_user, :download_code, project)
%span.btn-group{class: 'hidden-xs hidden-sm btn-grouped'}
.dropdown.inline
%button.btn{ 'data-toggle' => 'dropdown' }
= icon('download')
%span.caret
%span.sr-only
Select Archive Format
%ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
%li.dropdown-header Source code
%li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow' do
%i.fa.fa-download
%span Download zip
%li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do
%i.fa.fa-download
%span Download tar.gz
%li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do
%i.fa.fa-download
%span Download tar.bz2
%li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar'), rel: 'nofollow' do
%i.fa.fa-download
%span Download tar
- pipeline = project.pipelines.latest_successful_for(ref)
- if pipeline
- artifacts = pipeline.builds.latest.with_artifacts
- if artifacts.any?
%li.dropdown-header Artifacts
- unless pipeline.latest?
- latest_pipeline = project.pipeline_for(ref)
%li
.unclickable= ci_status_for_statuseable(latest_pipeline)
%li.dropdown-header Previous Artifacts
- artifacts.each do |job|
%li
= link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, ref, 'download', job: job.name), rel: 'nofollow' do
%i.fa.fa-download
%span Download '#{job.name}'
......@@ -5,11 +5,11 @@
- if is_playable
= link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do
= render_status_with_link('build', 'play')
= subject.name
%span.ci-status-text= subject.name
- elsif can?(current_user, :read_build, @project)
= link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do
= render_status_with_link('build', subject.status)
= subject.name
%span.ci-status-text= subject.name
- else
= render_status_with_link('build', subject.status)
= ci_icon_for_status(subject.status)
......@@ -3,7 +3,7 @@
- if subject.target_url
- link_to subject.target_url do
= render_status_with_link('commit status', subject.status)
= subject.name
%span.ci-status-text= subject.name
- else
= render_status_with_link('commit status', subject.status)
= subject.name
%span.ci-status-text= subject.name
......@@ -5,7 +5,7 @@
- @related_branches.each do |branch|
%li
- target = @project.repository.find_branch(branch).target
- pipeline = @project.pipeline(target.sha, branch) if target
- pipeline = @project.pipeline_for(branch, target.sha) if target
- if pipeline
%span.related-branch-ci-status
= render_pipeline_status(pipeline)
......
- if @merge_request.closed_without_fork?
.alert.alert-danger
%p The source project of this merge request has been removed.
.clearfix.detail-page-header
.issuable-header
.issuable-status-box.status-box{ class: status_box_class(@merge_request) }
......
- ref = ref || nil
- btn_class = btn_class || ''
- split_button = split_button || false
- if split_button == true
%span.btn-group{class: btn_class}
= link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn col-xs-10', rel: 'nofollow' do
%i.fa.fa-download
%span Download zip
%a.col-xs-2.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' }
%span.caret
%span.sr-only
Select Archive Format
%ul.col-xs-10.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
%li
= link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), rel: 'nofollow' do
%i.fa.fa-download
%span Download zip
%li
= link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do
%i.fa.fa-download
%span Download tar.gz
%li
= link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do
%i.fa.fa-download
%span Download tar.bz2
%li
= link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar'), rel: 'nofollow' do
%i.fa.fa-download
%span Download tar
- else
%span.btn-group{class: btn_class}
= link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn', rel: 'nofollow' do
%i.fa.fa-download
%span zip
= link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.gz'), class: 'btn', rel: 'nofollow' do
%i.fa.fa-download
%span tar.gz
......@@ -72,7 +72,7 @@
= render "projects/buttons/koding"
.btn-group.project-repo-btn-group
= render "projects/buttons/download"
= render 'projects/buttons/download', project: @project, ref: @ref
= render 'projects/buttons/dropdown'
= render 'shared/notifications/button', notification_setting: @notification_setting
......
%span.btn-group
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), class: 'btn btn-default', rel: 'nofollow' do
%span Source code
%a.btn.btn-default.dropdown-toggle{ 'data-toggle' => 'dropdown' }
%span.caret
%span.sr-only
Select Archive Format
%ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
%li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow' do
%span Download zip
%li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do
%span Download tar.gz
......@@ -11,8 +11,7 @@
= strip_gpg_signature(tag.message)
.controls
- if can?(current_user, :download_code, @project)
= render 'projects/tags/download', ref: tag.name, project: @project
= render 'projects/buttons/download', project: @project, ref: tag.name
- if can?(current_user, :push_code, @project)
= link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn has-tooltip', title: "Edit release notes", data: { container: "body" } do
......
......@@ -12,8 +12,7 @@
= icon('files-o')
= link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Browse commits' do
= icon('history')
- if can? current_user, :download_code, @project
= render 'projects/tags/download', ref: @tag.name, project: @project
= render 'projects/buttons/download', project: @project, ref: @tag.name
- if can?(current_user, :admin_project, @project)
.pull-right
= link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
......
......@@ -10,8 +10,7 @@
%div{ class: container_class }
.tree-controls
= render 'projects/find_file_link'
- if can? current_user, :download_code, @project
= render 'projects/repositories/download_archive', ref: @ref, btn_class: 'hidden-xs hidden-sm btn-grouped', split_button: true
= render 'projects/buttons/download', project: @project, ref: @ref
#tree-holder.tree-holder.clearfix
.nav-block
......
<svg width="36" height="36" id="tanuki-logo">
<path id="tanuki-right-ear" class="tanuki-shape" fill="#e24329" d="M2 14l9.38 9v-9l-4-12.28c-.205-.632-1.176-.632-1.38 0z"/>
<path id="tanuki-left-ear" class="tanuki-shape" fill="#e24329" d="M34 14l-9.38 9v-9l4-12.28c.205-.632 1.176-.632 1.38 0z"/>
<path id="tanuki-nose" class="tanuki-shape" fill="#e24329" d="M18,34.38 3,14 33,14 Z"/>
<path id="tanuki-right-eye" class="tanuki-shape" fill="#fc6d26" d="M18,34.38 11.38,14 2,14 6,25Z"/>
<path id="tanuki-left-eye" class="tanuki-shape" fill="#fc6d26" d="M18,34.38 24.62,14 34,14 30,25Z"/>
<path id="tanuki-right-cheek" class="tanuki-shape" fill="#fca326" d="M2 14L.1 20.16c-.18.565 0 1.2.5 1.56l17.42 12.66z"/>
<path id="tanuki-left-cheek" class="tanuki-shape" fill="#fca326" d="M34 14l1.9 6.16c.18.565 0 1.2-.5 1.56L18 34.38z"/>
<svg width="36" height="36" class="tanuki-logo">
<path class="tanuki-shape tanuki-left-ear" fill="#e24329" d="M2 14l9.38 9v-9l-4-12.28c-.205-.632-1.176-.632-1.38 0z"/>
<path class="tanuki-shape tanuki-right-ear" fill="#e24329" d="M34 14l-9.38 9v-9l4-12.28c.205-.632 1.176-.632 1.38 0z"/>
<path class="tanuki-shape tanuki-nose" fill="#e24329" d="M18,34.38 3,14 33,14 Z"/>
<path class="tanuki-shape tanuki-left-eye" fill="#fc6d26" d="M18,34.38 11.38,14 2,14 6,25Z"/>
<path class="tanuki-shape tanuki-right-eye" fill="#fc6d26" d="M18,34.38 24.62,14 34,14 30,25Z"/>
<path class="tanuki-shape tanuki-left-cheek" fill="#fca326" d="M2 14L.1 20.16c-.18.565 0 1.2.5 1.56l17.42 12.66z"/>
<path class="tanuki-shape tanuki-right-cheek" fill="#fca326" d="M34 14l1.9 6.16c.18.565 0 1.2-.5 1.56L18 34.38z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14" enable-background="new 0 0 14 14"><path d="M12.5,7 C12.5,4 10,1.5 7,1.5 C4,1.5 1.5,4 1.5,7 C1.5,10 4,12.5 7,12.5 C10,12.5 12.5,10 12.5,7 L12.5,7 Z M0,7 C0,3.1 3.1,0 7,0 C10.9,0 14,3.1 14,7 C14,10.9 10.9,14 7,14 C3.1,14 0,10.9 0,7 L0,7 Z" /><circle cx="7" cy="7" r="3.25"/></svg>
......@@ -134,7 +134,7 @@
title: 'Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location.' }
= icon('question-circle')
- if issuable.is_a?(MergeRequest)
- if issuable.is_a?(MergeRequest) && !issuable.closed_without_fork?
%hr
- if @merge_request.new_record?
.form-group
......@@ -175,7 +175,7 @@
= link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class]), class: 'btn btn-cancel'
- else
.pull-right
- if current_user.can?(:"destroy_#{issuable.to_ability_name}", @project)
- if can?(current_user, :"destroy_#{issuable.to_ability_name}", @project)
= link_to 'Delete', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.class.name.titleize} will be removed! Are you sure?" },
method: :delete, class: 'btn btn-danger btn-grouped'
= link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), class: 'btn btn-grouped btn-cancel'
......
......@@ -782,6 +782,14 @@ Rails.application.routes.draw do
resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do
collection do
post :cancel_all
resources :artifacts, only: [] do
collection do
get :latest_succeeded,
path: '*ref_name_and_path',
format: false
end
end
end
member do
......
......@@ -10,7 +10,7 @@ GET /projects/:id/repository/commits
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
| `ref_name` | string | no | The name of a repository branch or tag or if not given the default branch |
| `since` | string | no | Only commits after or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ |
| `until` | string | no | Only commits before or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ |
......@@ -58,7 +58,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
| `sha` | string | yes | The commit hash or name of a repository branch or tag |
```bash
......@@ -102,7 +102,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
| `sha` | string | yes | The commit hash or name of a repository branch or tag |
```bash
......@@ -138,7 +138,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
| `sha` | string | yes | The commit hash or name of a repository branch or tag |
```bash
......@@ -187,7 +187,7 @@ POST /projects/:id/repository/commits/:sha/comments
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
| `sha` | string | yes | The commit SHA or name of a repository branch or tag |
| `note` | string | yes | The text of the comment |
| `path` | string | no | The file path relative to the repository |
......@@ -232,7 +232,7 @@ GET /projects/:id/repository/commits/:sha/statuses
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project
| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
| `sha` | string | yes | The commit SHA
| `ref_name`| string | no | The name of a repository branch or tag or, if not given, the default branch
| `stage` | string | no | Filter by [build stage](../ci/yaml/README.md#stages), e.g., `test`
......@@ -306,7 +306,7 @@ POST /projects/:id/statuses/:sha
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project
| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
| `sha` | string | yes | The commit SHA
| `state` | string | yes | The state of the status. Can be one of the following: `pending`, `running`, `success`, `failed`, `canceled`
| `ref` | string | no | The `ref` (branch or tag) to which the status refers
......
......@@ -489,7 +489,7 @@ PUT /projects/:id
Parameters:
- `id` (required) - The ID of a project
- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project
- `name` (optional) - project name
- `path` (optional) - repository name for project
- `description` (optional) - short project description
......@@ -519,7 +519,7 @@ POST /projects/fork/:id
Parameters:
- `id` (required) - The ID of the project to be forked
- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked
### Star a project
......@@ -532,7 +532,7 @@ POST /projects/:id/star
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of the project |
| `id` | integer | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/star"
......@@ -599,7 +599,7 @@ DELETE /projects/:id/star
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of the project |
| `id` | integer | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
```bash
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/star"
......@@ -670,7 +670,7 @@ POST /projects/:id/archive
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of the project |
| `id` | integer | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/archive"
......@@ -757,7 +757,7 @@ POST /projects/:id/unarchive
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of the project |
| `id` | integer | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/unarchive"
......@@ -839,7 +839,7 @@ DELETE /projects/:id
Parameters:
- `id` (required) - The ID of a project
- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked
## Uploads
......@@ -853,7 +853,7 @@ POST /projects/:id/uploads
Parameters:
- `id` (required) - The ID of the project
- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked
- `file` (required) - The file to be uploaded
```json
......@@ -882,7 +882,7 @@ POST /projects/:id/share
Parameters:
- `id` (required) - The ID of a project
- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked
- `group_id` (required) - The ID of a group
- `group_access` (required) - Level of permissions for sharing
......@@ -1114,7 +1114,7 @@ POST /projects/:id/fork/:forked_from_id
Parameters:
- `id` (required) - The ID of the project
- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked
- `forked_from_id:` (required) - The ID of the project that was forked from
### Delete an existing forked from relationship
......@@ -1125,7 +1125,7 @@ DELETE /projects/:id/fork
Parameter:
- `id` (required) - The ID of the project
- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked
## Search for projects by name
......
......@@ -39,7 +39,7 @@ If you want a quick introduction to GitLab CI, follow our
- [before_script and after_script](#before_script-and-after_script)
- [Git Strategy](#git-strategy)
- [Shallow cloning](#shallow-cloning)
- [Hidden jobs](#hidden-jobs)
- [Hidden keys](#hidden-keys)
- [Special YAML features](#special-yaml-features)
- [Anchors](#anchors)
- [Validate the .gitlab-ci.yml](#validate-the-gitlab-ci-yml)
......@@ -934,24 +934,27 @@ variables:
GIT_DEPTH: "3"
```
## Hidden jobs
## Hidden keys
>**Note:**
Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
Jobs that start with a dot (`.`) will be not processed by GitLab CI. You can
Keys that start with a dot (`.`) will be not processed by GitLab CI. You can
use this feature to ignore jobs, or use the
[special YAML features](#special-yaml-features) and transform the hidden jobs
[special YAML features](#special-yaml-features) and transform the hidden keys
into templates.
In the following example, `.job_name` will be ignored:
In the following example, `.key_name` will be ignored:
```yaml
.job_name:
.key_name:
script:
- rake spec
```
Hidden keys can be hashes like normal CI jobs, but you are also allowed to use
different types of structures to leverage special YAML features.
## Special YAML features
It's possible to use special YAML features like anchors (`&`), aliases (`*`)
......@@ -967,7 +970,7 @@ Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
YAML also has a handy feature called 'anchors', which let you easily duplicate
content across your document. Anchors can be used to duplicate/inherit
properties, and is a perfect example to be used with [hidden jobs](#hidden-jobs)
properties, and is a perfect example to be used with [hidden keys](#hidden-keys)
to provide templates for your jobs.
The following example uses anchors and map merging. It will create two jobs,
......@@ -975,7 +978,7 @@ The following example uses anchors and map merging. It will create two jobs,
having their own custom `script` defined:
```yaml
.job_template: &job_definition # Hidden job that defines an anchor named 'job_definition'
.job_template: &job_definition # Hidden key that defines an anchor named 'job_definition'
image: ruby:2.1
services:
- postgres
......@@ -1081,7 +1084,7 @@ test:mysql:
- ruby
```
You can see that the hidden jobs are conveniently used as templates.
You can see that the hidden keys are conveniently used as templates.
## Validate the .gitlab-ci.yml
......
......@@ -222,18 +222,26 @@ For example, if you were to move `doc/workflow/lfs/lfs_administration.md` to
```
1. Find and replace any occurrences of the old location with the new one.
A quick way to find them is to use `grep`:
A quick way to find them is to use `git grep`. First go to the root directory
where you cloned the `gitlab-ce` repository and then do:
```
grep -nR "lfs_administration.md" doc/
git grep -n "workflow/lfs/lfs_administration"
git grep -n "lfs/lfs_administration"
```
The above command will search in the `doc/` directory for
`lfs_administration.md` recursively and will print the file and the line
where this file is mentioned. Note that we used just the filename
(`lfs_administration.md`) and not the whole the relative path
(`workflow/lfs/lfs_administration.md`).
Things to note:
- Since we also use inline documentation, except for the documentation itself,
the document might also be referenced in the views of GitLab (`app/`) which will
render when visiting `/help`, and sometimes in the testing suite (`spec/`).
- The above `git grep` command will search recursively in the directory you run
it in for `workflow/lfs/lfs_administration` and `lfs/lfs_administration`
and will print the file and the line where this file is mentioned.
You may ask why the two greps. Since we use relative paths to link to
documentation, sometimes it might be useful to search a path deeper.
- The `*.md` extension is not used when a document is linked to GitLab's
built-in help page, that's why we omit it in `git grep`.
## Configuration documentation for source and Omnibus installations
......
......@@ -2,7 +2,7 @@
This style guide recommends best practices for newlines in Ruby code.
## Rule: separate code with newlines only when it makes sense from logic perspectice
## Rule: separate code with newlines only to group together related logic
```ruby
# bad
......
This diff is collapsed.
......@@ -102,8 +102,8 @@ To change these settings:
block_auto_created_users: true
```
Now we can choose one or more of the Supported Providers listed above to continue
the configuration process.
Now we can choose one or more of the [Supported Providers](#supported-providers)
listed above to continue the configuration process.
## Enable OmniAuth for an Existing User
......
......@@ -26,5 +26,5 @@ do.
| `/done` | Mark todo as done |
| `/subscribe` | Subscribe |
| `/unsubscribe` | Unsubscribe |
| `/due <in 2 days or this Friday or December 31st>` | Set due date |
| <code>/due &lt;in 2 days &#124; this Friday &#124; December 31st&gt;</code> | Set due date |
| `/remove_due_date` | Remove due date |
......@@ -64,7 +64,7 @@ module API
ref = branches.first
end
pipeline = @project.ensure_pipeline(commit.sha, ref, current_user)
pipeline = @project.ensure_pipeline(ref, commit.sha, current_user)
name = params[:name] || params[:context]
status = GenericCommitStatus.running_or_pending.find_by(pipeline: pipeline, name: name, ref: params[:ref])
......
......@@ -55,7 +55,7 @@ module Backup
bk_repos_path = File.join(path, '..', 'repositories.old.' + Time.now.to_i.to_s)
FileUtils.mv(path, bk_repos_path)
# This is expected from gitlab:check
FileUtils.mkdir_p(path, mode: 2770)
FileUtils.mkdir_p(path, mode: 02770)
end
Project.find_each(batch_size: 1000) do |project|
......
......@@ -12,9 +12,7 @@ module Gitlab
@ref = ref
@job = job
@pipeline = @project.pipelines
.latest_successful_for(@ref)
.first
@pipeline = @project.pipelines.latest_successful_for(@ref)
end
def entity
......
......@@ -5,11 +5,10 @@ module Gitlab
##
# Entry that represents a hidden CI/CD job.
#
class HiddenJob < Entry
class Hidden < Entry
include Validatable
validations do
validates :config, type: Hash
validates :config, presence: true
end
......
......@@ -30,7 +30,7 @@ module Gitlab
def compose!
@config.each do |name, config|
node = hidden?(name) ? Node::HiddenJob : Node::Job
node = hidden?(name) ? Node::Hidden : Node::Job
factory = Node::Factory.new(node)
.value(config || {})
......
......@@ -170,6 +170,35 @@ describe Projects::MergeRequestsController do
expect(response).to redirect_to([merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request])
expect(merge_request.reload.closed?).to be_truthy
end
it 'allows editing of a closed merge request' do
merge_request.close!
put :update,
namespace_id: project.namespace.path,
project_id: project.path,
id: merge_request.iid,
merge_request: {
title: 'New title'
}
expect(response).to redirect_to([merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request])
expect(merge_request.reload.title).to eq 'New title'
end
it 'does not allow to update target branch closed merge request' do
merge_request.close!
put :update,
namespace_id: project.namespace.path,
project_id: project.path,
id: merge_request.iid,
merge_request: {
target_branch: 'new_branch'
}
expect { merge_request.reload.target_branch }.not_to change { merge_request.target_branch }
end
end
end
......
......@@ -147,6 +147,37 @@ feature 'Diff notes', js: true, feature: true do
end
end
context 'when the MR only supports legacy diff notes' do
before do
@merge_request.merge_request_diff.update_attributes(start_commit_sha: nil)
visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, view: 'inline')
end
context 'with a new line' do
it 'should allow commenting' do
should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'))
end
end
context 'with an old line' do
it 'should allow commenting' do
should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]'))
end
end
context 'with an unchanged line' do
it 'should allow commenting' do
should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]'))
end
end
context 'with a match line' do
it 'should not allow commenting' do
should_not_allow_commenting(find('.match', match: :first))
end
end
end
def should_allow_commenting(line_holder, diff_side = nil)
line = get_line_components(line_holder, diff_side)
line[:content].hover
......
require 'spec_helper'
feature 'Download buttons in branches page', feature: true do
given(:user) { create(:user) }
given(:role) { :developer }
given(:status) { 'success' }
given(:project) { create(:project) }
given(:pipeline) do
create(:ci_pipeline,
project: project,
sha: project.commit('binary-encoding').sha,
ref: 'binary-encoding', # make sure the branch is in the 1st page!
status: status)
end
given!(:build) do
create(:ci_build, :success, :artifacts,
pipeline: pipeline,
status: pipeline.status,
name: 'build')
end
background do
login_as(user)
project.team << [user, role]
end
describe 'when checking branches' do
context 'with artifacts' do
before do
visit namespace_project_branches_path(project.namespace, project)
end
scenario 'shows download artifacts button' do
expect(page).to have_link "Download '#{build.name}'"
end
end
end
end
require 'spec_helper'
feature 'Download buttons in files tree', feature: true do
given(:user) { create(:user) }
given(:role) { :developer }
given(:status) { 'success' }
given(:project) { create(:project) }
given(:pipeline) do
create(:ci_pipeline,
project: project,
sha: project.commit.sha,
ref: project.default_branch,
status: status)
end
given!(:build) do
create(:ci_build, :success, :artifacts,
pipeline: pipeline,
status: pipeline.status,
name: 'build')
end
background do
login_as(user)
project.team << [user, role]
end
describe 'when files tree' do
context 'with artifacts' do
before do
visit namespace_project_tree_path(
project.namespace, project, project.default_branch)
end
scenario 'shows download artifacts button' do
expect(page).to have_link "Download '#{build.name}'"
end
end
end
end
require 'spec_helper'
feature 'Download buttons in project main page', feature: true do
given(:user) { create(:user) }
given(:role) { :developer }
given(:status) { 'success' }
given(:project) { create(:project) }
given(:pipeline) do
create(:ci_pipeline,
project: project,
sha: project.commit.sha,
ref: project.default_branch,
status: status)
end
given!(:build) do
create(:ci_build, :success, :artifacts,
pipeline: pipeline,
status: pipeline.status,
name: 'build')
end
background do
login_as(user)
project.team << [user, role]
end
describe 'when checking project main page' do
context 'with artifacts' do
before do
visit namespace_project_path(project.namespace, project)
end
scenario 'shows download artifacts button' do
expect(page).to have_link "Download '#{build.name}'"
end
end
end
end
require 'spec_helper'
feature 'Download buttons in tags page', feature: true do
given(:user) { create(:user) }
given(:role) { :developer }
given(:status) { 'success' }
given(:tag) { 'v1.0.0' }
given(:project) { create(:project) }
given(:pipeline) do
create(:ci_pipeline,
project: project,
sha: project.commit(tag).sha,
ref: tag,
status: status)
end
given!(:build) do
create(:ci_build, :success, :artifacts,
pipeline: pipeline,
status: pipeline.status,
name: 'build')
end
background do
login_as(user)
project.team << [user, role]
end
describe 'when checking tags' do
context 'with artifacts' do
before do
visit namespace_project_tags_path(project.namespace, project)
end
scenario 'shows download artifacts button' do
expect(page).to have_link "Download '#{build.name}'"
end
end
end
end
This diff is collapsed.
require 'spec_helper'
describe Gitlab::Ci::Config::Node::HiddenJob do
describe Gitlab::Ci::Config::Node::Hidden do
let(:entry) { described_class.new(config) }
describe 'validations' do
context 'when entry config value is correct' do
let(:config) { { image: 'ruby:2.2' } }
let(:config) { [:some, :array] }
describe '#value' do
it 'returns key value' do
expect(entry.value).to eq(image: 'ruby:2.2')
expect(entry.value).to eq [:some, :array]
end
end
......@@ -21,17 +21,6 @@ describe Gitlab::Ci::Config::Node::HiddenJob do
end
context 'when entry value is not correct' do
context 'incorrect config value type' do
let(:config) { ['incorrect'] }
describe '#errors' do
it 'saves errors' do
expect(entry.errors)
.to include 'hidden job config should be a hash'
end
end
end
context 'when config is empty' do
let(:config) { {} }
......
......@@ -74,7 +74,7 @@ describe Gitlab::Ci::Config::Node::Jobs do
expect(entry.descendants.first(2))
.to all(be_an_instance_of(Gitlab::Ci::Config::Node::Job))
expect(entry.descendants.last)
.to be_an_instance_of(Gitlab::Ci::Config::Node::HiddenJob)
.to be_an_instance_of(Gitlab::Ci::Config::Node::Hidden)
end
end
......
......@@ -282,4 +282,17 @@ describe Ability, lib: true do
end
end
end
describe '.project_disabled_features_rules' do
let(:project) { build(:project) }
subject { described_class.project_disabled_features_rules(project) }
context 'wiki named abilities' do
it 'disables wiki abilities if the project has no wiki' do
expect(project).to receive(:has_wiki?).and_return(false)
expect(subject).to include(:read_wiki, :create_wiki, :update_wiki, :admin_wiki)
end
end
end
end
......@@ -948,15 +948,17 @@ describe Ci::Build, models: true do
before { build.run! }
it 'returns false' do
expect(build.retryable?).to be false
expect(build).not_to be_retryable
end
end
context 'when build is finished' do
before { build.success! }
before do
build.success!
end
it 'returns true' do
expect(build.retryable?).to be true
expect(build).to be_retryable
end
end
end
......
......@@ -195,6 +195,36 @@ describe Ci::Pipeline, models: true do
end
end
context 'with non-empty project' do
let(:project) { create(:project) }
let(:pipeline) do
create(:ci_pipeline,
project: project,
ref: project.default_branch,
sha: project.commit.sha)
end
describe '#latest?' do
context 'with latest sha' do
it 'returns true' do
expect(pipeline).to be_latest
end
end
context 'with not latest sha' do
before do
pipeline.update(
sha: project.commit("#{project.default_branch}~1").sha)
end
it 'returns false' do
expect(pipeline).not_to be_latest
end
end
end
end
describe '#manual_actions' do
subject { pipeline.manual_actions }
......
......@@ -477,8 +477,8 @@ describe MergeRequest, models: true do
allow(subject).to receive(:diff_head_sha).and_return('123abc')
expect(subject.source_project).to receive(:pipeline).
with('123abc', 'master').
expect(subject.source_project).to receive(:pipeline_for).
with('master', '123abc').
and_return(pipeline)
expect(subject.pipeline).to eq(pipeline)
......@@ -962,4 +962,80 @@ describe MergeRequest, models: true do
expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_truthy
end
end
describe "#forked_source_project_missing?" do
let(:project) { create(:project) }
let(:fork_project) { create(:project, forked_from_project: project) }
let(:user) { create(:user) }
let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) }
context "when the fork exists" do
let(:merge_request) do
create(:merge_request,
source_project: fork_project,
target_project: project)
end
it { expect(merge_request.forked_source_project_missing?).to be_falsey }
end
context "when the source project is the same as the target project" do
let(:merge_request) { create(:merge_request, source_project: project) }
it { expect(merge_request.forked_source_project_missing?).to be_falsey }
end
context "when the fork does not exist" do
let(:merge_request) do
create(:merge_request,
source_project: fork_project,
target_project: project)
end
it "returns true" do
unlink_project.execute
merge_request.reload
expect(merge_request.forked_source_project_missing?).to be_truthy
end
end
end
describe "#closed_without_fork?" do
let(:project) { create(:project) }
let(:fork_project) { create(:project, forked_from_project: project) }
let(:user) { create(:user) }
let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) }
context "when the merge request is closed" do
let(:closed_merge_request) do
create(:closed_merge_request,
source_project: fork_project,
target_project: project)
end
it "returns false if the fork exist" do
expect(closed_merge_request.closed_without_fork?).to be_falsey
end
it "returns true if the fork does not exist" do
unlink_project.execute
closed_merge_request.reload
expect(closed_merge_request.closed_without_fork?).to be_truthy
end
end
context "when the merge request is open" do
let(:open_merge_request) do
create(:merge_request,
source_project: fork_project,
target_project: project)
end
it "returns false" do
expect(open_merge_request.closed_without_fork?).to be_falsey
end
end
end
end
......@@ -506,6 +506,18 @@ describe Project, models: true do
end
end
describe '#has_wiki?' do
let(:no_wiki_project) { build(:project, wiki_enabled: false, has_external_wiki: false) }
let(:wiki_enabled_project) { build(:project, wiki_enabled: true) }
let(:external_wiki_project) { build(:project, has_external_wiki: true) }
it 'returns true if project is wiki enabled or has external wiki' do
expect(wiki_enabled_project).to have_wiki
expect(external_wiki_project).to have_wiki
expect(no_wiki_project).not_to have_wiki
end
end
describe '#external_wiki' do
let(:project) { create(:project) }
......@@ -685,23 +697,37 @@ describe Project, models: true do
end
end
describe '#pipeline' do
let(:project) { create :project }
let(:pipeline) { create :ci_pipeline, project: project, ref: 'master' }
subject { project.pipeline(pipeline.sha, 'master') }
describe '#pipeline_for' do
let(:project) { create(:project) }
let!(:pipeline) { create_pipeline }
it { is_expected.to eq(pipeline) }
shared_examples 'giving the correct pipeline' do
it { is_expected.to eq(pipeline) }
context 'return latest' do
let(:pipeline2) { create :ci_pipeline, project: project, ref: 'master' }
context 'return latest' do
let!(:pipeline2) { create_pipeline }
before do
pipeline
pipeline2
it { is_expected.to eq(pipeline2) }
end
end
context 'with explicit sha' do
subject { project.pipeline_for('master', pipeline.sha) }
it_behaves_like 'giving the correct pipeline'
end
context 'with implicit sha' do
subject { project.pipeline_for('master') }
it_behaves_like 'giving the correct pipeline'
end
it { is_expected.to eq(pipeline2) }
def create_pipeline
create(:ci_pipeline,
project: project,
ref: 'master',
sha: project.commit('master').sha)
end
end
......
......@@ -15,7 +15,9 @@ describe API::API, api: true do
describe 'GET /projects/:id/builds ' do
let(:query) { '' }
before { get api("/projects/#{project.id}/builds?#{query}", api_user) }
before do
get api("/projects/#{project.id}/builds?#{query}", api_user)
end
context 'authorized user' do
it 'returns project builds' do
......@@ -122,7 +124,9 @@ describe API::API, api: true do
end
describe 'GET /projects/:id/builds/:build_id' do
before { get api("/projects/#{project.id}/builds/#{build.id}", api_user) }
before do
get api("/projects/#{project.id}/builds/#{build.id}", api_user)
end
context 'authorized user' do
it 'returns specific build data' do
......@@ -141,7 +145,9 @@ describe API::API, api: true do
end
describe 'GET /projects/:id/builds/:build_id/artifacts' do
before { get api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user) }
before do
get api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user)
end
context 'build with artifacts' do
let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) }
......@@ -292,7 +298,9 @@ describe API::API, api: true do
end
describe 'POST /projects/:id/builds/:build_id/cancel' do
before { post api("/projects/#{project.id}/builds/#{build.id}/cancel", api_user) }
before do
post api("/projects/#{project.id}/builds/#{build.id}/cancel", api_user)
end
context 'authorized user' do
context 'user with :update_build persmission' do
......@@ -323,7 +331,9 @@ describe API::API, api: true do
describe 'POST /projects/:id/builds/:build_id/retry' do
let(:build) { create(:ci_build, :canceled, pipeline: pipeline) }
before { post api("/projects/#{project.id}/builds/#{build.id}/retry", api_user) }
before do
post api("/projects/#{project.id}/builds/#{build.id}/retry", api_user)
end
context 'authorized user' do
context 'user with :update_build permission' do
......
......@@ -95,7 +95,7 @@ describe API::API, api: true do
end
it "returns status for CI" do
pipeline = project.ensure_pipeline(project.repository.commit.sha, 'master')
pipeline = project.ensure_pipeline('master', project.repository.commit.sha)
pipeline.update(status: 'success')
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
......@@ -105,7 +105,7 @@ describe API::API, api: true do
end
it "returns status for CI when pipeline is created" do
project.ensure_pipeline(project.repository.commit.sha, 'master')
project.ensure_pipeline('master', project.repository.commit.sha)
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
......
require 'spec_helper'
describe Projects::ArtifactsController do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:pipeline) do
create(:ci_pipeline,
project: project,
sha: project.commit.sha,
ref: project.default_branch,
status: 'success')
end
let(:build) { create(:ci_build, :success, :artifacts, pipeline: pipeline) }
describe 'GET /:project/builds/artifacts/:ref_name/browse?job=name' do
before do
project.team << [user, :developer]
login_as(user)
end
def path_from_ref(
ref = pipeline.ref, job = build.name, path = 'browse')
latest_succeeded_namespace_project_artifacts_path(
project.namespace,
project,
[ref, path].join('/'),
job: job)
end
context 'cannot find the build' do
shared_examples 'not found' do
it { expect(response).to have_http_status(:not_found) }
end
context 'has no such ref' do
before do
get path_from_ref('TAIL', build.name)
end
it_behaves_like 'not found'
end
context 'has no such build' do
before do
get path_from_ref(pipeline.ref, 'NOBUILD')
end
it_behaves_like 'not found'
end
context 'has no path' do
before do
get path_from_ref(pipeline.sha, build.name, '')
end
it_behaves_like 'not found'
end
end
context 'found the build and redirect' do
shared_examples 'redirect to the build' do
it 'redirects' do
path = browse_namespace_project_build_artifacts_path(
project.namespace,
project,
build)
expect(response).to redirect_to(path)
end
end
context 'with regular branch' do
before do
pipeline.update(ref: 'master',
sha: project.commit('master').sha)
get path_from_ref('master')
end
it_behaves_like 'redirect to the build'
end
context 'with branch name containing slash' do
before do
pipeline.update(ref: 'improve/awesome',
sha: project.commit('improve/awesome').sha)
get path_from_ref('improve/awesome')
end
it_behaves_like 'redirect to the build'
end
context 'with branch name and path containing slashes' do
before do
pipeline.update(ref: 'improve/awesome',
sha: project.commit('improve/awesome').sha)
get path_from_ref('improve/awesome', build.name, 'file/README.md')
end
it 'redirects' do
path = file_namespace_project_build_artifacts_path(
project.namespace,
project,
build,
'README.md')
expect(response).to redirect_to(path)
end
end
end
end
end
......@@ -5,7 +5,7 @@ module Ci
let(:service) { ImageForBuildService.new }
let(:project) { FactoryGirl.create(:empty_project) }
let(:commit_sha) { '01234567890123456789' }
let(:pipeline) { project.ensure_pipeline(commit_sha, 'master') }
let(:pipeline) { project.ensure_pipeline('master', commit_sha) }
let(:build) { FactoryGirl.create(:ci_build, pipeline: pipeline) }
describe '#execute' do
......
require 'spec_helper'
describe MergeRequests::ResolveService do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:fork_project) do
create(:forked_project_with_submodules) do |fork_project|
fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
fork_project.save
end
end
let(:merge_request) do
create(:merge_request,
source_branch: 'conflict-resolvable', source_project: project,
target_branch: 'conflict-start')
end
let(:merge_request_from_fork) do
create(:merge_request,
source_branch: 'conflict-resolvable-fork', source_project: fork_project,
target_branch: 'conflict-start', target_project: project)
end
describe '#execute' do
context 'with valid params' do
let(:params) do
{
sections: {
'2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head',
'6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head',
'6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin',
'6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin'
},
commit_message: 'This is a commit message!'
}
end
context 'when the source and target project are the same' do
before do
MergeRequests::ResolveService.new(project, user, params).execute(merge_request)
end
it 'creates a commit with the message' do
expect(merge_request.source_branch_head.message).to eq(params[:commit_message])
end
it 'creates a commit with the correct parents' do
expect(merge_request.source_branch_head.parents.map(&:id)).
to eq(['1450cd639e0bc6721eb02800169e464f212cde06',
'75284c70dd26c87f2a3fb65fd5a1f0b0138d3a6b'])
end
end
context 'when the source project is a fork and does not contain the HEAD of the target branch' do
let!(:target_head) do
project.repository.commit_file(user, 'new-file-in-target', '', 'Add new file in target', 'conflict-start', false)
end
before do
MergeRequests::ResolveService.new(fork_project, user, params).execute(merge_request_from_fork)
end
it 'creates a commit with the message' do
expect(merge_request_from_fork.source_branch_head.message).to eq(params[:commit_message])
end
it 'creates a commit with the correct parents' do
expect(merge_request_from_fork.source_branch_head.parents.map(&:id)).
to eq(['404fa3fc7c2c9b5dacff102f353bdf55b1be2813',
target_head])
end
end
end
context 'when a resolution is missing' do
let(:invalid_params) { { sections: { '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head' } } }
let(:service) { MergeRequests::ResolveService.new(project, user, invalid_params) }
it 'raises a MissingResolution error' do
expect { service.execute(merge_request) }.
to raise_error(Gitlab::Conflict::File::MissingResolution)
end
end
end
end
......@@ -26,9 +26,9 @@ RSpec.configure do |config|
config.verbose_retry = true
config.display_try_failure_messages = true
config.include Devise::TestHelpers, type: :controller
config.include LoginHelpers, type: :feature
config.include LoginHelpers, type: :request
config.include Devise::TestHelpers, type: :controller
config.include Warden::Test::Helpers, type: :request
config.include LoginHelpers, type: :feature
config.include StubConfiguration
config.include EmailHelpers
config.include TestEnv
......
......@@ -3,30 +3,57 @@
# Requires a context containing:
# subject { Issue or MergeRequest }
shared_examples 'a Taskable' do
before do
subject.description = <<-EOT.strip_heredoc
* [ ] Task 1
* [x] Task 2
* [x] Task 3
* [ ] Task 4
* [ ] Task 5
EOT
describe 'with multiple tasks' do
before do
subject.description = <<-EOT.strip_heredoc
* [ ] Task 1
* [x] Task 2
* [x] Task 3
* [ ] Task 4
* [ ] Task 5
EOT
end
it 'returns the correct task status' do
expect(subject.task_status).to match('2 of')
expect(subject.task_status).to match('5 tasks completed')
end
describe '#tasks?' do
it 'returns true when object has tasks' do
expect(subject.tasks?).to eq true
end
it 'returns false when object has no tasks' do
subject.description = 'Now I have no tasks'
expect(subject.tasks?).to eq false
end
end
end
it 'returns the correct task status' do
expect(subject.task_status).to match('5 tasks')
expect(subject.task_status).to match('2 completed')
expect(subject.task_status).to match('3 remaining')
describe 'with an incomplete task' do
before do
subject.description = <<-EOT.strip_heredoc
* [ ] Task 1
EOT
end
it 'returns the correct task status' do
expect(subject.task_status).to match('0 of')
expect(subject.task_status).to match('1 task completed')
end
end
describe '#tasks?' do
it 'returns true when object has tasks' do
expect(subject.tasks?).to eq true
describe 'with a complete task' do
before do
subject.description = <<-EOT.strip_heredoc
* [x] Task 1
EOT
end
it 'returns false when object has no tasks' do
subject.description = 'Now I have no tasks'
expect(subject.tasks?).to eq false
it 'returns the correct task status' do
expect(subject.task_status).to match('1 of')
expect(subject.task_status).to match('1 task completed')
end
end
end
......@@ -6,7 +6,7 @@ module TestEnv
# When developing the seed repository, comment out the branch you will modify.
BRANCH_SHA = {
'empty-branch' => '7efb185',
'ends-with.json' => '98b0d8b3',
'ends-with.json' => '98b0d8b',
'flatten-dir' => 'e56497b',
'feature' => '0b4bc9a',
'feature_conflict' => 'bb5206f',
......@@ -37,9 +37,10 @@ module TestEnv
# need to keep all the branches in sync.
# We currently only need a subset of the branches
FORKED_BRANCH_SHA = {
'add-submodule-version-bump' => '3f547c08',
'master' => '5937ac0',
'remove-submodule' => '2a33e0c0'
'add-submodule-version-bump' => '3f547c0',
'master' => '5937ac0',
'remove-submodule' => '2a33e0c',
'conflict-resolvable-fork' => '404fa3f'
}
# Test environment
......@@ -117,22 +118,7 @@ module TestEnv
system(*%W(#{Gitlab.config.git.bin_path} clone -q #{clone_url} #{repo_path}))
end
Dir.chdir(repo_path) do
branch_sha.each do |branch, sha|
# Try to reset without fetching to avoid using the network.
reset = %W(#{Gitlab.config.git.bin_path} update-ref refs/heads/#{branch} #{sha})
unless system(*reset)
if system(*%W(#{Gitlab.config.git.bin_path} fetch origin))
unless system(*reset)
raise 'The fetched test seed '\
'does not contain the required revision.'
end
else
raise 'Could not fetch test seed repository.'
end
end
end
end
set_repo_refs(repo_path, branch_sha)
# We must copy bare repositories because we will push to them.
system(git_env, *%W(#{Gitlab.config.git.bin_path} clone -q --bare #{repo_path} #{repo_path_bare}))
......@@ -144,6 +130,7 @@ module TestEnv
FileUtils.mkdir_p(target_repo_path)
FileUtils.cp_r("#{base_repo_path}/.", target_repo_path)
FileUtils.chmod_R 0755, target_repo_path
set_repo_refs(target_repo_path, BRANCH_SHA)
end
def repos_path
......@@ -160,6 +147,7 @@ module TestEnv
FileUtils.mkdir_p(target_repo_path)
FileUtils.cp_r("#{base_repo_path}/.", target_repo_path)
FileUtils.chmod_R 0755, target_repo_path
set_repo_refs(target_repo_path, FORKED_BRANCH_SHA)
end
# When no cached assets exist, manually hit the root path to create them
......@@ -209,4 +197,23 @@ module TestEnv
def git_env
{ 'GIT_TEMPLATE_DIR' => '' }
end
def set_repo_refs(repo_path, branch_sha)
Dir.chdir(repo_path) do
branch_sha.each do |branch, sha|
# Try to reset without fetching to avoid using the network.
reset = %W(#{Gitlab.config.git.bin_path} update-ref refs/heads/#{branch} #{sha})
unless system(*reset)
if system(*%W(#{Gitlab.config.git.bin_path} fetch origin))
unless system(*reset)
raise 'The fetched test seed '\
'does not contain the required revision.'
end
else
raise 'Could not fetch test seed repository.'
end
end
end
end
end
end
require 'spec_helper'
describe 'projects/merge_requests/edit.html.haml' do
include Devise::TestHelpers
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:fork_project) { create(:project, forked_from_project: project) }
let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) }
let(:closed_merge_request) do
create(:closed_merge_request,
source_project: fork_project,
target_project: project,
author: user)
end
before do
assign(:project, project)
assign(:merge_request, closed_merge_request)
allow(view).to receive(:can?).and_return(true)
allow(view).to receive(:current_user)
.and_return(User.find(closed_merge_request.author_id))
end
context 'when a merge request without fork' do
it "shows editable fields" do
unlink_project.execute
closed_merge_request.reload
render
expect(rendered).to have_field('merge_request[title]')
expect(rendered).to have_field('merge_request[description]')
expect(rendered).to have_selector('#merge_request_assignee_id', visible: false)
expect(rendered).to have_selector('#merge_request_milestone_id', visible: false)
expect(rendered).not_to have_selector('#merge_request_target_branch', visible: false)
end
end
context 'when a merge request with an existing source project is closed' do
it "shows editable fields" do
render
expect(rendered).to have_field('merge_request[title]')
expect(rendered).to have_field('merge_request[description]')
expect(rendered).to have_selector('#merge_request_assignee_id', visible: false)
expect(rendered).to have_selector('#merge_request_milestone_id', visible: false)
expect(rendered).to have_selector('#merge_request_target_branch', visible: false)
end
end
end
require 'spec_helper'
describe 'projects/merge_requests/show.html.haml' do
include Devise::TestHelpers
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:fork_project) { create(:project, forked_from_project: project) }
let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) }
let(:closed_merge_request) do
create(:closed_merge_request,
source_project: fork_project,
target_project: project,
author: user)
end
before do
assign(:project, project)
assign(:merge_request, closed_merge_request)
assign(:commits_count, 0)
allow(view).to receive(:can?).and_return(true)
end
context 'when the merge request is closed' do
it 'shows the "Reopen" button' do
render
expect(rendered).to have_css('a', visible: true, text: 'Reopen')
expect(rendered).to have_css('a', visible: false, text: 'Close')
end
it 'does not show the "Reopen" button when the source project does not exist' do
unlink_project.execute
closed_merge_request.reload
render
expect(rendered).to have_css('a', visible: false, text: 'Reopen')
expect(rendered).to have_css('a', visible: false, text: 'Close')
end
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