diff options
author | Winnie Hellmann <winnie@gitlab.com> | 2017-10-18 18:52:53 +0000 |
---|---|---|
committer | Winnie Hellmann <winnie@gitlab.com> | 2017-10-18 18:52:53 +0000 |
commit | 7e72a6751fc954756f514396453042927766a785 (patch) | |
tree | b0bf571e885791fb3bbd85bca8f06593a2e56b2b | |
parent | 03106288f92ac5ec9c4867cc1be9b070f157ccac (diff) | |
parent | 83b59da6549d3137f1424ee9bf16b95e4f72173e (diff) | |
download | gitlab-ce-7e72a6751fc954756f514396453042927766a785.tar.gz |
Merge branch '10-1-stable-prepare-rc3' into '10-1-stable'
Prepare 10.1 RC3 release
See merge request gitlab-org/gitlab-ce!14921
54 files changed, 450 insertions, 67 deletions
diff --git a/app/assets/images/auth_buttons/signin_with_google.png b/app/assets/images/auth_buttons/signin_with_google.png Binary files differindex b1327b4f7b4..f27bb243304 100644 --- a/app/assets/images/auth_buttons/signin_with_google.png +++ b/app/assets/images/auth_buttons/signin_with_google.png diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 1edd460f380..d7995b59a7b 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -161,9 +161,6 @@ import AjaxLoadingSpinner from './ajax_loading_spinner'; const filteredSearchManager = new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests'); filteredSearchManager.setup(); } - if (page === 'projects:merge_requests:index') { - new UserCallout({ setCalloutPerProject: true }); - } const pagePrefix = page === 'projects:merge_requests:index' ? 'merge_request_' : 'issue_'; IssuableIndex.init(pagePrefix); @@ -345,7 +342,10 @@ import AjaxLoadingSpinner from './ajax_loading_spinner'; case 'projects:show': shortcut_handler = new ShortcutsNavigation(); new NotificationsForm(); - new UserCallout({ setCalloutPerProject: true }); + new UserCallout({ + setCalloutPerProject: true, + className: 'js-autodevops-banner', + }); if ($('#tree-slider').length) new TreeView(); if ($('.blob-viewer').length) new BlobViewer(); @@ -365,9 +365,6 @@ import AjaxLoadingSpinner from './ajax_loading_spinner'; case 'projects:pipelines:new': new NewBranchForm($('.js-new-pipeline-form')); break; - case 'projects:pipelines:index': - new UserCallout({ setCalloutPerProject: true }); - break; case 'projects:pipelines:builds': case 'projects:pipelines:failures': case 'projects:pipelines:show': @@ -425,7 +422,6 @@ import AjaxLoadingSpinner from './ajax_loading_spinner'; new TreeView(); new BlobViewer(); new NewCommitForm($('.js-create-dir-form')); - new UserCallout({ setCalloutPerProject: true }); $('#tree-slider').waitForImages(function() { ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath); }); diff --git a/app/assets/javascripts/registry/stores/mutations.js b/app/assets/javascripts/registry/stores/mutations.js index e40382e7afc..208c3c39866 100644 --- a/app/assets/javascripts/registry/stores/mutations.js +++ b/app/assets/javascripts/registry/stores/mutations.js @@ -38,7 +38,7 @@ export default { tag: element.name, revision: element.revision, shortRevision: element.short_revision, - size: element.size, + size: element.total_size, layers: element.layers, location: element.location, createdAt: element.created_at, diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index 0d7a5cba928..aa61ddc6a2c 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -7,6 +7,7 @@ @import "framework/animations"; @import "framework/avatar"; @import "framework/asciidoctor"; +@import "framework/banner"; @import "framework/blocks"; @import "framework/buttons"; @import "framework/badges"; diff --git a/app/assets/stylesheets/framework/banner.scss b/app/assets/stylesheets/framework/banner.scss new file mode 100644 index 00000000000..6433b0c7855 --- /dev/null +++ b/app/assets/stylesheets/framework/banner.scss @@ -0,0 +1,25 @@ +.banner-callout { + display: flex; + position: relative; + flex-wrap: wrap; + + .banner-close { + position: absolute; + top: 10px; + right: 10px; + opacity: 1; + + .dismiss-icon { + color: $gl-text-color; + font-size: $gl-font-size; + } + } + + .banner-graphic { + margin: 20px auto; + } + + &.banner-non-empty-state { + border-bottom: 1px solid $border-color; + } +} diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 9dcf332eee2..a9d804e735d 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -838,6 +838,7 @@ a { padding: 8px 40px; + &.is-indeterminate::before, &.is-active::before { left: 16px; } diff --git a/app/assets/stylesheets/framework/tooltips.scss b/app/assets/stylesheets/framework/tooltips.scss index 93baf73cb78..98f28987a82 100644 --- a/app/assets/stylesheets/framework/tooltips.scss +++ b/app/assets/stylesheets/framework/tooltips.scss @@ -3,5 +3,5 @@ border-radius: $border-radius-default; line-height: 16px; font-weight: $gl-font-weight-normal; - padding: $gl-btn-padding; + padding: 8px; } diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 8d02d5de5c3..4754a67450f 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -309,4 +309,8 @@ module ApplicationHelper def show_new_repo? cookies["new_repo"] == "true" && body_data_page != 'projects:show' end + + def locale_path + asset_path("locale/#{Gitlab::I18n.locale}/app.js") + end end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index c0cc60d5ebf..5b5bb3cbe2c 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -33,6 +33,8 @@ class ApplicationSetting < ActiveRecord::Base attr_accessor :domain_whitelist_raw, :domain_blacklist_raw + default_value_for :id, 1 + validates :uuid, presence: true validates :session_expire_delay, diff --git a/app/models/gcp/cluster.rb b/app/models/gcp/cluster.rb index 18bd6a6dcb4..162a690c0e3 100644 --- a/app/models/gcp/cluster.rb +++ b/app/models/gcp/cluster.rb @@ -7,6 +7,9 @@ module Gcp belongs_to :user belongs_to :service + scope :enabled, -> { where(enabled: true) } + scope :disabled, -> { where(enabled: false) } + default_value_for :gcp_cluster_zone, 'us-central1-a' default_value_for :gcp_cluster_size, 3 default_value_for :gcp_machine_type, 'n1-standard-4' diff --git a/app/models/project.rb b/app/models/project.rb index 57e91ab3b88..984ef015f4a 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1262,7 +1262,7 @@ class Project < ActiveRecord::Base # self.forked_from_project will be nil before the project is saved, so # we need to go through the relation - original_project = forked_project_link.forked_from_project + original_project = forked_project_link&.forked_from_project return true unless original_project level <= original_project.visibility_level diff --git a/app/models/repository.rb b/app/models/repository.rb index bf526ca1762..43521a0ddd3 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1127,7 +1127,7 @@ class Repository def last_commit_id_for_path_by_shelling_out(sha, path) args = %W(rev-list --max-count=1 #{sha} -- #{path}) - run_git(args).first.strip + raw_repository.run_git_with_timeout(args, Gitlab::Git::Popen::FAST_GIT_PROCESS_TIMEOUT).first.strip end def repository_storage_path diff --git a/app/serializers/container_tag_entity.rb b/app/serializers/container_tag_entity.rb index ec1fc349586..26a68c43807 100644 --- a/app/serializers/container_tag_entity.rb +++ b/app/serializers/container_tag_entity.rb @@ -1,7 +1,7 @@ class ContainerTagEntity < Grape::Entity include RequestAwareEntity - expose :name, :location, :revision, :total_size, :created_at + expose :name, :location, :revision, :short_revision, :total_size, :created_at expose :destroy_path, if: -> (*) { can_destroy? } do |tag| project_registry_repository_tag_path(project, tag.repository, tag.name, format: :json) diff --git a/app/services/projects/unlink_fork_service.rb b/app/services/projects/unlink_fork_service.rb index abe414d0c05..2b82e5732e4 100644 --- a/app/services/projects/unlink_fork_service.rb +++ b/app/services/projects/unlink_fork_service.rb @@ -15,8 +15,8 @@ module Projects refresh_forks_count(@project.forked_from_project) - @project.forked_project_link.destroy @project.fork_network_member.destroy + @project.forked_project_link.destroy end def refresh_forks_count(project) diff --git a/app/views/discussions/_parallel_diff_discussion.html.haml b/app/views/discussions/_parallel_diff_discussion.html.haml index 253cd336882..079d9083dff 100644 --- a/app/views/discussions/_parallel_diff_discussion.html.haml +++ b/app/views/discussions/_parallel_diff_discussion.html.haml @@ -4,7 +4,7 @@ %td.notes_line.old %td.notes_content.parallel.old .content{ class: ('hide' unless discussions_left.any?(&:expanded?)) } - = render partial: "discussions/notes", collection: discussions_left, as: :discussion, line_type: 'old' + = render partial: "discussions/notes", collection: discussions_left, as: :discussion, line_type: 'old', locals: { disable_collapse_class: true } - else %td.notes_line.old= ("") %td.notes_content.parallel.old @@ -14,7 +14,7 @@ %td.notes_line.new %td.notes_content.parallel.new .content{ class: ('hide' unless discussions_right.any?(&:expanded?)) } - = render partial: "discussions/notes", collection: discussions_right, as: :discussion, line_type: 'new' + = render partial: "discussions/notes", collection: discussions_right, as: :discussion, line_type: 'new', locals: { disable_collapse_class: true } - else %td.notes_line.new= ("") %td.notes_content.parallel.new diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index b18b3dd5766..29b23ae2e52 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -17,10 +17,6 @@ %th Global Shortcuts %tr %td.shortcut - .key n - %td Main Navigation - %tr - %td.shortcut .key s %td Focus Search %tr diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index f1b32274664..1597621fa78 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -37,7 +37,7 @@ - if content_for?(:library_javascripts) = yield :library_javascripts - = javascript_include_tag asset_path("locale/#{I18n.locale.to_s || I18n.default_locale.to_s}/app.js") unless I18n.locale == :en + = javascript_include_tag locale_path unless I18n.locale == :en = webpack_bundle_tag "webpack_runtime" = webpack_bundle_tag "common" = webpack_bundle_tag "main" diff --git a/app/views/projects/clusters/login.html.haml b/app/views/projects/clusters/login.html.haml index ae132672b7e..fde030b500b 100644 --- a/app/views/projects/clusters/login.html.haml +++ b/app/views/projects/clusters/login.html.haml @@ -10,7 +10,7 @@ .col-sm-8.col-sm-offset-4.signin-with-google - if @authorize_url = link_to @authorize_url do - = image_tag('auth_buttons/signin_with_google.png') + = image_tag('auth_buttons/signin_with_google.png', width: '191px') - else - link = link_to(s_('ClusterIntegration|properly configured'), help_page_path("integration/google"), target: '_blank', rel: 'noopener noreferrer') = s_('Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service.').html_safe % { link_to_documentation: link } diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 3f3ce10419f..c9956183e12 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -24,10 +24,15 @@ %p You will need to be owner or have the master permission level for the initial push, as the master branch is automatically protected. + - if show_auto_devops_callout?(@project) + %p + - link = link_to(s_('AutoDevOps|Auto DevOps (Beta)'), project_settings_ci_cd_path(@project, anchor: 'js-general-pipeline-settings')) + = s_('AutoDevOps|You can activate %{link_to_settings} for this project.').html_safe % { link_to_settings: link } + %p + = s_('AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration.') + - if can?(current_user, :push_code, @project) %div{ class: container_class } - - if show_auto_devops_callout?(@project) - = render 'shared/auto_devops_callout' .prepend-top-20 .empty_wrapper %h3.page-title-empty diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index 6b8dcb3e60b..8da2243adef 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -13,8 +13,6 @@ - if @project.merge_requests.exists? %div{ class: container_class } - - if show_auto_devops_callout?(@project) - = render 'shared/auto_devops_callout' .top-area = render 'shared/issuable/nav', type: :merge_requests .nav-controls diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index a10a7c23924..f8627a3818b 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -2,8 +2,6 @@ - page_title "Pipelines" %div{ 'class' => container_class } - - if show_auto_devops_callout?(@project) - = render 'shared/auto_devops_callout' #pipelines-list-vue{ data: { endpoint: project_pipelines_path(@project, format: :json), "help-page-path" => help_page_path('ci/quick_start/README'), "help-auto-devops-path" => help_page_path('topics/autodevops/index.md'), diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml index 0cc6674842a..745a6040488 100644 --- a/app/views/projects/tree/show.html.haml +++ b/app/views/projects/tree/show.html.haml @@ -12,7 +12,5 @@ = webpack_bundle_tag 'repo' %div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } - - if show_auto_devops_callout?(@project) && !show_new_repo? - = render 'shared/auto_devops_callout' = render 'projects/last_push' = render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id) diff --git a/app/views/shared/_auto_devops_callout.html.haml b/app/views/shared/_auto_devops_callout.html.haml index 7c633175a06..934d65e8b42 100644 --- a/app/views/shared/_auto_devops_callout.html.haml +++ b/app/views/shared/_auto_devops_callout.html.haml @@ -1,15 +1,16 @@ -.user-callout{ data: { uid: 'auto_devops_settings_dismissed', project_path: project_path(@project) } } - .bordered-box.landing.content-block - %button.btn.btn-default.close.js-close-callout{ type: 'button', - 'aria-label' => 'Dismiss Auto DevOps box' } - = icon('times', class: 'dismiss-icon', 'aria-hidden' => 'true') - .svg-container - = custom_icon('icon_autodevops') - .user-callout-copy - %h4= s_('AutoDevOps|Auto DevOps (Beta)') - %p= s_('AutoDevOps|Auto DevOps can be activated for this project. It will automatically build, test, and deploy your application based on a predefined CI/CD configuration.') - %p - - link = link_to(s_('AutoDevOps|Auto DevOps documentation'), help_page_path('topics/autodevops/index.md'), target: '_blank', rel: 'noopener noreferrer') - = s_('AutoDevOps|Learn more in the %{link_to_documentation}').html_safe % { link_to_documentation: link } +.js-autodevops-banner.banner-callout.banner-non-empty-state.append-bottom-20{ data: { uid: 'auto_devops_settings_dismissed', project_path: project_path(@project) } } + .banner-graphic + = custom_icon('icon_autodevops') - = link_to s_('AutoDevOps|Enable in settings'), project_settings_ci_cd_path(@project, anchor: 'js-general-pipeline-settings'), class: 'btn btn-primary js-close-callout' + .prepend-top-10.prepend-left-10.append-bottom-10 + %h5= s_('AutoDevOps|Auto DevOps (Beta)') + %p= s_('AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration.') + %p + - link = link_to(s_('AutoDevOps|Auto DevOps documentation'), help_page_path('topics/autodevops/index.md'), target: '_blank', rel: 'noopener noreferrer') + = s_('AutoDevOps|Learn more in the %{link_to_documentation}').html_safe % { link_to_documentation: link } + .prepend-top-10 + = link_to s_('AutoDevOps|Enable in settings'), project_settings_ci_cd_path(@project, anchor: 'js-general-pipeline-settings'), class: 'btn js-close-callout' + + %button.btn-transparent.banner-close.close.js-close-callout{ type: 'button', + 'aria-label' => 'Dismiss Auto DevOps box' } + = icon('times', class: 'dismiss-icon', 'aria-hidden' => 'true') diff --git a/app/views/shared/icons/_icon_autodevops.svg b/app/views/shared/icons/_icon_autodevops.svg index 807ff27bb67..7e47c084bde 100644 --- a/app/views/shared/icons/_icon_autodevops.svg +++ b/app/views/shared/icons/_icon_autodevops.svg @@ -1,4 +1,4 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="189" height="179" viewBox="0 0 189 179"> +<svg xmlns="http://www.w3.org/2000/svg" width="189" height="110" viewBox="0 0 189 179"> <g fill="none" fill-rule="evenodd"> <path fill="#FFFFFF" fill-rule="nonzero" d="M110.160166,47.6956996 L160.160166,47.6956996 C165.683013,47.6956996 170.160166,52.1728521 170.160166,57.6956996 L170.160166,117.6957 C170.160166,123.218547 165.683013,127.6957 160.160166,127.6957 L110.160166,127.6957 C104.637318,127.6957 100.160166,123.218547 100.160166,117.6957 L100.160166,57.6956996 C100.160166,52.1728521 104.637318,47.6956996 110.160166,47.6956996 Z" transform="rotate(10 135.16 87.696)"/> <path fill="#EEEEEE" fill-rule="nonzero" d="M110.160166,51.6956996 C106.846457,51.6956996 104.160166,54.3819911 104.160166,57.6956996 L104.160166,117.6957 C104.160166,121.009408 106.846457,123.6957 110.160166,123.6957 L160.160166,123.6957 C163.473874,123.6957 166.160166,121.009408 166.160166,117.6957 L166.160166,57.6956996 C166.160166,54.3819911 163.473874,51.6956996 160.160166,51.6956996 L110.160166,51.6956996 Z M110.160166,47.6956996 L160.160166,47.6956996 C165.683013,47.6956996 170.160166,52.1728521 170.160166,57.6956996 L170.160166,117.6957 C170.160166,123.218547 165.683013,127.6957 160.160166,127.6957 L110.160166,127.6957 C104.637318,127.6957 100.160166,123.218547 100.160166,117.6957 L100.160166,57.6956996 C100.160166,52.1728521 104.637318,47.6956996 110.160166,47.6956996 Z" transform="rotate(10 135.16 87.696)"/> diff --git a/changelogs/unreleased/39017-gitlabusagepingworker-is-not-running-on-gitlab-com.yml b/changelogs/unreleased/39017-gitlabusagepingworker-is-not-running-on-gitlab-com.yml new file mode 100644 index 00000000000..89506f88637 --- /dev/null +++ b/changelogs/unreleased/39017-gitlabusagepingworker-is-not-running-on-gitlab-com.yml @@ -0,0 +1,5 @@ +--- +title: Make usage ping scheduling more robust +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/an-popen-deadline.yml b/changelogs/unreleased/an-popen-deadline.yml new file mode 100644 index 00000000000..4b74c63ed5c --- /dev/null +++ b/changelogs/unreleased/an-popen-deadline.yml @@ -0,0 +1,5 @@ +--- +title: Use a timeout on certain git operations +merge_request: 14872 +author: +type: security diff --git a/changelogs/unreleased/bvl-fix-deleting-forked-projects.yml b/changelogs/unreleased/bvl-fix-deleting-forked-projects.yml new file mode 100644 index 00000000000..95f56facc4b --- /dev/null +++ b/changelogs/unreleased/bvl-fix-deleting-forked-projects.yml @@ -0,0 +1,5 @@ +--- +title: Fix error when updating a forked project with deleted `ForkedProjectLink` +merge_request: 14916 +author: +type: fixed diff --git a/changelogs/unreleased/bvl-fix-locale-path.yml b/changelogs/unreleased/bvl-fix-locale-path.yml new file mode 100644 index 00000000000..97e0e000e3c --- /dev/null +++ b/changelogs/unreleased/bvl-fix-locale-path.yml @@ -0,0 +1,5 @@ +--- +title: Correctly render asset path for locales with a region +merge_request: 14924 +author: +type: fixed diff --git a/changelogs/unreleased/fix-resolved-side-by-side.yml b/changelogs/unreleased/fix-resolved-side-by-side.yml new file mode 100644 index 00000000000..424130c3eb0 --- /dev/null +++ b/changelogs/unreleased/fix-resolved-side-by-side.yml @@ -0,0 +1,5 @@ +--- +title: Fix resolved discussions not expanding on side by side view +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/fl-autodevops-fix.yml b/changelogs/unreleased/fl-autodevops-fix.yml new file mode 100644 index 00000000000..21b739231a8 --- /dev/null +++ b/changelogs/unreleased/fl-autodevops-fix.yml @@ -0,0 +1,5 @@ +--- +title: Improve autodevops banner UX and render it only in project page +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/kt-bug-fix-revision-and-size-for-container-registry.yml b/changelogs/unreleased/kt-bug-fix-revision-and-size-for-container-registry.yml new file mode 100644 index 00000000000..acbb24d16fc --- /dev/null +++ b/changelogs/unreleased/kt-bug-fix-revision-and-size-for-container-registry.yml @@ -0,0 +1,5 @@ +--- +title: Fix revision and total size missing for Container Registry +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/prevent-creating-multiple-application-settings.yml b/changelogs/unreleased/prevent-creating-multiple-application-settings.yml new file mode 100644 index 00000000000..fd49028b9e9 --- /dev/null +++ b/changelogs/unreleased/prevent-creating-multiple-application-settings.yml @@ -0,0 +1,5 @@ +--- +title: Prevent creating multiple ApplicationSetting instances +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/winh-indeterminate-dropdown.yml b/changelogs/unreleased/winh-indeterminate-dropdown.yml new file mode 100644 index 00000000000..61205d1643e --- /dev/null +++ b/changelogs/unreleased/winh-indeterminate-dropdown.yml @@ -0,0 +1,5 @@ +--- +title: Fix alignment for indeterminate marker in dropdowns +merge_request: 14809 +author: +type: fixed diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index a4b7c1a3919..b790df565c6 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -113,12 +113,14 @@ class Settings < Settingslogic URI.parse(url_without_path).host end - # Random cron time every Sunday to load balance usage pings - def cron_random_weekly_time + # Runs every minute in a random ten-minute period on Sundays, to balance the + # load on the server receiving these pings. The usage ping is safe to run + # multiple times because of a 24 hour exclusive lock. + def cron_for_usage_ping hour = rand(24) - minute = rand(60) + minute = rand(6) - "#{minute} #{hour} * * 0" + "#{minute}0-#{minute}9 #{hour} * * 0" end end end @@ -398,7 +400,7 @@ Settings.cron_jobs['stuck_import_jobs_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['stuck_import_jobs_worker']['cron'] ||= '15 * * * *' Settings.cron_jobs['stuck_import_jobs_worker']['job_class'] = 'StuckImportJobsWorker' Settings.cron_jobs['gitlab_usage_ping_worker'] ||= Settingslogic.new({}) -Settings.cron_jobs['gitlab_usage_ping_worker']['cron'] ||= Settings.__send__(:cron_random_weekly_time) +Settings.cron_jobs['gitlab_usage_ping_worker']['cron'] ||= Settings.__send__(:cron_for_usage_ping) Settings.cron_jobs['gitlab_usage_ping_worker']['job_class'] = 'GitlabUsagePingWorker' Settings.cron_jobs['schedule_update_user_activity_worker'] ||= Settingslogic.new({}) diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md index 67ef189fee9..e18711f3392 100644 --- a/doc/gitlab-basics/create-project.md +++ b/doc/gitlab-basics/create-project.md @@ -17,7 +17,7 @@ [Project Templates](https://gitlab.com/gitlab-org/project-templates): this will kickstart your repository code and CI automatically. Otherwise, if you have a project in a different repository, you can [import it] by - clicking an **Import project from** button provided this is enabled in + clicking on the **Import project** tab, provided this is enabled in your GitLab instance. Ask your administrator if not. 1. Provide the following information: diff --git a/doc/gitlab-basics/img/create_new_project_info.png b/doc/gitlab-basics/img/create_new_project_info.png Binary files differindex ef8753e224b..ce4f7d1204b 100644 --- a/doc/gitlab-basics/img/create_new_project_info.png +++ b/doc/gitlab-basics/img/create_new_project_info.png diff --git a/doc/install/installation.md b/doc/install/installation.md index 2c93297ca2f..2a004152d5e 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -80,7 +80,7 @@ Make sure you have the right version of Git installed # Install Git sudo apt-get install -y git-core - # Make sure Git is version 2.13.0 or higher + # Make sure Git is version 2.13.6 or higher git --version Is the system packaged Git too old? Remove it and compile from source. diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 17fe80fa93d..3d7becd18fc 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -121,8 +121,8 @@ Existing users using GitLab with MySQL/MariaDB are advised to ### PostgreSQL Requirements -As of GitLab 9.3, PostgreSQL 9.2 or newer is required, and earlier versions are -not supported. We highly recommend users to use at least PostgreSQL 9.6 as this +As of GitLab 10.0, PostgreSQL 9.6 or newer is required, and earlier versions are +not supported. We highly recommend users to use PostgreSQL 9.6 as this is the PostgreSQL version used for development and testing. Users using PostgreSQL must ensure the `pg_trgm` extension is loaded into every diff --git a/doc/user/project/issues/automatic_issue_closing.md b/doc/user/project/issues/automatic_issue_closing.md index d6f3a7d5555..10dede255ec 100644 --- a/doc/user/project/issues/automatic_issue_closing.md +++ b/doc/user/project/issues/automatic_issue_closing.md @@ -19,7 +19,7 @@ When not specified, the default issue closing pattern as shown below will be used: ```bash -((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing))(:?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\d+))+) +((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)|[Ii]mplement(?:s|ed|ing)?)(:?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\d+))+) ``` Note that `%{issue_ref}` is a complex regular expression defined inside GitLab's @@ -34,6 +34,7 @@ This translates to the following keywords: - Close, Closes, Closed, Closing, close, closes, closed, closing - Fix, Fixes, Fixed, Fixing, fix, fixes, fixed, fixing - Resolve, Resolves, Resolved, Resolving, resolve, resolves, resolved, resolving +- Implement, Implements, Implemented, Implementing, implement, implements, implemented, implementing --- diff --git a/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb b/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb index 8e5c95f2287..380802258f5 100644 --- a/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb +++ b/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb @@ -81,6 +81,7 @@ module Gitlab def single_diff_rows(merge_request_diff) sha_attribute = Gitlab::Database::ShaAttribute.new commits = YAML.load(merge_request_diff.st_commits) rescue [] + commits ||= [] commit_rows = commits.map.with_index do |commit, index| commit_hash = commit.to_hash.with_indifferent_access.except(:parent_ids) diff --git a/lib/gitlab/git/popen.rb b/lib/gitlab/git/popen.rb index 3d2fc471d28..b45da6020ee 100644 --- a/lib/gitlab/git/popen.rb +++ b/lib/gitlab/git/popen.rb @@ -5,6 +5,8 @@ require 'open3' module Gitlab module Git module Popen + FAST_GIT_PROCESS_TIMEOUT = 15.seconds + def popen(cmd, path, vars = {}) unless cmd.is_a?(Array) raise "System commands must be given as an array of strings" @@ -27,6 +29,67 @@ module Gitlab [@cmd_output, @cmd_status] end + + def popen_with_timeout(cmd, timeout, path, vars = {}) + unless cmd.is_a?(Array) + raise "System commands must be given as an array of strings" + end + + path ||= Dir.pwd + vars['PWD'] = path + + unless File.directory?(path) + FileUtils.mkdir_p(path) + end + + rout, wout = IO.pipe + rerr, werr = IO.pipe + + pid = Process.spawn(vars, *cmd, out: wout, err: werr, chdir: path, pgroup: true) + + begin + status = process_wait_with_timeout(pid, timeout) + + # close write ends so we could read them + wout.close + werr.close + + cmd_output = rout.readlines.join + cmd_output << rerr.readlines.join # Copying the behaviour of `popen` which merges stderr into output + + [cmd_output, status.exitstatus] + rescue Timeout::Error => e + kill_process_group_for_pid(pid) + + raise e + ensure + wout.close unless wout.closed? + werr.close unless werr.closed? + + rout.close + rerr.close + end + end + + def process_wait_with_timeout(pid, timeout) + deadline = timeout.seconds.from_now + wait_time = 0.01 + + while deadline > Time.now + sleep(wait_time) + _, status = Process.wait2(pid, Process::WNOHANG) + + return status unless status.nil? + end + + raise Timeout::Error, "Timeout waiting for process ##{pid}" + end + + def kill_process_group_for_pid(pid) + Process.kill("KILL", -pid) + Process.wait(pid) + rescue Errno::ESRCH + end end end end diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 0f059bef808..19cbae070ef 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -1058,6 +1058,13 @@ module Gitlab end # Refactoring aid; allows us to copy code from app/models/repository.rb + def run_git_with_timeout(args, timeout, env: {}) + circuit_breaker.perform do + popen_with_timeout([Gitlab.config.git.bin_path, *args], timeout, path, env) + end + end + + # Refactoring aid; allows us to copy code from app/models/repository.rb def commit(ref = 'HEAD') Gitlab::Git::Commit.find(self, ref) end diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index 3f3ba77d47f..70a403652e7 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -49,6 +49,8 @@ module Gitlab deployments: Deployment.count, environments: ::Environment.count, gcp_clusters: ::Gcp::Cluster.count, + gcp_clusters_enabled: ::Gcp::Cluster.enabled.count, + gcp_clusters_disabled: ::Gcp::Cluster.disabled.count, in_review_folder: ::Environment.in_review_folder.count, groups: Group.count, issues: Issue.count, diff --git a/spec/features/merge_requests/diff_notes_resolve_spec.rb b/spec/features/merge_requests/diff_notes_resolve_spec.rb index 475c8586f45..3db0729cafb 100644 --- a/spec/features/merge_requests/diff_notes_resolve_spec.rb +++ b/spec/features/merge_requests/diff_notes_resolve_spec.rb @@ -97,14 +97,33 @@ feature 'Diff notes resolve', :js do visit_merge_request end - it 'hides when resolve discussion is clicked' do - expect(page).to have_selector('.discussion-body', visible: false) + describe 'timeline view' do + it 'hides when resolve discussion is clicked' do + expect(page).to have_selector('.discussion-body', visible: false) + end + + it 'shows resolved discussion when toggled' do + find(".timeline-content .discussion[data-discussion-id='#{note.discussion_id}'] .discussion-toggle-button").click + + expect(page.find(".timeline-content #note_#{note.noteable_id}")).to be_visible + end end - it 'shows resolved discussion when toggled' do - find(".timeline-content .discussion[data-discussion-id='#{note.discussion_id}'] .discussion-toggle-button").click + describe 'side-by-side view' do + before do + page.within('.merge-request-tabs') { click_link 'Changes' } + page.find('#parallel-diff-btn').click + end - expect(page.find(".timeline-content #note_#{note.noteable_id}")).to be_visible + it 'hides when resolve discussion is clicked' do + expect(page).to have_selector('.diffs .diff-file .notes_holder', visible: false) + end + + it 'shows resolved discussion when toggled' do + find('.diff-comment-avatar-holders').click + + expect(find('.diffs .diff-file .notes_holder')).to be_visible + end end end diff --git a/spec/fixtures/api/schemas/registry/tag.json b/spec/fixtures/api/schemas/registry/tag.json index 5bc307e0e64..3a2c88791e1 100644 --- a/spec/fixtures/api/schemas/registry/tag.json +++ b/spec/fixtures/api/schemas/registry/tag.json @@ -14,6 +14,11 @@ "revision": { "type": "string" }, + "short_revision": { + "type": "string", + "minLength": 9, + "maxLength": 9 + }, "total_size": { "type": "integer" }, diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 87ae1fa5660..7a241b02d28 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -309,4 +309,12 @@ describe ApplicationHelper do end end end + + describe '#locale_path' do + it 'returns the locale path with an `_`' do + Gitlab::I18n.with_locale('pt-BR') do + expect(helper.locale_path).to include('assets/locale/pt_BR/app') + end + end + end end diff --git a/spec/javascripts/registry/mock_data.js b/spec/javascripts/registry/mock_data.js index 18600d00bff..6bffb47be55 100644 --- a/spec/javascripts/registry/mock_data.js +++ b/spec/javascripts/registry/mock_data.js @@ -26,7 +26,7 @@ export const registryServerResponse = [ name: 'centos7', short_revision: 'b118ab5b0', revision: 'b118ab5b0e90b7cb5127db31d5321ac14961d097516a8e0e72084b6cdc783b43', - size: 679, + total_size: 679, layers: 19, location: 'location', created_at: 1505828744434, @@ -36,7 +36,7 @@ export const registryServerResponse = [ name: 'centos6', short_revision: 'b118ab5b0', revision: 'b118ab5b0e90b7cb5127db31d5321ac14961d097516a8e0e72084b6cdc783b43', - size: 679, + total_size: 679, layers: 19, location: 'location', created_at: 1505828744434, @@ -70,7 +70,7 @@ export const parsedRegistryServerResponse = [ tag: registryServerResponse[0].name, revision: registryServerResponse[0].revision, shortRevision: registryServerResponse[0].short_revision, - size: registryServerResponse[0].size, + size: registryServerResponse[0].total_size, layers: registryServerResponse[0].layers, location: registryServerResponse[0].location, createdAt: registryServerResponse[0].created_at, @@ -81,7 +81,7 @@ export const parsedRegistryServerResponse = [ tag: registryServerResponse[1].name, revision: registryServerResponse[1].revision, shortRevision: registryServerResponse[1].short_revision, - size: registryServerResponse[1].size, + size: registryServerResponse[1].total_size, layers: registryServerResponse[1].layers, location: registryServerResponse[1].location, createdAt: registryServerResponse[1].created_at, 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 d2e7243ee05..4d3fdbd9554 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 @@ -31,8 +31,8 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :t end it 'creates correct entries in the merge_request_diff_commits table' do - expect(updated_merge_request_diff.merge_request_diff_commits.count).to eq(commits.count) - expect(updated_merge_request_diff.commits.map(&:to_hash)).to eq(commits) + expect(updated_merge_request_diff.merge_request_diff_commits.count).to eq(expected_commits.count) + expect(updated_merge_request_diff.commits.map(&:to_hash)).to eq(expected_commits) end it 'creates correct entries in the merge_request_diff_files table' do @@ -199,6 +199,16 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :t context 'when the merge request diff has valid commits and diffs' do let(:commits) { merge_request_diff.commits.map(&:to_hash) } + let(:expected_commits) { commits } + let(:diffs) { diffs_to_hashes(merge_request_diff.merge_request_diff_files) } + let(:expected_diffs) { diffs } + + include_examples 'updated MR diff' + end + + context 'when the merge request diff has diffs but no commits' do + let(:commits) { nil } + let(:expected_commits) { [] } let(:diffs) { diffs_to_hashes(merge_request_diff.merge_request_diff_files) } let(:expected_diffs) { diffs } @@ -207,6 +217,7 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :t context 'when the merge request diffs do not have too_large set' do let(:commits) { merge_request_diff.commits.map(&:to_hash) } + let(:expected_commits) { commits } let(:expected_diffs) { diffs_to_hashes(merge_request_diff.merge_request_diff_files) } let(:diffs) do @@ -218,6 +229,7 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :t context 'when the merge request diffs do not have a_mode and b_mode set' do let(:commits) { merge_request_diff.commits.map(&:to_hash) } + let(:expected_commits) { commits } let(:expected_diffs) { diffs_to_hashes(merge_request_diff.merge_request_diff_files) } let(:diffs) do @@ -229,6 +241,7 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :t context 'when the merge request diffs have binary content' do let(:commits) { merge_request_diff.commits.map(&:to_hash) } + let(:expected_commits) { commits } let(:expected_diffs) { diffs } # The start of a PDF created by Illustrator @@ -257,6 +270,7 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :t context 'when the merge request diff has commits, but no diffs' do let(:commits) { merge_request_diff.commits.map(&:to_hash) } + let(:expected_commits) { commits } let(:diffs) { [] } let(:expected_diffs) { diffs } @@ -265,6 +279,7 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :t context 'when the merge request diffs have invalid content' do let(:commits) { merge_request_diff.commits.map(&:to_hash) } + let(:expected_commits) { commits } let(:diffs) { ['--broken-diff'] } let(:expected_diffs) { [] } @@ -274,6 +289,7 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :t context 'when the merge request diffs are Rugged::Patch instances' do let(:commits) { merge_request_diff.commits.map(&:to_hash) } let(:first_commit) { merge_request.project.repository.commit(merge_request_diff.head_commit_sha) } + let(:expected_commits) { commits } let(:diffs) { first_commit.rugged_diff_from_parent.patches } let(:expected_diffs) { [] } @@ -283,6 +299,7 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :t context 'when the merge request diffs are Rugged::Diff::Delta instances' do let(:commits) { merge_request_diff.commits.map(&:to_hash) } let(:first_commit) { merge_request.project.repository.commit(merge_request_diff.head_commit_sha) } + let(:expected_commits) { commits } let(:diffs) { first_commit.rugged_diff_from_parent.deltas } let(:expected_diffs) { [] } diff --git a/spec/lib/gitlab/git/popen_spec.rb b/spec/lib/gitlab/git/popen_spec.rb new file mode 100644 index 00000000000..2b65bc1cf15 --- /dev/null +++ b/spec/lib/gitlab/git/popen_spec.rb @@ -0,0 +1,132 @@ +require 'spec_helper' + +describe 'Gitlab::Git::Popen' do + let(:path) { Rails.root.join('tmp').to_s } + + let(:klass) do + Class.new(Object) do + include Gitlab::Git::Popen + end + end + + context 'popen' do + context 'zero status' do + let(:result) { klass.new.popen(%w(ls), path) } + let(:output) { result.first } + let(:status) { result.last } + + it { expect(status).to be_zero } + it { expect(output).to include('tests') } + end + + context 'non-zero status' do + let(:result) { klass.new.popen(%w(cat NOTHING), path) } + let(:output) { result.first } + let(:status) { result.last } + + it { expect(status).to eq(1) } + it { expect(output).to include('No such file or directory') } + end + + context 'unsafe string command' do + it 'raises an error when it gets called with a string argument' do + expect { klass.new.popen('ls', path) }.to raise_error(RuntimeError) + end + end + + context 'with custom options' do + let(:vars) { { 'foobar' => 123, 'PWD' => path } } + let(:options) { { chdir: path } } + + it 'calls popen3 with the provided environment variables' do + expect(Open3).to receive(:popen3).with(vars, 'ls', options) + + klass.new.popen(%w(ls), path, { 'foobar' => 123 }) + end + end + + context 'use stdin' do + let(:result) { klass.new.popen(%w[cat], path) { |stdin| stdin.write 'hello' } } + let(:output) { result.first } + let(:status) { result.last } + + it { expect(status).to be_zero } + it { expect(output).to eq('hello') } + end + end + + context 'popen_with_timeout' do + let(:timeout) { 1.second } + + context 'no timeout' do + context 'zero status' do + let(:result) { klass.new.popen_with_timeout(%w(ls), timeout, path) } + let(:output) { result.first } + let(:status) { result.last } + + it { expect(status).to be_zero } + it { expect(output).to include('tests') } + end + + context 'non-zero status' do + let(:result) { klass.new.popen_with_timeout(%w(cat NOTHING), timeout, path) } + let(:output) { result.first } + let(:status) { result.last } + + it { expect(status).to eq(1) } + it { expect(output).to include('No such file or directory') } + end + + context 'unsafe string command' do + it 'raises an error when it gets called with a string argument' do + expect { klass.new.popen_with_timeout('ls', timeout, path) }.to raise_error(RuntimeError) + end + end + end + + context 'timeout' do + context 'timeout' do + it "raises a Timeout::Error" do + expect { klass.new.popen_with_timeout(%w(sleep 1000), timeout, path) }.to raise_error(Timeout::Error) + end + + it "handles processes that do not shutdown correctly" do + expect { klass.new.popen_with_timeout(['bash', '-c', "trap -- '' SIGTERM; sleep 1000"], timeout, path) }.to raise_error(Timeout::Error) + end + end + + context 'timeout period' do + let(:time_taken) do + begin + start = Time.now + klass.new.popen_with_timeout(%w(sleep 1000), timeout, path) + rescue + Time.now - start + end + end + + it { expect(time_taken).to be >= timeout } + end + + context 'clean up' do + let(:instance) { klass.new } + + it 'kills the child process' do + expect(instance).to receive(:kill_process_group_for_pid).and_wrap_original do |m, *args| + # is the PID, and it should not be running at this point + m.call(*args) + + pid = args.first + begin + Process.getpgid(pid) + raise "The child process should have been killed" + rescue Errno::ESRCH + end + end + + expect { instance.popen_with_timeout(['bash', '-c', "trap -- '' SIGTERM; sleep 1000"], timeout, path) }.to raise_error(Timeout::Error) + end + end + end + end +end diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 777e9c8e21d..a7b65e94706 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -61,6 +61,8 @@ describe Gitlab::UsageData do deployments environments gcp_clusters + gcp_clusters_enabled + gcp_clusters_disabled in_review_folder groups issues diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 78cacf9ff5d..eff84c308b5 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -209,6 +209,16 @@ describe ApplicationSetting do end end + context 'restrict creating duplicates' do + before do + described_class.create_from_defaults + end + + it 'raises an record creation violation if already created' do + expect { described_class.create_from_defaults }.to raise_error(ActiveRecord::RecordNotUnique) + end + end + context 'restricted signup domains' do it 'sets single domain' do setting.domain_whitelist_raw = 'example.com' diff --git a/spec/models/gcp/cluster_spec.rb b/spec/models/gcp/cluster_spec.rb index 350fbc257d9..8f39fff6394 100644 --- a/spec/models/gcp/cluster_spec.rb +++ b/spec/models/gcp/cluster_spec.rb @@ -7,6 +7,30 @@ describe Gcp::Cluster do it { is_expected.to validate_presence_of(:gcp_cluster_zone) } + describe '.enabled' do + subject { described_class.enabled } + + let!(:cluster) { create(:gcp_cluster, enabled: true) } + + before do + create(:gcp_cluster, enabled: false) + end + + it { is_expected.to contain_exactly(cluster) } + end + + describe '.disabled' do + subject { described_class.disabled } + + let!(:cluster) { create(:gcp_cluster, enabled: false) } + + before do + create(:gcp_cluster, enabled: true) + end + + it { is_expected.to contain_exactly(cluster) } + end + describe '#default_value_for' do let(:cluster) { described_class.new } diff --git a/spec/serializers/container_tag_entity_spec.rb b/spec/serializers/container_tag_entity_spec.rb index 6dcc5204516..4beb50c70f8 100644 --- a/spec/serializers/container_tag_entity_spec.rb +++ b/spec/serializers/container_tag_entity_spec.rb @@ -22,7 +22,7 @@ describe ContainerTagEntity do end it 'exposes required informations' do - expect(subject).to include(:name, :location, :revision, :total_size, :created_at) + expect(subject).to include(:name, :location, :revision, :short_revision, :total_size, :created_at) end context 'when user can manage repositories' do diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb index c90bad46295..0bec2054f50 100644 --- a/spec/services/projects/destroy_service_spec.rb +++ b/spec/services/projects/destroy_service_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Projects::DestroyService do + include ProjectForksHelper + let!(:user) { create(:user) } let!(:project) { create(:project, :repository, namespace: user.namespace) } let!(:path) { project.repository.path_to_repo } @@ -212,6 +214,21 @@ describe Projects::DestroyService do end end + context 'for a forked project with LFS objects' do + let(:forked_project) { fork_project(project, user) } + + before do + project.lfs_objects << create(:lfs_object) + forked_project.forked_project_link.destroy + forked_project.reload + end + + it 'destroys the fork' do + expect { destroy_project(forked_project, user) } + .not_to raise_error + end + end + context 'as the root of a fork network' do let!(:fork_network) { create(:fork_network, root_project: project) } |