summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/issue_templates/Feature Proposal.md43
-rw-r--r--CONTRIBUTING.md1
-rw-r--r--app/assets/javascripts/environments/components/environment.vue36
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines.vue6
-rw-r--r--app/assets/stylesheets/framework/blank.scss9
-rw-r--r--app/assets/stylesheets/framework/contextual-sidebar.scss19
-rw-r--r--app/assets/stylesheets/framework/filters.scss5
-rw-r--r--app/assets/stylesheets/framework/gitlab-theme.scss8
-rw-r--r--app/assets/stylesheets/framework/header.scss20
-rw-r--r--app/assets/stylesheets/framework/icons.scss28
-rw-r--r--app/assets/stylesheets/framework/images.scss2
-rw-r--r--app/assets/stylesheets/framework/mixins.scss8
-rw-r--r--app/assets/stylesheets/framework/zen.scss8
-rw-r--r--app/assets/stylesheets/pages/help.scss20
-rw-r--r--app/assets/stylesheets/pages/notes.scss8
-rw-r--r--app/assets/stylesheets/pages/status.scss4
-rw-r--r--app/models/ci/pipeline.rb5
-rw-r--r--app/models/note.rb15
-rw-r--r--app/models/repository.rb6
-rw-r--r--app/models/system_note_metadata.rb10
-rw-r--r--app/models/user.rb4
-rw-r--r--app/services/system_note_service.rb4
-rw-r--r--app/uploaders/file_uploader.rb10
-rw-r--r--app/views/help/index.html.haml2
-rw-r--r--app/views/projects/environments/show.html.haml17
-rw-r--r--app/views/shared/_import_form.html.haml3
-rw-r--r--changelogs/unreleased/39461-notes-api-for-issues-no-longer-returns-label-additions-removals.yml5
-rw-r--r--changelogs/unreleased/39977-gitlab-shell-default-timeout.yml5
-rw-r--r--changelogs/unreleased/40377-blank-states.yml5
-rw-r--r--changelogs/unreleased/reduce-queries-for-artifacts-button.yml5
-rw-r--r--changelogs/unreleased/skip_confirmation_user_API.yml7
-rw-r--r--config/gitlab.yml.example4
-rw-r--r--config/initializers/1_settings.rb2
-rw-r--r--doc/api/users.md1
-rw-r--r--doc/integration/external-issue-tracker.md1
-rw-r--r--doc/user/project/integrations/custom_issue_tracker.md20
-rw-r--r--lib/api/notes.rb4
-rw-r--r--lib/api/users.rb3
-rw-r--r--lib/gitlab/middleware/read_only.rb8
-rw-r--r--lib/gitlab/shell.rb3
-rw-r--r--lib/tasks/gitlab/cleanup.rake5
-rw-r--r--lib/tasks/gitlab/gitaly.rake11
-rw-r--r--spec/lib/gitlab/middleware/read_only_spec.rb11
-rw-r--r--spec/models/ci/pipeline_spec.rb4
-rw-r--r--spec/models/note_spec.rb31
-rw-r--r--spec/models/repository_spec.rb25
-rw-r--r--spec/requests/api/users_spec.rb8
-rw-r--r--spec/tasks/gitlab/cleanup_rake_spec.rb41
-rw-r--r--spec/tasks/gitlab/gitaly_rake_spec.rb16
-rw-r--r--spec/uploaders/file_uploader_spec.rb50
50 files changed, 335 insertions, 241 deletions
diff --git a/.gitlab/issue_templates/Feature Proposal.md b/.gitlab/issue_templates/Feature Proposal.md
index 1278061a410..5b55eb1374b 100644
--- a/.gitlab/issue_templates/Feature Proposal.md
+++ b/.gitlab/issue_templates/Feature Proposal.md
@@ -1,22 +1,3 @@
-Please read this!
-
-Before opening a new issue, make sure to search for keywords in the issues
-filtered by the "feature proposal" label:
-
-For the Community Edition issue tracker:
-
-- https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=feature+proposal
-
-For the Enterprise Edition issue tracker:
-
-- https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name%5B%5D=feature+proposal
-
-and verify the issue you're about to submit isn't a duplicate.
-
-Please remove this notice if you're confident your issue isn't a duplicate.
-
-------
-
### Description
(Include problem, use cases, benefits, and/or goals)
@@ -25,26 +6,4 @@ Please remove this notice if you're confident your issue isn't a duplicate.
### Links / references
-### Documentation blurb
-
-#### Overview
-
-What is it?
-Why should someone use this feature?
-What is the underlying (business) problem?
-How do you use this feature?
-
-#### Use cases
-
-Who is this for? Provide one or more use cases.
-
-### Feature checklist
-
-Make sure these are completed before closing the issue,
-with a link to the relevant commit.
-
-- [ ] [Feature assurance](https://about.gitlab.com/handbook/product/#feature-assurance)
-- [ ] Documentation
-- [ ] Added to [features.yml](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/features.yml)
-
-/label ~"feature proposal" \ No newline at end of file
+/label ~"feature proposal"
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c4e5fd842df..4930b541ba2 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -543,6 +543,7 @@ When having your code reviewed and when reviewing merge requests please take the
etc.), they should conform to our [Licensing guidelines][license-finder-doc].
See the instructions in that document for help if your MR fails the
"license-finder" test with a "Dependencies that need approval" error.
+1. The merge request meets the [definition of done](#definition-of-done).
## Definition of done
diff --git a/app/assets/javascripts/environments/components/environment.vue b/app/assets/javascripts/environments/components/environment.vue
index c039ae85cfb..ffb7757bed8 100644
--- a/app/assets/javascripts/environments/components/environment.vue
+++ b/app/assets/javascripts/environments/components/environment.vue
@@ -227,25 +227,27 @@ export default {
/>
<div
- class="blank-state blank-state-no-icon"
+ class="blank-state-row"
v-if="!isLoading && state.environments.length === 0">
- <h2 class="blank-state-title js-blank-state-title">
- You don't have any environments right now.
- </h2>
- <p class="blank-state-text">
- Environments are places where code gets deployed, such as staging or production.
- <br />
- <a :href="helpPagePath">
- Read more about environments
+ <div class="blank-state-center">
+ <h2 class="blank-state-title js-blank-state-title">
+ You don't have any environments right now.
+ </h2>
+ <p class="blank-state-text">
+ Environments are places where code gets deployed, such as staging or production.
+ <br />
+ <a :href="helpPagePath">
+ Read more about environments
+ </a>
+ </p>
+
+ <a
+ v-if="canCreateEnvironmentParsed"
+ :href="newEnvironmentPath"
+ class="btn btn-create js-new-environment-button">
+ New environment
</a>
- </p>
-
- <a
- v-if="canCreateEnvironmentParsed"
- :href="newEnvironmentPath"
- class="btn btn-create js-new-environment-button">
- New environment
- </a>
+ </div>
</div>
<div
diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines.vue
index cf241c8ffed..233be8a49c8 100644
--- a/app/assets/javascripts/pipelines/components/pipelines.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines.vue
@@ -267,9 +267,11 @@
/>
<div
- class="blank-state blank-state-no-icon"
+ class="blank-state-row"
v-if="shouldRenderNoPipelinesMessage">
- <h2 class="blank-state-title js-blank-state-title">No pipelines to show.</h2>
+ <div class="blank-state-center">
+ <h2 class="blank-state-title js-blank-state-title">No pipelines to show.</h2>
+ </div>
</div>
<div
diff --git a/app/assets/stylesheets/framework/blank.scss b/app/assets/stylesheets/framework/blank.scss
index 10f9e9b70b0..9982a5779af 100644
--- a/app/assets/stylesheets/framework/blank.scss
+++ b/app/assets/stylesheets/framework/blank.scss
@@ -56,6 +56,12 @@
}
}
+.blank-state-center {
+ padding-top: 20px;
+ padding-bottom: 20px;
+ text-align: center;
+}
+
.blank-state {
padding: 20px;
border: 1px solid $border-color;
@@ -66,7 +72,10 @@
align-items: center;
padding: 50px 30px;
}
+}
+.blank-state,
+.blank-state-center {
.blank-state-icon {
svg {
display: block;
diff --git a/app/assets/stylesheets/framework/contextual-sidebar.scss b/app/assets/stylesheets/framework/contextual-sidebar.scss
index 320f458630a..b73932eb7e1 100644
--- a/app/assets/stylesheets/framework/contextual-sidebar.scss
+++ b/app/assets/stylesheets/framework/contextual-sidebar.scss
@@ -40,12 +40,6 @@
a:hover {
background-color: $link-hover-background;
color: $gl-text-color;
-
- .settings-avatar {
- svg {
- fill: $gl-text-color;
- }
- }
}
.avatar-container {
@@ -138,10 +132,6 @@
color: $gl-text-color-secondary;
}
- svg {
- fill: $gl-text-color-secondary;
- }
-
.nav-item-name {
flex: 1;
}
@@ -224,10 +214,6 @@
&:hover {
color: $gl-text-color;
-
- svg {
- fill: $gl-text-color;
- }
}
}
@@ -338,7 +324,6 @@
align-items: center;
svg {
- fill: $gl-text-color-secondary;
margin-right: 8px;
}
@@ -349,10 +334,6 @@
&:hover {
background-color: $border-color;
color: $gl-text-color;
-
- svg {
- fill: $gl-text-color;
- }
}
}
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index 74b6b31b07e..cf8165eab5b 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -305,16 +305,11 @@
color: $gl-text-color;
border-color: $dropdown-input-focus-border;
outline: none;
-
- svg {
- fill: $gl-text-color;
- }
}
svg {
height: 14px;
width: 14px;
- fill: $gl-text-color-secondary;
vertical-align: middle;
}
diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss
index dc591c06c88..db36e27fa74 100644
--- a/app/assets/stylesheets/framework/gitlab-theme.scss
+++ b/app/assets/stylesheets/framework/gitlab-theme.scss
@@ -30,10 +30,6 @@
&.dropdown.open > a {
color: $color-900;
background-color: $color-alternate;
-
- svg {
- fill: currentColor;
- }
}
&.line-separator {
@@ -51,10 +47,6 @@
color: $color-200;
> a {
- svg {
- fill: $color-200;
- }
-
&.header-user-dropdown-toggle {
.header-user-avatar {
border-color: $color-200;
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 2218b5705fc..f985a3aea5c 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -235,10 +235,6 @@
opacity: 1;
color: $white-light;
- svg {
- fill: currentColor;
- }
-
&.header-user-dropdown-toggle .header-user-avatar {
border-color: $white-light;
}
@@ -269,14 +265,6 @@
font-size: 20px;
}
}
-
- &.active > a,
- &.dropdown.open > a {
-
- svg {
- fill: currentColor;
- }
- }
}
}
}
@@ -289,10 +277,6 @@
text-decoration: none;
outline: 0;
color: $white-light;
-
- svg {
- fill: currentColor;
- }
}
> a {
@@ -307,10 +291,6 @@
border-radius: $border-radius-default;
height: 32px;
font-weight: $gl-font-weight-bold;
-
- svg {
- fill: currentColor;
- }
}
&.line-separator {
diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss
index ef864e8f6a9..1ab5e6a93f9 100644
--- a/app/assets/stylesheets/framework/icons.scss
+++ b/app/assets/stylesheets/framework/icons.scss
@@ -1,63 +1,35 @@
.ci-status-icon-success,
.ci-status-icon-passed {
color: $green-500;
-
- svg {
- fill: $green-500;
- }
}
.ci-status-icon-failed {
color: $gl-danger;
-
- svg {
- fill: $gl-danger;
- }
}
.ci-status-icon-pending,
.ci-status-icon-failed_with_warnings,
.ci-status-icon-success_with_warnings {
color: $orange-500;
-
- svg {
- fill: $orange-500;
- }
}
.ci-status-icon-running {
color: $blue-400;
-
- svg {
- fill: $blue-400;
- }
}
.ci-status-icon-canceled,
.ci-status-icon-disabled,
.ci-status-icon-not-found {
color: $gl-text-color;
-
- svg {
- fill: $gl-text-color;
- }
}
.ci-status-icon-created,
.ci-status-icon-skipped {
color: $gray-darkest;
-
- svg {
- fill: $gray-darkest;
- }
}
.ci-status-icon-manual {
color: $gl-text-color;
-
- svg {
- fill: $gl-text-color;
- }
}
.icon-link {
diff --git a/app/assets/stylesheets/framework/images.scss b/app/assets/stylesheets/framework/images.scss
index 6819fd88b7f..78a8e57ddbb 100644
--- a/app/assets/stylesheets/framework/images.scss
+++ b/app/assets/stylesheets/framework/images.scss
@@ -27,6 +27,8 @@
}
svg {
+ fill: currentColor;
+
&.s8 { @include svg-size(8px); }
&.s12 { @include svg-size(12px); }
&.s16 { @include svg-size(16px); }
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 33012133b66..e12b5aab381 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -130,14 +130,6 @@
background-color: $color-light;
color: $color-dark;
border-color: $color-dark;
-
- svg {
- fill: $color-dark;
- }
- }
-
- svg {
- fill: $color-main;
}
}
diff --git a/app/assets/stylesheets/framework/zen.scss b/app/assets/stylesheets/framework/zen.scss
index 5a4d3ba0ee9..dbd3144b9b4 100644
--- a/app/assets/stylesheets/framework/zen.scss
+++ b/app/assets/stylesheets/framework/zen.scss
@@ -57,15 +57,7 @@
padding: 5px;
font-size: 36px;
- svg {
- fill: $gl-text-color;
- }
-
&:hover {
color: $black;
-
- svg {
- fill: $black;
- }
}
}
diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss
index dae8ccdef6c..9cc9e11bcd1 100644
--- a/app/assets/stylesheets/pages/help.scss
+++ b/app/assets/stylesheets/pages/help.scss
@@ -1,23 +1,3 @@
-.documentation-index {
- h1 {
- margin: 0;
- }
-
- h2 {
- font-size: 20px;
- }
-
- li {
- line-height: 24px;
- color: $document-index-color;
-
- a {
- margin-right: 3px;
- }
- }
-}
-
-
.shortcut-mappings {
font-size: 12px;
color: $help-shortcut-mapping-color;
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 9537eeeee97..2461b818219 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -547,10 +547,6 @@ ul.notes {
width: 16px;
top: 0;
vertical-align: text-top;
-
- path {
- fill: currentColor;
- }
}
.award-control-icon-positive,
@@ -570,10 +566,6 @@ ul.notes {
.link-highlight {
color: $gl-link-color;
fill: $gl-link-color;
-
- svg {
- fill: $gl-link-color;
- }
}
.award-control-icon-neutral {
diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss
index 25c80e1f950..ade5ddd147b 100644
--- a/app/assets/stylesheets/pages/status.scss
+++ b/app/assets/stylesheets/pages/status.scss
@@ -55,10 +55,6 @@
&:not(span):hover {
background-color: rgba($gl-text-color-secondary, .07);
}
-
- svg {
- fill: $gl-text-color-secondary;
- }
}
}
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 3ded675bec0..ebbefc51a4f 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -507,7 +507,10 @@ module Ci
end
def latest_builds_with_artifacts
- @latest_builds_with_artifacts ||= builds.latest.with_artifacts
+ # We purposely cast the builds to an Array here. Because we always use the
+ # rows if there are more than 0 this prevents us from having to run two
+ # queries: one to get the count and one to get the rows.
+ @latest_builds_with_artifacts ||= builds.latest.with_artifacts.to_a
end
private
diff --git a/app/models/note.rb b/app/models/note.rb
index f9676361072..50c9caf8529 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -110,6 +110,7 @@ class Note < ActiveRecord::Base
includes(:author, :noteable, :updated_by,
project: [:project_members, { group: [:group_members] }])
end
+ scope :with_metadata, -> { includes(:system_note_metadata) }
after_initialize :ensure_discussion_id
before_validation :nullify_blank_type, :nullify_blank_line_code
@@ -169,7 +170,13 @@ class Note < ActiveRecord::Base
end
def cross_reference?
- system? && matches_cross_reference_regex?
+ return unless system?
+
+ if force_cross_reference_regex_check?
+ matches_cross_reference_regex?
+ else
+ SystemNoteService.cross_reference?(note)
+ end
end
def diff_note?
@@ -382,4 +389,10 @@ class Note < ActiveRecord::Base
def set_discussion_id
self.discussion_id ||= discussion_class.discussion_id(self)
end
+
+ def force_cross_reference_regex_check?
+ return unless system?
+
+ SystemNoteMetadata::TYPES_WITH_CROSS_REFERENCES.include?(system_note_metadata&.action)
+ end
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 26d1bc12232..2bf21cbdcc4 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -217,11 +217,7 @@ class Repository
def branch_exists?(branch_name)
return false unless raw_repository
- @branch_exists_memo ||= Hash.new do |hash, key|
- hash[key] = raw_repository.branch_exists?(key)
- end
-
- @branch_exists_memo[branch_name]
+ branch_names.include?(branch_name)
end
def ref_exists?(ref)
diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb
index 1f9f8d7286b..29035480371 100644
--- a/app/models/system_note_metadata.rb
+++ b/app/models/system_note_metadata.rb
@@ -1,4 +1,14 @@
class SystemNoteMetadata < ActiveRecord::Base
+ # These notes's action text might contain a reference that is external.
+ # We should always force a deep validation upon references that are found
+ # in this note type.
+ # Other notes can always be safely shown as all its references are
+ # in the same project (i.e. with the same permissions)
+ TYPES_WITH_CROSS_REFERENCES = %w[
+ commit cross_reference
+ close duplicate
+ ].freeze
+
ICON_TYPES = %w[
commit description merge confidential visible label assignee cross_reference
title time_tracking branch milestone discussion task moved
diff --git a/app/models/user.rb b/app/models/user.rb
index 0329d094d09..f98165754ca 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -445,6 +445,10 @@ class User < ActiveRecord::Base
skip_confirmation! if bool
end
+ def skip_reconfirmation=(bool)
+ skip_reconfirmation! if bool
+ end
+
def generate_reset_token
@reset_token, enc = Devise.token_generator.generate(self.class, :reset_password_token)
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index e946218824c..fe71a405565 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -583,6 +583,10 @@ module SystemNoteService
create_note(NoteSummary.new(issuable, issuable.project, author, body, action: action))
end
+ def cross_reference?(note_text)
+ note_text =~ /\A#{cross_reference_note_prefix}/i
+ end
+
private
def notes_for_mentioner(mentioner, noteable, notes)
diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb
index d4ba3a028be..f4a5cf75018 100644
--- a/app/uploaders/file_uploader.rb
+++ b/app/uploaders/file_uploader.rb
@@ -26,11 +26,15 @@ class FileUploader < GitlabUploader
# This is used to build Upload paths dynamically based on the model's current
# namespace and path, allowing us to ignore renames or transfers.
#
- # model - Object that responds to `path_with_namespace`
+ # model - Object that responds to `full_path` and `disk_path`
#
# Returns a String without a trailing slash
- def self.dynamic_path_segment(model)
- File.join(CarrierWave.root, base_dir, model.disk_path)
+ def self.dynamic_path_segment(project)
+ if project.hashed_storage?(:attachments)
+ File.join(CarrierWave.root, base_dir, project.disk_path)
+ else
+ File.join(CarrierWave.root, base_dir, project.full_path)
+ end
end
attr_accessor :model
diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml
index d0c2e0b1d69..021de4f0caf 100644
--- a/app/views/help/index.html.haml
+++ b/app/views/help/index.html.haml
@@ -29,7 +29,7 @@
.row.prepend-top-default
.col-md-8
- .documentation-index
+ .documentation-index.wiki
= markdown(@help_index)
.col-md-4
.panel.panel-default
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index d7859c9fbeb..add394a6356 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -19,14 +19,15 @@
.environments-container
- if @deployments.blank?
- .blank-state.blank-state-no-icon
- %h2.blank-state-title
- You don't have any deployments right now.
- %p.blank-state-text
- Define environments in the deploy stage(s) in
- %code .gitlab-ci.yml
- to track deployments here.
- = link_to "Read more", help_page_path("ci/environments"), class: "btn btn-success"
+ .blank-state-row
+ .blank-state-center
+ %h2.blank-state-title
+ You don't have any deployments right now.
+ %p.blank-state-text
+ Define environments in the deploy stage(s) in
+ %code .gitlab-ci.yml
+ to track deployments here.
+ = link_to "Read more", help_page_path("ci/environments"), class: "btn btn-success"
- else
.table-holder
.ci-table.environments{ role: 'grid' }
diff --git a/app/views/shared/_import_form.html.haml b/app/views/shared/_import_form.html.haml
index 233d8c95eda..736afa085e8 100644
--- a/app/views/shared/_import_form.html.haml
+++ b/app/views/shared/_import_form.html.haml
@@ -11,6 +11,7 @@
%li
If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>.
%li
- The import will time out after 15 minutes. For repositories that take longer, use a clone/push combination.
+ The import will time out after #{time_interval_in_words(Gitlab.config.gitlab_shell.git_timeout)}.
+ For repositories that take longer, use a clone/push combination.
%li
To migrate an SVN repository, check out #{link_to "this document", help_page_path('user/project/import/svn')}.
diff --git a/changelogs/unreleased/39461-notes-api-for-issues-no-longer-returns-label-additions-removals.yml b/changelogs/unreleased/39461-notes-api-for-issues-no-longer-returns-label-additions-removals.yml
new file mode 100644
index 00000000000..36c2f789eeb
--- /dev/null
+++ b/changelogs/unreleased/39461-notes-api-for-issues-no-longer-returns-label-additions-removals.yml
@@ -0,0 +1,5 @@
+---
+title: Label addition/removal are not going to be redacted wrongfully in the API.
+merge_request: 15080
+author:
+type: fixed
diff --git a/changelogs/unreleased/39977-gitlab-shell-default-timeout.yml b/changelogs/unreleased/39977-gitlab-shell-default-timeout.yml
new file mode 100644
index 00000000000..b7a974fd8d9
--- /dev/null
+++ b/changelogs/unreleased/39977-gitlab-shell-default-timeout.yml
@@ -0,0 +1,5 @@
+---
+title: Set the default gitlab-shell timeout to 3 hours
+merge_request: 15292
+author:
+type: fixed
diff --git a/changelogs/unreleased/40377-blank-states.yml b/changelogs/unreleased/40377-blank-states.yml
new file mode 100644
index 00000000000..7635602c68c
--- /dev/null
+++ b/changelogs/unreleased/40377-blank-states.yml
@@ -0,0 +1,5 @@
+---
+title: Fix blank states using old css
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/reduce-queries-for-artifacts-button.yml b/changelogs/unreleased/reduce-queries-for-artifacts-button.yml
new file mode 100644
index 00000000000..f2d469b5a80
--- /dev/null
+++ b/changelogs/unreleased/reduce-queries-for-artifacts-button.yml
@@ -0,0 +1,5 @@
+---
+title: Use arrays in Pipeline#latest_builds_with_artifacts
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/skip_confirmation_user_API.yml b/changelogs/unreleased/skip_confirmation_user_API.yml
new file mode 100644
index 00000000000..144ccd69e68
--- /dev/null
+++ b/changelogs/unreleased/skip_confirmation_user_API.yml
@@ -0,0 +1,7 @@
+---
+title: Add email confirmation parameters for user creation and update via API
+merge_request:
+author: Daniel Juarez
+type: added
+
+
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 7547ba4a8fa..7f6e68ceed6 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -559,8 +559,8 @@ production: &base
upload_pack: true
receive_pack: true
- # Git import/fetch timeout
- # git_timeout: 800
+ # Git import/fetch timeout, in seconds. Defaults to 3 hours.
+ # git_timeout: 10800
# If you use non-standard ssh port you need to specify it
# ssh_port: 22
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index d1156b0c8a8..f7c83f7b0f7 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -429,7 +429,7 @@ Settings.gitlab_shell['ssh_port'] ||= 22
Settings.gitlab_shell['ssh_user'] ||= Settings.gitlab.user
Settings.gitlab_shell['owner_group'] ||= Settings.gitlab.user
Settings.gitlab_shell['ssh_path_prefix'] ||= Settings.__send__(:build_gitlab_shell_ssh_path_prefix)
-Settings.gitlab_shell['git_timeout'] ||= 800
+Settings.gitlab_shell['git_timeout'] ||= 10800
#
# Workhorse
diff --git a/doc/api/users.md b/doc/api/users.md
index aa711090af1..478d747a50d 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -297,6 +297,7 @@ Parameters:
- `location` (optional) - User's location
- `admin` (optional) - User is admin - true or false (default)
- `can_create_group` (optional) - User can create groups - true or false
+- `skip_reconfirmation` (optional) - Skip reconfirmation - true or false (default)
- `external` (optional) - Flags the user as external - true or false(default)
- `avatar` (optional) - Image file for user's avatar
diff --git a/doc/integration/external-issue-tracker.md b/doc/integration/external-issue-tracker.md
index 372e1909330..075feaeead9 100644
--- a/doc/integration/external-issue-tracker.md
+++ b/doc/integration/external-issue-tracker.md
@@ -22,6 +22,7 @@ Visit the links below for details:
- [Redmine](../user/project/integrations/redmine.md)
- [Jira](../user/project/integrations/jira.md)
- [Bugzilla](../user/project/integrations/bugzilla.md)
+- [Custom Issue Tracker](../user/project/integrations/custom_issue_tracker.md)
### Service Template
diff --git a/doc/user/project/integrations/custom_issue_tracker.md b/doc/user/project/integrations/custom_issue_tracker.md
new file mode 100644
index 00000000000..757522c2ae3
--- /dev/null
+++ b/doc/user/project/integrations/custom_issue_tracker.md
@@ -0,0 +1,20 @@
+# Custom Issue Tracker Service
+
+To enable the Custom Issue Tracker integration in a project, navigate to the
+[Integrations page](project_services.md#accessing-the-project-services), click
+the **Customer Issue Tracker** service, and fill in the required details on the page as described
+in the table below.
+
+| Field | Description |
+| ----- | ----------- |
+| `title` | A title for the issue tracker (to differentiate between instances, for example) |
+| `description` | A name for the issue tracker (to differentiate between instances, for example) |
+| `project_url` | Currently unused. Will be changed in a future release. |
+| `issues_url` | The URL to the issue in the issue tracker project that is linked to this GitLab project. Note that the `issues_url` requires `:id` in the URL. This ID is used by GitLab as a placeholder to replace the issue number. For example, `https://customissuetracker.com/project-name/:id`. |
+| `new_issue_url` | Currently unused. Will be changed in a future release. |
+
+
+## Referencing issues
+
+Issues are referenced with `#<ID>`, where `<ID>` is a number (example `#143`).
+So with the example above, `#143` would refer to `https://customissuetracker.com/project-name/143`. \ No newline at end of file
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 0b9ab4eeb05..ceaaeca4046 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -33,7 +33,7 @@ module API
# paginate() only works with a relation. This could lead to a
# mismatch between the pagination headers info and the actual notes
# array returned, but this is really a edge-case.
- paginate(noteable.notes)
+ paginate(noteable.notes.with_metadata)
.reject { |n| n.cross_reference_not_visible_for?(current_user) }
present notes, with: Entities::Note
else
@@ -50,7 +50,7 @@ module API
end
get ":id/#{noteables_str}/:noteable_id/notes/:note_id" do
noteable = find_project_noteable(noteables_str, params[:noteable_id])
- note = noteable.notes.find(params[:note_id])
+ note = noteable.notes.with_metadata.find(params[:note_id])
can_read_note = can?(current_user, noteable_read_ability_name(noteable), noteable) && !note.cross_reference_not_visible_for?(current_user)
if can_read_note
diff --git a/lib/api/users.rb b/lib/api/users.rb
index d80b364bd09..0cd89b1bcf8 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -31,7 +31,6 @@ module API
optional :location, type: String, desc: 'The location of the user'
optional :admin, type: Boolean, desc: 'Flag indicating the user is an administrator'
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
@@ -101,6 +100,7 @@ module API
requires :email, type: String, desc: 'The email of the user'
optional :password, type: String, desc: 'The password of the new user'
optional :reset_password, type: Boolean, desc: 'Flag indicating the user will be sent a password reset token'
+ optional :skip_confirmation, type: Boolean, desc: 'Flag indicating the account is confirmed'
at_least_one_of :password, :reset_password
requires :name, type: String, desc: 'The name of the user'
requires :username, type: String, desc: 'The username of the user'
@@ -134,6 +134,7 @@ module API
requires :id, type: Integer, desc: 'The ID of the user'
optional :email, type: String, desc: 'The email of the user'
optional :password, type: String, desc: 'The password of the new user'
+ optional :skip_reconfirmation, type: Boolean, desc: 'Flag indicating the account skips the confirmation by email'
optional :name, type: String, desc: 'The name of the user'
optional :username, type: String, desc: 'The username of the user'
use :optional_attributes
diff --git a/lib/gitlab/middleware/read_only.rb b/lib/gitlab/middleware/read_only.rb
index 5e4932e4e57..c26656704d7 100644
--- a/lib/gitlab/middleware/read_only.rb
+++ b/lib/gitlab/middleware/read_only.rb
@@ -58,7 +58,7 @@ module Gitlab
end
def last_visited_url
- @env['HTTP_REFERER'] || rack_session['user_return_to'] || Rails.application.routes.url_helpers.root_url
+ @env['HTTP_REFERER'] || rack_session['user_return_to'] || Gitlab::Routing.url_helpers.root_url
end
def route_hash
@@ -74,10 +74,16 @@ module Gitlab
end
def grack_route
+ # Calling route_hash may be expensive. Only do it if we think there's a possible match
+ return false unless request.path.end_with?('.git/git-upload-pack')
+
route_hash[:controller] == 'projects/git_http' && route_hash[:action] == 'git_upload_pack'
end
def lfs_route
+ # Calling route_hash may be expensive. Only do it if we think there's a possible match
+ return false unless request.path.end_with?('/info/lfs/objects/batch')
+
route_hash[:controller] == 'projects/lfs_api' && route_hash[:action] == 'batch'
end
end
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index dc0184e4ad9..996d213fdb4 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -101,8 +101,7 @@ module Gitlab
#
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387
def import_repository(storage, name, url)
- # Timeout should be less than 900 ideally, to prevent the memory killer
- # to silently kill the process without knowing we are timing out here.
+ # The timeout ensures the subprocess won't hang forever
cmd = [gitlab_shell_projects_path, 'import-project',
storage, "#{name}.git", url, "#{Gitlab.config.gitlab_shell.git_timeout}"]
gitlab_shell_fast_execute_raise_error(cmd)
diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake
index 91c74bfb6b4..301affc9522 100644
--- a/lib/tasks/gitlab/cleanup.rake
+++ b/lib/tasks/gitlab/cleanup.rake
@@ -59,7 +59,10 @@ namespace :gitlab do
.sub(%r{^/*}, '')
.chomp('.git')
.chomp('.wiki')
- next if Project.find_by_full_path(repo_with_namespace)
+
+ # TODO ignoring hashed repositories for now. But revisit to fully support
+ # possible orphaned hashed repos
+ next if repo_with_namespace.start_with?('@hashed/') || Project.find_by_full_path(repo_with_namespace)
new_path = path + move_suffix
puts path.inspect + ' -> ' + new_path.inspect
diff --git a/lib/tasks/gitlab/gitaly.rake b/lib/tasks/gitlab/gitaly.rake
index 87835dbe719..f2002d7a426 100644
--- a/lib/tasks/gitlab/gitaly.rake
+++ b/lib/tasks/gitlab/gitaly.rake
@@ -14,8 +14,10 @@ namespace :gitlab do
checkout_or_clone_version(version: version, repo: args.repo, target_dir: args.dir)
+ command = %w[/usr/bin/env -u RUBYOPT -u BUNDLE_GEMFILE]
+
_, status = Gitlab::Popen.popen(%w[which gmake])
- command = status.zero? ? ['gmake'] : ['make']
+ command << (status.zero? ? 'gmake' : 'make')
command << 'BUNDLE_FLAGS=--no-deployment' if Rails.env.test?
@@ -23,7 +25,7 @@ namespace :gitlab do
create_gitaly_configuration
# In CI we run scripts/gitaly-test-build instead of this command
unless ENV['CI'].present?
- Bundler.with_original_env { run_command!(%w[/usr/bin/env -u RUBYOPT -u BUNDLE_GEMFILE] + command) }
+ Bundler.with_original_env { run_command!(command) }
end
end
end
@@ -80,9 +82,12 @@ namespace :gitlab do
end
def create_gitaly_configuration
- File.open("config.toml", "w") do |f|
+ File.open("config.toml", File::WRONLY | File::CREAT | File::EXCL) do |f|
f.puts gitaly_configuration_toml
end
+ rescue Errno::EEXIST
+ puts "Skipping config.toml generation:"
+ puts "A configuration file already exists."
rescue ArgumentError => e
puts "Skipping config.toml generation:"
puts e.message
diff --git a/spec/lib/gitlab/middleware/read_only_spec.rb b/spec/lib/gitlab/middleware/read_only_spec.rb
index b14735943a5..07ba11b93a3 100644
--- a/spec/lib/gitlab/middleware/read_only_spec.rb
+++ b/spec/lib/gitlab/middleware/read_only_spec.rb
@@ -84,14 +84,23 @@ describe Gitlab::Middleware::ReadOnly do
end
it 'expects POST of new file that looks like an LFS batch url to be disallowed' do
+ expect(Rails.application.routes).to receive(:recognize_path).and_call_original
response = request.post('/root/gitlab-ce/new/master/app/info/lfs/objects/batch')
expect(response).to be_a_redirect
expect(subject).to disallow_request
end
+ it 'returns last_vistited_url for disallowed request' do
+ response = request.post('/test_request')
+
+ expect(response.location).to eq 'http://localhost/'
+ end
+
context 'whitelisted requests' do
it 'expects a POST internal request to be allowed' do
+ expect(Rails.application.routes).not_to receive(:recognize_path)
+
response = request.post("/api/#{API::API.version}/internal")
expect(response).not_to be_a_redirect
@@ -99,6 +108,7 @@ describe Gitlab::Middleware::ReadOnly do
end
it 'expects a POST LFS request to batch URL to be allowed' do
+ expect(Rails.application.routes).to receive(:recognize_path).and_call_original
response = request.post('/root/rouge.git/info/lfs/objects/batch')
expect(response).not_to be_a_redirect
@@ -106,6 +116,7 @@ describe Gitlab::Middleware::ReadOnly do
end
it 'expects a POST request to git-upload-pack URL to be allowed' do
+ expect(Rails.application.routes).to receive(:recognize_path).and_call_original
response = request.post('/root/rouge.git/git-upload-pack')
expect(response).not_to be_a_redirect
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index b89b0e555d9..3a19a0753e2 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -1502,6 +1502,10 @@ describe Ci::Pipeline, :mailer do
create(:ci_build, :success, :artifacts, pipeline: pipeline)
end
+ it 'returns an Array' do
+ expect(pipeline.latest_builds_with_artifacts).to be_an_instance_of(Array)
+ end
+
it 'returns the latest builds' do
expect(pipeline.latest_builds_with_artifacts).to eq([build])
end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 1ecb50586c7..6e7e8c4c570 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -231,6 +231,37 @@ describe Note do
end
end
+ describe '#cross_reference?' do
+ it 'falsey for user-generated notes' do
+ note = create(:note, system: false)
+
+ expect(note.cross_reference?).to be_falsy
+ end
+
+ context 'when the note might contain cross references' do
+ SystemNoteMetadata::TYPES_WITH_CROSS_REFERENCES.each do |type|
+ let(:note) { create(:note, :system) }
+ let!(:metadata) { create(:system_note_metadata, note: note, action: type) }
+
+ it 'delegates to the cross-reference regex' do
+ expect(note).to receive(:matches_cross_reference_regex?).and_return(false)
+
+ note.cross_reference?
+ end
+ end
+ end
+
+ context 'when the note cannot contain cross references' do
+ let(:commit_note) { build(:note, note: 'mentioned in 1312312313 something else.', system: true) }
+ let(:label_note) { build(:note, note: 'added ~2323232323', system: true) }
+
+ it 'scan for a `mentioned in` prefix' do
+ expect(commit_note.cross_reference?).to be_truthy
+ expect(label_note.cross_reference?).to be_falsy
+ end
+ end
+ end
+
describe 'clear_blank_line_code!' do
it 'clears a blank line code before validation' do
note = build(:note, line_code: ' ')
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 8a6aa767ce6..e9e6abb0d5f 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -1166,6 +1166,31 @@ describe Repository do
end
end
+ describe '#branch_exists?' do
+ it 'uses branch_names' do
+ allow(repository).to receive(:branch_names).and_return(['foobar'])
+
+ expect(repository.branch_exists?('foobar')).to eq(true)
+ expect(repository.branch_exists?('master')).to eq(false)
+ end
+ end
+
+ describe '#branch_names', :use_clean_rails_memory_store_caching do
+ let(:fake_branch_names) { ['foobar'] }
+
+ it 'gets cached across Repository instances' do
+ allow(repository.raw_repository).to receive(:branch_names).once.and_return(fake_branch_names)
+
+ expect(repository.branch_names).to eq(fake_branch_names)
+
+ fresh_repository = Project.find(project.id).repository
+ expect(fresh_repository.object_id).not_to eq(repository.object_id)
+
+ expect(fresh_repository.raw_repository).not_to receive(:branch_names)
+ expect(fresh_repository.branch_names).to eq(fake_branch_names)
+ end
+ end
+
describe '#update_autocrlf_option' do
describe 'when autocrlf is not already set to :input' do
before do
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 2aeae6f9ec7..2428e63e149 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -510,6 +510,14 @@ describe API::Users do
expect(user.reload.notification_email).to eq('new@email.com')
end
+ it 'skips reconfirmation when requested' do
+ put api("/users/#{user.id}", admin), { skip_reconfirmation: true }
+
+ user.reload
+
+ expect(user.confirmed_at).to be_present
+ end
+
it 'updates user with his own username' do
put api("/users/#{user.id}", admin), username: user.username
diff --git a/spec/tasks/gitlab/cleanup_rake_spec.rb b/spec/tasks/gitlab/cleanup_rake_spec.rb
new file mode 100644
index 00000000000..641eccfd334
--- /dev/null
+++ b/spec/tasks/gitlab/cleanup_rake_spec.rb
@@ -0,0 +1,41 @@
+require 'rake_helper'
+
+describe 'gitlab:cleanup rake tasks' do
+ before do
+ Rake.application.rake_require 'tasks/gitlab/cleanup'
+ end
+
+ context 'cleanup repositories' do
+ let(:gitaly_address) { Gitlab.config.repositories.storages.default.gitaly_address }
+ let(:storages) do
+ {
+ 'default' => { 'path' => Settings.absolute('tmp/tests/default_storage'), 'gitaly_address' => gitaly_address }
+ }
+ end
+
+ before do
+ FileUtils.mkdir(Settings.absolute('tmp/tests/default_storage'))
+ allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
+ end
+
+ after do
+ FileUtils.rm_rf(Settings.absolute('tmp/tests/default_storage'))
+ end
+
+ it 'moves it to an orphaned path' do
+ FileUtils.mkdir_p(Settings.absolute('tmp/tests/default_storage/broken/project.git'))
+ run_rake_task('gitlab:cleanup:repos')
+ repo_list = Dir['tmp/tests/default_storage/broken/*']
+
+ expect(repo_list.first).to include('+orphaned+')
+ end
+
+ it 'ignores @hashed repos' do
+ FileUtils.mkdir_p(Settings.absolute('tmp/tests/default_storage/@hashed/12/34/5678.git'))
+
+ run_rake_task('gitlab:cleanup:repos')
+
+ expect(Dir.exist?(Settings.absolute('tmp/tests/default_storage/@hashed/12/34/5678.git'))).to be_truthy
+ end
+ end
+end
diff --git a/spec/tasks/gitlab/gitaly_rake_spec.rb b/spec/tasks/gitlab/gitaly_rake_spec.rb
index 255f0a37ec8..a51374e2645 100644
--- a/spec/tasks/gitlab/gitaly_rake_spec.rb
+++ b/spec/tasks/gitlab/gitaly_rake_spec.rb
@@ -47,7 +47,7 @@ describe 'gitlab:gitaly namespace rake task' do
stub_env('CI', false)
FileUtils.mkdir_p(clone_path)
expect(Dir).to receive(:chdir).with(clone_path).and_call_original
- allow(Bundler).to receive(:bundle_path).and_return('/fake/bundle_path')
+ allow(Rails.env).to receive(:test?).and_return(false)
end
context 'gmake is available' do
@@ -57,7 +57,7 @@ describe 'gitlab:gitaly namespace rake task' do
it 'calls gmake in the gitaly directory' do
expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['/usr/bin/gmake', 0])
- expect(main_object).to receive(:run_command!).with(command_preamble + %w[gmake BUNDLE_FLAGS=--no-deployment]).and_return(true)
+ expect(main_object).to receive(:run_command!).with(command_preamble + %w[gmake]).and_return(true)
run_rake_task('gitlab:gitaly:install', clone_path)
end
@@ -70,18 +70,20 @@ describe 'gitlab:gitaly namespace rake task' do
end
it 'calls make in the gitaly directory' do
- expect(main_object).to receive(:run_command!).with(command_preamble + %w[make BUNDLE_FLAGS=--no-deployment]).and_return(true)
+ expect(main_object).to receive(:run_command!).with(command_preamble + %w[make]).and_return(true)
run_rake_task('gitlab:gitaly:install', clone_path)
end
- context 'when Rails.env is not "test"' do
+ context 'when Rails.env is test' do
+ let(:command) { %w[make BUNDLE_FLAGS=--no-deployment] }
+
before do
- allow(Rails.env).to receive(:test?).and_return(false)
+ allow(Rails.env).to receive(:test?).and_return(true)
end
- it 'calls make in the gitaly directory without BUNDLE_PATH' do
- expect(main_object).to receive(:run_command!).with(command_preamble + ['make']).and_return(true)
+ it 'calls make in the gitaly directory with --no-deployment flag for bundle' do
+ expect(main_object).to receive(:run_command!).with(command_preamble + command).and_return(true)
run_rake_task('gitlab:gitaly:install', clone_path)
end
diff --git a/spec/uploaders/file_uploader_spec.rb b/spec/uploaders/file_uploader_spec.rb
index f52b2bab05b..fd195d6f9b8 100644
--- a/spec/uploaders/file_uploader_spec.rb
+++ b/spec/uploaders/file_uploader_spec.rb
@@ -28,25 +28,51 @@ describe FileUploader do
end
context 'hashed storage' do
- let(:project) { build_stubbed(:project, :hashed) }
+ context 'when rolled out attachments' do
+ let(:project) { build_stubbed(:project, :hashed) }
- describe '.absolute_path' do
- it 'returns the correct absolute path by building it dynamically' do
- upload = double(model: project, path: 'secret/foo.jpg')
+ describe '.absolute_path' do
+ it 'returns the correct absolute path by building it dynamically' do
+ upload = double(model: project, path: 'secret/foo.jpg')
- dynamic_segment = project.disk_path
+ dynamic_segment = project.disk_path
- expect(described_class.absolute_path(upload))
- .to end_with("#{dynamic_segment}/secret/foo.jpg")
+ expect(described_class.absolute_path(upload))
+ .to end_with("#{dynamic_segment}/secret/foo.jpg")
+ end
+ end
+
+ describe "#store_dir" do
+ it "stores in the namespace path" do
+ uploader = described_class.new(project)
+
+ expect(uploader.store_dir).to include(project.disk_path)
+ expect(uploader.store_dir).not_to include("system")
+ end
end
end
- describe "#store_dir" do
- it "stores in the namespace path" do
- uploader = described_class.new(project)
+ context 'when only repositories are rolled out' do
+ let(:project) { build_stubbed(:project, storage_version: Project::HASHED_STORAGE_FEATURES[:repository]) }
- expect(uploader.store_dir).to include(project.disk_path)
- expect(uploader.store_dir).not_to include("system")
+ describe '.absolute_path' do
+ it 'returns the correct absolute path by building it dynamically' do
+ upload = double(model: project, path: 'secret/foo.jpg')
+
+ dynamic_segment = project.full_path
+
+ expect(described_class.absolute_path(upload))
+ .to end_with("#{dynamic_segment}/secret/foo.jpg")
+ end
+ end
+
+ describe "#store_dir" do
+ it "stores in the namespace path" do
+ uploader = described_class.new(project)
+
+ expect(uploader.store_dir).to include(project.full_path)
+ expect(uploader.store_dir).not_to include("system")
+ end
end
end
end