diff options
46 files changed, 403 insertions, 111 deletions
diff --git a/.gitlab/merge_request_templates/Documentation.md b/.gitlab/merge_request_templates/Documentation.md index 9b541aadad1..102eb7e7953 100644 --- a/.gitlab/merge_request_templates/Documentation.md +++ b/.gitlab/merge_request_templates/Documentation.md @@ -11,4 +11,6 @@ See the guidelines: http://docs.gitlab.com/ce/development/doc_styleguide.html#ch - [ ] Make sure the old link is not removed and has its contents replaced with a link to the new location. - [ ] Make sure internal links pointing to the document in question are not broken. - [ ] Search and replace any links referring to old docs in GitLab Rails app, specifically under the `app/views/` directory. +- [ ] Make sure to add [`redirect_from`](https://docs.gitlab.com/ee/development/doc_styleguide.html#redirections-for-pages-with-disqus-comments) to the new document if there are any Disqus comments on the old document thread. - [ ] If working on CE, submit an MR to EE with the changes as well. +- [ ] Ping one of the technical writers for review. diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index afa1bc19790..0cd2d5f6fef 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -54,7 +54,6 @@ import ShortcutsIssuable from './shortcuts_issuable'; import U2FAuthenticate from './u2f/authenticate'; import Members from './members'; import memberExpirationDate from './member_expiration_date'; -import DueDateSelectors from './due_date_select'; import Diff from './diff'; import ProjectLabelSubscription from './project_label_subscription'; import SearchAutocomplete from './search_autocomplete'; @@ -185,18 +184,28 @@ import { fetchCommitMergeRequests } from './commit_merge_requests'; .catch(fail); break; case 'projects:milestones:new': + case 'projects:milestones:create': + import('./pages/projects/milestones/new') + .then(callDefault) + .catch(fail); + break; case 'projects:milestones:edit': case 'projects:milestones:update': - new ZenMode(); - new DueDateSelectors(); - new GLForm($('.milestone-form'), true); + import('./pages/projects/milestones/edit') + .then(callDefault) + .catch(fail); break; case 'groups:milestones:new': + case 'groups:milestones:create': + import('./pages/groups/milestones/new') + .then(callDefault) + .catch(fail); + break; case 'groups:milestones:edit': case 'groups:milestones:update': - new ZenMode(); - new DueDateSelectors(); - new GLForm($('.milestone-form'), false); + import('./pages/groups/milestones/edit') + .then(callDefault) + .catch(fail); break; case 'projects:compare:show': new Diff(); diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js index cb3cdea8111..e26bf437efc 100644 --- a/app/assets/javascripts/merge_request.js +++ b/app/assets/javascripts/merge_request.js @@ -110,7 +110,7 @@ MergeRequest.prototype.initCommitMessageListeners = function() { }); }; -MergeRequest.prototype.updateStatusText = function(classToRemove, classToAdd, newStatusText) { +MergeRequest.updateStatusText = function(classToRemove, classToAdd, newStatusText) { $('.detail-page-header .status-box') .removeClass(classToRemove) .addClass(classToAdd) @@ -118,14 +118,14 @@ MergeRequest.prototype.updateStatusText = function(classToRemove, classToAdd, ne .text(newStatusText); }; -MergeRequest.prototype.decreaseCounter = function(by = 1) { - const $el = $('.nav-links .js-merge-counter'); +MergeRequest.decreaseCounter = function(by = 1) { + const $el = $('.js-merge-counter'); const count = Math.max((parseInt($el.text().replace(/[^\d]/, ''), 10) - by), 0); $el.text(addDelimiter(count)); }; -MergeRequest.prototype.hideCloseButton = function() { +MergeRequest.hideCloseButton = function() { const el = document.querySelector('.merge-request .js-issuable-actions'); const closeDropdownItem = el.querySelector('li.close-item'); if (closeDropdownItem) { diff --git a/app/assets/javascripts/pages/groups/milestones/edit/index.js b/app/assets/javascripts/pages/groups/milestones/edit/index.js new file mode 100644 index 00000000000..5c99c90e24d --- /dev/null +++ b/app/assets/javascripts/pages/groups/milestones/edit/index.js @@ -0,0 +1,3 @@ +import initForm from '../../../../shared/milestones/form'; + +export default () => initForm(false); diff --git a/app/assets/javascripts/pages/groups/milestones/new/index.js b/app/assets/javascripts/pages/groups/milestones/new/index.js new file mode 100644 index 00000000000..5c99c90e24d --- /dev/null +++ b/app/assets/javascripts/pages/groups/milestones/new/index.js @@ -0,0 +1,3 @@ +import initForm from '../../../../shared/milestones/form'; + +export default () => initForm(false); diff --git a/app/assets/javascripts/pages/projects/milestones/edit/index.js b/app/assets/javascripts/pages/projects/milestones/edit/index.js new file mode 100644 index 00000000000..10e3979a36e --- /dev/null +++ b/app/assets/javascripts/pages/projects/milestones/edit/index.js @@ -0,0 +1,3 @@ +import initForm from '../../../../shared/milestones/form'; + +export default () => initForm(); diff --git a/app/assets/javascripts/pages/projects/milestones/new/index.js b/app/assets/javascripts/pages/projects/milestones/new/index.js new file mode 100644 index 00000000000..10e3979a36e --- /dev/null +++ b/app/assets/javascripts/pages/projects/milestones/new/index.js @@ -0,0 +1,3 @@ +import initForm from '../../../../shared/milestones/form'; + +export default () => initForm(); diff --git a/app/assets/javascripts/shared/milestones/form.js b/app/assets/javascripts/shared/milestones/form.js new file mode 100644 index 00000000000..db466f722c4 --- /dev/null +++ b/app/assets/javascripts/shared/milestones/form.js @@ -0,0 +1,9 @@ +import ZenMode from '../../zen_mode'; +import DueDateSelectors from '../../due_date_select'; +import GLForm from '../../gl_form'; + +export default (initGFM = true) => { + new ZenMode(); // eslint-disable-line no-new + new DueDateSelectors(); // eslint-disable-line no-new + new GLForm($('.milestone-form'), initGFM); // eslint-disable-line no-new +}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js index e82fb979162..60f42c46ffe 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js @@ -1,6 +1,7 @@ import successSvg from 'icons/_icon_status_success.svg'; import warningSvg from 'icons/_icon_status_warning.svg'; import simplePoll from '~/lib/utils/simple_poll'; +import MergeRequest from '../../../merge_request'; import Flash from '../../../flash'; import statusIcon from '../mr_widget_status_icon'; import eventHub from '../../event_hub'; @@ -165,11 +166,9 @@ export default { // If state is merged we should update the widget and stop the polling eventHub.$emit('MRWidgetUpdateRequested'); eventHub.$emit('FetchActionsContent'); - if (window.mergeRequest) { - window.mergeRequest.updateStatusText('status-box-open', 'status-box-merged', 'Merged'); - window.mergeRequest.hideCloseButton(); - window.mergeRequest.decreaseCounter(); - } + MergeRequest.updateStatusText('status-box-open', 'status-box-merged', 'Merged'); + MergeRequest.hideCloseButton(); + MergeRequest.decreaseCounter(); stopPolling(); // If user checked remove source branch and we didn't remove the branch yet diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb index 3d2926d5d75..0df80fa700f 100644 --- a/app/controllers/projects/merge_requests/creations_controller.rb +++ b/app/controllers/projects/merge_requests/creations_controller.rb @@ -43,11 +43,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap end def diffs - @diffs = if @merge_request.can_be_created - @merge_request.diffs(diff_options) - else - [] - end + @diffs = @merge_request.diffs(diff_options) if @merge_request.can_be_created @diff_notes_disabled = true diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 6012dbba1b9..df67fb243ad 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -8,6 +8,7 @@ module Ci MissingDependenciesError = Class.new(StandardError) + belongs_to :project, inverse_of: :builds belongs_to :runner belongs_to :trigger_request belongs_to :erased_by, class_name: 'User' diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index d4690da3be6..d7153d7b816 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -7,7 +7,7 @@ module Ci include Presentable include Gitlab::OptimisticLocking - belongs_to :project + belongs_to :project, inverse_of: :pipelines belongs_to :user belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline' belongs_to :pipeline_schedule, class_name: 'Ci::PipelineSchedule' diff --git a/app/models/commit.rb b/app/models/commit.rb index 21904c87f01..2d2d89af030 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -372,19 +372,19 @@ class Commit # uri_type('doc/README.md') # => :blob # uri_type('doc/logo.png') # => :raw # uri_type('doc/api') # => :tree - # uri_type('not/found') # => :nil + # uri_type('not/found') # => nil # # Returns a symbol def uri_type(path) - entry = @raw.rugged_tree_entry(path) + entry = @raw.tree_entry(path) + return unless entry + if entry[:type] == :blob blob = ::Blob.decorate(Gitlab::Git::Blob.new(name: entry[:name]), @project) blob.image? || blob.video? ? :raw : :blob else entry[:type] end - rescue Rugged::TreeError - nil end def raw_diffs(*args) diff --git a/app/models/project.rb b/app/models/project.rb index 7ab7df4fdcd..d011b614c69 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -199,13 +199,13 @@ class Project < ActiveRecord::Base has_many :container_repositories, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :commit_statuses - has_many :pipelines, class_name: 'Ci::Pipeline' + has_many :pipelines, class_name: 'Ci::Pipeline', inverse_of: :project # Ci::Build objects store data on the file system such as artifact files and # build traces. Currently there's no efficient way of removing this data in # bulk that doesn't involve loading the rows into memory. As a result we're # still using `dependent: :destroy` here. - has_many :builds, class_name: 'Ci::Build', dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :builds, class_name: 'Ci::Build', inverse_of: :project, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :build_trace_section_names, class_name: 'Ci::BuildTraceSectionName' has_many :runner_projects, class_name: 'Ci::RunnerProject' has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner' diff --git a/app/models/repository.rb b/app/models/repository.rb index 2ffd9558ebc..a67bb7294e6 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -938,9 +938,11 @@ class Repository end def search_files_by_name(query, ref) - return [] if empty? || query.blank? + safe_query = Regexp.escape(query.sub(/^\/*/, "")) + + return [] if empty? || safe_query.blank? - args = %W(ls-tree --full-tree -r #{ref || root_ref} --name-status | #{Regexp.escape(query)}) + args = %W(ls-tree --full-tree -r #{ref || root_ref} --name-status | #{safe_query}) run_git(args).first.lines.map(&:strip) end diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 30a5aab13bf..06b23cd7076 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -68,21 +68,14 @@ module SystemNoteService # # Returns the created Note object def change_issue_assignees(issue, project, author, old_assignees) - body = - if issue.assignees.any? && old_assignees.any? - unassigned_users = old_assignees - issue.assignees - added_users = issue.assignees.to_a - old_assignees - - text_parts = [] - text_parts << "assigned to #{added_users.map(&:to_reference).to_sentence}" if added_users.any? - text_parts << "unassigned #{unassigned_users.map(&:to_reference).to_sentence}" if unassigned_users.any? - - text_parts.join(' and ') - elsif old_assignees.any? - "removed assignee" - elsif issue.assignees.any? - "assigned to #{issue.assignees.map(&:to_reference).to_sentence}" - end + unassigned_users = old_assignees - issue.assignees + added_users = issue.assignees.to_a - old_assignees + + text_parts = [] + text_parts << "assigned to #{added_users.map(&:to_reference).to_sentence}" if added_users.any? + text_parts << "unassigned #{unassigned_users.map(&:to_reference).to_sentence}" if unassigned_users.any? + + body = text_parts.join(' and ') create_note(NoteSummary.new(issue, project, author, body, action: 'assignee')) end diff --git a/app/views/dashboard/_activity_head.html.haml b/app/views/dashboard/_activity_head.html.haml index ecdf76ef5c5..7a3f3667ac1 100644 --- a/app/views/dashboard/_activity_head.html.haml +++ b/app/views/dashboard/_activity_head.html.haml @@ -2,7 +2,7 @@ %ul.nav-links %li{ class: active_when(params[:filter].nil?) }> = link_to activity_dashboard_path, class: 'shortcuts-activity', data: {placement: 'right'} do - Your Projects + Your projects %li{ class: active_when(params[:filter] == 'starred') }> = link_to activity_dashboard_path(filter: 'starred'), data: {placement: 'right'} do - Starred Projects + Starred projects diff --git a/app/views/dashboard/_snippets_head.html.haml b/app/views/dashboard/_snippets_head.html.haml index 7330f4cb523..a9488df07bd 100644 --- a/app/views/dashboard/_snippets_head.html.haml +++ b/app/views/dashboard/_snippets_head.html.haml @@ -2,10 +2,10 @@ %ul.nav-links = nav_link(page: dashboard_snippets_path, html_options: {class: 'home'}) do = link_to dashboard_snippets_path, title: 'Your snippets', data: {placement: 'right'} do - Your Snippets + Your snippets = nav_link(page: explore_snippets_path) do = link_to explore_snippets_path, title: 'Explore snippets', data: {placement: 'right'} do - Explore Snippets + Explore snippets - if current_user .nav-controls.hidden-xs diff --git a/app/views/profiles/gpg_keys/index.html.haml b/app/views/profiles/gpg_keys/index.html.haml index 86ebec0179c..e44506ec9c9 100644 --- a/app/views/profiles/gpg_keys/index.html.haml +++ b/app/views/profiles/gpg_keys/index.html.haml @@ -3,12 +3,12 @@ = render 'profiles/head' .row.prepend-top-default - .col-lg-3.profile-settings-sidebar + .col-lg-4.profile-settings-sidebar %h4.prepend-top-0 = page_title %p GPG keys allow you to verify signed commits. - .col-lg-9 + .col-lg-8 %h5.prepend-top-0 Add a GPG key %p.profile-settings-content diff --git a/app/views/projects/merge_requests/creations/_diffs.html.haml b/app/views/projects/merge_requests/creations/_diffs.html.haml index 627fc4e9671..5b70e894b39 100644 --- a/app/views/projects/merge_requests/creations/_diffs.html.haml +++ b/app/views/projects/merge_requests/creations/_diffs.html.haml @@ -1 +1,5 @@ -= render "projects/diffs/diffs", diffs: @diffs, environment: @environment, show_whitespace_toggle: false +- if @merge_request.can_be_created + = render "projects/diffs/diffs", diffs: @diffs, environment: @environment, show_whitespace_toggle: false +- else + .nothing-here-block + This merge request cannot be created. diff --git a/app/views/projects/runners/_shared_runners.html.haml b/app/views/projects/runners/_shared_runners.html.haml index a4e820628f3..67607e4e9c6 100644 --- a/app/views/projects/runners/_shared_runners.html.haml +++ b/app/views/projects/runners/_shared_runners.html.haml @@ -23,6 +23,3 @@ %h4.underlined-title Available shared Runners : #{@shared_runners_count} %ul.bordered-list.available-shared-runners = render partial: 'projects/runners/runner', collection: @shared_runners, as: :runner - - if @shared_runners_count > 10 - .light - and #{@shared_runners_count - 10} more... diff --git a/changelogs/unreleased/16301-update-removed-assignee-note-to-include-old-assignee-reference.yml b/changelogs/unreleased/16301-update-removed-assignee-note-to-include-old-assignee-reference.yml new file mode 100644 index 00000000000..e94b4f8bb26 --- /dev/null +++ b/changelogs/unreleased/16301-update-removed-assignee-note-to-include-old-assignee-reference.yml @@ -0,0 +1,5 @@ +--- +title: "Update 'removed assignee' note to include old assignee reference" +merge_request: 16301 +author: Maurizio De Santis +type: changed diff --git a/changelogs/unreleased/36571-ignore-root-in-repo.yml b/changelogs/unreleased/36571-ignore-root-in-repo.yml new file mode 100644 index 00000000000..396e82be51b --- /dev/null +++ b/changelogs/unreleased/36571-ignore-root-in-repo.yml @@ -0,0 +1,5 @@ +--- +title: Ignore leading slashes when searching for files within context of repository. +merge_request: +author: Andrew McCallum +type: fixed diff --git a/changelogs/unreleased/41476-enable-project-milestons-deletion-via-api.yml b/changelogs/unreleased/41476-enable-project-milestons-deletion-via-api.yml new file mode 100644 index 00000000000..bb5c1fdf082 --- /dev/null +++ b/changelogs/unreleased/41476-enable-project-milestons-deletion-via-api.yml @@ -0,0 +1,5 @@ +--- +title: Enables Project Milestone Deletion via the API +merge_request: 16478 +author: Jacopo Beschi @jacopo-beschi +type: added diff --git a/changelogs/unreleased/42055-update-marked-from-0-3-6-to-0-3-12.yml b/changelogs/unreleased/42055-update-marked-from-0-3-6-to-0-3-12.yml new file mode 100644 index 00000000000..2b043761856 --- /dev/null +++ b/changelogs/unreleased/42055-update-marked-from-0-3-6-to-0-3-12.yml @@ -0,0 +1,5 @@ +--- +title: Update marked from 0.3.6 to 0.3.12 +merge_request: 16480 +author: Takuya Noguchi +type: security diff --git a/changelogs/unreleased/issue_41460.yml b/changelogs/unreleased/issue_41460.yml new file mode 100644 index 00000000000..24d3eae6bf8 --- /dev/null +++ b/changelogs/unreleased/issue_41460.yml @@ -0,0 +1,5 @@ +--- +title: Fix error on changes tab when merge request cannot be created +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/mr-status-box-update.yml b/changelogs/unreleased/mr-status-box-update.yml new file mode 100644 index 00000000000..68265be16a1 --- /dev/null +++ b/changelogs/unreleased/mr-status-box-update.yml @@ -0,0 +1,5 @@ +--- +title: Fixed merge request status badge not updating after merging +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/sh-remove-shared-runners-and-more.yml b/changelogs/unreleased/sh-remove-shared-runners-and-more.yml new file mode 100644 index 00000000000..cc079617883 --- /dev/null +++ b/changelogs/unreleased/sh-remove-shared-runners-and-more.yml @@ -0,0 +1,5 @@ +--- +title: Remove erroneous text in shared runners page that suggested more runners available +merge_request: +author: +type: fixed diff --git a/doc/api/milestones.md b/doc/api/milestones.md index 84930f0bdc9..d35e940d7b1 100644 --- a/doc/api/milestones.md +++ b/doc/api/milestones.md @@ -93,6 +93,19 @@ Parameters: - `start_date` (optional) - The start date of the milestone - `state_event` (optional) - The state event of the milestone (close|activate) +## Delete project milestone + +Only for user with developer access to the project. + +``` +DELETE /projects/:id/milestones/:milestone_id +``` + +Parameters: + +- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user +- `milestone_id` (required) - The ID of the project's milestone + ## Get all issues assigned to a single milestone Gets all issues assigned to a single project milestone. diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md index f9990c1c4ea..f41d31797af 100644 --- a/doc/development/doc_styleguide.md +++ b/doc/development/doc_styleguide.md @@ -34,7 +34,6 @@ The table below shows what kind of documentation goes where. | `doc/install/`| Probably the most visited directory, since `installation.md` is there. Ideally this should go under `doc/administration/`, but it's best to leave it as-is in order to avoid confusion (still debated though). | | `doc/update/` | Same with `doc/install/`. Should be under `administration/`, but this is a well known location, better leave as-is, at least for now. | | `doc/topics/` | Indexes per Topic (`doc/topics/topic-name/index.md`): all resources for that topic (user and admin documentation, articles, and third-party docs) | -| `doc/articles/` | [Technical Articles](writing_documentation.md#technical-articles): user guides, admin guides, technical overviews, tutorials (`doc/articles/article-title/index.md`). | --- @@ -67,11 +66,10 @@ The table below shows what kind of documentation goes where. 1. The `doc/topics/` directory holds topic-related technical content. Create `doc/topics/topic-name/subtopic-name/index.md` when subtopics become necessary. General user- and admin- related documentation, should be placed accordingly. -1. For technical articles, place their images under `doc/articles/article-title/img/`. --- -If you are unsure where a document should live, you can ping `@axil` in your +If you are unsure where a document should live, you can ping `@axil` or `@marcia` in your merge request. ## Text @@ -108,8 +106,8 @@ merge request. - Avoid adding things that show ephemeral statuses. For example, if a feature is considered beta or experimental, put this info in a note, not in the heading. - When introducing a new document, be careful for the headings to be - grammatically and syntactically correct. It is advised to mention one or all - of the following GitLab members for a review: `@axil`, `@rspeicher`, `@marcia`. + grammatically and syntactically correct. Mention one or all + of the following GitLab members for a review: `@axil` or `@marcia`. This is to ensure that no document with wrong heading is going live without an audit, thus preventing dead links and redirection issues when corrected @@ -330,6 +328,10 @@ For example, if you were to move `doc/workflow/lfs/lfs_administration.md` to git grep -n "lfs/lfs_administration" ``` +NOTE: **Note:** +If the document being moved has any Disqus comments on it, there are extra steps +to follow documented just [below](#redirections-for-pages-with-disqus-comments). + Things to note: - Since we also use inline documentation, except for the documentation itself, @@ -342,6 +344,32 @@ Things to note: documentation, sometimes it might be useful to search a path deeper. - The `*.md` extension is not used when a document is linked to GitLab's built-in help page, that's why we omit it in `git grep`. +- Use the checklist on the documentation MR description template. + +### Redirections for pages with Disqus comments + +If the documentation page being relocated already has any Disqus comments, +we need to preserve the Disqus thread. + +Disqus uses an identifier per page, and for docs.gitlab.com, the page identifier +is configured to be the page URL. Therefore, when we change the document location, +we need to preserve the old URL as the same Disqus identifier. + +To do that, add to the frontmatter the variable `redirect_from`, +using the old URL as value. For example, let's say I moved the document +available under `https://docs.gitlab.com/my-old-location/README.html` to a new location, +`https://docs.gitlab.com/my-new-location/index.html`. + +Into the **new document** frontmatter add the following: + +```yaml +--- +redirect_from: 'https://docs.gitlab.com/my-old-location/README.html' +--- +``` + +Note: it is necessary to include the file name in the `redirect_from` URL, +even if it's `index.html` or `README.html`. ## Configuration documentation for source and Omnibus installations diff --git a/doc/development/writing_documentation.md b/doc/development/writing_documentation.md index 133ac0234cf..2a1d744668b 100644 --- a/doc/development/writing_documentation.md +++ b/doc/development/writing_documentation.md @@ -25,6 +25,26 @@ them to review it for you. We use the [monthly release blog post](https://about.gitlab.com/handbook/marketing/blog/release-posts/#monthly-releases) as a changelog checklist to ensure everything is documented. +Whenever you submit a merge request for the documentation, use the documentation MR description template. + +### Documentation directory structure + +The documentation is structured based on the GitLab UI structure itself, +separated by [`user`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/user), +[`administrator`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/administration), and [`contributor`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/development). + +To learn where to place a new document, check the [documentation style guide](doc_styleguide.md#location-and-naming-of-documents). + +In order to have a [solid site structure](https://searchengineland.com/seo-benefits-developing-solid-site-structure-277456) for our documentation, +all docs should be linked. Every new document should be cross-linked to its related documentation, and linked from its topic-related index, when existent. + +The directories `/workflow/`, `/gitlab-basics/`, `/university/`, and `/articles/` have +been deprecated and the majority their docs have been moved to their correct location +in small iterations. Please don't create new docs in these folders. + +To move a document from its location to another directory, read the section +[changing document location](doc_styleguide.md#changing-document-location) of the doc style guide. + ### Feature overview and use cases Every major feature (regardless if present in GitLab Community or Enterprise editions) diff --git a/lib/api/project_milestones.rb b/lib/api/project_milestones.rb index 0cb209a02d0..306dc0e63d7 100644 --- a/lib/api/project_milestones.rb +++ b/lib/api/project_milestones.rb @@ -60,6 +60,15 @@ module API update_milestone_for(user_project) end + desc 'Remove a project milestone' + delete ":id/milestones/:milestone_id" do + authorize! :admin_milestone, user_project + + user_project.milestones.find(params[:milestone_id]).destroy + + status(204) + end + desc 'Get all issues for a single project milestone' do success Entities::IssueBasic end diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb index 016437b2419..46e0c0e82a2 100644 --- a/lib/gitlab/git/commit.rb +++ b/lib/gitlab/git/commit.rb @@ -436,6 +436,16 @@ module Gitlab parent_ids.size > 1 end + def tree_entry(path) + @repository.gitaly_migrate(:commit_tree_entry) do |is_migrated| + if is_migrated + gitaly_tree_entry(path) + else + rugged_tree_entry(path) + end + end + end + def to_gitaly_commit return raw_commit if raw_commit.is_a?(Gitaly::GitCommit) @@ -450,11 +460,6 @@ module Gitlab ) end - # Is this the same as Blob.find_entry_by_path ? - def rugged_tree_entry(path) - rugged_commit.tree.path(path) - end - private def init_from_hash(hash) @@ -501,6 +506,28 @@ module Gitlab SERIALIZE_KEYS end + def gitaly_tree_entry(path) + # We're only interested in metadata, so limit actual data to 1 byte + # since Gitaly doesn't support "send no data" option. + entry = @repository.gitaly_commit_client.tree_entry(id, path, 1) + return unless entry + + # To be compatible with the rugged format + entry = entry.to_h + entry.delete(:data) + entry[:name] = File.basename(path) + entry[:type] = entry[:type].downcase + + entry + end + + # Is this the same as Blob.find_entry_by_path ? + def rugged_tree_entry(path) + rugged_commit.tree.path(path) + rescue Rugged::TreeError + nil + end + def gitaly_commit_author_from_rugged(author_or_committer) Gitaly::CommitAuthor.new( name: author_or_committer[:name].b, diff --git a/package.json b/package.json index 4759ae76817..35c145aaebe 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "js-cookie": "^2.1.3", "jszip": "^3.1.3", "jszip-utils": "^0.0.2", - "marked": "^0.3.6", + "marked": "^0.3.12", "monaco-editor": "0.10.0", "mousetrap": "^1.4.6", "name-all-modules-plugin": "^1.0.1", diff --git a/spec/controllers/projects/merge_requests/creations_controller_spec.rb b/spec/controllers/projects/merge_requests/creations_controller_spec.rb index 7e2366847f4..92db7284e0e 100644 --- a/spec/controllers/projects/merge_requests/creations_controller_spec.rb +++ b/spec/controllers/projects/merge_requests/creations_controller_spec.rb @@ -4,6 +4,16 @@ describe Projects::MergeRequests::CreationsController do let(:project) { create(:project, :repository) } let(:user) { project.owner } let(:fork_project) { create(:forked_project_with_submodules) } + let(:get_diff_params) do + { + namespace_id: fork_project.namespace.to_param, + project_id: fork_project, + merge_request: { + source_branch: 'remove-submodule', + target_branch: 'master' + } + } + end before do fork_project.add_master(user) @@ -13,18 +23,23 @@ describe Projects::MergeRequests::CreationsController do describe 'GET new' do context 'merge request that removes a submodule' do - render_views - it 'renders new merge request widget template' do - get :new, - namespace_id: fork_project.namespace.to_param, - project_id: fork_project, - merge_request: { - source_branch: 'remove-submodule', - target_branch: 'master' - } + get :new, get_diff_params + + expect(response).to be_success + end + end + end + + describe 'GET diffs' do + context 'when merge request cannot be created' do + it 'does not assign diffs var' do + allow_any_instance_of(MergeRequest).to receive(:can_be_created).and_return(false) + + get :diffs, get_diff_params.merge(format: 'json') expect(response).to be_success + expect(assigns[:diffs]).to be_nil end end end @@ -37,14 +52,7 @@ describe Projects::MergeRequests::CreationsController do end it 'renders JSON including serialized pipelines' do - get :pipelines, - namespace_id: fork_project.namespace.to_param, - project_id: fork_project, - merge_request: { - source_branch: 'remove-submodule', - target_branch: 'master' - }, - format: :json + get :pipelines, get_diff_params.merge(format: 'json') expect(response).to be_ok expect(json_response).to have_key 'pipelines' diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js index 9d6ea3781bc..bae3219b043 100644 --- a/spec/javascripts/merge_request_spec.js +++ b/spec/javascripts/merge_request_spec.js @@ -70,8 +70,8 @@ import IssuablesHelper from '~/helpers/issuables_helper'; beforeEach(() => { loadFixtures('merge_requests/merge_request_with_task_list.html.raw'); this.el = document.querySelector('.js-issuable-actions'); - const merge = new MergeRequest(); - merge.hideCloseButton(); + new MergeRequest(); // eslint-disable-line no-new + MergeRequest.hideCloseButton(); }); it('hides the dropdown close item and selects the next item', () => { @@ -90,8 +90,7 @@ import IssuablesHelper from '~/helpers/issuables_helper'; beforeEach(() => { loadFixtures('merge_requests/merge_request_of_current_user.html.raw'); this.el = document.querySelector('.js-issuable-actions'); - const merge = new MergeRequest(); - merge.hideCloseButton(); + MergeRequest.hideCloseButton(); }); it('hides the close button', () => { diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js index 1127576617b..11858e45386 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js @@ -371,6 +371,10 @@ describe('MRWidgetReadyToMerge', () => { }); }); + beforeEach(() => { + loadFixtures('merge_requests/merge_request_of_current_user.html.raw'); + }); + it('should call start and stop polling when MR merged', (done) => { spyOn(eventHub, '$emit'); spyOn(vm.service, 'poll').and.returnValue(returnPromise('merged')); @@ -392,6 +396,47 @@ describe('MRWidgetReadyToMerge', () => { }, 333); }); + it('updates status box', (done) => { + spyOn(vm.service, 'poll').and.returnValue(returnPromise('merged')); + spyOn(vm, 'initiateRemoveSourceBranchPolling'); + + vm.handleMergePolling(() => {}, () => {}); + + setTimeout(() => { + const statusBox = document.querySelector('.status-box'); + expect(statusBox.classList.contains('status-box-merged')).toBeTruthy(); + expect(statusBox.textContent).toContain('Merged'); + + done(); + }); + }); + + it('hides close button', (done) => { + spyOn(vm.service, 'poll').and.returnValue(returnPromise('merged')); + spyOn(vm, 'initiateRemoveSourceBranchPolling'); + + vm.handleMergePolling(() => {}, () => {}); + + setTimeout(() => { + expect(document.querySelector('.btn-close').classList.contains('hidden')).toBeTruthy(); + + done(); + }); + }); + + it('updates merge request count badge', (done) => { + spyOn(vm.service, 'poll').and.returnValue(returnPromise('merged')); + spyOn(vm, 'initiateRemoveSourceBranchPolling'); + + vm.handleMergePolling(() => {}, () => {}); + + setTimeout(() => { + expect(document.querySelector('.js-merge-counter').textContent).toBe('0'); + + done(); + }); + }); + it('should continue polling until MR is merged', (done) => { spyOn(vm.service, 'poll').and.returnValue(returnPromise('some_other_state')); spyOn(vm, 'initiateRemoveSourceBranchPolling'); diff --git a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb index 98730602863..d21183b668b 100644 --- a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb +++ b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb @@ -15,6 +15,10 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :t .to receive(:commits_count=).and_return(nil) end + after do + [Project, MergeRequest, MergeRequestDiff].each(&:reset_column_information) + end + def diffs_to_hashes(diffs) diffs.as_json(only: Gitlab::Git::Diff::SERIALIZE_KEYS).map(&:with_indifferent_access) end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 3eaeeebf97d..45a606c1ea8 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -25,6 +25,13 @@ describe Ci::Build do it { is_expected.to be_a(ArtifactMigratable) } + describe 'associations' do + it 'has a bidirectional relationship with projects' do + expect(described_class.reflect_on_association(:project).has_inverse?).to eq(:builds) + expect(Project.reflect_on_association(:builds).has_inverse?).to eq(:project) + end + end + describe 'callbacks' do context 'when running after_create callback' do it 'triggers asynchronous build hooks worker' do diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 7bef798a782..14d234f6aab 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -28,6 +28,13 @@ describe Ci::Pipeline, :mailer do it { is_expected.to respond_to :short_sha } it { is_expected.to delegate_method(:full_path).to(:project).with_prefix } + describe 'associations' do + it 'has a bidirectional relationship with projects' do + expect(described_class.reflect_on_association(:project).has_inverse?).to eq(:pipelines) + expect(Project.reflect_on_association(:pipelines).has_inverse?).to eq(:project) + end + end + describe '#source' do context 'when creating new pipeline' do let(:pipeline) do diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index d3826417762..f8a98b43e46 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -439,15 +439,25 @@ eos end describe '#uri_type' do - it 'returns the URI type at the given path' do - expect(commit.uri_type('files/html')).to be(:tree) - expect(commit.uri_type('files/images/logo-black.png')).to be(:raw) - expect(project.commit('video').uri_type('files/videos/intro.mp4')).to be(:raw) - expect(commit.uri_type('files/js/application.js')).to be(:blob) + shared_examples 'URI type' do + it 'returns the URI type at the given path' do + expect(commit.uri_type('files/html')).to be(:tree) + expect(commit.uri_type('files/images/logo-black.png')).to be(:raw) + expect(project.commit('video').uri_type('files/videos/intro.mp4')).to be(:raw) + expect(commit.uri_type('files/js/application.js')).to be(:blob) + end + + it "returns nil if the path doesn't exists" do + expect(commit.uri_type('this/path/doesnt/exist')).to be_nil + end + end + + context 'when Gitaly commit_tree_entry feature is enabled' do + it_behaves_like 'URI type' end - it "returns nil if the path doesn't exists" do - expect(commit.uri_type('this/path/doesnt/exist')).to be_nil + context 'when Gitaly commit_tree_entry feature is disabled', :disable_gitaly do + it_behaves_like 'URI type' end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index f3456e5b354..d9395ca61d7 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -668,6 +668,18 @@ describe Repository do expect(results.first).to eq('files/html/500.html') end + it 'ignores leading slashes' do + results = repository.search_files_by_name('/files', 'master') + + expect(results.first).to eq('files/html/500.html') + end + + it 'properly handles when query is only slashes' do + results = repository.search_files_by_name('//', 'master') + + expect(results).to match_array([]) + end + it 'properly handles when query is not present' do results = repository.search_files_by_name('', 'master') diff --git a/spec/requests/api/project_milestones_spec.rb b/spec/requests/api/project_milestones_spec.rb index 08ea7314bb3..6c05c166bd6 100644 --- a/spec/requests/api/project_milestones_spec.rb +++ b/spec/requests/api/project_milestones_spec.rb @@ -14,6 +14,46 @@ describe API::ProjectMilestones do let(:route) { "/projects/#{project.id}/milestones" } end + describe 'DELETE /projects/:id/milestones/:milestone_id' do + let(:guest) { create(:user) } + let(:reporter) { create(:user) } + + before do + project.add_reporter(reporter) + end + + it 'returns 404 response when the project does not exists' do + delete api("/projects/999/milestones/#{milestone.id}", user) + + expect(response).to have_gitlab_http_status(404) + end + + it 'returns 404 response when the milestone does not exists' do + delete api("/projects/#{project.id}/milestones/999", user) + + expect(response).to have_gitlab_http_status(404) + end + + it "returns 404 from guest user deleting a milestone" do + delete api("/projects/#{project.id}/milestones/#{milestone.id}", guest) + + expect(response).to have_gitlab_http_status(404) + end + + it "rejects a member with reporter access from deleting a milestone" do + delete api("/projects/#{project.id}/milestones/#{milestone.id}", reporter) + + expect(response).to have_gitlab_http_status(403) + end + + it 'deletes the milestone when the user has developer access to the project' do + delete api("/projects/#{project.id}/milestones/#{milestone.id}", user) + + expect(project.milestones.find_by_id(milestone.id)).to be_nil + expect(response).to have_gitlab_http_status(204) + end + end + describe 'PUT /projects/:id/milestones/:milestone_id to test observer on close' do it 'creates an activity event when an milestone is closed' do expect(Event).to receive(:create!) diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 965fd39c967..ab3a257f36f 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -158,7 +158,7 @@ describe SystemNoteService do end it 'builds a correct phrase when assignee removed' do - expect(build_note([assignee1], [])).to eq 'removed assignee' + expect(build_note([assignee1], [])).to eq "unassigned @#{assignee1.username}" end it 'builds a correct phrase when assignees changed' do diff --git a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml index 75de266369d..eec356b9f47 100644 --- a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml @@ -90,10 +90,14 @@ codequality: performance: stage: performance - image: - name: sitespeedio/sitespeed.io:6.0.3 - entrypoint: [""] + image: docker:latest + variables: + DOCKER_DRIVER: overlay2 + allow_failure: true + services: + - docker:dind script: + - setup_docker - performance artifacts: paths: @@ -112,7 +116,7 @@ sast: - sast . artifacts: paths: [gl-sast-report.json] - + sast:container: image: docker:latest variables: @@ -260,7 +264,7 @@ production: export CI_APPLICATION_TAG=$CI_COMMIT_SHA export CI_CONTAINER_NAME=ci_job_build_${CI_JOB_ID} export TILLER_NAMESPACE=$KUBE_NAMESPACE - + function sast_container() { docker run -d --name db arminc/clair-db:latest docker run -p 6060:6060 --link db:postgres -d --name clair arminc/clair-local-scan:v2.0.1 @@ -466,26 +470,26 @@ production: --docker-email="$GITLAB_USER_EMAIL" \ -o yaml --dry-run | kubectl replace -n "$KUBE_NAMESPACE" --force -f - } - + function performance() { export CI_ENVIRONMENT_URL=$(cat environment_url.txt) - + mkdir gitlab-exporter wget -O gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/10-3/index.js - + mkdir sitespeed-results - + if [ -f .gitlab-urls.txt ] then sed -i -e 's@^@'"$CI_ENVIRONMENT_URL"'@' .gitlab-urls.txt - /start.sh --plugins.add gitlab-exporter --outputFolder sitespeed-results .gitlab-urls.txt + docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.0.3 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results .gitlab-urls.txt else - /start.sh --plugins.add gitlab-exporter --outputFolder sitespeed-results $CI_ENVIRONMENT_URL + docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.0.3 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results "$CI_ENVIRONMENT_URL" fi - + mv sitespeed-results/data/performance.json performance.json } - + function persist_environment_url() { echo $CI_ENVIRONMENT_URL > environment_url.txt } diff --git a/yarn.lock b/yarn.lock index 5d40e833889..64b66ee446a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4191,9 +4191,9 @@ map-stream@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194" -marked@^0.3.6: - version "0.3.6" - resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.6.tgz#b2c6c618fccece4ef86c4fc6cb8a7cbf5aeda8d7" +marked@^0.3.12: + version "0.3.12" + resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.12.tgz#7cf25ff2252632f3fe2406bde258e94eee927519" math-expression-evaluator@^1.2.14: version "1.2.16" |