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. Please view this file on the master branch, on stable branches it's out of date.
v 8.12.0 (unreleased) v 8.12.0 (unreleased)
- Make push events have equal vertical spacing.
- Add two-factor recovery endpoint to internal API !5510 - 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) - 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 - Change merge_error column from string to text type
- Reduce contributions calendar data payload (ClemMakesApps) - Reduce contributions calendar data payload (ClemMakesApps)
- Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) - 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) - 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) - 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) - Optimistic locking for Issues and Merge Requests (title and description overriding prevention)
- Add `wiki_page_events` to project hook APIs (Ben Boeckel) - Add `wiki_page_events` to project hook APIs (Ben Boeckel)
- Remove Gitorious import - Remove Gitorious import
- Fix inconsistent background color for filter input field (ClemMakesApps)
- Add Sentry logging to API calls - Add Sentry logging to API calls
- Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling)
- Remove unused mixins (ClemMakesApps)
- Fix groups sort dropdown alignment (ClemMakesApps) - Fix groups sort dropdown alignment (ClemMakesApps)
- Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps) - Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps)
- Fix markdown help references (ClemMakesApps) - Fix markdown help references (ClemMakesApps)
- Added tests for diff notes - 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) - Add delimiter to project stars and forks count (ClemMakesApps)
- Fix badge count alignment (ClemMakesApps) - Fix badge count alignment (ClemMakesApps)
- Fix branch title trailing space on hover (ClemMakesApps) - Fix branch title trailing space on hover (ClemMakesApps)
...@@ -26,6 +35,7 @@ v 8.12.0 (unreleased) ...@@ -26,6 +35,7 @@ v 8.12.0 (unreleased)
- Update merge_requests.md with a simpler way to check out a merge request. !5944 - Update merge_requests.md with a simpler way to check out a merge request. !5944
- Fix button missing type (ClemMakesApps) - Fix button missing type (ClemMakesApps)
- Move to project dropdown with infinite scroll for better performance - 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. - Load branches asynchronously in Cherry Pick and Revert dialogs.
- Add merge request versions !5467 - Add merge request versions !5467
- Change using size to use count and caching it for number of group members. !5935 - Change using size to use count and caching it for number of group members. !5935
...@@ -34,11 +44,24 @@ v 8.12.0 (unreleased) ...@@ -34,11 +44,24 @@ v 8.12.0 (unreleased)
- Capitalize mentioned issue timeline notes (ClemMakesApps) - Capitalize mentioned issue timeline notes (ClemMakesApps)
- Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger) - 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 - 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) 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 - Allow system info page to handle case where info is unavailable
- Label list shows all issues (opened or closed) with that label - Label list shows all issues (opened or closed) with that label
- Don't show resolve conflicts link before MR status is updated - 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 - Fix IE11 fork button bug !598
- Don't prevent viewing the MR when git refs for conflicts can't be found on disk - 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 - Fix external issue tracker "Issues" link leading to 404s
......
...@@ -129,7 +129,7 @@ request that potentially fixes it. ...@@ -129,7 +129,7 @@ request that potentially fixes it.
### Feature proposals ### 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]. [issue tracker of CE][ce-tracker].
For feature proposals for EE, open an issue on the 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"`. ...@@ -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 Please keep feature proposals as small and simple as possible, complex ones
might be edited to make them small and simple. might be edited to make them small and simple.
You are encouraged to use the template below for feature proposals. Please submit Feature Proposals using the ['Feature Proposal' issue template](.gitlab/issue_templates/Feature Proposal.md) provided on the issue tracker.
```
## Description
Include problem, use cases, benefits, and/or goals
## Proposal
## Links / references
```
For changes in the interface, it can be helpful to create a mockup first. 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 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 ...@@ -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 feature proposal. Show your support with an award emoji and/or join the
discussion. 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 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 when submitting the actual issue. You can copy-paste it and then edit as you
see fit. 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
Issue weight allows us to get an idea of the amount of work required to solve Issue weight allows us to get an idea of the amount of work required to solve
......
...@@ -117,7 +117,7 @@ ...@@ -117,7 +117,7 @@
} }
}); });
} else { } else {
return elements.show(); return elements.show().removeClass('option-hidden');
} }
} }
}; };
...@@ -190,9 +190,9 @@ ...@@ -190,9 +190,9 @@
currentIndex = -1; 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 CURSOR_SELECT_SCROLL_PADDING = 5
...@@ -565,10 +565,6 @@ ...@@ -565,10 +565,6 @@
} else { } else {
field.remove(); field.remove();
} }
if (this.options.toggleLabel) {
this.updateLabel(selectedObject, el, this);
}
return selectedObject;
} else if (el.hasClass(INDETERMINATE_CLASS)) { } else if (el.hasClass(INDETERMINATE_CLASS)) {
el.addClass(ACTIVE_CLASS); el.addClass(ACTIVE_CLASS);
el.removeClass(INDETERMINATE_CLASS); el.removeClass(INDETERMINATE_CLASS);
...@@ -578,7 +574,6 @@ ...@@ -578,7 +574,6 @@
if (!field.length && fieldName) { if (!field.length && fieldName) {
this.addInput(fieldName, value, selectedObject); this.addInput(fieldName, value, selectedObject);
} }
return selectedObject;
} else { } else {
if (!this.options.multiSelect || el.hasClass('dropdown-clear-active')) { if (!this.options.multiSelect || el.hasClass('dropdown-clear-active')) {
this.dropdown.find("." + ACTIVE_CLASS).removeClass(ACTIVE_CLASS); this.dropdown.find("." + ACTIVE_CLASS).removeClass(ACTIVE_CLASS);
...@@ -590,9 +585,6 @@ ...@@ -590,9 +585,6 @@
field.remove(); field.remove();
} }
el.addClass(ACTIVE_CLASS); el.addClass(ACTIVE_CLASS);
if (this.options.toggleLabel) {
this.updateLabel(selectedObject, el, this);
}
if (value != null) { if (value != null) {
if (!field.length && fieldName) { if (!field.length && fieldName) {
this.addInput(fieldName, value, selectedObject); this.addInput(fieldName, value, selectedObject);
...@@ -600,8 +592,14 @@ ...@@ -600,8 +592,14 @@
field.val(value).trigger('change'); 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) { GitLabDropdown.prototype.addInput = function(fieldName, value, selectedObject) {
......
(function() { (function() {
var clearHighlights, currentTimer, defaultClass, delay, firstPiece, pieceIndex, pieces, start, stop, work;
Turbolinks.enableProgressBar(); 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() { start = function() {
clearHighlights(); $('.tanuki-logo').addClass('animate');
pieceIndex = 0;
if (pieces[0] !== firstPiece) {
pieces.reverse();
}
if (currentTimer) {
clearInterval(currentTimer);
}
return currentTimer = setInterval(work, delay);
}; };
stop = function() { stop = function() {
clearInterval(currentTimer); $('.tanuki-logo').removeClass('animate');
return clearHighlights();
};
work = function() {
clearHighlights();
$(pieces[pieceIndex]).attr('class', defaultClass + " highlight");
if (pieceIndex === pieces.length - 1) {
pieceIndex = 0;
return pieces.reverse();
} else {
return pieceIndex++;
}
}; };
$(document).on('page:fetch', start); $(document).on('page:fetch', start);
......
...@@ -75,10 +75,8 @@ class MergeConflictResolver { ...@@ -75,10 +75,8 @@ class MergeConflictResolver {
window.location.href = data.redirect_to; window.location.href = data.redirect_to;
}) })
.error(() => { .error(() => {
new Flash('Something went wrong!');
})
.always(() => {
this.vue.isSubmitting = false; this.vue.isSubmitting = false;
new Flash('Something went wrong!');
}); });
} }
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
@import "framework/issue_box.scss"; @import "framework/issue_box.scss";
@import "framework/jquery.scss"; @import "framework/jquery.scss";
@import "framework/lists.scss"; @import "framework/lists.scss";
@import "framework/logo.scss";
@import "framework/markdown_area.scss"; @import "framework/markdown_area.scss";
@import "framework/mobile.scss"; @import "framework/mobile.scss";
@import "framework/modal.scss"; @import "framework/modal.scss";
......
...@@ -195,6 +195,12 @@ ...@@ -195,6 +195,12 @@
.separator + .dropdown-header { .separator + .dropdown-header {
padding-top: 2px; padding-top: 2px;
} }
.unclickable {
cursor: not-allowed;
padding: 5px 8px;
color: $dropdown-header-color;
}
} }
.dropdown-menu-large { .dropdown-menu-large {
......
...@@ -19,7 +19,6 @@ input[type='text'].danger { ...@@ -19,7 +19,6 @@ input[type='text'].danger {
} }
.form-actions { .form-actions {
margin: -$gl-padding;
margin-top: 0; margin-top: 0;
margin-bottom: -$gl-padding; margin-bottom: -$gl-padding;
padding: $gl-padding; padding: $gl-padding;
......
...@@ -2,16 +2,6 @@ ...@@ -2,16 +2,6 @@
* Application Header * 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 { header {
transition: padding $sidebar-transition-duration; transition: padding $sidebar-transition-duration;
...@@ -25,7 +15,7 @@ header { ...@@ -25,7 +15,7 @@ header {
margin: 8px 0; margin: 8px 0;
text-align: center; text-align: center;
#tanuki-logo, img { .tanuki-logo, img {
height: 36px; height: 36px;
} }
} }
...@@ -205,26 +195,6 @@ header { ...@@ -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) { @media (max-width: $screen-xs-max) {
header .container-fluid { header .container-fluid {
font-size: 18px; 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 @@ ...@@ -9,22 +9,6 @@
border-radius: $radius; 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) { @mixin transition($transition) {
-webkit-transition: $transition; -webkit-transition: $transition;
-moz-transition: $transition; -moz-transition: $transition;
...@@ -38,14 +22,6 @@ ...@@ -38,14 +22,6 @@
* Mixins with fixed values * 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%) { @mixin str-truncated($max_width: 82%) {
display: inline-block; display: inline-block;
overflow: hidden; overflow: hidden;
...@@ -94,23 +70,6 @@ ...@@ -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 { @mixin bulleted-list {
> ul { > ul {
list-style-type: disc; list-style-type: disc;
...@@ -129,3 +88,8 @@ ...@@ -129,3 +88,8 @@
color: rgba(255, 255, 255, 0.3); color: rgba(255, 255, 255, 0.3);
background: rgba(255, 255, 255, 0.1); background: rgba(255, 255, 255, 0.1);
} }
@mixin webkit-prefix($property, $value) {
#{'-webkit-' + $property}: $value;
#{$property}: $value;
}
...@@ -8,10 +8,7 @@ ...@@ -8,10 +8,7 @@
height: 30px; height: 30px;
transition-duration: .3s; transition-duration: .3s;
-webkit-transform: translateZ(0); -webkit-transform: translateZ(0);
background: -webkit-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));
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%);
&.scrolling { &.scrolling {
visibility: visible; visibility: visible;
...@@ -161,6 +158,7 @@ ...@@ -161,6 +158,7 @@
> .dropdown { > .dropdown {
margin-right: $gl-padding-top; margin-right: $gl-padding-top;
display: inline-block; display: inline-block;
vertical-align: top;
&:last-child { &:last-child {
margin-right: 0; margin-right: 0;
...@@ -210,12 +208,6 @@ ...@@ -210,12 +208,6 @@
} }
} }
.project-filter-form {
input {
background-color: $background-color;
}
}
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
padding-bottom: 0; padding-bottom: 0;
width: 100%; width: 100%;
......
...@@ -115,11 +115,8 @@ ...@@ -115,11 +115,8 @@
} }
&.commits-stat { &.commits-stat {
margin-top: 3px;
display: block; display: block;
padding: 3px; padding: 0 3px 0 0;
padding-left: 0;
&:hover { &:hover {
background: none; background: none;
} }
......
...@@ -319,6 +319,14 @@ ...@@ -319,6 +319,14 @@
a { a {
color: $layout-link-gray; color: $layout-link-gray;
text-decoration: none;
&:hover {
.ci-status-text {
text-decoration: underline;
}
}
} }
} }
......
...@@ -600,7 +600,13 @@ pre.light-well { ...@@ -600,7 +600,13 @@ pre.light-well {
} }
} }
.project-show-readme .readme-holder { .project-show-readme {
.row-content-block {
background-color: inherit;
border: none;
}
.readme-holder {
padding: $gl-padding 0; padding: $gl-padding 0;
border-top: 0; border-top: 0;
...@@ -613,6 +619,7 @@ pre.light-well { ...@@ -613,6 +619,7 @@ pre.light-well {
border-bottom: none; border-bottom: none;
padding: 0; padding: 0;
} }
}
} }
.git-clone-holder { .git-clone-holder {
......
...@@ -43,6 +43,15 @@ ...@@ -43,6 +43,15 @@
border-color: $blue-normal; border-color: $blue-normal;
} }
&.ci-created {
color: $table-text-gray;
border-color: $table-text-gray;
svg {
fill: $table-text-gray;
}
}
svg { svg {
height: 13px; height: 13px;
width: 13px; width: 13px;
......
class Projects::ArtifactsController < Projects::ApplicationController class Projects::ArtifactsController < Projects::ApplicationController
include ExtractsPath
layout 'project' layout 'project'
before_action :authorize_read_build! before_action :authorize_read_build!
before_action :authorize_update_build!, only: [:keep] before_action :authorize_update_build!, only: [:keep]
before_action :extract_ref_name_and_path
before_action :validate_artifacts! before_action :validate_artifacts!
def download def download
unless artifacts_file.file_storage? if artifacts_file.file_storage?
return redirect_to artifacts_file.url
end
send_file artifacts_file.path, disposition: 'attachment' send_file artifacts_file.path, disposition: 'attachment'
else
redirect_to artifacts_file.url
end
end end
def browse def browse
directory = params[:path] ? "#{params[:path]}/" : '' directory = params[:path] ? "#{params[:path]}/" : ''
@entry = build.artifacts_metadata_entry(directory) @entry = build.artifacts_metadata_entry(directory)
return render_404 unless @entry.exists? render_404 unless @entry.exists?
end end
def file def file
...@@ -34,14 +37,41 @@ class Projects::ArtifactsController < Projects::ApplicationController ...@@ -34,14 +37,41 @@ class Projects::ArtifactsController < Projects::ApplicationController
redirect_to namespace_project_build_path(project.namespace, project, build) redirect_to namespace_project_build_path(project.namespace, project, build)
end 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 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! def validate_artifacts!
render_404 unless build.artifacts? render_404 unless build && build.artifacts?
end end
def build 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 end
def artifacts_file def artifacts_file
......
...@@ -25,6 +25,11 @@ module CiStatusHelper ...@@ -25,6 +25,11 @@ module CiStatusHelper
end end
end end
def ci_status_for_statuseable(subject)
status = subject.try(:status) || 'not found'
status.humanize
end
def ci_icon_for_status(status) def ci_icon_for_status(status)
icon_name = icon_name =
case status case status
...@@ -41,7 +46,7 @@ module CiStatusHelper ...@@ -41,7 +46,7 @@ module CiStatusHelper
when 'play' when 'play'
'icon_play' 'icon_play'
when 'created' when 'created'
'icon_status_pending' 'icon_status_created'
else else
'icon_status_cancel' 'icon_status_cancel'
end end
...@@ -66,10 +71,10 @@ module CiStatusHelper ...@@ -66,10 +71,10 @@ module CiStatusHelper
Ci::Runner.shared.blank? Ci::Runner.shared.blank?
end 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}" klass = "ci-status-link ci-status-icon-#{status.dasherize} #{cssclass}"
title = "#{type.titleize}: #{ci_label_for_status(status)}" title = "#{type.titleize}: #{ci_label_for_status(status)}"
data = { toggle: 'tooltip', placement: tooltip_placement } data = { toggle: 'tooltip', placement: tooltip_placement, container: container }
if path if path
link_to ci_icon_for_status(status), path, link_to ci_icon_for_status(status), path,
......
...@@ -149,4 +149,20 @@ module GitlabRoutingHelper ...@@ -149,4 +149,20 @@ module GitlabRoutingHelper
def resend_invite_group_member_path(group_member, *args) def resend_invite_group_member_path(group_member, *args)
resend_invite_group_group_member_path(group_member.source, group_member) resend_invite_group_group_member_path(group_member.source, group_member)
end 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 end
...@@ -98,6 +98,6 @@ module MergeRequestsHelper ...@@ -98,6 +98,6 @@ module MergeRequestsHelper
end end
def merge_request_button_visibility(merge_request, closed) 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
end end
...@@ -355,7 +355,7 @@ class Ability ...@@ -355,7 +355,7 @@ class Ability
rules += named_abilities('project_snippet') rules += named_abilities('project_snippet')
end end
unless project.wiki_enabled unless project.has_wiki?
rules += named_abilities('wiki') rules += named_abilities('wiki')
end end
......
...@@ -65,8 +65,8 @@ module Ci ...@@ -65,8 +65,8 @@ module Ci
end end
# ref can't be HEAD or SHA, can only be branch/tag name # ref can't be HEAD or SHA, can only be branch/tag name
scope :latest_successful_for, ->(ref = default_branch) do def self.latest_successful_for(ref)
where(ref: ref).success.order(id: :desc).limit(1) where(ref: ref).order(id: :desc).success.first
end end
def self.truncate_sha(sha) def self.truncate_sha(sha)
......
...@@ -28,4 +28,8 @@ module NoteOnDiff ...@@ -28,4 +28,8 @@ module NoteOnDiff
def can_be_award_emoji? def can_be_award_emoji?
false false
end end
def to_discussion
Discussion.new([self])
end
end end
...@@ -52,11 +52,11 @@ module Taskable ...@@ -52,11 +52,11 @@ module Taskable
end end
# Return a string that describes the current state of this Taskable's task # 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 def task_status
return '' if description.blank? return '' if description.blank?
sum = tasks.summary 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
end end
...@@ -107,10 +107,6 @@ class DiffNote < Note ...@@ -107,10 +107,6 @@ class DiffNote < Note
self.noteable.find_diff_discussion(self.discussion_id) self.noteable.find_diff_discussion(self.discussion_id)
end end
def to_discussion
Discussion.new([self])
end
private private
def supported? def supported?
......
...@@ -91,13 +91,13 @@ class MergeRequest < ActiveRecord::Base ...@@ -91,13 +91,13 @@ class MergeRequest < ActiveRecord::Base
end end
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 :source_branch, presence: true
validates :target_project, presence: true validates :target_project, presence: true
validates :target_branch, presence: true validates :target_branch, presence: true
validates :merge_user, presence: true, if: :merge_when_build_succeeds? validates :merge_user, presence: true, if: :merge_when_build_succeeds?
validate :validate_branches, unless: [:allow_broken, :importing?] validate :validate_branches, unless: [:allow_broken, :importing?, :closed_without_fork?]
validate :validate_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 :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) } scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
...@@ -305,19 +305,22 @@ class MergeRequest < ActiveRecord::Base ...@@ -305,19 +305,22 @@ class MergeRequest < ActiveRecord::Base
def validate_fork def validate_fork
return true unless target_project && source_project 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, errors.add :validate_fork,
'Source project is not a fork of target project' 'Source project is not a fork of the target project'
end end
def closed_without_fork?
closed? && forked_source_project_missing?
end end
def forked_source_project_missing?
return false unless for_fork?
return true unless source_project
!source_project.forked_from?(target_project)
end end
def ensure_merge_request_diff def ensure_merge_request_diff
...@@ -726,7 +729,9 @@ class MergeRequest < ActiveRecord::Base ...@@ -726,7 +729,9 @@ class MergeRequest < ActiveRecord::Base
end end
def pipeline 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 end
def all_pipelines def all_pipelines
......
...@@ -436,7 +436,7 @@ class Project < ActiveRecord::Base ...@@ -436,7 +436,7 @@ class Project < ActiveRecord::Base
# ref can't be HEAD, can only be branch/tag name or SHA # ref can't be HEAD, can only be branch/tag name or SHA
def latest_successful_builds_for(ref = default_branch) 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 if latest_pipeline
latest_pipeline.builds.latest.with_artifacts latest_pipeline.builds.latest.with_artifacts
...@@ -680,6 +680,10 @@ class Project < ActiveRecord::Base ...@@ -680,6 +680,10 @@ class Project < ActiveRecord::Base
update_column(:has_external_issue_tracker, services.external_issue_trackers.any?) update_column(:has_external_issue_tracker, services.external_issue_trackers.any?)
end end
def has_wiki?
wiki_enabled? || has_external_wiki?
end
def external_wiki def external_wiki
if has_external_wiki.nil? if has_external_wiki.nil?
cache_has_external_wiki # Populate cache_has_external_wiki # Populate
...@@ -1096,12 +1100,17 @@ class Project < ActiveRecord::Base ...@@ -1096,12 +1100,17 @@ class Project < ActiveRecord::Base
!namespace.share_with_group_lock !namespace.share_with_group_lock
end 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) pipelines.order(id: :desc).find_by(sha: sha, ref: ref)
end end
def ensure_pipeline(sha, ref, current_user = nil) def ensure_pipeline(ref, sha, current_user = nil)
pipeline(sha, ref) || pipelines.create(sha: sha, ref: ref, user: current_user) pipeline_for(ref, sha) ||
pipelines.create(sha: sha, ref: ref, user: current_user)
end end
def enable_ci def enable_ci
......
module MergeRequests module MergeRequests
class ResolveService < MergeRequests::BaseService class ResolveService < MergeRequests::BaseService
attr_accessor :conflicts, :rugged, :merge_index attr_accessor :conflicts, :rugged, :merge_index, :merge_request
def execute(merge_request) def execute(merge_request)
@conflicts = merge_request.conflicts @conflicts = merge_request.conflicts
@rugged = project.repository.rugged @rugged = project.repository.rugged
@merge_index = conflicts.merge_index @merge_index = conflicts.merge_index
@merge_request = merge_request
fetch_their_commit!
conflicts.files.each do |file| conflicts.files.each do |file|
write_resolved_file_to_index(file, params[:sections]) write_resolved_file_to_index(file, params[:sections])
...@@ -27,5 +30,21 @@ module MergeRequests ...@@ -27,5 +30,21 @@ module MergeRequests
merge_index.add(path: our_path, oid: rugged.write(new_file, :blob), mode: file.our_mode) merge_index.add(path: our_path, oid: rugged.write(new_file, :blob), mode: file.our_mode)
merge_index.conflict_remove(our_path) merge_index.conflict_remove(our_path)
end 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
end end
...@@ -11,6 +11,10 @@ module MergeRequests ...@@ -11,6 +11,10 @@ module MergeRequests
params.except!(:target_project_id) params.except!(:target_project_id)
params.except!(:source_branch) 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) merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
update(merge_request) update(merge_request)
......
- page_title "CI Lint"
- page_description "Validate your GitLab CI configuration file"
%h2 Check your .gitlab-ci.yml %h2 Check your .gitlab-ci.yml
%hr %hr
......
...@@ -27,6 +27,8 @@ ...@@ -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 = 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 Compare
= render 'projects/buttons/download', project: @project, ref: branch.name
- if can_remove_branch?(@project, 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 = 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") = icon("trash-o")
......
- unless @project.empty_repo? - if !project.empty_repo? && can?(current_user, :download_code, project)
- if can? current_user, :download_code, @project %span.btn-group{class: 'hidden-xs hidden-sm btn-grouped'}
= 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 .dropdown.inline
%button.btn{ 'data-toggle' => 'dropdown' }
= icon('download') = 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 @@ ...@@ -5,11 +5,11 @@
- if is_playable - 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 = 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') = render_status_with_link('build', 'play')
= subject.name %span.ci-status-text= subject.name
- elsif can?(current_user, :read_build, @project) - elsif can?(current_user, :read_build, @project)
= link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do
= render_status_with_link('build', subject.status) = render_status_with_link('build', subject.status)
= subject.name %span.ci-status-text= subject.name
- else - else
= render_status_with_link('build', subject.status) = render_status_with_link('build', subject.status)
= ci_icon_for_status(subject.status) = ci_icon_for_status(subject.status)
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
- if subject.target_url - if subject.target_url
- link_to subject.target_url do - link_to subject.target_url do
= render_status_with_link('commit status', subject.status) = render_status_with_link('commit status', subject.status)
= subject.name %span.ci-status-text= subject.name
- else - else
= render_status_with_link('commit status', subject.status) = render_status_with_link('commit status', subject.status)
= subject.name %span.ci-status-text= subject.name
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
- @related_branches.each do |branch| - @related_branches.each do |branch|
%li %li
- target = @project.repository.find_branch(branch).target - 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 - if pipeline
%span.related-branch-ci-status %span.related-branch-ci-status
= render_pipeline_status(pipeline) = 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 .clearfix.detail-page-header
.issuable-header .issuable-header
.issuable-status-box.status-box{ class: status_box_class(@merge_request) } .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 @@ ...@@ -72,7 +72,7 @@
= render "projects/buttons/koding" = render "projects/buttons/koding"
.btn-group.project-repo-btn-group .btn-group.project-repo-btn-group
= render "projects/buttons/download" = render 'projects/buttons/download', project: @project, ref: @ref
= render 'projects/buttons/dropdown' = render 'projects/buttons/dropdown'
= render 'shared/notifications/button', notification_setting: @notification_setting = 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 @@ ...@@ -11,8 +11,7 @@
= strip_gpg_signature(tag.message) = strip_gpg_signature(tag.message)
.controls .controls
- if can?(current_user, :download_code, @project) = render 'projects/buttons/download', project: @project, ref: tag.name
= render 'projects/tags/download', ref: tag.name, project: @project
- if can?(current_user, :push_code, @project) - 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 = 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 @@ ...@@ -12,8 +12,7 @@
= icon('files-o') = icon('files-o')
= link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Browse commits' do = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Browse commits' do
= icon('history') = icon('history')
- if can? current_user, :download_code, @project = render 'projects/buttons/download', project: @project, ref: @tag.name
= render 'projects/tags/download', ref: @tag.name, project: @project
- if can?(current_user, :admin_project, @project) - if can?(current_user, :admin_project, @project)
.pull-right .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 = 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 @@ ...@@ -10,8 +10,7 @@
%div{ class: container_class } %div{ class: container_class }
.tree-controls .tree-controls
= render 'projects/find_file_link' = render 'projects/find_file_link'
- if can? current_user, :download_code, @project = render 'projects/buttons/download', project: @project, ref: @ref
= render 'projects/repositories/download_archive', ref: @ref, btn_class: 'hidden-xs hidden-sm btn-grouped', split_button: true
#tree-holder.tree-holder.clearfix #tree-holder.tree-holder.clearfix
.nav-block .nav-block
......
<svg width="36" height="36" id="tanuki-logo"> <svg width="36" height="36" class="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 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 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 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 id="tanuki-nose" class="tanuki-shape" fill="#e24329" d="M18,34.38 3,14 33,14 Z"/> <path class="tanuki-shape tanuki-nose" 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 class="tanuki-shape tanuki-left-eye" 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 class="tanuki-shape tanuki-right-eye" 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 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 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"/> <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>
<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 @@ ...@@ -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.' } 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') = icon('question-circle')
- if issuable.is_a?(MergeRequest) - if issuable.is_a?(MergeRequest) && !issuable.closed_without_fork?
%hr %hr
- if @merge_request.new_record? - if @merge_request.new_record?
.form-group .form-group
...@@ -175,7 +175,7 @@ ...@@ -175,7 +175,7 @@
= link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class]), class: 'btn btn-cancel' = link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class]), class: 'btn btn-cancel'
- else - else
.pull-right .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?" }, = 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' 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' = 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 ...@@ -782,6 +782,14 @@ Rails.application.routes.draw do
resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do
collection do collection do
post :cancel_all post :cancel_all
resources :artifacts, only: [] do
collection do
get :latest_succeeded,
path: '*ref_name_and_path',
format: false
end
end
end end
member do member do
......
...@@ -10,7 +10,7 @@ GET /projects/:id/repository/commits ...@@ -10,7 +10,7 @@ GET /projects/:id/repository/commits
| Attribute | Type | Required | Description | | 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 | | `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 | | `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 | | `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: ...@@ -58,7 +58,7 @@ Parameters:
| Attribute | Type | Required | Description | | 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 | | `sha` | string | yes | The commit hash or name of a repository branch or tag |
```bash ```bash
...@@ -102,7 +102,7 @@ Parameters: ...@@ -102,7 +102,7 @@ Parameters:
| Attribute | Type | Required | Description | | 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 | | `sha` | string | yes | The commit hash or name of a repository branch or tag |
```bash ```bash
...@@ -138,7 +138,7 @@ Parameters: ...@@ -138,7 +138,7 @@ Parameters:
| Attribute | Type | Required | Description | | 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 | | `sha` | string | yes | The commit hash or name of a repository branch or tag |
```bash ```bash
...@@ -187,7 +187,7 @@ POST /projects/:id/repository/commits/:sha/comments ...@@ -187,7 +187,7 @@ POST /projects/:id/repository/commits/:sha/comments
| Attribute | Type | Required | Description | | 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 | | `sha` | string | yes | The commit SHA or name of a repository branch or tag |
| `note` | string | yes | The text of the comment | | `note` | string | yes | The text of the comment |
| `path` | string | no | The file path relative to the repository | | `path` | string | no | The file path relative to the repository |
...@@ -232,7 +232,7 @@ GET /projects/:id/repository/commits/:sha/statuses ...@@ -232,7 +232,7 @@ GET /projects/:id/repository/commits/:sha/statuses
| Attribute | Type | Required | Description | | 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 | `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 | `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` | `stage` | string | no | Filter by [build stage](../ci/yaml/README.md#stages), e.g., `test`
...@@ -306,7 +306,7 @@ POST /projects/:id/statuses/:sha ...@@ -306,7 +306,7 @@ POST /projects/:id/statuses/:sha
| Attribute | Type | Required | Description | | 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 | `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` | `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 | `ref` | string | no | The `ref` (branch or tag) to which the status refers
......
...@@ -489,7 +489,7 @@ PUT /projects/:id ...@@ -489,7 +489,7 @@ PUT /projects/:id
Parameters: Parameters:
- `id` (required) - The ID of a project - `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project
- `name` (optional) - project name - `name` (optional) - project name
- `path` (optional) - repository name for project - `path` (optional) - repository name for project
- `description` (optional) - short project description - `description` (optional) - short project description
...@@ -519,7 +519,7 @@ POST /projects/fork/:id ...@@ -519,7 +519,7 @@ POST /projects/fork/:id
Parameters: 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 ### Star a project
...@@ -532,7 +532,7 @@ POST /projects/:id/star ...@@ -532,7 +532,7 @@ POST /projects/:id/star
| Attribute | Type | Required | Description | | 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 ```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/star" curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/star"
...@@ -599,7 +599,7 @@ DELETE /projects/:id/star ...@@ -599,7 +599,7 @@ DELETE /projects/:id/star
| Attribute | Type | Required | Description | | 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 ```bash
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/star" curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/star"
...@@ -670,7 +670,7 @@ POST /projects/:id/archive ...@@ -670,7 +670,7 @@ POST /projects/:id/archive
| Attribute | Type | Required | Description | | 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 ```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/archive" curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/archive"
...@@ -757,7 +757,7 @@ POST /projects/:id/unarchive ...@@ -757,7 +757,7 @@ POST /projects/:id/unarchive
| Attribute | Type | Required | Description | | 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 ```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/unarchive" curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/unarchive"
...@@ -839,7 +839,7 @@ DELETE /projects/:id ...@@ -839,7 +839,7 @@ DELETE /projects/:id
Parameters: Parameters:
- `id` (required) - The ID of a project - `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked
## Uploads ## Uploads
...@@ -853,7 +853,7 @@ POST /projects/:id/uploads ...@@ -853,7 +853,7 @@ POST /projects/:id/uploads
Parameters: 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 - `file` (required) - The file to be uploaded
```json ```json
...@@ -882,7 +882,7 @@ POST /projects/:id/share ...@@ -882,7 +882,7 @@ POST /projects/:id/share
Parameters: 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_id` (required) - The ID of a group
- `group_access` (required) - Level of permissions for sharing - `group_access` (required) - Level of permissions for sharing
...@@ -1114,7 +1114,7 @@ POST /projects/:id/fork/:forked_from_id ...@@ -1114,7 +1114,7 @@ POST /projects/:id/fork/:forked_from_id
Parameters: 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 - `forked_from_id:` (required) - The ID of the project that was forked from
### Delete an existing forked from relationship ### Delete an existing forked from relationship
...@@ -1125,7 +1125,7 @@ DELETE /projects/:id/fork ...@@ -1125,7 +1125,7 @@ DELETE /projects/:id/fork
Parameter: 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 ## Search for projects by name
......
...@@ -39,7 +39,7 @@ If you want a quick introduction to GitLab CI, follow our ...@@ -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) - [before_script and after_script](#before_script-and-after_script)
- [Git Strategy](#git-strategy) - [Git Strategy](#git-strategy)
- [Shallow cloning](#shallow-cloning) - [Shallow cloning](#shallow-cloning)
- [Hidden jobs](#hidden-jobs) - [Hidden keys](#hidden-keys)
- [Special YAML features](#special-yaml-features) - [Special YAML features](#special-yaml-features)
- [Anchors](#anchors) - [Anchors](#anchors)
- [Validate the .gitlab-ci.yml](#validate-the-gitlab-ci-yml) - [Validate the .gitlab-ci.yml](#validate-the-gitlab-ci-yml)
...@@ -934,24 +934,27 @@ variables: ...@@ -934,24 +934,27 @@ variables:
GIT_DEPTH: "3" GIT_DEPTH: "3"
``` ```
## Hidden jobs ## Hidden keys
>**Note:** >**Note:**
Introduced in GitLab 8.6 and GitLab Runner v1.1.1. 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 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. into templates.
In the following example, `.job_name` will be ignored: In the following example, `.key_name` will be ignored:
```yaml ```yaml
.job_name: .key_name:
script: script:
- rake spec - 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 ## Special YAML features
It's possible to use special YAML features like anchors (`&`), aliases (`*`) 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. ...@@ -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 YAML also has a handy feature called 'anchors', which let you easily duplicate
content across your document. Anchors can be used to duplicate/inherit 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. to provide templates for your jobs.
The following example uses anchors and map merging. It will create two 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, ...@@ -975,7 +978,7 @@ The following example uses anchors and map merging. It will create two jobs,
having their own custom `script` defined: having their own custom `script` defined:
```yaml ```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 image: ruby:2.1
services: services:
- postgres - postgres
...@@ -1081,7 +1084,7 @@ test:mysql: ...@@ -1081,7 +1084,7 @@ test:mysql:
- ruby - 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 ## Validate the .gitlab-ci.yml
......
...@@ -222,18 +222,26 @@ For example, if you were to move `doc/workflow/lfs/lfs_administration.md` to ...@@ -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. 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 Things to note:
`lfs_administration.md` recursively and will print the file and the line
where this file is mentioned. Note that we used just the filename - Since we also use inline documentation, except for the documentation itself,
(`lfs_administration.md`) and not the whole the relative path the document might also be referenced in the views of GitLab (`app/`) which will
(`workflow/lfs/lfs_administration.md`). 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 ## Configuration documentation for source and Omnibus installations
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
This style guide recommends best practices for newlines in Ruby code. 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 ```ruby
# bad # bad
......
# Integrate your server with Bitbucket # Integrate your GitLab server with Bitbucket
Import projects from Bitbucket and login to your GitLab instance with your Bitbucket account. Import projects from Bitbucket.org and login to your GitLab instance with your
Bitbucket.org account.
To enable the Bitbucket OmniAuth provider you must register your application with Bitbucket. ## Overview
Bitbucket will generate an application ID and secret key for you to use.
1. Sign in to Bitbucket. You can set up Bitbucket.org as an OAuth provider so that you can use your
credentials to authenticate into GitLab or import your projects from
Bitbucket.org.
1. Navigate to your individual user settings or a team's settings, depending on how you want the application registered. It does not matter if the application is registered as an individual or a team - that is entirely up to you. - To use Bitbucket.org as an OmniAuth provider, follow the [Bitbucket OmniAuth
provider](#bitbucket-omniauth-provider) section.
- To import projects from Bitbucket, follow both the
[Bitbucket OmniAuth provider](#bitbucket-omniauth-provider) and
[Bitbucket project import](#bitbucket-project-import) sections.
1. Select "OAuth" in the left menu. ## Bitbucket OmniAuth provider
1. Select "Add consumer". > **Note:**
Make sure to first follow the [Initial OmniAuth configuration][init-oauth]
before proceeding with setting up the Bitbucket integration.
1. Provide the required details. To enable the Bitbucket OmniAuth provider you must register your application
- Name: This can be anything. Consider something like `<Organization>'s GitLab` or `<Your Name>'s GitLab` or something else descriptive. with Bitbucket.org. Bitbucket will generate an application ID and secret key for
- Application description: Fill this in if you wish. you to use.
- URL: The URL to your GitLab installation. 'https://gitlab.company.com'
1. Select "Save".
1. You should now see a Key and Secret in the list of OAuth customers. 1. Sign in to [Bitbucket.org](https://bitbucket.org).
Keep this page open as you continue configuration. 1. Navigate to your individual user settings (**Bitbucket settings**) or a team's
settings (**Manage team**), depending on how you want the application registered.
It does not matter if the application is registered as an individual or a
team, that is entirely up to you.
1. Select **OAuth** in the left menu under "Access Management".
1. Select **Add consumer**.
1. Provide the required details:
1. On your GitLab server, open the configuration file. | Item | Description |
| :--- | :---------- |
| **Name** | This can be anything. Consider something like `<Organization>'s GitLab` or `<Your Name>'s GitLab` or something else descriptive. |
| **Application description** | Fill this in if you wish. |
| **Callback URL** | Leave blank. |
| **URL** | The URL to your GitLab installation, e.g., `https://gitlab.example.com`. |
For omnibus package: And grant at least the following permissions:
```sh ```
sudo editor /etc/gitlab/gitlab.rb Account: Email
Repositories: Read, Admin
``` ```
For installations from source: >**Note:**
It may seem a little odd to giving GitLab admin permissions to repositories,
but this is needed in order for GitLab to be able to clone the repositories.
```sh ![Bitbucket OAuth settings page](img/bitbucket_oauth_settings_page.png)
cd /home/git/gitlab
1. Select **Save**.
1. Select your newly created OAuth consumer and you should now see a Key and
Secret in the list of OAuth customers. Keep this page open as you continue
the configuration.
![Bitbucket OAuth key](img/bitbucket_oauth_keys.png)
1. On your GitLab server, open the configuration file:
sudo -u git -H editor config/gitlab.yml
``` ```
# For Omnibus packages
sudo editor /etc/gitlab/gitlab.rb
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings. # For installations from source
sudo -u git -H editor /home/git/gitlab/config/gitlab.yml
```
1. Add the provider configuration: 1. Follow the [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration)
for initial settings.
1. Add the Bitbucket provider configuration:
For omnibus package: For Omnibus packages:
```ruby ```ruby
gitlab_rails['omniauth_providers'] = [ gitlab_rails['omniauth_providers'] = [
{ {
"name" => "bitbucket", "name" => "bitbucket",
"app_id" => "YOUR_KEY", "app_id" => "BITBUCKET_APP_KEY",
"app_secret" => "YOUR_APP_SECRET", "app_secret" => "BITBUCKET_APP_SECRET",
"url" => "https://bitbucket.org/" "url" => "https://bitbucket.org/"
} }
] ]
``` ```
For installation from source: For installations from source:
``` ```yaml
- { name: 'bitbucket', app_id: 'YOUR_KEY', - { name: 'bitbucket',
app_secret: 'YOUR_APP_SECRET' } app_id: 'BITBUCKET_APP_KEY',
app_secret: 'BITBUCKET_APP_SECRET' }
``` ```
1. Change 'YOUR_APP_ID' to the key from the Bitbucket application page from step 7. ---
1. Change 'YOUR_APP_SECRET' to the secret from the Bitbucket application page from step 7. Where `BITBUCKET_APP_KEY` is the Key and `BITBUCKET_APP_SECRET` the Secret
from the Bitbucket application page.
1. Save the configuration file. 1. Save the configuration file.
1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
installed GitLab via Omnibus or from source respectively.
1. If you're using the omnibus package, reconfigure GitLab (```gitlab-ctl reconfigure```). On the sign in page there should now be a Bitbucket icon below the regular sign
in form. Click the icon to begin the authentication process. Bitbucket will ask
1. Restart GitLab for the changes to take effect. the user to sign in and authorize the GitLab application. If everything goes
well, the user will be returned to GitLab and will be signed in.
On the sign in page there should now be a Bitbucket icon below the regular sign in form.
Click the icon to begin the authentication process. Bitbucket will ask the user to sign in and authorize the GitLab application.
If everything goes well the user will be returned to GitLab and will be signed in.
## Bitbucket project import ## Bitbucket project import
To allow projects to be imported directly into GitLab, Bitbucket requires two extra setup steps compared to GitHub and GitLab.com. To allow projects to be imported directly into GitLab, Bitbucket requires two
extra setup steps compared to [GitHub](github.md) and [GitLab.com](gitlab.md).
Bitbucket doesn't allow OAuth applications to clone repositories over HTTPS, and instead requires GitLab to use SSH and identify itself using your GitLab server's SSH key. Bitbucket doesn't allow OAuth applications to clone repositories over HTTPS, and
instead requires GitLab to use SSH and identify itself using your GitLab
server's SSH key.
### Step 1: Public key To be able to access repositories on Bitbucket, GitLab will automatically
register your public key with Bitbucket as a deploy key for the repositories to
be imported. Your public key needs to be at `~/.ssh/bitbucket_rsa` which
translates to `/var/opt/gitlab/.ssh/bitbucket_rsa` for Omnibus packages and to
`/home/git/.ssh/bitbucket_rsa.pub` for installations from source.
To be able to access repositories on Bitbucket, GitLab will automatically register your public key with Bitbucket as a deploy key for the repositories to be imported. Your public key needs to be at `~/.ssh/bitbucket_rsa.pub`, which will expand to `/home/git/.ssh/bitbucket_rsa.pub` in most configurations. ---
If you have that file in place, you're all set and should see the "Import projects from Bitbucket" option enabled. If you don't, do the following: Below are the steps that will allow GitLab to be able to import your projects
from Bitbucket.
1. Create a new SSH key: 1. Make sure you [have enabled the Bitbucket OAuth support](#bitbucket-omniauth-provider).
1. Create a new SSH key with an **empty passphrase**:
```sh ```sh
sudo -u git -H ssh-keygen sudo -u git -H ssh-keygen
``` ```
When asked `Enter file in which to save the key` specify the correct path, eg. `/home/git/.ssh/bitbucket_rsa`. When asked to 'Enter file in which to save the key' enter:
Make sure to use an **empty passphrase**. `/var/opt/gitlab/.ssh/bitbucket_rsa` for Omnibus packages or
`/home/git/.ssh/bitbucket_rsa` for installations from source. The name is
important so make sure to get it right.
1. Configure SSH client to use your new key: > **Warning:**
This key must NOT be associated with ANY existing Bitbucket accounts. If it
is, the import will fail with an `Access denied! Please verify you can add
deploy keys to this repository.` error.
Open the SSH configuration file of the git user. 1. Next, you need to to configure the SSH client to use your new key. Open the
SSH configuration file of the `git` user:
```sh ```
# For Omnibus packages
sudo editor /var/opt/gitlab/.ssh/config
# For installations from source
sudo editor /home/git/.ssh/config sudo editor /home/git/.ssh/config
``` ```
Add a host configuration for `bitbucket.org`. 1. Add a host configuration for `bitbucket.org`:
```sh ```sh
Host bitbucket.org Host bitbucket.org
...@@ -113,28 +166,46 @@ If you have that file in place, you're all set and should see the "Import projec ...@@ -113,28 +166,46 @@ If you have that file in place, you're all set and should see the "Import projec
User git User git
``` ```
### Step 2: Known hosts 1. Save the file and exit.
1. Manually connect to `bitbucket.org` over SSH, while logged in as the `git`
To allow GitLab to connect to Bitbucket over SSH, you need to add 'bitbucket.org' to your GitLab server's known SSH hosts. Take the following steps to do so: user that GitLab will use:
1. Manually connect to 'bitbucket.org' over SSH, while logged in as the `git` account that GitLab will use:
```sh ```sh
sudo -u git -H ssh bitbucket.org sudo -u git -H ssh bitbucket.org
``` ```
1. Verify the RSA key fingerprint you'll see in the response matches the one in the [Bitbucket documentation](https://confluence.atlassian.com/display/BITBUCKET/Use+the+SSH+protocol+with+Bitbucket#UsetheSSHprotocolwithBitbucket-KnownhostorBitbucket'spublickeyfingerprints) (the specific IP address doesn't matter): That step is performed because GitLab needs to connect to Bitbucket over SSH,
in order to add `bitbucket.org` to your GitLab server's known SSH hosts.
1. Verify the RSA key fingerprint you'll see in the response matches the one
in the [Bitbucket documentation][bitbucket-docs] (the specific IP address
doesn't matter):
```sh ```sh
The authenticity of host 'bitbucket.org (207.223.240.182)' can't be established. The authenticity of host 'bitbucket.org (104.192.143.1)' can't be established.
RSA key fingerprint is 97:8c:1b:f2:6f:14:6b:5c:3b:ec:aa:46:46:74:7c:40. RSA key fingerprint is SHA256:zzXQOXSRBEiUtuE8AikJYKwbHaxvSc0ojez9YXaGp1A.
Are you sure you want to continue connecting (yes/no)? Are you sure you want to continue connecting (yes/no)?
``` ```
1. If the fingerprint matches, type `yes` to continue connecting and have 'bitbucket.org' be added to your known hosts. 1. If the fingerprint matches, type `yes` to continue connecting and have
`bitbucket.org` be added to your known SSH hosts. After confirming you should
see a permission denied message. If you see an authentication successful
message you have done something wrong. The key you are using has already been
added to a Bitbucket account and will cause the import script to fail. Ensure
the key you are using CANNOT authenticate with Bitbucket.
1. Restart GitLab to allow it to find the new public key.
Your GitLab server is now able to connect to Bitbucket over SSH. You should be
able to see the "Import projects from Bitbucket" option on the New Project page
enabled.
1. Your GitLab server is now able to connect to Bitbucket over SSH. ## Acknowledgemts
1. Restart GitLab to allow it to find the new public key. Special thanks to the writer behind the following article:
- http://stratus3d.com/blog/2015/09/06/migrating-from-bitbucket-to-local-gitlab-server/
You should now see the "Import projects from Bitbucket" option on the New Project page enabled. [init-oauth]: omniauth.md#initial-omniauth-configuration
[bitbucket-docs]: https://confluence.atlassian.com/bitbucket/use-the-ssh-protocol-with-bitbucket-cloud-221449711.html#UsetheSSHprotocolwithBitbucketCloud-KnownhostorBitbucket%27spublickeyfingerprints
[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
...@@ -102,8 +102,8 @@ To change these settings: ...@@ -102,8 +102,8 @@ To change these settings:
block_auto_created_users: true block_auto_created_users: true
``` ```
Now we can choose one or more of the Supported Providers listed above to continue Now we can choose one or more of the [Supported Providers](#supported-providers)
the configuration process. listed above to continue the configuration process.
## Enable OmniAuth for an Existing User ## Enable OmniAuth for an Existing User
......
...@@ -26,5 +26,5 @@ do. ...@@ -26,5 +26,5 @@ do.
| `/done` | Mark todo as done | | `/done` | Mark todo as done |
| `/subscribe` | Subscribe | | `/subscribe` | Subscribe |
| `/unsubscribe` | Unsubscribe | | `/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 | | `/remove_due_date` | Remove due date |
...@@ -64,7 +64,7 @@ module API ...@@ -64,7 +64,7 @@ module API
ref = branches.first ref = branches.first
end end
pipeline = @project.ensure_pipeline(commit.sha, ref, current_user) pipeline = @project.ensure_pipeline(ref, commit.sha, current_user)
name = params[:name] || params[:context] name = params[:name] || params[:context]
status = GenericCommitStatus.running_or_pending.find_by(pipeline: pipeline, name: name, ref: params[:ref]) status = GenericCommitStatus.running_or_pending.find_by(pipeline: pipeline, name: name, ref: params[:ref])
......
...@@ -55,7 +55,7 @@ module Backup ...@@ -55,7 +55,7 @@ module Backup
bk_repos_path = File.join(path, '..', 'repositories.old.' + Time.now.to_i.to_s) bk_repos_path = File.join(path, '..', 'repositories.old.' + Time.now.to_i.to_s)
FileUtils.mv(path, bk_repos_path) FileUtils.mv(path, bk_repos_path)
# This is expected from gitlab:check # This is expected from gitlab:check
FileUtils.mkdir_p(path, mode: 2770) FileUtils.mkdir_p(path, mode: 02770)
end end
Project.find_each(batch_size: 1000) do |project| Project.find_each(batch_size: 1000) do |project|
......
...@@ -12,9 +12,7 @@ module Gitlab ...@@ -12,9 +12,7 @@ module Gitlab
@ref = ref @ref = ref
@job = job @job = job
@pipeline = @project.pipelines @pipeline = @project.pipelines.latest_successful_for(@ref)
.latest_successful_for(@ref)
.first
end end
def entity def entity
......
...@@ -5,11 +5,10 @@ module Gitlab ...@@ -5,11 +5,10 @@ module Gitlab
## ##
# Entry that represents a hidden CI/CD job. # Entry that represents a hidden CI/CD job.
# #
class HiddenJob < Entry class Hidden < Entry
include Validatable include Validatable
validations do validations do
validates :config, type: Hash
validates :config, presence: true validates :config, presence: true
end end
......
...@@ -30,7 +30,7 @@ module Gitlab ...@@ -30,7 +30,7 @@ module Gitlab
def compose! def compose!
@config.each do |name, config| @config.each do |name, config|
node = hidden?(name) ? Node::HiddenJob : Node::Job node = hidden?(name) ? Node::Hidden : Node::Job
factory = Node::Factory.new(node) factory = Node::Factory.new(node)
.value(config || {}) .value(config || {})
......
...@@ -170,6 +170,35 @@ describe Projects::MergeRequestsController do ...@@ -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(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 expect(merge_request.reload.closed?).to be_truthy
end 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
end end
......
...@@ -147,6 +147,37 @@ feature 'Diff notes', js: true, feature: true do ...@@ -147,6 +147,37 @@ feature 'Diff notes', js: true, feature: true do
end end
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) def should_allow_commenting(line_holder, diff_side = nil)
line = get_line_components(line_holder, diff_side) line = get_line_components(line_holder, diff_side)
line[:content].hover 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
...@@ -20,6 +20,22 @@ feature 'Task Lists', feature: true do ...@@ -20,6 +20,22 @@ feature 'Task Lists', feature: true do
MARKDOWN MARKDOWN
end end
let(:singleIncompleteMarkdown) do
<<-MARKDOWN.strip_heredoc
This is a task list:
- [ ] Incomplete entry 1
MARKDOWN
end
let(:singleCompleteMarkdown) do
<<-MARKDOWN.strip_heredoc
This is a task list:
- [x] Incomplete entry 1
MARKDOWN
end
before do before do
Warden.test_mode! Warden.test_mode!
...@@ -34,6 +50,7 @@ feature 'Task Lists', feature: true do ...@@ -34,6 +50,7 @@ feature 'Task Lists', feature: true do
end end
describe 'for Issues' do describe 'for Issues' do
describe 'multiple tasks' do
let!(:issue) { create(:issue, description: markdown, author: user, project: project) } let!(:issue) { create(:issue, description: markdown, author: user, project: project) }
it 'renders' do it 'renders' do
...@@ -69,12 +86,48 @@ feature 'Task Lists', feature: true do ...@@ -69,12 +86,48 @@ feature 'Task Lists', feature: true do
it 'provides a summary on Issues#index' do it 'provides a summary on Issues#index' do
visit namespace_project_issues_path(project.namespace, project) visit namespace_project_issues_path(project.namespace, project)
expect(page).to have_content("6 tasks (2 completed, 4 remaining)") expect(page).to have_content("2 of 6 tasks completed")
end
end
describe 'single incomplete task' do
let!(:issue) { create(:issue, description: singleIncompleteMarkdown, author: user, project: project) }
it 'renders' do
visit_issue(project, issue)
expect(page).to have_selector('ul.task-list', count: 1)
expect(page).to have_selector('li.task-list-item', count: 1)
expect(page).to have_selector('ul input[checked]', count: 0)
end
it 'provides a summary on Issues#index' do
visit namespace_project_issues_path(project.namespace, project)
expect(page).to have_content("0 of 1 task completed")
end
end
describe 'single complete task' do
let!(:issue) { create(:issue, description: singleCompleteMarkdown, author: user, project: project) }
it 'renders' do
visit_issue(project, issue)
expect(page).to have_selector('ul.task-list', count: 1)
expect(page).to have_selector('li.task-list-item', count: 1)
expect(page).to have_selector('ul input[checked]', count: 1)
end
it 'provides a summary on Issues#index' do
visit namespace_project_issues_path(project.namespace, project)
expect(page).to have_content("1 of 1 task completed")
end
end end
end end
describe 'for Notes' do describe 'for Notes' do
let!(:issue) { create(:issue, author: user, project: project) } let!(:issue) { create(:issue, author: user, project: project) }
describe 'multiple tasks' do
let!(:note) do let!(:note) do
create(:note, note: markdown, noteable: issue, create(:note, note: markdown, noteable: issue,
project: project, author: user) project: project, author: user)
...@@ -108,11 +161,43 @@ feature 'Task Lists', feature: true do ...@@ -108,11 +161,43 @@ feature 'Task Lists', feature: true do
end end
end end
describe 'single incomplete task' do
let!(:note) do
create(:note, note: singleIncompleteMarkdown, noteable: issue,
project: project, author: user)
end
it 'renders for note body' do
visit_issue(project, issue)
expect(page).to have_selector('.note ul.task-list', count: 1)
expect(page).to have_selector('.note li.task-list-item', count: 1)
expect(page).to have_selector('.note ul input[checked]', count: 0)
end
end
describe 'single complete task' do
let!(:note) do
create(:note, note: singleCompleteMarkdown, noteable: issue,
project: project, author: user)
end
it 'renders for note body' do
visit_issue(project, issue)
expect(page).to have_selector('.note ul.task-list', count: 1)
expect(page).to have_selector('.note li.task-list-item', count: 1)
expect(page).to have_selector('.note ul input[checked]', count: 1)
end
end
end
describe 'for Merge Requests' do describe 'for Merge Requests' do
def visit_merge_request(project, merge) def visit_merge_request(project, merge)
visit namespace_project_merge_request_path(project.namespace, project, merge) visit namespace_project_merge_request_path(project.namespace, project, merge)
end end
describe 'multiple tasks' do
let!(:merge) { create(:merge_request, :simple, description: markdown, author: user, source_project: project) } let!(:merge) { create(:merge_request, :simple, description: markdown, author: user, source_project: project) }
it 'renders for description' do it 'renders for description' do
...@@ -148,7 +233,42 @@ feature 'Task Lists', feature: true do ...@@ -148,7 +233,42 @@ feature 'Task Lists', feature: true do
it 'provides a summary on MergeRequests#index' do it 'provides a summary on MergeRequests#index' do
visit namespace_project_merge_requests_path(project.namespace, project) visit namespace_project_merge_requests_path(project.namespace, project)
expect(page).to have_content("6 tasks (2 completed, 4 remaining)") expect(page).to have_content("2 of 6 tasks completed")
end
end
describe 'single incomplete task' do
let!(:merge) { create(:merge_request, :simple, description: singleIncompleteMarkdown, author: user, source_project: project) }
it 'renders for description' do
visit_merge_request(project, merge)
expect(page).to have_selector('ul.task-list', count: 1)
expect(page).to have_selector('li.task-list-item', count: 1)
expect(page).to have_selector('ul input[checked]', count: 0)
end
it 'provides a summary on MergeRequests#index' do
visit namespace_project_merge_requests_path(project.namespace, project)
expect(page).to have_content("0 of 1 task completed")
end
end
describe 'single complete task' do
let!(:merge) { create(:merge_request, :simple, description: singleCompleteMarkdown, author: user, source_project: project) }
it 'renders for description' do
visit_merge_request(project, merge)
expect(page).to have_selector('ul.task-list', count: 1)
expect(page).to have_selector('li.task-list-item', count: 1)
expect(page).to have_selector('ul input[checked]', count: 1)
end
it 'provides a summary on MergeRequests#index' do
visit namespace_project_merge_requests_path(project.namespace, project)
expect(page).to have_content("1 of 1 task completed")
end
end end
end end
end end
require 'spec_helper' require 'spec_helper'
describe Gitlab::Ci::Config::Node::HiddenJob do describe Gitlab::Ci::Config::Node::Hidden do
let(:entry) { described_class.new(config) } let(:entry) { described_class.new(config) }
describe 'validations' do describe 'validations' do
context 'when entry config value is correct' do context 'when entry config value is correct' do
let(:config) { { image: 'ruby:2.2' } } let(:config) { [:some, :array] }
describe '#value' do describe '#value' do
it 'returns key 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
end end
...@@ -21,17 +21,6 @@ describe Gitlab::Ci::Config::Node::HiddenJob do ...@@ -21,17 +21,6 @@ describe Gitlab::Ci::Config::Node::HiddenJob do
end end
context 'when entry value is not correct' do 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 context 'when config is empty' do
let(:config) { {} } let(:config) { {} }
......
...@@ -74,7 +74,7 @@ describe Gitlab::Ci::Config::Node::Jobs do ...@@ -74,7 +74,7 @@ describe Gitlab::Ci::Config::Node::Jobs do
expect(entry.descendants.first(2)) expect(entry.descendants.first(2))
.to all(be_an_instance_of(Gitlab::Ci::Config::Node::Job)) .to all(be_an_instance_of(Gitlab::Ci::Config::Node::Job))
expect(entry.descendants.last) 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
end end
......
...@@ -282,4 +282,17 @@ describe Ability, lib: true do ...@@ -282,4 +282,17 @@ describe Ability, lib: true do
end end
end 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 end
...@@ -948,15 +948,17 @@ describe Ci::Build, models: true do ...@@ -948,15 +948,17 @@ describe Ci::Build, models: true do
before { build.run! } before { build.run! }
it 'returns false' do it 'returns false' do
expect(build.retryable?).to be false expect(build).not_to be_retryable
end end
end end
context 'when build is finished' do context 'when build is finished' do
before { build.success! } before do
build.success!
end
it 'returns true' do it 'returns true' do
expect(build.retryable?).to be true expect(build).to be_retryable
end end
end end
end end
......
...@@ -195,6 +195,36 @@ describe Ci::Pipeline, models: true do ...@@ -195,6 +195,36 @@ describe Ci::Pipeline, models: true do
end end
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 describe '#manual_actions' do
subject { pipeline.manual_actions } subject { pipeline.manual_actions }
......
...@@ -477,8 +477,8 @@ describe MergeRequest, models: true do ...@@ -477,8 +477,8 @@ describe MergeRequest, models: true do
allow(subject).to receive(:diff_head_sha).and_return('123abc') allow(subject).to receive(:diff_head_sha).and_return('123abc')
expect(subject.source_project).to receive(:pipeline). expect(subject.source_project).to receive(:pipeline_for).
with('123abc', 'master'). with('master', '123abc').
and_return(pipeline) and_return(pipeline)
expect(subject.pipeline).to eq(pipeline) expect(subject.pipeline).to eq(pipeline)
...@@ -962,4 +962,80 @@ describe MergeRequest, models: true do ...@@ -962,4 +962,80 @@ describe MergeRequest, models: true do
expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_truthy expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_truthy
end end
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 end
...@@ -506,6 +506,18 @@ describe Project, models: true do ...@@ -506,6 +506,18 @@ describe Project, models: true do
end end
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 describe '#external_wiki' do
let(:project) { create(:project) } let(:project) { create(:project) }
...@@ -685,23 +697,37 @@ describe Project, models: true do ...@@ -685,23 +697,37 @@ describe Project, models: true do
end end
end end
describe '#pipeline' do describe '#pipeline_for' do
let(:project) { create :project } let(:project) { create(:project) }
let(:pipeline) { create :ci_pipeline, project: project, ref: 'master' } let!(:pipeline) { create_pipeline }
subject { project.pipeline(pipeline.sha, 'master') }
shared_examples 'giving the correct pipeline' do
it { is_expected.to eq(pipeline) } it { is_expected.to eq(pipeline) }
context 'return latest' do context 'return latest' do
let(:pipeline2) { create :ci_pipeline, project: project, ref: 'master' } let!(:pipeline2) { create_pipeline }
before do it { is_expected.to eq(pipeline2) }
pipeline end
pipeline2
end end
it { is_expected.to eq(pipeline2) } 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
def create_pipeline
create(:ci_pipeline,
project: project,
ref: 'master',
sha: project.commit('master').sha)
end end
end end
......
...@@ -15,7 +15,9 @@ describe API::API, api: true do ...@@ -15,7 +15,9 @@ describe API::API, api: true do
describe 'GET /projects/:id/builds ' do describe 'GET /projects/:id/builds ' do
let(:query) { '' } 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 context 'authorized user' do
it 'returns project builds' do it 'returns project builds' do
...@@ -122,7 +124,9 @@ describe API::API, api: true do ...@@ -122,7 +124,9 @@ describe API::API, api: true do
end end
describe 'GET /projects/:id/builds/:build_id' do 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 context 'authorized user' do
it 'returns specific build data' do it 'returns specific build data' do
...@@ -141,7 +145,9 @@ describe API::API, api: true do ...@@ -141,7 +145,9 @@ describe API::API, api: true do
end end
describe 'GET /projects/:id/builds/:build_id/artifacts' do 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 context 'build with artifacts' do
let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) } let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) }
...@@ -292,7 +298,9 @@ describe API::API, api: true do ...@@ -292,7 +298,9 @@ describe API::API, api: true do
end end
describe 'POST /projects/:id/builds/:build_id/cancel' do 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 'authorized user' do
context 'user with :update_build persmission' do context 'user with :update_build persmission' do
...@@ -323,7 +331,9 @@ describe API::API, api: true do ...@@ -323,7 +331,9 @@ describe API::API, api: true do
describe 'POST /projects/:id/builds/:build_id/retry' do describe 'POST /projects/:id/builds/:build_id/retry' do
let(:build) { create(:ci_build, :canceled, pipeline: pipeline) } 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 'authorized user' do
context 'user with :update_build permission' do context 'user with :update_build permission' do
......
...@@ -95,7 +95,7 @@ describe API::API, api: true do ...@@ -95,7 +95,7 @@ describe API::API, api: true do
end end
it "returns status for CI" do 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') pipeline.update(status: 'success')
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
...@@ -105,7 +105,7 @@ describe API::API, api: true do ...@@ -105,7 +105,7 @@ describe API::API, api: true do
end end
it "returns status for CI when pipeline is created" do 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) 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 ...@@ -5,7 +5,7 @@ module Ci
let(:service) { ImageForBuildService.new } let(:service) { ImageForBuildService.new }
let(:project) { FactoryGirl.create(:empty_project) } let(:project) { FactoryGirl.create(:empty_project) }
let(:commit_sha) { '01234567890123456789' } 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) } let(:build) { FactoryGirl.create(:ci_build, pipeline: pipeline) }
describe '#execute' do 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
...@@ -27,8 +27,8 @@ RSpec.configure do |config| ...@@ -27,8 +27,8 @@ RSpec.configure do |config|
config.display_try_failure_messages = true config.display_try_failure_messages = true
config.include Devise::TestHelpers, type: :controller config.include Devise::TestHelpers, type: :controller
config.include Warden::Test::Helpers, type: :request
config.include LoginHelpers, type: :feature config.include LoginHelpers, type: :feature
config.include LoginHelpers, type: :request
config.include StubConfiguration config.include StubConfiguration
config.include EmailHelpers config.include EmailHelpers
config.include TestEnv config.include TestEnv
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
# Requires a context containing: # Requires a context containing:
# subject { Issue or MergeRequest } # subject { Issue or MergeRequest }
shared_examples 'a Taskable' do shared_examples 'a Taskable' do
describe 'with multiple tasks' do
before do before do
subject.description = <<-EOT.strip_heredoc subject.description = <<-EOT.strip_heredoc
* [ ] Task 1 * [ ] Task 1
...@@ -14,9 +15,8 @@ shared_examples 'a Taskable' do ...@@ -14,9 +15,8 @@ shared_examples 'a Taskable' do
end end
it 'returns the correct task status' do it 'returns the correct task status' do
expect(subject.task_status).to match('5 tasks') expect(subject.task_status).to match('2 of')
expect(subject.task_status).to match('2 completed') expect(subject.task_status).to match('5 tasks completed')
expect(subject.task_status).to match('3 remaining')
end end
describe '#tasks?' do describe '#tasks?' do
...@@ -29,4 +29,31 @@ shared_examples 'a Taskable' do ...@@ -29,4 +29,31 @@ shared_examples 'a Taskable' do
expect(subject.tasks?).to eq false expect(subject.tasks?).to eq false
end end
end end
end
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 'with a complete task' do
before do
subject.description = <<-EOT.strip_heredoc
* [x] Task 1
EOT
end
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 end
...@@ -6,7 +6,7 @@ module TestEnv ...@@ -6,7 +6,7 @@ module TestEnv
# When developing the seed repository, comment out the branch you will modify. # When developing the seed repository, comment out the branch you will modify.
BRANCH_SHA = { BRANCH_SHA = {
'empty-branch' => '7efb185', 'empty-branch' => '7efb185',
'ends-with.json' => '98b0d8b3', 'ends-with.json' => '98b0d8b',
'flatten-dir' => 'e56497b', 'flatten-dir' => 'e56497b',
'feature' => '0b4bc9a', 'feature' => '0b4bc9a',
'feature_conflict' => 'bb5206f', 'feature_conflict' => 'bb5206f',
...@@ -37,9 +37,10 @@ module TestEnv ...@@ -37,9 +37,10 @@ module TestEnv
# need to keep all the branches in sync. # need to keep all the branches in sync.
# We currently only need a subset of the branches # We currently only need a subset of the branches
FORKED_BRANCH_SHA = { FORKED_BRANCH_SHA = {
'add-submodule-version-bump' => '3f547c08', 'add-submodule-version-bump' => '3f547c0',
'master' => '5937ac0', 'master' => '5937ac0',
'remove-submodule' => '2a33e0c0' 'remove-submodule' => '2a33e0c',
'conflict-resolvable-fork' => '404fa3f'
} }
# Test environment # Test environment
...@@ -117,22 +118,7 @@ module TestEnv ...@@ -117,22 +118,7 @@ module TestEnv
system(*%W(#{Gitlab.config.git.bin_path} clone -q #{clone_url} #{repo_path})) system(*%W(#{Gitlab.config.git.bin_path} clone -q #{clone_url} #{repo_path}))
end end
Dir.chdir(repo_path) do set_repo_refs(repo_path, branch_sha)
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
# We must copy bare repositories because we will push to them. # 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})) system(git_env, *%W(#{Gitlab.config.git.bin_path} clone -q --bare #{repo_path} #{repo_path_bare}))
...@@ -144,6 +130,7 @@ module TestEnv ...@@ -144,6 +130,7 @@ module TestEnv
FileUtils.mkdir_p(target_repo_path) FileUtils.mkdir_p(target_repo_path)
FileUtils.cp_r("#{base_repo_path}/.", target_repo_path) FileUtils.cp_r("#{base_repo_path}/.", target_repo_path)
FileUtils.chmod_R 0755, target_repo_path FileUtils.chmod_R 0755, target_repo_path
set_repo_refs(target_repo_path, BRANCH_SHA)
end end
def repos_path def repos_path
...@@ -160,6 +147,7 @@ module TestEnv ...@@ -160,6 +147,7 @@ module TestEnv
FileUtils.mkdir_p(target_repo_path) FileUtils.mkdir_p(target_repo_path)
FileUtils.cp_r("#{base_repo_path}/.", target_repo_path) FileUtils.cp_r("#{base_repo_path}/.", target_repo_path)
FileUtils.chmod_R 0755, target_repo_path FileUtils.chmod_R 0755, target_repo_path
set_repo_refs(target_repo_path, FORKED_BRANCH_SHA)
end end
# When no cached assets exist, manually hit the root path to create them # When no cached assets exist, manually hit the root path to create them
...@@ -209,4 +197,23 @@ module TestEnv ...@@ -209,4 +197,23 @@ module TestEnv
def git_env def git_env
{ 'GIT_TEMPLATE_DIR' => '' } { 'GIT_TEMPLATE_DIR' => '' }
end 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 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