summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/boards/filtered_search_boards.js1
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_hint.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js4
-rw-r--r--app/assets/stylesheets/framework/filters.scss2
-rw-r--r--app/assets/stylesheets/framework/forms.scss11
-rw-r--r--app/assets/stylesheets/framework/selects.scss29
-rw-r--r--app/assets/stylesheets/framework/variables.scss1
-rw-r--r--app/controllers/projects/git_http_client_controller.rb46
-rw-r--r--app/controllers/projects/git_http_controller.rb2
-rw-r--r--app/finders/issuable_finder.rb13
-rw-r--r--app/helpers/projects_helper.rb23
-rw-r--r--app/models/user.rb29
-rw-r--r--app/services/users/refresh_authorized_projects_service.rb2
-rw-r--r--app/views/admin/broadcast_messages/_form.html.haml4
-rw-r--r--app/views/profiles/show.html.haml15
-rw-r--r--app/views/projects/_visibility_select.html.haml4
-rw-r--r--app/views/projects/commit/_commit_box.html.haml2
-rw-r--r--app/views/projects/edit.html.haml6
-rw-r--r--app/views/projects/project_members/_new_project_member.html.haml4
-rw-r--r--app/views/projects/project_members/_new_shared_group.html.haml2
-rw-r--r--changelogs/unreleased/12151-add-since-and-until-params-to-issuables.yml4
-rw-r--r--changelogs/unreleased/26212-upload-user-avatar-trough-api.yml4
-rw-r--r--changelogs/unreleased/27697-make-arrow-icons-consistent-in-dropdown.yml4
-rw-r--r--changelogs/unreleased/28139-use-color-input-broadcast-messages.yml4
-rw-r--r--changelogs/unreleased/30725-reset-user-limits-when-unchecking-external-user.yml4
-rw-r--r--changelogs/unreleased/33461-display-user-id.yml4
-rw-r--r--changelogs/unreleased/issue_33205.yml4
-rw-r--r--changelogs/unreleased/moved-submodules.yml4
-rw-r--r--changelogs/unreleased/mr-widget-memory-usage-tech-debt-fix.yml4
-rw-r--r--changelogs/unreleased/reduce-sidekiq-wait-timings.yml4
-rw-r--r--config/karma.config.js1
-rw-r--r--config/webpack.config.js23
-rw-r--r--doc/api/issues.md3
-rw-r--r--doc/api/merge_requests.md2
-rw-r--r--doc/api/users.md2
-rw-r--r--doc/user/permissions.md1
-rw-r--r--doc/user/project/integrations/img/merge_request_performance.pngbin66775 -> 60194 bytes
-rw-r--r--doc/user/project/integrations/prometheus.md8
-rw-r--r--lib/api/helpers/internal_helpers.rb7
-rw-r--r--lib/api/internal.rb2
-rw-r--r--lib/api/issues.rb2
-rw-r--r--lib/api/merge_requests.rb4
-rw-r--r--lib/api/users.rb1
-rw-r--r--lib/gitlab/current_settings.rb3
-rw-r--r--lib/gitlab/database/migration_helpers.rb6
-rw-r--r--lib/gitlab/git/gitmodules_parser.rb77
-rw-r--r--lib/gitlab/git/repository.rb52
-rw-r--r--lib/gitlab/git_access.rb21
-rw-r--r--lib/gitlab/job_waiter.rb2
-rw-r--r--lib/gitlab/repo_path.rb21
-rw-r--r--spec/features/milestones/milestones_spec.rb102
-rw-r--r--spec/finders/issues_finder_spec.rb22
-rw-r--r--spec/finders/merge_requests_finder_spec.rb42
-rw-r--r--spec/javascripts/project_title_spec.js76
-rw-r--r--spec/javascripts/test_bundle.js8
-rw-r--r--spec/lib/gitlab/git/gitmodules_parser_spec.rb28
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb8
-rw-r--r--spec/lib/gitlab/git_access_spec.rb43
-rw-r--r--spec/lib/gitlab/git_access_wiki_spec.rb3
-rw-r--r--spec/lib/gitlab/repo_path_spec.rb75
-rw-r--r--spec/models/user_spec.rb34
-rw-r--r--spec/requests/api/internal_spec.rb62
-rw-r--r--spec/requests/api/merge_requests_spec.rb26
-rw-r--r--spec/requests/api/users_spec.rb10
-rw-r--r--spec/requests/git_http_spec.rb49
-rw-r--r--spec/views/profiles/show.html.haml_spec.rb19
-rw-r--r--spec/workers/post_receive_spec.rb2
67 files changed, 760 insertions, 329 deletions
diff --git a/app/assets/javascripts/boards/filtered_search_boards.js b/app/assets/javascripts/boards/filtered_search_boards.js
index b37698fe9ca..3f083655f95 100644
--- a/app/assets/javascripts/boards/filtered_search_boards.js
+++ b/app/assets/javascripts/boards/filtered_search_boards.js
@@ -11,7 +11,6 @@ export default class FilteredSearchBoards extends gl.FilteredSearchManager {
// Issue boards is slightly different, we handle all the requests async
// instead or reloading the page, we just re-fire the list ajax requests
this.isHandledAsync = true;
-
this.cantEdit = cantEdit;
}
diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js b/app/assets/javascripts/filtered_search/dropdown_hint.js
index 2af242a69df..5838b1bdbb7 100644
--- a/app/assets/javascripts/filtered_search/dropdown_hint.js
+++ b/app/assets/javascripts/filtered_search/dropdown_hint.js
@@ -56,7 +56,7 @@ class DropdownHint extends gl.FilteredSearchDropdown {
}
renderContent() {
- const dropdownData = gl.FilteredSearchTokenKeys.get()
+ const dropdownData = this.tokenKeys.get()
.map(tokenKey => ({
icon: `fa-${tokenKey.icon}`,
hint: tokenKey.key,
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js
index 8155218681c..76cb71b6c12 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js
@@ -1,5 +1,5 @@
-import statusCodes from '~/lib/utils/http_status';
-import { bytesToMiB } from '~/lib/utils/number_utils';
+import statusCodes from '../../lib/utils/http_status';
+import { bytesToMiB } from '../../lib/utils/number_utils';
import MemoryGraph from '../../vue_shared/components/memory_graph';
import MRWidgetService from '../services/mr_widget_service';
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index cfbaaaa04c7..880ab52fa1b 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -152,7 +152,7 @@
}
.value-container {
- background-color: $filter-value-selected-color;
+ box-shadow: inset 0 0 0 100px $filtered-search-term-shadow-color;
}
}
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index a78179e727f..61e3897f369 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -125,10 +125,11 @@ label {
.select-wrapper {
position: relative;
- .fa-caret-down {
+ .fa-chevron-down {
position: absolute;
+ font-size: 10px;
right: 10px;
- top: 10px;
+ top: 12px;
color: $gray-darkest;
pointer-events: none;
}
@@ -138,6 +139,12 @@ label {
padding-left: 10px;
padding-right: 10px;
-webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+
+ &::-ms-expand {
+ display: none;
+ }
}
.form-control-inline {
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index 1b20c35ad98..40e654f4838 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -18,19 +18,28 @@
background-image: none;
background-color: transparent;
border: none;
- padding-top: 6px;
- padding-right: 10px;
+ padding-top: 12px;
+ padding-right: 20px;
+ font-size: 10px;
b {
- display: inline-block;
- width: 0;
- height: 0;
- margin-left: 2px;
- vertical-align: middle;
- border-top: 5px dashed;
- border-right: 5px solid transparent;
- border-left: 5px solid transparent;
+ display: none;
+ }
+
+ &::after {
+ content: "\f078";
+ position: absolute;
+ z-index: 1;
+ text-align: center;
+ pointer-events: none;
+ box-sizing: border-box;
color: $gray-darkest;
+ display: inline-block;
+ font: normal normal normal 14px/1 FontAwesome;
+ font-size: inherit;
+ text-rendering: auto;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
}
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 49ba0108228..71513199d84 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -282,6 +282,7 @@ $dropdown-toggle-active-border-color: darken($border-color, 14%);
/*
* Filtered Search
*/
+$filtered-search-term-shadow-color: rgba(0, 0, 0, 0.09);
$dropdown-hover-color: $blue-400;
/*
diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb
index 928f17e6a8e..7d0e2b3e2ef 100644
--- a/app/controllers/projects/git_http_client_controller.rb
+++ b/app/controllers/projects/git_http_client_controller.rb
@@ -4,7 +4,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController
include ActionController::HttpAuthentication::Basic
include KerberosSpnegoHelper
- attr_reader :authentication_result
+ attr_reader :authentication_result, :redirected_path
delegate :actor, :authentication_abilities, to: :authentication_result, allow_nil: true
@@ -14,7 +14,6 @@ class Projects::GitHttpClientController < Projects::ApplicationController
skip_before_action :verify_authenticity_token
skip_before_action :repository
before_action :authenticate_user
- before_action :ensure_project_found!
private
@@ -68,38 +67,14 @@ class Projects::GitHttpClientController < Projects::ApplicationController
headers['Www-Authenticate'] = challenges.join("\n") if challenges.any?
end
- def ensure_project_found!
- render_not_found if project.blank?
- end
-
def project
- return @project if defined?(@project)
-
- project_id, _ = project_id_with_suffix
- @project =
- if project_id.blank?
- nil
- else
- Project.find_by_full_path("#{params[:namespace_id]}/#{project_id}")
- end
- end
+ parse_repo_path unless defined?(@project)
- # This method returns two values so that we can parse
- # params[:project_id] (untrusted input!) in exactly one place.
- def project_id_with_suffix
- id = params[:project_id] || ''
-
- %w[.wiki.git .git].each do |suffix|
- if id.end_with?(suffix)
- # Be careful to only remove the suffix from the end of 'id'.
- # Accidentally removing it from the middle is how security
- # vulnerabilities happen!
- return [id.slice(0, id.length - suffix.length), suffix]
- end
- end
+ @project
+ end
- # Something is wrong with params[:project_id]; do not pass it on.
- [nil, nil]
+ def parse_repo_path
+ @project, @wiki, @redirected_path = Gitlab::RepoPath.parse("#{params[:namespace_id]}/#{params[:project_id]}")
end
def render_missing_personal_token
@@ -114,14 +89,9 @@ class Projects::GitHttpClientController < Projects::ApplicationController
end
def wiki?
- return @wiki if defined?(@wiki)
-
- _, suffix = project_id_with_suffix
- @wiki = suffix == '.wiki.git'
- end
+ parse_repo_path unless defined?(@wiki)
- def render_not_found
- render plain: 'Not Found', status: :not_found
+ @wiki
end
def handle_basic_authentication(login, password)
diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb
index b6b62da7b60..71ae60cb8cd 100644
--- a/app/controllers/projects/git_http_controller.rb
+++ b/app/controllers/projects/git_http_controller.rb
@@ -56,7 +56,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController
end
def access
- @access ||= access_klass.new(access_actor, project, 'http', authentication_abilities: authentication_abilities)
+ @access ||= access_klass.new(access_actor, project, 'http', authentication_abilities: authentication_abilities, redirected_path: redirected_path)
end
def access_actor
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 957ad875858..558f8b5e2e5 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -41,6 +41,7 @@ class IssuableFinder
items = by_iids(items)
items = by_milestone(items)
items = by_label(items)
+ items = by_created_at(items)
# Filtering by project HAS TO be the last because we use the project IDs yielded by the issuable query thus far
items = by_project(items)
@@ -402,6 +403,18 @@ class IssuableFinder
params[:non_archived].present? ? items.non_archived : items
end
+ def by_created_at(items)
+ if params[:created_after].present?
+ items = items.where(items.klass.arel_table[:created_at].gteq(params[:created_after]))
+ end
+
+ if params[:created_before].present?
+ items = items.where(items.klass.arel_table[:created_at].lteq(params[:created_before]))
+ end
+
+ items
+ end
+
def current_user_related?
params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me'
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 84c09a32987..0d0459f5a70 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -151,14 +151,21 @@ module ProjectsHelper
disabled: disabled_option
)
- content_tag(
- :select,
- options,
- name: "project[project_feature_attributes][#{field}]",
- id: "project_project_feature_attributes_#{field}",
- class: "pull-right form-control #{repo_children_classes(field)}",
- data: { field: field }
- ).html_safe
+ content_tag :div, class: "select-wrapper" do
+ concat(
+ content_tag(
+ :select,
+ options,
+ name: "project[project_feature_attributes][#{field}]",
+ id: "project_project_feature_attributes_#{field}",
+ class: "pull-right form-control select-control #{repo_children_classes(field)} ",
+ data: { field: field }
+ )
+ )
+ concat(
+ icon('chevron-down')
+ )
+ end.html_safe
end
def link_to_autodeploy_doc
diff --git a/app/models/user.rb b/app/models/user.rb
index 5d128e4b390..782c162e1f3 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -139,21 +139,21 @@ class User < ActiveRecord::Base
presence: true,
uniqueness: { case_sensitive: false }
- validate :namespace_uniq, if: ->(user) { user.username_changed? }
+ validate :namespace_uniq, if: :username_changed?
validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
- validate :unique_email, if: ->(user) { user.email_changed? }
- validate :owns_notification_email, if: ->(user) { user.notification_email_changed? }
- validate :owns_public_email, if: ->(user) { user.public_email_changed? }
+ validate :unique_email, if: :email_changed?
+ validate :owns_notification_email, if: :notification_email_changed?
+ validate :owns_public_email, if: :public_email_changed?
validate :signup_domain_valid?, on: :create, if: ->(user) { !user.created_by_id }
validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
before_validation :sanitize_attrs
- before_validation :set_notification_email, if: ->(user) { user.email_changed? }
- before_validation :set_public_email, if: ->(user) { user.public_email_changed? }
+ before_validation :set_notification_email, if: :email_changed?
+ before_validation :set_public_email, if: :public_email_changed?
- after_update :update_emails_with_primary_email, if: ->(user) { user.email_changed? }
+ after_update :update_emails_with_primary_email, if: :email_changed?
before_save :ensure_authentication_token, :ensure_incoming_email_token
- before_save :ensure_external_user_rights
+ before_save :ensure_user_rights_and_limits, if: :external_changed?
after_save :ensure_namespace_correct
after_initialize :set_projects_limit
after_destroy :post_destroy_hook
@@ -1033,11 +1033,14 @@ class User < ActiveRecord::Base
super
end
- def ensure_external_user_rights
- return unless external?
-
- self.can_create_group = false
- self.projects_limit = 0
+ def ensure_user_rights_and_limits
+ if external?
+ self.can_create_group = false
+ self.projects_limit = 0
+ else
+ self.can_create_group = gitlab_config.default_can_create_group
+ self.projects_limit = current_application_settings.default_projects_limit
+ end
end
def signup_domain_valid?
diff --git a/app/services/users/refresh_authorized_projects_service.rb b/app/services/users/refresh_authorized_projects_service.rb
index 3e07b811027..f028f5eb0d4 100644
--- a/app/services/users/refresh_authorized_projects_service.rb
+++ b/app/services/users/refresh_authorized_projects_service.rb
@@ -34,7 +34,7 @@ module Users
# Keep trying until we obtain the lease. If we don't do so we may end up
# not updating the list of authorized projects properly. To prevent
# hammering Redis too much we'll wait for a bit between retries.
- sleep(1)
+ sleep(0.1)
end
begin
diff --git a/app/views/admin/broadcast_messages/_form.html.haml b/app/views/admin/broadcast_messages/_form.html.haml
index 2269fb1fd8c..5a4ed1c3a2a 100644
--- a/app/views/admin/broadcast_messages/_form.html.haml
+++ b/app/views/admin/broadcast_messages/_form.html.haml
@@ -21,11 +21,11 @@
.form-group.js-toggle-colors-container.hide
= f.label :color, "Background Color", class: 'control-label'
.col-sm-10
- = f.text_field :color, class: "form-control"
+ = f.color_field :color, class: "form-control"
.form-group.js-toggle-colors-container.hide
= f.label :font, "Font Color", class: 'control-label'
.col-sm-10
- = f.text_field :font, class: "form-control"
+ = f.color_field :font, class: "form-control"
.form-group
= f.label :starts_at, class: 'control-label'
.col-sm-10.datetime-controls
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index fcfd350f0da..15672289c65 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -42,10 +42,17 @@
- if current_user.ldap_user?
Some options are unavailable for LDAP accounts
.col-lg-9
- .form-group
- = f.label :name, class: "label-light"
- = f.text_field :name, class: "form-control", required: true
- %span.help-block Enter your name, so people you know can recognize you.
+ .row
+ .form-group.col-md-9
+ = f.label :name, class: "label-light"
+ = f.text_field :name, class: "form-control", required: true
+ %span.help-block Enter your name, so people you know can recognize you.
+
+ .form-group.col-md-3
+ = f.label :id, class: 'label-light' do
+ User ID
+ = f.text_field :id, class: 'form-control', readonly: true
+
.form-group
= f.label :email, class: "label-light"
diff --git a/app/views/projects/_visibility_select.html.haml b/app/views/projects/_visibility_select.html.haml
index 65fc0a36ca9..4026b9e3c46 100644
--- a/app/views/projects/_visibility_select.html.haml
+++ b/app/views/projects/_visibility_select.html.haml
@@ -1,5 +1,7 @@
- if can_change_visibility_level?(@project, current_user)
- = form.select(model_method, visibility_select_options(@project, selected_level), {}, class: 'form-control visibility-select')
+ .select-wrapper
+ = form.select(model_method, visibility_select_options(@project, selected_level), {}, class: 'form-control visibility-select select-control')
+ = icon('chevron-down')
- else
.info.js-locked{ data: { help_block: visibility_level_description(@project.visibility_level, @project) } }
= visibility_level_icon(@project.visibility_level)
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index df97b4d9c13..7fe44816bae 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -6,7 +6,7 @@
= clipboard_button(text: @commit.id, title: _("Copy commit SHA to clipboard"))
%span.hidden-xs authored
#{time_ago_with_tooltip(@commit.authored_date)}
- %span= _('ByAuthor|by')
+ %span= s_('ByAuthor|by')
= author_avatar(@commit, size: 24)
%strong
= commit_author_link(@commit, avatar: true, size: 24)
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index c3dab68cea5..296e37e20e6 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -99,9 +99,9 @@
Git Large File Storage
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
.col-md-3
- = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control project-repo-select', data: { field: 'lfs_enabled' }
-
-
+ .select-wrapper
+ = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control project-repo-select select-control', data: { field: 'lfs_enabled' }
+ = icon('chevron-down')
- if Gitlab.config.registry.enabled
.form-group.js-container-registry{ style: ("display: none;" if @project.project_feature.send(:repository_access_level) == 0) }
.checkbox
diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml
index 247c4bdbe2d..8bf2246662a 100644
--- a/app/views/projects/project_members/_new_project_member.html.haml
+++ b/app/views/projects/project_members/_new_project_member.html.haml
@@ -6,7 +6,9 @@
= users_select_tag(:user_ids, multiple: true, class: "input-clamp", scope: :all, email_user: true, placeholder: "Search for members to update or invite")
.form-group
= label_tag :access_level, "Choose a role permission", class: "label-light"
- = select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "form-control project-access-select"
+ .select-wrapper
+ = select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "form-control project-access-select select-control"
+ = icon('chevron-down')
.help-block.append-bottom-10
= link_to "Read more", help_page_path("user/permissions"), class: "vlink"
about role permissions
diff --git a/app/views/projects/project_members/_new_shared_group.html.haml b/app/views/projects/project_members/_new_shared_group.html.haml
index b7cc8dd7062..643569db646 100644
--- a/app/views/projects/project_members/_new_shared_group.html.haml
+++ b/app/views/projects/project_members/_new_shared_group.html.haml
@@ -8,7 +8,7 @@
= label_tag :link_group_access, "Max access level", class: "label-light"
.select-wrapper
= select_tag :link_group_access, options_for_select(ProjectGroupLink.access_options, ProjectGroupLink.default_access), class: "form-control select-control"
- = icon('caret-down')
+ = icon('chevron-down')
.help-block.append-bottom-10
= link_to "Read more", help_page_path("user/permissions"), class: "vlink"
about role permissions
diff --git a/changelogs/unreleased/12151-add-since-and-until-params-to-issuables.yml b/changelogs/unreleased/12151-add-since-and-until-params-to-issuables.yml
new file mode 100644
index 00000000000..2c915e62357
--- /dev/null
+++ b/changelogs/unreleased/12151-add-since-and-until-params-to-issuables.yml
@@ -0,0 +1,4 @@
+---
+title: Added "created_after" and "created_before" params to issuables
+merge_request: 12151
+author: Kyle Bishop @kybishop
diff --git a/changelogs/unreleased/26212-upload-user-avatar-trough-api.yml b/changelogs/unreleased/26212-upload-user-avatar-trough-api.yml
new file mode 100644
index 00000000000..667454ae95d
--- /dev/null
+++ b/changelogs/unreleased/26212-upload-user-avatar-trough-api.yml
@@ -0,0 +1,4 @@
+---
+title: Accept image for avatar in user API
+merge_request: 12143
+author: Ivan Chernov
diff --git a/changelogs/unreleased/27697-make-arrow-icons-consistent-in-dropdown.yml b/changelogs/unreleased/27697-make-arrow-icons-consistent-in-dropdown.yml
new file mode 100644
index 00000000000..92b5b59f46f
--- /dev/null
+++ b/changelogs/unreleased/27697-make-arrow-icons-consistent-in-dropdown.yml
@@ -0,0 +1,4 @@
+---
+title: Use fa-chevron-down on dropdown arrows for consistency
+merge_request: 9659
+author: TM Lee
diff --git a/changelogs/unreleased/28139-use-color-input-broadcast-messages.yml b/changelogs/unreleased/28139-use-color-input-broadcast-messages.yml
new file mode 100644
index 00000000000..97ebabaff1c
--- /dev/null
+++ b/changelogs/unreleased/28139-use-color-input-broadcast-messages.yml
@@ -0,0 +1,4 @@
+---
+title: Use color inputs for broadcast messages
+merge_request:
+author:
diff --git a/changelogs/unreleased/30725-reset-user-limits-when-unchecking-external-user.yml b/changelogs/unreleased/30725-reset-user-limits-when-unchecking-external-user.yml
new file mode 100644
index 00000000000..3058404b3f8
--- /dev/null
+++ b/changelogs/unreleased/30725-reset-user-limits-when-unchecking-external-user.yml
@@ -0,0 +1,4 @@
+---
+title: Ensures default user limits when external user is unchecked
+merge_request: 12218
+author:
diff --git a/changelogs/unreleased/33461-display-user-id.yml b/changelogs/unreleased/33461-display-user-id.yml
new file mode 100644
index 00000000000..cba94625b07
--- /dev/null
+++ b/changelogs/unreleased/33461-display-user-id.yml
@@ -0,0 +1,4 @@
+---
+title: Display own user id in account settings page
+merge_request: 12141
+author: Riccardo Padovani
diff --git a/changelogs/unreleased/issue_33205.yml b/changelogs/unreleased/issue_33205.yml
new file mode 100644
index 00000000000..54b442048d8
--- /dev/null
+++ b/changelogs/unreleased/issue_33205.yml
@@ -0,0 +1,4 @@
+---
+title: Fix API bug accepting wrong parameter to create merge request
+merge_request:
+author:
diff --git a/changelogs/unreleased/moved-submodules.yml b/changelogs/unreleased/moved-submodules.yml
new file mode 100644
index 00000000000..eee858717ed
--- /dev/null
+++ b/changelogs/unreleased/moved-submodules.yml
@@ -0,0 +1,4 @@
+---
+title: 'Handle renamed submodules in repository browser'
+merge_request: 10798
+author: David Turner
diff --git a/changelogs/unreleased/mr-widget-memory-usage-tech-debt-fix.yml b/changelogs/unreleased/mr-widget-memory-usage-tech-debt-fix.yml
new file mode 100644
index 00000000000..14b5493a246
--- /dev/null
+++ b/changelogs/unreleased/mr-widget-memory-usage-tech-debt-fix.yml
@@ -0,0 +1,4 @@
+---
+title: Changed utilities imports from ~ to relative paths
+merge_request:
+author:
diff --git a/changelogs/unreleased/reduce-sidekiq-wait-timings.yml b/changelogs/unreleased/reduce-sidekiq-wait-timings.yml
new file mode 100644
index 00000000000..4d23accc82e
--- /dev/null
+++ b/changelogs/unreleased/reduce-sidekiq-wait-timings.yml
@@ -0,0 +1,4 @@
+---
+title: Reduce time spent waiting for certain Sidekiq jobs to complete
+merge_request:
+author:
diff --git a/config/karma.config.js b/config/karma.config.js
index 978850e5d70..5911a9a7e10 100644
--- a/config/karma.config.js
+++ b/config/karma.config.js
@@ -54,6 +54,7 @@ module.exports = function(config) {
subdir: '.',
fixWebpackSourcePaths: true
};
+ karmaConfig.browserNoActivityTimeout = 60000; // 60 seconds
}
if (process.env.DEBUG) {
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 3c2455ebf35..fb91ffef7e7 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -11,22 +11,13 @@ var WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeMod
var ROOT_PATH = path.resolve(__dirname, '..');
var IS_PRODUCTION = process.env.NODE_ENV === 'production';
-var IS_DEV_SERVER = process.argv[1].indexOf('webpack-dev-server') !== -1;
+var IS_DEV_SERVER = process.argv.join(' ').indexOf('webpack-dev-server') !== -1;
var DEV_SERVER_HOST = process.env.DEV_SERVER_HOST || 'localhost';
var DEV_SERVER_PORT = parseInt(process.env.DEV_SERVER_PORT, 10) || 3808;
var DEV_SERVER_LIVERELOAD = process.env.DEV_SERVER_LIVERELOAD !== 'false';
var WEBPACK_REPORT = process.env.WEBPACK_REPORT;
var NO_COMPRESSION = process.env.NO_COMPRESSION;
-// optional dependency `node-zopfli` is unavailable on CentOS 6
-var ZOPFLI_AVAILABLE;
-try {
- require.resolve('node-zopfli');
- ZOPFLI_AVAILABLE = true;
-} catch(err) {
- ZOPFLI_AVAILABLE = false;
-}
-
var config = {
// because sqljs requires fs.
node: {
@@ -233,12 +224,12 @@ if (IS_PRODUCTION) {
// zopfli requires a lot of compute time and is disabled in CI
if (!NO_COMPRESSION) {
- config.plugins.push(
- new CompressionPlugin({
- asset: '[path].gz[query]',
- algorithm: ZOPFLI_AVAILABLE ? 'zopfli' : 'gzip',
- })
- );
+ // gracefully fall back to gzip if `node-zopfli` is unavailable (e.g. in CentOS 6)
+ try {
+ config.plugins.push(new CompressionPlugin({ algorithm: 'zopfli' }));
+ } catch(err) {
+ config.plugins.push(new CompressionPlugin({ algorithm: 'gzip' }));
+ }
}
}
diff --git a/doc/api/issues.md b/doc/api/issues.md
index 3f949ca5667..df5666bb7b6 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -221,7 +221,8 @@ GET /projects/:id/issues?search=issue+title+or+description
| `order_by` | string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Search project issues against their `title` and `description` |
-
+| `created_after` | datetime | no | Return issues created after the given time (inclusive) |
+| `created_before` | datetime | no | Return issues created before the given time (inclusive) |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index cb22b67f556..3dc808c196d 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -26,6 +26,8 @@ Parameters:
| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
| `milestone` | string | no | Return merge requests for a specific milestone |
| `labels` | string | no | Return merge requests matching a comma separated list of labels |
+| `created_after` | datetime | no | Return merge requests created after the given time (inclusive) |
+| `created_before` | datetime | no | Return merge requests created before the given time (inclusive) |
```json
[
diff --git a/doc/api/users.md b/doc/api/users.md
index 91ce4f6dac3..b1ebd7b0c47 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -251,6 +251,7 @@ Parameters:
- `can_create_group` (optional) - User can create groups - true or false
- `confirm` (optional) - Require confirmation - true (default) or false
- `external` (optional) - Flags the user as external - true or false(default)
+- `avatar` (optional) - Image file for user's avatar
## User modification
@@ -279,6 +280,7 @@ Parameters:
- `admin` (optional) - User is admin - true or false (default)
- `can_create_group` (optional) - User can create groups - true or false
- `external` (optional) - Flags the user as external - true or false(default)
+- `avatar` (optional) - Image file for user's avatar
On password update, user will be forced to change it upon next login.
Note, at the moment this method does only return a `404` error,
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 3fda47b9e34..3d47e644ad2 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -89,6 +89,7 @@ group.
| Create project in group | | | | ✓ | ✓ |
| Manage group members | | | | | ✓ |
| Remove group | | | | | ✓ |
+| Manage group labels | | ✓ | ✓ | ✓ | ✓ |
## External Users
diff --git a/doc/user/project/integrations/img/merge_request_performance.png b/doc/user/project/integrations/img/merge_request_performance.png
index 93b2626fed7..eba6515a6ae 100644
--- a/doc/user/project/integrations/img/merge_request_performance.png
+++ b/doc/user/project/integrations/img/merge_request_performance.png
Binary files differ
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index d3fb5916dc6..86ceb14b965 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -167,15 +167,15 @@ environment which has had a successful deployment.
## Determining the performance impact of a merge
> [Introduced][ce-10408] in GitLab 9.2.
+> GitLab 9.3 added the [numeric comparison](https://gitlab.com/gitlab-org/gitlab-ce/issues/27439) of the 30 minute averages.
Developers can view the performance impact of their changes within the merge
-request workflow. When a source branch has been deployed to an environment, a
-sparkline will appear showing the average memory consumption of the app. The dot
+request workflow. When a source branch has been deployed to an environment, a sparkline and numeric comparison of the average memory consumption will appear. On the sparkline, a dot
indicates when the current changes were deployed, with up to 30 minutes of
-performance data displayed before and after. The sparkline will be updated after
+performance data displayed before and after. The comparison shows the difference between the 30 minute average before and after the deployment. This information is updated after
each commit has been deployed.
-Once merged and the target branch has been redeployed, the sparkline will switch
+Once merged and the target branch has been redeployed, the metrics will switch
to show the new environments this revision has been deployed to.
Performance data will be available for the duration it is persisted on the
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index d3732d67622..5e9cf5e68b1 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -10,6 +10,10 @@ module API
set_project unless defined?(@project)
@project
end
+
+ def redirected_path
+ @redirected_path
+ end
def ssh_authentication_abilities
[
@@ -38,8 +42,9 @@ module API
def set_project
if params[:gl_repository]
@project, @wiki = Gitlab::GlRepository.parse(params[:gl_repository])
+ @redirected_path = nil
else
- @project, @wiki = Gitlab::RepoPath.parse(params[:project])
+ @project, @wiki, @redirected_path = Gitlab::RepoPath.parse(params[:project])
end
end
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index ecd6d672cf7..9ec418edea4 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -34,7 +34,7 @@ module API
access_checker_klass = wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess
access_checker = access_checker_klass
- .new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities)
+ .new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities, redirected_path: redirected_path)
begin
access_checker.check(params[:action], params[:changes])
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 78db960ae28..09dca0dff8b 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -27,6 +27,8 @@ module API
optional :milestone, type: String, desc: 'Return issues for a specific milestone'
optional :iids, type: Array[Integer], desc: 'The IID array of issues'
optional :search, type: String, desc: 'Search issues for text present in the title or description'
+ optional :created_after, type: DateTime, desc: 'Return issues created after the specified time'
+ optional :created_before, type: DateTime, desc: 'Return issues created before the specified time'
use :pagination
end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 710deba5ae3..1118fc7465b 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -72,6 +72,8 @@ module API
optional :iids, type: Array[Integer], desc: 'The IID array of merge requests'
optional :milestone, type: String, desc: 'Return merge requests for a specific milestone'
optional :labels, type: String, desc: 'Comma-separated list of label names'
+ optional :created_after, type: DateTime, desc: 'Return merge requests created after the specified time'
+ optional :created_before, type: DateTime, desc: 'Return merge requests created before the specified time'
use :pagination
end
get ":id/merge_requests" do
@@ -97,7 +99,7 @@ module API
authorize! :create_merge_request, user_project
mr_params = declared_params(include_missing: false)
- mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch) if mr_params[:remove_source_branch].present?
+ mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch)
merge_request = ::MergeRequests::CreateService.new(user_project, current_user, mr_params).execute
diff --git a/lib/api/users.rb b/lib/api/users.rb
index dda64715ee1..7257ecb5b67 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -29,6 +29,7 @@ module API
optional :can_create_group, type: Boolean, desc: 'Flag indicating the user can create groups'
optional :skip_confirmation, type: Boolean, default: false, desc: 'Flag indicating the account is confirmed'
optional :external, type: Boolean, desc: 'Flag indicating the user is an external user'
+ optional :avatar, type: File, desc: 'Avatar image for user'
all_or_none_of :extern_uid, :provider
end
end
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index 48735fd197d..2fd5452dd78 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -62,8 +62,7 @@ module Gitlab
active_db_connection = ActiveRecord::Base.connection.active? rescue false
active_db_connection &&
- ActiveRecord::Base.connection.table_exists?('application_settings') &&
- !ActiveRecord::Migrator.needs_migration?
+ ActiveRecord::Base.connection.table_exists?('application_settings')
rescue ActiveRecord::NoDatabaseError
false
end
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index cd85f961242..9181202a091 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -233,6 +233,12 @@ module Gitlab
# Update in batches of 5% until we run out of any rows to update.
batch_size = ((total / 100.0) * 5.0).ceil
+ max_size = 1000
+
+ # The upper limit is 1000 to ensure we don't lock too many rows. For
+ # example, for "merge_requests" even 1% of the table is around 35 000
+ # rows for GitLab.com.
+ batch_size = max_size if batch_size > max_size
start_arel = table.project(table[:id]).order(table[:id].asc).take(1)
start_arel = yield table, start_arel if block_given?
diff --git a/lib/gitlab/git/gitmodules_parser.rb b/lib/gitlab/git/gitmodules_parser.rb
new file mode 100644
index 00000000000..f4e3b5e5129
--- /dev/null
+++ b/lib/gitlab/git/gitmodules_parser.rb
@@ -0,0 +1,77 @@
+module Gitlab
+ module Git
+ class GitmodulesParser
+ def initialize(content)
+ @content = content
+ end
+
+ # Parses the contents of a .gitmodules file and returns a hash of
+ # submodule information, indexed by path.
+ def parse
+ reindex_by_path(get_submodules_by_name)
+ end
+
+ private
+
+ class State
+ def initialize
+ @result = {}
+ @current_submodule = nil
+ end
+
+ def start_section(section)
+ # In some .gitmodules files (e.g. nodegit's), a header
+ # with the same name appears multiple times; we want to
+ # accumulate the configs across these
+ @current_submodule = @result[section] || { 'name' => section }
+ @result[section] = @current_submodule
+ end
+
+ def set_attribute(attr, value)
+ @current_submodule[attr] = value
+ end
+
+ def section_started?
+ !@current_submodule.nil?
+ end
+
+ def submodules_by_name
+ @result
+ end
+ end
+
+ def get_submodules_by_name
+ iterator = State.new
+
+ @content.split("\n").each_with_object(iterator) do |text, iterator|
+ next if text =~ /^\s*#/
+
+ if text =~ /\A\[submodule "(?<name>[^"]+)"\]\z/
+ iterator.start_section($~[:name])
+ else
+ next unless iterator.section_started?
+
+ next unless text =~ /\A\s*(?<key>\w+)\s*=\s*(?<value>.*)\z/
+
+ value = $~[:value].chomp
+ iterator.set_attribute($~[:key], value)
+ end
+ end
+
+ iterator.submodules_by_name
+ end
+
+ def reindex_by_path(submodules_by_name)
+ # Convert from an indexed by name to an array indexed by path
+ # If a submodule doesn't have a path, it is considered bogus
+ # and is ignored
+ submodules_by_name.each_with_object({}) do |(name, data), results|
+ path = data.delete 'path'
+ next unless path
+
+ results[path] = data
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 85695d0a4df..c1f942f931a 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -617,9 +617,9 @@ module Gitlab
#
# Ex.
# {
- # "rack" => {
+ # "current_path/rack" => {
+ # "name" => "original_path/rack",
# "id" => "c67be4624545b4263184c4a0e8f887efd0a66320",
- # "path" => "rack",
# "url" => "git://github.com/chneukirchen/rack.git"
# },
# "encoding" => {
@@ -637,7 +637,8 @@ module Gitlab
return {}
end
- parse_gitmodules(commit, content)
+ parser = GitmodulesParser.new(content)
+ fill_submodule_ids(commit, parser.parse)
end
# Return total commits count accessible from passed ref
@@ -998,42 +999,19 @@ module Gitlab
end
end
- # Parses the contents of a .gitmodules file and returns a hash of
- # submodule information.
- def parse_gitmodules(commit, content)
- modules = {}
-
- name = nil
- content.each_line do |line|
- case line.strip
- when /\A\[submodule "(?<name>[^"]+)"\]\z/ # Submodule header
- name = $~[:name]
- modules[name] = {}
- when /\A(?<key>\w+)\s*=\s*(?<value>.*)\z/ # Key/value pair
- key = $~[:key]
- value = $~[:value].chomp
-
- next unless name && modules[name]
-
- modules[name][key] = value
-
- if key == 'path'
- begin
- modules[name]['id'] = blob_content(commit, value)
- rescue InvalidBlobName
- # The current entry is invalid
- modules.delete(name)
- name = nil
- end
- end
- when /\A#/ # Comment
- next
- else # Invalid line
- name = nil
+ # Fill in the 'id' field of a submodule hash from its values
+ # as-of +commit+. Return a Hash consisting only of entries
+ # from the submodule hash for which the 'id' field is filled.
+ def fill_submodule_ids(commit, submodule_data)
+ submodule_data.each do |path, data|
+ id = begin
+ blob_content(commit, path)
+ rescue InvalidBlobName
+ nil
end
+ data['id'] = id
end
-
- modules
+ submodule_data.select { |path, data| data['id'] }
end
# Returns true if +commit+ introduced changes to +path+, using commit
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index 0a19d24eb20..0b62911958d 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -22,12 +22,13 @@ module Gitlab
PUSH_COMMANDS = %w{ git-receive-pack }.freeze
ALL_COMMANDS = DOWNLOAD_COMMANDS + PUSH_COMMANDS
- attr_reader :actor, :project, :protocol, :authentication_abilities
+ attr_reader :actor, :project, :protocol, :authentication_abilities, :redirected_path
- def initialize(actor, project, protocol, authentication_abilities:)
+ def initialize(actor, project, protocol, authentication_abilities:, redirected_path: nil)
@actor = actor
@project = project
@protocol = protocol
+ @redirected_path = redirected_path
@authentication_abilities = authentication_abilities
end
@@ -35,6 +36,7 @@ module Gitlab
check_protocol!
check_active_user!
check_project_accessibility!
+ check_project_moved!
check_command_disabled!(cmd)
check_command_existence!(cmd)
check_repository_existence!
@@ -87,6 +89,21 @@ module Gitlab
end
end
+ def check_project_moved!
+ if redirected_path
+ url = protocol == 'ssh' ? project.ssh_url_to_repo : project.http_url_to_repo
+ message = <<-MESSAGE.strip_heredoc
+ Project '#{redirected_path}' was moved to '#{project.full_path}'.
+
+ Please update your Git remote and try again:
+
+ git remote set-url origin #{url}
+ MESSAGE
+
+ raise NotFoundError, message
+ end
+ end
+
def check_command_disabled!(cmd)
if upload_pack?(cmd)
check_upload_pack_disabled!
diff --git a/lib/gitlab/job_waiter.rb b/lib/gitlab/job_waiter.rb
index 8db91d25a4b..208f0e1bbea 100644
--- a/lib/gitlab/job_waiter.rb
+++ b/lib/gitlab/job_waiter.rb
@@ -14,7 +14,7 @@ module Gitlab
# timeout - The maximum amount of seconds to block the caller for. This
# ensures we don't indefinitely block a caller in case a job takes
# long to process, or is never processed.
- def wait(timeout = 60)
+ def wait(timeout = 10)
start = Time.current
while (Time.current - start) <= timeout
diff --git a/lib/gitlab/repo_path.rb b/lib/gitlab/repo_path.rb
index 878e03f61d7..3591fa9145e 100644
--- a/lib/gitlab/repo_path.rb
+++ b/lib/gitlab/repo_path.rb
@@ -3,16 +3,18 @@ module Gitlab
NotFoundError = Class.new(StandardError)
def self.parse(repo_path)
+ wiki = false
project_path = strip_storage_path(repo_path.sub(/\.git\z/, ''), fail_on_not_found: false)
- project = Project.find_by_full_path(project_path)
- if project_path.end_with?('.wiki') && !project
- project = Project.find_by_full_path(project_path.chomp('.wiki'))
+ project, was_redirected = find_project(project_path)
+
+ if project_path.end_with?('.wiki') && project.nil?
+ project, was_redirected = find_project(project_path.chomp('.wiki'))
wiki = true
- else
- wiki = false
end
- [project, wiki]
+ redirected_path = project_path if was_redirected
+
+ [project, wiki, redirected_path]
end
def self.strip_storage_path(repo_path, fail_on_not_found: true)
@@ -30,5 +32,12 @@ module Gitlab
result.sub(/\A\/*/, '')
end
+
+ def self.find_project(project_path)
+ project = Project.find_by_full_path(project_path, follow_redirects: true)
+ was_redirected = project && project.full_path.casecmp(project_path) != 0
+
+ [project, was_redirected]
+ end
end
end
diff --git a/spec/features/milestones/milestones_spec.rb b/spec/features/milestones/milestones_spec.rb
deleted file mode 100644
index 9e117c8ed9f..00000000000
--- a/spec/features/milestones/milestones_spec.rb
+++ /dev/null
@@ -1,102 +0,0 @@
-# require 'rails_helper'
-
-# describe 'Milestone draggable', feature: true, js: true do
-# include DragTo
-
-# let(:milestone) { create(:milestone, project: project, title: 8.14) }
-# let(:project) { create(:empty_project, :public) }
-# let(:user) { create(:user) }
-
-# context 'issues' do
-# let(:issue) { page.find_by_id('issues-list-unassigned').find('li') }
-# let(:issue_target) { page.find_by_id('issues-list-ongoing') }
-
-# it 'does not allow guest to drag issue' do
-# create_and_drag_issue
-
-# expect(issue_target).not_to have_selector('.issuable-row')
-# end
-
-# it 'does not allow authorized user to drag issue' do
-# login_as(user)
-# create_and_drag_issue
-
-# expect(issue_target).not_to have_selector('.issuable-row')
-# end
-
-# it 'allows author to drag issue' do
-# login_as(user)
-# create_and_drag_issue(author: user)
-
-# expect(issue_target).to have_selector('.issuable-row')
-# end
-
-# it 'allows admin to drag issue' do
-# login_as(:admin)
-
-# create_and_drag_issue
-
-# expect(issue_target).to have_selector('.issuable-row')
-# end
-# end
-
-# context 'merge requests' do
-# let(:merge_request) { page.find_by_id('merge_requests-list-unassigned').find('li') }
-# let(:merge_request_target) { page.find_by_id('merge_requests-list-ongoing') }
-
-# it 'does not allow guest to drag merge request' do
-# create_and_drag_merge_request
-
-# expect(merge_request_target).not_to have_selector('.issuable-row')
-# end
-
-# it 'does not allow authorized user to drag merge request' do
-# login_as(user)
-# create_and_drag_merge_request
-
-# expect(merge_request_target).not_to have_selector('.issuable-row')
-# end
-
-# it 'allows author to drag merge request' do
-# login_as(user)
-# create_and_drag_merge_request(author: user)
-
-# expect(merge_request_target).to have_selector('.issuable-row')
-# end
-
-# it 'allows admin to drag merge request' do
-# login_as(:admin)
-# create_and_drag_merge_request
-
-# expect(merge_request_target).to have_selector('.issuable-row')
-# end
-# end
-
-# def create_and_drag_issue(params = {})
-# create(:issue, params.merge(title: 'Foo', project: project, milestone: milestone))
-
-# visit namespace_project_milestone_path(project.namespace, project, milestone)
-# scroll_into_view('.milestone-content')
-# drag_to(selector: '.issues-sortable-list', list_to_index: 1)
-
-# wait_for_requests
-# end
-
-# def create_and_drag_merge_request(params = {})
-# create(:merge_request, params.merge(title: 'Foo', source_project: project, target_project: project, milestone: milestone))
-
-# visit namespace_project_milestone_path(project.namespace, project, milestone)
-# page.find("a[href='#tab-merge-requests']").click
-
-# wait_for_requests
-
-# scroll_into_view('.milestone-content')
-# drag_to(selector: '.merge_requests-sortable-list', list_to_index: 1)
-
-# wait_for_requests
-# end
-
-# def scroll_into_view(selector)
-# page.evaluate_script("document.querySelector('#{selector}').scrollIntoView();")
-# end
-# end
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index 8f2d60f2f1b..8ace1fb5751 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -7,9 +7,9 @@ describe IssuesFinder do
set(:project2) { create(:empty_project) }
set(:milestone) { create(:milestone, project: project1) }
set(:label) { create(:label, project: project2) }
- set(:issue1) { create(:issue, author: user, assignees: [user], project: project1, milestone: milestone, title: 'gitlab') }
+ set(:issue1) { create(:issue, author: user, assignees: [user], project: project1, milestone: milestone, title: 'gitlab', created_at: 1.week.ago) }
set(:issue2) { create(:issue, author: user, assignees: [user], project: project2, description: 'gitlab') }
- set(:issue3) { create(:issue, author: user2, assignees: [user2], project: project2, title: 'tanuki', description: 'tanuki') }
+ set(:issue3) { create(:issue, author: user2, assignees: [user2], project: project2, title: 'tanuki', description: 'tanuki', created_at: 1.week.from_now) }
describe '#execute' do
set(:closed_issue) { create(:issue, author: user2, assignees: [user2], project: project2, state: 'closed') }
@@ -215,6 +215,24 @@ describe IssuesFinder do
end
end
+ context 'filtering by created_at' do
+ context 'through created_after' do
+ let(:params) { { created_after: issue3.created_at } }
+
+ it 'returns issues created on or after the given date' do
+ expect(issues).to contain_exactly(issue3)
+ end
+ end
+
+ context 'through created_before' do
+ let(:params) { { created_before: issue1.created_at + 1.second } }
+
+ it 'returns issues created on or before the given date' do
+ expect(issues).to contain_exactly(issue1)
+ end
+ end
+ end
+
context 'when the user is unauthorized' do
let(:search_user) { nil }
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index 58b7cd5e098..5eb26de6c92 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -46,5 +46,47 @@ describe MergeRequestsFinder do
expect(merge_requests).to contain_exactly(merge_request1)
end
+
+ context 'with created_after and created_before params' do
+ let(:project4) { create(:empty_project, forked_from_project: project1) }
+
+ let!(:new_merge_request) do
+ create(:merge_request,
+ :simple,
+ author: user,
+ created_at: 1.week.from_now,
+ source_project: project4,
+ target_project: project1)
+ end
+
+ let!(:old_merge_request) do
+ create(:merge_request,
+ :simple,
+ author: user,
+ created_at: 1.week.ago,
+ source_project: project4,
+ target_project: project4)
+ end
+
+ before do
+ project4.add_master(user)
+ end
+
+ it 'filters by created_after' do
+ params = { project_id: project1.id, created_after: new_merge_request.created_at }
+
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(new_merge_request)
+ end
+
+ it 'filters by created_before' do
+ params = { project_id: project4.id, created_before: old_merge_request.created_at + 1.second }
+
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(old_merge_request)
+ end
+ end
end
end
diff --git a/spec/javascripts/project_title_spec.js b/spec/javascripts/project_title_spec.js
index 3dba2e817ff..cc336180ff7 100644
--- a/spec/javascripts/project_title_spec.js
+++ b/spec/javascripts/project_title_spec.js
@@ -1,4 +1,3 @@
-/* eslint-disable space-before-function-paren, no-unused-expressions, no-return-assign, no-param-reassign, no-var, new-cap, wrap-iife, no-unused-vars, quotes, jasmine/no-expect-in-setup-teardown, max-len */
/* global Project */
import 'select2/select2';
@@ -7,47 +6,52 @@ import '~/api';
import '~/project_select';
import '~/project';
-(function() {
- describe('Project Title', function() {
- preloadFixtures('issues/open-issue.html.raw');
- loadJSONFixtures('projects.json');
+describe('Project Title', () => {
+ preloadFixtures('issues/open-issue.html.raw');
+ loadJSONFixtures('projects.json');
- beforeEach(function() {
- loadFixtures('issues/open-issue.html.raw');
+ beforeEach(() => {
+ loadFixtures('issues/open-issue.html.raw');
- window.gon = {};
- window.gon.api_version = 'v3';
+ window.gon = {};
+ window.gon.api_version = 'v3';
- return this.project = new Project();
- });
+ // eslint-disable-next-line no-new
+ new Project();
+ });
- describe('project list', function() {
- var fakeAjaxResponse = function fakeAjaxResponse(req) {
- var d;
- expect(req.url).toBe('/api/v3/projects.json?simple=true');
- expect(req.data).toEqual({ search: '', order_by: 'last_activity_at', per_page: 20, membership: true });
- d = $.Deferred();
- d.resolve(this.projects_data);
- return d.promise();
- };
-
- beforeEach((function(_this) {
- return function() {
- _this.projects_data = getJSONFixture('projects.json');
- return spyOn(jQuery, 'ajax').and.callFake(fakeAjaxResponse.bind(_this));
- };
- })(this));
- it('toggles dropdown', function() {
- var menu = $('.js-dropdown-menu-projects');
- $('.js-projects-dropdown-toggle').click();
- expect(menu).toHaveClass('open');
- menu.find('.dropdown-menu-close-icon').click();
- expect(menu).not.toHaveClass('open');
+ describe('project list', () => {
+ let reqUrl;
+ let reqData;
+
+ beforeEach(() => {
+ const fakeResponseData = getJSONFixture('projects.json');
+ spyOn(jQuery, 'ajax').and.callFake((req) => {
+ const def = $.Deferred();
+ reqUrl = req.url;
+ reqData = req.data;
+ def.resolve(fakeResponseData);
+ return def.promise();
});
});
- afterEach(() => {
- window.gon = {};
+ it('toggles dropdown', () => {
+ const $menu = $('.js-dropdown-menu-projects');
+ $('.js-projects-dropdown-toggle').click();
+ expect($menu).toHaveClass('open');
+ expect(reqUrl).toBe('/api/v3/projects.json?simple=true');
+ expect(reqData).toEqual({
+ search: '',
+ order_by: 'last_activity_at',
+ per_page: 20,
+ membership: true,
+ });
+ $menu.find('.dropdown-menu-close-icon').click();
+ expect($menu).not.toHaveClass('open');
});
});
-}).call(window);
+
+ afterEach(() => {
+ window.gon = {};
+ });
+});
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index 2c34402576b..729db02e06c 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -16,6 +16,14 @@ window.gl = window.gl || {};
window.gl.TEST_HOST = 'http://test.host';
window.gon = window.gon || {};
+// HACK: Chrome 59 disconnects if there are too many synchronous tests in a row
+// because it appears to lock up the thread that communicates to Karma's socket
+// This async beforeEach gets called on every spec and releases the JS thread long
+// enough for the socket to continue to communicate.
+// The downside is that it creates a minor performance penalty in the time it takes
+// to run our unit tests.
+beforeEach(done => done()); // eslint-disable-line jasmine/no-global-setup
+
// render all of our tests
const testsContext = require.context('.', true, /_spec$/);
testsContext.keys().forEach(function (path) {
diff --git a/spec/lib/gitlab/git/gitmodules_parser_spec.rb b/spec/lib/gitlab/git/gitmodules_parser_spec.rb
new file mode 100644
index 00000000000..143aa2218c9
--- /dev/null
+++ b/spec/lib/gitlab/git/gitmodules_parser_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+describe Gitlab::Git::GitmodulesParser do
+ it 'should parse a .gitmodules file correctly' do
+ parser = described_class.new(<<-'GITMODULES'.strip_heredoc)
+ [submodule "vendor/libgit2"]
+ path = vendor/libgit2
+ [submodule "vendor/libgit2"]
+ url = https://github.com/nodegit/libgit2.git
+
+ # a comment
+ [submodule "moved"]
+ path = new/path
+ url = https://example.com/some/project
+ [submodule "bogus"]
+ url = https://example.com/another/project
+ GITMODULES
+
+ modules = parser.parse
+
+ expect(modules).to eq({
+ 'vendor/libgit2' => { 'name' => 'vendor/libgit2',
+ 'url' => 'https://github.com/nodegit/libgit2.git' },
+ 'new/path' => { 'name' => 'moved',
+ 'url' => 'https://example.com/some/project' }
+ })
+ end
+end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index eee4c9eab6d..02b3f167250 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -358,7 +358,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
expect(submodule).to eq([
"six", {
"id" => "409f37c4f05865e4fb208c771485f211a22c4c2d",
- "path" => "six",
+ "name" => "six",
"url" => "git://github.com/randx/six.git"
}
])
@@ -366,14 +366,14 @@ describe Gitlab::Git::Repository, seed_helper: true do
it 'should handle nested submodules correctly' do
nested = submodules['nested/six']
- expect(nested['path']).to eq('nested/six')
+ expect(nested['name']).to eq('nested/six')
expect(nested['url']).to eq('git://github.com/randx/six.git')
expect(nested['id']).to eq('24fb71c79fcabc63dfd8832b12ee3bf2bf06b196')
end
it 'should handle deeply nested submodules correctly' do
nested = submodules['deeper/nested/six']
- expect(nested['path']).to eq('deeper/nested/six')
+ expect(nested['name']).to eq('deeper/nested/six')
expect(nested['url']).to eq('git://github.com/randx/six.git')
expect(nested['id']).to eq('24fb71c79fcabc63dfd8832b12ee3bf2bf06b196')
end
@@ -393,7 +393,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
expect(submodules.first).to eq([
"six", {
"id" => "409f37c4f05865e4fb208c771485f211a22c4c2d",
- "path" => "six",
+ "name" => "six",
"url" => "git://github.com/randx/six.git"
}
])
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index 3dcc20c48e8..9a86cfa66e4 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -3,11 +3,12 @@ require 'spec_helper'
describe Gitlab::GitAccess, lib: true do
let(:pull_access_check) { access.check('git-upload-pack', '_any') }
let(:push_access_check) { access.check('git-receive-pack', '_any') }
- let(:access) { Gitlab::GitAccess.new(actor, project, protocol, authentication_abilities: authentication_abilities) }
+ let(:access) { Gitlab::GitAccess.new(actor, project, protocol, authentication_abilities: authentication_abilities, redirected_path: redirected_path) }
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:actor) { user }
let(:protocol) { 'ssh' }
+ let(:redirected_path) { nil }
let(:authentication_abilities) do
[
:read_project,
@@ -162,6 +163,46 @@ describe Gitlab::GitAccess, lib: true do
end
end
+ describe '#check_project_moved!' do
+ before do
+ project.team << [user, :master]
+ end
+
+ context 'when a redirect was not followed to find the project' do
+ context 'pull code' do
+ it { expect { pull_access_check }.not_to raise_error }
+ end
+
+ context 'push code' do
+ it { expect { push_access_check }.not_to raise_error }
+ end
+ end
+
+ context 'when a redirect was followed to find the project' do
+ let(:redirected_path) { 'some/other-path' }
+
+ context 'pull code' do
+ it { expect { pull_access_check }.to raise_not_found(/Project '#{redirected_path}' was moved to '#{project.full_path}'/) }
+ it { expect { pull_access_check }.to raise_not_found(/git remote set-url origin #{project.ssh_url_to_repo}/) }
+
+ context 'http protocol' do
+ let(:protocol) { 'http' }
+ it { expect { pull_access_check }.to raise_not_found(/git remote set-url origin #{project.http_url_to_repo}/) }
+ end
+ end
+
+ context 'push code' do
+ it { expect { push_access_check }.to raise_not_found(/Project '#{redirected_path}' was moved to '#{project.full_path}'/) }
+ it { expect { push_access_check }.to raise_not_found(/git remote set-url origin #{project.ssh_url_to_repo}/) }
+
+ context 'http protocol' do
+ let(:protocol) { 'http' }
+ it { expect { push_access_check }.to raise_not_found(/git remote set-url origin #{project.http_url_to_repo}/) }
+ end
+ end
+ end
+ end
+
describe '#check_command_disabled!' do
before do
project.team << [user, :master]
diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb
index a1eb95750ba..797ec8cb23e 100644
--- a/spec/lib/gitlab/git_access_wiki_spec.rb
+++ b/spec/lib/gitlab/git_access_wiki_spec.rb
@@ -1,9 +1,10 @@
require 'spec_helper'
describe Gitlab::GitAccessWiki, lib: true do
- let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web', authentication_abilities: authentication_abilities) }
+ let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web', authentication_abilities: authentication_abilities, redirected_path: redirected_path) }
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
+ let(:redirected_path) { nil }
let(:authentication_abilities) do
[
:read_project,
diff --git a/spec/lib/gitlab/repo_path_spec.rb b/spec/lib/gitlab/repo_path_spec.rb
index f9025397107..efea4f429bf 100644
--- a/spec/lib/gitlab/repo_path_spec.rb
+++ b/spec/lib/gitlab/repo_path_spec.rb
@@ -4,24 +4,44 @@ describe ::Gitlab::RepoPath do
describe '.parse' do
set(:project) { create(:project) }
- it 'parses a full repository path' do
- expect(described_class.parse(project.repository.path)).to eq([project, false])
- end
+ context 'a repository storage path' do
+ it 'parses a full repository path' do
+ expect(described_class.parse(project.repository.path)).to eq([project, false, nil])
+ end
- it 'parses a full wiki path' do
- expect(described_class.parse(project.wiki.repository.path)).to eq([project, true])
+ it 'parses a full wiki path' do
+ expect(described_class.parse(project.wiki.repository.path)).to eq([project, true, nil])
+ end
end
- it 'parses a relative repository path' do
- expect(described_class.parse(project.full_path + '.git')).to eq([project, false])
- end
+ context 'a relative path' do
+ it 'parses a relative repository path' do
+ expect(described_class.parse(project.full_path + '.git')).to eq([project, false, nil])
+ end
- it 'parses a relative wiki path' do
- expect(described_class.parse(project.full_path + '.wiki.git')).to eq([project, true])
- end
+ it 'parses a relative wiki path' do
+ expect(described_class.parse(project.full_path + '.wiki.git')).to eq([project, true, nil])
+ end
+
+ it 'parses a relative path starting with /' do
+ expect(described_class.parse('/' + project.full_path + '.git')).to eq([project, false, nil])
+ end
+
+ context 'of a redirected project' do
+ let(:redirect) { project.route.create_redirect('foo/bar') }
+
+ it 'parses a relative repository path' do
+ expect(described_class.parse(redirect.path + '.git')).to eq([project, false, 'foo/bar'])
+ end
+
+ it 'parses a relative wiki path' do
+ expect(described_class.parse(redirect.path + '.wiki.git')).to eq([project, true, 'foo/bar.wiki'])
+ end
- it 'parses a relative path starting with /' do
- expect(described_class.parse('/' + project.full_path + '.git')).to eq([project, false])
+ it 'parses a relative path starting with /' do
+ expect(described_class.parse('/' + redirect.path + '.git')).to eq([project, false, 'foo/bar'])
+ end
+ end
end
end
@@ -43,4 +63,33 @@ describe ::Gitlab::RepoPath do
)
end
end
+
+ describe '.find_project' do
+ let(:project) { create(:empty_project) }
+ let(:redirect) { project.route.create_redirect('foo/bar/baz') }
+
+ context 'when finding a project by its canonical path' do
+ context 'when the cases match' do
+ it 'returns the project and false' do
+ expect(described_class.find_project(project.full_path)).to eq([project, false])
+ end
+ end
+
+ context 'when the cases do not match' do
+ # This is slightly different than web behavior because on the web it is
+ # easy and safe to redirect someone to the correctly-cased URL. For git
+ # requests, we should accept wrongly-cased URLs because it is a pain to
+ # block people's git operations and force them to update remote URLs.
+ it 'returns the project and false' do
+ expect(described_class.find_project(project.full_path.upcase)).to eq([project, false])
+ end
+ end
+ end
+
+ context 'when finding a project via a redirect' do
+ it 'returns the project and true' do
+ expect(described_class.find_project(redirect.path)).to eq([project, true])
+ end
+ end
+ end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 1a1bbd60504..f6b7120e2c9 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -451,6 +451,40 @@ describe User, models: true do
end
end
+ describe '#ensure_user_rights_and_limits' do
+ describe 'with external user' do
+ let(:user) { create(:user, external: true) }
+
+ it 'receives callback when external changes' do
+ expect(user).to receive(:ensure_user_rights_and_limits)
+
+ user.update_attributes(external: false)
+ end
+
+ it 'ensures correct rights and limits for user' do
+ stub_config_setting(default_can_create_group: true)
+
+ expect { user.update_attributes(external: false) }.to change { user.can_create_group }.to(true)
+ .and change { user.projects_limit }.to(current_application_settings.default_projects_limit)
+ end
+ end
+
+ describe 'without external user' do
+ let(:user) { create(:user, external: false) }
+
+ it 'receives callback when external changes' do
+ expect(user).to receive(:ensure_user_rights_and_limits)
+
+ user.update_attributes(external: true)
+ end
+
+ it 'ensures correct rights and limits for user' do
+ expect { user.update_attributes(external: true) }.to change { user.can_create_group }.to(false)
+ .and change { user.projects_limit }.to(0)
+ end
+ end
+ end
+
describe 'rss token' do
it 'ensures an rss token on read' do
user = create(:user, rss_token: nil)
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 86e15d896df..6deaea956e0 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -321,8 +321,6 @@ describe API::Internal do
end
context "archived project" do
- let(:personal_project) { create(:empty_project, namespace: user.namespace) }
-
before do
project.team << [user, :developer]
project.archive!
@@ -445,6 +443,42 @@ describe API::Internal do
expect(json_response['status']).to be_truthy
end
end
+
+ context 'the project path was changed' do
+ let!(:old_path_to_repo) { project.repository.path_to_repo }
+ let!(:old_full_path) { project.full_path }
+ let(:project_moved_message) do
+ <<-MSG.strip_heredoc
+ Project '#{old_full_path}' was moved to '#{project.full_path}'.
+
+ Please update your Git remote and try again:
+
+ git remote set-url origin #{project.ssh_url_to_repo}
+ MSG
+ end
+
+ before do
+ project.team << [user, :developer]
+ project.path = 'new_path'
+ project.save!
+ end
+
+ it 'rejects the push' do
+ push_with_path(key, old_path_to_repo)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['status']).to be_falsey
+ expect(json_response['message']).to eq(project_moved_message)
+ end
+
+ it 'rejects the SSH pull' do
+ pull_with_path(key, old_path_to_repo)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['status']).to be_falsey
+ expect(json_response['message']).to eq(project_moved_message)
+ end
+ end
end
describe 'GET /internal/merge_request_urls' do
@@ -587,6 +621,17 @@ describe API::Internal do
)
end
+ def pull_with_path(key, path_to_repo, protocol = 'ssh')
+ post(
+ api("/internal/allowed"),
+ key_id: key.id,
+ project: path_to_repo,
+ action: 'git-upload-pack',
+ secret_token: secret_token,
+ protocol: protocol
+ )
+ end
+
def push(key, project, protocol = 'ssh', env: nil)
post(
api("/internal/allowed"),
@@ -600,6 +645,19 @@ describe API::Internal do
)
end
+ def push_with_path(key, path_to_repo, protocol = 'ssh', env: nil)
+ post(
+ api("/internal/allowed"),
+ changes: 'd14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/master',
+ key_id: key.id,
+ project: path_to_repo,
+ action: 'git-receive-pack',
+ secret_token: secret_token,
+ protocol: protocol,
+ env: env
+ )
+ end
+
def archive(key, project)
post(
api("/internal/allowed"),
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 16e5efb2f5b..452ff4aba8e 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -334,14 +334,13 @@ describe API::MergeRequests do
target_branch: 'master',
author: user,
labels: 'label, label2',
- milestone_id: milestone.id,
- remove_source_branch: true
+ milestone_id: milestone.id
expect(response).to have_http_status(201)
expect(json_response['title']).to eq('Test merge_request')
expect(json_response['labels']).to eq(%w(label label2))
expect(json_response['milestone']['id']).to eq(milestone.id)
- expect(json_response['force_remove_source_branch']).to be_truthy
+ expect(json_response['force_remove_source_branch']).to be_falsy
end
it "returns 422 when source_branch equals target_branch" do
@@ -404,6 +403,27 @@ describe API::MergeRequests do
expect(response).to have_http_status(409)
end
end
+
+ context 'accepts remove_source_branch parameter' do
+ let(:params) do
+ { title: 'Test merge_request',
+ source_branch: 'markdown',
+ target_branch: 'master',
+ author: user }
+ end
+
+ it 'sets force_remove_source_branch to false' do
+ post api("/projects/#{project.id}/merge_requests", user), params.merge(remove_source_branch: false)
+
+ expect(json_response['force_remove_source_branch']).to be_falsy
+ end
+
+ it 'sets force_remove_source_branch to true' do
+ post api("/projects/#{project.id}/merge_requests", user), params.merge(remove_source_branch: true)
+
+ expect(json_response['force_remove_source_branch']).to be_truthy
+ end
+ end
end
context 'forked projects' do
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 9dc4b6972a6..bc869ea1108 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -377,6 +377,16 @@ describe API::Users do
expect(user.reload.organization).to eq('GitLab')
end
+ it 'updates user with avatar' do
+ put api("/users/#{user.id}", admin), { avatar: fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
+
+ user.reload
+
+ expect(user.avatar).to be_present
+ expect(response).to have_http_status(200)
+ expect(json_response['avatar_url']).to include(user.avatar_path)
+ end
+
it 'updates user with his own email' do
put api("/users/#{user.id}", admin), email: user.email
expect(response).to have_http_status(200)
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index dce78faefc9..b08148eca3c 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -316,6 +316,26 @@ describe 'Git HTTP requests', lib: true do
it_behaves_like 'pushes require Basic HTTP Authentication'
end
end
+
+ context 'and the user requests a redirected path' do
+ let!(:redirect) { project.route.create_redirect('foo/bar') }
+ let(:path) { "#{redirect.path}.git" }
+ let(:project_moved_message) do
+ <<-MSG.strip_heredoc
+ Project '#{redirect.path}' was moved to '#{project.full_path}'.
+
+ Please update your Git remote and try again:
+
+ git remote set-url origin #{project.http_url_to_repo}
+ MSG
+ end
+
+ it 'downloads get status 404 with "project was moved" message' do
+ clone_get(path, {})
+ expect(response).to have_http_status(:not_found)
+ expect(response.body).to match(project_moved_message)
+ end
+ end
end
context "when the project is private" do
@@ -505,6 +525,33 @@ describe 'Git HTTP requests', lib: true do
Rack::Attack::Allow2Ban.reset(ip, options)
end
end
+
+ context 'and the user requests a redirected path' do
+ let!(:redirect) { project.route.create_redirect('foo/bar') }
+ let(:path) { "#{redirect.path}.git" }
+ let(:project_moved_message) do
+ <<-MSG.strip_heredoc
+ Project '#{redirect.path}' was moved to '#{project.full_path}'.
+
+ Please update your Git remote and try again:
+
+ git remote set-url origin #{project.http_url_to_repo}
+ MSG
+ end
+
+ it 'downloads get status 404 with "project was moved" message' do
+ clone_get(path, env)
+ expect(response).to have_http_status(:not_found)
+ expect(response.body).to match(project_moved_message)
+ end
+
+ it 'uploads get status 404 with "project was moved" message' do
+ upload(path, env) do |response|
+ expect(response).to have_http_status(:not_found)
+ expect(response.body).to match(project_moved_message)
+ end
+ end
+ end
end
context "when the user doesn't have access to the project" do
@@ -680,7 +727,7 @@ describe 'Git HTTP requests', lib: true do
end
context "POST git-receive-pack" do
- it "failes to find a route" do
+ it "fails to find a route" do
expect { push_post(project.path_with_namespace) }.to raise_error(ActionController::RoutingError)
end
end
diff --git a/spec/views/profiles/show.html.haml_spec.rb b/spec/views/profiles/show.html.haml_spec.rb
new file mode 100644
index 00000000000..e89a8cb9626
--- /dev/null
+++ b/spec/views/profiles/show.html.haml_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe 'profiles/show' do
+ let(:user) { create(:user) }
+
+ before do
+ assign(:user, user)
+ allow(controller).to receive(:current_user).and_return(user)
+ end
+
+ context 'when the profile page is opened' do
+ it 'displays the correct elements' do
+ render
+
+ expect(rendered).to have_field('user_name', user.name)
+ expect(rendered).to have_field('user_id', user.id)
+ end
+ end
+end
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index 3c93da63f2e..6c4df7350ea 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -32,7 +32,7 @@ describe PostReceive do
context "with an absolute path as the project identifier" do
it "searches the project by full path" do
- expect(Project).to receive(:find_by_full_path).with(project.full_path).and_call_original
+ expect(Project).to receive(:find_by_full_path).with(project.full_path, follow_redirects: true).and_call_original
described_class.new.perform(pwd(project), key_id, base64_changes)
end