diff options
60 files changed, 630 insertions, 198 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index fe6d01c1a45..5aee1345c52 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.88.0 +0.89.0 diff --git a/Gemfile.lock b/Gemfile.lock index fa99ec3febe..b85c7085d07 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -63,7 +63,7 @@ GEM fog-core mime-types (>= 2.99) unf - ast (2.3.0) + ast (2.4.0) atomic (1.1.99) attr_encrypted (3.0.3) encryptor (~> 3.0.0) @@ -586,8 +586,8 @@ GEM orm_adapter (0.5.0) os (0.9.6) parallel (1.12.1) - parser (2.4.0.2) - ast (~> 2.3) + parser (2.5.0.3) + ast (~> 2.4.0) parslet (1.5.0) blankslate (~> 2.0) path_expander (1.0.2) @@ -951,13 +951,13 @@ GEM get_process_mem (~> 0) unicorn (>= 4, < 6) uniform_notifier (1.10.0) - unparser (0.2.6) + unparser (0.2.7) abstract_type (~> 0.0.7) adamantium (~> 0.2.0) concord (~> 0.1.5) diff-lcs (~> 1.3) equalizer (~> 0.0.9) - parser (>= 2.3.1.2, < 2.5) + parser (>= 2.3.1.2, < 2.6) procto (~> 0.0.2) url_safe_base64 (0.2.2) validates_hostname (1.0.6) diff --git a/app/assets/javascripts/notes/services/notes_service.js b/app/assets/javascripts/notes/services/notes_service.js index 4766351dfc5..b4c19a9ec22 100644 --- a/app/assets/javascripts/notes/services/notes_service.js +++ b/app/assets/javascripts/notes/services/notes_service.js @@ -27,10 +27,11 @@ export default { return Vue.http[method](endpoint); }, poll(data = {}) { - const { endpoint, lastFetchedAt } = data; + const endpoint = data.notesData.notesPath; + const lastFetchedAt = data.lastFetchedAt; const options = { headers: { - 'X-Last-Fetched-At': lastFetchedAt, + 'X-Last-Fetched-At': lastFetchedAt ? `${lastFetchedAt}` : undefined, }, }; diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js index 70fc368a471..ebbacb576d6 100644 --- a/app/assets/javascripts/notes/stores/actions.js +++ b/app/assets/javascripts/notes/stores/actions.js @@ -209,18 +209,16 @@ const pollSuccessCallBack = (resp, commit, state, getters) => { }); } - commit(types.SET_LAST_FETCHED_AT, resp.lastFetchedAt); + commit(types.SET_LAST_FETCHED_AT, resp.last_fetched_at); return resp; }; export const poll = ({ commit, state, getters }) => { - const requestData = { endpoint: state.notesData.notesPath, lastFetchedAt: state.lastFetchedAt }; - eTagPoll = new Poll({ resource: service, method: 'poll', - data: requestData, + data: state, successCallback: resp => resp.json() .then(data => pollSuccessCallBack(data, commit, state, getters)), errorCallback: () => Flash('Something went wrong while fetching latest comments.'), @@ -229,7 +227,7 @@ export const poll = ({ commit, state, getters }) => { if (!Visibility.hidden()) { eTagPoll.makeRequest(); } else { - service.poll(requestData); + service.poll(state); } Visibility.change(() => { diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js index f07afbe4303..9308daa36f1 100644 --- a/app/assets/javascripts/notes/stores/mutations.js +++ b/app/assets/javascripts/notes/stores/mutations.js @@ -90,19 +90,21 @@ export default { const notes = []; notesData.forEach((note) => { - const nn = Object.assign({}, note); - // To support legacy notes, should be very rare case. if (note.individual_note && note.notes.length > 1) { note.notes.forEach((n) => { - nn.notes = [n]; // override notes array to only have one item to mimick individual_note - notes.push(nn); + notes.push({ + ...note, + notes: [n], // override notes array to only have one item to mimick individual_note + }); }); } else { const oldNote = utils.findNoteObjectById(state.notes, note.id); - nn.expanded = oldNote ? oldNote.expanded : note.expanded; - notes.push(nn); + notes.push({ + ...note, + expanded: (oldNote ? oldNote.expanded : note.expanded), + }); } }); diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue index de98a77be6f..7ff7fc7988a 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue @@ -63,7 +63,7 @@ }; this.isRemovingSourceBranch = true; - this.service.mergeResource.save(options) + this.service.merge(options) .then(res => res.data) .then((data) => { if (data.status === 'merge_when_pipeline_succeeds') { diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge.js deleted file mode 100644 index ebfd6765934..00000000000 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge.js +++ /dev/null @@ -1,44 +0,0 @@ -import emptyStateSVG from 'icons/_mr_widget_empty_state.svg'; - -export default { - name: 'MRWidgetNothingToMerge', - props: { - mr: { - type: Object, - required: true, - }, - }, - data() { - return { emptyStateSVG }; - }, - template: ` - <div class="mr-widget-body mr-widget-empty-state"> - <div class="row"> - <div class="artwork col-sm-5 col-sm-push-7 col-xs-12 text-center"> - <span v-html="emptyStateSVG"></span> - </div> - <div class="text col-sm-7 col-sm-pull-5 col-xs-12"> - <span> - Merge requests are a place to propose changes you have made to a project - and discuss those changes with others. - </span> - <p> - Interested parties can even contribute by pushing commits if they want to. - </p> - <p> - Currently there are no changes in this merge request's source branch. - Please push new commits or use a different branch. - </p> - <div> - <a - v-if="mr.newBlobPath" - :href="mr.newBlobPath" - class="btn btn-inverted btn-save"> - Create file - </a> - </div> - </div> - </div> - </div> - `, -}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue new file mode 100644 index 00000000000..3d9161f6926 --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue @@ -0,0 +1,47 @@ +<script> +import emptyStateSVG from 'icons/_mr_widget_empty_state.svg'; + +export default { + name: 'MRWidgetNothingToMerge', + props: { + mr: { + type: Object, + required: true, + }, + }, + data() { + return { emptyStateSVG }; + }, +}; +</script> + +<template> + <div class="mr-widget-body mr-widget-empty-state"> + <div class="row"> + <div class="artwork col-sm-5 col-sm-push-7 col-xs-12 text-center"> + <span v-html="emptyStateSVG"></span> + </div> + <div class="text col-sm-7 col-sm-pull-5 col-xs-12"> + <span> + Merge requests are a place to propose changes you have made to a project + and discuss those changes with others. + </span> + <p> + Interested parties can even contribute by pushing commits if they want to. + </p> + <p> + Currently there are no changes in this merge request's source branch. + Please push new commits or use a different branch. + </p> + <div> + <a + v-if="mr.newBlobPath" + :href="mr.newBlobPath" + class="btn btn-inverted btn-save"> + Create file + </a> + </div> + </div> + </div> + </div> +</template> diff --git a/app/assets/javascripts/vue_merge_request_widget/dependencies.js b/app/assets/javascripts/vue_merge_request_widget/dependencies.js index b867dd90a41..20624aad0ad 100644 --- a/app/assets/javascripts/vue_merge_request_widget/dependencies.js +++ b/app/assets/javascripts/vue_merge_request_widget/dependencies.js @@ -24,7 +24,7 @@ export { default as MergingState } from './components/states/mr_widget_merging.v export { default as WipState } from './components/states/mr_widget_wip'; export { default as ArchivedState } from './components/states/mr_widget_archived.vue'; export { default as ConflictsState } from './components/states/mr_widget_conflicts.vue'; -export { default as NothingToMergeState } from './components/states/mr_widget_nothing_to_merge'; +export { default as NothingToMergeState } from './components/states/nothing_to_merge.vue'; export { default as MissingBranchState } from './components/states/mr_widget_missing_branch.vue'; export { default as NotAllowedState } from './components/states/mr_widget_not_allowed.vue'; export { default as ReadyToMergeState } from './components/states/mr_widget_ready_to_merge'; diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js index 01365b70897..cc8bc6af1e1 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js +++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js @@ -71,7 +71,8 @@ export default { return this.mr.deployments.length; }, shouldRenderSourceBranchRemovalStatus() { - return !this.mr.canRemoveSourceBranch && this.mr.shouldRemoveSourceBranch; + return !this.mr.canRemoveSourceBranch && this.mr.shouldRemoveSourceBranch && + (!this.mr.isNothingToMergeState && !this.mr.isMergedState); }, }, methods: { diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js index 5d07bcf1bb9..a47ca9fae86 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js @@ -125,6 +125,10 @@ export default class MergeRequestStore { return this.state === stateKey.nothingToMerge; } + get isMergedState() { + return this.state === stateKey.merged; + } + initRebase(data) { this.canPushToSourceBranch = data.can_push_to_source_branch; this.rebaseInProgress = data.rebase_in_progress; diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js b/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js index 29d5bd4a1da..483ad52b8cc 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js @@ -49,6 +49,7 @@ export const stateKey = { notAllowedToMerge: 'notAllowedToMerge', readyToMerge: 'readyToMerge', rebase: 'rebase', + merged: 'merged', }; export default { diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 4c9732c26d9..e21a9f0afc9 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -137,12 +137,22 @@ z-index: 200; overflow: hidden; - a:not(.btn-retry), - .btn-link { + a:not(.btn) { color: inherit; + + &:hover { + color: $gl-link-hover-color; + + .avatar { + border-color: rgba($avatar-border, .2); + } + + } + } .btn-link { + color: inherit; outline: none; } @@ -214,7 +224,7 @@ &:hover { text-decoration: underline; - color: $md-link-color; + color: $gl-link-hover-color; } } } @@ -486,16 +496,6 @@ } } - a:not(.btn-retry) { - &:hover { - color: $md-link-color; - - .avatar { - border-color: rgba($avatar-border, .2); - } - } - } - .dropdown-menu-toggle { width: 100%; padding-top: 6px; @@ -503,6 +503,20 @@ .dropdown-menu { width: 100%; + + /* + * Overwrite hover style for dropdown items, so that they are not blue + * This should be removed during dev of https://gitlab.com/gitlab-org/gitlab-ce/issues/44040 + */ + li a { + &:hover, + &:active, + &:focus, + &.is-focused { + @include dropdown-item-hover; + } + } + } } diff --git a/app/helpers/import_helper.rb b/app/helpers/import_helper.rb index 9149d79ecb8..4664b1728c4 100644 --- a/app/helpers/import_helper.rb +++ b/app/helpers/import_helper.rb @@ -1,4 +1,6 @@ module ImportHelper + include ::Gitlab::Utils::StrongMemoize + def has_ci_cd_only_params? false end @@ -75,17 +77,18 @@ module ImportHelper private def github_project_url(full_path) - "#{github_root_url}/#{full_path}" + URI.join(github_root_url, full_path).to_s end def github_root_url - return @github_url if defined?(@github_url) + strong_memoize(:github_url) do + provider = Gitlab::Auth::OAuth::Provider.config_for('github') - provider = Gitlab.config.omniauth.providers.find { |p| p.name == 'github' } - @github_url = provider.fetch('url', 'https://github.com') if provider + provider&.dig('url').presence || 'https://github.com' + end end def gitea_project_url(full_path) - "#{@gitea_host_url.sub(%r{/+\z}, '')}/#{full_path}" + URI.join(@gitea_host_url, full_path).to_s end end diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index a70e73a6da9..20aed60cb7a 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -169,7 +169,7 @@ module NotesHelper reopenPath: reopen_issuable_path(issuable), notesPath: notes_url, totalNotes: issuable.discussions.length, - lastFetchedAt: Time.now + lastFetchedAt: Time.now.to_i }.to_json end diff --git a/app/models/compare.rb b/app/models/compare.rb index 3a8bbcb1acd..feb4b89c781 100644 --- a/app/models/compare.rb +++ b/app/models/compare.rb @@ -1,4 +1,6 @@ class Compare + include Gitlab::Utils::StrongMemoize + delegate :same, :head, :base, to: :@compare attr_reader :project @@ -11,9 +13,10 @@ class Compare end end - def initialize(compare, project, straight: false) + def initialize(compare, project, base_sha: nil, straight: false) @compare = compare @project = project + @base_sha = base_sha @straight = straight end @@ -22,40 +25,36 @@ class Compare end def start_commit - return @start_commit if defined?(@start_commit) + strong_memoize(:start_commit) do + commit = @compare.base - commit = @compare.base - @start_commit = commit ? ::Commit.new(commit, project) : nil + ::Commit.new(commit, project) if commit + end end def head_commit - return @head_commit if defined?(@head_commit) + strong_memoize(:head_commit) do + commit = @compare.head - commit = @compare.head - @head_commit = commit ? ::Commit.new(commit, project) : nil + ::Commit.new(commit, project) if commit + end end alias_method :commit, :head_commit - def base_commit - return @base_commit if defined?(@base_commit) - - @base_commit = if start_commit && head_commit - project.merge_base_commit(start_commit.id, head_commit.id) - else - nil - end - end - def start_commit_sha - start_commit.try(:sha) + start_commit&.sha end def base_commit_sha - base_commit.try(:sha) + strong_memoize(:base_commit) do + next unless start_commit && head_commit + + @base_sha || project.merge_base_commit(start_commit.id, head_commit.id)&.sha + end end def head_commit_sha - commit.try(:sha) + commit&.sha end def raw_diffs(*args) diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb index 1db91c3c90c..2a69a205629 100644 --- a/app/services/compare_service.rb +++ b/app/services/compare_service.rb @@ -10,9 +10,14 @@ class CompareService @start_ref_name = new_start_ref_name end - def execute(target_project, target_ref, straight: false) + def execute(target_project, target_ref, base_sha: nil, straight: false) raw_compare = target_project.repository.compare_source_branch(target_ref, start_project.repository, start_ref_name, straight: straight) - Compare.new(raw_compare, target_project, straight: straight) if raw_compare + return unless raw_compare + + Compare.new(raw_compare, + target_project, + base_sha: base_sha, + straight: straight) end end diff --git a/app/views/shared/boards/_show.html.haml b/app/views/shared/boards/_show.html.haml index 44b09545a61..dac60094686 100644 --- a/app/views/shared/boards/_show.html.haml +++ b/app/views/shared/boards/_show.html.haml @@ -27,7 +27,7 @@ ":issue-link-base" => "issueLinkBase", ":root-path" => "rootPath", ":board-id" => "boardId", - ":key" => "_uid" } + ":key" => "list.id" } = render "shared/boards/components/sidebar", group: group - if @project %board-add-issues-modal{ "new-issue-path" => new_project_issue_path(@project), diff --git a/app/views/shared/issuable/form/_contribution.html.haml b/app/views/shared/issuable/form/_contribution.html.haml index 0f2d313a5cc..de508278d7c 100644 --- a/app/views/shared/issuable/form/_contribution.html.haml +++ b/app/views/shared/issuable/form/_contribution.html.haml @@ -14,7 +14,7 @@ .checkbox = form.label :allow_maintainer_to_push do = form.check_box :allow_maintainer_to_push, disabled: !issuable.can_allow_maintainer_to_push?(current_user) - = _('Allow edits from maintainers') + = _('Allow edits from maintainers.') = link_to 'About this feature', help_page_path('user/project/merge_requests/maintainer_access') .help-block = allow_maintainer_push_unavailable_reason(issuable) diff --git a/changelogs/unreleased/34604-fix-generated-url-for-external-repository.yml b/changelogs/unreleased/34604-fix-generated-url-for-external-repository.yml new file mode 100644 index 00000000000..c4b5f59b724 --- /dev/null +++ b/changelogs/unreleased/34604-fix-generated-url-for-external-repository.yml @@ -0,0 +1,5 @@ +--- +title: Fix generated URL when listing repoitories for import +merge_request: 17692 +author: +type: fixed diff --git a/changelogs/unreleased/42579-fix-sidebar-dropdown-hover-style.yml b/changelogs/unreleased/42579-fix-sidebar-dropdown-hover-style.yml new file mode 100644 index 00000000000..c0a247dc895 --- /dev/null +++ b/changelogs/unreleased/42579-fix-sidebar-dropdown-hover-style.yml @@ -0,0 +1,5 @@ +--- +title: Fix hover style of dropdown items in the right sidebar +merge_request: 17519 +author: +type: fixed diff --git a/changelogs/unreleased/42814-fix-remove-source-branch-when-mwps.yml b/changelogs/unreleased/42814-fix-remove-source-branch-when-mwps.yml new file mode 100644 index 00000000000..08e77ee7c3b --- /dev/null +++ b/changelogs/unreleased/42814-fix-remove-source-branch-when-mwps.yml @@ -0,0 +1,6 @@ +--- +title: Fix "Remove source branch" button in Merge request widget during merge when pipeline + succeeds state +merge_request: 17192 +author: +type: fixed diff --git a/changelogs/unreleased/44139-fix-issue-boards-dup-keys.yml b/changelogs/unreleased/44139-fix-issue-boards-dup-keys.yml new file mode 100644 index 00000000000..dd5f2f06d6c --- /dev/null +++ b/changelogs/unreleased/44139-fix-issue-boards-dup-keys.yml @@ -0,0 +1,6 @@ +--- +title: Use object ID to prevent duplicate keys Vue warning on Issue Boards page during + development +merge_request: 17682 +author: +type: other diff --git a/changelogs/unreleased/add-indexes-to-todos-for-heavy-users-like-sean.yml b/changelogs/unreleased/add-indexes-to-todos-for-heavy-users-like-sean.yml new file mode 100644 index 00000000000..f0e5103a9d9 --- /dev/null +++ b/changelogs/unreleased/add-indexes-to-todos-for-heavy-users-like-sean.yml @@ -0,0 +1,5 @@ +--- +title: Add partial indexes on todos to handle users with many todos +merge_request: +author: +type: performance diff --git a/changelogs/unreleased/fix-code-search-500-with-non-ascii-filename.yml b/changelogs/unreleased/fix-code-search-500-with-non-ascii-filename.yml new file mode 100644 index 00000000000..29e3b7be985 --- /dev/null +++ b/changelogs/unreleased/fix-code-search-500-with-non-ascii-filename.yml @@ -0,0 +1,5 @@ +--- +title: Fix code and wiki search results when filename is non-ASCII +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/osw-stop-recalculating-merge-base-on-mr-loading.yml b/changelogs/unreleased/osw-stop-recalculating-merge-base-on-mr-loading.yml new file mode 100644 index 00000000000..1673e1d3658 --- /dev/null +++ b/changelogs/unreleased/osw-stop-recalculating-merge-base-on-mr-loading.yml @@ -0,0 +1,5 @@ +--- +title: Avoid re-fetching merge-base SHA from Gitaly unnecessarily +merge_request: +author: +type: performance diff --git a/changelogs/unreleased/refactor-move-mr-widget-nothing-to-merge-vue-component.yml b/changelogs/unreleased/refactor-move-mr-widget-nothing-to-merge-vue-component.yml new file mode 100644 index 00000000000..dc8ff95dc27 --- /dev/null +++ b/changelogs/unreleased/refactor-move-mr-widget-nothing-to-merge-vue-component.yml @@ -0,0 +1,5 @@ +--- +title: Move NothingToMerge vue component +merge_request: 17544 +author: George Tsiolis +type: performance diff --git a/changelogs/unreleased/tc-api-fix-expose_url.yml b/changelogs/unreleased/tc-api-fix-expose_url.yml new file mode 100644 index 00000000000..c701f64d6bf --- /dev/null +++ b/changelogs/unreleased/tc-api-fix-expose_url.yml @@ -0,0 +1,5 @@ +--- +title: Ensure the API returns https links when https is configured +merge_request: 17681 +author: +type: fixed diff --git a/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb index 59385cdf379..58941aae1b0 100644 --- a/config/initializers/backtrace_silencers.rb +++ b/config/initializers/backtrace_silencers.rb @@ -1,7 +1,2 @@ -# Be sure to restart your server when you modify this file. - -# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. -# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } - -# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. -# Rails.backtrace_cleaner.remove_silencers! +Rails.backtrace_cleaner.remove_silencers! +Rails.backtrace_cleaner.add_silencer { |line| line !~ Gitlab::APP_DIRS_PATTERN } diff --git a/db/migrate/20180309160427_add_partial_indexes_on_todos.rb b/db/migrate/20180309160427_add_partial_indexes_on_todos.rb new file mode 100644 index 00000000000..18a5c69df1b --- /dev/null +++ b/db/migrate/20180309160427_add_partial_indexes_on_todos.rb @@ -0,0 +1,28 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddPartialIndexesOnTodos < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + disable_ddl_transaction! + + INDEX_NAME_PENDING="index_todos_on_user_id_and_id_pending" + INDEX_NAME_DONE="index_todos_on_user_id_and_id_done" + + def up + unless index_exists?(:todos, [:user_id, :id], name: INDEX_NAME_PENDING) + add_concurrent_index(:todos, [:user_id, :id], where: "state='pending'", name: INDEX_NAME_PENDING) + end + unless index_exists?(:todos, [:user_id, :id], name: INDEX_NAME_DONE) + add_concurrent_index(:todos, [:user_id, :id], where: "state='done'", name: INDEX_NAME_DONE) + end + end + + def down + remove_concurrent_index(:todos, [:user_id, :id], where: "state='pending'", name: INDEX_NAME_PENDING) + remove_concurrent_index(:todos, [:user_id, :id], where: "state='done'", name: INDEX_NAME_DONE) + end +end diff --git a/db/schema.rb b/db/schema.rb index 970b1ad9948..ab4370e2754 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180309121820) do +ActiveRecord::Schema.define(version: 20180309160427) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -1778,6 +1778,8 @@ ActiveRecord::Schema.define(version: 20180309121820) do add_index "todos", ["note_id"], name: "index_todos_on_note_id", using: :btree add_index "todos", ["project_id"], name: "index_todos_on_project_id", using: :btree add_index "todos", ["target_type", "target_id"], name: "index_todos_on_target_type_and_target_id", using: :btree + add_index "todos", ["user_id", "id"], name: "index_todos_on_user_id_and_id_done", where: "((state)::text = 'done'::text)", using: :btree + add_index "todos", ["user_id", "id"], name: "index_todos_on_user_id_and_id_pending", where: "((state)::text = 'pending'::text)", using: :btree add_index "todos", ["user_id"], name: "index_todos_on_user_id", using: :btree create_table "trending_projects", force: :cascade do |t| diff --git a/doc/development/fe_guide/style_guide_js.md b/doc/development/fe_guide/style_guide_js.md index 917d28b48ee..7b5fa6ca42f 100644 --- a/doc/development/fe_guide/style_guide_js.md +++ b/doc/development/fe_guide/style_guide_js.md @@ -548,6 +548,57 @@ On those a default key should not be provided. 1. Properties in a Vue Component: Check [order of properties in components rule][vue-order]. +#### `:key` +When using `v-for` you need to provide a *unique* `:key` attribute for each item. + +1. If the elements of the array being iterated have an unique `id` it is advised to use it: + ```html + <div + v-for="item in items" + :key="item.id" + > + <!-- content --> + </div> + ``` + +1. When the elements being iterated don't have a unique id, you can use the array index as the `:key` attribute + ```html + <div + v-for="(item, index) in items" + :key="index" + > + <!-- content --> + </div> + ``` + + +1. When using `v-for` with `template` and there is more than one child element, the `:key` values must be unique. It's advised to use `kebab-case` namespaces. + ```html + <template v-for="(item, index) in items"> + <span :key="`span-${index}`"></span> + <button :key="`button-${index}`"></button> + </template> + ``` + +1. When dealing with nested `v-for` use the same guidelines as above. + ```html + <div + v-for="item in items" + :key="item.id" + > + <span + v-for="element in array" + :key="element.id" + > + <!-- content --> + </span> + </div> + ``` + + +Useful links: +1. [`key`](https://vuejs.org/v2/guide/list.html#key) +1. [Vue Style Guide: Keyed v-for](https://vuejs.org/v2/style-guide/#Keyed-v-for-essential ) #### Vue and Bootstrap 1. Tooltips: Do not rely on `has-tooltip` class name for Vue components diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md index 093a3ca4407..c1170fa3b13 100644 --- a/doc/development/fe_guide/vue.md +++ b/doc/development/fe_guide/vue.md @@ -53,13 +53,13 @@ you can find a clear separation of concerns: ``` new_feature ├── components -│ └── component.js.es6 +│ └── component.vue │ └── ... -├── store -│ └── new_feature_store.js.es6 -├── service -│ └── new_feature_service.js.es6 -├── new_feature_bundle.js.es6 +├── stores +│ └── new_feature_store.js +├── services +│ └── new_feature_service.js +├── new_feature_bundle.js ``` _For consistency purposes, we recommend you to follow the same structure._ diff --git a/doc/user/project/merge_requests/img/allow_maintainer_push.png b/doc/user/project/merge_requests/img/allow_maintainer_push.png Binary files differindex 1631527071b..91cc399f4ff 100644 --- a/doc/user/project/merge_requests/img/allow_maintainer_push.png +++ b/doc/user/project/merge_requests/img/allow_maintainer_push.png diff --git a/lib/api/helpers/related_resources_helpers.rb b/lib/api/helpers/related_resources_helpers.rb index 1f677529b07..7f4d6e58b34 100644 --- a/lib/api/helpers/related_resources_helpers.rb +++ b/lib/api/helpers/related_resources_helpers.rb @@ -15,7 +15,7 @@ module API url_options = Gitlab::Application.routes.default_url_options protocol, host, port = url_options.slice(:protocol, :host, :port).values - URI::HTTP.build(scheme: protocol, host: host, port: port, path: path).to_s + URI::Generic.build(scheme: protocol, host: host, port: port, path: path).to_s end private diff --git a/lib/generators/rails/post_deployment_migration/post_deployment_migration_generator.rb b/lib/generators/rails/post_deployment_migration/post_deployment_migration_generator.rb index 7cb4bccb23c..91175b49c79 100644 --- a/lib/generators/rails/post_deployment_migration/post_deployment_migration_generator.rb +++ b/lib/generators/rails/post_deployment_migration/post_deployment_migration_generator.rb @@ -3,7 +3,7 @@ require 'rails/generators' module Rails class PostDeploymentMigrationGenerator < Rails::Generators::NamedBase def create_migration_file - timestamp = Time.now.strftime('%Y%m%d%H%I%S') + timestamp = Time.now.strftime('%Y%m%d%H%M%S') template "migration.rb", "db/post_migrate/#{timestamp}_#{file_name}.rb" end diff --git a/lib/gitlab.rb b/lib/gitlab.rb index 11f7c8b9510..aa9fd36d9ff 100644 --- a/lib/gitlab.rb +++ b/lib/gitlab.rb @@ -2,6 +2,7 @@ require_dependency 'gitlab/git' module Gitlab COM_URL = 'https://gitlab.com'.freeze + APP_DIRS_PATTERN = %r{^/?(app|config|ee|lib|spec|\(\w*\))} def self.com? # Check `staging?` as well to keep parity with gitlab.com diff --git a/lib/gitlab/auth/saml/config.rb b/lib/gitlab/auth/saml/config.rb index e654e7fe438..2760b1a3247 100644 --- a/lib/gitlab/auth/saml/config.rb +++ b/lib/gitlab/auth/saml/config.rb @@ -4,7 +4,7 @@ module Gitlab class Config class << self def options - Gitlab.config.omniauth.providers.find { |provider| provider.name == 'saml' } + Gitlab::Auth::OAuth::Provider.config_for('saml') end def groups diff --git a/lib/gitlab/diff/diff_refs.rb b/lib/gitlab/diff/diff_refs.rb index 88e0db830f6..81df47964be 100644 --- a/lib/gitlab/diff/diff_refs.rb +++ b/lib/gitlab/diff/diff_refs.rb @@ -44,7 +44,11 @@ module Gitlab project.commit(head_sha) else straight = start_sha == base_sha - CompareService.new(project, head_sha).execute(project, start_sha, straight: straight) + + CompareService.new(project, head_sha).execute(project, + start_sha, + base_sha: base_sha, + straight: straight) end end end diff --git a/lib/gitlab/github_import/client.rb b/lib/gitlab/github_import/client.rb index 4f160e4a447..a61beafae0d 100644 --- a/lib/gitlab/github_import/client.rb +++ b/lib/gitlab/github_import/client.rb @@ -197,10 +197,7 @@ module Gitlab end def github_omniauth_provider - @github_omniauth_provider ||= - Gitlab.config.omniauth.providers - .find { |provider| provider.name == 'github' } - .to_h + @github_omniauth_provider ||= Gitlab::Auth::OAuth::Provider.config_for('github').to_h end def rate_limit_counter diff --git a/lib/gitlab/gitlab_import/client.rb b/lib/gitlab/gitlab_import/client.rb index 075b3982608..5482504e72e 100644 --- a/lib/gitlab/gitlab_import/client.rb +++ b/lib/gitlab/gitlab_import/client.rb @@ -72,7 +72,7 @@ module Gitlab end def config - Gitlab.config.omniauth.providers.find {|provider| provider.name == "gitlab"} + Gitlab::Auth::OAuth::Provider.config_for('gitlab') end def gitlab_options diff --git a/lib/gitlab/legacy_github_import/client.rb b/lib/gitlab/legacy_github_import/client.rb index 53c910d44bd..d8ed0ebca9d 100644 --- a/lib/gitlab/legacy_github_import/client.rb +++ b/lib/gitlab/legacy_github_import/client.rb @@ -83,7 +83,7 @@ module Gitlab end def config - Gitlab.config.omniauth.providers.find { |provider| provider.name == "github" } + Gitlab::Auth::OAuth::Provider.config_for('github') end def github_options diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb index 757ef71b95a..1e45d074e0a 100644 --- a/lib/gitlab/search_results.rb +++ b/lib/gitlab/search_results.rb @@ -7,8 +7,8 @@ module Gitlab def initialize(opts = {}) @id = opts.fetch(:id, nil) - @filename = opts.fetch(:filename, nil) - @basename = opts.fetch(:basename, nil) + @filename = encode_utf8(opts.fetch(:filename, nil)) + @basename = encode_utf8(opts.fetch(:basename, nil)) @ref = opts.fetch(:ref, nil) @startline = opts.fetch(:startline, nil) @data = encode_utf8(opts.fetch(:data, nil)) diff --git a/lib/google_api/auth.rb b/lib/google_api/auth.rb index 99a82c849e0..1aeaa387a49 100644 --- a/lib/google_api/auth.rb +++ b/lib/google_api/auth.rb @@ -32,7 +32,7 @@ module GoogleApi private def config - Gitlab.config.omniauth.providers.find { |provider| provider.name == "google_oauth2" } + Gitlab::Auth::OAuth::Provider.config_for('google_oauth2') end def client diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 3f05e878cc8..a04f869f2bb 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-03-06 17:36+0100\n" -"PO-Revision-Date: 2018-03-06 17:36+0100\n" +"POT-Creation-Date: 2018-03-12 19:50+0100\n" +"PO-Revision-Date: 2018-03-12 19:50+0100\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "Language: \n" @@ -28,6 +28,11 @@ msgid_plural "%d commits behind" msgstr[0] "" msgstr[1] "" +msgid "%d exporter" +msgid_plural "%d exporters" +msgstr[0] "" +msgstr[1] "" + msgid "%d issue" msgid_plural "%d issues" msgstr[0] "" @@ -43,6 +48,11 @@ msgid_plural "%d merge requests" msgstr[0] "" msgstr[1] "" +msgid "%d metric" +msgid_plural "%d metrics" +msgstr[0] "" +msgstr[1] "" + msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "%s additional commits have been omitted to prevent performance issues." msgstr[0] "" @@ -102,6 +112,9 @@ msgstr "" msgid "2FA enabled" msgstr "" +msgid "<strong>Removes</strong> source branch" +msgstr "" + msgid "A collection of graphs regarding Continuous Integration" msgstr "" @@ -111,6 +124,9 @@ msgstr "" msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}." msgstr "" +msgid "A user with write access to the source branch selected this option" +msgstr "" + msgid "About auto deploy" msgstr "" @@ -213,7 +229,7 @@ msgstr "" msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings." msgstr "" -msgid "Allow edits from maintainers" +msgid "Allow edits from maintainers." msgstr "" msgid "Allows you to add and manage Kubernetes clusters." @@ -857,6 +873,9 @@ msgstr "" msgid "ClusterIntegration|Learn more about environments" msgstr "" +msgid "ClusterIntegration|Learn more about security configuration" +msgstr "" + msgid "ClusterIntegration|Machine type" msgstr "" @@ -914,6 +933,9 @@ msgstr "" msgid "ClusterIntegration|Save changes" msgstr "" +msgid "ClusterIntegration|Security" +msgstr "" + msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster" msgstr "" @@ -941,6 +963,9 @@ msgstr "" msgid "ClusterIntegration|Something went wrong while installing %{title}" msgstr "" +msgid "ClusterIntegration|The default cluster configuration grants access to a wide set of functionalities needed to successfully build and deploy a containerised application." +msgstr "" + msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below" msgstr "" @@ -1182,6 +1207,9 @@ msgstr "" msgid "Create empty bare repository" msgstr "" +msgid "Create group label" +msgstr "" + msgid "Create lists from labels. Issues with that label appear in that list." msgstr "" @@ -1197,6 +1225,9 @@ msgstr "" msgid "Create new..." msgstr "" +msgid "Create project label" +msgstr "" + msgid "CreateNewFork|Fork" msgstr "" @@ -1776,9 +1807,18 @@ msgstr "" msgid "Labels" msgstr "" +msgid "Labels can be applied to %{features}. Group labels are available for any project within the group." +msgstr "" + msgid "Labels can be applied to issues and merge requests to categorize them." msgstr "" +msgid "Labels|Promote Label" +msgstr "" + +msgid "Labels|Promote label %{labelTitle} to Group Label?" +msgstr "" + msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "" @@ -1850,9 +1890,15 @@ msgstr "" msgid "Login" msgstr "" +msgid "Manage group labels" +msgstr "" + msgid "Manage labels" msgstr "" +msgid "Manage project labels" +msgstr "" + msgid "Mar" msgstr "" @@ -1907,6 +1953,12 @@ msgstr "" msgid "Milestones|Milestone %{milestoneTitle} was not found" msgstr "" +msgid "Milestones|Promote %{milestoneTitle} to group milestone?" +msgstr "" + +msgid "Milestones|Promote Milestone" +msgstr "" + msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "" @@ -2002,6 +2054,9 @@ msgstr "" msgid "No file chosen" msgstr "" +msgid "No labels created yet." +msgstr "" + msgid "No repository" msgstr "" @@ -2251,9 +2306,15 @@ msgstr "" msgid "Pipelines|Loading Pipelines" msgstr "" +msgid "Pipelines|Project cache successfully reset." +msgstr "" + msgid "Pipelines|Run Pipeline" msgstr "" +msgid "Pipelines|Something went wrong while cleaning runners cache." +msgstr "" + msgid "Pipelines|There are currently no %{scope} pipelines." msgstr "" @@ -2380,9 +2441,6 @@ msgstr "" msgid "Project avatar in repository: %{link}" msgstr "" -msgid "Project cache successfully reset." -msgstr "" - msgid "Project details" msgstr "" @@ -2446,6 +2504,12 @@ msgstr "" msgid "ProjectsDropdown|This feature requires browser localStorage support" msgstr "" +msgid "PrometheusService|%{exporters} with %{metrics} were found" +msgstr "" + +msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">common metrics</a> were found</p>" +msgstr "" + msgid "PrometheusService|Active" msgstr "" @@ -2458,6 +2522,9 @@ msgstr "" msgid "PrometheusService|By default, Prometheus listens on ‘http://localhost:9090’. It’s not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server." msgstr "" +msgid "PrometheusService|Common metrics" +msgstr "" + msgid "PrometheusService|Finding and configuring metrics..." msgstr "" @@ -2479,15 +2546,9 @@ msgstr "" msgid "PrometheusService|Missing environment variable" msgstr "" -msgid "PrometheusService|Monitored" -msgstr "" - msgid "PrometheusService|More information" msgstr "" -msgid "PrometheusService|No metrics are being monitored. To start monitoring, deploy to an environment." -msgstr "" - msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "" @@ -2503,7 +2564,16 @@ msgstr "" msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below" msgstr "" -msgid "PrometheusService|View environments" +msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics" +msgstr "" + +msgid "Promote" +msgstr "" + +msgid "Promote to Group Label" +msgstr "" + +msgid "Promote to Group Milestone" msgstr "" msgid "Protip:" @@ -2515,9 +2585,6 @@ msgstr "" msgid "Public - The project can be accessed without any authentication." msgstr "" -msgid "Push access to this project is necessary in order to enable this option" -msgstr "" - msgid "Push events" msgstr "" @@ -3338,9 +3405,6 @@ msgstr "" msgid "Trigger this manual action" msgstr "" -msgid "Unable to reset project cache." -msgstr "" - msgid "Unlock" msgstr "" @@ -3383,12 +3447,18 @@ msgstr "" msgid "View file @ " msgstr "" +msgid "View group labels" +msgstr "" + msgid "View labels" msgstr "" msgid "View open merge request" msgstr "" +msgid "View project labels" +msgstr "" + msgid "View replaced file @ " msgstr "" diff --git a/package.json b/package.json index 6549da99f97..472bdbebda8 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js" }, "dependencies": { - "@gitlab-org/gitlab-svgs": "^1.13.0", + "@gitlab-org/gitlab-svgs": "^1.14.0", "autosize": "^4.0.0", "axios": "^0.17.1", "babel-core": "^6.26.0", diff --git a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb index 890774922aa..db92a3504f3 100644 --- a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb +++ b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb @@ -125,6 +125,12 @@ describe 'Merge request > User merges when pipeline succeeds', :js do expect(page).to have_content "canceled the automatic merge" end + it 'allows to remove source branch' do + click_link "Remove source branch" + + expect(page).to have_content "The source branch will be removed" + end + context 'when pipeline succeeds' do before do build.success diff --git a/spec/helpers/import_helper_spec.rb b/spec/helpers/import_helper_spec.rb index 9afff47f4e9..57d843c1be2 100644 --- a/spec/helpers/import_helper_spec.rb +++ b/spec/helpers/import_helper_spec.rb @@ -27,25 +27,48 @@ describe ImportHelper do describe '#provider_project_link' do context 'when provider is "github"' do + let(:github_server_url) { nil } + + before do + setting = Settingslogic.new('name' => 'github') + setting['url'] = github_server_url if github_server_url + + allow(Gitlab.config.omniauth).to receive(:providers).and_return([setting]) + end + context 'when provider does not specify a custom URL' do it 'uses default GitHub URL' do - allow(Gitlab.config.omniauth).to receive(:providers) - .and_return([Settingslogic.new('name' => 'github')]) - expect(helper.provider_project_link('github', 'octocat/Hello-World')) .to include('href="https://github.com/octocat/Hello-World"') end end context 'when provider specify a custom URL' do + let(:github_server_url) { 'https://github.company.com' } + it 'uses custom URL' do - allow(Gitlab.config.omniauth).to receive(:providers) - .and_return([Settingslogic.new('name' => 'github', 'url' => 'https://github.company.com')]) + expect(helper.provider_project_link('github', 'octocat/Hello-World')) + .to include('href="https://github.company.com/octocat/Hello-World"') + end + end + + context "when custom URL contains a '/' char at the end" do + let(:github_server_url) { 'https://github.company.com/' } + it "doesn't render double slash" do expect(helper.provider_project_link('github', 'octocat/Hello-World')) .to include('href="https://github.company.com/octocat/Hello-World"') end end + + context 'when provider is missing' do + it 'uses the default URL' do + allow(Gitlab.config.omniauth).to receive(:providers).and_return([]) + + expect(helper.provider_project_link('github', 'octocat/Hello-World')) + .to include('href="https://github.com/octocat/Hello-World"') + end + end end context 'when provider is "gitea"' do diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js index bf60cb12f52..5be13ed0dfe 100644 --- a/spec/javascripts/notes/mock_data.js +++ b/spec/javascripts/notes/mock_data.js @@ -1,7 +1,7 @@ /* eslint-disable */ export const notesDataMock = { discussionsPath: '/gitlab-org/gitlab-ce/issues/26/discussions.json', - lastFetchedAt: '1501862675', + lastFetchedAt: 1501862675, markdownDocsPath: '/help/user/markdown', newSessionPath: '/users/sign_in?redirect_to_referer=yes', notesPath: '/gitlab-org/gitlab-ce/noteable/issue/98/notes', diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js index 0f092810574..91249b2c79e 100644 --- a/spec/javascripts/notes/stores/actions_spec.js +++ b/spec/javascripts/notes/stores/actions_spec.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import _ from 'underscore'; +import { headersInterceptor } from 'spec/helpers/vue_resource_helper'; import * as actions from '~/notes/stores/actions'; import store from '~/notes/stores'; import testAction from '../../helpers/vuex_action_helper'; @@ -145,4 +146,68 @@ describe('Actions Notes Store', () => { ], done); }); }); + + describe('poll', () => { + beforeEach((done) => { + jasmine.clock().install(); + + spyOn(Vue.http, 'get').and.callThrough(); + + store.dispatch('setNotesData', notesDataMock) + .then(done) + .catch(done.fail); + }); + + afterEach(() => { + jasmine.clock().uninstall(); + }); + + it('calls service with last fetched state', (done) => { + const interceptor = (request, next) => { + next(request.respondWith(JSON.stringify({ + notes: [], + last_fetched_at: '123456', + }), { + status: 200, + headers: { + 'poll-interval': '1000', + }, + })); + }; + + Vue.http.interceptors.push(interceptor); + Vue.http.interceptors.push(headersInterceptor); + + store.dispatch('poll') + .then(() => new Promise(resolve => requestAnimationFrame(resolve))) + .then(() => { + expect(Vue.http.get).toHaveBeenCalledWith(jasmine.anything(), { + url: jasmine.anything(), + method: 'get', + headers: { + 'X-Last-Fetched-At': undefined, + }, + }); + expect(store.state.lastFetchedAt).toBe('123456'); + + jasmine.clock().tick(1500); + }) + .then(() => new Promise((resolve) => { + requestAnimationFrame(resolve); + })) + .then(() => { + expect(Vue.http.get.calls.count()).toBe(2); + expect(Vue.http.get.calls.mostRecent().args[1].headers).toEqual({ + 'X-Last-Fetched-At': '123456', + }); + }) + .then(() => store.dispatch('stopPolling')) + .then(() => { + Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor); + Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor); + }) + .then(done) + .catch(done.fail); + }); + }); }); diff --git a/spec/javascripts/notes/stores/mutation_spec.js b/spec/javascripts/notes/stores/mutation_spec.js index 2627f721d9d..98f101d6bc5 100644 --- a/spec/javascripts/notes/stores/mutation_spec.js +++ b/spec/javascripts/notes/stores/mutation_spec.js @@ -101,10 +101,21 @@ describe('Notes Store mutations', () => { const state = { notes: [], }; + const legacyNote = { + id: 2, + individual_note: true, + notes: [{ + note: '1', + }, { + note: '2', + }], + }; - mutations.SET_INITIAL_NOTES(state, [note]); + mutations.SET_INITIAL_NOTES(state, [note, legacyNote]); expect(state.notes[0].id).toEqual(note.id); - expect(state.notes.length).toEqual(1); + expect(state.notes[1].notes[0].note).toBe(legacyNote.notes[0].note); + expect(state.notes[2].notes[0].note).toBe(legacyNote.notes[1].note); + expect(state.notes.length).toEqual(3); }); }); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js index dd907ad9015..d47815a5b5a 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import mwpsComponent from '~/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue'; +import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service'; import eventHub from '~/vue_merge_request_widget/event_hub'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; @@ -25,12 +26,7 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => { targetBranchPath, targetBranch, }, - service: { - cancelAutomaticMerge() {}, - mergeResource: { - save() {}, - }, - }, + service: new MRWidgetService({}), }); }); @@ -90,18 +86,16 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => { describe('removeSourceBranch', () => { it('should set flag and call service then request main component to update the widget', (done) => { - spyOn(vm.service.mergeResource, 'save').and.returnValue(new Promise((resolve) => { - resolve({ - data: { - status: 'merge_when_pipeline_succeeds', - }, - }); + spyOn(vm.service, 'merge').and.returnValue(Promise.resolve({ + data: { + status: 'merge_when_pipeline_succeeds', + }, })); vm.removeSourceBranch(); setTimeout(() => { expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); - expect(vm.service.mergeResource.save).toHaveBeenCalledWith({ + expect(vm.service.merge).toHaveBeenCalledWith({ sha, merge_when_pipeline_succeeds: true, should_remove_source_branch: true, diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js index a8a02fa6b66..2a762c9336e 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js @@ -1,9 +1,9 @@ import Vue from 'vue'; -import nothingToMergeComponent from '~/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge'; +import NothingToMerge from '~/vue_merge_request_widget/components/states/nothing_to_merge.vue'; -describe('MRWidgetNothingToMerge', () => { +describe('NothingToMerge', () => { describe('template', () => { - const Component = Vue.extend(nothingToMergeComponent); + const Component = Vue.extend(NothingToMerge); const newBlobPath = '/foo'; const vm = new Component({ el: document.createElement('div'), diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js index 3e310980ffa..32876ca66ac 100644 --- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js +++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js @@ -82,6 +82,10 @@ describe('mrWidgetOptions', () => { }); describe('shouldRenderSourceBranchRemovalStatus', () => { + beforeEach(() => { + vm.mr.state = 'readyToMerge'; + }); + it('should return true when cannot remove source branch and branch will be removed', () => { vm.mr.canRemoveSourceBranch = false; vm.mr.shouldRemoveSourceBranch = true; @@ -102,6 +106,22 @@ describe('mrWidgetOptions', () => { expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false); }); + + it('should return false when in merged state', () => { + vm.mr.canRemoveSourceBranch = false; + vm.mr.shouldRemoveSourceBranch = true; + vm.mr.state = 'merged'; + + expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false); + }); + + it('should return false when in nothing to merge state', () => { + vm.mr.canRemoveSourceBranch = false; + vm.mr.shouldRemoveSourceBranch = true; + vm.mr.state = 'nothingToMerge'; + + expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false); + }); }); describe('shouldRenderDeployments', () => { @@ -407,6 +427,7 @@ describe('mrWidgetOptions', () => { it('renders when user cannot remove branch and branch should be removed', (done) => { vm.mr.canRemoveSourceBranch = false; vm.mr.shouldRemoveSourceBranch = true; + vm.mr.state = 'readyToMerge'; vm.$nextTick(() => { const tooltip = vm.$el.querySelector('.fa-question-circle'); @@ -419,5 +440,18 @@ describe('mrWidgetOptions', () => { done(); }); }); + + it('does not render in merged state', (done) => { + vm.mr.canRemoveSourceBranch = false; + vm.mr.shouldRemoveSourceBranch = true; + vm.mr.state = 'merged'; + + vm.$nextTick(() => { + expect(vm.$el.textContent).toContain('The source branch has been removed'); + expect(vm.$el.textContent).not.toContain('Removes source branch'); + + done(); + }); + }); }); }); diff --git a/spec/lib/api/helpers/related_resources_helpers_spec.rb b/spec/lib/api/helpers/related_resources_helpers_spec.rb new file mode 100644 index 00000000000..b918301f1cb --- /dev/null +++ b/spec/lib/api/helpers/related_resources_helpers_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe API::Helpers::RelatedResourcesHelpers do + subject(:helpers) do + Class.new.include(described_class).new + end + + describe '#expose_url' do + let(:path) { '/api/v4/awesome_endpoint' } + subject(:url) { helpers.expose_url(path) } + + def stub_default_url_options(protocol: 'http', host: 'example.com', port: nil) + expect(Gitlab::Application.routes).to receive(:default_url_options) + .and_return(protocol: protocol, host: host, port: port) + end + + it 'respects the protocol if it is HTTP' do + stub_default_url_options(protocol: 'http') + + is_expected.to start_with('http://') + end + + it 'respects the protocol if it is HTTPS' do + stub_default_url_options(protocol: 'https') + + is_expected.to start_with('https://') + end + + it 'accepts port to be nil' do + stub_default_url_options(port: nil) + + is_expected.to start_with('http://example.com/') + end + + it 'includes port if provided' do + stub_default_url_options(port: 8080) + + is_expected.to start_with('http://example.com:8080/') + end + end +end diff --git a/spec/lib/gitlab/profiler_spec.rb b/spec/lib/gitlab/profiler_spec.rb index f02b1cf55fb..3d5b56cd5b8 100644 --- a/spec/lib/gitlab/profiler_spec.rb +++ b/spec/lib/gitlab/profiler_spec.rb @@ -94,10 +94,12 @@ describe Gitlab::Profiler do it 'strips out the private token' do expect(custom_logger).to receive(:add) do |severity, _progname, message| + next if message.include?('spec/') + expect(severity).to eq(Logger::DEBUG) expect(message).to include('public').and include(described_class::FILTERED_STRING) expect(message).not_to include(private_token) - end + end.twice custom_logger.debug("public #{private_token}") end diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb index c46bb8edebf..57905a74e92 100644 --- a/spec/lib/gitlab/project_search_results_spec.rb +++ b/spec/lib/gitlab/project_search_results_spec.rb @@ -108,14 +108,26 @@ describe Gitlab::ProjectSearchResults do context 'when the search returns non-ASCII data' do context 'with UTF-8' do - let(:results) { project.repository.search_files_by_content("файл", 'master') } + let(:results) { project.repository.search_files_by_content('файл', 'master') } it 'returns results as UTF-8' do expect(subject.filename).to eq('encoding/russian.rb') expect(subject.basename).to eq('encoding/russian') expect(subject.ref).to eq('master') expect(subject.startline).to eq(1) - expect(subject.data).to eq("Хороший файл") + expect(subject.data).to eq('Хороший файл') + end + end + + context 'with UTF-8 in the filename' do + let(:results) { project.repository.search_files_by_content('webhook', 'master') } + + it 'returns results as UTF-8' do + expect(subject.filename).to eq('encoding/テスト.txt') + expect(subject.basename).to eq('encoding/テスト') + expect(subject.ref).to eq('master') + expect(subject.startline).to eq(3) + expect(subject.data).to include('WebHookの確認') end end diff --git a/spec/models/compare_spec.rb b/spec/models/compare_spec.rb index 04f3cecae00..8e88bb81162 100644 --- a/spec/models/compare_spec.rb +++ b/spec/models/compare_spec.rb @@ -37,33 +37,51 @@ describe Compare do end end - describe '#base_commit' do - let(:base_commit) { Commit.new(another_sample_commit, project) } + describe '#base_commit_sha' do + it 'returns @base_sha if it is present' do + expect(project).not_to receive(:merge_base_commit) - it 'returns project merge base commit' do - expect(project).to receive(:merge_base_commit).with(start_commit.id, head_commit.id).and_return(base_commit) + sha = double + service = described_class.new(raw_compare, project, base_sha: sha) - expect(subject.base_commit).to eq(base_commit) + expect(service.base_commit_sha).to eq(sha) + end + + it 'fetches merge base SHA from repo when @base_sha is nil' do + expect(project).to receive(:merge_base_commit) + .with(start_commit.id, head_commit.id) + .once + .and_call_original + + expect(subject.base_commit_sha) + .to eq(project.repository.merge_base(start_commit.id, head_commit.id)) + end + + it 'is memoized on first call' do + expect(project).to receive(:merge_base_commit) + .with(start_commit.id, head_commit.id) + .once + .and_call_original + + 3.times { subject.base_commit_sha } end it 'returns nil if there is no start_commit' do expect(subject).to receive(:start_commit).and_return(nil) - expect(subject.base_commit).to eq(nil) + expect(subject.base_commit_sha).to eq(nil) end it 'returns nil if there is no head commit' do expect(subject).to receive(:head_commit).and_return(nil) - expect(subject.base_commit).to eq(nil) + expect(subject.base_commit_sha).to eq(nil) end end describe '#diff_refs' do - it 'uses base_commit sha as base_sha' do - expect(subject).to receive(:base_commit).at_least(:once).and_call_original - - expect(subject.diff_refs.base_sha).to eq(subject.base_commit.id) + it 'uses base_commit_sha sha as base_sha' do + expect(subject.diff_refs.base_sha).to eq(subject.base_commit_sha) end it 'uses start_commit sha as start_sha' do diff --git a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml index c233e255fe0..33f9efc1490 100644 --- a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml @@ -137,7 +137,7 @@ sast:container: dast: stage: dast allow_failure: true - image: owasp/zap2docker-stable + image: registry.gitlab.com/gitlab-org/security-products/zaproxy variables: POSTGRES_DB: "false" script: diff --git a/yarn.lock b/yarn.lock index a2eee3a547d..adbb37bea72 100644 --- a/yarn.lock +++ b/yarn.lock @@ -54,9 +54,9 @@ lodash "^4.2.0" to-fast-properties "^2.0.0" -"@gitlab-org/gitlab-svgs@^1.13.0": - version "1.13.0" - resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.13.0.tgz#9e856ef9fa7bbe49b2dce9789187a89e11311215" +"@gitlab-org/gitlab-svgs@^1.14.0": + version "1.14.0" + resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.14.0.tgz#b4a5cca3106f33224c5486cf674ba3b70cee727e" "@types/jquery@^2.0.40": version "2.0.48" |