summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/issue_templates/Bug.md44
-rw-r--r--.gitlab/issue_templates/Feature Proposal.md7
-rw-r--r--CHANGELOG23
-rw-r--r--CONTRIBUTING.md59
-rw-r--r--app/assets/javascripts/gl_dropdown.js22
-rw-r--r--app/assets/javascripts/logo.js42
-rw-r--r--app/assets/javascripts/merge_conflict_resolver.js.es64
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss6
-rw-r--r--app/assets/stylesheets/framework/forms.scss1
-rw-r--r--app/assets/stylesheets/framework/header.scss32
-rw-r--r--app/assets/stylesheets/framework/logo.scss118
-rw-r--r--app/assets/stylesheets/framework/mixins.scss46
-rw-r--r--app/assets/stylesheets/framework/nav.scss12
-rw-r--r--app/assets/stylesheets/pages/events.scss5
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss8
-rw-r--r--app/assets/stylesheets/pages/projects.scss27
-rw-r--r--app/assets/stylesheets/pages/status.scss9
-rw-r--r--app/controllers/projects/artifacts_controller.rb44
-rw-r--r--app/helpers/ci_status_helper.rb11
-rw-r--r--app/helpers/gitlab_routing_helper.rb16
-rw-r--r--app/helpers/merge_requests_helper.rb2
-rw-r--r--app/models/ability.rb2
-rw-r--r--app/models/ci/pipeline.rb4
-rw-r--r--app/models/concerns/note_on_diff.rb4
-rw-r--r--app/models/concerns/taskable.rb4
-rw-r--r--app/models/diff_note.rb4
-rw-r--r--app/models/merge_request.rb37
-rw-r--r--app/models/project.rb17
-rw-r--r--app/services/merge_requests/resolve_service.rb21
-rw-r--r--app/services/merge_requests/update_service.rb4
-rw-r--r--app/views/ci/lints/show.html.haml3
-rw-r--r--app/views/projects/branches/_branch.html.haml2
-rw-r--r--app/views/projects/buttons/_download.html.haml46
-rw-r--r--app/views/projects/ci/builds/_build_pipeline.html.haml4
-rw-r--r--app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml4
-rw-r--r--app/views/projects/issues/_related_branches.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml4
-rw-r--r--app/views/projects/repositories/_download_archive.html.haml37
-rw-r--r--app/views/projects/show.html.haml2
-rw-r--r--app/views/projects/tags/_download.html.haml14
-rw-r--r--app/views/projects/tags/_tag.html.haml3
-rw-r--r--app/views/projects/tags/show.html.haml3
-rw-r--r--app/views/projects/tree/show.html.haml3
-rw-r--r--app/views/shared/_logo.svg16
-rw-r--r--app/views/shared/icons/_icon_status_created.svg1
-rw-r--r--app/views/shared/issuable/_form.html.haml4
-rw-r--r--config/routes.rb8
-rw-r--r--doc/api/commits.md14
-rw-r--r--doc/api/projects.md22
-rw-r--r--doc/ci/yaml/README.md21
-rw-r--r--doc/development/doc_styleguide.md24
-rw-r--r--doc/development/newlines_styleguide.md2
-rw-r--r--doc/integration/bitbucket.md215
-rw-r--r--doc/integration/img/bitbucket_oauth_keys.pngbin0 -> 12073 bytes
-rw-r--r--doc/integration/img/bitbucket_oauth_settings_page.pngbin0 -> 82818 bytes
-rw-r--r--doc/integration/omniauth.md4
-rw-r--r--doc/user/project/slash_commands.md2
-rw-r--r--lib/api/commit_statuses.rb2
-rw-r--r--lib/backup/repository.rb2
-rw-r--r--lib/gitlab/badge/coverage/report.rb4
-rw-r--r--lib/gitlab/ci/config/node/hidden.rb (renamed from lib/gitlab/ci/config/node/hidden_job.rb)3
-rw-r--r--lib/gitlab/ci/config/node/jobs.rb2
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb29
-rw-r--r--spec/features/merge_requests/diff_notes_spec.rb31
-rw-r--r--spec/features/projects/branches/download_buttons_spec.rb40
-rw-r--r--spec/features/projects/builds_spec.rb (renamed from spec/features/builds_spec.rb)0
-rw-r--r--spec/features/projects/files/download_buttons_spec.rb41
-rw-r--r--spec/features/projects/main/download_buttons_spec.rb40
-rw-r--r--spec/features/projects/tags/download_buttons_spec.rb41
-rw-r--r--spec/features/task_lists_spec.rb266
-rw-r--r--spec/lib/gitlab/ci/config/node/hidden_spec.rb (renamed from spec/lib/gitlab/ci/config/node/hidden_job_spec.rb)17
-rw-r--r--spec/lib/gitlab/ci/config/node/jobs_spec.rb2
-rw-r--r--spec/models/ability_spec.rb13
-rw-r--r--spec/models/build_spec.rb8
-rw-r--r--spec/models/ci/pipeline_spec.rb30
-rw-r--r--spec/models/merge_request_spec.rb80
-rw-r--r--spec/models/project_spec.rb50
-rw-r--r--spec/requests/api/builds_spec.rb20
-rw-r--r--spec/requests/api/commits_spec.rb4
-rw-r--r--spec/requests/projects/artifacts_controller_spec.rb117
-rw-r--r--spec/services/ci/image_for_build_service_spec.rb2
-rw-r--r--spec/services/merge_requests/resolve_service_spec.rb87
-rw-r--r--spec/spec_helper.rb6
-rw-r--r--spec/support/taskable_shared_examples.rb63
-rw-r--r--spec/support/test_env.rb47
-rw-r--r--spec/views/projects/merge_requests/edit.html.haml_spec.rb53
-rw-r--r--spec/views/projects/merge_requests/show.html.haml_spec.rb44
88 files changed, 1641 insertions, 599 deletions
diff --git a/.gitlab/issue_templates/Bug.md b/.gitlab/issue_templates/Bug.md
new file mode 100644
index 00000000000..b676916fdf4
--- /dev/null
+++ b/.gitlab/issue_templates/Bug.md
@@ -0,0 +1,44 @@
+### 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)
diff --git a/.gitlab/issue_templates/Feature Proposal.md b/.gitlab/issue_templates/Feature Proposal.md
new file mode 100644
index 00000000000..ea895ee6275
--- /dev/null
+++ b/.gitlab/issue_templates/Feature Proposal.md
@@ -0,0 +1,7 @@
+### Description
+
+(Include problem, use cases, benefits, and/or goals)
+
+### Proposal
+
+### Links / references
diff --git a/CHANGELOG b/CHANGELOG
index f4c850fe00c..18efe057299 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,22 +1,31 @@
Please view this file on the master branch, on stable branches it's out of date.
v 8.12.0 (unreleased)
+ - Make push events have equal vertical spacing.
- Add two-factor recovery endpoint to internal API !5510
+ - Remove vendor prefixes for linear-gradient CSS (ClemMakesApps)
- Add font color contrast to external label in admin area (ClemMakesApps)
+ - Change logo animation to CSS (ClemMakesApps)
- Change merge_error column from string to text type
- Reduce contributions calendar data payload (ClemMakesApps)
- Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel)
- Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling)
+ - Shorten task status phrase (ClemMakesApps)
- Add hover color to emoji icon (ClemMakesApps)
+ - Fix branches page dropdown sort alignment (ClemMakesApps)
+ - Add white background for no readme container (ClemMakesApps)
- Optimistic locking for Issues and Merge Requests (title and description overriding prevention)
- Add `wiki_page_events` to project hook APIs (Ben Boeckel)
- Remove Gitorious import
+ - Fix inconsistent background color for filter input field (ClemMakesApps)
- Add Sentry logging to API calls
- Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling)
+ - Remove unused mixins (ClemMakesApps)
- Fix groups sort dropdown alignment (ClemMakesApps)
- Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps)
- Fix markdown help references (ClemMakesApps)
- Added tests for diff notes
+ - Add a button to download latest successful artifacts for branches and tags !5142
- Add delimiter to project stars and forks count (ClemMakesApps)
- Fix badge count alignment (ClemMakesApps)
- Fix branch title trailing space on hover (ClemMakesApps)
@@ -26,6 +35,7 @@ v 8.12.0 (unreleased)
- Update merge_requests.md with a simpler way to check out a merge request. !5944
- Fix button missing type (ClemMakesApps)
- Move to project dropdown with infinite scroll for better performance
+ - Fix leaking of submit buttons outside the width of a main container !18731 (originally by @pavelloz)
- Load branches asynchronously in Cherry Pick and Revert dialogs.
- Add merge request versions !5467
- Change using size to use count and caching it for number of group members. !5935
@@ -34,11 +44,24 @@ v 8.12.0 (unreleased)
- Capitalize mentioned issue timeline notes (ClemMakesApps)
- Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger)
- Adds response mime type to transaction metric action when it's not HTML
+ - Fix hover leading space bug in pipeline graph !5980
+ - User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496
+
+v 8.11.4 (unreleased)
+ - Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner)
+
+v 8.11.4 (unreleased)
+ - Fix resolving conflicts on forks
+
+v 8.11.4 (unreleased)
+ - Fix diff commenting on merge requests created prior to 8.10
v 8.11.3 (unreleased)
+ - Do not enforce using hash with hidden key in CI configuration. !6079
- Allow system info page to handle case where info is unavailable
- Label list shows all issues (opened or closed) with that label
- Don't show resolve conflicts link before MR status is updated
+ - Fix "Wiki" link not appearing in navigation for projects with external wiki
- Fix IE11 fork button bug !598
- Don't prevent viewing the MR when git refs for conflicts can't be found on disk
- Fix external issue tracker "Issues" link leading to 404s
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index d8093a61b4c..c457af2ae6f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -129,7 +129,7 @@ request that potentially fixes it.
### Feature proposals
-To create a feature proposal for CE and CI, open an issue on the
+To create a feature proposal for CE, open an issue on the
[issue tracker of CE][ce-tracker].
For feature proposals for EE, open an issue on the
@@ -144,16 +144,7 @@ code snippet right after your description in a new line: `~"feature proposal"`.
Please keep feature proposals as small and simple as possible, complex ones
might be edited to make them small and simple.
-You are encouraged to use the template below for feature proposals.
-
-```
-## Description
-Include problem, use cases, benefits, and/or goals
-
-## Proposal
-
-## Links / references
-```
+Please submit Feature Proposals using the ['Feature Proposal' issue template](.gitlab/issue_templates/Feature Proposal.md) provided on the issue tracker.
For changes in the interface, it can be helpful to create a mockup first.
If you want to create something yourself, consider opening an issue first to
@@ -166,55 +157,11 @@ submitting your own, there's a good chance somebody else had the same issue or
feature proposal. Show your support with an award emoji and/or join the
discussion.
-Please submit bugs using the following template in the issue description area.
+Please submit bugs using the ['Bug' issue template](.gitlab/issue_templates/Bug.md) provided on the issue tracker.
The text in the parenthesis is there to help you with what to include. Omit it
when submitting the actual issue. You can copy-paste it and then edit as you
see fit.
-```
-## Summary
-
-(Summarize your issue in one sentence - what goes wrong, what did you expect to happen)
-
-## Steps to reproduce
-
-(How one can reproduce the issue - this is very important)
-
-## Expected behavior
-
-(What you should see instead)
-
-## Relevant logs and/or screenshots
-
-(Paste any relevant logs - please use code blocks (```) to format console output,
-logs, and code as it's very hard to read otherwise.)
-
-## Output of checks
-
-### Results of GitLab Application Check
-
-(For installations with omnibus-gitlab package run and paste the output of:
-sudo gitlab-rake gitlab:check SANITIZE=true)
-
-(For installations from source run and paste the output of:
-sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true)
-
-(we will only investigate if the tests are passing)
-
-### Results of GitLab Environment Info
-
-(For installations with omnibus-gitlab package run and paste the output of:
-sudo gitlab-rake gitlab:env:info)
-
-(For installations from source run and paste the output of:
-sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production)
-
-## Possible fixes
-
-(If you can, link to the line of code that might be responsible for the problem)
-
-```
-
### Issue weight
Issue weight allows us to get an idea of the amount of work required to solve
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index 0179b320a3b..5a2a8523d9f 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -117,7 +117,7 @@
}
});
} else {
- return elements.show();
+ return elements.show().removeClass('option-hidden');
}
}
};
@@ -190,9 +190,9 @@
currentIndex = -1;
- NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link, .option-hidden';
+ NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link';
- SELECTABLE_CLASSES = ".dropdown-content li:not(" + NON_SELECTABLE_CLASSES + ")";
+ SELECTABLE_CLASSES = ".dropdown-content li:not(" + NON_SELECTABLE_CLASSES + ", .option-hidden)";
CURSOR_SELECT_SCROLL_PADDING = 5
@@ -565,10 +565,6 @@
} else {
field.remove();
}
- if (this.options.toggleLabel) {
- this.updateLabel(selectedObject, el, this);
- }
- return selectedObject;
} else if (el.hasClass(INDETERMINATE_CLASS)) {
el.addClass(ACTIVE_CLASS);
el.removeClass(INDETERMINATE_CLASS);
@@ -578,7 +574,6 @@
if (!field.length && fieldName) {
this.addInput(fieldName, value, selectedObject);
}
- return selectedObject;
} else {
if (!this.options.multiSelect || el.hasClass('dropdown-clear-active')) {
this.dropdown.find("." + ACTIVE_CLASS).removeClass(ACTIVE_CLASS);
@@ -590,9 +585,6 @@
field.remove();
}
el.addClass(ACTIVE_CLASS);
- if (this.options.toggleLabel) {
- this.updateLabel(selectedObject, el, this);
- }
if (value != null) {
if (!field.length && fieldName) {
this.addInput(fieldName, value, selectedObject);
@@ -600,8 +592,14 @@
field.val(value).trigger('change');
}
}
- return selectedObject;
}
+
+ // Update label right after input has been added
+ if (this.options.toggleLabel) {
+ this.updateLabel(selectedObject, el, this);
+ }
+
+ return selectedObject;
};
GitLabDropdown.prototype.addInput = function(fieldName, value, selectedObject) {
diff --git a/app/assets/javascripts/logo.js b/app/assets/javascripts/logo.js
index 218f24fe908..e5d4fd44c96 100644
--- a/app/assets/javascripts/logo.js
+++ b/app/assets/javascripts/logo.js
@@ -1,50 +1,12 @@
(function() {
- var clearHighlights, currentTimer, defaultClass, delay, firstPiece, pieceIndex, pieces, start, stop, work;
-
Turbolinks.enableProgressBar();
- defaultClass = 'tanuki-shape';
-
- pieces = ['path#tanuki-right-cheek', 'path#tanuki-right-eye, path#tanuki-right-ear', 'path#tanuki-nose', 'path#tanuki-left-eye, path#tanuki-left-ear', 'path#tanuki-left-cheek'];
-
- pieceIndex = 0;
-
- firstPiece = pieces[0];
-
- currentTimer = null;
-
- delay = 150;
-
- clearHighlights = function() {
- return $("." + defaultClass + ".highlight").attr('class', defaultClass);
- };
-
start = function() {
- clearHighlights();
- pieceIndex = 0;
- if (pieces[0] !== firstPiece) {
- pieces.reverse();
- }
- if (currentTimer) {
- clearInterval(currentTimer);
- }
- return currentTimer = setInterval(work, delay);
+ $('.tanuki-logo').addClass('animate');
};
stop = function() {
- clearInterval(currentTimer);
- return clearHighlights();
- };
-
- work = function() {
- clearHighlights();
- $(pieces[pieceIndex]).attr('class', defaultClass + " highlight");
- if (pieceIndex === pieces.length - 1) {
- pieceIndex = 0;
- return pieces.reverse();
- } else {
- return pieceIndex++;
- }
+ $('.tanuki-logo').removeClass('animate');
};
$(document).on('page:fetch', start);
diff --git a/app/assets/javascripts/merge_conflict_resolver.js.es6 b/app/assets/javascripts/merge_conflict_resolver.js.es6
index 77bffbcb403..b56fd5aa658 100644
--- a/app/assets/javascripts/merge_conflict_resolver.js.es6
+++ b/app/assets/javascripts/merge_conflict_resolver.js.es6
@@ -75,10 +75,8 @@ class MergeConflictResolver {
window.location.href = data.redirect_to;
})
.error(() => {
- new Flash('Something went wrong!');
- })
- .always(() => {
this.vue.isSubmitting = false;
+ new Flash('Something went wrong!');
});
}
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index a306b8f3f29..d5cca1b10fb 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -24,6 +24,7 @@
@import "framework/issue_box.scss";
@import "framework/jquery.scss";
@import "framework/lists.scss";
+@import "framework/logo.scss";
@import "framework/markdown_area.scss";
@import "framework/mobile.scss";
@import "framework/modal.scss";
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index be5c64c56d3..edb2ff01f88 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -195,6 +195,12 @@
.separator + .dropdown-header {
padding-top: 2px;
}
+
+ .unclickable {
+ cursor: not-allowed;
+ padding: 5px 8px;
+ color: $dropdown-header-color;
+ }
}
.dropdown-menu-large {
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 43d55661541..37ff7e22ed1 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -19,7 +19,6 @@ input[type='text'].danger {
}
.form-actions {
- margin: -$gl-padding;
margin-top: 0;
margin-bottom: -$gl-padding;
padding: $gl-padding;
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 0c607071840..afe4a276ae5 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -2,16 +2,6 @@
* Application Header
*
*/
-@mixin tanuki-logo-colors($path-color) {
- fill: $path-color;
- transition: all 0.8s;
-
- &:hover,
- &.highlight {
- fill: lighten($path-color, 25%);
- transition: all 0.1s;
- }
-}
header {
transition: padding $sidebar-transition-duration;
@@ -25,7 +15,7 @@ header {
margin: 8px 0;
text-align: center;
- #tanuki-logo, img {
+ .tanuki-logo, img {
height: 36px;
}
}
@@ -205,26 +195,6 @@ header {
}
}
-#tanuki-logo {
-
- #tanuki-left-ear,
- #tanuki-right-ear,
- #tanuki-nose {
- @include tanuki-logo-colors($tanuki-red);
- }
-
- #tanuki-left-eye,
- #tanuki-right-eye {
- @include tanuki-logo-colors($tanuki-orange);
- }
-
- #tanuki-left-cheek,
- #tanuki-right-cheek {
- @include tanuki-logo-colors($tanuki-yellow);
- }
-
-}
-
@media (max-width: $screen-xs-max) {
header .container-fluid {
font-size: 18px;
diff --git a/app/assets/stylesheets/framework/logo.scss b/app/assets/stylesheets/framework/logo.scss
new file mode 100644
index 00000000000..3ee3fb4cee5
--- /dev/null
+++ b/app/assets/stylesheets/framework/logo.scss
@@ -0,0 +1,118 @@
+@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
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index d2d60ed7196..396a37bab6e 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -9,22 +9,6 @@
border-radius: $radius;
}
-@mixin border-radius-left($radius) {
- @include border-radius($radius 0 0 $radius)
-}
-
-@mixin border-radius-right($radius) {
- @include border-radius(0 0 $radius $radius)
-}
-
-@mixin linear-gradient($from, $to) {
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from($from), to($to));
- background-image: -webkit-linear-gradient($from, $to);
- background-image: -moz-linear-gradient($from, $to);
- background-image: -ms-linear-gradient($from, $to);
- background-image: -o-linear-gradient($from, $to);
-}
-
@mixin transition($transition) {
-webkit-transition: $transition;
-moz-transition: $transition;
@@ -38,14 +22,6 @@
* Mixins with fixed values
*/
-@mixin shade {
- @include box-shadow(0 0 3px #ddd);
-}
-
-@mixin solid-shade {
- @include box-shadow(0 0 0 3px #f1f1f1);
-}
-
@mixin str-truncated($max_width: 82%) {
display: inline-block;
overflow: hidden;
@@ -94,23 +70,6 @@
}
}
-@mixin input-big {
- height: 36px;
- padding: 5px 10px;
- font-size: 16px;
- line-height: 24px;
- color: #7f8fa4;
- background-color: #fff;
- border-color: #e7e9ed;
-}
-
-@mixin btn-big {
- height: 36px;
- padding: 5px 10px;
- font-size: 16px;
- line-height: 24px;
-}
-
@mixin bulleted-list {
> ul {
list-style-type: disc;
@@ -129,3 +88,8 @@
color: rgba(255, 255, 255, 0.3);
background: rgba(255, 255, 255, 0.1);
}
+
+@mixin webkit-prefix($property, $value) {
+ #{'-webkit-' + $property}: $value;
+ #{$property}: $value;
+}
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index cf7cf125504..9efbaf54e90 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -8,10 +8,7 @@
height: 30px;
transition-duration: .3s;
-webkit-transform: translateZ(0);
- background: -webkit-linear-gradient($gradient-direction, rgba($gradient-color, 0.4), $gradient-color 45%);
- background: -o-linear-gradient($gradient-direction, rgba($gradient-color, 0.4), $gradient-color 45%);
- background: -moz-linear-gradient($gradient-direction, rgba($gradient-color, 0.4), $gradient-color 45%);
- background: linear-gradient($gradient-direction, rgba($gradient-color, 0.4), $gradient-color 45%);
+ background: linear-gradient(to $gradient-direction, $gradient-color 45%, rgba($gradient-color, 0.4));
&.scrolling {
visibility: visible;
@@ -161,6 +158,7 @@
> .dropdown {
margin-right: $gl-padding-top;
display: inline-block;
+ vertical-align: top;
&:last-child {
margin-right: 0;
@@ -210,12 +208,6 @@
}
}
- .project-filter-form {
- input {
- background-color: $background-color;
- }
- }
-
@media (max-width: $screen-xs-max) {
padding-bottom: 0;
width: 100%;
diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss
index 5c336bb1c7e..0cd45fb90bf 100644
--- a/app/assets/stylesheets/pages/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -115,11 +115,8 @@
}
&.commits-stat {
- margin-top: 3px;
display: block;
- padding: 3px;
- padding-left: 0;
-
+ padding: 0 3px 0 0;
&:hover {
background: none;
}
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 743fe89a9f4..0dcf61dd2dd 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -319,6 +319,14 @@
a {
color: $layout-link-gray;
+ text-decoration: none;
+
+ &:hover {
+ .ci-status-text {
+ text-decoration: underline;
+ }
+ }
+
}
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index eaf2d3270b3..83500a687bb 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -600,18 +600,25 @@ pre.light-well {
}
}
-.project-show-readme .readme-holder {
- padding: $gl-padding 0;
- border-top: 0;
-
- .edit-project-readme {
- z-index: 2;
- position: relative;
+.project-show-readme {
+ .row-content-block {
+ background-color: inherit;
+ border: none;
}
- .wiki h1 {
- border-bottom: none;
- padding: 0;
+ .readme-holder {
+ padding: $gl-padding 0;
+ border-top: 0;
+
+ .edit-project-readme {
+ z-index: 2;
+ position: relative;
+ }
+
+ .wiki h1 {
+ border-bottom: none;
+ padding: 0;
+ }
}
}
diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss
index 587f2d9f3c1..0ee7ceecae5 100644
--- a/app/assets/stylesheets/pages/status.scss
+++ b/app/assets/stylesheets/pages/status.scss
@@ -43,6 +43,15 @@
border-color: $blue-normal;
}
+ &.ci-created {
+ color: $table-text-gray;
+ border-color: $table-text-gray;
+
+ svg {
+ fill: $table-text-gray;
+ }
+ }
+
svg {
height: 13px;
width: 13px;
diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb
index 7241949393b..59222637961 100644
--- a/app/controllers/projects/artifacts_controller.rb
+++ b/app/controllers/projects/artifacts_controller.rb
@@ -1,22 +1,25 @@
class Projects::ArtifactsController < Projects::ApplicationController
+ include ExtractsPath
+
layout 'project'
before_action :authorize_read_build!
before_action :authorize_update_build!, only: [:keep]
+ before_action :extract_ref_name_and_path
before_action :validate_artifacts!
def download
- unless artifacts_file.file_storage?
- return redirect_to artifacts_file.url
+ if artifacts_file.file_storage?
+ send_file artifacts_file.path, disposition: 'attachment'
+ else
+ redirect_to artifacts_file.url
end
-
- send_file artifacts_file.path, disposition: 'attachment'
end
def browse
directory = params[:path] ? "#{params[:path]}/" : ''
@entry = build.artifacts_metadata_entry(directory)
- return render_404 unless @entry.exists?
+ render_404 unless @entry.exists?
end
def file
@@ -34,14 +37,41 @@ class Projects::ArtifactsController < Projects::ApplicationController
redirect_to namespace_project_build_path(project.namespace, project, build)
end
+ def latest_succeeded
+ target_path = artifacts_action_path(@path, project, build)
+
+ if target_path
+ redirect_to(target_path)
+ else
+ render_404
+ end
+ end
+
private
+ def extract_ref_name_and_path
+ return unless params[:ref_name_and_path]
+
+ @ref_name, @path = extract_ref(params[:ref_name_and_path])
+ end
+
def validate_artifacts!
- render_404 unless build.artifacts?
+ render_404 unless build && build.artifacts?
end
def build
- @build ||= project.builds.find_by!(id: params[:build_id])
+ @build ||= build_from_id || build_from_ref
+ end
+
+ def build_from_id
+ project.builds.find_by(id: params[:build_id]) if params[:build_id]
+ end
+
+ def build_from_ref
+ return unless @ref_name
+
+ builds = project.latest_successful_builds_for(@ref_name)
+ builds.find_by(name: params[:job])
end
def artifacts_file
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index bb285a17baf..639deb7c521 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -25,6 +25,11 @@ module CiStatusHelper
end
end
+ def ci_status_for_statuseable(subject)
+ status = subject.try(:status) || 'not found'
+ status.humanize
+ end
+
def ci_icon_for_status(status)
icon_name =
case status
@@ -41,7 +46,7 @@ module CiStatusHelper
when 'play'
'icon_play'
when 'created'
- 'icon_status_pending'
+ 'icon_status_created'
else
'icon_status_cancel'
end
@@ -66,10 +71,10 @@ module CiStatusHelper
Ci::Runner.shared.blank?
end
- def render_status_with_link(type, status, path = nil, tooltip_placement: 'auto left', cssclass: '')
+ def render_status_with_link(type, status, path = nil, tooltip_placement: 'auto left', cssclass: '', container: 'body')
klass = "ci-status-link ci-status-icon-#{status.dasherize} #{cssclass}"
title = "#{type.titleize}: #{ci_label_for_status(status)}"
- data = { toggle: 'tooltip', placement: tooltip_placement }
+ data = { toggle: 'tooltip', placement: tooltip_placement, container: container }
if path
link_to ci_icon_for_status(status), path,
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index 5386ddadc62..a322a90cc4e 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -149,4 +149,20 @@ module GitlabRoutingHelper
def resend_invite_group_member_path(group_member, *args)
resend_invite_group_group_member_path(group_member.source, group_member)
end
+
+ # Artifacts
+
+ def artifacts_action_path(path, project, build)
+ action, path_params = path.split('/', 2)
+ args = [project.namespace, project, build, path_params]
+
+ case action
+ when 'download'
+ download_namespace_project_build_artifacts_path(*args)
+ when 'browse'
+ browse_namespace_project_build_artifacts_path(*args)
+ when 'file'
+ file_namespace_project_build_artifacts_path(*args)
+ end
+ end
end
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index db6e731c744..a9e175c3f5c 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -98,6 +98,6 @@ module MergeRequestsHelper
end
def merge_request_button_visibility(merge_request, closed)
- return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?)
+ return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?) || merge_request.closed_without_fork?
end
end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index a49dd703926..c1df4a865f6 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -355,7 +355,7 @@ class Ability
rules += named_abilities('project_snippet')
end
- unless project.wiki_enabled
+ unless project.has_wiki?
rules += named_abilities('wiki')
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 03812cd195f..bd1737c7587 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -65,8 +65,8 @@ module Ci
end
# ref can't be HEAD or SHA, can only be branch/tag name
- scope :latest_successful_for, ->(ref = default_branch) do
- where(ref: ref).success.order(id: :desc).limit(1)
+ def self.latest_successful_for(ref)
+ where(ref: ref).order(id: :desc).success.first
end
def self.truncate_sha(sha)
diff --git a/app/models/concerns/note_on_diff.rb b/app/models/concerns/note_on_diff.rb
index a881fb83b7f..b8dd27a7afe 100644
--- a/app/models/concerns/note_on_diff.rb
+++ b/app/models/concerns/note_on_diff.rb
@@ -28,4 +28,8 @@ module NoteOnDiff
def can_be_award_emoji?
false
end
+
+ def to_discussion
+ Discussion.new([self])
+ end
end
diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb
index df2a9e3e84b..a3ac577cf3e 100644
--- a/app/models/concerns/taskable.rb
+++ b/app/models/concerns/taskable.rb
@@ -52,11 +52,11 @@ module Taskable
end
# Return a string that describes the current state of this Taskable's task
- # list items, e.g. "20 tasks (12 completed, 8 remaining)"
+ # list items, e.g. "12 of 20 tasks completed"
def task_status
return '' if description.blank?
sum = tasks.summary
- "#{sum.item_count} tasks (#{sum.complete_count} completed, #{sum.incomplete_count} remaining)"
+ "#{sum.complete_count} of #{sum.item_count} #{'task'.pluralize(sum.item_count)} completed"
end
end
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index c8320ff87fa..4442cefc7e9 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -107,10 +107,6 @@ class DiffNote < Note
self.noteable.find_diff_discussion(self.discussion_id)
end
- def to_discussion
- Discussion.new([self])
- end
-
private
def supported?
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 1d05e4a85d1..62163e74000 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -91,13 +91,13 @@ class MergeRequest < ActiveRecord::Base
end
end
- validates :source_project, presence: true, unless: [:allow_broken, :importing?]
+ validates :source_project, presence: true, unless: [:allow_broken, :importing?, :closed_without_fork?]
validates :source_branch, presence: true
validates :target_project, presence: true
validates :target_branch, presence: true
validates :merge_user, presence: true, if: :merge_when_build_succeeds?
- validate :validate_branches, unless: [:allow_broken, :importing?]
- validate :validate_fork
+ validate :validate_branches, unless: [:allow_broken, :importing?, :closed_without_fork?]
+ validate :validate_fork, unless: :closed_without_fork?
scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) }
scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
@@ -305,19 +305,22 @@ class MergeRequest < ActiveRecord::Base
def validate_fork
return true unless target_project && source_project
+ return true if target_project == source_project
+ return true unless forked_source_project_missing?
- if target_project == source_project
- true
- else
- # If source and target projects are different
- # we should check if source project is actually a fork of target project
- if source_project.forked_from?(target_project)
- true
- else
- errors.add :validate_fork,
- 'Source project is not a fork of target project'
- end
- end
+ errors.add :validate_fork,
+ 'Source project is not a fork of the target project'
+ end
+
+ def closed_without_fork?
+ closed? && forked_source_project_missing?
+ end
+
+ def forked_source_project_missing?
+ return false unless for_fork?
+ return true unless source_project
+
+ !source_project.forked_from?(target_project)
end
def ensure_merge_request_diff
@@ -726,7 +729,9 @@ class MergeRequest < ActiveRecord::Base
end
def pipeline
- @pipeline ||= source_project.pipeline(diff_head_sha, source_branch) if diff_head_sha && source_project
+ return unless diff_head_sha && source_project
+
+ @pipeline ||= source_project.pipeline_for(source_branch, diff_head_sha)
end
def all_pipelines
diff --git a/app/models/project.rb b/app/models/project.rb
index 0e4fb94f8eb..c34064f96ce 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -436,7 +436,7 @@ class Project < ActiveRecord::Base
# ref can't be HEAD, can only be branch/tag name or SHA
def latest_successful_builds_for(ref = default_branch)
- latest_pipeline = pipelines.latest_successful_for(ref).first
+ latest_pipeline = pipelines.latest_successful_for(ref)
if latest_pipeline
latest_pipeline.builds.latest.with_artifacts
@@ -680,6 +680,10 @@ class Project < ActiveRecord::Base
update_column(:has_external_issue_tracker, services.external_issue_trackers.any?)
end
+ def has_wiki?
+ wiki_enabled? || has_external_wiki?
+ end
+
def external_wiki
if has_external_wiki.nil?
cache_has_external_wiki # Populate
@@ -1096,12 +1100,17 @@ class Project < ActiveRecord::Base
!namespace.share_with_group_lock
end
- def pipeline(sha, ref)
+ def pipeline_for(ref, sha = nil)
+ sha ||= commit(ref).try(:sha)
+
+ return unless sha
+
pipelines.order(id: :desc).find_by(sha: sha, ref: ref)
end
- def ensure_pipeline(sha, ref, current_user = nil)
- pipeline(sha, ref) || pipelines.create(sha: sha, ref: ref, user: current_user)
+ def ensure_pipeline(ref, sha, current_user = nil)
+ pipeline_for(ref, sha) ||
+ pipelines.create(sha: sha, ref: ref, user: current_user)
end
def enable_ci
diff --git a/app/services/merge_requests/resolve_service.rb b/app/services/merge_requests/resolve_service.rb
index adc71b0c2bc..19caa038c44 100644
--- a/app/services/merge_requests/resolve_service.rb
+++ b/app/services/merge_requests/resolve_service.rb
@@ -1,11 +1,14 @@
module MergeRequests
class ResolveService < MergeRequests::BaseService
- attr_accessor :conflicts, :rugged, :merge_index
+ attr_accessor :conflicts, :rugged, :merge_index, :merge_request
def execute(merge_request)
@conflicts = merge_request.conflicts
@rugged = project.repository.rugged
@merge_index = conflicts.merge_index
+ @merge_request = merge_request
+
+ fetch_their_commit!
conflicts.files.each do |file|
write_resolved_file_to_index(file, params[:sections])
@@ -27,5 +30,21 @@ module MergeRequests
merge_index.add(path: our_path, oid: rugged.write(new_file, :blob), mode: file.our_mode)
merge_index.conflict_remove(our_path)
end
+
+ # If their commit (in the target project) doesn't exist in the source project, it
+ # can't be a parent for the merge commit we're about to create. If that's the case,
+ # fetch the target branch ref into the source project so the commit exists in both.
+ #
+ def fetch_their_commit!
+ return if rugged.include?(conflicts.their_commit.oid)
+
+ random_string = SecureRandom.hex
+
+ project.repository.fetch_ref(
+ merge_request.target_project.repository.path_to_repo,
+ "refs/heads/#{merge_request.target_branch}",
+ "refs/tmp/#{random_string}/head"
+ )
+ end
end
end
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 30c5f24988c..398ec47f0ea 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -11,6 +11,10 @@ module MergeRequests
params.except!(:target_project_id)
params.except!(:source_branch)
+ if merge_request.closed_without_fork?
+ params.except!(:target_branch, :force_remove_source_branch)
+ end
+
merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
update(merge_request)
diff --git a/app/views/ci/lints/show.html.haml b/app/views/ci/lints/show.html.haml
index 0044d779c31..889086c62b1 100644
--- a/app/views/ci/lints/show.html.haml
+++ b/app/views/ci/lints/show.html.haml
@@ -1,3 +1,6 @@
+- page_title "CI Lint"
+- page_description "Validate your GitLab CI configuration file"
+
%h2 Check your .gitlab-ci.yml
%hr
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 6192ccb710b..808e6b95746 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -27,6 +27,8 @@
= link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: 'btn btn-default', method: :post, title: "Compare" do
Compare
+ = render 'projects/buttons/download', project: @project, ref: branch.name
+
- if can_remove_branch?(@project, branch.name)
= link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-remove remove-row has-tooltip', title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do
= icon("trash-o")
diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml
index 58f43ecb5d5..5f5e071eb40 100644
--- a/app/views/projects/buttons/_download.html.haml
+++ b/app/views/projects/buttons/_download.html.haml
@@ -1,4 +1,42 @@
-- unless @project.empty_repo?
- - if can? current_user, :download_code, @project
- = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn has-tooltip', data: {container: "body"}, rel: 'nofollow', title: "Download ZIP" do
- = icon('download')
+- if !project.empty_repo? && can?(current_user, :download_code, project)
+ %span.btn-group{class: 'hidden-xs hidden-sm btn-grouped'}
+ .dropdown.inline
+ %button.btn{ 'data-toggle' => 'dropdown' }
+ = icon('download')
+ %span.caret
+ %span.sr-only
+ Select Archive Format
+ %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
+ %li.dropdown-header Source code
+ %li
+ = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow' do
+ %i.fa.fa-download
+ %span Download zip
+ %li
+ = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do
+ %i.fa.fa-download
+ %span Download tar.gz
+ %li
+ = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do
+ %i.fa.fa-download
+ %span Download tar.bz2
+ %li
+ = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar'), rel: 'nofollow' do
+ %i.fa.fa-download
+ %span Download tar
+
+ - pipeline = project.pipelines.latest_successful_for(ref)
+ - if pipeline
+ - artifacts = pipeline.builds.latest.with_artifacts
+ - if artifacts.any?
+ %li.dropdown-header Artifacts
+ - unless pipeline.latest?
+ - latest_pipeline = project.pipeline_for(ref)
+ %li
+ .unclickable= ci_status_for_statuseable(latest_pipeline)
+ %li.dropdown-header Previous Artifacts
+ - artifacts.each do |job|
+ %li
+ = link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, ref, 'download', job: job.name), rel: 'nofollow' do
+ %i.fa.fa-download
+ %span Download '#{job.name}'
diff --git a/app/views/projects/ci/builds/_build_pipeline.html.haml b/app/views/projects/ci/builds/_build_pipeline.html.haml
index ebdf60ae370..36fb0300aeb 100644
--- a/app/views/projects/ci/builds/_build_pipeline.html.haml
+++ b/app/views/projects/ci/builds/_build_pipeline.html.haml
@@ -5,11 +5,11 @@
- if is_playable
= link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do
= render_status_with_link('build', 'play')
- = subject.name
+ %span.ci-status-text= subject.name
- elsif can?(current_user, :read_build, @project)
= link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do
= render_status_with_link('build', subject.status)
- = subject.name
+ %span.ci-status-text= subject.name
- else
= render_status_with_link('build', subject.status)
= ci_icon_for_status(subject.status)
diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml
index 584c0fa18ae..31d40f6ad03 100644
--- a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml
+++ b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml
@@ -3,7 +3,7 @@
- if subject.target_url
- link_to subject.target_url do
= render_status_with_link('commit status', subject.status)
- = subject.name
+ %span.ci-status-text= subject.name
- else
= render_status_with_link('commit status', subject.status)
- = subject.name
+ %span.ci-status-text= subject.name
diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml
index 6ea9f612d13..a8eeab3e55e 100644
--- a/app/views/projects/issues/_related_branches.html.haml
+++ b/app/views/projects/issues/_related_branches.html.haml
@@ -5,7 +5,7 @@
- @related_branches.each do |branch|
%li
- target = @project.repository.find_branch(branch).target
- - pipeline = @project.pipeline(target.sha, branch) if target
+ - pipeline = @project.pipeline_for(branch, target.sha) if target
- if pipeline
%span.related-branch-ci-status
= render_pipeline_status(pipeline)
diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml
index 098ce19da21..e35291dff7d 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -1,3 +1,7 @@
+- if @merge_request.closed_without_fork?
+ .alert.alert-danger
+ %p The source project of this merge request has been removed.
+
.clearfix.detail-page-header
.issuable-header
.issuable-status-box.status-box{ class: status_box_class(@merge_request) }
diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml
deleted file mode 100644
index 24658319060..00000000000
--- a/app/views/projects/repositories/_download_archive.html.haml
+++ /dev/null
@@ -1,37 +0,0 @@
-- 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
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 340e159c874..9adce776c1c 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -72,7 +72,7 @@
= render "projects/buttons/koding"
.btn-group.project-repo-btn-group
- = render "projects/buttons/download"
+ = render 'projects/buttons/download', project: @project, ref: @ref
= render 'projects/buttons/dropdown'
= render 'shared/notifications/button', notification_setting: @notification_setting
diff --git a/app/views/projects/tags/_download.html.haml b/app/views/projects/tags/_download.html.haml
deleted file mode 100644
index 8a11dbfa9f4..00000000000
--- a/app/views/projects/tags/_download.html.haml
+++ /dev/null
@@ -1,14 +0,0 @@
-%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
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
index 2c11c0e5b21..a156d98bab8 100644
--- a/app/views/projects/tags/_tag.html.haml
+++ b/app/views/projects/tags/_tag.html.haml
@@ -11,8 +11,7 @@
= strip_gpg_signature(tag.message)
.controls
- - if can?(current_user, :download_code, @project)
- = render 'projects/tags/download', ref: tag.name, project: @project
+ = render 'projects/buttons/download', project: @project, ref: tag.name
- if can?(current_user, :push_code, @project)
= link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn has-tooltip', title: "Edit release notes", data: { container: "body" } do
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index 395d7af6cbb..4dd7439b2d0 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -12,8 +12,7 @@
= icon('files-o')
= link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Browse commits' do
= icon('history')
- - if can? current_user, :download_code, @project
- = render 'projects/tags/download', ref: @tag.name, project: @project
+ = render 'projects/buttons/download', project: @project, ref: @tag.name
- if can?(current_user, :admin_project, @project)
.pull-right
= link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index bf5360b4dee..37d341212af 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -10,8 +10,7 @@
%div{ class: container_class }
.tree-controls
= render 'projects/find_file_link'
- - if can? current_user, :download_code, @project
- = render 'projects/repositories/download_archive', ref: @ref, btn_class: 'hidden-xs hidden-sm btn-grouped', split_button: true
+ = render 'projects/buttons/download', project: @project, ref: @ref
#tree-holder.tree-holder.clearfix
.nav-block
diff --git a/app/views/shared/_logo.svg b/app/views/shared/_logo.svg
index b07f1c5603e..9b67422da2c 100644
--- a/app/views/shared/_logo.svg
+++ b/app/views/shared/_logo.svg
@@ -1,9 +1,9 @@
-<svg width="36" height="36" id="tanuki-logo">
- <path id="tanuki-right-ear" class="tanuki-shape" fill="#e24329" d="M2 14l9.38 9v-9l-4-12.28c-.205-.632-1.176-.632-1.38 0z"/>
- <path id="tanuki-left-ear" class="tanuki-shape" fill="#e24329" d="M34 14l-9.38 9v-9l4-12.28c.205-.632 1.176-.632 1.38 0z"/>
- <path id="tanuki-nose" class="tanuki-shape" fill="#e24329" d="M18,34.38 3,14 33,14 Z"/>
- <path id="tanuki-right-eye" class="tanuki-shape" fill="#fc6d26" d="M18,34.38 11.38,14 2,14 6,25Z"/>
- <path id="tanuki-left-eye" class="tanuki-shape" fill="#fc6d26" d="M18,34.38 24.62,14 34,14 30,25Z"/>
- <path id="tanuki-right-cheek" class="tanuki-shape" fill="#fca326" d="M2 14L.1 20.16c-.18.565 0 1.2.5 1.56l17.42 12.66z"/>
- <path id="tanuki-left-cheek" class="tanuki-shape" fill="#fca326" d="M34 14l1.9 6.16c.18.565 0 1.2-.5 1.56L18 34.38z"/>
+<svg width="36" height="36" class="tanuki-logo">
+ <path class="tanuki-shape tanuki-left-ear" fill="#e24329" d="M2 14l9.38 9v-9l-4-12.28c-.205-.632-1.176-.632-1.38 0z"/>
+ <path class="tanuki-shape tanuki-right-ear" fill="#e24329" d="M34 14l-9.38 9v-9l4-12.28c.205-.632 1.176-.632 1.38 0z"/>
+ <path class="tanuki-shape tanuki-nose" fill="#e24329" d="M18,34.38 3,14 33,14 Z"/>
+ <path class="tanuki-shape tanuki-left-eye" fill="#fc6d26" d="M18,34.38 11.38,14 2,14 6,25Z"/>
+ <path class="tanuki-shape tanuki-right-eye" fill="#fc6d26" d="M18,34.38 24.62,14 34,14 30,25Z"/>
+ <path class="tanuki-shape tanuki-left-cheek" fill="#fca326" d="M2 14L.1 20.16c-.18.565 0 1.2.5 1.56l17.42 12.66z"/>
+ <path class="tanuki-shape tanuki-right-cheek" fill="#fca326" d="M34 14l1.9 6.16c.18.565 0 1.2-.5 1.56L18 34.38z"/>
</svg>
diff --git a/app/views/shared/icons/_icon_status_created.svg b/app/views/shared/icons/_icon_status_created.svg
new file mode 100644
index 00000000000..4a08fd65860
--- /dev/null
+++ b/app/views/shared/icons/_icon_status_created.svg
@@ -0,0 +1 @@
+<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>
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 22594b46443..3856a4917b4 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -134,7 +134,7 @@
title: 'Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location.' }
= icon('question-circle')
-- if issuable.is_a?(MergeRequest)
+- if issuable.is_a?(MergeRequest) && !issuable.closed_without_fork?
%hr
- if @merge_request.new_record?
.form-group
@@ -175,7 +175,7 @@
= link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class]), class: 'btn btn-cancel'
- else
.pull-right
- - if current_user.can?(:"destroy_#{issuable.to_ability_name}", @project)
+ - if can?(current_user, :"destroy_#{issuable.to_ability_name}", @project)
= link_to 'Delete', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.class.name.titleize} will be removed! Are you sure?" },
method: :delete, class: 'btn btn-danger btn-grouped'
= link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), class: 'btn btn-grouped btn-cancel'
diff --git a/config/routes.rb b/config/routes.rb
index 24f9b44a53a..262a174437a 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -782,6 +782,14 @@ Rails.application.routes.draw do
resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do
collection do
post :cancel_all
+
+ resources :artifacts, only: [] do
+ collection do
+ get :latest_succeeded,
+ path: '*ref_name_and_path',
+ format: false
+ end
+ end
end
member do
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 5c98c5d7565..55d0de7afd9 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -10,7 +10,7 @@ GET /projects/:id/repository/commits
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
| `ref_name` | string | no | The name of a repository branch or tag or if not given the default branch |
| `since` | string | no | Only commits after or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ |
| `until` | string | no | Only commits before or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ |
@@ -58,7 +58,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
| `sha` | string | yes | The commit hash or name of a repository branch or tag |
```bash
@@ -102,7 +102,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
| `sha` | string | yes | The commit hash or name of a repository branch or tag |
```bash
@@ -138,7 +138,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
| `sha` | string | yes | The commit hash or name of a repository branch or tag |
```bash
@@ -187,7 +187,7 @@ POST /projects/:id/repository/commits/:sha/comments
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project |
+| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
| `sha` | string | yes | The commit SHA or name of a repository branch or tag |
| `note` | string | yes | The text of the comment |
| `path` | string | no | The file path relative to the repository |
@@ -232,7 +232,7 @@ GET /projects/:id/repository/commits/:sha/statuses
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project
+| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
| `sha` | string | yes | The commit SHA
| `ref_name`| string | no | The name of a repository branch or tag or, if not given, the default branch
| `stage` | string | no | Filter by [build stage](../ci/yaml/README.md#stages), e.g., `test`
@@ -306,7 +306,7 @@ POST /projects/:id/statuses/:sha
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a project
+| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user
| `sha` | string | yes | The commit SHA
| `state` | string | yes | The state of the status. Can be one of the following: `pending`, `running`, `success`, `failed`, `canceled`
| `ref` | string | no | The `ref` (branch or tag) to which the status refers
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 0e4806e31c5..22c3d416107 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -489,7 +489,7 @@ PUT /projects/:id
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project
- `name` (optional) - project name
- `path` (optional) - repository name for project
- `description` (optional) - short project description
@@ -519,7 +519,7 @@ POST /projects/fork/:id
Parameters:
-- `id` (required) - The ID of the project to be forked
+- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked
### Star a project
@@ -532,7 +532,7 @@ POST /projects/:id/star
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of the project |
+| `id` | integer | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/star"
@@ -599,7 +599,7 @@ DELETE /projects/:id/star
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of the project |
+| `id` | integer | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
```bash
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/star"
@@ -670,7 +670,7 @@ POST /projects/:id/archive
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of the project |
+| `id` | integer | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/archive"
@@ -757,7 +757,7 @@ POST /projects/:id/unarchive
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of the project |
+| `id` | integer | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/unarchive"
@@ -839,7 +839,7 @@ DELETE /projects/:id
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked
## Uploads
@@ -853,7 +853,7 @@ POST /projects/:id/uploads
Parameters:
-- `id` (required) - The ID of the project
+- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked
- `file` (required) - The file to be uploaded
```json
@@ -882,7 +882,7 @@ POST /projects/:id/share
Parameters:
-- `id` (required) - The ID of a project
+- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked
- `group_id` (required) - The ID of a group
- `group_access` (required) - Level of permissions for sharing
@@ -1114,7 +1114,7 @@ POST /projects/:id/fork/:forked_from_id
Parameters:
-- `id` (required) - The ID of the project
+- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked
- `forked_from_id:` (required) - The ID of the project that was forked from
### Delete an existing forked from relationship
@@ -1125,7 +1125,7 @@ DELETE /projects/:id/fork
Parameter:
-- `id` (required) - The ID of the project
+- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked
## Search for projects by name
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index e7850aa2c9d..58d5306f12a 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -39,7 +39,7 @@ If you want a quick introduction to GitLab CI, follow our
- [before_script and after_script](#before_script-and-after_script)
- [Git Strategy](#git-strategy)
- [Shallow cloning](#shallow-cloning)
-- [Hidden jobs](#hidden-jobs)
+- [Hidden keys](#hidden-keys)
- [Special YAML features](#special-yaml-features)
- [Anchors](#anchors)
- [Validate the .gitlab-ci.yml](#validate-the-gitlab-ci-yml)
@@ -934,24 +934,27 @@ variables:
GIT_DEPTH: "3"
```
-## Hidden jobs
+## Hidden keys
>**Note:**
Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
-Jobs that start with a dot (`.`) will be not processed by GitLab CI. You can
+Keys that start with a dot (`.`) will be not processed by GitLab CI. You can
use this feature to ignore jobs, or use the
-[special YAML features](#special-yaml-features) and transform the hidden jobs
+[special YAML features](#special-yaml-features) and transform the hidden keys
into templates.
-In the following example, `.job_name` will be ignored:
+In the following example, `.key_name` will be ignored:
```yaml
-.job_name:
+.key_name:
script:
- rake spec
```
+Hidden keys can be hashes like normal CI jobs, but you are also allowed to use
+different types of structures to leverage special YAML features.
+
## Special YAML features
It's possible to use special YAML features like anchors (`&`), aliases (`*`)
@@ -967,7 +970,7 @@ Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
YAML also has a handy feature called 'anchors', which let you easily duplicate
content across your document. Anchors can be used to duplicate/inherit
-properties, and is a perfect example to be used with [hidden jobs](#hidden-jobs)
+properties, and is a perfect example to be used with [hidden keys](#hidden-keys)
to provide templates for your jobs.
The following example uses anchors and map merging. It will create two jobs,
@@ -975,7 +978,7 @@ The following example uses anchors and map merging. It will create two jobs,
having their own custom `script` defined:
```yaml
-.job_template: &job_definition # Hidden job that defines an anchor named 'job_definition'
+.job_template: &job_definition # Hidden key that defines an anchor named 'job_definition'
image: ruby:2.1
services:
- postgres
@@ -1081,7 +1084,7 @@ test:mysql:
- ruby
```
-You can see that the hidden jobs are conveniently used as templates.
+You can see that the hidden keys are conveniently used as templates.
## Validate the .gitlab-ci.yml
diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md
index 8bf24f70a9a..37bb59e112c 100644
--- a/doc/development/doc_styleguide.md
+++ b/doc/development/doc_styleguide.md
@@ -222,18 +222,26 @@ For example, if you were to move `doc/workflow/lfs/lfs_administration.md` to
```
1. Find and replace any occurrences of the old location with the new one.
- A quick way to find them is to use `grep`:
+ A quick way to find them is to use `git grep`. First go to the root directory
+ where you cloned the `gitlab-ce` repository and then do:
```
- grep -nR "lfs_administration.md" doc/
+ git grep -n "workflow/lfs/lfs_administration"
+ git grep -n "lfs/lfs_administration"
```
- The above command will search in the `doc/` directory for
- `lfs_administration.md` recursively and will print the file and the line
- where this file is mentioned. Note that we used just the filename
- (`lfs_administration.md`) and not the whole the relative path
- (`workflow/lfs/lfs_administration.md`).
-
+Things to note:
+
+- Since we also use inline documentation, except for the documentation itself,
+ the document might also be referenced in the views of GitLab (`app/`) which will
+ render when visiting `/help`, and sometimes in the testing suite (`spec/`).
+- The above `git grep` command will search recursively in the directory you run
+ it in for `workflow/lfs/lfs_administration` and `lfs/lfs_administration`
+ and will print the file and the line where this file is mentioned.
+ You may ask why the two greps. Since we use relative paths to link to
+ documentation, sometimes it might be useful to search a path deeper.
+- The `*.md` extension is not used when a document is linked to GitLab's
+ built-in help page, that's why we omit it in `git grep`.
## Configuration documentation for source and Omnibus installations
diff --git a/doc/development/newlines_styleguide.md b/doc/development/newlines_styleguide.md
index e03adcaadea..32aac2529a4 100644
--- a/doc/development/newlines_styleguide.md
+++ b/doc/development/newlines_styleguide.md
@@ -2,7 +2,7 @@
This style guide recommends best practices for newlines in Ruby code.
-## Rule: separate code with newlines only when it makes sense from logic perspectice
+## Rule: separate code with newlines only to group together related logic
```ruby
# bad
diff --git a/doc/integration/bitbucket.md b/doc/integration/bitbucket.md
index 2eb6266ebe7..556d71b8b76 100644
--- a/doc/integration/bitbucket.md
+++ b/doc/integration/bitbucket.md
@@ -1,111 +1,164 @@
-# 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.
-Bitbucket will generate an application ID and secret key for you to use.
+## Overview
-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.
- - 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.
- - URL: The URL to your GitLab installation. 'https://gitlab.company.com'
-1. Select "Save".
+To enable the Bitbucket OmniAuth provider you must register your application
+with Bitbucket.org. Bitbucket will generate an application ID and secret key for
+you to use.
-1. You should now see a Key and Secret in the list of OAuth customers.
- Keep this page open as you continue configuration.
+1. Sign in to [Bitbucket.org](https://bitbucket.org).
+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
- cd /home/git/gitlab
+ ![Bitbucket OAuth settings page](img/bitbucket_oauth_settings_page.png)
+
+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
- gitlab_rails['omniauth_providers'] = [
- {
- "name" => "bitbucket",
- "app_id" => "YOUR_KEY",
- "app_secret" => "YOUR_APP_SECRET",
- "url" => "https://bitbucket.org/"
- }
- ]
+ gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "bitbucket",
+ "app_id" => "BITBUCKET_APP_KEY",
+ "app_secret" => "BITBUCKET_APP_SECRET",
+ "url" => "https://bitbucket.org/"
+ }
+ ]
```
- For installation from source:
+ For installations from source:
- ```
- - { name: 'bitbucket', app_id: 'YOUR_KEY',
- app_secret: 'YOUR_APP_SECRET' }
+ ```yaml
+ - { name: 'bitbucket',
+ 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. [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```).
-
-1. Restart GitLab for the changes to take effect.
-
-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.
+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
-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
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`.
- Make sure to use an **empty passphrase**.
+ When asked to 'Enter file in which to save the key' enter:
+ `/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
- sudo editor /home/git/.ssh/config
+ ```
+ # For Omnibus packages
+ sudo editor /var/opt/gitlab/.ssh/config
+
+ # For installations from source
+ sudo editor /home/git/.ssh/config
```
- Add a host configuration for `bitbucket.org`.
+1. Add a host configuration for `bitbucket.org`:
```sh
Host bitbucket.org
@@ -113,28 +166,46 @@ If you have that file in place, you're all set and should see the "Import projec
User git
```
-### Step 2: Known hosts
-
-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:
-
-1. Manually connect to 'bitbucket.org' over SSH, while logged in as the `git` account that GitLab will use:
+1. Save the file and exit.
+1. Manually connect to `bitbucket.org` over SSH, while logged in as the `git`
+ user that GitLab will use:
```sh
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
- The authenticity of host 'bitbucket.org (207.223.240.182)' can't be established.
- RSA key fingerprint is 97:8c:1b:f2:6f:14:6b:5c:3b:ec:aa:46:46:74:7c:40.
+ The authenticity of host 'bitbucket.org (104.192.143.1)' can't be established.
+ RSA key fingerprint is SHA256:zzXQOXSRBEiUtuE8AikJYKwbHaxvSc0ojez9YXaGp1A.
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.
-1. Your GitLab server is now able to connect to Bitbucket over SSH.
+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. Restart GitLab to allow it to find the new public key.
+## Acknowledgemts
+
+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
diff --git a/doc/integration/img/bitbucket_oauth_keys.png b/doc/integration/img/bitbucket_oauth_keys.png
new file mode 100644
index 00000000000..3fb2f7524a3
--- /dev/null
+++ b/doc/integration/img/bitbucket_oauth_keys.png
Binary files differ
diff --git a/doc/integration/img/bitbucket_oauth_settings_page.png b/doc/integration/img/bitbucket_oauth_settings_page.png
new file mode 100644
index 00000000000..a3047712d8c
--- /dev/null
+++ b/doc/integration/img/bitbucket_oauth_settings_page.png
Binary files differ
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
index 46b260e7033..8a55fce96fe 100644
--- a/doc/integration/omniauth.md
+++ b/doc/integration/omniauth.md
@@ -102,8 +102,8 @@ To change these settings:
block_auto_created_users: true
```
-Now we can choose one or more of the Supported Providers listed above to continue
-the configuration process.
+Now we can choose one or more of the [Supported Providers](#supported-providers)
+listed above to continue the configuration process.
## Enable OmniAuth for an Existing User
diff --git a/doc/user/project/slash_commands.md b/doc/user/project/slash_commands.md
index 11e1574f772..1792a0c501d 100644
--- a/doc/user/project/slash_commands.md
+++ b/doc/user/project/slash_commands.md
@@ -26,5 +26,5 @@ do.
| `/done` | Mark todo as done |
| `/subscribe` | Subscribe |
| `/unsubscribe` | Unsubscribe |
-| `/due <in 2 days or this Friday or December 31st>` | Set due date |
+| <code>/due &lt;in 2 days &#124; this Friday &#124; December 31st&gt;</code> | Set due date |
| `/remove_due_date` | Remove due date |
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
index 4df6ca8333e..5e3c9563703 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -64,7 +64,7 @@ module API
ref = branches.first
end
- pipeline = @project.ensure_pipeline(commit.sha, ref, current_user)
+ pipeline = @project.ensure_pipeline(ref, commit.sha, current_user)
name = params[:name] || params[:context]
status = GenericCommitStatus.running_or_pending.find_by(pipeline: pipeline, name: name, ref: params[:ref])
diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb
index f117fc3d37d..9fcd9a3f999 100644
--- a/lib/backup/repository.rb
+++ b/lib/backup/repository.rb
@@ -55,7 +55,7 @@ module Backup
bk_repos_path = File.join(path, '..', 'repositories.old.' + Time.now.to_i.to_s)
FileUtils.mv(path, bk_repos_path)
# This is expected from gitlab:check
- FileUtils.mkdir_p(path, mode: 2770)
+ FileUtils.mkdir_p(path, mode: 02770)
end
Project.find_each(batch_size: 1000) do |project|
diff --git a/lib/gitlab/badge/coverage/report.rb b/lib/gitlab/badge/coverage/report.rb
index 95d925dc7f3..9a0482306b7 100644
--- a/lib/gitlab/badge/coverage/report.rb
+++ b/lib/gitlab/badge/coverage/report.rb
@@ -12,9 +12,7 @@ module Gitlab
@ref = ref
@job = job
- @pipeline = @project.pipelines
- .latest_successful_for(@ref)
- .first
+ @pipeline = @project.pipelines.latest_successful_for(@ref)
end
def entity
diff --git a/lib/gitlab/ci/config/node/hidden_job.rb b/lib/gitlab/ci/config/node/hidden.rb
index 073044b66f8..fe4ee8a7fc6 100644
--- a/lib/gitlab/ci/config/node/hidden_job.rb
+++ b/lib/gitlab/ci/config/node/hidden.rb
@@ -5,11 +5,10 @@ module Gitlab
##
# Entry that represents a hidden CI/CD job.
#
- class HiddenJob < Entry
+ class Hidden < Entry
include Validatable
validations do
- validates :config, type: Hash
validates :config, presence: true
end
diff --git a/lib/gitlab/ci/config/node/jobs.rb b/lib/gitlab/ci/config/node/jobs.rb
index 51683c82ceb..a1a26d4fd8f 100644
--- a/lib/gitlab/ci/config/node/jobs.rb
+++ b/lib/gitlab/ci/config/node/jobs.rb
@@ -30,7 +30,7 @@ module Gitlab
def compose!
@config.each do |name, config|
- node = hidden?(name) ? Node::HiddenJob : Node::Job
+ node = hidden?(name) ? Node::Hidden : Node::Job
factory = Node::Factory.new(node)
.value(config || {})
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index c64c2b075c5..a219400d75f 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -170,6 +170,35 @@ describe Projects::MergeRequestsController do
expect(response).to redirect_to([merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request])
expect(merge_request.reload.closed?).to be_truthy
end
+
+ it 'allows editing of a closed merge request' do
+ merge_request.close!
+
+ put :update,
+ namespace_id: project.namespace.path,
+ project_id: project.path,
+ id: merge_request.iid,
+ merge_request: {
+ title: 'New title'
+ }
+
+ expect(response).to redirect_to([merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request])
+ expect(merge_request.reload.title).to eq 'New title'
+ end
+
+ it 'does not allow to update target branch closed merge request' do
+ merge_request.close!
+
+ put :update,
+ namespace_id: project.namespace.path,
+ project_id: project.path,
+ id: merge_request.iid,
+ merge_request: {
+ target_branch: 'new_branch'
+ }
+
+ expect { merge_request.reload.target_branch }.not_to change { merge_request.target_branch }
+ end
end
end
diff --git a/spec/features/merge_requests/diff_notes_spec.rb b/spec/features/merge_requests/diff_notes_spec.rb
index a818679a874..06fad1007e8 100644
--- a/spec/features/merge_requests/diff_notes_spec.rb
+++ b/spec/features/merge_requests/diff_notes_spec.rb
@@ -147,6 +147,37 @@ feature 'Diff notes', js: true, feature: true do
end
end
+ context 'when the MR only supports legacy diff notes' do
+ before do
+ @merge_request.merge_request_diff.update_attributes(start_commit_sha: nil)
+ visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, view: 'inline')
+ end
+
+ context 'with a new line' do
+ it 'should allow commenting' do
+ should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'))
+ end
+ end
+
+ context 'with an old line' do
+ it 'should allow commenting' do
+ should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]'))
+ end
+ end
+
+ context 'with an unchanged line' do
+ it 'should allow commenting' do
+ should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]'))
+ end
+ end
+
+ context 'with a match line' do
+ it 'should not allow commenting' do
+ should_not_allow_commenting(find('.match', match: :first))
+ end
+ end
+ end
+
def should_allow_commenting(line_holder, diff_side = nil)
line = get_line_components(line_holder, diff_side)
line[:content].hover
diff --git a/spec/features/projects/branches/download_buttons_spec.rb b/spec/features/projects/branches/download_buttons_spec.rb
new file mode 100644
index 00000000000..04058300570
--- /dev/null
+++ b/spec/features/projects/branches/download_buttons_spec.rb
@@ -0,0 +1,40 @@
+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
diff --git a/spec/features/builds_spec.rb b/spec/features/projects/builds_spec.rb
index 0cfeb2e57d8..0cfeb2e57d8 100644
--- a/spec/features/builds_spec.rb
+++ b/spec/features/projects/builds_spec.rb
diff --git a/spec/features/projects/files/download_buttons_spec.rb b/spec/features/projects/files/download_buttons_spec.rb
new file mode 100644
index 00000000000..be5cebcd7c9
--- /dev/null
+++ b/spec/features/projects/files/download_buttons_spec.rb
@@ -0,0 +1,41 @@
+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
diff --git a/spec/features/projects/main/download_buttons_spec.rb b/spec/features/projects/main/download_buttons_spec.rb
new file mode 100644
index 00000000000..b26c0ea7a14
--- /dev/null
+++ b/spec/features/projects/main/download_buttons_spec.rb
@@ -0,0 +1,40 @@
+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
diff --git a/spec/features/projects/tags/download_buttons_spec.rb b/spec/features/projects/tags/download_buttons_spec.rb
new file mode 100644
index 00000000000..6e0022c179f
--- /dev/null
+++ b/spec/features/projects/tags/download_buttons_spec.rb
@@ -0,0 +1,41 @@
+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
diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb
index 6ed279ef9be..abb27c90e0a 100644
--- a/spec/features/task_lists_spec.rb
+++ b/spec/features/task_lists_spec.rb
@@ -20,6 +20,22 @@ feature 'Task Lists', feature: true do
MARKDOWN
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
Warden.test_mode!
@@ -34,77 +50,145 @@ feature 'Task Lists', feature: true do
end
describe 'for Issues' do
- let!(:issue) { create(:issue, description: markdown, author: user, project: project) }
+ describe 'multiple tasks' do
+ let!(:issue) { create(:issue, description: markdown, author: user, project: project) }
- it 'renders' do
- visit_issue(project, issue)
+ 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: 6)
- expect(page).to have_selector('ul input[checked]', count: 2)
- end
+ expect(page).to have_selector('ul.task-list', count: 1)
+ expect(page).to have_selector('li.task-list-item', count: 6)
+ expect(page).to have_selector('ul input[checked]', count: 2)
+ end
+
+ it 'contains the required selectors' do
+ visit_issue(project, issue)
+
+ container = '.detail-page-description .description.js-task-list-container'
- it 'contains the required selectors' do
- visit_issue(project, issue)
+ expect(page).to have_selector(container)
+ expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox")
+ expect(page).to have_selector("#{container} .js-task-list-field")
+ expect(page).to have_selector('form.js-issuable-update')
+ expect(page).to have_selector('a.btn-close')
+ end
- container = '.detail-page-description .description.js-task-list-container'
+ it 'is only editable by author' do
+ visit_issue(project, issue)
+ expect(page).to have_selector('.js-task-list-container')
- expect(page).to have_selector(container)
- expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox")
- expect(page).to have_selector("#{container} .js-task-list-field")
- expect(page).to have_selector('form.js-issuable-update')
- expect(page).to have_selector('a.btn-close')
+ logout(:user)
+
+ login_as(user2)
+ visit current_path
+ expect(page).not_to have_selector('.js-task-list-container')
+ end
+
+ it 'provides a summary on Issues#index' do
+ visit namespace_project_issues_path(project.namespace, project)
+ expect(page).to have_content("2 of 6 tasks completed")
+ end
end
- it 'is only editable by author' do
- visit_issue(project, issue)
- expect(page).to have_selector('.js-task-list-container')
+ describe 'single incomplete task' do
+ let!(:issue) { create(:issue, description: singleIncompleteMarkdown, author: user, project: project) }
- logout(:user)
+ it 'renders' do
+ visit_issue(project, issue)
- login_as(user2)
- visit current_path
- expect(page).not_to have_selector('.js-task-list-container')
+ 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
- it 'provides a summary on Issues#index' do
- visit namespace_project_issues_path(project.namespace, project)
- expect(page).to have_content("6 tasks (2 completed, 4 remaining)")
+ 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
describe 'for Notes' do
let!(:issue) { create(:issue, author: user, project: project) }
- let!(:note) do
- create(:note, note: markdown, noteable: issue,
- project: project, author: user)
+ describe 'multiple tasks' do
+ let!(:note) do
+ create(:note, note: markdown, 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: 6)
+ expect(page).to have_selector('.note ul input[checked]', count: 2)
+ end
+
+ it 'contains the required selectors' do
+ visit_issue(project, issue)
+
+ expect(page).to have_selector('.note .js-task-list-container')
+ expect(page).to have_selector('.note .js-task-list-container .task-list .task-list-item .task-list-item-checkbox')
+ expect(page).to have_selector('.note .js-task-list-container .js-task-list-field')
+ end
+
+ it 'is only editable by author' do
+ visit_issue(project, issue)
+ expect(page).to have_selector('.js-task-list-container')
+
+ logout(:user)
+
+ login_as(user2)
+ visit current_path
+ expect(page).not_to have_selector('.js-task-list-container')
+ end
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: 6)
- expect(page).to have_selector('.note ul input[checked]', count: 2)
- end
+ describe 'single incomplete task' do
+ let!(:note) do
+ create(:note, note: singleIncompleteMarkdown, noteable: issue,
+ project: project, author: user)
+ end
- it 'contains the required selectors' do
- visit_issue(project, issue)
+ it 'renders for note body' do
+ visit_issue(project, issue)
- expect(page).to have_selector('.note .js-task-list-container')
- expect(page).to have_selector('.note .js-task-list-container .task-list .task-list-item .task-list-item-checkbox')
- expect(page).to have_selector('.note .js-task-list-container .js-task-list-field')
+ 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
- it 'is only editable by author' do
- visit_issue(project, issue)
- expect(page).to have_selector('.js-task-list-container')
+ describe 'single complete task' do
+ let!(:note) do
+ create(:note, note: singleCompleteMarkdown, noteable: issue,
+ project: project, author: user)
+ end
- logout(:user)
+ it 'renders for note body' do
+ visit_issue(project, issue)
- login_as(user2)
- visit current_path
- expect(page).not_to have_selector('.js-task-list-container')
+ 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
@@ -113,42 +197,78 @@ feature 'Task Lists', feature: true do
visit namespace_project_merge_request_path(project.namespace, project, merge)
end
- let!(:merge) { create(:merge_request, :simple, description: markdown, author: user, source_project: project) }
+ describe 'multiple tasks' do
+ let!(:merge) { create(:merge_request, :simple, description: markdown, author: user, source_project: project) }
- it 'renders for description' do
- visit_merge_request(project, merge)
+ 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: 6)
- expect(page).to have_selector('ul input[checked]', count: 2)
- end
+ expect(page).to have_selector('ul.task-list', count: 1)
+ expect(page).to have_selector('li.task-list-item', count: 6)
+ expect(page).to have_selector('ul input[checked]', count: 2)
+ end
- it 'contains the required selectors' do
- visit_merge_request(project, merge)
+ it 'contains the required selectors' do
+ visit_merge_request(project, merge)
- container = '.detail-page-description .description.js-task-list-container'
+ container = '.detail-page-description .description.js-task-list-container'
- expect(page).to have_selector(container)
- expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox")
- expect(page).to have_selector("#{container} .js-task-list-field")
- expect(page).to have_selector('form.js-issuable-update')
- expect(page).to have_selector('a.btn-close')
- end
+ expect(page).to have_selector(container)
+ expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox")
+ expect(page).to have_selector("#{container} .js-task-list-field")
+ expect(page).to have_selector('form.js-issuable-update')
+ expect(page).to have_selector('a.btn-close')
+ end
- it 'is only editable by author' do
- visit_merge_request(project, merge)
- expect(page).to have_selector('.js-task-list-container')
+ it 'is only editable by author' do
+ visit_merge_request(project, merge)
+ expect(page).to have_selector('.js-task-list-container')
- logout(:user)
+ logout(:user)
- login_as(user2)
- visit current_path
- expect(page).not_to have_selector('.js-task-list-container')
+ login_as(user2)
+ visit current_path
+ expect(page).not_to have_selector('.js-task-list-container')
+ end
+
+ it 'provides a summary on MergeRequests#index' do
+ visit namespace_project_merge_requests_path(project.namespace, project)
+ 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
- it 'provides a summary on MergeRequests#index' do
- visit namespace_project_merge_requests_path(project.namespace, project)
- expect(page).to have_content("6 tasks (2 completed, 4 remaining)")
+ 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
diff --git a/spec/lib/gitlab/ci/config/node/hidden_job_spec.rb b/spec/lib/gitlab/ci/config/node/hidden_spec.rb
index cc44e2cc054..61e2a554419 100644
--- a/spec/lib/gitlab/ci/config/node/hidden_job_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/hidden_spec.rb
@@ -1,15 +1,15 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Node::HiddenJob do
+describe Gitlab::Ci::Config::Node::Hidden do
let(:entry) { described_class.new(config) }
describe 'validations' do
context 'when entry config value is correct' do
- let(:config) { { image: 'ruby:2.2' } }
+ let(:config) { [:some, :array] }
describe '#value' do
it 'returns key value' do
- expect(entry.value).to eq(image: 'ruby:2.2')
+ expect(entry.value).to eq [:some, :array]
end
end
@@ -21,17 +21,6 @@ describe Gitlab::Ci::Config::Node::HiddenJob do
end
context 'when entry value is not correct' do
- context 'incorrect config value type' do
- let(:config) { ['incorrect'] }
-
- describe '#errors' do
- it 'saves errors' do
- expect(entry.errors)
- .to include 'hidden job config should be a hash'
- end
- end
- end
-
context 'when config is empty' do
let(:config) { {} }
diff --git a/spec/lib/gitlab/ci/config/node/jobs_spec.rb b/spec/lib/gitlab/ci/config/node/jobs_spec.rb
index b8d9c70479c..ae2c88aac37 100644
--- a/spec/lib/gitlab/ci/config/node/jobs_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/jobs_spec.rb
@@ -74,7 +74,7 @@ describe Gitlab::Ci::Config::Node::Jobs do
expect(entry.descendants.first(2))
.to all(be_an_instance_of(Gitlab::Ci::Config::Node::Job))
expect(entry.descendants.last)
- .to be_an_instance_of(Gitlab::Ci::Config::Node::HiddenJob)
+ .to be_an_instance_of(Gitlab::Ci::Config::Node::Hidden)
end
end
diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb
index aa3b2bbf471..c50ca38bdd9 100644
--- a/spec/models/ability_spec.rb
+++ b/spec/models/ability_spec.rb
@@ -282,4 +282,17 @@ describe Ability, lib: true do
end
end
end
+
+ describe '.project_disabled_features_rules' do
+ let(:project) { build(:project) }
+
+ subject { described_class.project_disabled_features_rules(project) }
+
+ context 'wiki named abilities' do
+ it 'disables wiki abilities if the project has no wiki' do
+ expect(project).to receive(:has_wiki?).and_return(false)
+ expect(subject).to include(:read_wiki, :create_wiki, :update_wiki, :admin_wiki)
+ end
+ end
+ end
end
diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb
index ee2c3d04984..c45c2635cf4 100644
--- a/spec/models/build_spec.rb
+++ b/spec/models/build_spec.rb
@@ -948,15 +948,17 @@ describe Ci::Build, models: true do
before { build.run! }
it 'returns false' do
- expect(build.retryable?).to be false
+ expect(build).not_to be_retryable
end
end
context 'when build is finished' do
- before { build.success! }
+ before do
+ build.success!
+ end
it 'returns true' do
- expect(build.retryable?).to be true
+ expect(build).to be_retryable
end
end
end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 721b20e0cb2..598df576001 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -195,6 +195,36 @@ describe Ci::Pipeline, models: true do
end
end
+ context 'with non-empty project' do
+ let(:project) { create(:project) }
+
+ let(:pipeline) do
+ create(:ci_pipeline,
+ project: project,
+ ref: project.default_branch,
+ sha: project.commit.sha)
+ end
+
+ describe '#latest?' do
+ context 'with latest sha' do
+ it 'returns true' do
+ expect(pipeline).to be_latest
+ end
+ end
+
+ context 'with not latest sha' do
+ before do
+ pipeline.update(
+ sha: project.commit("#{project.default_branch}~1").sha)
+ end
+
+ it 'returns false' do
+ expect(pipeline).not_to be_latest
+ end
+ end
+ end
+ end
+
describe '#manual_actions' do
subject { pipeline.manual_actions }
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index d67f71bbb9c..5bf3b8e609e 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -477,8 +477,8 @@ describe MergeRequest, models: true do
allow(subject).to receive(:diff_head_sha).and_return('123abc')
- expect(subject.source_project).to receive(:pipeline).
- with('123abc', 'master').
+ expect(subject.source_project).to receive(:pipeline_for).
+ with('master', '123abc').
and_return(pipeline)
expect(subject.pipeline).to eq(pipeline)
@@ -962,4 +962,80 @@ describe MergeRequest, models: true do
expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_truthy
end
end
+
+ describe "#forked_source_project_missing?" do
+ let(:project) { create(:project) }
+ let(:fork_project) { create(:project, forked_from_project: project) }
+ let(:user) { create(:user) }
+ let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) }
+
+ context "when the fork exists" do
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: fork_project,
+ target_project: project)
+ end
+
+ it { expect(merge_request.forked_source_project_missing?).to be_falsey }
+ end
+
+ context "when the source project is the same as the target project" do
+ let(:merge_request) { create(:merge_request, source_project: project) }
+
+ it { expect(merge_request.forked_source_project_missing?).to be_falsey }
+ end
+
+ context "when the fork does not exist" do
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: fork_project,
+ target_project: project)
+ end
+
+ it "returns true" do
+ unlink_project.execute
+ merge_request.reload
+
+ expect(merge_request.forked_source_project_missing?).to be_truthy
+ end
+ end
+ end
+
+ describe "#closed_without_fork?" do
+ let(:project) { create(:project) }
+ let(:fork_project) { create(:project, forked_from_project: project) }
+ let(:user) { create(:user) }
+ let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) }
+
+ context "when the merge request is closed" do
+ let(:closed_merge_request) do
+ create(:closed_merge_request,
+ source_project: fork_project,
+ target_project: project)
+ end
+
+ it "returns false if the fork exist" do
+ expect(closed_merge_request.closed_without_fork?).to be_falsey
+ end
+
+ it "returns true if the fork does not exist" do
+ unlink_project.execute
+ closed_merge_request.reload
+
+ expect(closed_merge_request.closed_without_fork?).to be_truthy
+ end
+ end
+
+ context "when the merge request is open" do
+ let(:open_merge_request) do
+ create(:merge_request,
+ source_project: fork_project,
+ target_project: project)
+ end
+
+ it "returns false" do
+ expect(open_merge_request.closed_without_fork?).to be_falsey
+ end
+ end
+ end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index b2baeeb31bb..dd309ea1b68 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -506,6 +506,18 @@ describe Project, models: true do
end
end
+ describe '#has_wiki?' do
+ let(:no_wiki_project) { build(:project, wiki_enabled: false, has_external_wiki: false) }
+ let(:wiki_enabled_project) { build(:project, wiki_enabled: true) }
+ let(:external_wiki_project) { build(:project, has_external_wiki: true) }
+
+ it 'returns true if project is wiki enabled or has external wiki' do
+ expect(wiki_enabled_project).to have_wiki
+ expect(external_wiki_project).to have_wiki
+ expect(no_wiki_project).not_to have_wiki
+ end
+ end
+
describe '#external_wiki' do
let(:project) { create(:project) }
@@ -685,23 +697,37 @@ describe Project, models: true do
end
end
- describe '#pipeline' do
- let(:project) { create :project }
- let(:pipeline) { create :ci_pipeline, project: project, ref: 'master' }
-
- subject { project.pipeline(pipeline.sha, 'master') }
+ describe '#pipeline_for' do
+ let(:project) { create(:project) }
+ let!(:pipeline) { create_pipeline }
- it { is_expected.to eq(pipeline) }
+ shared_examples 'giving the correct pipeline' do
+ it { is_expected.to eq(pipeline) }
- context 'return latest' do
- let(:pipeline2) { create :ci_pipeline, project: project, ref: 'master' }
+ context 'return latest' do
+ let!(:pipeline2) { create_pipeline }
- before do
- pipeline
- pipeline2
+ it { is_expected.to eq(pipeline2) }
end
+ end
+
+ context 'with explicit sha' do
+ subject { project.pipeline_for('master', pipeline.sha) }
+
+ it_behaves_like 'giving the correct pipeline'
+ end
+
+ context 'with implicit sha' do
+ subject { project.pipeline_for('master') }
+
+ it_behaves_like 'giving the correct pipeline'
+ end
- it { is_expected.to eq(pipeline2) }
+ def create_pipeline
+ create(:ci_pipeline,
+ project: project,
+ ref: 'master',
+ sha: project.commit('master').sha)
end
end
diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb
index 9a17a705b1e..ee0b61e2ca4 100644
--- a/spec/requests/api/builds_spec.rb
+++ b/spec/requests/api/builds_spec.rb
@@ -15,7 +15,9 @@ describe API::API, api: true do
describe 'GET /projects/:id/builds ' do
let(:query) { '' }
- before { get api("/projects/#{project.id}/builds?#{query}", api_user) }
+ before do
+ get api("/projects/#{project.id}/builds?#{query}", api_user)
+ end
context 'authorized user' do
it 'returns project builds' do
@@ -122,7 +124,9 @@ describe API::API, api: true do
end
describe 'GET /projects/:id/builds/:build_id' do
- before { get api("/projects/#{project.id}/builds/#{build.id}", api_user) }
+ before do
+ get api("/projects/#{project.id}/builds/#{build.id}", api_user)
+ end
context 'authorized user' do
it 'returns specific build data' do
@@ -141,7 +145,9 @@ describe API::API, api: true do
end
describe 'GET /projects/:id/builds/:build_id/artifacts' do
- before { get api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user) }
+ before do
+ get api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user)
+ end
context 'build with artifacts' do
let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) }
@@ -292,7 +298,9 @@ describe API::API, api: true do
end
describe 'POST /projects/:id/builds/:build_id/cancel' do
- before { post api("/projects/#{project.id}/builds/#{build.id}/cancel", api_user) }
+ before do
+ post api("/projects/#{project.id}/builds/#{build.id}/cancel", api_user)
+ end
context 'authorized user' do
context 'user with :update_build persmission' do
@@ -323,7 +331,9 @@ describe API::API, api: true do
describe 'POST /projects/:id/builds/:build_id/retry' do
let(:build) { create(:ci_build, :canceled, pipeline: pipeline) }
- before { post api("/projects/#{project.id}/builds/#{build.id}/retry", api_user) }
+ before do
+ post api("/projects/#{project.id}/builds/#{build.id}/retry", api_user)
+ end
context 'authorized user' do
context 'user with :update_build permission' do
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 7ca75d77673..5b3dc60aba2 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -95,7 +95,7 @@ describe API::API, api: true do
end
it "returns status for CI" do
- pipeline = project.ensure_pipeline(project.repository.commit.sha, 'master')
+ pipeline = project.ensure_pipeline('master', project.repository.commit.sha)
pipeline.update(status: 'success')
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
@@ -105,7 +105,7 @@ describe API::API, api: true do
end
it "returns status for CI when pipeline is created" do
- project.ensure_pipeline(project.repository.commit.sha, 'master')
+ project.ensure_pipeline('master', project.repository.commit.sha)
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
diff --git a/spec/requests/projects/artifacts_controller_spec.rb b/spec/requests/projects/artifacts_controller_spec.rb
new file mode 100644
index 00000000000..e02f0eacc93
--- /dev/null
+++ b/spec/requests/projects/artifacts_controller_spec.rb
@@ -0,0 +1,117 @@
+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
diff --git a/spec/services/ci/image_for_build_service_spec.rb b/spec/services/ci/image_for_build_service_spec.rb
index c931c3e4829..b3e0a7b9b58 100644
--- a/spec/services/ci/image_for_build_service_spec.rb
+++ b/spec/services/ci/image_for_build_service_spec.rb
@@ -5,7 +5,7 @@ module Ci
let(:service) { ImageForBuildService.new }
let(:project) { FactoryGirl.create(:empty_project) }
let(:commit_sha) { '01234567890123456789' }
- let(:pipeline) { project.ensure_pipeline(commit_sha, 'master') }
+ let(:pipeline) { project.ensure_pipeline('master', commit_sha) }
let(:build) { FactoryGirl.create(:ci_build, pipeline: pipeline) }
describe '#execute' do
diff --git a/spec/services/merge_requests/resolve_service_spec.rb b/spec/services/merge_requests/resolve_service_spec.rb
new file mode 100644
index 00000000000..d71932458fa
--- /dev/null
+++ b/spec/services/merge_requests/resolve_service_spec.rb
@@ -0,0 +1,87 @@
+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
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index c144cd85487..02b2b3ca101 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -26,9 +26,9 @@ RSpec.configure do |config|
config.verbose_retry = true
config.display_try_failure_messages = true
- config.include Devise::TestHelpers, type: :controller
- config.include LoginHelpers, type: :feature
- config.include LoginHelpers, type: :request
+ config.include Devise::TestHelpers, type: :controller
+ config.include Warden::Test::Helpers, type: :request
+ config.include LoginHelpers, type: :feature
config.include StubConfiguration
config.include EmailHelpers
config.include TestEnv
diff --git a/spec/support/taskable_shared_examples.rb b/spec/support/taskable_shared_examples.rb
index 927c72c7409..201614e45a4 100644
--- a/spec/support/taskable_shared_examples.rb
+++ b/spec/support/taskable_shared_examples.rb
@@ -3,30 +3,57 @@
# Requires a context containing:
# subject { Issue or MergeRequest }
shared_examples 'a Taskable' do
- before do
- subject.description = <<-EOT.strip_heredoc
- * [ ] Task 1
- * [x] Task 2
- * [x] Task 3
- * [ ] Task 4
- * [ ] Task 5
- EOT
+ describe 'with multiple tasks' do
+ before do
+ subject.description = <<-EOT.strip_heredoc
+ * [ ] Task 1
+ * [x] Task 2
+ * [x] Task 3
+ * [ ] Task 4
+ * [ ] Task 5
+ EOT
+ end
+
+ it 'returns the correct task status' do
+ expect(subject.task_status).to match('2 of')
+ expect(subject.task_status).to match('5 tasks completed')
+ end
+
+ describe '#tasks?' do
+ it 'returns true when object has tasks' do
+ expect(subject.tasks?).to eq true
+ end
+
+ it 'returns false when object has no tasks' do
+ subject.description = 'Now I have no tasks'
+ expect(subject.tasks?).to eq false
+ end
+ end
end
- it 'returns the correct task status' do
- expect(subject.task_status).to match('5 tasks')
- expect(subject.task_status).to match('2 completed')
- expect(subject.task_status).to match('3 remaining')
+ describe 'with an incomplete task' do
+ before do
+ subject.description = <<-EOT.strip_heredoc
+ * [ ] Task 1
+ EOT
+ end
+
+ it 'returns the correct task status' do
+ expect(subject.task_status).to match('0 of')
+ expect(subject.task_status).to match('1 task completed')
+ end
end
- describe '#tasks?' do
- it 'returns true when object has tasks' do
- expect(subject.tasks?).to eq true
+ describe 'with a complete task' do
+ before do
+ subject.description = <<-EOT.strip_heredoc
+ * [x] Task 1
+ EOT
end
- it 'returns false when object has no tasks' do
- subject.description = 'Now I have no tasks'
- expect(subject.tasks?).to eq false
+ it 'returns the correct task status' do
+ expect(subject.task_status).to match('1 of')
+ expect(subject.task_status).to match('1 task completed')
end
end
end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index c7a45fc4ff9..0097dbf8fad 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -6,7 +6,7 @@ module TestEnv
# When developing the seed repository, comment out the branch you will modify.
BRANCH_SHA = {
'empty-branch' => '7efb185',
- 'ends-with.json' => '98b0d8b3',
+ 'ends-with.json' => '98b0d8b',
'flatten-dir' => 'e56497b',
'feature' => '0b4bc9a',
'feature_conflict' => 'bb5206f',
@@ -37,9 +37,10 @@ module TestEnv
# need to keep all the branches in sync.
# We currently only need a subset of the branches
FORKED_BRANCH_SHA = {
- 'add-submodule-version-bump' => '3f547c08',
- 'master' => '5937ac0',
- 'remove-submodule' => '2a33e0c0'
+ 'add-submodule-version-bump' => '3f547c0',
+ 'master' => '5937ac0',
+ 'remove-submodule' => '2a33e0c',
+ 'conflict-resolvable-fork' => '404fa3f'
}
# Test environment
@@ -117,22 +118,7 @@ module TestEnv
system(*%W(#{Gitlab.config.git.bin_path} clone -q #{clone_url} #{repo_path}))
end
- Dir.chdir(repo_path) do
- branch_sha.each do |branch, sha|
- # Try to reset without fetching to avoid using the network.
- reset = %W(#{Gitlab.config.git.bin_path} update-ref refs/heads/#{branch} #{sha})
- unless system(*reset)
- if system(*%W(#{Gitlab.config.git.bin_path} fetch origin))
- unless system(*reset)
- raise 'The fetched test seed '\
- 'does not contain the required revision.'
- end
- else
- raise 'Could not fetch test seed repository.'
- end
- end
- end
- end
+ set_repo_refs(repo_path, branch_sha)
# We must copy bare repositories because we will push to them.
system(git_env, *%W(#{Gitlab.config.git.bin_path} clone -q --bare #{repo_path} #{repo_path_bare}))
@@ -144,6 +130,7 @@ module TestEnv
FileUtils.mkdir_p(target_repo_path)
FileUtils.cp_r("#{base_repo_path}/.", target_repo_path)
FileUtils.chmod_R 0755, target_repo_path
+ set_repo_refs(target_repo_path, BRANCH_SHA)
end
def repos_path
@@ -160,6 +147,7 @@ module TestEnv
FileUtils.mkdir_p(target_repo_path)
FileUtils.cp_r("#{base_repo_path}/.", target_repo_path)
FileUtils.chmod_R 0755, target_repo_path
+ set_repo_refs(target_repo_path, FORKED_BRANCH_SHA)
end
# When no cached assets exist, manually hit the root path to create them
@@ -209,4 +197,23 @@ module TestEnv
def git_env
{ 'GIT_TEMPLATE_DIR' => '' }
end
+
+ def set_repo_refs(repo_path, branch_sha)
+ Dir.chdir(repo_path) do
+ branch_sha.each do |branch, sha|
+ # Try to reset without fetching to avoid using the network.
+ reset = %W(#{Gitlab.config.git.bin_path} update-ref refs/heads/#{branch} #{sha})
+ unless system(*reset)
+ if system(*%W(#{Gitlab.config.git.bin_path} fetch origin))
+ unless system(*reset)
+ raise 'The fetched test seed '\
+ 'does not contain the required revision.'
+ end
+ else
+ raise 'Could not fetch test seed repository.'
+ end
+ end
+ end
+ end
+ end
end
diff --git a/spec/views/projects/merge_requests/edit.html.haml_spec.rb b/spec/views/projects/merge_requests/edit.html.haml_spec.rb
new file mode 100644
index 00000000000..31bbb150698
--- /dev/null
+++ b/spec/views/projects/merge_requests/edit.html.haml_spec.rb
@@ -0,0 +1,53 @@
+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
diff --git a/spec/views/projects/merge_requests/show.html.haml_spec.rb b/spec/views/projects/merge_requests/show.html.haml_spec.rb
new file mode 100644
index 00000000000..fe0780e72df
--- /dev/null
+++ b/spec/views/projects/merge_requests/show.html.haml_spec.rb
@@ -0,0 +1,44 @@
+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