diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-04 15:08:40 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-04 15:08:40 +0000 |
commit | 6b833f1e0340e00fdee074da9c42c0d4e07a46d2 (patch) | |
tree | 6fc3a7a2f8a02fec8d1e7561b453d33eb4048dad | |
parent | 88a0824944720b6edaaef56376713541b9a02118 (diff) | |
download | gitlab-ce-6b833f1e0340e00fdee074da9c42c0d4e07a46d2.tar.gz |
Add latest changes from gitlab-org/gitlab@master
79 files changed, 1109 insertions, 344 deletions
diff --git a/app/assets/javascripts/diffs/components/diff_stats.vue b/app/assets/javascripts/diffs/components/diff_stats.vue index 5329cb1e241..0138790cf0e 100644 --- a/app/assets/javascripts/diffs/components/diff_stats.vue +++ b/app/assets/javascripts/diffs/components/diff_stats.vue @@ -17,7 +17,7 @@ export default { diffFilesLength: { type: Number, required: false, - default: 0, + default: null, }, }, computed: { diff --git a/app/assets/javascripts/error_tracking/components/error_details.vue b/app/assets/javascripts/error_tracking/components/error_details.vue index 98fc121d39f..88861b7da0e 100644 --- a/app/assets/javascripts/error_tracking/components/error_details.vue +++ b/app/assets/javascripts/error_tracking/components/error_details.vue @@ -2,7 +2,15 @@ import { mapActions, mapGetters, mapState } from 'vuex'; import dateFormat from 'dateformat'; import createFlash from '~/flash'; -import { GlButton, GlFormInput, GlLink, GlLoadingIcon, GlBadge } from '@gitlab/ui'; +import { + GlButton, + GlFormInput, + GlLink, + GlLoadingIcon, + GlBadge, + GlAlert, + GlSprintf, +} from '@gitlab/ui'; import { __, sprintf, n__ } from '~/locale'; import LoadingButton from '~/vue_shared/components/loading_button.vue'; import Icon from '~/vue_shared/components/icon.vue'; @@ -26,6 +34,8 @@ export default { Icon, Stacktrace, GlBadge, + GlAlert, + GlSprintf, }, directives: { TrackEvent: TrackEventDirective, @@ -85,6 +95,8 @@ export default { return { GQLerror: null, issueCreationInProgress: false, + isAlertVisible: false, + closedIssueId: null, }; }, computed: { @@ -184,7 +196,14 @@ export default { onResolveStatusUpdate() { const status = this.errorStatus === errorStatus.RESOLVED ? errorStatus.UNRESOLVED : errorStatus.RESOLVED; - this.updateResolveStatus({ endpoint: this.issueUpdatePath, status }); + + // eslint-disable-next-line promise/catch-or-return + this.updateResolveStatus({ endpoint: this.issueUpdatePath, status }).then(res => { + this.closedIssueId = res.closed_issue_iid; + if (this.closedIssueId) { + this.isAlertVisible = true; + } + }); }, formatDate(date) { return `${this.timeFormatted(date)} (${dateFormat(date, 'UTC:yyyy-mm-dd h:MM:ssTT Z')})`; @@ -199,6 +218,18 @@ export default { <gl-loading-icon :size="3" /> </div> <div v-else-if="showDetails" class="error-details"> + <gl-alert v-if="isAlertVisible" @dismiss="isAlertVisible = false"> + <gl-sprintf + :message=" + __('The associated issue #%{issueId} has been closed as the error is now resolved.') + " + > + <template #issueId> + <span>{{ closedIssueId }}</span> + </template> + </gl-sprintf> + </gl-alert> + <div class="top-area align-items-center justify-content-between py-3"> <span v-if="!loadingStacktrace && stacktrace" v-html="reported"></span> <div class="d-inline-flex"> diff --git a/app/assets/javascripts/error_tracking/store/actions.js b/app/assets/javascripts/error_tracking/store/actions.js index 49fa5f3cec5..8f6f404ef8a 100644 --- a/app/assets/javascripts/error_tracking/store/actions.js +++ b/app/assets/javascripts/error_tracking/store/actions.js @@ -11,9 +11,11 @@ export const setStatus = ({ commit }, status) => { export const updateStatus = ({ commit }, { endpoint, redirectUrl, status }) => service .updateErrorStatus(endpoint, status) - .then(() => { - if (redirectUrl) visitUrl(redirectUrl); + .then(resp => { commit(types.SET_ERROR_STATUS, status); + if (redirectUrl) visitUrl(redirectUrl); + + return resp.data.result; }) .catch(() => createFlash(__('Failed to update issue status'))); diff --git a/app/assets/javascripts/releases/list/components/release_block_author.vue b/app/assets/javascripts/releases/list/components/release_block_author.vue index ff6b00d8221..e7075d4d67a 100644 --- a/app/assets/javascripts/releases/list/components/release_block_author.vue +++ b/app/assets/javascripts/releases/list/components/release_block_author.vue @@ -27,7 +27,7 @@ export default { <template> <div class="d-flex"> - <gl-sprintf message="by %{user}"> + <gl-sprintf :message="__('by %{user}')"> <template #user> <user-avatar-link class="prepend-left-4" diff --git a/app/assets/javascripts/repository/components/last_commit.vue b/app/assets/javascripts/repository/components/last_commit.vue index 573b0c4963e..c0c599f4b3c 100644 --- a/app/assets/javascripts/repository/components/last_commit.vue +++ b/app/assets/javascripts/repository/components/last_commit.vue @@ -146,9 +146,8 @@ export default { v-if="commit.description" :class="{ 'd-block': showDescription }" class="commit-row-description append-bottom-8" + >{{ commit.description }}</pre > - {{ commit.description }} - </pre> </div> <div class="commit-actions flex-row"> <div v-if="commit.signatureHtml" v-html="commit.signatureHtml"></div> diff --git a/app/assets/javascripts/snippets/components/snippet_header.vue b/app/assets/javascripts/snippets/components/snippet_header.vue index e8f1bfeaf43..36ba6eeecbd 100644 --- a/app/assets/javascripts/snippets/components/snippet_header.vue +++ b/app/assets/javascripts/snippets/components/snippet_header.vue @@ -165,7 +165,7 @@ export default { <gl-icon :name="visibilityLevelIcon" :size="14" /> </div> <div class="creator"> - <gl-sprintf message="Authored %{timeago} by %{author}"> + <gl-sprintf :message="__('Authored %{timeago} by %{author}')"> <template #timeago> <time-ago-tooltip :time="snippet.createdAt" @@ -218,7 +218,7 @@ export default { errorMessage }}</gl-alert> - <gl-sprintf message="Are you sure you want to delete %{name}?"> + <gl-sprintf :message="__('Are you sure you want to delete %{name}?')"> <template #name ><strong>{{ snippet.title }}</strong></template > diff --git a/app/assets/javascripts/snippets/components/snippet_title.vue b/app/assets/javascripts/snippets/components/snippet_title.vue index fc8a9b4a390..6646e70f5db 100644 --- a/app/assets/javascripts/snippets/components/snippet_title.vue +++ b/app/assets/javascripts/snippets/components/snippet_title.vue @@ -25,7 +25,7 @@ export default { </div> <small v-if="snippet.updatedAt !== snippet.createdAt" class="edited-text"> - <gl-sprintf message="Edited %{timeago}"> + <gl-sprintf :message="__('Edited %{timeago}')"> <template #timeago> <time-ago-tooltip :time="snippet.updatedAt" tooltip-placement="bottom" /> </template> diff --git a/app/models/blob.rb b/app/models/blob.rb index 258006d8d2e..d8282c918b7 100644 --- a/app/models/blob.rb +++ b/app/models/blob.rb @@ -65,7 +65,10 @@ class Blob < SimpleDelegator BlobViewer::YarnLock ].freeze - attr_reader :project + attr_reader :container + + delegate :repository, to: :container, allow_nil: true + delegate :project, to: :repository, allow_nil: true # Wrap a Gitlab::Git::Blob object, or return nil when given nil # @@ -77,22 +80,22 @@ class Blob < SimpleDelegator # # blob = Blob.decorate(nil) # puts "truthy" if blob # No output - def self.decorate(blob, project = nil) + def self.decorate(blob, container = nil) return if blob.nil? - new(blob, project) + new(blob, container) end - def self.lazy(project, commit_id, path, blob_size_limit: Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE) - BatchLoader.for([commit_id, path]).batch(key: project.repository) do |items, loader, args| + def self.lazy(container, commit_id, path, blob_size_limit: Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE) + BatchLoader.for([commit_id, path]).batch(key: container.repository) do |items, loader, args| args[:key].blobs_at(items, blob_size_limit: blob_size_limit).each do |blob| loader.call([blob.commit_id, blob.path], blob) if blob end end end - def initialize(blob, project = nil) - @project = project + def initialize(blob, container = nil) + @container = container super(blob) end @@ -116,7 +119,7 @@ class Blob < SimpleDelegator def load_all_data! # Endpoint needed: https://gitlab.com/gitlab-org/gitaly/issues/756 Gitlab::GitalyClient.allow_n_plus_1_calls do - super(project.repository) if project + super(repository) if container end end diff --git a/app/models/commit.rb b/app/models/commit.rb index 31a890096e9..f2a6a8b6cbb 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -21,11 +21,14 @@ class Commit participant :committer participant :notes_with_associations - attr_accessor :project, :author + attr_accessor :author attr_accessor :redacted_description_html attr_accessor :redacted_title_html attr_accessor :redacted_full_title_html - attr_reader :gpg_commit + attr_reader :gpg_commit, :container + + delegate :repository, to: :container + delegate :project, to: :repository, allow_nil: true DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines] @@ -44,12 +47,12 @@ class Commit cache_markdown_field :description, pipeline: :commit_description class << self - def decorate(commits, project) + def decorate(commits, container) commits.map do |commit| if commit.is_a?(Commit) commit else - self.new(commit, project) + self.new(commit, container) end end end @@ -85,24 +88,24 @@ class Commit } end - def from_hash(hash, project) - raw_commit = Gitlab::Git::Commit.new(project.repository.raw, hash) - new(raw_commit, project) + def from_hash(hash, container) + raw_commit = Gitlab::Git::Commit.new(container.repository.raw, hash) + new(raw_commit, container) end def valid_hash?(key) !!(EXACT_COMMIT_SHA_PATTERN =~ key) end - def lazy(project, oid) - BatchLoader.for({ project: project, oid: oid }).batch(replace_methods: false) do |items, loader| - items_by_project = items.group_by { |i| i[:project] } + def lazy(container, oid) + BatchLoader.for({ container: container, oid: oid }).batch(replace_methods: false) do |items, loader| + items_by_container = items.group_by { |i| i[:container] } - items_by_project.each do |project, commit_ids| + items_by_container.each do |container, commit_ids| oids = commit_ids.map { |i| i[:oid] } - project.repository.commits_by(oids: oids).each do |commit| - loader.call({ project: commit.project, oid: commit.id }, commit) if commit + container.repository.commits_by(oids: oids).each do |commit| + loader.call({ container: commit.container, oid: commit.id }, commit) if commit end end end @@ -115,12 +118,12 @@ class Commit attr_accessor :raw - def initialize(raw_commit, project) + def initialize(raw_commit, container) raise "Nil as raw commit passed" unless raw_commit @raw = raw_commit - @project = project - @gpg_commit = Gitlab::Gpg::Commit.new(self) if project + @container = container + @gpg_commit = Gitlab::Gpg::Commit.new(self) if container end delegate \ @@ -141,7 +144,7 @@ class Commit end def project_id - project.id + project&.id end def ==(other) @@ -269,17 +272,17 @@ class Commit end def parents - @parents ||= parent_ids.map { |oid| Commit.lazy(project, oid) } + @parents ||= parent_ids.map { |oid| Commit.lazy(container, oid) } end def parent strong_memoize(:parent) do - project.commit_by(oid: self.parent_id) if self.parent_id + container.commit_by(oid: self.parent_id) if self.parent_id end end def notes - project.notes.for_commit_id(self.id) + container.notes.for_commit_id(self.id) end def user_mentions @@ -295,7 +298,7 @@ class Commit end def merge_requests - @merge_requests ||= project.merge_requests.by_commit_sha(sha) + @merge_requests ||= project&.merge_requests&.by_commit_sha(sha) end def method_missing(method, *args, &block) @@ -330,7 +333,7 @@ class Commit end def cherry_pick_branch_name - project.repository.next_branch("cherry-pick-#{short_id}", mild: true) + repository.next_branch("cherry-pick-#{short_id}", mild: true) end def cherry_pick_description(user) @@ -418,7 +421,7 @@ class Commit return unless entry if entry[:type] == :blob - blob = ::Blob.decorate(Gitlab::Git::Blob.new(name: entry[:name]), @project) + blob = ::Blob.decorate(Gitlab::Git::Blob.new(name: entry[:name]), container) blob.image? || blob.video? || blob.audio? ? :raw : :blob else entry[:type] @@ -484,7 +487,7 @@ class Commit end def commit_reference(from, referable_commit_id, full: false) - base = project.to_reference_base(from, full: full) + base = project&.to_reference_base(from, full: full) if base.present? "#{base}#{self.class.reference_prefix}#{referable_commit_id}" @@ -510,6 +513,6 @@ class Commit end def merged_merge_request_no_cache(user) - MergeRequestsFinder.new(user, project_id: project.id).find_by(merge_commit_sha: id) if merge_commit? + MergeRequestsFinder.new(user, project_id: project_id).find_by(merge_commit_sha: id) if merge_commit? end end diff --git a/app/models/commit_collection.rb b/app/models/commit_collection.rb index d4c29aa295b..456d32bf403 100644 --- a/app/models/commit_collection.rb +++ b/app/models/commit_collection.rb @@ -1,17 +1,20 @@ # frozen_string_literal: true -# A collection of Commit instances for a specific project and Git reference. +# A collection of Commit instances for a specific container and Git reference. class CommitCollection include Enumerable include Gitlab::Utils::StrongMemoize - attr_reader :project, :ref, :commits + attr_reader :container, :ref, :commits - # project - The project the commits belong to. + delegate :repository, to: :container, allow_nil: true + delegate :project, to: :repository, allow_nil: true + + # container - The object the commits belong to. # commits - The Commit instances to store. # ref - The name of the ref (e.g. "master"). - def initialize(project, commits, ref = nil) - @project = project + def initialize(container, commits, ref = nil) + @container = container @commits = commits @ref = ref end @@ -39,6 +42,8 @@ class CommitCollection # Setting the pipeline for each commit ahead of time removes the need for running # a query for every commit we're displaying. def with_latest_pipeline(ref = nil) + return self unless project + pipelines = project.ci_pipelines.latest_pipeline_per_commit(map(&:id), ref) each do |commit| @@ -59,16 +64,16 @@ class CommitCollection # Batch load any commits that are not backed by full gitaly data, and # replace them in the collection. def enrich! - # A project is needed in order to fetch data from gitaly. Projects + # A container is needed in order to fetch data from gitaly. Containers # can be absent from commits in certain rare situations (like when # viewing a MR of a deleted fork). In these cases, assume that the # enriched data is not needed. - return self if project.blank? || fully_enriched? + return self if container.blank? || fully_enriched? # Batch load full Commits from the repository # and map to a Hash of id => Commit replacements = Hash[unenriched.map do |c| - [c.id, Commit.lazy(project, c.id)] + [c.id, Commit.lazy(container, c.id)] end.compact] # Replace the commits, keeping the same order diff --git a/app/models/concerns/has_repository.rb b/app/models/concerns/has_repository.rb new file mode 100644 index 00000000000..66c2f57bedd --- /dev/null +++ b/app/models/concerns/has_repository.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +module HasRepository + extend ActiveSupport::Concern + include Gitlab::ShellAdapter + include AfterCommitQueue + include Gitlab::Utils::StrongMemoize + + delegate :base_dir, :disk_path, to: :storage + + def valid_repo? + repository.exists? + rescue + errors.add(:path, _('Invalid repository path')) + false + end + + def repo_exists? + strong_memoize(:repo_exists) do + repository.exists? + rescue + false + end + end + + def repository_exists? + !!repository.exists? + end + + def root_ref?(branch) + repository.root_ref == branch + end + + def commit(ref = 'HEAD') + repository.commit(ref) + end + + def commit_by(oid:) + repository.commit_by(oid: oid) + end + + def commits_by(oids:) + repository.commits_by(oids: oids) + end + + def repository + raise NotImplementedError + end + + def storage + raise NotImplementedError + end + + def full_path + raise NotImplementedError + end + + def empty_repo? + repository.empty? + end + + def default_branch + @default_branch ||= repository.root_ref + end + + def reload_default_branch + @default_branch = nil # rubocop:disable Gitlab/ModuleWithInstanceVariables + + default_branch + end + + def url_to_repo + gitlab_shell.url_to_repo(full_path) + end + + def ssh_url_to_repo + url_to_repo + end + + def http_url_to_repo + custom_root = Gitlab::CurrentSettings.custom_http_clone_url_root + + url = if custom_root.present? + Gitlab::Utils.append_path( + custom_root, + web_url(only_path: true) + ) + else + web_url + end + + "#{url}.git" + end + + def web_url(only_path: nil) + raise NotImplementedError + end +end diff --git a/app/models/project.rb b/app/models/project.rb index 54bed41e9e7..31aeb0146df 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -19,6 +19,7 @@ class Project < ApplicationRecord include ProjectFeaturesCompatibility include SelectForProjectAuthorization include Presentable + include HasRepository include Routable include GroupDescendant include Gitlab::SQL::Pattern @@ -326,7 +327,6 @@ class Project < ApplicationRecord to: :project_feature, allow_nil: true delegate :scheduled?, :started?, :in_progress?, :failed?, :finished?, prefix: :import, to: :import_state, allow_nil: true - delegate :base_dir, :disk_path, to: :storage delegate :no_import?, to: :import_state, allow_nil: true delegate :name, to: :owner, allow_nil: true, prefix: true delegate :members, to: :team, prefix: true @@ -767,10 +767,6 @@ class Project < ApplicationRecord Feature.enabled?(:context_commits, default_enabled: true) end - def empty_repo? - repository.empty? - end - def team @team ||= ProjectTeam.new(self) end @@ -798,18 +794,6 @@ class Project < ApplicationRecord has_root_container_repository_tags? end - def commit(ref = 'HEAD') - repository.commit(ref) - end - - def commit_by(oid:) - repository.commit_by(oid: oid) - end - - def commits_by(oids:) - repository.commits_by(oids: oids) - end - # ref can't be HEAD, can only be branch/tag name def latest_successful_build_for_ref(job_name, ref = default_branch) return unless ref @@ -1357,48 +1341,6 @@ class Project < ApplicationRecord services.public_send(hooks_scope).any? # rubocop:disable GitlabSecurity/PublicSend end - def valid_repo? - repository.exists? - rescue - errors.add(:path, _('Invalid repository path')) - false - end - - def url_to_repo - gitlab_shell.url_to_repo(full_path) - end - - def repo_exists? - strong_memoize(:repo_exists) do - repository.exists? - rescue - false - end - end - - def root_ref?(branch) - repository.root_ref == branch - end - - def ssh_url_to_repo - url_to_repo - end - - def http_url_to_repo - custom_root = Gitlab::CurrentSettings.custom_http_clone_url_root - - project_url = if custom_root.present? - Gitlab::Utils.append_path( - custom_root, - web_url(only_path: true) - ) - else - web_url - end - - "#{project_url}.git" - end - # Is overridden in EE def lfs_http_url_to_repo(_) http_url_to_repo @@ -1538,15 +1480,6 @@ class Project < ApplicationRecord end end - def default_branch - @default_branch ||= repository.root_ref - end - - def reload_default_branch - @default_branch = nil - default_branch - end - def visibility_level_field :visibility_level end @@ -1583,10 +1516,6 @@ class Project < ApplicationRecord create_repository(force: true) unless repository_exists? end - def repository_exists? - !!repository.exists? - end - def wiki_repository_exists? wiki.repository_exists? end diff --git a/app/models/repository.rb b/app/models/repository.rb index 9807aed07a9..d720980faab 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -22,7 +22,7 @@ class Repository include Gitlab::RepositoryCacheAdapter - attr_accessor :full_path, :disk_path, :project, :repo_type + attr_accessor :full_path, :disk_path, :container, :repo_type delegate :ref_name_for_sha, to: :raw_repository delegate :bundle_to_disk, to: :raw_repository @@ -67,10 +67,10 @@ class Repository MERGED_BRANCH_NAMES_CACHE_DURATION = 10.minutes - def initialize(full_path, project, disk_path: nil, repo_type: Gitlab::GlRepository::PROJECT) + def initialize(full_path, container, disk_path: nil, repo_type: Gitlab::GlRepository::PROJECT) @full_path = full_path @disk_path = disk_path || full_path - @project = project + @container = container @commit_cache = {} @repo_type = repo_type end @@ -97,7 +97,7 @@ class Repository def path_to_repo @path_to_repo ||= begin - storage = Gitlab.config.repositories.storages[project.repository_storage] + storage = Gitlab.config.repositories.storages[container.repository_storage] File.expand_path( File.join(storage.legacy_disk_path, disk_path + '.git') @@ -130,7 +130,7 @@ class Repository commits = Gitlab::Git::Commit.batch_by_oid(raw_repository, oids) if commits.present? - Commit.decorate(commits, project) + Commit.decorate(commits, container) else [] end @@ -161,14 +161,14 @@ class Repository } commits = Gitlab::Git::Commit.where(options) - commits = Commit.decorate(commits, project) if commits.present? + commits = Commit.decorate(commits, container) if commits.present? - CommitCollection.new(project, commits, ref) + CommitCollection.new(container, commits, ref) end def commits_between(from, to) commits = Gitlab::Git::Commit.between(raw_repository, from, to) - commits = Commit.decorate(commits, project) if commits.present? + commits = Commit.decorate(commits, container) if commits.present? commits end @@ -176,7 +176,7 @@ class Repository def new_commits(newrev) commits = raw.new_commits(newrev) - ::Commit.decorate(commits, project) + ::Commit.decorate(commits, container) end # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/384 @@ -188,7 +188,7 @@ class Repository commits = raw_repository.find_commits_by_message(query, ref, path, limit, offset).map do |c| commit(c) end - CommitCollection.new(project, commits, ref) + CommitCollection.new(container, commits, ref) end def find_branch(name) @@ -281,7 +281,7 @@ class Repository raw_repository.archive_metadata( ref, storage_path, - project.path, + project&.path, format, append_sha: append_sha, path: path @@ -499,7 +499,7 @@ class Repository end def blob_at(sha, path) - blob = Blob.decorate(raw_repository.blob_at(sha, path), project) + blob = Blob.decorate(raw_repository.blob_at(sha, path), container) # Don't attempt to return a special result if there is no blob at all return unless blob @@ -522,7 +522,7 @@ class Repository return [] unless exists? raw_repository.batch_blobs(items, blob_size_limit: blob_size_limit).map do |blob| - Blob.decorate(blob, project) + Blob.decorate(blob, container) end end @@ -701,13 +701,13 @@ class Repository commits = raw_repository.list_last_commits_for_tree(sha, path, offset: offset, limit: limit) commits.each do |path, commit| - commits[path] = ::Commit.new(commit, project) + commits[path] = ::Commit.new(commit, container) end end def last_commit_for_path(sha, path) commit = raw_repository.last_commit_for_path(sha, path) - ::Commit.new(commit, project) if commit + ::Commit.new(commit, container) if commit end def last_commit_id_for_path(sha, path) @@ -986,6 +986,7 @@ class Repository # rubocop:disable Gitlab/RailsLogger def async_remove_remote(remote_name) return unless remote_name + return unless project job_id = RepositoryRemoveRemoteWorker.perform_async(project.id, remote_name) @@ -1157,6 +1158,10 @@ class Repository Gitlab::Git::Blob.batch_metadata(raw, references).map { |raw_blob| Blob.decorate(raw_blob) } end + def project + container + end + private # TODO Genericize finder, later split this on finders by Ref or Oid @@ -1203,10 +1208,10 @@ class Repository end def initialize_raw_repository - Gitlab::Git::Repository.new(project.repository_storage, + Gitlab::Git::Repository.new(container.repository_storage, disk_path + '.git', - repo_type.identifier_for_container(project), - project.full_path) + repo_type.identifier_for_container(container), + container.full_path) end end diff --git a/app/models/user.rb b/app/models/user.rb index bc113c72762..d44e8162abe 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1527,6 +1527,13 @@ class User < ApplicationRecord end def read_only_attribute?(attribute) + if Feature.enabled?(:ldap_readonly_attributes, default_enabled: true) + enabled = Gitlab::Auth::LDAP::Config.enabled? + read_only = attribute.to_sym.in?(UserSyncedAttributesMetadata::SYNCABLE_ATTRIBUTES) + + return true if enabled && read_only + end + user_synced_attributes_metadata&.read_only?(attribute) end diff --git a/app/services/merge_requests/delete_non_latest_diffs_service.rb b/app/services/merge_requests/delete_non_latest_diffs_service.rb index bdb7ec8a7c2..49ec3c7538c 100644 --- a/app/services/merge_requests/delete_non_latest_diffs_service.rb +++ b/app/services/merge_requests/delete_non_latest_diffs_service.rb @@ -13,7 +13,7 @@ module MergeRequests diffs.each_batch(of: BATCH_SIZE) do |relation, index| ids = relation.pluck_primary_key.map { |id| [id] } - DeleteDiffFilesWorker.bulk_perform_in(index * 5.minutes, ids) + DeleteDiffFilesWorker.bulk_perform_in(index * 5.minutes, ids) # rubocop:disable Scalability/BulkPerformWithContext end end end diff --git a/app/services/merge_requests/migrate_external_diffs_service.rb b/app/services/merge_requests/migrate_external_diffs_service.rb index 16050244637..9fa01e0a134 100644 --- a/app/services/merge_requests/migrate_external_diffs_service.rb +++ b/app/services/merge_requests/migrate_external_diffs_service.rb @@ -9,7 +9,7 @@ module MergeRequests def self.enqueue! ids = MergeRequestDiff.ids_for_external_storage_migration(limit: MAX_JOBS) - MigrateExternalDiffsWorker.bulk_perform_async(ids.map { |id| [id] }) + MigrateExternalDiffsWorker.bulk_perform_async(ids.map { |id| [id] }) # rubocop:disable Scalability/BulkPerformWithContext end def initialize(merge_request_diff) diff --git a/app/services/user_project_access_changed_service.rb b/app/services/user_project_access_changed_service.rb index 21b52944800..21d0861ac3f 100644 --- a/app/services/user_project_access_changed_service.rb +++ b/app/services/user_project_access_changed_service.rb @@ -11,7 +11,7 @@ class UserProjectAccessChangedService if blocking AuthorizedProjectsWorker.bulk_perform_and_wait(bulk_args) else - AuthorizedProjectsWorker.bulk_perform_async(bulk_args) + AuthorizedProjectsWorker.bulk_perform_async(bulk_args) # rubocop:disable Scalability/BulkPerformWithContext end end end diff --git a/app/services/users/update_service.rb b/app/services/users/update_service.rb index e7667b0ca18..57209043e3b 100644 --- a/app/services/users/update_service.rb +++ b/app/services/users/update_service.rb @@ -53,7 +53,11 @@ module Users end def discard_read_only_attributes - discard_synced_attributes + if Feature.enabled?(:ldap_readonly_attributes, default_enabled: true) + params.reject! { |key, _| @user.read_only_attribute?(key.to_sym) } + else + discard_synced_attributes + end end def discard_synced_attributes diff --git a/app/workers/admin_email_worker.rb b/app/workers/admin_email_worker.rb index be05d2a6752..074b59414ab 100644 --- a/app/workers/admin_email_worker.rb +++ b/app/workers/admin_email_worker.rb @@ -2,7 +2,7 @@ class AdminEmailWorker include ApplicationWorker - include CronjobQueue + include CronjobQueue # rubocop:disable Scalability/CronWorkerContext feature_category_not_owned! diff --git a/app/workers/ci/archive_traces_cron_worker.rb b/app/workers/ci/archive_traces_cron_worker.rb index 74f389175b9..c73c7ba2dd8 100644 --- a/app/workers/ci/archive_traces_cron_worker.rb +++ b/app/workers/ci/archive_traces_cron_worker.rb @@ -3,7 +3,7 @@ module Ci class ArchiveTracesCronWorker include ApplicationWorker - include CronjobQueue + include CronjobQueue # rubocop:disable Scalability/CronWorkerContext feature_category :continuous_integration diff --git a/app/workers/container_expiration_policy_worker.rb b/app/workers/container_expiration_policy_worker.rb index 595208230f6..a930dfa0186 100644 --- a/app/workers/container_expiration_policy_worker.rb +++ b/app/workers/container_expiration_policy_worker.rb @@ -2,7 +2,7 @@ class ContainerExpirationPolicyWorker include ApplicationWorker - include CronjobQueue + include CronjobQueue # rubocop:disable Scalability/CronWorkerContext feature_category :container_registry diff --git a/app/workers/expire_build_artifacts_worker.rb b/app/workers/expire_build_artifacts_worker.rb index 383fd30e098..ee57bd6dfe2 100644 --- a/app/workers/expire_build_artifacts_worker.rb +++ b/app/workers/expire_build_artifacts_worker.rb @@ -2,7 +2,7 @@ class ExpireBuildArtifactsWorker include ApplicationWorker - include CronjobQueue + include CronjobQueue # rubocop:disable Scalability/CronWorkerContext feature_category :continuous_integration diff --git a/app/workers/gitlab_usage_ping_worker.rb b/app/workers/gitlab_usage_ping_worker.rb index ad8302a844a..577293c2d8d 100644 --- a/app/workers/gitlab_usage_ping_worker.rb +++ b/app/workers/gitlab_usage_ping_worker.rb @@ -4,7 +4,7 @@ class GitlabUsagePingWorker LEASE_TIMEOUT = 86400 include ApplicationWorker - include CronjobQueue + include CronjobQueue # rubocop:disable Scalability/CronWorkerContext feature_category_not_owned! diff --git a/app/workers/import_export_project_cleanup_worker.rb b/app/workers/import_export_project_cleanup_worker.rb index 07c29d40b54..8a018024e95 100644 --- a/app/workers/import_export_project_cleanup_worker.rb +++ b/app/workers/import_export_project_cleanup_worker.rb @@ -2,7 +2,7 @@ class ImportExportProjectCleanupWorker include ApplicationWorker - include CronjobQueue + include CronjobQueue # rubocop:disable Scalability/CronWorkerContext feature_category :importers diff --git a/app/workers/issue_due_scheduler_worker.rb b/app/workers/issue_due_scheduler_worker.rb index d4d47659ef0..59027907284 100644 --- a/app/workers/issue_due_scheduler_worker.rb +++ b/app/workers/issue_due_scheduler_worker.rb @@ -2,7 +2,7 @@ class IssueDueSchedulerWorker include ApplicationWorker - include CronjobQueue + include CronjobQueue # rubocop:disable Scalability/CronWorkerContext feature_category :issue_tracking @@ -10,7 +10,7 @@ class IssueDueSchedulerWorker def perform project_ids = Issue.opened.due_tomorrow.group(:project_id).pluck(:project_id).map { |id| [id] } - MailScheduler::IssueDueWorker.bulk_perform_async(project_ids) + MailScheduler::IssueDueWorker.bulk_perform_async(project_ids) # rubocop:disable Scalability/BulkPerformWithContext end # rubocop: enable CodeReuse/ActiveRecord end diff --git a/app/workers/namespaces/prune_aggregation_schedules_worker.rb b/app/workers/namespaces/prune_aggregation_schedules_worker.rb index 9a5f533fe9a..aeb5aa37a10 100644 --- a/app/workers/namespaces/prune_aggregation_schedules_worker.rb +++ b/app/workers/namespaces/prune_aggregation_schedules_worker.rb @@ -3,7 +3,7 @@ module Namespaces class PruneAggregationSchedulesWorker include ApplicationWorker - include CronjobQueue + include CronjobQueue # rubocop:disable Scalability/CronWorkerContext feature_category :source_code_management worker_resource_boundary :cpu diff --git a/app/workers/pages_domain_removal_cron_worker.rb b/app/workers/pages_domain_removal_cron_worker.rb index 07ecde55922..b14d0d4597c 100644 --- a/app/workers/pages_domain_removal_cron_worker.rb +++ b/app/workers/pages_domain_removal_cron_worker.rb @@ -2,7 +2,7 @@ class PagesDomainRemovalCronWorker include ApplicationWorker - include CronjobQueue + include CronjobQueue # rubocop:disable Scalability/CronWorkerContext feature_category :pages worker_resource_boundary :cpu diff --git a/app/workers/pages_domain_ssl_renewal_cron_worker.rb b/app/workers/pages_domain_ssl_renewal_cron_worker.rb index f7a243e9b3b..b20ed23dc89 100644 --- a/app/workers/pages_domain_ssl_renewal_cron_worker.rb +++ b/app/workers/pages_domain_ssl_renewal_cron_worker.rb @@ -2,7 +2,7 @@ class PagesDomainSslRenewalCronWorker include ApplicationWorker - include CronjobQueue + include CronjobQueue # rubocop:disable Scalability/CronWorkerContext feature_category :pages diff --git a/app/workers/pages_domain_verification_cron_worker.rb b/app/workers/pages_domain_verification_cron_worker.rb index bb3a7fede9a..8bd7fe33334 100644 --- a/app/workers/pages_domain_verification_cron_worker.rb +++ b/app/workers/pages_domain_verification_cron_worker.rb @@ -2,7 +2,7 @@ class PagesDomainVerificationCronWorker include ApplicationWorker - include CronjobQueue + include CronjobQueue # rubocop:disable Scalability/CronWorkerContext feature_category :pages diff --git a/app/workers/personal_access_tokens/expiring_worker.rb b/app/workers/personal_access_tokens/expiring_worker.rb index f28109c4583..b291e9ef279 100644 --- a/app/workers/personal_access_tokens/expiring_worker.rb +++ b/app/workers/personal_access_tokens/expiring_worker.rb @@ -3,7 +3,7 @@ module PersonalAccessTokens class ExpiringWorker include ApplicationWorker - include CronjobQueue + include CronjobQueue # rubocop:disable Scalability/CronWorkerContext feature_category :authentication_and_authorization diff --git a/app/workers/pipeline_schedule_worker.rb b/app/workers/pipeline_schedule_worker.rb index 19c3c5fcc2f..841308611eb 100644 --- a/app/workers/pipeline_schedule_worker.rb +++ b/app/workers/pipeline_schedule_worker.rb @@ -2,7 +2,7 @@ class PipelineScheduleWorker include ApplicationWorker - include CronjobQueue + include CronjobQueue # rubocop:disable Scalability/CronWorkerContext feature_category :continuous_integration worker_resource_boundary :cpu diff --git a/app/workers/prune_old_events_worker.rb b/app/workers/prune_old_events_worker.rb index 1d915832833..1a54b85a384 100644 --- a/app/workers/prune_old_events_worker.rb +++ b/app/workers/prune_old_events_worker.rb @@ -2,7 +2,7 @@ class PruneOldEventsWorker include ApplicationWorker - include CronjobQueue + include CronjobQueue # rubocop:disable Scalability/CronWorkerContext feature_category_not_owned! diff --git a/app/workers/prune_web_hook_logs_worker.rb b/app/workers/prune_web_hook_logs_worker.rb index 69a1dd43e69..7d128a6f1ac 100644 --- a/app/workers/prune_web_hook_logs_worker.rb +++ b/app/workers/prune_web_hook_logs_worker.rb @@ -4,7 +4,7 @@ # table. class PruneWebHookLogsWorker include ApplicationWorker - include CronjobQueue + include CronjobQueue # rubocop:disable Scalability/CronWorkerContext feature_category :integrations diff --git a/app/workers/remove_expired_group_links_worker.rb b/app/workers/remove_expired_group_links_worker.rb index a43e6fd11d5..db35dfb3ca8 100644 --- a/app/workers/remove_expired_group_links_worker.rb +++ b/app/workers/remove_expired_group_links_worker.rb @@ -2,7 +2,7 @@ class RemoveExpiredGroupLinksWorker include ApplicationWorker - include CronjobQueue + include CronjobQueue # rubocop:disable Scalability/CronWorkerContext feature_category :authentication_and_authorization diff --git a/app/workers/remove_expired_members_worker.rb b/app/workers/remove_expired_members_worker.rb index bf209fcec9f..278adee98e9 100644 --- a/app/workers/remove_expired_members_worker.rb +++ b/app/workers/remove_expired_members_worker.rb @@ -2,7 +2,7 @@ class RemoveExpiredMembersWorker include ApplicationWorker - include CronjobQueue + include CronjobQueue # rubocop:disable Scalability/CronWorkerContext feature_category :authentication_and_authorization worker_resource_boundary :cpu diff --git a/app/workers/remove_unreferenced_lfs_objects_worker.rb b/app/workers/remove_unreferenced_lfs_objects_worker.rb index 7f2c23f4685..486f8f12014 100644 --- a/app/workers/remove_unreferenced_lfs_objects_worker.rb +++ b/app/workers/remove_unreferenced_lfs_objects_worker.rb @@ -2,7 +2,7 @@ class RemoveUnreferencedLfsObjectsWorker include ApplicationWorker - include CronjobQueue + include CronjobQueue # rubocop:disable Scalability/CronWorkerContext feature_category :source_code_management diff --git a/app/workers/repository_archive_cache_worker.rb b/app/workers/repository_archive_cache_worker.rb index ebc83c1b17a..bbeb9d9eace 100644 --- a/app/workers/repository_archive_cache_worker.rb +++ b/app/workers/repository_archive_cache_worker.rb @@ -2,7 +2,7 @@ class RepositoryArchiveCacheWorker include ApplicationWorker - include CronjobQueue + include CronjobQueue # rubocop:disable Scalability/CronWorkerContext feature_category :source_code_management diff --git a/app/workers/repository_check/dispatch_worker.rb b/app/workers/repository_check/dispatch_worker.rb index d2bd5f9b967..ca1a82ed160 100644 --- a/app/workers/repository_check/dispatch_worker.rb +++ b/app/workers/repository_check/dispatch_worker.rb @@ -3,7 +3,7 @@ module RepositoryCheck class DispatchWorker include ApplicationWorker - include CronjobQueue + include CronjobQueue # rubocop:disable Scalability/CronWorkerContext include ::EachShardWorker include ExclusiveLeaseGuard diff --git a/app/workers/requests_profiles_worker.rb b/app/workers/requests_profiles_worker.rb index 6ab020afb10..593451eb16b 100644 --- a/app/workers/requests_profiles_worker.rb +++ b/app/workers/requests_profiles_worker.rb @@ -2,7 +2,7 @@ class RequestsProfilesWorker include ApplicationWorker - include CronjobQueue + include CronjobQueue # rubocop:disable Scalability/CronWorkerContext feature_category :source_code_management diff --git a/app/workers/schedule_migrate_external_diffs_worker.rb b/app/workers/schedule_migrate_external_diffs_worker.rb index 8abb5922b54..4c6f19cbc8f 100644 --- a/app/workers/schedule_migrate_external_diffs_worker.rb +++ b/app/workers/schedule_migrate_external_diffs_worker.rb @@ -2,7 +2,7 @@ class ScheduleMigrateExternalDiffsWorker include ApplicationWorker - include CronjobQueue + include CronjobQueue # rubocop:disable Scalability/CronWorkerContext include Gitlab::ExclusiveLeaseHelpers feature_category :source_code_management diff --git a/app/workers/stuck_ci_jobs_worker.rb b/app/workers/stuck_ci_jobs_worker.rb index d08cea9e494..7e3f7275b55 100644 --- a/app/workers/stuck_ci_jobs_worker.rb +++ b/app/workers/stuck_ci_jobs_worker.rb @@ -2,7 +2,7 @@ class StuckCiJobsWorker include ApplicationWorker - include CronjobQueue + include CronjobQueue # rubocop:disable Scalability/CronWorkerContext feature_category :continuous_integration worker_resource_boundary :cpu diff --git a/app/workers/stuck_import_jobs_worker.rb b/app/workers/stuck_import_jobs_worker.rb index d9a9a613ca9..4a72fcedee3 100644 --- a/app/workers/stuck_import_jobs_worker.rb +++ b/app/workers/stuck_import_jobs_worker.rb @@ -2,7 +2,7 @@ class StuckImportJobsWorker include ApplicationWorker - include CronjobQueue + include CronjobQueue # rubocop:disable Scalability/CronWorkerContext feature_category :importers worker_resource_boundary :cpu diff --git a/app/workers/stuck_merge_jobs_worker.rb b/app/workers/stuck_merge_jobs_worker.rb index 024863ab530..9214ae038a8 100644 --- a/app/workers/stuck_merge_jobs_worker.rb +++ b/app/workers/stuck_merge_jobs_worker.rb @@ -2,7 +2,7 @@ class StuckMergeJobsWorker include ApplicationWorker - include CronjobQueue + include CronjobQueue # rubocop:disable Scalability/CronWorkerContext feature_category :source_code_management diff --git a/app/workers/trending_projects_worker.rb b/app/workers/trending_projects_worker.rb index 4c8ee1ee425..5db661dd0e6 100644 --- a/app/workers/trending_projects_worker.rb +++ b/app/workers/trending_projects_worker.rb @@ -2,7 +2,7 @@ class TrendingProjectsWorker include ApplicationWorker - include CronjobQueue + include CronjobQueue # rubocop:disable Scalability/CronWorkerContext feature_category :source_code_management diff --git a/changelogs/unreleased/14624-vendored-template-for-license-scanning.yml b/changelogs/unreleased/14624-vendored-template-for-license-scanning.yml new file mode 100644 index 00000000000..577c3439d73 --- /dev/null +++ b/changelogs/unreleased/14624-vendored-template-for-license-scanning.yml @@ -0,0 +1,5 @@ +--- +title: Introduce license_scanning CI template +merge_request: 22773 +author: +type: added diff --git a/changelogs/unreleased/197371-show-notification-on-related-error-close.yml b/changelogs/unreleased/197371-show-notification-on-related-error-close.yml new file mode 100644 index 00000000000..8cc4ddbbc6b --- /dev/null +++ b/changelogs/unreleased/197371-show-notification-on-related-error-close.yml @@ -0,0 +1,5 @@ +--- +title: Close related GitLab issue on Sentry error resolve +merge_request: 23610 +author: +type: added diff --git a/changelogs/unreleased/197926-initial-package-detail-title.yml b/changelogs/unreleased/197926-initial-package-detail-title.yml new file mode 100644 index 00000000000..c0420d2860d --- /dev/null +++ b/changelogs/unreleased/197926-initial-package-detail-title.yml @@ -0,0 +1,5 @@ +--- +title: Updated package details page header to begin updating the page design. +merge_request: 24055 +author: +type: added diff --git a/changelogs/unreleased/34262-readonly-ldap-attributes.yml b/changelogs/unreleased/34262-readonly-ldap-attributes.yml new file mode 100644 index 00000000000..7a3f3cd460e --- /dev/null +++ b/changelogs/unreleased/34262-readonly-ldap-attributes.yml @@ -0,0 +1,5 @@ +--- +title: Make name, email, and location attributes readonly for LDAP enabled instances +merge_request: 24049 +author: +type: changed diff --git a/db/migrate/20200203025400_default_lock_version_to_zero_for_merge_requests.rb b/db/migrate/20200203025400_default_lock_version_to_zero_for_merge_requests.rb new file mode 100644 index 00000000000..c0c58cfa3a7 --- /dev/null +++ b/db/migrate/20200203025400_default_lock_version_to_zero_for_merge_requests.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class DefaultLockVersionToZeroForMergeRequests < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + def change + with_lock_retries do + change_column_default :merge_requests, :lock_version, from: nil, to: 0 + end + end +end diff --git a/db/migrate/20200203025602_default_lock_version_to_zero_for_issues.rb b/db/migrate/20200203025602_default_lock_version_to_zero_for_issues.rb new file mode 100644 index 00000000000..72c265fdf38 --- /dev/null +++ b/db/migrate/20200203025602_default_lock_version_to_zero_for_issues.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class DefaultLockVersionToZeroForIssues < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + def change + with_lock_retries do + change_column_default :issues, :lock_version, from: nil, to: 0 + end + end +end diff --git a/db/migrate/20200203025619_default_lock_version_to_zero_for_epics.rb b/db/migrate/20200203025619_default_lock_version_to_zero_for_epics.rb new file mode 100644 index 00000000000..f44cfd07ee2 --- /dev/null +++ b/db/migrate/20200203025619_default_lock_version_to_zero_for_epics.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class DefaultLockVersionToZeroForEpics < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + def change + with_lock_retries do + change_column_default :epics, :lock_version, from: nil, to: 0 + end + end +end diff --git a/db/migrate/20200203025744_default_lock_version_to_zero_for_ci_builds.rb b/db/migrate/20200203025744_default_lock_version_to_zero_for_ci_builds.rb new file mode 100644 index 00000000000..feda8c36947 --- /dev/null +++ b/db/migrate/20200203025744_default_lock_version_to_zero_for_ci_builds.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class DefaultLockVersionToZeroForCiBuilds < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + def change + with_lock_retries do + change_column_default :ci_builds, :lock_version, from: nil, to: 0 + end + end +end diff --git a/db/migrate/20200203025801_default_lock_version_to_zero_for_ci_stages.rb b/db/migrate/20200203025801_default_lock_version_to_zero_for_ci_stages.rb new file mode 100644 index 00000000000..b825c4ff1c4 --- /dev/null +++ b/db/migrate/20200203025801_default_lock_version_to_zero_for_ci_stages.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class DefaultLockVersionToZeroForCiStages < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + def change + with_lock_retries do + change_column_default :ci_stages, :lock_version, from: nil, to: 0 + end + end +end diff --git a/db/migrate/20200203025821_default_lock_version_to_zero_for_ci_pipelines.rb b/db/migrate/20200203025821_default_lock_version_to_zero_for_ci_pipelines.rb new file mode 100644 index 00000000000..6c4c84cb7e7 --- /dev/null +++ b/db/migrate/20200203025821_default_lock_version_to_zero_for_ci_pipelines.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class DefaultLockVersionToZeroForCiPipelines < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + def change + with_lock_retries do + change_column_default :ci_pipelines, :lock_version, from: nil, to: 0 + end + end +end diff --git a/db/schema.rb b/db/schema.rb index e7a1ace68f4..f8be58fe30c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_01_30_161817) do +ActiveRecord::Schema.define(version: 2020_02_03_025821) do # These are extensions that must be enabled in order to support this database enable_extension "pg_trgm" @@ -659,7 +659,7 @@ ActiveRecord::Schema.define(version: 2020_01_30_161817) do t.text "yaml_variables" t.datetime "queued_at" t.string "token" - t.integer "lock_version" + t.integer "lock_version", default: 0 t.string "coverage_regex" t.integer "auto_canceled_by_id" t.boolean "retried" @@ -835,7 +835,7 @@ ActiveRecord::Schema.define(version: 2020_01_30_161817) do t.datetime "finished_at" t.integer "duration" t.integer "user_id" - t.integer "lock_version" + t.integer "lock_version", default: 0 t.integer "auto_canceled_by_id" t.integer "pipeline_schedule_id" t.integer "source" @@ -949,7 +949,7 @@ ActiveRecord::Schema.define(version: 2020_01_30_161817) do t.datetime "updated_at" t.string "name" t.integer "status" - t.integer "lock_version" + t.integer "lock_version", default: 0 t.integer "position" t.index ["pipeline_id", "name"], name: "index_ci_stages_on_pipeline_id_and_name", unique: true t.index ["pipeline_id", "position"], name: "index_ci_stages_on_pipeline_id_and_position" @@ -1523,7 +1523,7 @@ ActiveRecord::Schema.define(version: 2020_01_30_161817) do t.integer "cached_markdown_version" t.integer "updated_by_id" t.integer "last_edited_by_id" - t.integer "lock_version" + t.integer "lock_version", default: 0 t.date "start_date" t.date "end_date" t.datetime "last_edited_at" @@ -2140,7 +2140,7 @@ ActiveRecord::Schema.define(version: 2020_01_30_161817) do t.boolean "confidential", default: false, null: false t.date "due_date" t.integer "moved_to_id" - t.integer "lock_version" + t.integer "lock_version", default: 0 t.text "title_html" t.text "description_html" t.integer "time_estimate" @@ -2562,7 +2562,7 @@ ActiveRecord::Schema.define(version: 2020_01_30_161817) do t.integer "approvals_before_merge" t.string "rebase_commit_sha" t.string "in_progress_merge_commit_sha" - t.integer "lock_version" + t.integer "lock_version", default: 0 t.text "title_html" t.text "description_html" t.integer "time_estimate" diff --git a/doc/development/sidekiq_style_guide.md b/doc/development/sidekiq_style_guide.md index 062a3e13c39..dd6a5232295 100644 --- a/doc/development/sidekiq_style_guide.md +++ b/doc/development/sidekiq_style_guide.md @@ -276,6 +276,125 @@ class SomeCrossCuttingConcernWorker end ``` +## Worker context + +To have some more information about workers in the logs, we add +[metadata to the jobs in the form of an +`ApplicationContext`](logging.md#logging-context-metadata-through-rails-or-grape-requests). +In most cases, when scheduling a job from a request, this context will +already be deducted from the request and added to the scheduled +job. + +When a job runs, the context that was active when it was scheduled +will be restored. This causes the context to be propagated to any job +scheduled from within the running job. + +All this means that in most cases, to add context to jobs, we don't +need to do anything. + +There are however some instances when there would be no context +present when the job is scheduled, or the context that is present is +likely to be incorrect. For these instances we've added rubocop-rules +to draw attention and avoid incorrect metadata in our logs. + +As with most our cops, there are perfectly valid reasons for disabling +them. In this case it could be that the context from the request is +correct. Or maybe you've specified a context already in a way that +isn't picked up by the cops. In any case, please leave a code-comment +pointing to which context will be used when disabling the cops. + +When you do provide objects to the context, please make sure that the +route for namespaces and projects is preloaded. This can be done using +the `.with_route` scope defined on all `Routable`s. + +### Cron-Workers + +The context is automatically cleared for workers in the cronjob-queue +(which `include CronjobQueue`), even when scheduling them from +requests. We do this to avoid incorrect metadata when other jobs are +scheduled from the cron-worker. + +Cron-Workers themselves run instance wide, so they aren't scoped to +users, namespaces, projects or other resources that should be added to +the context. + +However, they often schedule other jobs that _do_ require context. + +That is why there needs to be an indication of context somewhere in +the worker. This can be done by using one of the following methods +somewhere within the worker: + +1. Wrap the code that schedules jobs in the `with_context` helper: + +```ruby + def perform + deletion_cutoff = Gitlab::CurrentSettings + .deletion_adjourned_period.days.ago.to_date + projects = Project.with_route.with_namespace + .aimed_for_deletion(deletion_cutoff) + + projects.find_each(batch_size: 100).with_index do |project, index| + delay = index * INTERVAL + + with_context(project: project) do + AdjournedProjectDeletionWorker.perform_in(delay, project.id) + end + end + end +``` + +1. Use the a batch scheduling method that provides context: + +```ruby + def schedule_projects_in_batch(projects) + ProjectImportScheduleWorker.bulk_perform_async_with_contexts( + projects, + arguments_proc: -> (project) { project.id }, + context_proc: -> (project) { { project: project } } + ) + end +``` + +or when scheduling with delays: + +```ruby + diffs.each_batch(of: BATCH_SIZE) do |diffs, index| + DeleteDiffFilesWorker + .bulk_perform_in_with_contexts(index * 5.minutes, + diffs, + arguments_proc: -> (diff) { diff.id }, + context_proc: -> (diff) { { project: diff.merge_request.target_project } }) + end +``` + +### Jobs scheduled in bulk + +Often, when scheduling jobs in bulk, these jobs should have a separate +context rather than the overarching context. + +If that is the case, `bulk_perform_async` can be replaced by the +`bulk_perform_async_with_context` helper, and instead of +`bulk_perform_in` use `bulk_perform_in_with_context`. + +For example: + +```ruby + ProjectImportScheduleWorker.bulk_perform_async_with_contexts( + projects, + arguments_proc: -> (project) { project.id }, + context_proc: -> (project) { { project: project } } + ) +``` + +Each object from the enumerable in the first argument is yielded into 2 +blocks: + +The `arguments_proc` which needs to return the list of arguments the +job needs to be scheduled with. + +The `context_proc` which needs to return a hash with the context +information for the job. + ## Tests Each Sidekiq worker must be tested using RSpec, just like any other class. These diff --git a/lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml index f10a445f7c9..58fd018a82d 100644 --- a/lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml @@ -1,8 +1,5 @@ -# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/license_management/ -# -# Configure the scanning tool through the environment variables. -# List of the variables: https://gitlab.com/gitlab-org/security-products/license-management#settings -# How to set: https://docs.gitlab.com/ee/ci/yaml/#variables +# Deprecated: https://gitlab.com/gitlab-org/gitlab/issues/14624 +# Please, use License-Scanning.gitlab-ci.yml template instead variables: LICENSE_MANAGEMENT_SETUP_CMD: '' # If needed, specify a command to setup your environment with a custom package manager. @@ -16,6 +13,7 @@ license_management: SETUP_CMD: $LICENSE_MANAGEMENT_SETUP_CMD allow_failure: true script: + - echo "This template is deprecated, please use License-Scanning.gitlab-ci.yml template instead." - /run.sh analyze . artifacts: reports: diff --git a/lib/gitlab/ci/templates/Security/License-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/License-Scanning.gitlab-ci.yml new file mode 100644 index 00000000000..5c790f3e0ab --- /dev/null +++ b/lib/gitlab/ci/templates/Security/License-Scanning.gitlab-ci.yml @@ -0,0 +1,33 @@ +# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/license_compliance/ +# +# Configure the scanning tool through the environment variables. +# List of the variables: https://gitlab.com/gitlab-org/security-products/license-management#settings +# How to set: https://docs.gitlab.com/ee/ci/yaml/#variables + +variables: + LICENSE_MANAGEMENT_SETUP_CMD: '' # If needed, specify a command to setup your environment with a custom package manager. + +license_scanning: + stage: test + image: + name: "registry.gitlab.com/gitlab-org/security-products/license-management:$CI_SERVER_VERSION_MAJOR-$CI_SERVER_VERSION_MINOR-stable" + entrypoint: [""] + variables: + SETUP_CMD: $LICENSE_MANAGEMENT_SETUP_CMD + allow_failure: true + script: + - /run.sh analyze . + after_script: + - mv gl-license-management-report.json gl-license-scanning-report.json + artifacts: + reports: + license_scanning: gl-license-scanning-report.json + dependencies: [] + only: + refs: + - branches + variables: + - $GITLAB_FEATURES =~ /\blicense_management\b/ + except: + variables: + - $LICENSE_MANAGEMENT_DISABLED diff --git a/lib/gitlab/diff/formatters/image_formatter.rb b/lib/gitlab/diff/formatters/image_formatter.rb index 5bc9f0c337f..90cd74c84bb 100644 --- a/lib/gitlab/diff/formatters/image_formatter.rb +++ b/lib/gitlab/diff/formatters/image_formatter.rb @@ -37,7 +37,9 @@ module Gitlab def ==(other) other.is_a?(self.class) && x == other.x && - y == other.y + y == other.y && + width == other.width && + height == other.height end end end diff --git a/lib/gitlab/file_hook.rb b/lib/gitlab/file_hook.rb index f886fd10f53..38c19ff506f 100644 --- a/lib/gitlab/file_hook.rb +++ b/lib/gitlab/file_hook.rb @@ -17,7 +17,7 @@ module Gitlab def self.execute_all_async(data) args = files.map { |file| [file, data] } - FileHookWorker.bulk_perform_async(args) + FileHookWorker.bulk_perform_async(args) # rubocop:disable Scalability/BulkPerformWithContext end def self.execute(file, data) diff --git a/lib/gitlab/gpg/commit.rb b/lib/gitlab/gpg/commit.rb index 48b327a54e1..800e94e51e8 100644 --- a/lib/gitlab/gpg/commit.rb +++ b/lib/gitlab/gpg/commit.rb @@ -8,7 +8,7 @@ module Gitlab def initialize(commit) @commit = commit - repo = commit.project.repository.raw_repository + repo = commit.container.repository.raw_repository @signature_data = Gitlab::Git::Commit.extract_signature_lazily(repo, commit.sha || commit.id) lazy_signature diff --git a/locale/gitlab.pot b/locale/gitlab.pot index c91bae519e5..67362b2efcc 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -340,6 +340,9 @@ msgstr "" msgid "%{name}'s avatar" msgstr "" +msgid "%{numberOfDays} days" +msgstr "" + msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead" msgstr "" @@ -537,6 +540,9 @@ msgstr "" msgid "+%{extraOptionCount} more" msgstr "" +msgid "+%{tags} more" +msgstr "" + msgid ", or " msgstr "" @@ -2146,6 +2152,9 @@ msgstr "" msgid "Are you sure you want to cancel editing this comment?" msgstr "" +msgid "Are you sure you want to delete %{name}?" +msgstr "" + msgid "Are you sure you want to delete these artifacts?" msgstr "" @@ -2411,6 +2420,9 @@ msgstr "" msgid "Author" msgstr "" +msgid "Authored %{timeago} by %{author}" +msgstr "" + msgid "Authorization code:" msgstr "" @@ -5903,6 +5915,9 @@ msgstr "" msgid "CycleAnalytics|Tasks by type" msgstr "" +msgid "CycleAnalytics|The given date range is larger than 180 days" +msgstr "" + msgid "CycleAnalytics|Total days to completion" msgstr "" @@ -6918,6 +6933,9 @@ msgstr "" msgid "Edit your most recent comment in a thread (from an empty textarea)" msgstr "" +msgid "Edited %{timeago}" +msgstr "" + msgid "Editing" msgstr "" @@ -13266,6 +13284,18 @@ msgstr "" msgid "PackageRegistry|yarn" msgstr "" +msgid "PackageType|Conan" +msgstr "" + +msgid "PackageType|Maven" +msgstr "" + +msgid "PackageType|NPM" +msgstr "" + +msgid "PackageType|NuGet" +msgstr "" + msgid "Packages" msgstr "" @@ -18669,6 +18699,9 @@ msgstr "" msgid "The application will be used where the client secret can be kept confidential. Native mobile apps and Single Page Apps are considered non-confidential." msgstr "" +msgid "The associated issue #%{issueId} has been closed as the error is now resolved." +msgstr "" + msgid "The branch for this project has no active pipeline configuration." msgstr "" @@ -22155,6 +22188,9 @@ msgstr "" msgid "by" msgstr "" +msgid "by %{user}" +msgstr "" + msgid "cannot be changed if a personal project has container registry tags." msgstr "" diff --git a/package.json b/package.json index 2f91aa1e605..e8f4b24d518 100644 --- a/package.json +++ b/package.json @@ -39,8 +39,8 @@ "@babel/plugin-syntax-dynamic-import": "^7.2.0", "@babel/plugin-syntax-import-meta": "^7.2.0", "@babel/preset-env": "^7.6.2", - "@gitlab/svgs": "^1.91.0", - "@gitlab/ui": "^9.0.0", + "@gitlab/svgs": "^1.94.0", + "@gitlab/ui": "^9.3.0", "@gitlab/visual-review-tools": "1.5.1", "@sentry/browser": "^5.10.2", "@sourcegraph/code-host-integration": "0.0.21", diff --git a/rubocop/cop/scalability/bulk_perform_with_context.rb b/rubocop/cop/scalability/bulk_perform_with_context.rb new file mode 100644 index 00000000000..3c5d7f39680 --- /dev/null +++ b/rubocop/cop/scalability/bulk_perform_with_context.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require_relative '../../migration_helpers' +require_relative '../../code_reuse_helpers' + +module RuboCop + module Cop + module Scalability + class BulkPerformWithContext < RuboCop::Cop::Cop + include RuboCop::MigrationHelpers + include RuboCop::CodeReuseHelpers + + MSG = <<~MSG + Prefer using `Worker.bulk_perform_async_with_contexts` and + `Worker.bulk_perform_in_with_context` over the methods without a context + if your worker deals with specific projects or namespaces + The context is required to add metadata to our logs. + + If there is already a parent context that will apply to the jobs + being scheduled, please disable this cop with a comment explaing which + context will be applied. + + Read more about it https://docs.gitlab.com/ee/development/sidekiq_style_guide.html#worker-context + MSG + + def_node_matcher :schedules_in_batch_without_context?, <<~PATTERN + (send (...) {:bulk_perform_async :bulk_perform_in} _*) + PATTERN + + def on_send(node) + return if in_migration?(node) || in_spec?(node) + return unless schedules_in_batch_without_context?(node) + return if name_of_receiver(node) == "BackgroundMigrationWorker" + + add_offense(node, location: :expression) + end + + private + + def in_spec?(node) + file_path_for_node(node).end_with?("_spec.rb") + end + end + end + end +end diff --git a/rubocop/cop/scalability/cron_worker_context.rb b/rubocop/cop/scalability/cron_worker_context.rb new file mode 100644 index 00000000000..d8eba0f098e --- /dev/null +++ b/rubocop/cop/scalability/cron_worker_context.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Scalability + class CronWorkerContext < RuboCop::Cop::Cop + MSG = <<~MSG + Manually define an ApplicationContext for cronjob-workers. The context + is required to add metadata to our logs. + + If there is no relevant metadata, please disable the cop with a comment + explaining this. + + Read more about it https://docs.gitlab.com/ee/development/sidekiq_style_guide.html#worker-context + MSG + + def_node_matcher :includes_cronjob_queue?, <<~PATTERN + (send nil? :include (const nil? :CronjobQueue)) + PATTERN + + def_node_search :defines_contexts?, <<~PATTERN + (send nil? :with_context _) + PATTERN + + def_node_search :schedules_with_batch_context?, <<~PATTERN + (send (...) {:bulk_perform_async_with_contexts :bulk_perform_in_with_contexts} (...)) + PATTERN + + def on_send(node) + return unless includes_cronjob_queue?(node) + return if defines_contexts?(node.parent) + return if schedules_with_batch_context?(node.parent) + + add_offense(node.arguments.first, location: :expression) + end + end + end + end +end diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb index d39683c271f..5284cef5346 100644 --- a/rubocop/rubocop.rb +++ b/rubocop/rubocop.rb @@ -44,6 +44,8 @@ require_relative 'cop/qa/element_with_pattern' require_relative 'cop/qa/ambiguous_page_object_name' require_relative 'cop/sidekiq_options_queue' require_relative 'cop/scalability/file_uploads' +require_relative 'cop/scalability/bulk_perform_with_context' +require_relative 'cop/scalability/cron_worker_context' require_relative 'cop/destroy_all' require_relative 'cop/ruby_interpolation_in_translation' require_relative 'code_reuse_helpers' diff --git a/spec/frontend/diffs/components/diff_stats_spec.js b/spec/frontend/diffs/components/diff_stats_spec.js index 9073462a51f..0dffc74f608 100644 --- a/spec/frontend/diffs/components/diff_stats_spec.js +++ b/spec/frontend/diffs/components/diff_stats_spec.js @@ -3,6 +3,18 @@ import Icon from '~/vue_shared/components/icon.vue'; import DiffStats from '~/diffs/components/diff_stats.vue'; describe('diff_stats', () => { + it('does not render a group if diffFileLengths is empty', () => { + const wrapper = shallowMount(DiffStats, { + propsData: { + addedLines: 1, + removedLines: 2, + }, + }); + const groups = wrapper.findAll('.diff-stats-group'); + + expect(groups.length).toBe(2); + }); + it('does not render a group if diffFileLengths is not a number', () => { const wrapper = shallowMount(DiffStats, { propsData: { diff --git a/spec/frontend/error_tracking/components/error_details_spec.js b/spec/frontend/error_tracking/components/error_details_spec.js index 1e1d20800da..cfb9ce51979 100644 --- a/spec/frontend/error_tracking/components/error_details_spec.js +++ b/spec/frontend/error_tracking/components/error_details_spec.js @@ -1,7 +1,7 @@ import { createLocalVue, shallowMount } from '@vue/test-utils'; import Vuex from 'vuex'; import { __ } from '~/locale'; -import { GlLoadingIcon, GlLink, GlBadge, GlFormInput } from '@gitlab/ui'; +import { GlLoadingIcon, GlLink, GlBadge, GlFormInput, GlAlert, GlSprintf } from '@gitlab/ui'; import LoadingButton from '~/vue_shared/components/loading_button.vue'; import Stacktrace from '~/error_tracking/components/stacktrace.vue'; import ErrorDetails from '~/error_tracking/components/error_details.vue'; @@ -28,7 +28,7 @@ describe('ErrorDetails', () => { function mountComponent() { wrapper = shallowMount(ErrorDetails, { - stubs: { LoadingButton }, + stubs: { LoadingButton, GlSprintf }, localVue, store, mocks, @@ -62,7 +62,7 @@ describe('ErrorDetails', () => { startPollingDetails: () => {}, startPollingStacktrace: () => {}, updateIgnoreStatus: jest.fn(), - updateResolveStatus: jest.fn(), + updateResolveStatus: jest.fn().mockResolvedValue({ closed_issue_iid: 1 }), }; getters = { @@ -313,6 +313,20 @@ describe('ErrorDetails', () => { expect.objectContaining({ status: errorStatus.UNRESOLVED }), ); }); + + it('should show alert with closed issueId', () => { + const findAlert = () => wrapper.find(GlAlert); + const closedIssueId = 123; + wrapper.setData({ + isAlertVisible: true, + closedIssueId, + }); + + return wrapper.vm.$nextTick().then(() => { + expect(findAlert().exists()).toBe(true); + expect(findAlert().text()).toContain(`#${closedIssueId}`); + }); + }); }); }); diff --git a/spec/frontend/pages/admin/users/components/__snapshots__/user_operation_confirmation_modal_spec.js.snap b/spec/frontend/pages/admin/users/components/__snapshots__/user_operation_confirmation_modal_spec.js.snap index 4b4e9997953..dbf8caae357 100644 --- a/spec/frontend/pages/admin/users/components/__snapshots__/user_operation_confirmation_modal_spec.js.snap +++ b/spec/frontend/pages/admin/users/components/__snapshots__/user_operation_confirmation_modal_spec.js.snap @@ -6,6 +6,7 @@ exports[`User Operation confirmation modal renders modal with form included 1`] modalid="user-operation-modal" ok-title="action" ok-variant="warning" + size="md" title="title" titletag="h4" > diff --git a/spec/frontend/self_monitor/components/__snapshots__/self_monitor_spec.js.snap b/spec/frontend/self_monitor/components/__snapshots__/self_monitor_spec.js.snap index b1644ac2b1f..d5546021430 100644 --- a/spec/frontend/self_monitor/components/__snapshots__/self_monitor_spec.js.snap +++ b/spec/frontend/self_monitor/components/__snapshots__/self_monitor_spec.js.snap @@ -61,6 +61,7 @@ exports[`self monitor component When the self monitor project has not been creat modalid="delete-self-monitor-modal" ok-title="Delete project" ok-variant="danger" + size="md" title="Disable self monitoring?" titletag="h4" > diff --git a/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb b/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb index 2e6eb71d37d..edf30ffc56f 100644 --- a/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb +++ b/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb @@ -3,20 +3,34 @@ require 'spec_helper' describe Gitlab::Diff::Formatters::ImageFormatter do - it_behaves_like "position formatter" do - let(:base_attrs) do - { - base_sha: 123, - start_sha: 456, - head_sha: 789, - old_path: 'old_image.png', - new_path: 'new_image.png', - position_type: 'image' - } - end + let(:base_attrs) do + { + base_sha: 123, + start_sha: 456, + head_sha: 789, + old_path: 'old_image.png', + new_path: 'new_image.png', + position_type: 'image' + } + end + + let(:attrs) do + base_attrs.merge(width: 100, height: 100, x: 1, y: 2) + end + + it_behaves_like 'position formatter' + + describe '#==' do + subject { described_class.new(attrs) } + + it { is_expected.to eq(subject) } + + [:width, :height, :x, :y].each do |attr| + let(:other_formatter) do + described_class.new(attrs.merge(attr => 9)) + end - let(:attrs) do - base_attrs.merge(width: 100, height: 100, x: 1, y: 2) + it { is_expected.not_to eq(other_formatter) } end end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index df32545b90b..7decc1bc911 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -106,6 +106,14 @@ describe Project do it { is_expected.to have_many(:sourced_pipelines) } it { is_expected.to have_many(:source_pipelines) } + it_behaves_like 'model with repository' do + let_it_be(:container) { create(:project, :repository, path: 'somewhere') } + let(:stubbed_container) { build_stubbed(:project) } + let(:expected_full_path) { "#{container.namespace.full_path}/somewhere" } + let(:expected_repository_klass) { Repository } + let(:expected_storage_klass) { Storage::Hashed } + end + it 'has an inverse relationship with merge requests' do expect(described_class.reflect_on_association(:merge_requests).has_inverse?).to eq(:target_project) end @@ -510,7 +518,6 @@ describe Project do describe 'Respond to' do it { is_expected.to respond_to(:url_to_repo) } - it { is_expected.to respond_to(:repo_exists?) } it { is_expected.to respond_to(:execute_hooks) } it { is_expected.to respond_to(:owner) } it { is_expected.to respond_to(:path_with_namespace) } @@ -664,44 +671,6 @@ describe Project do expect(project.url_to_repo).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + 'somewhere.git') end - describe "#web_url" do - let(:project) { create(:project, path: "somewhere") } - - context 'when given the only_path option' do - subject { project.web_url(only_path: only_path) } - - context 'when only_path is false' do - let(:only_path) { false } - - it 'returns the full web URL for this repo' do - expect(subject).to eq("#{Gitlab.config.gitlab.url}/#{project.namespace.full_path}/somewhere") - end - end - - context 'when only_path is true' do - let(:only_path) { true } - - it 'returns the relative web URL for this repo' do - expect(subject).to eq("/#{project.namespace.full_path}/somewhere") - end - end - - context 'when only_path is nil' do - let(:only_path) { nil } - - it 'returns the full web URL for this repo' do - expect(subject).to eq("#{Gitlab.config.gitlab.url}/#{project.namespace.full_path}/somewhere") - end - end - end - - context 'when not given the only_path option' do - it 'returns the full web URL for this repo' do - expect(project.web_url).to eq("#{Gitlab.config.gitlab.url}/#{project.namespace.full_path}/somewhere") - end - end - end - describe "#readme_url" do context 'with a non-existing repository' do let(:project) { create(:project) } @@ -931,14 +900,6 @@ describe Project do end end - describe '#repository' do - let(:project) { create(:project, :repository) } - - it 'returns valid repo' do - expect(project.repository).to be_kind_of(Repository) - end - end - describe '#default_issues_tracker?' do it "is true if used internal tracker" do project = build(:project) @@ -954,24 +915,6 @@ describe Project do end end - describe '#empty_repo?' do - context 'when the repo does not exist' do - let(:project) { build_stubbed(:project) } - - it 'returns true' do - expect(project.empty_repo?).to be(true) - end - end - - context 'when the repo exists' do - let(:project) { create(:project, :repository) } - let(:empty_project) { create(:project, :empty_repo) } - - it { expect(empty_project.empty_repo?).to be(true) } - it { expect(project.empty_repo?).to be(false) } - end - end - describe '#external_issue_tracker' do let(:project) { create(:project) } let(:ext_project) { create(:redmine_project) } @@ -3406,59 +3349,6 @@ describe Project do end end - describe '#http_url_to_repo' do - let(:project) { create(:project) } - - context 'when a custom HTTP clone URL root is not set' do - it 'returns the url to the repo without a username' do - expect(project.http_url_to_repo).to eq("#{project.web_url}.git") - expect(project.http_url_to_repo).not_to include('@') - end - end - - context 'when a custom HTTP clone URL root is set' do - before do - stub_application_setting(custom_http_clone_url_root: custom_http_clone_url_root) - end - - context 'when custom HTTP clone URL root has a relative URL root' do - context 'when custom HTTP clone URL root ends with a slash' do - let(:custom_http_clone_url_root) { 'https://git.example.com:51234/mygitlab/' } - - it 'returns the url to the repo, with the root replaced with the custom one' do - expect(project.http_url_to_repo).to eq("https://git.example.com:51234/mygitlab/#{project.full_path}.git") - end - end - - context 'when custom HTTP clone URL root does not end with a slash' do - let(:custom_http_clone_url_root) { 'https://git.example.com:51234/mygitlab' } - - it 'returns the url to the repo, with the root replaced with the custom one' do - expect(project.http_url_to_repo).to eq("https://git.example.com:51234/mygitlab/#{project.full_path}.git") - end - end - end - - context 'when custom HTTP clone URL root does not have a relative URL root' do - context 'when custom HTTP clone URL root ends with a slash' do - let(:custom_http_clone_url_root) { 'https://git.example.com:51234/' } - - it 'returns the url to the repo, with the root replaced with the custom one' do - expect(project.http_url_to_repo).to eq("https://git.example.com:51234/#{project.full_path}.git") - end - end - - context 'when custom HTTP clone URL root does not end with a slash' do - let(:custom_http_clone_url_root) { 'https://git.example.com:51234' } - - it 'returns the url to the repo, with the root replaced with the custom one' do - expect(project.http_url_to_repo).to eq("https://git.example.com:51234/#{project.full_path}.git") - end - end - end - end - end - describe '#lfs_http_url_to_repo' do let(:project) { create(:project) } @@ -5054,16 +4944,6 @@ describe Project do end end - context '#commits_by' do - let(:project) { create(:project, :repository) } - let(:commits) { project.repository.commits('HEAD', limit: 3).commits } - let(:commit_shas) { commits.map(&:id) } - - it 'retrieves several commits from the repository by oid' do - expect(project.commits_by(oids: commit_shas)).to eq commits - end - end - context '#members_among' do let(:users) { create_list(:user, 3) } diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 42151d86ac0..610b3e47c11 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -4084,4 +4084,46 @@ describe User, :do_not_mock_admin_mode do end end end + + describe '#read_only_attribute?' do + context 'when LDAP server is enabled' do + before do + allow(Gitlab::Auth::LDAP::Config).to receive(:enabled?).and_return(true) + end + + %i[name email location].each do |attribute| + it "is true for #{attribute}" do + expect(subject.read_only_attribute?(attribute)).to be_truthy + end + end + + context 'and ldap_readonly_attributes feature is disabled' do + before do + stub_feature_flags(ldap_readonly_attributes: false) + end + + %i[name email location].each do |attribute| + it "is false" do + expect(subject.read_only_attribute?(attribute)).to be_falsey + end + end + end + end + + context 'when synced attributes metadata is present' do + it 'delegates to synced_attributes_metadata' do + subject.build_user_synced_attributes_metadata + + expect(subject.build_user_synced_attributes_metadata) + .to receive(:read_only?).with(:email).and_return('return-value') + expect(subject.read_only_attribute?(:email)).to eq('return-value') + end + end + + context 'when synced attributes metadata is present' do + it 'is false for any attribute' do + expect(subject.read_only_attribute?(:email)).to be_falsey + end + end + end end diff --git a/spec/rubocop/cop/scalability/bulk_perform_with_context_spec.rb b/spec/rubocop/cop/scalability/bulk_perform_with_context_spec.rb new file mode 100644 index 00000000000..8107cfa8957 --- /dev/null +++ b/spec/rubocop/cop/scalability/bulk_perform_with_context_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' +require 'rubocop' +require_relative '../../../support/helpers/expect_offense' +require_relative '../../../../rubocop/cop/scalability/bulk_perform_with_context' + +describe RuboCop::Cop::Scalability::BulkPerformWithContext do + include CopHelper + include ExpectOffense + + subject(:cop) { described_class.new } + + it "adds an offense when calling bulk_perform_async" do + inspect_source(<<~CODE.strip_indent) + Worker.bulk_perform_async(args) + CODE + + expect(cop.offenses.size).to eq(1) + end + + it "adds an offense when calling bulk_perform_in" do + inspect_source(<<~CODE.strip_indent) + diffs.each_batch(of: BATCH_SIZE) do |relation, index| + ids = relation.pluck_primary_key.map { |id| [id] } + DeleteDiffFilesWorker.bulk_perform_in(index * 5.minutes, ids) + end + CODE + + expect(cop.offenses.size).to eq(1) + end + + it "does not add an offense for migrations" do + allow(cop).to receive(:in_migration?).and_return(true) + + inspect_source(<<~CODE.strip_indent) + Worker.bulk_perform_in(args) + CODE + + expect(cop.offenses.size).to eq(0) + end + + it "does not add an offence for specs" do + allow(cop).to receive(:in_spec?).and_return(true) + + inspect_source(<<~CODE.strip_indent) + Worker.bulk_perform_in(args) + CODE + + expect(cop.offenses.size).to eq(0) + end + + it "does not add an offense for scheduling BackgroundMigrations" do + inspect_source(<<~CODE.strip_indent) + BackgroundMigrationWorker.bulk_perform_in(args) + CODE + + expect(cop.offenses.size).to eq(0) + end +end diff --git a/spec/rubocop/cop/scalability/cron_worker_context_spec.rb b/spec/rubocop/cop/scalability/cron_worker_context_spec.rb new file mode 100644 index 00000000000..bf10b8dc02c --- /dev/null +++ b/spec/rubocop/cop/scalability/cron_worker_context_spec.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' +require 'rubocop' +require_relative '../../../support/helpers/expect_offense' +require_relative '../../../../rubocop/cop/scalability/cron_worker_context' + +describe RuboCop::Cop::Scalability::CronWorkerContext do + include CopHelper + include ExpectOffense + + subject(:cop) { described_class.new } + + it 'adds an offense when including CronjobQueue' do + inspect_source(<<~CODE.strip_indent) + class SomeWorker + include CronjobQueue + end + CODE + + expect(cop.offenses.size).to eq(1) + end + + it 'does not add offenses for other workers' do + expect_no_offenses(<<~CODE.strip_indent) + class SomeWorker + end + CODE + end + + it 'does not add an offense when the class defines a context' do + expect_no_offenses(<<~CODE.strip_indent) + class SomeWorker + include CronjobQueue + + with_context user: 'bla' + end + CODE + end + + it 'does not add an offense when the worker calls `with_context`' do + expect_no_offenses(<<~CODE.strip_indent) + class SomeWorker + include CronjobQueue + + def perform + with_context(user: 'bla') do + # more work + end + end + end + CODE + end + + it 'does not add an offense when the worker calls `bulk_perform_async_with_contexts`' do + expect_no_offenses(<<~CODE.strip_indent) + class SomeWorker + include CronjobQueue + + def perform + SomeOtherWorker.bulk_perform_async_with_contexts(contexts_for_arguments) + end + end + CODE + end + + it 'does not add an offense when the worker calls `bulk_perform_in_with_contexts`' do + expect_no_offenses(<<~CODE.strip_indent) + class SomeWorker + include CronjobQueue + + def perform + SomeOtherWorker.bulk_perform_in_with_contexts(contexts_for_arguments) + end + end + CODE + end +end diff --git a/spec/services/users/update_service_spec.rb b/spec/services/users/update_service_spec.rb index 50bbb16e368..5cd6283ca96 100644 --- a/spec/services/users/update_service_spec.rb +++ b/spec/services/users/update_service_spec.rb @@ -55,6 +55,15 @@ describe Users::UpdateService do expect(result[:message]).to eq("Emoji is not included in the list") end + it 'ignores read-only attributes' do + allow(user).to receive(:read_only_attribute?).with(:name).and_return(true) + + expect do + update_user(user, name: 'changed' + user.name) + user.reload + end.not_to change { user.name } + end + def update_user(user, opts) described_class.new(user, opts.merge(user: user)).execute end diff --git a/spec/support/shared_examples/models/concerns/has_repository_shared_examples.rb b/spec/support/shared_examples/models/concerns/has_repository_shared_examples.rb new file mode 100644 index 00000000000..fdea312dfa9 --- /dev/null +++ b/spec/support/shared_examples/models/concerns/has_repository_shared_examples.rb @@ -0,0 +1,171 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'model with repository' do + describe '#commits_by' do + let(:commits) { container.repository.commits('HEAD', limit: 3).commits } + let(:commit_shas) { commits.map(&:id) } + + it 'retrieves several commits from the repository by oid' do + expect(container.commits_by(oids: commit_shas)).to eq commits + end + end + + describe "#web_url" do + context 'when given the only_path option' do + subject { container.web_url(only_path: only_path) } + + context 'when only_path is false' do + let(:only_path) { false } + + it 'returns the full web URL for this repo' do + expect(subject).to eq("#{Gitlab.config.gitlab.url}/#{expected_full_path}") + end + end + + context 'when only_path is true' do + let(:only_path) { true } + + it 'returns the relative web URL for this repo' do + expect(subject).to eq("/#{expected_full_path}") + end + end + + context 'when only_path is nil' do + let(:only_path) { nil } + + it 'returns the full web URL for this repo' do + expect(subject).to eq("#{Gitlab.config.gitlab.url}/#{expected_full_path}") + end + end + end + + context 'when not given the only_path option' do + it 'returns the full web URL for this repo' do + expect(container.web_url).to eq("#{Gitlab.config.gitlab.url}/#{expected_full_path}") + end + end + end + + describe '#ssh_url_to_repo' do + it 'returns container ssh address' do + expect(container.ssh_url_to_repo).to eq container.url_to_repo + end + end + + describe '#http_url_to_repo' do + subject { container.http_url_to_repo } + + context 'when a custom HTTP clone URL root is not set' do + it 'returns the url to the repo without a username' do + expect(subject).to eq("#{container.web_url}.git") + expect(subject).not_to include('@') + end + end + + context 'when a custom HTTP clone URL root is set' do + before do + stub_application_setting(custom_http_clone_url_root: custom_http_clone_url_root) + end + + context 'when custom HTTP clone URL root has a relative URL root' do + context 'when custom HTTP clone URL root ends with a slash' do + let(:custom_http_clone_url_root) { 'https://git.example.com:51234/mygitlab/' } + + it 'returns the url to the repo, with the root replaced with the custom one' do + expect(subject).to eq("#{custom_http_clone_url_root}#{expected_full_path}.git") + end + end + + context 'when custom HTTP clone URL root does not end with a slash' do + let(:custom_http_clone_url_root) { 'https://git.example.com:51234/mygitlab' } + + it 'returns the url to the repo, with the root replaced with the custom one' do + expect(subject).to eq("#{custom_http_clone_url_root}/#{expected_full_path}.git") + end + end + end + + context 'when custom HTTP clone URL root does not have a relative URL root' do + context 'when custom HTTP clone URL root ends with a slash' do + let(:custom_http_clone_url_root) { 'https://git.example.com:51234/' } + + it 'returns the url to the repo, with the root replaced with the custom one' do + expect(subject).to eq("#{custom_http_clone_url_root}#{expected_full_path}.git") + end + end + + context 'when custom HTTP clone URL root does not end with a slash' do + let(:custom_http_clone_url_root) { 'https://git.example.com:51234' } + + it 'returns the url to the repo, with the root replaced with the custom one' do + expect(subject).to eq("#{custom_http_clone_url_root}/#{expected_full_path}.git") + end + end + end + end + end + + describe '#repository' do + it 'returns valid repo' do + expect(container.repository).to be_kind_of(expected_repository_klass) + end + end + + describe '#storage' do + it 'returns valid storage' do + expect(container.storage).to be_kind_of(expected_storage_klass) + end + end + + describe '#full_path' do + it 'returns valid full_path' do + expect(container.full_path).to eq(expected_full_path) + end + end + + describe '#empty_repo?' do + context 'when the repo does not exist' do + it 'returns true' do + expect(stubbed_container.empty_repo?).to be(true) + end + end + + context 'when the repo exists' do + it { expect(container.empty_repo?).to be(false) } + + it 'returns true when repository is empty' do + allow(container.repository).to receive(:empty?).and_return(true) + + expect(container.empty_repo?).to be(true) + end + end + end + + describe '#valid_repo?' do + it { expect(stubbed_container.valid_repo?).to be(false)} + it { expect(container.valid_repo?).to be(true) } + end + + describe '#repository_exists?' do + it { expect(stubbed_container.repository_exists?).to be(false)} + it { expect(container.repository_exists?).to be(true) } + end + + describe '#repo_exists?' do + it { expect(stubbed_container.repo_exists?).to be(false)} + it { expect(container.repo_exists?).to be(true) } + end + + describe '#root_ref' do + let(:root_ref) { container.repository.root_ref } + + it { expect(container.root_ref?(root_ref)).to be(true) } + it { expect(container.root_ref?('HEAD')).to be(false) } + it { expect(container.root_ref?('foo')).to be(false) } + end + + describe 'Respond to' do + it { is_expected.to respond_to(:base_dir) } + it { is_expected.to respond_to(:disk_path) } + end +end diff --git a/spec/workers/concerns/cronjob_queue_spec.rb b/spec/workers/concerns/cronjob_queue_spec.rb index 21483d0e4e3..ea3b7bad2e1 100644 --- a/spec/workers/concerns/cronjob_queue_spec.rb +++ b/spec/workers/concerns/cronjob_queue_spec.rb @@ -10,7 +10,7 @@ describe CronjobQueue do end include ApplicationWorker - include CronjobQueue + include CronjobQueue # rubocop:disable Scalability/CronWorkerContext end end diff --git a/yarn.lock b/yarn.lock index a3d9f9970e0..5b8c5c84ce9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -736,15 +736,15 @@ dependencies: vue-eslint-parser "^6.0.4" -"@gitlab/svgs@^1.91.0": - version "1.91.0" - resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.91.0.tgz#cf7b28e43779a929a438dcc0c51f39f92551e58e" - integrity sha512-Sz8aaaNnUUtlrk/FPf6FNceu4XsDLBh6g/c6nmzDVGF9kHrfiXfWL0tYAax8vhkrld5vewGHE0bRpq2ZILq0pw== - -"@gitlab/ui@^9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-9.0.0.tgz#16d637f47ba0537100fd1c6d452b56174b50171b" - integrity sha512-OfP8UAticpqKkqbPBZ+7bbCBsd9Fxq3eL55Uq5nEwxJ8gGXm+nSc+HFnbzX/ryv0iz5+7nCI4DfIfgy9E4QAeQ== +"@gitlab/svgs@^1.94.0": + version "1.94.0" + resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.94.0.tgz#d6a39f982811f82d942692a91bf2678961752eba" + integrity sha512-lB7HTVsNPBLUEgNUXLLC4V/XJsWg7aSO7RBp6cuuL3n6fUS9VGfELH9aBnuPJglTHddktcnElkZ3S54XI8kYHw== + +"@gitlab/ui@^9.3.0": + version "9.3.0" + resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-9.3.0.tgz#851b4246c2e661a5a343184f74e0448c597ce28c" + integrity sha512-DB9Q8XDLfn3Ui6EfYTVnmHVYPwbukocYTWL+uD6zN3leiamYQqaoYGmtcrXk9oSiAyuJYwaJlCzlblG1GPwnfw== dependencies: "@babel/standalone" "^7.0.0" "@gitlab/vue-toasted" "^1.3.0" |