summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.rubocop.yml77
-rw-r--r--CHANGELOG17
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock6
-rw-r--r--app/assets/stylesheets/framework/blocks.scss11
-rw-r--r--app/assets/stylesheets/framework/nav.scss3
-rw-r--r--app/assets/stylesheets/framework/typography.scss2
-rw-r--r--app/assets/stylesheets/framework/variables.scss2
-rw-r--r--app/assets/stylesheets/pages/builds.scss4
-rw-r--r--app/assets/stylesheets/pages/groups.scss13
-rw-r--r--app/assets/stylesheets/pages/profile.scss41
-rw-r--r--app/assets/stylesheets/pages/snippets.scss7
-rw-r--r--app/controllers/concerns/toggle_award_emoji.rb8
-rw-r--r--app/controllers/projects/snippets_controller.rb5
-rw-r--r--app/controllers/snippets_controller.rb3
-rw-r--r--app/helpers/award_emoji_helper.rb9
-rw-r--r--app/helpers/gitlab_routing_helper.rb12
-rw-r--r--app/models/concerns/awardable.rb6
-rw-r--r--app/models/concerns/issuable.rb4
-rw-r--r--app/models/merge_request.rb30
-rw-r--r--app/models/merge_request_diff.rb8
-rw-r--r--app/models/note.rb4
-rw-r--r--app/models/project.rb16
-rw-r--r--app/models/project_feature.rb6
-rw-r--r--app/models/project_team.rb72
-rw-r--r--app/models/repository.rb2
-rw-r--r--app/models/snippet.rb1
-rw-r--r--app/views/admin/application_settings/_form.html.haml54
-rw-r--r--app/views/award_emoji/_awards_block.html.haml2
-rw-r--r--app/views/groups/show.html.haml2
-rw-r--r--app/views/projects/builds/_table.html.haml2
-rw-r--r--app/views/projects/merge_requests/widget/_heading.html.haml25
-rw-r--r--app/views/projects/snippets/_actions.html.haml2
-rw-r--r--app/views/projects/snippets/show.html.haml23
-rw-r--r--app/views/snippets/show.html.haml2
-rw-r--r--app/views/users/show.html.haml94
-rw-r--r--config/application.rb4
-rw-r--r--config/routes.rb18
-rw-r--r--doc/README.md4
-rw-r--r--doc/administration/issue_closing_pattern.md49
-rw-r--r--doc/api/award_emoji.md15
-rw-r--r--doc/customization/issue_closing.md41
-rw-r--r--doc/gitlab-basics/create-issue.md4
-rw-r--r--doc/intro/README.md2
-rw-r--r--doc/user/project/issues/automatic_issue_closing.md55
-rw-r--r--doc/user/project/repository/web_editor.md2
-rw-r--r--doc/workflow/README.md1
-rw-r--r--lib/api/award_emoji.rb29
-rw-r--r--lib/gitlab/git.rb2
-rw-r--r--lib/gitlab/import_export/import_export.yml3
-rw-r--r--spec/controllers/snippets_controller_spec.rb33
-rw-r--r--spec/factories/group_members.rb6
-rw-r--r--spec/features/projects/import_export/export_file_spec.rb2
-rw-r--r--spec/lib/gitlab/git_spec.rb45
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml14
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml22
-rw-r--r--spec/models/merge_request_spec.rb112
-rw-r--r--spec/models/project_spec.rb41
-rw-r--r--spec/models/project_team_spec.rb62
-rw-r--r--spec/models/snippet_spec.rb2
-rw-r--r--spec/requests/api/award_emoji_spec.rb54
-rw-r--r--spec/views/projects/merge_requests/_heading.html.haml_spec.rb2
63 files changed, 876 insertions, 327 deletions
diff --git a/.rubocop.yml b/.rubocop.yml
index b054675d677..5bd31ccf329 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -767,33 +767,26 @@ Rails/ScopeArgs:
RSpec/AnyInstance:
Enabled: false
-# Check for expectations where `be(...)` can replace `eql(...)`.
-RSpec/BeEql:
- Enabled: false
-
-# Check that the first argument to the top level describe is a constant.
+# Check that the first argument to the top level describe is the tested class or
+# module.
RSpec/DescribeClass:
Enabled: false
-# Checks that tests use `described_class`.
-RSpec/DescribedClass:
- Enabled: false
-
-# Checks that the second argument to `describe` specifies a method.
+# Use `described_class` for tested class / module.
RSpec/DescribeMethod:
Enabled: false
-# Checks if an example group does not include any tests.
-RSpec/EmptyExampleGroup:
+# Checks that the second argument to top level describe is the tested method
+# name.
+RSpec/DescribedClass:
Enabled: false
- CustomIncludeMethods: []
-# Checks for long examples.
+# Checks for long example.
RSpec/ExampleLength:
Enabled: false
Max: 5
-# Checks that example descriptions do not start with "should".
+# Do not use should when describing your tests.
RSpec/ExampleWording:
Enabled: false
CustomTransform:
@@ -802,10 +795,6 @@ RSpec/ExampleWording:
not: does not
IgnoredWords: []
-# Checks for `expect(...)` calls containing literal values.
-RSpec/ExpectActual:
- Enabled: false
-
# Checks the file and folder naming of the spec file.
RSpec/FilePath:
Enabled: false
@@ -817,65 +806,19 @@ RSpec/FilePath:
RSpec/Focus:
Enabled: true
-# Checks the arguments passed to `before`, `around`, and `after`.
-RSpec/HookArgument:
- Enabled: false
- EnforcedStyle: implicit
-
-# Check that a consistent implict expectation style is used.
-# TODO (rspeicher): Available in rubocop-rspec 1.8.0
-# RSpec/ImplicitExpect:
-# Enabled: true
-# EnforcedStyle: is_expected
-
# Checks for the usage of instance variables.
RSpec/InstanceVariable:
Enabled: false
-# Checks for `subject` definitions that come after `let` definitions.
-RSpec/LeadingSubject:
- Enabled: false
-
-# Checks unreferenced `let!` calls being used for test setup.
-RSpec/LetSetup:
- Enabled: false
-
-# Check that chains of messages are not being stubbed.
-RSpec/MessageChain:
- Enabled: false
-
-# Checks for consistent message expectation style.
-RSpec/MessageExpectation:
- Enabled: false
- EnforcedStyle: allow
-
-# Checks for multiple top level describes.
+# Checks for multiple top-level describes.
RSpec/MultipleDescribes:
Enabled: false
-# Checks if examples contain too many `expect` calls.
-RSpec/MultipleExpectations:
- Enabled: false
- Max: 1
-
-# Checks for explicitly referenced test subjects.
-RSpec/NamedSubject:
- Enabled: false
-
-# Checks for nested example groups.
-RSpec/NestedGroups:
- Enabled: false
- MaxNesting: 2
-
-# Checks for consistent method usage for negating expectations.
+# Enforces the usage of the same method on all negative message expectations.
RSpec/NotToNot:
EnforcedStyle: not_to
Enabled: true
-# Checks for stubbed test subjects.
-RSpec/SubjectStub:
- Enabled: false
-
# Prefer using verifying doubles over normal doubles.
RSpec/VerifiedDoubles:
Enabled: false
diff --git a/CHANGELOG b/CHANGELOG
index 9370bdd17d2..6e9567e7e20 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -24,11 +24,13 @@ v 8.12.0 (unreleased)
- Cycle analytics (first iteration) !5986
- Remove vendor prefixes for linear-gradient CSS (ClemMakesApps)
- Move pushes_since_gc from the database to Redis
+ - Limit number of shown environments on Merge Request: show only environments for target_branch, source_branch and tags
- Add font color contrast to external label in admin area (ClemMakesApps)
- Change logo animation to CSS (ClemMakesApps)
- Instructions for enabling Git packfile bitmaps !6104
- Use Search::GlobalService.new in the `GET /projects/search/:query` endpoint
- Fix long comments in diffs messing with table width
+ - Add spec covering 'Gitlab::Git::committer_hash' !6433 (dandunckelman)
- Fix pagination on user snippets page
- Run CI builds with the permissions of users !5735
- Fix sorting of issues in API
@@ -40,14 +42,17 @@ v 8.12.0 (unreleased)
- Escape search term before passing it to Regexp.new !6241 (winniehell)
- Fix pinned sidebar behavior in smaller viewports !6169
- Fix file permissions change when updating a file on the Gitlab UI !5979
+ - Added horizontal padding on build page sidebar on code coverage block. !6196 (Vitaly Baev)
- Change merge_error column from string to text type
- Reduce contributions calendar data payload (ClemMakesApps)
+ - Show all pipelines for merge requests even from discarded commits !6414
- Replace contributions calendar timezone payload with dates (ClemMakesApps)
- Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel)
- Enable pipeline events by default !6278
- Move parsing of sidekiq ps into helper !6245 (pascalbetz)
- Added go to issue boards keyboard shortcut
- Expose `sha` and `merge_commit_sha` in merge request API (Ben Boeckel)
+ - Emoji can be awarded on Snippets !4456
- Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling)
- Fix blame table layout width
- Spec testing if issue authors can read issues on private projects
@@ -165,7 +170,6 @@ v 8.12.0 (unreleased)
- Add notification_settings API calls !5632 (mahcsig)
- Remove duplication between project builds and admin builds view !5680 (Katarzyna Kobierska Ula Budziszewska)
- Fix URLs with anchors in wiki !6300 (houqp)
- - Use a ConnectionPool for Rails.cache on Sidekiq servers
- Deleting source project with existing fork link will close all related merge requests !6177 (Katarzyna Kobierska Ula Budziszeska)
- Return 204 instead of 404 for /ci/api/v1/builds/register.json if no builds are scheduled for a runner !6225
- Fix Gitlab::Popen.popen thread-safety issue
@@ -176,6 +180,11 @@ v 8.12.0 (unreleased)
- Fix non-master branch readme display in tree view
- Add UX improvements for merge request version diffs
+v 8.11.7
+ - Avoid conflict with admin labels when importing GitHub labels. !6158
+ - Restores `fieldName` to allow only string values in `gl_dropdown.js`. !6234
+ - Allow the Rails cookie to be used for API authentication.
+
v 8.11.6
- Fix unnecessary horizontal scroll area in pipeline visualizations. !6005
- Make merge conflict file size limit 200 KB, to match the docs. !6052
@@ -390,6 +399,9 @@ v 8.11.0
- Update gitlab_git gem to 10.4.7
- Simplify SQL queries of marking a todo as done
+v 8.10.10
+ - Allow the Rails cookie to be used for API authentication.
+
v 8.10.9
- Exclude some pending or inactivated rows in Member scopes
@@ -623,6 +635,9 @@ v 8.10.0
- Show tooltip on GitLab export link in new project page
- Fix import_data wrongly saved as a result of an invalid import_url !5206
+v 8.9.10
+ - Allow the Rails cookie to be used for API authentication.
+
v 8.9.9
- Exclude some pending or inactivated rows in Member scopes
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 1545d966571..40c341bdcdb 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-3.5.0
+3.6.0
diff --git a/Gemfile b/Gemfile
index cd8cf0a8b22..2ac0f8af755 100644
--- a/Gemfile
+++ b/Gemfile
@@ -298,7 +298,7 @@ group :development, :test do
gem 'spring-commands-teaspoon', '~> 0.0.2'
gem 'rubocop', '~> 0.42.0', require: false
- gem 'rubocop-rspec', '~> 1.7.0', require: false
+ gem 'rubocop-rspec', '~> 1.5.0', require: false
gem 'scss_lint', '~> 0.47.0', require: false
gem 'haml_lint', '~> 0.18.2', require: false
gem 'simplecov', '0.12.0', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index c9b4b30c1f2..a5a2e5785ac 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -626,8 +626,8 @@ GEM
rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1)
- rubocop-rspec (1.7.0)
- rubocop (>= 0.42.0)
+ rubocop-rspec (1.5.0)
+ rubocop (>= 0.40.0)
ruby-fogbugz (0.2.1)
crack (~> 0.4)
ruby-prof (0.15.9)
@@ -943,7 +943,7 @@ DEPENDENCIES
rspec-rails (~> 3.5.0)
rspec-retry (~> 0.4.5)
rubocop (~> 0.42.0)
- rubocop-rspec (~> 1.7.0)
+ rubocop-rspec (~> 1.5.0)
ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 0.15.9)
sanitize (~> 2.0)
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index f5223207f3a..2432ddb72f4 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -129,8 +129,6 @@
position: relative;
.avatar-holder {
- margin-bottom: 16px;
-
.avatar, .identicon {
margin: 0 auto;
float: none;
@@ -143,13 +141,7 @@
.cover-title {
color: $gl-header-color;
- margin: 0;
- font-size: 24px;
- font-weight: normal;
- margin-bottom: 10px;
- color: #4c4e54;
font-size: 23px;
- line-height: 1.1;
h1 {
color: $gl-gray-dark;
@@ -213,6 +205,9 @@
}
}
}
+ &.user-cover-block {
+ padding: 24px 0 0;
+ }
.group-info {
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 553768b2e68..ea43f4afc37 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -99,8 +99,7 @@
.top-area {
@include clearfix;
-
- border-bottom: 1px solid #eee;
+ border-bottom: 1px solid $btn-gray-hover;
.nav-text {
padding-top: 16px;
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 2582cde5a71..9f2d53d5206 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -204,7 +204,7 @@ body {
}
h1, h2, h3, h4, h5, h6 {
- color: $gl-header-color;
+ color: $gl-title-color;
font-weight: 600;
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 9f563a4de35..2f5e3ec8e44 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -102,7 +102,7 @@ $gl-grayish-blue: #7f8fa4;
$gl-gray: $gl-text-color;
$gl-gray-dark: #313236;
$gl-gray-light: $gl-placeholder-color;
-$gl-header-color: $gl-title-color;
+$gl-header-color: #4c4e54;
/*
* Lists
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index c879074c7fe..a5a260d4c8f 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -109,6 +109,10 @@
width: 100%;
}
+ .block-first {
+ padding: 5px 16px 11px;
+ }
+
.js-build-variable {
color: $code-color;
}
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index b657ca47d38..732dc645c66 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -55,3 +55,16 @@
}
}
}
+
+.groups-header {
+
+ @media (min-width: $screen-sm-min) {
+ .nav-links {
+ width: 35%;
+ }
+
+ .nav-controls {
+ width: 65%;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 6f58203f49c..0fcdaf94a21 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -93,8 +93,9 @@
.profile-user-bio {
// Limits the width of the user bio for readability.
- max-width: 750px;
- margin: auto;
+ max-width: 600px;
+ margin: 15px auto 0;
+ padding: 0 16px;
}
.user-avatar-button {
@@ -212,6 +213,28 @@
}
.user-profile {
+ .cover-controls a {
+ margin-left: 5px;
+ }
+ .profile-header {
+ margin: 0 auto;
+ .avatar-holder {
+ width: 90px;
+ display: inline-block;
+ }
+ .user-info {
+ display: inline-block;
+ text-align: left;
+ vertical-align: middle;
+ margin-left: 15px;
+ .handle {
+ color: $gl-gray-light;
+ }
+ .member-date {
+ margin-bottom: 4px;
+ }
+ }
+ }
@media (max-width: $screen-xs-max) {
.cover-block {
padding-top: 20px;
@@ -219,16 +242,26 @@
.cover-controls {
position: static;
+ padding: 0 16px;
margin-bottom: 20px;
+ display: -webkit-flex;
+ display: flex;
.btn {
- display: inline-block;
- width: 46%;
+ -webkit-flex-grow: 1;
+ flex-grow: 1;
+ &:first-child {
+ margin-left: 0;
+ }
}
}
}
}
+.user-profile-nav {
+ margin-top: 15px;
+}
+
table.u2f-registrations {
th:not(:last-child), td:not(:last-child) {
border-right: solid 1px transparent;
diff --git a/app/assets/stylesheets/pages/snippets.scss b/app/assets/stylesheets/pages/snippets.scss
index 5270aea4e79..4d5df566d9b 100644
--- a/app/assets/stylesheets/pages/snippets.scss
+++ b/app/assets/stylesheets/pages/snippets.scss
@@ -12,11 +12,18 @@
.snippet-file-content {
border-radius: 3px;
+ margin-bottom: $gl-padding;
+
.btn-clipboard {
@extend .btn;
}
}
+.project-snippets .awards {
+ border-bottom: 1px solid $table-border-color;
+ padding-bottom: $gl-padding;
+}
+
.snippet-title {
font-size: 24px;
font-weight: 600;
diff --git a/app/controllers/concerns/toggle_award_emoji.rb b/app/controllers/concerns/toggle_award_emoji.rb
index 172d5344b7a..3717c49f272 100644
--- a/app/controllers/concerns/toggle_award_emoji.rb
+++ b/app/controllers/concerns/toggle_award_emoji.rb
@@ -10,7 +10,9 @@ module ToggleAwardEmoji
if awardable.user_can_award?(current_user, name)
awardable.toggle_award_emoji(name, current_user)
- TodoService.new.new_award_emoji(to_todoable(awardable), current_user)
+
+ todoable = to_todoable(awardable)
+ TodoService.new.new_award_emoji(todoable, current_user) if todoable
render json: { ok: true }
else
@@ -24,8 +26,10 @@ module ToggleAwardEmoji
case awardable
when Note
awardable.noteable
- else
+ when MergeRequest, Issue
awardable
+ when Snippet
+ nil
end
end
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index 17ceefec3b8..e290a0eadda 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -1,6 +1,8 @@
class Projects::SnippetsController < Projects::ApplicationController
+ include ToggleAwardEmoji
+
before_action :module_enabled
- before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
+ before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji]
# Allow read any snippet
before_action :authorize_read_project_snippet!, except: [:new, :create, :index]
@@ -80,6 +82,7 @@ class Projects::SnippetsController < Projects::ApplicationController
def snippet
@snippet ||= @project.snippets.find(params[:id])
end
+ alias_method :awardable, :snippet
def authorize_read_project_snippet!
return render_404 unless can?(current_user, :read_project_snippet, @snippet)
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index 2a17c1f34db..d198782138a 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -1,4 +1,6 @@
class SnippetsController < ApplicationController
+ include ToggleAwardEmoji
+
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
# Allow read snippet
@@ -85,6 +87,7 @@ class SnippetsController < ApplicationController
PersonalSnippet.find(params[:id])
end
end
+ alias_method :awardable, :snippet
def authorize_read_snippet!
authenticate_user! unless can?(current_user, :read_personal_snippet, @snippet)
diff --git a/app/helpers/award_emoji_helper.rb b/app/helpers/award_emoji_helper.rb
new file mode 100644
index 00000000000..aa134cea31c
--- /dev/null
+++ b/app/helpers/award_emoji_helper.rb
@@ -0,0 +1,9 @@
+module AwardEmojiHelper
+ def toggle_award_url(awardable)
+ if @project
+ url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable])
+ else
+ url_for([:toggle_award_emoji, awardable])
+ end
+ end
+end
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index 5b71113feb9..670a7ca36f4 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -70,6 +70,10 @@ module GitlabRoutingHelper
namespace_project_runner_path(@project.namespace, @project, runner, *args)
end
+ def environment_path(environment, *args)
+ namespace_project_environment_path(environment.project.namespace, environment.project, environment, *args)
+ end
+
def issue_path(entity, *args)
namespace_project_issue_path(entity.project.namespace, entity.project, entity, *args)
end
@@ -102,6 +106,14 @@ module GitlabRoutingHelper
end
end
+ def toggle_award_emoji_personal_snippet_path(*args)
+ toggle_award_emoji_snippet_path(*args)
+ end
+
+ def toggle_award_emoji_namespace_project_project_snippet_path(*args)
+ toggle_award_emoji_namespace_project_snippet_path(*args)
+ end
+
## Members
def project_members_url(project, *args)
namespace_project_project_members_url(project.namespace, project)
diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb
index d8d4575bb4d..073ac4c1b65 100644
--- a/app/models/concerns/awardable.rb
+++ b/app/models/concerns/awardable.rb
@@ -71,6 +71,12 @@ module Awardable
end
end
+ def user_authored?(current_user)
+ author = self.respond_to?(:author) ? self.author : self.user
+
+ author == current_user
+ end
+
def awarded_emoji?(emoji_name, current_user)
award_emoji.where(name: emoji_name, user: current_user).exists?
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 1650ac9fcbe..ff465d2c745 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -200,10 +200,6 @@ module Issuable
end
end
- def user_authored?(user)
- user == author
- end
-
def subscribed_without_subscriptions?(user)
participants(user).include?(user)
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 616efaf3c42..2dcf7f89bfc 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -670,9 +670,12 @@ class MergeRequest < ActiveRecord::Base
def environments
return [] unless diff_head_commit
- target_project.environments.select do |environment|
- environment.includes_commit?(diff_head_commit)
- end
+ environments = source_project.environments_for(
+ source_branch, diff_head_commit)
+ environments += target_project.environments_for(
+ target_branch, diff_head_commit, with_tags: true)
+
+ environments.uniq
end
def state_human_name
@@ -761,10 +764,23 @@ class MergeRequest < ActiveRecord::Base
end
def all_pipelines
- @all_pipelines ||=
- if diff_head_sha && source_project
- source_project.pipelines.order(id: :desc).where(sha: commits_sha, ref: source_branch)
- end
+ return unless source_project
+
+ @all_pipelines ||= begin
+ sha = if persisted?
+ all_commits_sha
+ else
+ diff_head_sha
+ end
+
+ source_project.pipelines.order(id: :desc).
+ where(sha: sha, ref: source_branch)
+ end
+ end
+
+ # Note that this could also return SHA from now dangling commits
+ def all_commits_sha
+ merge_request_diffs.flat_map(&:commits_sha).uniq
end
def merge_commit
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 18c583add88..7362886e9f5 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -117,6 +117,14 @@ class MergeRequestDiff < ActiveRecord::Base
project.commit(head_commit_sha)
end
+ def commits_sha
+ if @commits
+ commits.map(&:sha)
+ else
+ st_commits.map { |commit| commit[:id] }
+ end
+ end
+
def diff_refs
return unless start_commit_sha || base_commit_sha
diff --git a/app/models/note.rb b/app/models/note.rb
index b94e3cff2ce..f2656df028b 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -223,10 +223,6 @@ class Note < ActiveRecord::Base
end
end
- def user_authored?(user)
- user == author
- end
-
def award_emoji?
can_be_award_emoji? && contains_emoji_only?
end
diff --git a/app/models/project.rb b/app/models/project.rb
index d7f20070be0..7265cb55594 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -1293,6 +1293,22 @@ class Project < ActiveRecord::Base
Gitlab::Redis.with { |redis| redis.del(pushes_since_gc_redis_key) }
end
+ def environments_for(ref, commit, with_tags: false)
+ environment_ids = deployments.group(:environment_id).
+ select(:environment_id)
+
+ environment_ids =
+ if with_tags
+ environment_ids.where('ref=? OR tag IS TRUE', ref)
+ else
+ environment_ids.where(ref: ref)
+ end
+
+ environments.where(id: environment_ids).select do |environment|
+ environment.includes_commit?(commit)
+ end
+ end
+
private
def pushes_since_gc_redis_key
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
index 9c602c582bd..8c9534c3565 100644
--- a/app/models/project_feature.rb
+++ b/app/models/project_feature.rb
@@ -22,6 +22,12 @@ class ProjectFeature < ActiveRecord::Base
belongs_to :project
+ default_value_for :builds_access_level, value: ENABLED, allows_nil: false
+ default_value_for :issues_access_level, value: ENABLED, allows_nil: false
+ default_value_for :merge_requests_access_level, value: ENABLED, allows_nil: false
+ default_value_for :snippets_access_level, value: ENABLED, allows_nil: false
+ default_value_for :wiki_access_level, value: ENABLED, allows_nil: false
+
def feature_available?(feature, user)
raise ArgumentError, 'invalid project feature' unless FEATURES.include?(feature)
diff --git a/app/models/project_team.rb b/app/models/project_team.rb
index ab6ea2aae36..d9ce5088903 100644
--- a/app/models/project_team.rb
+++ b/app/models/project_team.rb
@@ -163,7 +163,7 @@ class ProjectTeam
# Each group produces a list of maximum access level per user. We take the
# max of the values produced by each group.
- if project.invited_groups.any? && project.allowed_to_share_with_group?
+ if project_shared_with_group?
project.project_group_links.each do |group_link|
invited_access = max_invited_level_for_users(group_link, user_ids)
merge_max!(access, invited_access)
@@ -200,43 +200,17 @@ class ProjectTeam
def fetch_members(level = nil)
project_members = project.members
group_members = group ? group.members : []
- invited_members = []
-
- if project.invited_groups.any? && project.allowed_to_share_with_group?
- project.project_group_links.includes(group: [:group_members]).each do |group_link|
- invited_group = group_link.group
- im = invited_group.members
-
- if level
- int_level = GroupMember.access_level_roles[level.to_s.singularize.titleize]
-
- # Skip group members if we ask for masters
- # but max group access is developers
- next if int_level > group_link.group_access
-
- # If we ask for developers and max
- # group access is developers we need to provide
- # both group master, developers as devs
- if int_level == group_link.group_access
- im.where("access_level >= ?)", group_link.group_access)
- else
- im.send(level)
- end
- end
-
- invited_members << im
- end
-
- invited_members = invited_members.flatten.compact
- end
if level
- project_members = project_members.send(level)
- group_members = group_members.send(level) if group
+ project_members = project_members.public_send(level)
+ group_members = group_members.public_send(level) if group
end
user_ids = project_members.pluck(:user_id)
+
+ invited_members = fetch_invited_members(level)
user_ids.push(*invited_members.map(&:user_id)) if invited_members.any?
+
user_ids.push(*group_members.pluck(:user_id)) if group
User.where(id: user_ids)
@@ -249,4 +223,38 @@ class ProjectTeam
def merge_max!(first_hash, second_hash)
first_hash.merge!(second_hash) { |_key, old, new| old > new ? old : new }
end
+
+ def project_shared_with_group?
+ project.invited_groups.any? && project.allowed_to_share_with_group?
+ end
+
+ def fetch_invited_members(level = nil)
+ invited_members = []
+
+ return invited_members unless project_shared_with_group?
+
+ project.project_group_links.includes(group: [:group_members]).each do |link|
+ invited_group_members = link.group.members
+
+ if level
+ numeric_level = GroupMember.access_level_roles[level.to_s.singularize.titleize]
+
+ # If we're asked for a level that's higher than the group's access,
+ # there's nothing left to do
+ next if numeric_level > link.group_access
+
+ # Make sure we include everyone _above_ the requested level as well
+ invited_group_members =
+ if numeric_level == link.group_access
+ invited_group_members.where("access_level >= ?", link.group_access)
+ else
+ invited_group_members.public_send(level)
+ end
+ end
+
+ invited_members << invited_group_members
+ end
+
+ invited_members.flatten.compact
+ end
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 772c62a4124..51557228ab9 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -840,7 +840,7 @@ class Repository
def get_committer_and_author(user, email: nil, name: nil)
committer = user_to_committer(user)
- author = name && email ? Gitlab::Git::committer_hash(email: email, name: name) : committer
+ author = Gitlab::Git::committer_hash(email: email, name: name) || committer
{
author: author,
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 5ec933601ac..8a1730f3f36 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -4,6 +4,7 @@ class Snippet < ActiveRecord::Base
include Participable
include Referable
include Sortable
+ include Awardable
default_value_for :visibility_level, Snippet::PRIVATE
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index d929364fc96..0d79ca7dc52 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -49,28 +49,6 @@
= select(:application_setting, :enabled_git_access_protocol, [['Both SSH and HTTP(S)', nil], ['Only SSH', 'ssh'], ['Only HTTP(S)', 'http']], {}, class: 'form-control')
%span.help-block#clone-protocol-help
Allow only the selected protocols to be used for Git access.
- .form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- = f.label :version_check_enabled do
- = f.check_box :version_check_enabled
- Version check enabled
- .form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- = f.label :email_author_in_body do
- = f.check_box :email_author_in_body
- Include author name in notification email body
- .help-block
- Some email servers do not support overriding the email sender name.
- Enable this option to include the name of the author of the issue,
- merge request or comment in the email body instead.
- .form-group
- = f.label :admin_notification_email, class: 'control-label col-sm-2'
- .col-sm-10
- = f.text_field :admin_notification_email, class: 'form-control'
- .help-block
- Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area.
%fieldset
%legend Account and Limit Settings
@@ -341,6 +319,15 @@
%a{ href: 'http://www.akismet.com', target: 'blank'} http://www.akismet.com
%fieldset
+ %legend Abuse reports
+ .form-group
+ = f.label :admin_notification_email, 'Abuse reports notification email', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.text_field :admin_notification_email, class: 'form-control'
+ .help-block
+ Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area.
+
+ %fieldset
%legend Error Reporting and Logging
%p
These settings require a restart to take effect.
@@ -407,6 +394,29 @@
= succeed "." do
= link_to "Koding administration documentation", help_page_path("administration/integration/koding")
+ %fieldset
+ %legend Usage statistics
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :version_check_enabled do
+ = f.check_box :version_check_enabled
+ Version check enabled
+ .help-block
+ Let GitLab inform you when an update is available.
+
+ %fieldset
+ %legend Email
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :email_author_in_body do
+ = f.check_box :email_author_in_body
+ Include author name in notification email body
+ .help-block
+ Some email servers do not support overriding the email sender name.
+ Enable this option to include the name of the author of the issue,
+ merge request or comment in the email body instead.
.form-actions
= f.submit 'Save', class: 'btn btn-save'
diff --git a/app/views/award_emoji/_awards_block.html.haml b/app/views/award_emoji/_awards_block.html.haml
index 02efcecc889..fbe3ab912b6 100644
--- a/app/views/award_emoji/_awards_block.html.haml
+++ b/app/views/award_emoji/_awards_block.html.haml
@@ -1,5 +1,5 @@
- grouped_emojis = awardable.grouped_awards(with_thumbs: inline)
-.awards.js-awards-block{ class: ("hidden" if !inline && grouped_emojis.empty?), data: { award_url: url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable]) } }
+.awards.js-awards-block{ class: ("hidden" if !inline && grouped_emojis.empty?), data: { award_url: toggle_award_url(awardable) } }
- awards_sort(grouped_emojis).each do |emoji, awards|
%button.btn.award-control.js-emoji-btn.has-tooltip{ type: "button", class: (award_active_class(awards, current_user)), data: { placement: "bottom", title: award_user_list(awards, current_user) } }
= emoji_icon(emoji, sprite: false)
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 53ed4fa991d..31db6ee0cad 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -23,7 +23,7 @@
.cover-desc.description
= markdown(@group.description, pipeline: :description)
-%div{ class: container_class }
+%div.groups-header{ class: container_class }
.top-area
%ul.nav-links
%li.active
diff --git a/app/views/projects/builds/_table.html.haml b/app/views/projects/builds/_table.html.haml
index 61eff73da26..c2bcfb773a6 100644
--- a/app/views/projects/builds/_table.html.haml
+++ b/app/views/projects/builds/_table.html.haml
@@ -9,7 +9,7 @@
%thead
%tr
%th Status
- %th Commit
+ %th Build
- if admin
%th Project
%th Runner
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
index 494695a03a5..44e645a7e81 100644
--- a/app/views/projects/merge_requests/widget/_heading.html.haml
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -43,15 +43,16 @@
= icon("times-circle")
Could not connect to the CI server. Please check your settings and try again.
-- @merge_request.environments.each do |environment|
- .mr-widget-heading
- .ci_widget.ci-success
- = ci_icon_for_status("success")
- %span.hidden-sm
- Deployed to
- = succeed '.' do
- = link_to environment.name, namespace_project_environment_path(@project.namespace, @project, environment), class: 'environment'
- - external_url = environment.external_url
- - if external_url
- = link_to external_url, target: '_blank' do
- = icon('external-link', text: "View on #{external_url.gsub(/\A.*?:\/\//, '')}", right: true)
+- @merge_request.environments.sort_by(&:name).each do |environment|
+ - if can?(current_user, :read_environment, environment)
+ .mr-widget-heading
+ .ci_widget.ci-success
+ = ci_icon_for_status("success")
+ %span.hidden-sm
+ Deployed to
+ = succeed '.' do
+ = link_to environment.name, environment_path(environment), class: 'environment'
+ - external_url = environment.external_url
+ - if external_url
+ = link_to external_url, target: '_blank' do
+ = icon('external-link', text: "View on #{external_url.gsub(/\A.*?:\/\//, '')}", right: true)
diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml
index a5a5619fa12..4aa4ab46a2f 100644
--- a/app/views/projects/snippets/_actions.html.haml
+++ b/app/views/projects/snippets/_actions.html.haml
@@ -3,7 +3,7 @@
= link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create new-snippet-link', title: "New Snippet" do
New Snippet
- if can?(current_user, :update_project_snippet, @snippet)
- = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-warning", title: 'Delete Snippet' do
+ = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do
Delete
- if can?(current_user, :update_project_snippet, @snippet)
= link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped snippable-edit" do
diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml
index b70fda88a79..9503dbded13 100644
--- a/app/views/projects/snippets/show.html.haml
+++ b/app/views/projects/snippets/show.html.haml
@@ -2,13 +2,16 @@
= render 'shared/snippets/header'
-%article.file-holder.snippet-file-content
- .file-title
- = blob_icon 0, @snippet.file_name
- = @snippet.file_name
- .file-actions
- = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']")
- = link_to 'Raw', raw_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", target: "_blank"
- = render 'shared/snippets/blob'
-
-%div#notes= render "projects/notes/notes_with_form"
+.project-snippets
+ %article.file-holder.snippet-file-content
+ .file-title
+ = blob_icon 0, @snippet.file_name
+ = @snippet.file_name
+ .file-actions
+ = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']")
+ = link_to 'Raw', raw_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", target: "_blank"
+ = render 'shared/snippets/blob'
+
+ = render 'award_emoji/awards_block', awardable: @snippet, inline: true
+
+ %div#notes= render "projects/notes/notes_with_form"
diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml
index fa403da8f79..cd89155c616 100644
--- a/app/views/snippets/show.html.haml
+++ b/app/views/snippets/show.html.haml
@@ -10,3 +10,5 @@
= clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']")
= link_to 'Raw', raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank"
= render 'shared/snippets/blob'
+
+= render 'award_emoji/awards_block', awardable: @snippet, inline: true \ No newline at end of file
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 9a052abe40a..2a57ac90bab 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -10,72 +10,72 @@
= auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity")
.user-profile
- .cover-block
+ .cover-block.user-cover-block
.cover-controls
- if @user == current_user
= link_to profile_path, class: 'btn btn-gray' do
= icon('pencil')
- elsif current_user
- %span.report-abuse
- - if @user.abuse_report
- %button.btn.btn-danger{ title: 'Already reported for abuse',
- data: { toggle: 'tooltip', placement: 'left', container: 'body' }}
- = icon('exclamation-circle')
- - else
- = link_to new_abuse_report_path(user_id: @user.id, ref_url: request.referrer), class: 'btn btn-gray',
- title: 'Report abuse', data: {toggle: 'tooltip', placement: 'left', container: 'body'} do
- = icon('exclamation-circle')
+ - if @user.abuse_report
+ %button.btn.btn-danger{ title: 'Already reported for abuse',
+ data: { toggle: 'tooltip', placement: 'bottom', container: 'body' }}
+ = icon('exclamation-circle')
+ - else
+ = link_to new_abuse_report_path(user_id: @user.id, ref_url: request.referrer), class: 'btn btn-gray',
+ title: 'Report abuse', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = icon('exclamation-circle')
- if current_user
- &nbsp;
= link_to user_path(@user, :atom, { private_token: current_user.private_token }), class: 'btn btn-gray' do
= icon('rss')
- if current_user.admin?
- &nbsp;
= link_to [:admin, @user], class: 'btn btn-gray', title: 'View user in admin area',
data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('users')
- .avatar-holder
- = link_to avatar_icon(@user, 400), target: '_blank' do
- = image_tag avatar_icon(@user, 90), class: "avatar s90", alt: ''
- .cover-title
- = @user.name
-
- .cover-desc
- %span.middle-dot-divider
- @#{@user.username}
- %span.middle-dot-divider
- Member since #{@user.created_at.to_s(:medium)}
+ .profile-header
+ .avatar-holder
+ = link_to avatar_icon(@user, 400), target: '_blank' do
+ = image_tag avatar_icon(@user, 90), class: "avatar s90", alt: ''
+
+ .user-info
+ .cover-title
+ = @user.name
+ %span.handle
+ @#{@user.username}
+
+ .cover-desc.member-date
+ %span.middle-dot-divider
+ Member since #{@user.created_at.to_s(:medium)}
+
+ .cover-desc
+ - unless @user.public_email.blank?
+ .profile-link-holder.middle-dot-divider
+ = link_to @user.public_email, "mailto:#{@user.public_email}"
+ - unless @user.skype.blank?
+ .profile-link-holder.middle-dot-divider
+ = link_to "skype:#{@user.skype}", title: "Skype" do
+ = icon('skype')
+ - unless @user.linkedin.blank?
+ .profile-link-holder.middle-dot-divider
+ = link_to "https://www.linkedin.com/in/#{@user.linkedin}", title: "LinkedIn" do
+ = icon('linkedin-square')
+ - unless @user.twitter.blank?
+ .profile-link-holder.middle-dot-divider
+ = link_to "https://twitter.com/#{@user.twitter}", title: "Twitter" do
+ = icon('twitter-square')
+ - unless @user.website_url.blank?
+ .profile-link-holder.middle-dot-divider
+ = link_to @user.short_website_url, @user.full_website_url
+ - unless @user.location.blank?
+ .profile-link-holder.middle-dot-divider
+ = icon('map-marker')
+ = @user.location
- if @user.bio.present?
.cover-desc
%p.profile-user-bio
= @user.bio
- .cover-desc
- - unless @user.public_email.blank?
- .profile-link-holder.middle-dot-divider
- = link_to @user.public_email, "mailto:#{@user.public_email}"
- - unless @user.skype.blank?
- .profile-link-holder.middle-dot-divider
- = link_to "skype:#{@user.skype}", title: "Skype" do
- = icon('skype')
- - unless @user.linkedin.blank?
- .profile-link-holder.middle-dot-divider
- = link_to "https://www.linkedin.com/in/#{@user.linkedin}", title: "LinkedIn" do
- = icon('linkedin-square')
- - unless @user.twitter.blank?
- .profile-link-holder.middle-dot-divider
- = link_to "https://twitter.com/#{@user.twitter}", title: "Twitter" do
- = icon('twitter-square')
- - unless @user.website_url.blank?
- .profile-link-holder.middle-dot-divider
- = link_to @user.short_website_url, @user.full_website_url
- - unless @user.location.blank?
- .profile-link-holder.middle-dot-divider
- = icon('map-marker')
- = @user.location
-
%ul.nav-links.center.user-profile-nav
%li.js-activity-tab
= link_to user_calendar_activities_path, data: {target: 'div#activity', action: 'activity', toggle: 'tab'} do
diff --git a/config/application.rb b/config/application.rb
index 8166b6003f6..4792f6670a8 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -116,10 +116,6 @@ module Gitlab
redis_config_hash = Gitlab::Redis.params
redis_config_hash[:namespace] = Gitlab::Redis::CACHE_NAMESPACE
redis_config_hash[:expires_in] = 2.weeks # Cache should not grow forever
- if Sidekiq.server? # threaded context
- redis_config_hash[:pool_size] = Sidekiq.options[:concurrency] + 5
- redis_config_hash[:pool_timeout] = 1
- end
config.cache_store = :redis_store, redis_config_hash
config.active_record.raise_in_transactional_callbacks = true
diff --git a/config/routes.rb b/config/routes.rb
index c4eee59e7aa..4d6ec699cbd 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -35,6 +35,10 @@ Rails.application.routes.draw do
post :approve_access_request, on: :member
end
+ concern :awardable do
+ post :toggle_award_emoji, on: :member
+ end
+
namespace :ci do
# CI API
Ci::API::API.logger Rails.logger
@@ -98,7 +102,7 @@ Rails.application.routes.draw do
#
# Global snippets
#
- resources :snippets do
+ resources :snippets, concerns: :awardable do
member do
get 'raw'
end
@@ -110,7 +114,6 @@ Rails.application.routes.draw do
#
# Invites
#
-
resources :invites, only: [:show], constraints: { id: /[A-Za-z0-9_-]+/ } do
member do
post :accept
@@ -662,7 +665,7 @@ Rails.application.routes.draw do
end
end
- resources :snippets, constraints: { id: /\d+/ } do
+ resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do
member do
get 'raw'
end
@@ -724,7 +727,7 @@ Rails.application.routes.draw do
end
end
- resources :merge_requests, constraints: { id: /\d+/ } do
+ resources :merge_requests, concerns: :awardable, constraints: { id: /\d+/ } do
member do
get :commits
get :diffs
@@ -736,7 +739,6 @@ Rails.application.routes.draw do
post :cancel_merge_when_build_succeeds
get :ci_status
post :toggle_subscription
- post :toggle_award_emoji
post :remove_wip
get :diff_for_path
post :resolve_conflicts
@@ -840,10 +842,9 @@ Rails.application.routes.draw do
end
end
- resources :issues, constraints: { id: /\d+/ } do
+ resources :issues, concerns: :awardable, constraints: { id: /\d+/ } do
member do
post :toggle_subscription
- post :toggle_award_emoji
post :mark_as_spam
get :referenced_merge_requests
get :related_branches
@@ -871,9 +872,8 @@ Rails.application.routes.draw do
resources :group_links, only: [:index, :create, :destroy], constraints: { id: /\d+/ }
- resources :notes, only: [:index, :create, :destroy, :update], constraints: { id: /\d+/ } do
+ resources :notes, only: [:index, :create, :destroy, :update], concerns: :awardable, constraints: { id: /\d+/ } do
member do
- post :toggle_award_emoji
delete :delete_attachment
post :resolve
delete :resolve, action: :unresolve
diff --git a/doc/README.md b/doc/README.md
index 254394eb63e..dd0eb97489e 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -29,9 +29,9 @@
- [Install](install/README.md) Requirements, directory structures and installation from source.
- [Restart GitLab](administration/restart_gitlab.md) Learn how to restart GitLab and its components.
- [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, Twitter.
-- [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages.
+- [Issue closing pattern](administration/issue_closing_pattern.md) Customize how to close an issue from commit messages.
- [Koding](administration/integration/koding.md) Set up Koding to use with GitLab.
-- [Libravatar](customization/libravatar.md) Use Libravatar for user avatars.
+- [Libravatar](customization/libravatar.md) Use Libravatar instead of Gravatar for user avatars.
- [Log system](administration/logs.md) Log system.
- [Environment Variables](administration/environment_variables.md) to configure GitLab.
- [Operations](operations/README.md) Keeping GitLab up and running.
diff --git a/doc/administration/issue_closing_pattern.md b/doc/administration/issue_closing_pattern.md
new file mode 100644
index 00000000000..28e1fd4e12e
--- /dev/null
+++ b/doc/administration/issue_closing_pattern.md
@@ -0,0 +1,49 @@
+# Issue closing pattern
+
+>**Note:**
+This is the administration documentation.
+There is a separate [user documentation] on issue closing pattern.
+
+When a commit or merge request resolves one or more issues, it is possible to
+automatically have these issues closed when the commit or merge request lands
+in the project's default branch.
+
+## Change the issue closing pattern
+
+In order to change the pattern you need to have access to the server that GitLab
+is installed on.
+
+The default pattern can be located in [gitlab.yml.example] under the
+"Automatic issue closing" section.
+
+> **Tip:**
+You are advised to use http://rubular.com to test the issue closing pattern.
+Because Rubular doesn't understand `%{issue_ref}`, you can replace this by
+`#\d+` when testing your patterns, which matches only local issue references like `#123`.
+
+**For Omnibus installations**
+
+1. Open `/etc/gitlab/gitlab.rb` with your editor.
+1. Change the value of `gitlab_rails['issue_closing_pattern']` to a regular
+ expression of your liking:
+
+ ```ruby
+ gitlab_rails['issue_closing_pattern'] = "((?:[Cc]los(?:e[sd]|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)"
+ ```
+1. [Reconfigure] GitLab for the changes to take effect.
+
+**For installations from source**
+
+1. Open `gitlab.yml` with your editor.
+1. Change the value of `issue_closing_pattern`:
+
+ ```yaml
+ issue_closing_pattern: "((?:[Cc]los(?:e[sd]|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)"
+ ```
+
+1. [Restart] GitLab for the changes to take effect.
+
+[gitlab.yml.example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/gitlab.yml.example
+[reconfigure]: restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart]: restart_gitlab.md#installations-from-source
+[user documentation]: ../user/project/issues/automatic_issue_closing.md
diff --git a/doc/api/award_emoji.md b/doc/api/award_emoji.md
index 72ec99b7c56..c464e3f3f71 100644
--- a/doc/api/award_emoji.md
+++ b/doc/api/award_emoji.md
@@ -1,12 +1,13 @@
# Award Emoji
-> [Introduced][ce-4575] in GitLab 8.9.
+> [Introduced][ce-4575] in GitLab 8.9, Snippet support in 8.12
+
An awarded emoji tells a thousand words, and can be awarded on issues, merge
-requests and notes/comments. Issues, merge requests and notes are further called
+requests, snippets, and notes/comments. Issues, merge requests, snippets, and notes are further called
`awardables`.
-## Issues and merge requests
+## Issues, merge requests, and snippets
### List an awardable's award emoji
@@ -15,6 +16,7 @@ Gets a list of all award emoji
```
GET /projects/:id/issues/:issue_id/award_emoji
GET /projects/:id/merge_requests/:merge_request_id/award_emoji
+GET /projects/:id/snippets/:snippet_id/award_emoji
```
Parameters:
@@ -69,11 +71,12 @@ Example Response:
### Get single award emoji
-Gets a single award emoji from an issue or merge request.
+Gets a single award emoji from an issue, snippet, or merge request.
```
GET /projects/:id/issues/:issue_id/award_emoji/:award_id
GET /projects/:id/merge_requests/:merge_request_id/award_emoji/:award_id
+GET /projects/:id/snippets/:snippet_id/award_emoji/:award_id
```
Parameters:
@@ -116,6 +119,7 @@ This end point creates an award emoji on the specified resource
```
POST /projects/:id/issues/:issue_id/award_emoji
POST /projects/:id/merge_requests/:merge_request_id/award_emoji
+POST /projects/:id/snippets/:snippet_id/award_emoji
```
Parameters:
@@ -159,6 +163,7 @@ admins or the author of the award. Status code 200 on success, 401 if unauthoriz
```
DELETE /projects/:id/issues/:issue_id/award_emoji/:award_id
DELETE /projects/:id/merge_requests/:merge_request_id/award_emoji/:award_id
+DELETE /projects/:id/snippets/:snippet_id/award_emoji/:award_id
```
Parameters:
@@ -197,7 +202,7 @@ Example Response:
## Award Emoji on Notes
The endpoints documented above are available for Notes as well. Notes
-are a sub-resource of Issues and Merge Requests. The examples below
+are a sub-resource of Issues, Merge Requests, or Snippets. The examples below
describe working with Award Emoji on notes for an Issue, but can be
easily adapted for notes on a Merge Request.
diff --git a/doc/customization/issue_closing.md b/doc/customization/issue_closing.md
index 4620bb2dcde..31164ccd465 100644
--- a/doc/customization/issue_closing.md
+++ b/doc/customization/issue_closing.md
@@ -1,39 +1,4 @@
-# Issue closing pattern
+This document was split into:
-When a commit or merge request resolves one or more issues, it is possible to automatically have these issues closed when the commit or merge request lands in the project's default branch.
-
-If a commit message or merge request description contains a sentence matching the regular expression below, all issues referenced from
-the matched text will be closed. This happens when the commit is pushed to a project's default branch, or when a commit or merge request is merged into there.
-
-When not specified, the default `issue_closing_pattern` as shown below will be used:
-
-```bash
-((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing))(:?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\d+))+)
-```
-
-Here, `%{issue_ref}` is a complex regular expression defined inside GitLab, that matches a reference to a local issue (`#123`), cross-project issue (`group/project#123`) or a link to an issue (`https://gitlab.example.com/group/project/issues/123`).
-
-For example:
-
-```
-git commit -m "Awesome commit message (Fix #20, Fixes #21 and Closes group/otherproject#22). This commit is also related to #17 and fixes #18, #19 and https://gitlab.example.com/group/otherproject/issues/23."
-```
-
-will close `#18`, `#19`, `#20`, and `#21` in the project this commit is pushed to, as well as `#22` and `#23` in group/otherproject. `#17` won't be closed as it does not match the pattern. It also works with multiline commit messages.
-
-Tip: you can test this closing pattern at [http://rubular.com][1]. Use this site
-to test your own patterns.
-Because Rubular doesn't understand `%{issue_ref}`, you can replace this by `#\d+` in testing, which matches only local issue references like `#123`.
-
-## Change the pattern
-
-For Omnibus installs you can change the default pattern in `/etc/gitlab/gitlab.rb`:
-
-```
-issue_closing_pattern: '((?:[Cc]los(?:e[sd]|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)'
-```
-
-For manual installs you can customize the pattern in [gitlab.yml][0] using the `issue_closing_pattern` key.
-
-[0]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/gitlab.yml.example
-[1]: http://rubular.com/r/Xmbexed1OJ
+- [administration/issue_closing_pattern.md](../administration/issue_closing_pattern.md).
+- [user/project/issues/automatic_issue_closing](../user/project/issues/automatic_issue_closing.md).
diff --git a/doc/gitlab-basics/create-issue.md b/doc/gitlab-basics/create-issue.md
index 5221d85b661..da9a165b8f5 100644
--- a/doc/gitlab-basics/create-issue.md
+++ b/doc/gitlab-basics/create-issue.md
@@ -1,6 +1,6 @@
# How to create an Issue in GitLab
-The Issue Tracker is a good place to add things that need to be improved or solved in a project.
+The Issue Tracker is a good place to add things that need to be improved or solved in a project.
To create an Issue, sign in to GitLab.
@@ -24,4 +24,4 @@ You may assign the Issue to a user, add a milestone and add labels (they are all
![Submit new issue](basicsimages/submit_new_issue.png)
-Your Issue will now be added to the Issue Tracker and will be ready to be reviewed. You can comment on it and mention the people involved. You can also link Issues to the Merge Requests where the Issues are solved. To do this, you can use an [Issue closing pattern](http://docs.gitlab.com/ce/customization/issue_closing.html).
+Your Issue will now be added to the Issue Tracker and will be ready to be reviewed. You can comment on it and mention the people involved. You can also link Issues to the Merge Requests where the Issues are solved. To do this, you can use an [Issue closing pattern](../user/project/issues/automatic_issue_closing.md).
diff --git a/doc/intro/README.md b/doc/intro/README.md
index 71fef50ceb4..1790b2b761f 100644
--- a/doc/intro/README.md
+++ b/doc/intro/README.md
@@ -22,7 +22,7 @@ Create merge requests and review code.
- [Fork a project and contribute to it](../workflow/forking_workflow.md)
- [Create a new merge request](../gitlab-basics/add-merge-request.md)
-- [Automatically close issues from merge requests](../customization/issue_closing.md)
+- [Automatically close issues from merge requests](../user/project/issues/automatic_issue_closing.md)
- [Automatically merge when your builds succeed](../user/project/merge_requests/merge_when_build_succeeds.md)
- [Revert any commit](../user/project/merge_requests/revert_changes.md)
- [Cherry-pick any commit](../user/project/merge_requests/cherry_pick_changes.md)
diff --git a/doc/user/project/issues/automatic_issue_closing.md b/doc/user/project/issues/automatic_issue_closing.md
new file mode 100644
index 00000000000..d6f3a7d5555
--- /dev/null
+++ b/doc/user/project/issues/automatic_issue_closing.md
@@ -0,0 +1,55 @@
+# Automatic issue closing
+
+>**Note:**
+This is the user docs. In order to change the default issue closing pattern,
+follow the steps in the [administration docs].
+
+When a commit or merge request resolves one or more issues, it is possible to
+automatically have these issues closed when the commit or merge request lands
+in the project's default branch.
+
+If a commit message or merge request description contains a sentence matching
+a certain regular expression, all issues referenced from the matched text will
+be closed. This happens when the commit is pushed to a project's **default**
+branch, or when a commit or merge request is merged into it.
+
+## Default closing pattern value
+
+When not specified, the default issue closing pattern as shown below will be
+used:
+
+```bash
+((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing))(:?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\d+))+)
+```
+
+Note that `%{issue_ref}` is a complex regular expression defined inside GitLab's
+source code that can match a reference to 1) a local issue (`#123`),
+2) a cross-project issue (`group/project#123`) or 3) a link to an issue
+(`https://gitlab.example.com/group/project/issues/123`).
+
+---
+
+This translates to the following keywords:
+
+- Close, Closes, Closed, Closing, close, closes, closed, closing
+- Fix, Fixes, Fixed, Fixing, fix, fixes, fixed, fixing
+- Resolve, Resolves, Resolved, Resolving, resolve, resolves, resolved, resolving
+
+---
+
+For example the following commit message:
+
+```
+Awesome commit message
+
+Fix #20, Fixes #21 and Closes group/otherproject#22.
+This commit is also related to #17 and fixes #18, #19
+and https://gitlab.example.com/group/otherproject/issues/23.
+```
+
+will close `#18`, `#19`, `#20`, and `#21` in the project this commit is pushed
+to, as well as `#22` and `#23` in group/otherproject. `#17` won't be closed as
+it does not match the pattern. It works with multi-line commit messages as well
+as one-liners when used with `git commit -m`.
+
+[administration docs]: ../../../administration/issue_closing_pattern.md
diff --git a/doc/user/project/repository/web_editor.md b/doc/user/project/repository/web_editor.md
index 7c041d019bb..993c6bfb7e9 100644
--- a/doc/user/project/repository/web_editor.md
+++ b/doc/user/project/repository/web_editor.md
@@ -172,4 +172,4 @@ you commit the changes you will be taken to a new merge request form.
![New file button](basicsimages/file_button.png)
[ce-2808]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2808
-[issue closing pattern]: ../customization/issue_closing.md
+[issue closing pattern]: ../user/project/issues/automatic_issue_closing.md
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index e8ecb8d8fb4..2d9bfbc0629 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -1,5 +1,6 @@
# Workflow
+- [Automatic issue closing](../user/project/issues/automatic_issue_closing.md)
- [Change your time zone](timezone.md)
- [Cycle Analytics](../user/project/cycle_analytics.md)
- [Description templates](../user/project/description_templates.md)
diff --git a/lib/api/award_emoji.rb b/lib/api/award_emoji.rb
index 7c22b17e4e5..2461a783ea8 100644
--- a/lib/api/award_emoji.rb
+++ b/lib/api/award_emoji.rb
@@ -1,12 +1,12 @@
module API
class AwardEmoji < Grape::API
before { authenticate! }
- AWARDABLES = [Issue, MergeRequest]
+ AWARDABLES = %w[issue merge_request snippet]
resource :projects do
AWARDABLES.each do |awardable_type|
- awardable_string = awardable_type.to_s.underscore.pluralize
- awardable_id_string = "#{awardable_type.to_s.underscore}_id"
+ awardable_string = awardable_type.pluralize
+ awardable_id_string = "#{awardable_type}_id"
[ ":id/#{awardable_string}/:#{awardable_id_string}/award_emoji",
":id/#{awardable_string}/:#{awardable_id_string}/notes/:note_id/award_emoji"
@@ -87,9 +87,7 @@ module API
helpers do
def can_read_awardable?
- ability = "read_#{awardable.class.to_s.underscore}".to_sym
-
- can?(current_user, ability, awardable)
+ can?(current_user, read_ability(awardable), awardable)
end
def can_award_awardable?
@@ -100,18 +98,25 @@ module API
@awardable ||=
begin
if params.include?(:note_id)
- noteable.notes.find(params[:note_id])
+ note_id = params.delete(:note_id)
+
+ awardable.notes.find(note_id)
+ elsif params.include?(:issue_id)
+ user_project.issues.find(params[:issue_id])
+ elsif params.include?(:merge_request_id)
+ user_project.merge_requests.find(params[:merge_request_id])
else
- noteable
+ user_project.snippets.find(params[:snippet_id])
end
end
end
- def noteable
- if params.include?(:issue_id)
- user_project.issues.find(params[:issue_id])
+ def read_ability(awardable)
+ case awardable
+ when Note
+ read_ability(awardable.noteable)
else
- user_project.merge_requests.find(params[:merge_request_id])
+ :"read_#{awardable.class.to_s.underscore}"
end
end
end
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
index 3ab99360206..3cd515e4a3a 100644
--- a/lib/gitlab/git.rb
+++ b/lib/gitlab/git.rb
@@ -19,6 +19,8 @@ module Gitlab
end
def committer_hash(email:, name:)
+ return if email.nil? || name.nil?
+
{
email: email,
name: name,
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 925a952156f..88803d76623 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -10,6 +10,7 @@ project_tree:
- milestone:
- :events
- snippets:
+ - :award_emoji
- notes:
:author
- :releases
@@ -66,6 +67,8 @@ excluded_attributes:
- :milestone_id
merge_requests:
- :milestone_id
+ award_emoji:
+ - :awardable_id
methods:
statuses:
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index 2a89159c070..41d263a46a4 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -1,9 +1,9 @@
require 'spec_helper'
describe SnippetsController do
- describe 'GET #show' do
- let(:user) { create(:user) }
+ let(:user) { create(:user) }
+ describe 'GET #show' do
context 'when the personal snippet is private' do
let(:personal_snippet) { create(:personal_snippet, :private, author: user) }
@@ -230,4 +230,33 @@ describe SnippetsController do
end
end
end
+
+ context 'award emoji on snippets' do
+ let(:personal_snippet) { create(:personal_snippet, :public, author: user) }
+ let(:another_user) { create(:user) }
+
+ before do
+ sign_in(another_user)
+ end
+
+ describe 'POST #toggle_award_emoji' do
+ it "toggles the award emoji" do
+ expect do
+ post(:toggle_award_emoji, id: personal_snippet.to_param, name: "thumbsup")
+ end.to change { personal_snippet.award_emoji.count }.from(0).to(1)
+
+ expect(response.status).to eq(200)
+ end
+
+ it "removes the already awarded emoji" do
+ post(:toggle_award_emoji, id: personal_snippet.to_param, name: "thumbsup")
+
+ expect do
+ post(:toggle_award_emoji, id: personal_snippet.to_param, name: "thumbsup")
+ end.to change { personal_snippet.award_emoji.count }.from(1).to(0)
+
+ expect(response.status).to eq(200)
+ end
+ end
+ end
end
diff --git a/spec/factories/group_members.rb b/spec/factories/group_members.rb
index 2044ebec09a..795df5dfda9 100644
--- a/spec/factories/group_members.rb
+++ b/spec/factories/group_members.rb
@@ -3,5 +3,11 @@ FactoryGirl.define do
access_level { GroupMember::OWNER }
group
user
+
+ trait(:guest) { access_level GroupMember::GUEST }
+ trait(:reporter) { access_level GroupMember::REPORTER }
+ trait(:developer) { access_level GroupMember::DEVELOPER }
+ trait(:master) { access_level GroupMember::MASTER }
+ trait(:owner) { access_level GroupMember::OWNER }
end
end
diff --git a/spec/features/projects/import_export/export_file_spec.rb b/spec/features/projects/import_export/export_file_spec.rb
index 7e2c701e401..27c986c5187 100644
--- a/spec/features/projects/import_export/export_file_spec.rb
+++ b/spec/features/projects/import_export/export_file_spec.rb
@@ -15,7 +15,7 @@ feature 'Import/Export - project export integration test', feature: true, js: tr
let(:sensitive_words) { %w[pass secret token key] }
let(:safe_list) do
{
- token: [ProjectHook, Ci::Trigger],
+ token: [ProjectHook, Ci::Trigger, CommitStatus],
key: [Project, Ci::Variable, :yaml_variables]
}
end
diff --git a/spec/lib/gitlab/git_spec.rb b/spec/lib/gitlab/git_spec.rb
new file mode 100644
index 00000000000..219198eff60
--- /dev/null
+++ b/spec/lib/gitlab/git_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+describe Gitlab::Git, lib: true do
+ let(:committer_email) { FFaker::Internet.email }
+
+ # I have to remove periods from the end of the name
+ # This happened when the user's name had a suffix (i.e. "Sr.")
+ # This seems to be what git does under the hood. For example, this commit:
+ #
+ # $ git commit --author='Foo Sr. <foo@example.com>' -m 'Where's my trailing period?'
+ #
+ # results in this:
+ #
+ # $ git show --pretty
+ # ...
+ # Author: Foo Sr <foo@example.com>
+ # ...
+ let(:committer_name) { FFaker::Name.name.chomp("\.") }
+
+ describe 'committer_hash' do
+ it "returns a hash containing the given email and name" do
+ committer_hash = Gitlab::Git::committer_hash(email: committer_email, name: committer_name)
+
+ expect(committer_hash[:email]).to eq(committer_email)
+ expect(committer_hash[:name]).to eq(committer_name)
+ expect(committer_hash[:time]).to be_a(Time)
+ end
+
+ context 'when email is nil' do
+ it "returns nil" do
+ committer_hash = Gitlab::Git::committer_hash(email: nil, name: committer_name)
+
+ expect(committer_hash).to be_nil
+ end
+ end
+
+ context 'when name is nil' do
+ it "returns nil" do
+ committer_hash = Gitlab::Git::committer_hash(email: committer_email, name: nil)
+
+ expect(committer_hash).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 30968ba2d5f..006569254a6 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -13,6 +13,8 @@ issues:
- user_agent_detail
- moved_to
- events
+- merge_requests_closing_issues
+- metrics
events:
- author
- project
@@ -47,6 +49,7 @@ snippets:
- author
- project
- notes
+- award_emoji
releases:
- project
project_members:
@@ -71,6 +74,8 @@ merge_requests:
- merge_request_diffs
- merge_request_diff
- events
+- merge_requests_closing_issues
+- metrics
merge_request_diff:
- merge_request
pipelines:
@@ -101,6 +106,10 @@ protected_branches:
- project
- merge_access_levels
- push_access_levels
+merge_access_levels:
+- protected_branch
+push_access_levels:
+- protected_branch
project:
- taggings
- base_tags
@@ -172,4 +181,7 @@ project:
- triggers
- environments
- deployments
-- project_feature \ No newline at end of file
+- project_feature
+award_emoji:
+- awardable
+- user \ No newline at end of file
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index f2d272ca7e2..8bccd313d6c 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -214,6 +214,7 @@ CommitStatus:
- when
- yaml_variables
- queued_at
+- token
Ci::Variable:
- id
- project_id
@@ -307,4 +308,23 @@ ProjectFeature:
- snippets_access_level
- builds_access_level
- created_at
-- updated_at \ No newline at end of file
+- updated_at
+ProtectedBranch::MergeAccessLevel:
+- id
+- protected_branch_id
+- access_level
+- created_at
+- updated_at
+ProtectedBranch::PushAccessLevel:
+- id
+- protected_branch_id
+- access_level
+- created_at
+- updated_at
+AwardEmoji:
+- id
+- user_id
+- name
+- awardable_type
+- created_at
+- updated_at
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 06feeb1bbba..433aba7747b 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -495,15 +495,62 @@ describe MergeRequest, models: true do
end
describe '#all_pipelines' do
- let!(:pipelines) do
- subject.merge_request_diff.commits.map do |commit|
- create(:ci_empty_pipeline, project: subject.source_project, sha: commit.id, ref: subject.source_branch)
+ shared_examples 'returning pipelines with proper ordering' do
+ let!(:all_pipelines) do
+ subject.all_commits_sha.map do |sha|
+ create(:ci_empty_pipeline,
+ project: subject.source_project,
+ sha: sha,
+ ref: subject.source_branch)
+ end
+ end
+
+ it 'returns all pipelines' do
+ expect(subject.all_pipelines).not_to be_empty
+ expect(subject.all_pipelines).to eq(all_pipelines.reverse)
+ end
+ end
+
+ context 'with single merge_request_diffs' do
+ it_behaves_like 'returning pipelines with proper ordering'
+ end
+
+ context 'with multiple irrelevant merge_request_diffs' do
+ before do
+ subject.update(target_branch: 'markdown')
+ end
+
+ it_behaves_like 'returning pipelines with proper ordering'
+ end
+
+ context 'with unsaved merge request' do
+ subject { build(:merge_request) }
+
+ let!(:pipeline) do
+ create(:ci_empty_pipeline,
+ project: subject.project,
+ sha: subject.diff_head_sha,
+ ref: subject.source_branch)
+ end
+
+ it 'returns pipelines from diff_head_sha' do
+ expect(subject.all_pipelines).to contain_exactly(pipeline)
end
end
+ end
+
+ describe '#all_commits_sha' do
+ let(:all_commits_sha) do
+ subject.merge_request_diffs.flat_map(&:commits).map(&:sha).uniq
+ end
+
+ before do
+ subject.update(target_branch: 'markdown')
+ end
- it 'returns a pipelines from source projects with proper ordering' do
- expect(subject.all_pipelines).not_to be_empty
- expect(subject.all_pipelines).to eq(pipelines.reverse)
+ it 'returns all SHA from all merge_request_diffs' do
+ expect(subject.merge_request_diffs.size).to eq(2)
+ expect(subject.all_commits_sha).to eq(all_commits_sha)
end
end
@@ -701,16 +748,57 @@ describe MergeRequest, models: true do
end
end
- describe "#environments" do
+ describe '#environments' do
let(:project) { create(:project) }
let(:merge_request) { create(:merge_request, source_project: project) }
- it 'selects deployed environments' do
- environments = create_list(:environment, 3, project: project)
- create(:deployment, environment: environments.first, sha: project.commit('master').id)
- create(:deployment, environment: environments.second, sha: project.commit('feature').id)
+ context 'with multiple environments' do
+ let(:environments) { create_list(:environment, 3, project: project) }
- expect(merge_request.environments).to eq [environments.first]
+ before do
+ create(:deployment, environment: environments.first, ref: 'master', sha: project.commit('master').id)
+ create(:deployment, environment: environments.second, ref: 'feature', sha: project.commit('feature').id)
+ end
+
+ it 'selects deployed environments' do
+ expect(merge_request.environments).to contain_exactly(environments.first)
+ end
+ end
+
+ context 'with environments on source project' do
+ let(:source_project) do
+ create(:project) do |fork_project|
+ fork_project.create_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
+ end
+ end
+
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: source_project, source_branch: 'feature',
+ target_project: project)
+ end
+
+ let(:source_environment) { create(:environment, project: source_project) }
+
+ before do
+ create(:deployment, environment: source_environment, ref: 'feature', sha: merge_request.diff_head_sha)
+ end
+
+ it 'selects deployed environments' do
+ expect(merge_request.environments).to contain_exactly(source_environment)
+ end
+
+ context 'with environments on target project' do
+ let(:target_environment) { create(:environment, project: project) }
+
+ before do
+ create(:deployment, environment: target_environment, tag: true, sha: merge_request.diff_head_sha)
+ end
+
+ it 'selects deployed environments' do
+ expect(merge_request.environments).to contain_exactly(source_environment, target_environment)
+ end
+ end
end
context 'without a diff_head_commit' do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index a388ff703a6..83f61f0af0a 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -1647,6 +1647,47 @@ describe Project, models: true do
end
end
+ describe '#environments_for' do
+ let(:project) { create(:project) }
+ let(:environment) { create(:environment, project: project) }
+
+ context 'tagged deployment' do
+ before do
+ create(:deployment, environment: environment, ref: '1.0', tag: true, sha: project.commit.id)
+ end
+
+ it 'returns environment when with_tags is set' do
+ expect(project.environments_for('master', project.commit, with_tags: true)).to contain_exactly(environment)
+ end
+
+ it 'does not return environment when no with_tags is set' do
+ expect(project.environments_for('master', project.commit)).to be_empty
+ end
+
+ it 'does not return environment when commit is not part of deployment' do
+ expect(project.environments_for('master', project.commit('feature'))).to be_empty
+ end
+ end
+
+ context 'branch deployment' do
+ before do
+ create(:deployment, environment: environment, ref: 'master', sha: project.commit.id)
+ end
+
+ it 'returns environment when ref is set' do
+ expect(project.environments_for('master', project.commit)).to contain_exactly(environment)
+ end
+
+ it 'does not environment when ref is different' do
+ expect(project.environments_for('feature', project.commit)).to be_empty
+ end
+
+ it 'does not return environment when commit is not part of deployment' do
+ expect(project.environments_for('master', project.commit('feature'))).to be_empty
+ end
+ end
+ end
+
def enable_lfs
allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
end
diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb
index 5eaf0d3b7a6..f979d66c88c 100644
--- a/spec/models/project_team_spec.rb
+++ b/spec/models/project_team_spec.rb
@@ -73,6 +73,68 @@ describe ProjectTeam, models: true do
end
end
+ describe '#fetch_members' do
+ context 'personal project' do
+ let(:project) { create(:empty_project) }
+
+ it 'returns project members' do
+ user = create(:user)
+ project.team << [user, :guest]
+
+ expect(project.team.members).to contain_exactly(user)
+ end
+
+ it 'returns project members of a specified level' do
+ user = create(:user)
+ project.team << [user, :reporter]
+
+ expect(project.team.guests).to be_empty
+ expect(project.team.reporters).to contain_exactly(user)
+ end
+
+ it 'returns invited members of a group' do
+ group_member = create(:group_member)
+
+ project.project_group_links.create!(
+ group: group_member.group,
+ group_access: Gitlab::Access::GUEST
+ )
+
+ expect(project.team.members).to contain_exactly(group_member.user)
+ end
+
+ it 'returns invited members of a group of a specified level' do
+ group_member = create(:group_member)
+
+ project.project_group_links.create!(
+ group: group_member.group,
+ group_access: Gitlab::Access::REPORTER
+ )
+
+ expect(project.team.guests).to be_empty
+ expect(project.team.reporters).to contain_exactly(group_member.user)
+ end
+ end
+
+ context 'group project' do
+ let(:group) { create(:group) }
+ let(:project) { create(:empty_project, group: group) }
+
+ it 'returns project members' do
+ group_member = create(:group_member, group: group)
+
+ expect(project.team.members).to contain_exactly(group_member.user)
+ end
+
+ it 'returns project members of a specified level' do
+ group_member = create(:group_member, :reporter, group: group)
+
+ expect(project.team.guests).to be_empty
+ expect(project.team.reporters).to contain_exactly(group_member.user)
+ end
+ end
+ end
+
describe '#find_member' do
context 'personal project' do
let(:project) { create(:empty_project) }
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index 0621c6a06ce..e6bc5296398 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -9,12 +9,14 @@ describe Snippet, models: true do
it { is_expected.to include_module(Participable) }
it { is_expected.to include_module(Referable) }
it { is_expected.to include_module(Sortable) }
+ it { is_expected.to include_module(Awardable) }
end
describe 'associations' do
it { is_expected.to belong_to(:author).class_name('User') }
it { is_expected.to belong_to(:project) }
it { is_expected.to have_many(:notes).dependent(:destroy) }
+ it { is_expected.to have_many(:award_emoji).dependent(:destroy) }
end
describe 'validation' do
diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb
index 981a6791881..5ad4fc4865a 100644
--- a/spec/requests/api/award_emoji_spec.rb
+++ b/spec/requests/api/award_emoji_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe API::API, api: true do
include ApiHelpers
let(:user) { create(:user) }
- let!(:project) { create(:project) }
+ let!(:project) { create(:empty_project) }
let(:issue) { create(:issue, project: project) }
let!(:award_emoji) { create(:award_emoji, awardable: issue, user: user) }
let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
@@ -39,6 +39,19 @@ describe API::API, api: true do
end
end
+ context 'on a snippet' do
+ let(:snippet) { create(:project_snippet, :public, project: project) }
+ let!(:award) { create(:award_emoji, awardable: snippet) }
+
+ it 'returns the awarded emoji' do
+ get api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['name']).to eq(award.name)
+ end
+ end
+
context 'when the user has no access' do
it 'returns a status code 404' do
user1 = create(:user)
@@ -91,6 +104,20 @@ describe API::API, api: true do
end
end
+ context 'on a snippet' do
+ let(:snippet) { create(:project_snippet, :public, project: project) }
+ let!(:award) { create(:award_emoji, awardable: snippet) }
+
+ it 'returns the awarded emoji' do
+ get api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji/#{award.id}", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['name']).to eq(award.name)
+ expect(json_response['awardable_id']).to eq(snippet.id)
+ expect(json_response['awardable_type']).to eq("Snippet")
+ end
+ end
+
context 'when the user has no access' do
it 'returns a status code 404' do
user1 = create(:user)
@@ -160,6 +187,18 @@ describe API::API, api: true do
end
end
end
+
+ context 'on a snippet' do
+ it 'creates a new award emoji' do
+ snippet = create(:project_snippet, :public, project: project)
+
+ post api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji", user), name: 'blowfish'
+
+ expect(response).to have_http_status(201)
+ expect(json_response['name']).to eq('blowfish')
+ expect(json_response['user']['username']).to eq(user.username)
+ end
+ end
end
describe "POST /projects/:id/awardable/:awardable_id/notes/:note_id/award_emoji" do
@@ -229,6 +268,19 @@ describe API::API, api: true do
expect(response).to have_http_status(404)
end
end
+
+ context 'when the awardable is a Snippet' do
+ let(:snippet) { create(:project_snippet, :public, project: project) }
+ let!(:award) { create(:award_emoji, awardable: snippet, user: user) }
+
+ it 'deletes the award' do
+ expect do
+ delete api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji/#{award.id}", user)
+ end.to change { snippet.award_emoji.count }.from(1).to(0)
+
+ expect(response).to have_http_status(200)
+ end
+ end
end
describe 'DELETE /projects/:id/awardable/:awardable_id/award_emoji/:award_emoji_id' do
diff --git a/spec/views/projects/merge_requests/_heading.html.haml_spec.rb b/spec/views/projects/merge_requests/_heading.html.haml_spec.rb
index 733b2dfa7ff..21f49d396e7 100644
--- a/spec/views/projects/merge_requests/_heading.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/_heading.html.haml_spec.rb
@@ -15,6 +15,8 @@ describe 'projects/merge_requests/widget/_heading' do
assign(:merge_request, merge_request)
assign(:project, project)
+ allow(view).to receive(:can?).and_return(true)
+
render
end