summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.rubocop.yml16
-rw-r--r--.rubocop_todo.yml17
-rw-r--r--app/assets/javascripts/api.js2
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_plan_component.js.es66
-rw-r--r--app/assets/javascripts/profile/profile.js.es61
-rw-r--r--app/assets/stylesheets/framework/animations.scss3
-rw-r--r--app/assets/stylesheets/framework/avatar.scss2
-rw-r--r--app/assets/stylesheets/pages/cycle_analytics.scss6
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss8
-rw-r--r--app/assets/stylesheets/pages/wiki.scss30
-rw-r--r--app/controllers/dashboard/projects_controller.rb23
-rw-r--r--app/controllers/profiles/notifications_controller.rb2
-rw-r--r--app/controllers/projects/wikis_controller.rb3
-rw-r--r--app/helpers/builds_helper.rb2
-rw-r--r--app/helpers/preferences_helper.rb2
-rw-r--r--app/helpers/wiki_helper.rb13
-rw-r--r--app/models/event.rb4
-rw-r--r--app/models/merge_request.rb4
-rw-r--r--app/models/repository.rb8
-rw-r--r--app/models/user.rb7
-rw-r--r--app/models/wiki_directory.rb18
-rw-r--r--app/models/wiki_page.rb39
-rw-r--r--app/services/merge_requests/refresh_service.rb6
-rw-r--r--app/services/notification_service.rb11
-rw-r--r--app/views/profiles/notifications/show.html.haml5
-rw-r--r--app/views/projects/diffs/_file_header.html.haml6
-rw-r--r--app/views/projects/wikis/_new.html.haml4
-rw-r--r--app/views/projects/wikis/_pages_wiki_page.html.haml5
-rw-r--r--app/views/projects/wikis/_sidebar.html.haml6
-rw-r--r--app/views/projects/wikis/_sidebar_wiki_page.html.haml3
-rw-r--r--app/views/projects/wikis/_wiki_directory.html.haml4
-rw-r--r--app/views/projects/wikis/_wiki_page.html.haml1
-rw-r--r--app/views/projects/wikis/pages.html.haml10
-rw-r--r--app/views/projects/wikis/show.html.haml4
-rw-r--r--changelogs/unreleased/22818-licence-gitignore-and-yml-endpoints-removal.yml4
-rw-r--r--changelogs/unreleased/23535-folders-in-wiki-repository.yml4
-rw-r--r--changelogs/unreleased/24333-close-issues-with-merge-request-title-ui.yml5
-rw-r--r--changelogs/unreleased/27395-reduce-group-activity-sql-queries-2.yml4
-rw-r--r--changelogs/unreleased/27395-reduce-group-activity-sql-queries.yml4
-rw-r--r--changelogs/unreleased/27480-deploy_keys_should_not_show_up_in_users_keys_list.yml4
-rw-r--r--changelogs/unreleased/27939-fix-current-build-arrow.yml4
-rw-r--r--changelogs/unreleased/27947-missing-margin-between-loading-icon-and-text-in-merge-request-widget.yml4
-rw-r--r--changelogs/unreleased/28032-tooltips-file-name.yml5
-rw-r--r--changelogs/unreleased/dynamic-header-fixture.yml4
-rw-r--r--changelogs/unreleased/fix-cycle-analytics-events-limit.yml4
-rw-r--r--changelogs/unreleased/merge-request-tabs-fixture.yml4
-rw-r--r--changelogs/unreleased/new-branch-fixture.yml4
-rw-r--r--changelogs/unreleased/option-to-be-notified-of-own-activity.yml4
-rw-r--r--changelogs/unreleased/quick-submit-fixture.yml4
-rw-r--r--changelogs/unreleased/sh-add-labels-index.yml4
-rw-r--r--config/webpack.config.js3
-rw-r--r--db/migrate/20170123061730_add_notified_of_own_activity_to_users.rb14
-rw-r--r--db/migrate/20170210062829_add_index_to_labels_for_title_and_project.rb12
-rw-r--r--db/schema.rb3
-rw-r--r--doc/api/v3_to_v4.md10
-rw-r--r--lib/api/api.rb1
-rw-r--r--lib/api/templates.rb95
-rw-r--r--lib/api/v3/templates.rb122
-rw-r--r--lib/gitlab/cycle_analytics/base_event_fetcher.rb4
-rw-r--r--lib/gitlab/diff/parser.rb2
-rw-r--r--spec/controllers/profiles/keys_controller_spec.rb19
-rw-r--r--spec/controllers/profiles/notifications_controller_spec.rb45
-rw-r--r--spec/factories/keys.rb7
-rw-r--r--spec/factories/wiki_directories.rb6
-rw-r--r--spec/features/issues/filtered_search/filter_issues_spec.rb33
-rw-r--r--spec/features/merge_requests/closes_issues_spec.rb32
-rw-r--r--spec/features/profiles/user_changes_notified_of_own_activity_spec.rb32
-rw-r--r--spec/features/projects/builds_spec.rb4
-rw-r--r--spec/helpers/wiki_helper_spec.rb21
-rw-r--r--spec/javascripts/behaviors/quick_submit_spec.js46
-rw-r--r--spec/javascripts/fixtures/behaviors/quick_submit.html.haml6
-rw-r--r--spec/javascripts/fixtures/branches.rb28
-rw-r--r--spec/javascripts/fixtures/header.html.haml35
-rw-r--r--spec/javascripts/fixtures/merge_request_tabs.html.haml22
-rw-r--r--spec/javascripts/fixtures/merge_requests.rb36
-rw-r--r--spec/javascripts/fixtures/new_branch.html.haml4
-rw-r--r--spec/javascripts/header_spec.js2
-rw-r--r--spec/javascripts/merge_request_tabs_spec.js4
-rw-r--r--spec/javascripts/new_branch_spec.js4
-rw-r--r--spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb45
-rw-r--r--spec/lib/gitlab/regex_spec.rb71
-rw-r--r--spec/models/repository_spec.rb11
-rw-r--r--spec/models/user_spec.rb29
-rw-r--r--spec/models/wiki_directory_spec.rb44
-rw-r--r--spec/models/wiki_page_spec.rb105
-rw-r--r--spec/requests/api/templates_spec.rb58
-rw-r--r--spec/requests/api/v3/templates_spec.rb203
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb41
-rw-r--r--spec/services/notification_service_spec.rb59
89 files changed, 1331 insertions, 334 deletions
diff --git a/.rubocop.yml b/.rubocop.yml
index d30846e6e0b..b093d4d25d4 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -339,6 +339,10 @@ Style/OpMethod:
Style/ParenthesesAroundCondition:
Enabled: true
+# Checks for an obsolete RuntimeException argument in raise/fail.
+Style/RedundantException:
+ Enabled: true
+
# Checks for parentheses that seem not to serve any purpose.
Style/RedundantParentheses:
Enabled: true
@@ -568,6 +572,10 @@ Lint/ElseLayout:
Lint/EmptyEnsure:
Enabled: true
+# Checks for the presence of `when` branches without a body.
+Lint/EmptyWhen:
+ Enabled: true
+
# Align ends correctly.
Lint/EndAlignment:
Enabled: true
@@ -769,6 +777,10 @@ 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 the tested class or
# module.
RSpec/DescribeClass:
@@ -797,6 +809,10 @@ RSpec/ExampleWording:
not: does not
IgnoredWords: []
+# Checks for `expect(...)` calls containing literal values.
+RSpec/ExpectActual:
+ Enabled: true
+
# Checks the file and folder naming of the spec file.
RSpec/FilePath:
Enabled: false
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index d581610162f..648b3fc49d2 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -21,10 +21,6 @@ Lint/AmbiguousRegexpLiteral:
Lint/AssignmentInCondition:
Enabled: false
-# Offense count: 1
-Lint/EmptyWhen:
- Enabled: false
-
# Offense count: 20
Lint/HandleExceptions:
Enabled: false
@@ -80,19 +76,11 @@ Performance/RedundantMatch:
Performance/RedundantMerge:
Enabled: false
-# Offense count: 7
-RSpec/BeEql:
- Enabled: false
-
# Offense count: 15
# Configuration parameters: CustomIncludeMethods.
RSpec/EmptyExampleGroup:
Enabled: false
-# Offense count: 24
-RSpec/ExpectActual:
- Enabled: false
-
# Offense count: 58
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: implicit, each, example
@@ -424,11 +412,6 @@ Style/RaiseArgs:
Style/RedundantBegin:
Enabled: false
-# Offense count: 1
-# Cop supports --auto-correct.
-Style/RedundantException:
- Enabled: false
-
# Offense count: 29
# Cop supports --auto-correct.
Style/RedundantFreeze:
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index b4a8c827d7f..84bbe90f3b1 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -11,7 +11,7 @@
licensePath: "/api/:version/templates/licenses/:key",
gitignorePath: "/api/:version/templates/gitignores/:key",
gitlabCiYmlPath: "/api/:version/templates/gitlab_ci_ymls/:key",
- dockerfilePath: "/api/:version/dockerfiles/:key",
+ dockerfilePath: "/api/:version/templates/dockerfiles/:key",
issuableTemplatePath: "/:namespace_path/:project_path/templates/:type/:key",
group: function(group_id, callback) {
var url = Api.buildUrl(Api.groupPath)
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js.es6 b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js.es6
index 513298ba4e7..8652479e7bf 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js.es6
+++ b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js.es6
@@ -13,6 +13,12 @@
<div>
<div class="events-description">
{{ stage.description }}
+ <span v-if="items.length === 50" class="events-info pull-right">
+ <i class="fa fa-warning has-tooltip"
+ title="Limited to showing 50 events at most"
+ data-placement="top"></i>
+ Showing 50 events
+ </span>
</div>
<ul class="stage-event-list">
<li v-for="commit in items" class="stage-event-item">
diff --git a/app/assets/javascripts/profile/profile.js.es6 b/app/assets/javascripts/profile/profile.js.es6
index 5aec9c813fe..81374296522 100644
--- a/app/assets/javascripts/profile/profile.js.es6
+++ b/app/assets/javascripts/profile/profile.js.es6
@@ -25,6 +25,7 @@
bindEvents() {
$('.js-preferences-form').on('change.preference', 'input[type=radio]', this.submitForm);
$('#user_notification_email').on('change', this.submitForm);
+ $('#user_notified_of_own_activity').on('change', this.submitForm);
$('.update-username').on('ajax:before', this.beforeUpdateUsername);
$('.update-username').on('ajax:complete', this.afterUpdateUsername);
$('.update-notifications').on('ajax:success', this.onUpdateNotifs);
diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss
index 0a26b4c6a8c..0ca5a9343f7 100644
--- a/app/assets/stylesheets/framework/animations.scss
+++ b/app/assets/stylesheets/framework/animations.scss
@@ -128,8 +128,7 @@
.note-action-button .link-highlight,
.toolbar-btn,
-.dropdown-toggle-caret,
-.fa:not(.fa-bell) {
+.dropdown-toggle-caret {
@include transition(color);
}
diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index 1d59700543c..3f5b78ed445 100644
--- a/app/assets/stylesheets/framework/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -28,6 +28,8 @@
.avatar {
@extend .avatar-circle;
+ @include transition-property(none);
+
width: 40px;
height: 40px;
padding: 0;
diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss
index cda069e6c0e..5b777953fb0 100644
--- a/app/assets/stylesheets/pages/cycle_analytics.scss
+++ b/app/assets/stylesheets/pages/cycle_analytics.scss
@@ -284,7 +284,11 @@
.events-description {
line-height: 65px;
- padding-left: $gl-padding;
+ padding: 0 $gl-padding;
+ }
+
+ .events-info {
+ color: $gl-text-color-secondary;
}
}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index c02a65b0903..0b0c4bc130d 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -85,14 +85,18 @@
-webkit-align-items: center;
align-items: center;
+ i,
+ svg {
+ margin-right: 8px;
+ }
+
svg {
- margin-right: 4px;
position: relative;
top: 1px;
overflow: visible;
}
- &> span {
+ & > span {
padding-right: 4px;
}
diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss
index d5783e14b21..9bc47bbe173 100644
--- a/app/assets/stylesheets/pages/wiki.scss
+++ b/app/assets/stylesheets/pages/wiki.scss
@@ -1,3 +1,11 @@
+.new-wiki-page {
+ .new-wiki-page-slug-tip {
+ display: inline-block;
+ max-width: 100%;
+ margin-top: 5px;
+ }
+}
+
.title .edit-wiki-header {
width: 780px;
margin-left: auto;
@@ -9,12 +17,18 @@
@extend .top-area;
position: relative;
+ .wiki-breadcrumb {
+ border-bottom: 1px solid $white-normal;
+ padding: 11px 0;
+ }
+
.wiki-page-title {
margin: 0;
font-size: 22px;
}
.wiki-last-edit-by {
+ display: block;
color: $gl-text-color-secondary;
strong {
@@ -121,6 +135,10 @@
margin: 5px 0 10px;
}
+ ul.wiki-pages ul {
+ padding-left: 15px;
+ }
+
.wiki-sidebar-header {
padding: 0 $gl-padding $gl-padding;
@@ -129,3 +147,15 @@
}
}
}
+
+ul.wiki-pages-list.content-list {
+ & ul {
+ list-style: none;
+ margin-left: 0;
+ padding-left: 15px;
+ }
+
+ & ul li {
+ padding: 5px 0;
+ }
+}
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index 3ba8c2f8bb9..325ae565537 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -1,19 +1,14 @@
class Dashboard::ProjectsController < Dashboard::ApplicationController
include FilterProjects
- before_action :event_filter
-
def index
- @projects = current_user.authorized_projects.sorted_by_activity
- @projects = filter_projects(@projects)
- @projects = @projects.includes(:namespace)
+ @projects = load_projects(current_user.authorized_projects)
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page])
respond_to do |format|
format.html { @last_push = current_user.recent_push }
format.atom do
- event_filter
load_events
render layout: false
end
@@ -26,9 +21,8 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
end
def starred
- @projects = current_user.viewable_starred_projects.sorted_by_activity
- @projects = filter_projects(@projects)
- @projects = @projects.includes(:namespace, :forked_from_project, :tags)
+ @projects = load_projects(current_user.viewable_starred_projects)
+ @projects = @projects.includes(:forked_from_project, :tags)
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page])
@@ -37,7 +31,6 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
respond_to do |format|
format.html
-
format.json do
render json: {
html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
@@ -48,9 +41,15 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
private
+ def load_projects(base_scope)
+ projects = base_scope.sorted_by_activity.includes(:namespace)
+
+ filter_projects(projects)
+ end
+
def load_events
- @events = Event.in_projects(@projects)
- @events = @event_filter.apply_filter(@events).with_associations
+ @events = Event.in_projects(load_projects(current_user.authorized_projects))
+ @events = event_filter.apply_filter(@events).with_associations
@events = @events.limit(20).offset(params[:offset] || 0)
end
end
diff --git a/app/controllers/profiles/notifications_controller.rb b/app/controllers/profiles/notifications_controller.rb
index b8b71d295f6..a271e2dfc4b 100644
--- a/app/controllers/profiles/notifications_controller.rb
+++ b/app/controllers/profiles/notifications_controller.rb
@@ -17,6 +17,6 @@ class Profiles::NotificationsController < Profiles::ApplicationController
end
def user_params
- params.require(:user).permit(:notification_email)
+ params.require(:user).permit(:notification_email, :notified_of_own_activity)
end
end
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index 8e683931b2e..0faa71c4d7d 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -8,6 +8,7 @@ class Projects::WikisController < Projects::ApplicationController
def pages
@wiki_pages = Kaminari.paginate_array(@project_wiki.pages).page(params[:page])
+ @wiki_entries = WikiPage.group_by_directory(@wiki_pages)
end
def show
@@ -116,7 +117,7 @@ class Projects::WikisController < Projects::ApplicationController
# Call #wiki to make sure the Wiki Repo is initialized
@project_wiki.wiki
- @sidebar_wiki_pages = @project_wiki.pages.first(15)
+ @sidebar_wiki_entries = WikiPage.group_by_directory(@project_wiki.pages.first(15))
rescue ProjectWiki::CouldNotCreateWikiError
flash[:notice] = "Could not create Wiki Repository at this time. Please try again later."
redirect_to project_path(@project)
diff --git a/app/helpers/builds_helper.rb b/app/helpers/builds_helper.rb
index 9fc69e12266..ff937b5ebd2 100644
--- a/app/helpers/builds_helper.rb
+++ b/app/helpers/builds_helper.rb
@@ -1,7 +1,7 @@
module BuildsHelper
def sidebar_build_class(build, current_build)
build_class = ''
- build_class += ' active' if build == current_build
+ build_class += ' active' if build.id === current_build.id
build_class += ' retried' if build.retried?
build_class
end
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index 6e68aad4cb7..dd0a4ea03f0 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -23,7 +23,7 @@ module PreferencesHelper
if defined.size != DASHBOARD_CHOICES.size
# Ensure that anyone adding new options updates this method too
- raise RuntimeError, "`User` defines #{defined.size} dashboard choices," +
+ raise "`User` defines #{defined.size} dashboard choices," \
" but `DASHBOARD_CHOICES` defined #{DASHBOARD_CHOICES.size}."
else
defined.map do |key, _|
diff --git a/app/helpers/wiki_helper.rb b/app/helpers/wiki_helper.rb
new file mode 100644
index 00000000000..3e3f6246fc5
--- /dev/null
+++ b/app/helpers/wiki_helper.rb
@@ -0,0 +1,13 @@
+module WikiHelper
+ # Produces a pure text breadcrumb for a given page.
+ #
+ # page_slug - The slug of a WikiPage object.
+ #
+ # Returns a String composed of the capitalized name of each directory and the
+ # capitalized name of the page itself.
+ def breadcrumb(page_slug)
+ page_slug.split('/').
+ map { |dir_or_page| WikiPage.unhyphenize(dir_or_page).capitalize }.
+ join(' / ')
+ end
+end
diff --git a/app/models/event.rb b/app/models/event.rb
index 2662f170765..cf89ac5207f 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -36,10 +36,10 @@ class Event < ActiveRecord::Base
scope :code_push, -> { where(action: PUSHED) }
scope :in_projects, ->(projects) do
- where(project_id: projects.map(&:id)).recent
+ where(project_id: projects).recent
end
- scope :with_associations, -> { includes(project: :namespace) }
+ scope :with_associations, -> { includes(:author, :project, project: :namespace).preload(:target) }
scope :for_milestone_id, ->(milestone_id) { where(target_type: "Milestone", target_id: milestone_id) }
class << self
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index c0d4dd0197f..38646eba3ac 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -561,7 +561,7 @@ class MergeRequest < ActiveRecord::Base
# Return the set of issues that will be closed if this merge request is accepted.
def closes_issues(current_user = self.author)
if target_branch == project.default_branch
- messages = [description]
+ messages = [title, description]
messages.concat(commits.map(&:safe_message)) if merge_request_diff
Gitlab::ClosingIssueExtractor.new(project, current_user).
@@ -575,7 +575,7 @@ class MergeRequest < ActiveRecord::Base
return [] unless target_branch == project.default_branch
ext = Gitlab::ReferenceExtractor.new(project, current_user)
- ext.analyze(description)
+ ext.analyze("#{title}\n#{description}")
ext.issues - closes_issues(current_user)
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index d2d92a064a4..56c582cd9be 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -1230,6 +1230,14 @@ class Repository
action[:content]
end
+ detect = CharlockHolmes::EncodingDetector.new.detect(content) if content
+
+ unless detect && detect[:type] == :binary
+ # When writing to the repo directly as we are doing here,
+ # the `core.autocrlf` config isn't taken into account.
+ content.gsub!("\r\n", "\n") if self.autocrlf
+ end
+
oid = rugged.write(content, :blob)
index.add(path: path, oid: oid, mode: mode)
diff --git a/app/models/user.rb b/app/models/user.rb
index 867e61af56a..1649bf04eaa 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -51,7 +51,12 @@ class User < ActiveRecord::Base
has_one :namespace, -> { where type: nil }, dependent: :destroy, foreign_key: :owner_id
# Profile
- has_many :keys, dependent: :destroy
+ has_many :keys, -> do
+ type = Key.arel_table[:type]
+ where(type.not_eq('DeployKey').or(type.eq(nil)))
+ end, dependent: :destroy
+ has_many :deploy_keys, -> { where(type: 'DeployKey') }, dependent: :destroy
+
has_many :emails, dependent: :destroy
has_many :personal_access_tokens, dependent: :destroy
has_many :identities, dependent: :destroy, autosave: true
diff --git a/app/models/wiki_directory.rb b/app/models/wiki_directory.rb
new file mode 100644
index 00000000000..9340fc2dbbe
--- /dev/null
+++ b/app/models/wiki_directory.rb
@@ -0,0 +1,18 @@
+class WikiDirectory
+ include ActiveModel::Validations
+
+ attr_accessor :slug, :pages
+
+ validates :slug, presence: true
+
+ def initialize(slug, pages = [])
+ @slug = slug
+ @pages = pages
+ end
+
+ # Relative path to the partial to be used when rendering collections
+ # of this object.
+ def to_partial_path
+ 'projects/wikis/wiki_directory'
+ end
+end
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index e93d0eab6d8..6347b274341 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -12,6 +12,32 @@ class WikiPage
ActiveModel::Name.new(self, nil, 'wiki')
end
+ # Sorts and groups pages by directory.
+ #
+ # pages - an array of WikiPage objects.
+ #
+ # Returns an array of WikiPage and WikiDirectory objects. The entries are
+ # sorted by alphabetical order (directories and pages inside each directory).
+ # Pages at the root level come before everything.
+ def self.group_by_directory(pages)
+ return [] if pages.blank?
+
+ pages.sort_by { |page| [page.directory, page.slug] }.
+ group_by(&:directory).
+ map do |dir, pages|
+ if dir.present?
+ WikiDirectory.new(dir, pages)
+ else
+ pages
+ end
+ end.
+ flatten
+ end
+
+ def self.unhyphenize(name)
+ name.gsub(/-+/, ' ')
+ end
+
def to_key
[:slug]
end
@@ -56,7 +82,7 @@ class WikiPage
# The formatted title of this page.
def title
if @attributes[:title]
- @attributes[:title].gsub(/-+/, ' ')
+ self.class.unhyphenize(@attributes[:title])
else
""
end
@@ -72,6 +98,11 @@ class WikiPage
@attributes[:content] ||= @page&.text_data
end
+ # The hierarchy of the directory this page is contained in.
+ def directory
+ wiki.page_title_and_dir(slug).last
+ end
+
# The processed/formatted content of this page.
def formatted_content
@attributes[:formatted_content] ||= @page&.formatted_data
@@ -170,6 +201,12 @@ class WikiPage
end
end
+ # Relative path to the partial to be used when rendering collections
+ # of this object.
+ def to_partial_path
+ 'projects/wikis/wiki_page'
+ end
+
private
def set_attributes
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index b4bfb0e5e8c..581d18032e6 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -144,7 +144,11 @@ module MergeRequests
return unless @commits.present?
merge_requests_for_source_branch.each do |merge_request|
- wip_commit = @commits.detect(&:work_in_progress?)
+ commit_shas = merge_request.commits_sha
+
+ wip_commit = @commits.detect do |commit|
+ commit.work_in_progress? && commit_shas.include?(commit.sha)
+ end
if wip_commit && !merge_request.work_in_progress?
merge_request.update(title: merge_request.wip_title)
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index b2cc39763f3..3734e3c4253 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -217,7 +217,7 @@ class NotificationService
recipients = reject_unsubscribed_users(recipients, note.noteable)
recipients = reject_users_without_access(recipients, note.noteable)
- recipients.delete(note.author)
+ recipients.delete(note.author) unless note.author.notified_of_own_activity?
recipients = recipients.uniq
notify_method = "note_#{note.to_ability_name}_email".to_sym
@@ -327,8 +327,9 @@ class NotificationService
recipients ||= build_recipients(
pipeline,
pipeline.project,
- nil, # The acting user, who won't be added to recipients
- action: pipeline.status).map(&:notification_email)
+ pipeline.user,
+ action: pipeline.status,
+ skip_current_user: false).map(&:notification_email)
if recipients.any?
mailer.public_send(email_template, pipeline, recipients).deliver_later
@@ -627,7 +628,7 @@ class NotificationService
recipients = reject_unsubscribed_users(recipients, target)
recipients = reject_users_without_access(recipients, target)
- recipients.delete(current_user) if skip_current_user
+ recipients.delete(current_user) if skip_current_user && !current_user.notified_of_own_activity?
recipients.uniq
end
@@ -636,7 +637,7 @@ class NotificationService
recipients = add_labels_subscribers([], project, target, labels: labels)
recipients = reject_unsubscribed_users(recipients, target)
recipients = reject_users_without_access(recipients, target)
- recipients.delete(current_user)
+ recipients.delete(current_user) unless current_user.notified_of_own_activity?
recipients.uniq
end
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index 5c5e5940365..51c4e8e5a73 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -34,6 +34,11 @@
.clearfix
+ = form_for @user, url: profile_notifications_path, method: :put do |f|
+ %label{ for: 'user_notified_of_own_activity' }
+ = f.check_box :notified_of_own_activity
+ %span Receive notifications about your own activity
+
%hr
%h5
Groups (#{@group_notifications.count})
diff --git a/app/views/projects/diffs/_file_header.html.haml b/app/views/projects/diffs/_file_header.html.haml
index 5b09b6907ab..1dbfe830d52 100644
--- a/app/views/projects/diffs/_file_header.html.haml
+++ b/app/views/projects/diffs/_file_header.html.haml
@@ -10,13 +10,13 @@
- if diff_file.renamed_file
- old_path, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
- %strong.file-title-name.has-tooltip{ data: { title: old_path } }
+ %strong.file-title-name.has-tooltip{ data: { title: old_path, container: 'body' } }
= old_path
&rarr;
- %strong.file-title-name.has-tooltip{ data: { title: new_path } }
+ %strong.file-title-name.has-tooltip{ data: { title: new_path, container: 'body' } }
= new_path
- else
- %strong.file-title-name.has-tooltip{ data: { title: diff_file.new_path } }
+ %strong.file-title-name.has-tooltip{ data: { title: diff_file.new_path, container: 'body' } }
= diff_file.new_path
- if diff_file.deleted_file
deleted
diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml
index c74f53b4c39..3d33679f07d 100644
--- a/app/views/projects/wikis/_new.html.haml
+++ b/app/views/projects/wikis/_new.html.haml
@@ -13,5 +13,9 @@
= label_tag :new_wiki_path do
%span Page slug
= text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => namespace_project_wikis_path(@project.namespace, @project), autofocus: true
+ %span.new-wiki-page-slug-tip
+ = icon('lightbulb-o')
+ Tip: You can specify the full path for the new file.
+ We will automatically create any missing directories.
.form-actions
= button_tag 'Create Page', class: 'build-new-wiki btn btn-create'
diff --git a/app/views/projects/wikis/_pages_wiki_page.html.haml b/app/views/projects/wikis/_pages_wiki_page.html.haml
new file mode 100644
index 00000000000..6298cf6c8da
--- /dev/null
+++ b/app/views/projects/wikis/_pages_wiki_page.html.haml
@@ -0,0 +1,5 @@
+%li
+ = link_to wiki_page.title, namespace_project_wiki_path(@project.namespace, @project, wiki_page)
+ %small (#{wiki_page.format})
+ .pull-right
+ %small Last edited #{time_ago_with_tooltip(wiki_page.commit.authored_date)}
diff --git a/app/views/projects/wikis/_sidebar.html.haml b/app/views/projects/wikis/_sidebar.html.haml
index f115f60088c..8c582f747b3 100644
--- a/app/views/projects/wikis/_sidebar.html.haml
+++ b/app/views/projects/wikis/_sidebar.html.haml
@@ -12,10 +12,8 @@
.blocks-container
.block.block-first
%ul.wiki-pages
- - @sidebar_wiki_pages.each do |wiki_page|
- %li{ class: params[:id] == wiki_page.slug ? 'active' : '' }
- = link_to namespace_project_wiki_path(@project.namespace, @project, wiki_page) do
- = wiki_page.title.capitalize
+ = render @sidebar_wiki_entries, context: 'sidebar'
+
.block
= link_to namespace_project_wikis_pages_path(@project.namespace, @project), class: 'btn btn-block' do
More Pages
diff --git a/app/views/projects/wikis/_sidebar_wiki_page.html.haml b/app/views/projects/wikis/_sidebar_wiki_page.html.haml
new file mode 100644
index 00000000000..eb9bd14920d
--- /dev/null
+++ b/app/views/projects/wikis/_sidebar_wiki_page.html.haml
@@ -0,0 +1,3 @@
+%li{ class: params[:id] == wiki_page.slug ? 'active' : '' }
+ = link_to namespace_project_wiki_path(@project.namespace, @project, wiki_page) do
+ = wiki_page.title.capitalize
diff --git a/app/views/projects/wikis/_wiki_directory.html.haml b/app/views/projects/wikis/_wiki_directory.html.haml
new file mode 100644
index 00000000000..0e5f32ed859
--- /dev/null
+++ b/app/views/projects/wikis/_wiki_directory.html.haml
@@ -0,0 +1,4 @@
+%li
+ = wiki_directory.slug
+ %ul
+ = render wiki_directory.pages, context: context
diff --git a/app/views/projects/wikis/_wiki_page.html.haml b/app/views/projects/wikis/_wiki_page.html.haml
new file mode 100644
index 00000000000..c84d06dad02
--- /dev/null
+++ b/app/views/projects/wikis/_wiki_page.html.haml
@@ -0,0 +1 @@
+= render "#{context}_wiki_page", wiki_page: wiki_page
diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml
index e1eaffc6884..5fba2b1a5ae 100644
--- a/app/views/projects/wikis/pages.html.haml
+++ b/app/views/projects/wikis/pages.html.haml
@@ -13,11 +13,7 @@
= icon('cloud-download')
Clone repository
- %ul.content-list
- - @wiki_pages.each do |wiki_page|
- %li
- = link_to wiki_page.title, namespace_project_wiki_path(@project.namespace, @project, wiki_page)
- %small (#{wiki_page.format})
- .pull-right
- %small Last edited #{time_ago_with_tooltip(wiki_page.commit.authored_date)}
+ %ul.wiki-pages-list.content-list
+ = render @wiki_entries, context: 'pages'
+
= paginate @wiki_pages, theme: 'gitlab'
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index 1b6dceee241..3609461b721 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -6,9 +6,11 @@
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
+ .wiki-breadcrumb
+ %span= breadcrumb(@page.slug)
+
.nav-text
%h2.wiki-page-title= @page.title.capitalize
-
%span.wiki-last-edit-by
Last edited by
%strong
diff --git a/changelogs/unreleased/22818-licence-gitignore-and-yml-endpoints-removal.yml b/changelogs/unreleased/22818-licence-gitignore-and-yml-endpoints-removal.yml
new file mode 100644
index 00000000000..05d5993ddf3
--- /dev/null
+++ b/changelogs/unreleased/22818-licence-gitignore-and-yml-endpoints-removal.yml
@@ -0,0 +1,4 @@
+---
+title: V3 deprecated templates endpoints removal
+merge_request: 8853
+author:
diff --git a/changelogs/unreleased/23535-folders-in-wiki-repository.yml b/changelogs/unreleased/23535-folders-in-wiki-repository.yml
new file mode 100644
index 00000000000..05212b608d4
--- /dev/null
+++ b/changelogs/unreleased/23535-folders-in-wiki-repository.yml
@@ -0,0 +1,4 @@
+---
+title: Show directory hierarchy when listing wiki pages
+merge_request: 8133
+author: Alex Braha Stoll
diff --git a/changelogs/unreleased/24333-close-issues-with-merge-request-title-ui.yml b/changelogs/unreleased/24333-close-issues-with-merge-request-title-ui.yml
new file mode 100644
index 00000000000..fa137a29cb4
--- /dev/null
+++ b/changelogs/unreleased/24333-close-issues-with-merge-request-title-ui.yml
@@ -0,0 +1,5 @@
+---
+title: Show Issues mentioned / being closed from a Merge Requests title below the
+ 'Accept Merge Request' button
+merge_request: 9194
+author: Jan Christophersen
diff --git a/changelogs/unreleased/27395-reduce-group-activity-sql-queries-2.yml b/changelogs/unreleased/27395-reduce-group-activity-sql-queries-2.yml
new file mode 100644
index 00000000000..f3ce1709518
--- /dev/null
+++ b/changelogs/unreleased/27395-reduce-group-activity-sql-queries-2.yml
@@ -0,0 +1,4 @@
+---
+title: Include :author, :project, and :target in Event.with_associations
+merge_request:
+author:
diff --git a/changelogs/unreleased/27395-reduce-group-activity-sql-queries.yml b/changelogs/unreleased/27395-reduce-group-activity-sql-queries.yml
new file mode 100644
index 00000000000..3f6d922f2a0
--- /dev/null
+++ b/changelogs/unreleased/27395-reduce-group-activity-sql-queries.yml
@@ -0,0 +1,4 @@
+---
+title: Don't instantiate AR objects in Event.in_projects
+merge_request:
+author:
diff --git a/changelogs/unreleased/27480-deploy_keys_should_not_show_up_in_users_keys_list.yml b/changelogs/unreleased/27480-deploy_keys_should_not_show_up_in_users_keys_list.yml
new file mode 100644
index 00000000000..6e9192cb632
--- /dev/null
+++ b/changelogs/unreleased/27480-deploy_keys_should_not_show_up_in_users_keys_list.yml
@@ -0,0 +1,4 @@
+---
+title: Do not display deploy keys in user's own ssh keys list
+merge_request: 9024
+author:
diff --git a/changelogs/unreleased/27939-fix-current-build-arrow.yml b/changelogs/unreleased/27939-fix-current-build-arrow.yml
new file mode 100644
index 00000000000..280ab090f2c
--- /dev/null
+++ b/changelogs/unreleased/27939-fix-current-build-arrow.yml
@@ -0,0 +1,4 @@
+---
+title: Fix current build arrow indicator
+merge_request:
+author:
diff --git a/changelogs/unreleased/27947-missing-margin-between-loading-icon-and-text-in-merge-request-widget.yml b/changelogs/unreleased/27947-missing-margin-between-loading-icon-and-text-in-merge-request-widget.yml
new file mode 100644
index 00000000000..1dfabd3813b
--- /dev/null
+++ b/changelogs/unreleased/27947-missing-margin-between-loading-icon-and-text-in-merge-request-widget.yml
@@ -0,0 +1,4 @@
+---
+title: Add space between text and loading icon in Megre Request Widget
+merge_request: 9119
+author:
diff --git a/changelogs/unreleased/28032-tooltips-file-name.yml b/changelogs/unreleased/28032-tooltips-file-name.yml
new file mode 100644
index 00000000000..9fe11e7c2b6
--- /dev/null
+++ b/changelogs/unreleased/28032-tooltips-file-name.yml
@@ -0,0 +1,5 @@
+---
+title: Adds container to tooltip in order to make it work with overflow:hidden in
+ parent element
+merge_request:
+author:
diff --git a/changelogs/unreleased/dynamic-header-fixture.yml b/changelogs/unreleased/dynamic-header-fixture.yml
new file mode 100644
index 00000000000..9789a1999c8
--- /dev/null
+++ b/changelogs/unreleased/dynamic-header-fixture.yml
@@ -0,0 +1,4 @@
+---
+title: Replace static fixture for header_spec.js
+merge_request: 9174
+author: winniehell
diff --git a/changelogs/unreleased/fix-cycle-analytics-events-limit.yml b/changelogs/unreleased/fix-cycle-analytics-events-limit.yml
new file mode 100644
index 00000000000..152b37ca430
--- /dev/null
+++ b/changelogs/unreleased/fix-cycle-analytics-events-limit.yml
@@ -0,0 +1,4 @@
+---
+title: Add limit to the number of events showed in cycle analytics
+merge_request:
+author:
diff --git a/changelogs/unreleased/merge-request-tabs-fixture.yml b/changelogs/unreleased/merge-request-tabs-fixture.yml
new file mode 100644
index 00000000000..289cd7b604a
--- /dev/null
+++ b/changelogs/unreleased/merge-request-tabs-fixture.yml
@@ -0,0 +1,4 @@
+---
+title: Replace static fixture for merge_request_tabs_spec.js
+merge_request: 9172
+author: winniehell
diff --git a/changelogs/unreleased/new-branch-fixture.yml b/changelogs/unreleased/new-branch-fixture.yml
new file mode 100644
index 00000000000..ce5ed816102
--- /dev/null
+++ b/changelogs/unreleased/new-branch-fixture.yml
@@ -0,0 +1,4 @@
+---
+title: Replace static fixture for new_branch_spec.js
+merge_request: 9131
+author: winniehell
diff --git a/changelogs/unreleased/option-to-be-notified-of-own-activity.yml b/changelogs/unreleased/option-to-be-notified-of-own-activity.yml
new file mode 100644
index 00000000000..c2e0410cc33
--- /dev/null
+++ b/changelogs/unreleased/option-to-be-notified-of-own-activity.yml
@@ -0,0 +1,4 @@
+---
+title: Add option to receive email notifications about your own activity
+merge_request: 8836
+author: Richard Macklin
diff --git a/changelogs/unreleased/quick-submit-fixture.yml b/changelogs/unreleased/quick-submit-fixture.yml
new file mode 100644
index 00000000000..a2cf05dabec
--- /dev/null
+++ b/changelogs/unreleased/quick-submit-fixture.yml
@@ -0,0 +1,4 @@
+---
+title: Replace static fixture for behaviors/quick_submit_spec.js
+merge_request: 9086
+author: winniehell
diff --git a/changelogs/unreleased/sh-add-labels-index.yml b/changelogs/unreleased/sh-add-labels-index.yml
new file mode 100644
index 00000000000..b948a75081c
--- /dev/null
+++ b/changelogs/unreleased/sh-add-labels-index.yml
@@ -0,0 +1,4 @@
+---
+title: Add indices to improve loading of labels page
+merge_request:
+author:
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 00f448c1fbb..2ac779c8511 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -117,7 +117,8 @@ if (IS_PRODUCTION) {
if (IS_DEV_SERVER) {
config.devServer = {
port: DEV_SERVER_PORT,
- headers: { 'Access-Control-Allow-Origin': '*' }
+ headers: { 'Access-Control-Allow-Origin': '*' },
+ stats: 'errors-only',
};
config.output.publicPath = '//localhost:' + DEV_SERVER_PORT + config.output.publicPath;
}
diff --git a/db/migrate/20170123061730_add_notified_of_own_activity_to_users.rb b/db/migrate/20170123061730_add_notified_of_own_activity_to_users.rb
new file mode 100644
index 00000000000..f90637e1e35
--- /dev/null
+++ b/db/migrate/20170123061730_add_notified_of_own_activity_to_users.rb
@@ -0,0 +1,14 @@
+class AddNotifiedOfOwnActivityToUsers < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+
+ def up
+ add_column_with_default :users, :notified_of_own_activity, :boolean, default: false
+ end
+
+ def down
+ remove_column :users, :notified_of_own_activity
+ end
+end
diff --git a/db/migrate/20170210062829_add_index_to_labels_for_title_and_project.rb b/db/migrate/20170210062829_add_index_to_labels_for_title_and_project.rb
new file mode 100644
index 00000000000..f922ed209aa
--- /dev/null
+++ b/db/migrate/20170210062829_add_index_to_labels_for_title_and_project.rb
@@ -0,0 +1,12 @@
+class AddIndexToLabelsForTitleAndProject < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def change
+ add_concurrent_index :labels, :title
+ add_concurrent_index :labels, :project_id
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index d71911eaf14..d421d5c6774 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -581,6 +581,8 @@ ActiveRecord::Schema.define(version: 20170210075922) do
add_index "labels", ["group_id", "project_id", "title"], name: "index_labels_on_group_id_and_project_id_and_title", unique: true, using: :btree
add_index "labels", ["type", "project_id"], name: "index_labels_on_type_and_project_id", using: :btree
+ add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree
+ add_index "labels", ["title"], name: "index_labels_on_title", using: :btree
create_table "lfs_objects", force: :cascade do |t|
t.string "oid", null: false
@@ -1277,6 +1279,7 @@ ActiveRecord::Schema.define(version: 20170210075922) do
t.string "organization"
t.string "incoming_email_token"
t.boolean "authorized_projects_populated"
+ t.boolean "notified_of_own_activity", default: false, null: false
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
diff --git a/doc/api/v3_to_v4.md b/doc/api/v3_to_v4.md
index 7cb83a337f2..0ae07b5d3de 100644
--- a/doc/api/v3_to_v4.md
+++ b/doc/api/v3_to_v4.md
@@ -13,3 +13,13 @@ changes are in V4:
- Project snippets do not return deprecated field `expires_at`
- Endpoints under `projects/:id/keys` have been removed (use `projects/:id/deploy_keys`)
- Status 409 returned for POST `project/:id/members` when a member already exists
+- Removed the following deprecated Templates endpoints (these are still accessible with `/templates` prefix)
+ - `/licences`
+ - `/licences/:key`
+ - `/gitignores`
+ - `/gitlab_ci_ymls`
+ - `/dockerfiles`
+ - `/gitignores/:key`
+ - `/gitlab_ci_ymls/:key`
+ - `/dockerfiles/:key`
+
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 7ec089b9c29..06346ae822a 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -11,6 +11,7 @@ module API
mount ::API::V3::MergeRequests
mount ::API::V3::Projects
mount ::API::V3::ProjectSnippets
+ mount ::API::V3::Templates
end
before { allow_access_with_scope :api }
diff --git a/lib/api/templates.rb b/lib/api/templates.rb
index e23f99256a5..8a2d66efd89 100644
--- a/lib/api/templates.rb
+++ b/lib/api/templates.rb
@@ -24,7 +24,6 @@ module API
/[\<\{\[]
(fullname|name\sof\s(author|copyright\sowner))
[\>\}\]]/xi.freeze
- DEPRECATION_MESSAGE = ' This endpoint is deprecated and will be removed in GitLab 9.0.'.freeze
helpers do
def parsed_license_template
@@ -46,74 +45,58 @@ module API
end
end
- { "licenses" => :deprecated, "templates/licenses" => :ok }.each do |route, status|
- desc 'Get the list of the available license template' do
- detailed_desc = 'This feature was introduced in GitLab 8.7.'
- detailed_desc << DEPRECATION_MESSAGE unless status == :ok
- detail detailed_desc
- success Entities::RepoLicense
- end
- params do
- optional :popular, type: Boolean, desc: 'If passed, returns only popular licenses'
- end
- get route do
- options = {
- featured: declared(params).popular.present? ? true : nil
- }
- present Licensee::License.all(options), with: Entities::RepoLicense
- end
+ desc 'Get the list of the available license template' do
+ detail 'This feature was introduced in GitLab 8.7.'
+ success ::API::Entities::RepoLicense
+ end
+ params do
+ optional :popular, type: Boolean, desc: 'If passed, returns only popular licenses'
+ end
+ get "templates/licenses" do
+ options = {
+ featured: declared(params).popular.present? ? true : nil
+ }
+ present Licensee::License.all(options), with: ::API::Entities::RepoLicense
end
- { "licenses/:name" => :deprecated, "templates/licenses/:name" => :ok }.each do |route, status|
- desc 'Get the text for a specific license' do
- detailed_desc = 'This feature was introduced in GitLab 8.7.'
- detailed_desc << DEPRECATION_MESSAGE unless status == :ok
- detail detailed_desc
- success Entities::RepoLicense
- end
- params do
- requires :name, type: String, desc: 'The name of the template'
- end
- get route, requirements: { name: /[\w\.-]+/ } do
- not_found!('License') unless Licensee::License.find(declared(params).name)
+ desc 'Get the text for a specific license' do
+ detail 'This feature was introduced in GitLab 8.7.'
+ success ::API::Entities::RepoLicense
+ end
+ params do
+ requires :name, type: String, desc: 'The name of the template'
+ end
+ get "templates/licenses/:name", requirements: { name: /[\w\.-]+/ } do
+ not_found!('License') unless Licensee::License.find(declared(params).name)
- template = parsed_license_template
+ template = parsed_license_template
- present template, with: Entities::RepoLicense
- end
+ present template, with: ::API::Entities::RepoLicense
end
GLOBAL_TEMPLATE_TYPES.each do |template_type, properties|
klass = properties[:klass]
gitlab_version = properties[:gitlab_version]
- { template_type => :deprecated, "templates/#{template_type}" => :ok }.each do |route, status|
- desc 'Get the list of the available template' do
- detailed_desc = "This feature was introduced in GitLab #{gitlab_version}."
- detailed_desc << DEPRECATION_MESSAGE unless status == :ok
- detail detailed_desc
- success Entities::TemplatesList
- end
- get route do
- present klass.all, with: Entities::TemplatesList
- end
+ desc 'Get the list of the available template' do
+ detail "This feature was introduced in GitLab #{gitlab_version}."
+ success Entities::TemplatesList
+ end
+ get "templates/#{template_type}" do
+ present klass.all, with: Entities::TemplatesList
end
- { "#{template_type}/:name" => :deprecated, "templates/#{template_type}/:name" => :ok }.each do |route, status|
- desc 'Get the text for a specific template present in local filesystem' do
- detailed_desc = "This feature was introduced in GitLab #{gitlab_version}."
- detailed_desc << DEPRECATION_MESSAGE unless status == :ok
- detail detailed_desc
- success Entities::Template
- end
- params do
- requires :name, type: String, desc: 'The name of the template'
- end
- get route do
- new_template = klass.find(declared(params).name)
+ desc 'Get the text for a specific template present in local filesystem' do
+ detail "This feature was introduced in GitLab #{gitlab_version}."
+ success Entities::Template
+ end
+ params do
+ requires :name, type: String, desc: 'The name of the template'
+ end
+ get "templates/#{template_type}/:name" do
+ new_template = klass.find(declared(params).name)
- render_response(template_type, new_template)
- end
+ render_response(template_type, new_template)
end
end
end
diff --git a/lib/api/v3/templates.rb b/lib/api/v3/templates.rb
new file mode 100644
index 00000000000..4c577a8d2b7
--- /dev/null
+++ b/lib/api/v3/templates.rb
@@ -0,0 +1,122 @@
+module API
+ module V3
+ class Templates < Grape::API
+ GLOBAL_TEMPLATE_TYPES = {
+ gitignores: {
+ klass: Gitlab::Template::GitignoreTemplate,
+ gitlab_version: 8.8
+ },
+ gitlab_ci_ymls: {
+ klass: Gitlab::Template::GitlabCiYmlTemplate,
+ gitlab_version: 8.9
+ },
+ dockerfiles: {
+ klass: Gitlab::Template::DockerfileTemplate,
+ gitlab_version: 8.15
+ }
+ }.freeze
+ PROJECT_TEMPLATE_REGEX =
+ /[\<\{\[]
+ (project|description|
+ one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here
+ [\>\}\]]/xi.freeze
+ YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze
+ FULLNAME_TEMPLATE_REGEX =
+ /[\<\{\[]
+ (fullname|name\sof\s(author|copyright\sowner))
+ [\>\}\]]/xi.freeze
+ DEPRECATION_MESSAGE = ' This endpoint is deprecated and has been removed in V4.'.freeze
+
+ helpers do
+ def parsed_license_template
+ # We create a fresh Licensee::License object since we'll modify its
+ # content in place below.
+ template = Licensee::License.new(params[:name])
+
+ template.content.gsub!(YEAR_TEMPLATE_REGEX, Time.now.year.to_s)
+ template.content.gsub!(PROJECT_TEMPLATE_REGEX, params[:project]) if params[:project].present?
+
+ fullname = params[:fullname].presence || current_user.try(:name)
+ template.content.gsub!(FULLNAME_TEMPLATE_REGEX, fullname) if fullname
+ template
+ end
+
+ def render_response(template_type, template)
+ not_found!(template_type.to_s.singularize) unless template
+ present template, with: ::API::Entities::Template
+ end
+ end
+
+ { "licenses" => :deprecated, "templates/licenses" => :ok }.each do |route, status|
+ desc 'Get the list of the available license template' do
+ detailed_desc = 'This feature was introduced in GitLab 8.7.'
+ detailed_desc << DEPRECATION_MESSAGE unless status == :ok
+ detail detailed_desc
+ success ::API::Entities::RepoLicense
+ end
+ params do
+ optional :popular, type: Boolean, desc: 'If passed, returns only popular licenses'
+ end
+ get route do
+ options = {
+ featured: declared(params).popular.present? ? true : nil
+ }
+ present Licensee::License.all(options), with: ::API::Entities::RepoLicense
+ end
+ end
+
+ { "licenses/:name" => :deprecated, "templates/licenses/:name" => :ok }.each do |route, status|
+ desc 'Get the text for a specific license' do
+ detailed_desc = 'This feature was introduced in GitLab 8.7.'
+ detailed_desc << DEPRECATION_MESSAGE unless status == :ok
+ detail detailed_desc
+ success ::API::Entities::RepoLicense
+ end
+ params do
+ requires :name, type: String, desc: 'The name of the template'
+ end
+ get route, requirements: { name: /[\w\.-]+/ } do
+ not_found!('License') unless Licensee::License.find(declared(params).name)
+
+ template = parsed_license_template
+
+ present template, with: ::API::Entities::RepoLicense
+ end
+ end
+
+ GLOBAL_TEMPLATE_TYPES.each do |template_type, properties|
+ klass = properties[:klass]
+ gitlab_version = properties[:gitlab_version]
+
+ { template_type => :deprecated, "templates/#{template_type}" => :ok }.each do |route, status|
+ desc 'Get the list of the available template' do
+ detailed_desc = "This feature was introduced in GitLab #{gitlab_version}."
+ detailed_desc << DEPRECATION_MESSAGE unless status == :ok
+ detail detailed_desc
+ success ::API::Entities::TemplatesList
+ end
+ get route do
+ present klass.all, with: ::API::Entities::TemplatesList
+ end
+ end
+
+ { "#{template_type}/:name" => :deprecated, "templates/#{template_type}/:name" => :ok }.each do |route, status|
+ desc 'Get the text for a specific template present in local filesystem' do
+ detailed_desc = "This feature was introduced in GitLab #{gitlab_version}."
+ detailed_desc << DEPRECATION_MESSAGE unless status == :ok
+ detail detailed_desc
+ success ::API::Entities::Template
+ end
+ params do
+ requires :name, type: String, desc: 'The name of the template'
+ end
+ get route do
+ new_template = klass.find(declared(params).name)
+
+ render_response(template_type, new_template)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/cycle_analytics/base_event_fetcher.rb b/lib/gitlab/cycle_analytics/base_event_fetcher.rb
index 0d8791d396b..ab115afcaa5 100644
--- a/lib/gitlab/cycle_analytics/base_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/base_event_fetcher.rb
@@ -5,6 +5,8 @@ module Gitlab
attr_reader :projections, :query, :stage, :order
+ MAX_EVENTS = 50
+
def initialize(project:, stage:, options:)
@project = project
@stage = stage
@@ -38,7 +40,7 @@ module Gitlab
def events_query
diff_fn = subtract_datetimes_diff(base_query, @options[:start_time_attrs], @options[:end_time_attrs])
- base_query.project(extract_diff_epoch(diff_fn).as('total_time'), *projections).order(order.desc)
+ base_query.project(extract_diff_epoch(diff_fn).as('total_time'), *projections).order(order.desc).take(MAX_EVENTS)
end
def default_order
diff --git a/lib/gitlab/diff/parser.rb b/lib/gitlab/diff/parser.rb
index 59a2367b65d..89320f5d9dc 100644
--- a/lib/gitlab/diff/parser.rb
+++ b/lib/gitlab/diff/parser.rb
@@ -45,7 +45,7 @@ module Gitlab
line_new += 1
when "-"
line_old += 1
- when "\\"
+ when "\\" # rubocop:disable Lint/EmptyWhen
# No increment
else
line_new += 1
diff --git a/spec/controllers/profiles/keys_controller_spec.rb b/spec/controllers/profiles/keys_controller_spec.rb
index 6bcfae0fc13..f7219690722 100644
--- a/spec/controllers/profiles/keys_controller_spec.rb
+++ b/spec/controllers/profiles/keys_controller_spec.rb
@@ -42,10 +42,9 @@ describe Profiles::KeysController do
end
describe "user with keys" do
- before do
- user.keys << create(:key)
- user.keys << create(:another_key)
- end
+ let!(:key) { create(:key, user: user) }
+ let!(:another_key) { create(:another_key, user: user) }
+ let!(:deploy_key) { create(:deploy_key, user: user) }
it "does generally work" do
get :get_keys, username: user.username
@@ -53,16 +52,16 @@ describe Profiles::KeysController do
expect(response).to be_success
end
- it "renders all keys separated with a new line" do
+ it "renders all non deploy keys separated with a new line" do
get :get_keys, username: user.username
- expect(response.body).not_to eq("")
+ expect(response.body).not_to eq('')
expect(response.body).to eq(user.all_ssh_keys.join("\n"))
- # Unique part of key 1
- expect(response.body).to match(/PWx6WM4lhHNedGfBpPJNPpZ/)
- # Key 2
- expect(response.body).to match(/AQDmTillFzNTrrGgwaCKaSj/)
+ expect(response.body).to include(key.key.sub(' dummy@gitlab.com', ''))
+ expect(response.body).to include(another_key.key)
+
+ expect(response.body).not_to include(deploy_key.key)
end
it "does not render the comment of the key" do
diff --git a/spec/controllers/profiles/notifications_controller_spec.rb b/spec/controllers/profiles/notifications_controller_spec.rb
new file mode 100644
index 00000000000..58caf7999cf
--- /dev/null
+++ b/spec/controllers/profiles/notifications_controller_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+describe Profiles::NotificationsController do
+ let(:user) do
+ create(:user) do |user|
+ user.emails.create(email: 'original@example.com')
+ user.emails.create(email: 'new@example.com')
+ user.update(notification_email: 'original@example.com')
+ user.save!
+ end
+ end
+
+ describe 'GET show' do
+ it 'renders' do
+ sign_in(user)
+
+ get :show
+
+ expect(response).to render_template :show
+ end
+ end
+
+ describe 'POST update' do
+ it 'updates only permitted attributes' do
+ sign_in(user)
+
+ put :update, user: { notification_email: 'new@example.com', notified_of_own_activity: true, admin: true }
+
+ user.reload
+ expect(user.notification_email).to eq('new@example.com')
+ expect(user.notified_of_own_activity).to eq(true)
+ expect(user.admin).to eq(false)
+ expect(controller).to set_flash[:notice].to('Notification settings saved')
+ end
+
+ it 'shows an error message if the params are invalid' do
+ sign_in(user)
+
+ put :update, user: { notification_email: '' }
+
+ expect(user.reload.notification_email).to eq('original@example.com')
+ expect(controller).to set_flash[:alert].to('Failed to save new settings')
+ end
+ end
+end
diff --git a/spec/factories/keys.rb b/spec/factories/keys.rb
index d69c5b38d0a..dd93b439b2b 100644
--- a/spec/factories/keys.rb
+++ b/spec/factories/keys.rb
@@ -2,10 +2,13 @@ FactoryGirl.define do
factory :key do
title
key do
- "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0= dummy@gitlab.com"
+ 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0= dummy@gitlab.com'
end
factory :deploy_key, class: 'DeployKey' do
+ key do
+ 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFf6RYK3qu/RKF/3ndJmL5xgMLp3O96x8lTay+QGZ0+9FnnAXMdUqBq/ZU6d/gyMB4IaW3nHzM1w049++yAB6UPCzMB8Uo27K5/jyZCtj7Vm9PFNjF/8am1kp46c/SeYicQgQaSBdzIW3UDEa1Ef68qroOlvpi9PYZ/tA7M0YP0K5PXX+E36zaIRnJVMPT3f2k+GnrxtjafZrwFdpOP/Fol5BQLBgcsyiU+LM1SuaCrzd8c9vyaTA1CxrkxaZh+buAi0PmdDtaDrHd42gqZkXCKavyvgM5o2CkQ5LJHCgzpXy05qNFzmThBSkb+XtoxbyagBiGbVZtSVow6Xa7qewz'
+ end
end
factory :personal_key do
@@ -14,7 +17,7 @@ FactoryGirl.define do
factory :another_key do
key do
- "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDmTillFzNTrrGgwaCKaSj+QCz81E6jBc/s9av0+3b1Hwfxgkqjl4nAK/OD2NjgyrONDTDfR8cRN4eAAy6nY8GLkOyYBDyuc5nTMqs5z3yVuTwf3koGm/YQQCmo91psZ2BgDFTor8SVEE5Mm1D1k3JDMhDFxzzrOtRYFPci9lskTJaBjpqWZ4E9rDTD2q/QZntCqbC3wE9uSemRQB5f8kik7vD/AD8VQXuzKladrZKkzkONCPWsXDspUitjM8HkQdOf0PsYn1CMUC1xKYbCxkg5TkEosIwGv6CoEArUrdu/4+10LVslq494mAvEItywzrluCLCnwELfW+h/m8UHoVhZ"
+ 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDmTillFzNTrrGgwaCKaSj+QCz81E6jBc/s9av0+3b1Hwfxgkqjl4nAK/OD2NjgyrONDTDfR8cRN4eAAy6nY8GLkOyYBDyuc5nTMqs5z3yVuTwf3koGm/YQQCmo91psZ2BgDFTor8SVEE5Mm1D1k3JDMhDFxzzrOtRYFPci9lskTJaBjpqWZ4E9rDTD2q/QZntCqbC3wE9uSemRQB5f8kik7vD/AD8VQXuzKladrZKkzkONCPWsXDspUitjM8HkQdOf0PsYn1CMUC1xKYbCxkg5TkEosIwGv6CoEArUrdu/4+10LVslq494mAvEItywzrluCLCnwELfW+h/m8UHoVhZ'
end
factory :another_deploy_key, class: 'DeployKey' do
diff --git a/spec/factories/wiki_directories.rb b/spec/factories/wiki_directories.rb
new file mode 100644
index 00000000000..3f3c864ac2b
--- /dev/null
+++ b/spec/factories/wiki_directories.rb
@@ -0,0 +1,6 @@
+FactoryGirl.define do
+ factory :wiki_directory do
+ slug '/path_up_to/dir'
+ initialize_with { new(slug) }
+ end
+end
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index 6f7046c8461..64f448a83b7 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -113,13 +113,11 @@ describe 'Filter issues', js: true, feature: true do
end
it 'filters issues by invalid author' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
it 'filters issues by multiple authors' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
end
@@ -158,8 +156,7 @@ describe 'Filter issues', js: true, feature: true do
end
it 'sorting' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
end
@@ -182,13 +179,11 @@ describe 'Filter issues', js: true, feature: true do
end
it 'filters issues by invalid assignee' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
it 'filters issues by multiple assignees' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
end
@@ -228,8 +223,7 @@ describe 'Filter issues', js: true, feature: true do
context 'sorting' do
it 'sorts' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
end
end
@@ -253,8 +247,7 @@ describe 'Filter issues', js: true, feature: true do
end
it 'filters issues by invalid label' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
it 'filters issues by multiple labels' do
@@ -429,8 +422,7 @@ describe 'Filter issues', js: true, feature: true do
context 'sorting' do
it 'sorts' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
end
end
@@ -456,13 +448,11 @@ describe 'Filter issues', js: true, feature: true do
end
it 'filters issues by invalid milestones' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
it 'filters issues by multiple milestones' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
it 'filters issues by milestone containing special characters' do
@@ -523,8 +513,7 @@ describe 'Filter issues', js: true, feature: true do
context 'sorting' do
it 'sorts' do
- pending('to be tested, issue #26546')
- expect(true).to be(false)
+ skip('to be tested, issue #26546')
end
end
end
diff --git a/spec/features/merge_requests/closes_issues_spec.rb b/spec/features/merge_requests/closes_issues_spec.rb
index c73065cdce1..eafcab6a0d7 100644
--- a/spec/features/merge_requests/closes_issues_spec.rb
+++ b/spec/features/merge_requests/closes_issues_spec.rb
@@ -10,10 +10,12 @@ feature 'Merge Request closing issues message', feature: true do
:merge_request,
:simple,
source_project: project,
- description: merge_request_description
+ description: merge_request_description,
+ title: merge_request_title
)
end
let(:merge_request_description) { 'Merge Request Description' }
+ let(:merge_request_title) { 'Merge Request Title' }
before do
project.team << [user, :master]
@@ -45,8 +47,32 @@ feature 'Merge Request closing issues message', feature: true do
end
end
- context 'closing some issues and mentioning, but not closing, others' do
- let(:merge_request_description) { "Description\n\ncloses #{issue_1.to_reference}\n\n refers to #{issue_2.to_reference}" }
+ context 'closing some issues in title and mentioning, but not closing, others' do
+ let(:merge_request_title) { "closes #{issue_1.to_reference}\n\n refers to #{issue_2.to_reference}" }
+
+ it 'does not display closing issue message' do
+ expect(page).to have_content("Accepting this merge request will close issue #{issue_1.to_reference}. Issue #{issue_2.to_reference} is mentioned but will not be closed.")
+ end
+ end
+
+ context 'closing issues using title but not mentioning any other issue' do
+ let(:merge_request_title) { "closing #{issue_1.to_reference}, #{issue_2.to_reference}" }
+
+ it 'does not display closing issue message' do
+ expect(page).to have_content("Accepting this merge request will close issues #{issue_1.to_reference} and #{issue_2.to_reference}")
+ end
+ end
+
+ context 'mentioning issues using title but not closing them' do
+ let(:merge_request_title) { "Refers to #{issue_1.to_reference} and #{issue_2.to_reference}" }
+
+ it 'does not display closing issue message' do
+ expect(page).to have_content("Issues #{issue_1.to_reference} and #{issue_2.to_reference} are mentioned but will not be closed.")
+ end
+ end
+
+ context 'closing some issues using title and mentioning, but not closing, others' do
+ let(:merge_request_title) { "closes #{issue_1.to_reference}\n\n refers to #{issue_2.to_reference}" }
it 'does not display closing issue message' do
expect(page).to have_content("Accepting this merge request will close issue #{issue_1.to_reference}. Issue #{issue_2.to_reference} is mentioned but will not be closed.")
diff --git a/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb b/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb
new file mode 100644
index 00000000000..e05fbb3715c
--- /dev/null
+++ b/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+feature 'Profile > Notifications > User changes notified_of_own_activity setting', feature: true, js: true do
+ let(:user) { create(:user) }
+
+ before do
+ login_as(user)
+ end
+
+ scenario 'User opts into receiving notifications about their own activity' do
+ visit profile_notifications_path
+
+ expect(page).not_to have_checked_field('user[notified_of_own_activity]')
+
+ check 'user[notified_of_own_activity]'
+
+ expect(page).to have_content('Notification settings saved')
+ expect(page).to have_checked_field('user[notified_of_own_activity]')
+ end
+
+ scenario 'User opts out of receiving notifications about their own activity' do
+ user.update!(notified_of_own_activity: true)
+ visit profile_notifications_path
+
+ expect(page).to have_checked_field('user[notified_of_own_activity]')
+
+ uncheck 'user[notified_of_own_activity]'
+
+ expect(page).to have_content('Notification settings saved')
+ expect(page).not_to have_checked_field('user[notified_of_own_activity]')
+ end
+end
diff --git a/spec/features/projects/builds_spec.rb b/spec/features/projects/builds_spec.rb
index f7e0115643e..f1036b275f7 100644
--- a/spec/features/projects/builds_spec.rb
+++ b/spec/features/projects/builds_spec.rb
@@ -109,6 +109,10 @@ feature 'Builds', :feature do
expect(page).to have_content pipeline.git_commit_message
expect(page).to have_content pipeline.git_author_name
end
+
+ it 'shows active build' do
+ expect(page).to have_selector('.build-job.active')
+ end
end
context "Job from other project" do
diff --git a/spec/helpers/wiki_helper_spec.rb b/spec/helpers/wiki_helper_spec.rb
new file mode 100644
index 00000000000..92c6f27a867
--- /dev/null
+++ b/spec/helpers/wiki_helper_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe WikiHelper do
+ describe '#breadcrumb' do
+ context 'when the page is at the root level' do
+ it 'returns the capitalized page name' do
+ slug = 'page-name'
+
+ expect(helper.breadcrumb(slug)).to eq('Page name')
+ end
+ end
+
+ context 'when the page is inside a directory' do
+ it 'returns the capitalized name of each directory and of the page itself' do
+ slug = 'dir_1/page-name'
+
+ expect(helper.breadcrumb(slug)).to eq('Dir_1 / Page name')
+ end
+ end
+ end
+end
diff --git a/spec/javascripts/behaviors/quick_submit_spec.js b/spec/javascripts/behaviors/quick_submit_spec.js
index 1541037888f..0e4c2c560cc 100644
--- a/spec/javascripts/behaviors/quick_submit_spec.js
+++ b/spec/javascripts/behaviors/quick_submit_spec.js
@@ -5,73 +5,83 @@ require('~/behaviors/quick_submit');
(function() {
describe('Quick Submit behavior', function() {
var keydownEvent;
- preloadFixtures('static/behaviors/quick_submit.html.raw');
+ preloadFixtures('issues/open-issue.html.raw');
beforeEach(function() {
- loadFixtures('static/behaviors/quick_submit.html.raw');
+ loadFixtures('issues/open-issue.html.raw');
$('form').submit(function(e) {
// Prevent a form submit from moving us off the testing page
return e.preventDefault();
});
- return this.spies = {
+ this.spies = {
submit: spyOnEvent('form', 'submit')
};
+
+ this.textarea = $('.js-quick-submit textarea').first();
});
it('does not respond to other keyCodes', function() {
- $('input.quick-submit-input').trigger(keydownEvent({
+ this.textarea.trigger(keydownEvent({
keyCode: 32
}));
return expect(this.spies.submit).not.toHaveBeenTriggered();
});
it('does not respond to Enter alone', function() {
- $('input.quick-submit-input').trigger(keydownEvent({
+ this.textarea.trigger(keydownEvent({
ctrlKey: false,
metaKey: false
}));
return expect(this.spies.submit).not.toHaveBeenTriggered();
});
it('does not respond to repeated events', function() {
- $('input.quick-submit-input').trigger(keydownEvent({
+ this.textarea.trigger(keydownEvent({
repeat: true
}));
return expect(this.spies.submit).not.toHaveBeenTriggered();
});
- it('disables submit buttons', function() {
- $('textarea').trigger(keydownEvent());
- expect($('input[type=submit]')).toBeDisabled();
- return expect($('button[type=submit]')).toBeDisabled();
+ it('disables input of type submit', function() {
+ const submitButton = $('.js-quick-submit input[type=submit]');
+ this.textarea.trigger(keydownEvent());
+ expect(submitButton).toBeDisabled();
+ });
+ it('disables button of type submit', function() {
+ // button doesn't exist in fixture, add it manually
+ const submitButton = $('<button type="submit">Submit it</button>');
+ submitButton.insertAfter(this.textarea);
+
+ this.textarea.trigger(keydownEvent());
+ expect(submitButton).toBeDisabled();
});
// We cannot stub `navigator.userAgent` for CI's `rake karma` task, so we'll
// only run the tests that apply to the current platform
if (navigator.userAgent.match(/Macintosh/)) {
it('responds to Meta+Enter', function() {
- $('input.quick-submit-input').trigger(keydownEvent());
+ this.textarea.trigger(keydownEvent());
return expect(this.spies.submit).toHaveBeenTriggered();
});
it('excludes other modifier keys', function() {
- $('input.quick-submit-input').trigger(keydownEvent({
+ this.textarea.trigger(keydownEvent({
altKey: true
}));
- $('input.quick-submit-input').trigger(keydownEvent({
+ this.textarea.trigger(keydownEvent({
ctrlKey: true
}));
- $('input.quick-submit-input').trigger(keydownEvent({
+ this.textarea.trigger(keydownEvent({
shiftKey: true
}));
return expect(this.spies.submit).not.toHaveBeenTriggered();
});
} else {
it('responds to Ctrl+Enter', function() {
- $('input.quick-submit-input').trigger(keydownEvent());
+ this.textarea.trigger(keydownEvent());
return expect(this.spies.submit).toHaveBeenTriggered();
});
it('excludes other modifier keys', function() {
- $('input.quick-submit-input').trigger(keydownEvent({
+ this.textarea.trigger(keydownEvent({
altKey: true
}));
- $('input.quick-submit-input').trigger(keydownEvent({
+ this.textarea.trigger(keydownEvent({
metaKey: true
}));
- $('input.quick-submit-input').trigger(keydownEvent({
+ this.textarea.trigger(keydownEvent({
shiftKey: true
}));
return expect(this.spies.submit).not.toHaveBeenTriggered();
diff --git a/spec/javascripts/fixtures/behaviors/quick_submit.html.haml b/spec/javascripts/fixtures/behaviors/quick_submit.html.haml
deleted file mode 100644
index dc2ceed42f4..00000000000
--- a/spec/javascripts/fixtures/behaviors/quick_submit.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-%form.js-quick-submit{ action: '/foo' }
- %input{ type: 'text', class: 'quick-submit-input'}
- %textarea
-
- %input{ type: 'submit'} Submit
- %button.btn{ type: 'submit' } Submit
diff --git a/spec/javascripts/fixtures/branches.rb b/spec/javascripts/fixtures/branches.rb
new file mode 100644
index 00000000000..0e7c2351b66
--- /dev/null
+++ b/spec/javascripts/fixtures/branches.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+describe Projects::BranchesController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project, :repository, namespace: namespace, path: 'branches-project') }
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('branches/')
+ end
+
+ before(:each) do
+ sign_in(admin)
+ end
+
+ it 'branches/new_branch.html.raw' do |example|
+ get :new,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+end
diff --git a/spec/javascripts/fixtures/header.html.haml b/spec/javascripts/fixtures/header.html.haml
deleted file mode 100644
index f397f69e753..00000000000
--- a/spec/javascripts/fixtures/header.html.haml
+++ /dev/null
@@ -1,35 +0,0 @@
-%header.navbar.navbar-gitlab.nav_header_class
- .container-fluid
- .header-content
- %button.side-nav-toggle
- %span.sr-only
- Toggle navigation
- %i.fa.fa-bars
- %button.navbar-toggle
- %span.sr-only
- Toggle navigation
- %i.fa.fa-ellipsis-v
- .navbar-collapse.collapse
- %ui.nav.navbar-nav
- %li.hidden-sm.hidden-xs
- %li.visible-sm.visible-xs
- %li
- %a
- %i.fa.fa-bell.fa-fw
- %span.badge.todos-pending-count
- %li
- %a
- %i.fa.fa-plus.fa-fw
- %li.header-user.dropdown
- %a
- %img
- %span.caret
- .dropdown-menu-nav
- .dropdown-menu-align-right
- %ul
- %li
- %a.profile-link
- %li
- %a
- %li.divider
- %li.sign-out-link
diff --git a/spec/javascripts/fixtures/merge_request_tabs.html.haml b/spec/javascripts/fixtures/merge_request_tabs.html.haml
deleted file mode 100644
index 68678c3d7e3..00000000000
--- a/spec/javascripts/fixtures/merge_request_tabs.html.haml
+++ /dev/null
@@ -1,22 +0,0 @@
-%ul.nav.nav-tabs.merge-request-tabs
- %li.notes-tab
- %a{href: '/foo/bar/merge_requests/1', data: {target: 'div#notes', action: 'notes', toggle: 'tab'}}
- Discussion
- %li.commits-tab
- %a{href: '/foo/bar/merge_requests/1/commits', data: {target: 'div#commits', action: 'commits', toggle: 'tab'}}
- Commits
- %li.diffs-tab
- %a{href: '/foo/bar/merge_requests/1/diffs', data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'}}
- Diffs
-
-.tab-content
- #notes.notes.tab-pane
- Notes Content
- #commits.commits.tab-pane
- Commits Content
- #diffs.diffs.tab-pane
- Diffs Content
-
-.mr-loading-status
- .loading
- Loading Animation
diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb
new file mode 100644
index 00000000000..62984097099
--- /dev/null
+++ b/spec/javascripts/fixtures/merge_requests.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project, namespace: namespace, path: 'merge-requests-project') }
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('merge_requests/')
+ end
+
+ before(:each) do
+ sign_in(admin)
+ end
+
+ it 'merge_requests/merge_request_with_task_list.html.raw' do |example|
+ merge_request = create(:merge_request, :with_diffs, source_project: project, target_project: project, description: '- [ ] Task List Item')
+ render_merge_request(example.description, merge_request)
+ end
+
+ private
+
+ def render_merge_request(fixture_file_name, merge_request)
+ get :show,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: merge_request.to_param
+
+ expect(response).to be_success
+ store_frontend_fixture(response, fixture_file_name)
+ end
+end
diff --git a/spec/javascripts/fixtures/new_branch.html.haml b/spec/javascripts/fixtures/new_branch.html.haml
deleted file mode 100644
index f06629e5ecc..00000000000
--- a/spec/javascripts/fixtures/new_branch.html.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-%form.js-create-branch-form
- %input.js-branch-name
- .js-branch-name-error
- %input{id: "ref"}
diff --git a/spec/javascripts/header_spec.js b/spec/javascripts/header_spec.js
index cecebb0b038..2b263b71b7d 100644
--- a/spec/javascripts/header_spec.js
+++ b/spec/javascripts/header_spec.js
@@ -6,7 +6,7 @@ require('~/lib/utils/text_utility');
(function() {
describe('Header', function() {
var todosPendingCount = '.todos-pending-count';
- var fixtureTemplate = 'static/header.html.raw';
+ var fixtureTemplate = 'issues/open-issue.html.raw';
function isTodosCountHidden() {
return $(todosPendingCount).hasClass('hidden');
diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js
index 92a0f1c05f7..3810991f104 100644
--- a/spec/javascripts/merge_request_tabs_spec.js
+++ b/spec/javascripts/merge_request_tabs_spec.js
@@ -25,7 +25,7 @@ require('vendor/jquery.scrollTo');
};
$.extend(stubLocation, defaults, stubs || {});
};
- preloadFixtures('static/merge_request_tabs.html.raw');
+ preloadFixtures('merge_requests/merge_request_with_task_list.html.raw');
beforeEach(function () {
this.class = new gl.MergeRequestTabs({ stubLocation: stubLocation });
@@ -41,7 +41,7 @@ require('vendor/jquery.scrollTo');
describe('#activateTab', function () {
beforeEach(function () {
spyOn($, 'ajax').and.callFake(function () {});
- loadFixtures('static/merge_request_tabs.html.raw');
+ loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
this.subject = this.class.activateTab;
});
it('shows the first tab when action is show', function () {
diff --git a/spec/javascripts/new_branch_spec.js b/spec/javascripts/new_branch_spec.js
index 9b657868523..1d014502c2a 100644
--- a/spec/javascripts/new_branch_spec.js
+++ b/spec/javascripts/new_branch_spec.js
@@ -8,7 +8,7 @@ require('~/new_branch_form');
describe('Branch', function() {
return describe('create a new branch', function() {
var expectToHaveError, fillNameWith;
- preloadFixtures('static/new_branch.html.raw');
+ preloadFixtures('branches/new_branch.html.raw');
fillNameWith = function(value) {
return $('.js-branch-name').val(value).trigger('blur');
};
@@ -16,7 +16,7 @@ require('~/new_branch_form');
return expect($('.js-branch-name-error span').text()).toEqual(error);
};
beforeEach(function() {
- loadFixtures('static/new_branch.html.raw');
+ loadFixtures('branches/new_branch.html.raw');
$('form').on('submit', function(e) {
return e.preventDefault();
});
diff --git a/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
new file mode 100644
index 00000000000..c455cd9b942
--- /dev/null
+++ b/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+describe Gitlab::CycleAnalytics::BaseEventFetcher do
+ let(:max_events) { 2 }
+ let(:project) { create(:project) }
+ let(:user) { create(:user, :admin) }
+ let(:start_time_attrs) { Issue.arel_table[:created_at] }
+ let(:end_time_attrs) { [Issue::Metrics.arel_table[:first_associated_with_milestone_at]] }
+ let(:options) do
+ { start_time_attrs: start_time_attrs,
+ end_time_attrs: end_time_attrs,
+ from: 30.days.ago }
+ end
+
+ subject do
+ described_class.new(project: project,
+ stage: :issue,
+ options: options).fetch
+ end
+
+ before do
+ allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return(Issue.all)
+ allow_any_instance_of(Gitlab::CycleAnalytics::BaseEventFetcher).to receive(:serialize) do |event|
+ event
+ end
+
+ stub_const('Gitlab::CycleAnalytics::BaseEventFetcher::MAX_EVENTS', max_events)
+
+ setup_events(count: 3)
+ end
+
+ it 'limits the rows to the max number' do
+ expect(subject.count).to eq(max_events)
+ end
+
+ def setup_events(count:)
+ count.times do
+ issue = create(:issue, project: project, created_at: 2.days.ago)
+ milestone = create(:milestone, project: project)
+
+ issue.update(milestone: milestone)
+ create_merge_request_closing_issue(issue)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index c78cd30157e..1dbc2f6eb13 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -2,47 +2,52 @@
require 'spec_helper'
describe Gitlab::Regex, lib: true do
- describe 'project path regex' do
- it { expect('gitlab-ce').to match(Gitlab::Regex.project_path_regex) }
- it { expect('gitlab_git').to match(Gitlab::Regex.project_path_regex) }
- it { expect('_underscore.js').to match(Gitlab::Regex.project_path_regex) }
- it { expect('100px.com').to match(Gitlab::Regex.project_path_regex) }
- it { expect('?gitlab').not_to match(Gitlab::Regex.project_path_regex) }
- it { expect('git lab').not_to match(Gitlab::Regex.project_path_regex) }
- it { expect('gitlab.git').not_to match(Gitlab::Regex.project_path_regex) }
- end
+ describe '.project_path_regex' do
+ subject { described_class.project_path_regex }
- describe 'project name regex' do
- it { expect('gitlab-ce').to match(Gitlab::Regex.project_name_regex) }
- it { expect('GitLab CE').to match(Gitlab::Regex.project_name_regex) }
- it { expect('100 lines').to match(Gitlab::Regex.project_name_regex) }
- it { expect('gitlab.git').to match(Gitlab::Regex.project_name_regex) }
- it { expect('Český název').to match(Gitlab::Regex.project_name_regex) }
- it { expect('Dash – is this').to match(Gitlab::Regex.project_name_regex) }
- it { expect('?gitlab').not_to match(Gitlab::Regex.project_name_regex) }
+ it { is_expected.to match('gitlab-ce') }
+ it { is_expected.to match('gitlab_git') }
+ it { is_expected.to match('_underscore.js') }
+ it { is_expected.to match('100px.com') }
+ it { is_expected.not_to match('?gitlab') }
+ it { is_expected.not_to match('git lab') }
+ it { is_expected.not_to match('gitlab.git') }
end
- describe 'file name regex' do
- it { expect('foo@bar').to match(Gitlab::Regex.file_name_regex) }
+ describe '.project_name_regex' do
+ subject { described_class.project_name_regex }
+
+ it { is_expected.to match('gitlab-ce') }
+ it { is_expected.to match('GitLab CE') }
+ it { is_expected.to match('100 lines') }
+ it { is_expected.to match('gitlab.git') }
+ it { is_expected.to match('Český název') }
+ it { is_expected.to match('Dash – is this') }
+ it { is_expected.not_to match('?gitlab') }
end
- describe 'file path regex' do
- it { expect('foo@/bar').to match(Gitlab::Regex.file_path_regex) }
+ describe '.file_name_regex' do
+ subject { described_class.file_name_regex }
+
+ it { is_expected.to match('foo@bar') }
end
- describe 'environment slug regex' do
- def be_matched
- match(Gitlab::Regex.environment_slug_regex)
- end
+ describe '.file_path_regex' do
+ subject { described_class.file_path_regex }
+
+ it { is_expected.to match('foo@/bar') }
+ end
- it { expect('foo').to be_matched }
- it { expect('foo-1').to be_matched }
+ describe '.environment_slug_regex' do
+ subject { described_class.environment_slug_regex }
- it { expect('FOO').not_to be_matched }
- it { expect('foo/1').not_to be_matched }
- it { expect('foo.1').not_to be_matched }
- it { expect('foo*1').not_to be_matched }
- it { expect('9foo').not_to be_matched }
- it { expect('foo-').not_to be_matched }
+ it { is_expected.to match('foo') }
+ it { is_expected.to match('foo-1') }
+ it { is_expected.not_to match('FOO') }
+ it { is_expected.not_to match('foo/1') }
+ it { is_expected.not_to match('foo.1') }
+ it { is_expected.not_to match('foo*1') }
+ it { is_expected.not_to match('9foo') }
+ it { is_expected.not_to match('foo-') }
end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 9bfa6409607..838fd3754b2 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -351,6 +351,17 @@ describe Repository, models: true do
expect(blob.data).to eq('Changelog!')
end
+ it 'respects the autocrlf setting' do
+ repository.commit_file(user, 'hello.txt', "Hello,\r\nWorld",
+ message: 'Add hello world',
+ branch_name: 'master',
+ update: true)
+
+ blob = repository.blob_at('master', 'hello.txt')
+
+ expect(blob.data).to eq("Hello,\nWorld")
+ end
+
context "when an author is specified" do
it "uses the given email/name to set the commit's author" do
expect do
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 7fd49c73b37..89cef7ab978 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -19,6 +19,7 @@ describe User, models: true do
it { is_expected.to have_many(:project_members).dependent(:destroy) }
it { is_expected.to have_many(:groups) }
it { is_expected.to have_many(:keys).dependent(:destroy) }
+ it { is_expected.to have_many(:deploy_keys).dependent(:destroy) }
it { is_expected.to have_many(:events).dependent(:destroy) }
it { is_expected.to have_many(:recent_events).class_name('Event') }
it { is_expected.to have_many(:issues).dependent(:destroy) }
@@ -303,6 +304,34 @@ describe User, models: true do
end
end
+ shared_context 'user keys' do
+ let(:user) { create(:user) }
+ let!(:key) { create(:key, user: user) }
+ let!(:deploy_key) { create(:deploy_key, user: user) }
+ end
+
+ describe '#keys' do
+ include_context 'user keys'
+
+ context 'with key and deploy key stored' do
+ it 'returns stored key, but not deploy_key' do
+ expect(user.keys).to include key
+ expect(user.keys).not_to include deploy_key
+ end
+ end
+ end
+
+ describe '#deploy_keys' do
+ include_context 'user keys'
+
+ context 'with key and deploy key stored' do
+ it 'returns stored deploy key, but not normal key' do
+ expect(user.deploy_keys).to include deploy_key
+ expect(user.deploy_keys).not_to include key
+ end
+ end
+ end
+
describe '#confirm' do
before do
allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(true)
diff --git a/spec/models/wiki_directory_spec.rb b/spec/models/wiki_directory_spec.rb
new file mode 100644
index 00000000000..1caaa557085
--- /dev/null
+++ b/spec/models/wiki_directory_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+RSpec.describe WikiDirectory, models: true do
+ describe 'validations' do
+ subject { build(:wiki_directory) }
+
+ it { is_expected.to validate_presence_of(:slug) }
+ end
+
+ describe '#initialize' do
+ context 'when there are pages' do
+ let(:pages) { [build(:wiki_page)] }
+ let(:directory) { WikiDirectory.new('/path_up_to/dir', pages) }
+
+ it 'sets the slug attribute' do
+ expect(directory.slug).to eq('/path_up_to/dir')
+ end
+
+ it 'sets the pages attribute' do
+ expect(directory.pages).to eq(pages)
+ end
+ end
+
+ context 'when there are no pages' do
+ let(:directory) { WikiDirectory.new('/path_up_to/dir') }
+
+ it 'sets the slug attribute' do
+ expect(directory.slug).to eq('/path_up_to/dir')
+ end
+
+ it 'sets the pages attribute to an empty array' do
+ expect(directory.pages).to eq([])
+ end
+ end
+ end
+
+ describe '#to_partial_path' do
+ it 'returns the relative path to the partial to be used' do
+ directory = build(:wiki_directory)
+
+ expect(directory.to_partial_path).to eq('projects/wikis/wiki_directory')
+ end
+ end
+end
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index 5c34b1b0a30..579ebac7afb 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -7,6 +7,75 @@ describe WikiPage, models: true do
subject { WikiPage.new(wiki) }
+ describe '.group_by_directory' do
+ context 'when there are no pages' do
+ it 'returns an empty array' do
+ expect(WikiPage.group_by_directory(nil)).to eq([])
+ expect(WikiPage.group_by_directory([])).to eq([])
+ end
+ end
+
+ context 'when there are pages' do
+ before do
+ create_page('dir_1/dir_1_1/page_3', 'content')
+ create_page('dir_1/page_2', 'content')
+ create_page('dir_2/page_5', 'content')
+ create_page('dir_2/page_4', 'content')
+ create_page('page_1', 'content')
+ end
+ let(:page_1) { wiki.find_page('page_1') }
+ let(:dir_1) do
+ WikiDirectory.new('dir_1', [wiki.find_page('dir_1/page_2')])
+ end
+ let(:dir_1_1) do
+ WikiDirectory.new('dir_1/dir_1_1', [wiki.find_page('dir_1/dir_1_1/page_3')])
+ end
+ let(:dir_2) do
+ pages = [wiki.find_page('dir_2/page_5'),
+ wiki.find_page('dir_2/page_4')]
+ WikiDirectory.new('dir_2', pages)
+ end
+
+ it 'returns an array with pages and directories' do
+ expected_grouped_entries = [page_1, dir_1, dir_1_1, dir_2]
+
+ grouped_entries = WikiPage.group_by_directory(wiki.pages)
+
+ grouped_entries.each_with_index do |page_or_dir, i|
+ expected_page_or_dir = expected_grouped_entries[i]
+ expected_slugs = get_slugs(expected_page_or_dir)
+ slugs = get_slugs(page_or_dir)
+
+ expect(slugs).to match_array(expected_slugs)
+ end
+ end
+
+ it 'returns an array sorted by alphabetical position' do
+ # Directories and pages within directories are sorted alphabetically.
+ # Pages at root come before everything.
+ expected_order = ['page_1', 'dir_1/page_2', 'dir_1/dir_1_1/page_3',
+ 'dir_2/page_4', 'dir_2/page_5']
+
+ grouped_entries = WikiPage.group_by_directory(wiki.pages)
+
+ actual_order =
+ grouped_entries.map do |page_or_dir|
+ get_slugs(page_or_dir)
+ end.
+ flatten
+ expect(actual_order).to eq(expected_order)
+ end
+ end
+ end
+
+ describe '.unhyphenize' do
+ it 'removes hyphens from a name' do
+ name = 'a-name--with-hyphens'
+
+ expect(WikiPage.unhyphenize(name)).to eq('a name with hyphens')
+ end
+ end
+
describe "#initialize" do
context "when initialized with an existing gollum page" do
before do
@@ -189,6 +258,26 @@ describe WikiPage, models: true do
end
end
+ describe '#directory' do
+ context 'when the page is at the root directory' do
+ it 'returns an empty string' do
+ create_page('file', 'content')
+ page = wiki.find_page('file')
+
+ expect(page.directory).to eq('')
+ end
+ end
+
+ context 'when the page is inside an actual directory' do
+ it 'returns the full directory hierarchy' do
+ create_page('dir_1/dir_1_1/file', 'content')
+ page = wiki.find_page('dir_1/dir_1_1/file')
+
+ expect(page.directory).to eq('dir_1/dir_1_1')
+ end
+ end
+ end
+
describe '#historical?' do
before do
create_page('Update', 'content')
@@ -221,6 +310,14 @@ describe WikiPage, models: true do
end
end
+ describe '#to_partial_path' do
+ it 'returns the relative path to the partial to be used' do
+ page = build(:wiki_page)
+
+ expect(page.to_partial_path).to eq('projects/wikis/wiki_page')
+ end
+ end
+
private
def remove_temp_repo(path)
@@ -239,4 +336,12 @@ describe WikiPage, models: true do
page = wiki.wiki.paged(title)
wiki.wiki.delete_page(page, commit_details)
end
+
+ def get_slugs(page_or_dir)
+ if page_or_dir.is_a? WikiPage
+ [page_or_dir.slug]
+ else
+ page_or_dir.pages.present? ? page_or_dir.pages.map(&:slug) : []
+ end
+ end
end
diff --git a/spec/requests/api/templates_spec.rb b/spec/requests/api/templates_spec.rb
index d32ba60fc4c..c0a8c0832bb 100644
--- a/spec/requests/api/templates_spec.rb
+++ b/spec/requests/api/templates_spec.rb
@@ -3,23 +3,23 @@ require 'spec_helper'
describe API::Templates, api: true do
include ApiHelpers
- shared_examples_for 'the Template Entity' do |path|
- before { get api(path) }
+ context 'the Template Entity' do
+ before { get api('/templates/gitignores/Ruby') }
it { expect(json_response['name']).to eq('Ruby') }
it { expect(json_response['content']).to include('*.gem') }
end
-
- shared_examples_for 'the TemplateList Entity' do |path|
- before { get api(path) }
+
+ context 'the TemplateList Entity' do
+ before { get api('/templates/gitignores') }
it { expect(json_response.first['name']).not_to be_nil }
it { expect(json_response.first['content']).to be_nil }
end
- shared_examples_for 'requesting gitignores' do |path|
+ context 'requesting gitignores' do
it 'returns a list of available gitignore templates' do
- get api(path)
+ get api('/templates/gitignores')
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -27,9 +27,9 @@ describe API::Templates, api: true do
end
end
- shared_examples_for 'requesting gitlab-ci-ymls' do |path|
+ context 'requesting gitlab-ci-ymls' do
it 'returns a list of available gitlab_ci_ymls' do
- get api(path)
+ get api('/templates/gitlab_ci_ymls')
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -37,17 +37,17 @@ describe API::Templates, api: true do
end
end
- shared_examples_for 'requesting gitlab-ci-yml for Ruby' do |path|
+ context 'requesting gitlab-ci-yml for Ruby' do
it 'adds a disclaimer on the top' do
- get api(path)
+ get api('/templates/gitlab_ci_ymls/Ruby')
expect(response).to have_http_status(200)
expect(json_response['content']).to start_with("# This file is a template,")
end
end
- shared_examples_for 'the License Template Entity' do |path|
- before { get api(path) }
+ context 'the License Template Entity' do
+ before { get api('/templates/licenses/mit') }
it 'returns a license template' do
expect(json_response['key']).to eq('mit')
@@ -64,9 +64,9 @@ describe API::Templates, api: true do
end
end
- shared_examples_for 'GET licenses' do |path|
+ context 'GET templates/licenses' do
it 'returns a list of available license templates' do
- get api(path)
+ get api('/templates/licenses')
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -77,7 +77,7 @@ describe API::Templates, api: true do
describe 'the popular parameter' do
context 'with popular=1' do
it 'returns a list of available popular license templates' do
- get api("#{path}?popular=1")
+ get api('/templates/licenses?popular=1')
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -88,10 +88,10 @@ describe API::Templates, api: true do
end
end
- shared_examples_for 'GET licenses/:name' do |path|
+ context 'GET templates/licenses/:name' do
context 'with :project and :fullname given' do
before do
- get api("#{path}/#{license_type}?project=My+Awesome+Project&fullname=Anton+#{license_type.upcase}")
+ get api("/templates/licenses/#{license_type}?project=My+Awesome+Project&fullname=Anton+#{license_type.upcase}")
end
context 'for the mit license' do
@@ -178,26 +178,4 @@ describe API::Templates, api: true do
end
end
end
-
- describe 'with /templates namespace' do
- it_behaves_like 'the Template Entity', '/templates/gitignores/Ruby'
- it_behaves_like 'the TemplateList Entity', '/templates/gitignores'
- it_behaves_like 'requesting gitignores', '/templates/gitignores'
- it_behaves_like 'requesting gitlab-ci-ymls', '/templates/gitlab_ci_ymls'
- it_behaves_like 'requesting gitlab-ci-yml for Ruby', '/templates/gitlab_ci_ymls/Ruby'
- it_behaves_like 'the License Template Entity', '/templates/licenses/mit'
- it_behaves_like 'GET licenses', '/templates/licenses'
- it_behaves_like 'GET licenses/:name', '/templates/licenses'
- end
-
- describe 'without /templates namespace' do
- it_behaves_like 'the Template Entity', '/gitignores/Ruby'
- it_behaves_like 'the TemplateList Entity', '/gitignores'
- it_behaves_like 'requesting gitignores', '/gitignores'
- it_behaves_like 'requesting gitlab-ci-ymls', '/gitlab_ci_ymls'
- it_behaves_like 'requesting gitlab-ci-yml for Ruby', '/gitlab_ci_ymls/Ruby'
- it_behaves_like 'the License Template Entity', '/licenses/mit'
- it_behaves_like 'GET licenses', '/licenses'
- it_behaves_like 'GET licenses/:name', '/licenses'
- end
end
diff --git a/spec/requests/api/v3/templates_spec.rb b/spec/requests/api/v3/templates_spec.rb
new file mode 100644
index 00000000000..4fd4e70bedd
--- /dev/null
+++ b/spec/requests/api/v3/templates_spec.rb
@@ -0,0 +1,203 @@
+require 'spec_helper'
+
+describe API::V3::Templates, api: true do
+ include ApiHelpers
+
+ shared_examples_for 'the Template Entity' do |path|
+ before { get v3_api(path) }
+
+ it { expect(json_response['name']).to eq('Ruby') }
+ it { expect(json_response['content']).to include('*.gem') }
+ end
+
+ shared_examples_for 'the TemplateList Entity' do |path|
+ before { get v3_api(path) }
+
+ it { expect(json_response.first['name']).not_to be_nil }
+ it { expect(json_response.first['content']).to be_nil }
+ end
+
+ shared_examples_for 'requesting gitignores' do |path|
+ it 'returns a list of available gitignore templates' do
+ get v3_api(path)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to be > 15
+ end
+ end
+
+ shared_examples_for 'requesting gitlab-ci-ymls' do |path|
+ it 'returns a list of available gitlab_ci_ymls' do
+ get v3_api(path)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['name']).not_to be_nil
+ end
+ end
+
+ shared_examples_for 'requesting gitlab-ci-yml for Ruby' do |path|
+ it 'adds a disclaimer on the top' do
+ get v3_api(path)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['content']).to start_with("# This file is a template,")
+ end
+ end
+
+ shared_examples_for 'the License Template Entity' do |path|
+ before { get v3_api(path) }
+
+ it 'returns a license template' do
+ expect(json_response['key']).to eq('mit')
+ expect(json_response['name']).to eq('MIT License')
+ expect(json_response['nickname']).to be_nil
+ expect(json_response['popular']).to be true
+ expect(json_response['html_url']).to eq('http://choosealicense.com/licenses/mit/')
+ expect(json_response['source_url']).to eq('https://opensource.org/licenses/MIT')
+ expect(json_response['description']).to include('A permissive license that is short and to the point.')
+ expect(json_response['conditions']).to eq(%w[include-copyright])
+ expect(json_response['permissions']).to eq(%w[commercial-use modifications distribution private-use])
+ expect(json_response['limitations']).to eq(%w[no-liability])
+ expect(json_response['content']).to include('The MIT License (MIT)')
+ end
+ end
+
+ shared_examples_for 'GET licenses' do |path|
+ it 'returns a list of available license templates' do
+ get v3_api(path)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(15)
+ expect(json_response.map { |l| l['key'] }).to include('agpl-3.0')
+ end
+
+ describe 'the popular parameter' do
+ context 'with popular=1' do
+ it 'returns a list of available popular license templates' do
+ get v3_api("#{path}?popular=1")
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(3)
+ expect(json_response.map { |l| l['key'] }).to include('apache-2.0')
+ end
+ end
+ end
+ end
+
+ shared_examples_for 'GET licenses/:name' do |path|
+ context 'with :project and :fullname given' do
+ before do
+ get v3_api("#{path}/#{license_type}?project=My+Awesome+Project&fullname=Anton+#{license_type.upcase}")
+ end
+
+ context 'for the mit license' do
+ let(:license_type) { 'mit' }
+
+ it 'returns the license text' do
+ expect(json_response['content']).to include('The MIT License (MIT)')
+ end
+
+ it 'replaces placeholder values' do
+ expect(json_response['content']).to include("Copyright (c) #{Time.now.year} Anton")
+ end
+ end
+
+ context 'for the agpl-3.0 license' do
+ let(:license_type) { 'agpl-3.0' }
+
+ it 'returns the license text' do
+ expect(json_response['content']).to include('GNU AFFERO GENERAL PUBLIC LICENSE')
+ end
+
+ it 'replaces placeholder values' do
+ expect(json_response['content']).to include('My Awesome Project')
+ expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton")
+ end
+ end
+
+ context 'for the gpl-3.0 license' do
+ let(:license_type) { 'gpl-3.0' }
+
+ it 'returns the license text' do
+ expect(json_response['content']).to include('GNU GENERAL PUBLIC LICENSE')
+ end
+
+ it 'replaces placeholder values' do
+ expect(json_response['content']).to include('My Awesome Project')
+ expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton")
+ end
+ end
+
+ context 'for the gpl-2.0 license' do
+ let(:license_type) { 'gpl-2.0' }
+
+ it 'returns the license text' do
+ expect(json_response['content']).to include('GNU GENERAL PUBLIC LICENSE')
+ end
+
+ it 'replaces placeholder values' do
+ expect(json_response['content']).to include('My Awesome Project')
+ expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton")
+ end
+ end
+
+ context 'for the apache-2.0 license' do
+ let(:license_type) { 'apache-2.0' }
+
+ it 'returns the license text' do
+ expect(json_response['content']).to include('Apache License')
+ end
+
+ it 'replaces placeholder values' do
+ expect(json_response['content']).to include("Copyright #{Time.now.year} Anton")
+ end
+ end
+
+ context 'for an uknown license' do
+ let(:license_type) { 'muth-over9000' }
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ context 'with no :fullname given' do
+ context 'with an authenticated user' do
+ let(:user) { create(:user) }
+
+ it 'replaces the copyright owner placeholder with the name of the current user' do
+ get v3_api('/templates/licenses/mit', user)
+
+ expect(json_response['content']).to include("Copyright (c) #{Time.now.year} #{user.name}")
+ end
+ end
+ end
+ end
+
+ describe 'with /templates namespace' do
+ it_behaves_like 'the Template Entity', '/templates/gitignores/Ruby'
+ it_behaves_like 'the TemplateList Entity', '/templates/gitignores'
+ it_behaves_like 'requesting gitignores', '/templates/gitignores'
+ it_behaves_like 'requesting gitlab-ci-ymls', '/templates/gitlab_ci_ymls'
+ it_behaves_like 'requesting gitlab-ci-yml for Ruby', '/templates/gitlab_ci_ymls/Ruby'
+ it_behaves_like 'the License Template Entity', '/templates/licenses/mit'
+ it_behaves_like 'GET licenses', '/templates/licenses'
+ it_behaves_like 'GET licenses/:name', '/templates/licenses'
+ end
+
+ describe 'without /templates namespace' do
+ it_behaves_like 'the Template Entity', '/gitignores/Ruby'
+ it_behaves_like 'the TemplateList Entity', '/gitignores'
+ it_behaves_like 'requesting gitignores', '/gitignores'
+ it_behaves_like 'requesting gitlab-ci-ymls', '/gitlab_ci_ymls'
+ it_behaves_like 'requesting gitlab-ci-yml for Ruby', '/gitlab_ci_ymls/Ruby'
+ it_behaves_like 'the License Template Entity', '/licenses/mit'
+ it_behaves_like 'GET licenses', '/licenses'
+ it_behaves_like 'GET licenses/:name', '/licenses'
+ end
+end
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 2cc21acab7b..983dac6efdb 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -287,41 +287,64 @@ describe MergeRequests::RefreshService, services: true do
it 'references the commit that caused the Work in Progress status' do
refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
-
allow(refresh_service).to receive(:find_new_commits)
refresh_service.instance_variable_set("@commits", [
- instance_double(
- Commit,
+ double(
id: 'aaaaaaa',
+ sha: '38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e',
short_id: 'aaaaaaa',
title: 'Fix issue',
work_in_progress?: false
),
- instance_double(
- Commit,
+ double(
id: 'bbbbbbb',
+ sha: '498214de67004b1da3d820901307bed2a68a8ef6',
short_id: 'bbbbbbb',
title: 'fixup! Fix issue',
work_in_progress?: true,
to_reference: 'bbbbbbb'
),
- instance_double(
- Commit,
+ double(
id: 'ccccccc',
+ sha: '1b12f15a11fc6e62177bef08f47bc7b5ce50b141',
short_id: 'ccccccc',
title: 'fixup! Fix issue',
work_in_progress?: true,
to_reference: 'ccccccc'
),
])
-
refresh_service.execute(@oldrev, @newrev, 'refs/heads/wip')
reload_mrs
-
expect(@merge_request.notes.last.note).to eq(
"marked as a **Work In Progress** from bbbbbbb"
)
end
+
+ it 'does not mark as WIP based on commits that do not belong to an MR' do
+ allow(refresh_service).to receive(:find_new_commits)
+ refresh_service.instance_variable_set("@commits", [
+ double(
+ id: 'aaaaaaa',
+ sha: 'aaaaaaa',
+ short_id: 'aaaaaaa',
+ title: 'Fix issue',
+ work_in_progress?: false
+ ),
+ double(
+ id: 'bbbbbbb',
+ sha: 'bbbbbbbb',
+ short_id: 'bbbbbbb',
+ title: 'fixup! Fix issue',
+ work_in_progress?: true,
+ to_reference: 'bbbbbbb'
+ )
+ ])
+
+ refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
+ reload_mrs
+
+ expect(@merge_request.work_in_progress?).to be_falsey
+ end
end
def reload_mrs
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 7cf2cd9968f..839250b7d84 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -146,6 +146,16 @@ describe NotificationService, services: true do
should_not_email(@u_lazy_participant)
end
+ it "emails the note author if they've opted into notifications about their activity" do
+ add_users_with_subscription(note.project, issue)
+ note.author.notified_of_own_activity = true
+ reset_delivered_emails!
+
+ notification.new_note(note)
+
+ should_email(note.author)
+ end
+
it 'filters out "mentioned in" notes' do
mentioned_note = SystemNoteService.cross_reference(mentioned_issue, issue, issue.author)
@@ -476,6 +486,20 @@ describe NotificationService, services: true do
should_not_email(issue.assignee)
end
+ it "emails the author if they've opted into notifications about their activity" do
+ issue.author.notified_of_own_activity = true
+
+ notification.new_issue(issue, issue.author)
+
+ should_email(issue.author)
+ end
+
+ it "doesn't email the author if they haven't opted into notifications about their activity" do
+ notification.new_issue(issue, issue.author)
+
+ should_not_email(issue.author)
+ end
+
it "emails subscribers of the issue's labels" do
user_1 = create(:user)
user_2 = create(:user)
@@ -665,6 +689,19 @@ describe NotificationService, services: true do
should_email(subscriber_to_label_2)
end
+ it "emails the current user if they've opted into notifications about their activity" do
+ subscriber_to_label_2.notified_of_own_activity = true
+ notification.relabeled_issue(issue, [group_label_2, label_2], subscriber_to_label_2)
+
+ should_email(subscriber_to_label_2)
+ end
+
+ it "doesn't email the current user if they haven't opted into notifications about their activity" do
+ notification.relabeled_issue(issue, [group_label_2, label_2], subscriber_to_label_2)
+
+ should_not_email(subscriber_to_label_2)
+ end
+
it "doesn't send email to anyone but subscribers of the given labels" do
notification.relabeled_issue(issue, [group_label_2, label_2], @u_disabled)
@@ -818,6 +855,20 @@ describe NotificationService, services: true do
should_not_email(@u_lazy_participant)
end
+ it "emails the author if they've opted into notifications about their activity" do
+ merge_request.author.notified_of_own_activity = true
+
+ notification.new_merge_request(merge_request, merge_request.author)
+
+ should_email(merge_request.author)
+ end
+
+ it "doesn't email the author if they haven't opted into notifications about their activity" do
+ notification.new_merge_request(merge_request, merge_request.author)
+
+ should_not_email(merge_request.author)
+ end
+
it "emails subscribers of the merge request's labels" do
user_1 = create(:user)
user_2 = create(:user)
@@ -1013,6 +1064,14 @@ describe NotificationService, services: true do
should_not_email(@u_watcher)
end
+ it "notifies the merger when merge_when_build_succeeds is false but they've opted into notifications about their activity" do
+ merge_request.merge_when_build_succeeds = false
+ @u_watcher.notified_of_own_activity = true
+ notification.merge_mr(merge_request, @u_watcher)
+
+ should_email(@u_watcher)
+ end
+
it_behaves_like 'participating notifications' do
let(:participant) { create(:user, username: 'user-participant') }
let(:issuable) { merge_request }