summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlfredo Sumaran <alfredo@gitlab.com>2016-03-22 11:13:27 -0500
committerAlfredo Sumaran <alfredo@gitlab.com>2016-03-22 11:13:27 -0500
commita42b2c388df5154eecefd8eb7d8a68373e794d6e (patch)
tree7cfcbca6f78d66fb05d56586f322067dcb4d9c92
parent647f28bd1d92eabe239a7f0f4a65eb100c0cda73 (diff)
parent18c049886e0f9ad2d094f02483aea272d0e029fb (diff)
downloadgitlab-ce-a42b2c388df5154eecefd8eb7d8a68373e794d6e.tar.gz
Merge branch 'master' into issue_7959
-rw-r--r--.rubocop.yml958
-rw-r--r--CHANGELOG30
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile6
-rw-r--r--Gemfile.lock39
-rw-r--r--README.md2
-rw-r--r--app/assets/javascripts/application.js.coffee3
-rw-r--r--app/assets/javascripts/aside.js.coffee1
-rw-r--r--app/assets/javascripts/awards_handler.coffee2
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee3
-rw-r--r--app/assets/javascripts/gl_dropdown.js.coffee12
-rw-r--r--app/assets/javascripts/issuable_form.js.coffee52
-rw-r--r--app/assets/javascripts/issue.js.coffee25
-rw-r--r--app/assets/javascripts/issues.js.coffee26
-rw-r--r--app/assets/javascripts/labels_select.js.coffee76
-rw-r--r--app/assets/javascripts/merge_request_tabs.js.coffee25
-rw-r--r--app/assets/javascripts/milestone_select.js.coffee51
-rw-r--r--app/assets/javascripts/notes.js.coffee13
-rw-r--r--app/assets/javascripts/project.js.coffee1
-rw-r--r--app/assets/javascripts/project_new.js.coffee13
-rw-r--r--app/assets/javascripts/stat_graph_contributors_util.js.coffee2
-rw-r--r--app/assets/javascripts/todos.js.coffee56
-rw-r--r--app/assets/javascripts/users_select.js.coffee35
-rw-r--r--app/assets/stylesheets/framework/avatar.scss2
-rw-r--r--app/assets/stylesheets/framework/blocks.scss24
-rw-r--r--app/assets/stylesheets/framework/buttons.scss10
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss8
-rw-r--r--app/assets/stylesheets/framework/filters.scss17
-rw-r--r--app/assets/stylesheets/framework/gitlab-theme.scss2
-rw-r--r--app/assets/stylesheets/framework/layout.scss2
-rw-r--r--app/assets/stylesheets/framework/lists.scss11
-rw-r--r--app/assets/stylesheets/framework/mixins.scss2
-rw-r--r--app/assets/stylesheets/framework/selects.scss2
-rw-r--r--app/assets/stylesheets/framework/typography.scss24
-rw-r--r--app/assets/stylesheets/framework/variables.scss80
-rw-r--r--app/assets/stylesheets/notify.scss24
-rw-r--r--app/assets/stylesheets/pages/admin.scss6
-rw-r--r--app/assets/stylesheets/pages/commits.scss2
-rw-r--r--app/assets/stylesheets/pages/diff.scss12
-rw-r--r--app/assets/stylesheets/pages/events.scss9
-rw-r--r--app/assets/stylesheets/pages/issuable.scss151
-rw-r--r--app/assets/stylesheets/pages/issues.scss8
-rw-r--r--app/assets/stylesheets/pages/login.scss2
-rw-r--r--app/assets/stylesheets/pages/note_form.scss2
-rw-r--r--app/assets/stylesheets/pages/notes.scss12
-rw-r--r--app/assets/stylesheets/pages/profile.scss2
-rw-r--r--app/assets/stylesheets/pages/projects.scss31
-rw-r--r--app/assets/stylesheets/pages/stat_graph.scss2
-rw-r--r--app/assets/stylesheets/pages/todos.scss17
-rw-r--r--app/assets/stylesheets/pages/tree.scss2
-rw-r--r--app/assets/stylesheets/pages/xterm.scss10
-rw-r--r--app/controllers/admin/application_settings_controller.rb1
-rw-r--r--app/controllers/admin/groups_controller.rb8
-rw-r--r--app/controllers/admin/labels_controller.rb2
-rw-r--r--app/controllers/admin/projects_controller.rb7
-rw-r--r--app/controllers/application_controller.rb63
-rw-r--r--app/controllers/autocomplete_controller.rb2
-rw-r--r--app/controllers/concerns/global_milestones.rb2
-rw-r--r--app/controllers/concerns/issuable_actions.rb23
-rw-r--r--app/controllers/concerns/issues_action.rb2
-rw-r--r--app/controllers/concerns/merge_requests_action.rb2
-rw-r--r--app/controllers/dashboard/groups_controller.rb2
-rw-r--r--app/controllers/dashboard/projects_controller.rb4
-rw-r--r--app/controllers/dashboard/snippets_controller.rb2
-rw-r--r--app/controllers/dashboard/todos_controller.rb19
-rw-r--r--app/controllers/dashboard_controller.rb25
-rw-r--r--app/controllers/explore/groups_controller.rb4
-rw-r--r--app/controllers/explore/projects_controller.rb6
-rw-r--r--app/controllers/explore/snippets_controller.rb2
-rw-r--r--app/controllers/groups/application_controller.rb27
-rw-r--r--app/controllers/groups/avatars_controller.rb2
-rw-r--r--app/controllers/groups/group_members_controller.rb3
-rw-r--r--app/controllers/groups/milestones_controller.rb12
-rw-r--r--app/controllers/groups_controller.rb40
-rw-r--r--app/controllers/namespaces_controller.rb2
-rw-r--r--app/controllers/oauth/authorizations_controller.rb1
-rw-r--r--app/controllers/profiles_controller.rb3
-rw-r--r--app/controllers/projects/application_controller.rb72
-rw-r--r--app/controllers/projects/avatars_controller.rb2
-rw-r--r--app/controllers/projects/branches_controller.rb2
-rw-r--r--app/controllers/projects/forks_controller.rb2
-rw-r--r--app/controllers/projects/issues_controller.rb18
-rw-r--r--app/controllers/projects/labels_controller.rb9
-rw-r--r--app/controllers/projects/merge_requests_controller.rb15
-rw-r--r--app/controllers/projects/milestones_controller.rb10
-rw-r--r--app/controllers/projects/snippets_controller.rb2
-rw-r--r--app/controllers/projects/tags_controller.rb2
-rw-r--r--app/controllers/projects/uploads_controller.rb6
-rw-r--r--app/controllers/projects/wikis_controller.rb2
-rw-r--r--app/controllers/projects_controller.rb6
-rw-r--r--app/controllers/snippets_controller.rb2
-rw-r--r--app/controllers/users_controller.rb4
-rw-r--r--app/finders/contributed_projects_finder.rb24
-rw-r--r--app/finders/group_projects_finder.rb42
-rw-r--r--app/finders/groups_finder.rb18
-rw-r--r--app/finders/issuable_finder.rb16
-rw-r--r--app/finders/issues_finder.rb6
-rw-r--r--app/finders/joined_groups_finder.rb24
-rw-r--r--app/finders/personal_projects_finder.rb28
-rw-r--r--app/finders/projects_finder.rb77
-rw-r--r--app/finders/union_finder.rb11
-rw-r--r--app/helpers/application_helper.rb20
-rw-r--r--app/helpers/blob_helper.rb6
-rw-r--r--app/helpers/button_helper.rb30
-rw-r--r--app/helpers/commits_helper.rb2
-rw-r--r--app/helpers/dropdowns_helper.rb2
-rw-r--r--app/helpers/events_helper.rb2
-rw-r--r--app/helpers/groups_helper.rb4
-rw-r--r--app/helpers/issuables_helper.rb17
-rw-r--r--app/helpers/issues_helper.rb17
-rw-r--r--app/helpers/labels_helper.rb34
-rw-r--r--app/helpers/milestones_helper.rb24
-rw-r--r--app/helpers/projects_helper.rb8
-rw-r--r--app/helpers/todos_helper.rb11
-rw-r--r--app/helpers/visibility_level_helper.rb37
-rw-r--r--app/mailers/emails/issues.rb8
-rw-r--r--app/models/ability.rb63
-rw-r--r--app/models/application_setting.rb1
-rw-r--r--app/models/commit_status.rb2
-rw-r--r--app/models/concerns/internal_id.rb5
-rw-r--r--app/models/concerns/issuable.rb11
-rw-r--r--app/models/concerns/milestoneish.rb20
-rw-r--r--app/models/event.rb6
-rw-r--r--app/models/group.rb37
-rw-r--r--app/models/issue.rb32
-rw-r--r--app/models/merge_request.rb20
-rw-r--r--app/models/milestone.rb4
-rw-r--r--app/models/project.rb56
-rw-r--r--app/models/project_wiki.rb12
-rw-r--r--app/models/repository.rb79
-rw-r--r--app/models/todo.rb32
-rw-r--r--app/models/user.rb2
-rw-r--r--app/services/base_service.rb7
-rw-r--r--app/services/ci/create_builds_service.rb2
-rw-r--r--app/services/commits/revert_service.rb7
-rw-r--r--app/services/create_snippet_service.rb3
-rw-r--r--app/services/git_push_service.rb4
-rw-r--r--app/services/groups/base_service.rb9
-rw-r--r--app/services/groups/create_service.rb21
-rw-r--r--app/services/groups/update_service.rb20
-rw-r--r--app/services/issues/close_service.rb6
-rw-r--r--app/services/issues/create_service.rb2
-rw-r--r--app/services/issues/move_service.rb94
-rw-r--r--app/services/merge_requests/base_service.rb13
-rw-r--r--app/services/merge_requests/build_service.rb2
-rw-r--r--app/services/merge_requests/post_merge_service.rb2
-rw-r--r--app/services/notification_service.rb10
-rw-r--r--app/services/projects/autocomplete_service.rb6
-rw-r--r--app/services/projects/create_service.rb10
-rw-r--r--app/services/projects/housekeeping_service.rb2
-rw-r--r--app/services/projects/update_service.rb29
-rw-r--r--app/services/search/global_service.rb2
-rw-r--r--app/services/search/project_service.rb3
-rw-r--r--app/services/system_note_service.rb36
-rw-r--r--app/services/todo_service.rb65
-rw-r--r--app/services/update_snippet_service.rb1
-rw-r--r--app/views/admin/application_settings/_form.html.haml4
-rw-r--r--app/views/admin/applications/_delete_form.html.haml2
-rw-r--r--app/views/admin/groups/_form.html.haml2
-rw-r--r--app/views/admin/groups/index.html.haml3
-rw-r--r--app/views/admin/groups/show.html.haml5
-rw-r--r--app/views/admin/labels/_label.html.haml2
-rw-r--r--app/views/admin/projects/show.html.haml2
-rw-r--r--app/views/dashboard/todos/_todo.html.haml6
-rw-r--r--app/views/dashboard/todos/index.html.haml14
-rw-r--r--app/views/devise/sessions/_new_crowd.html.haml2
-rw-r--r--app/views/doorkeeper/applications/new.html.haml2
-rw-r--r--app/views/doorkeeper/authorizations/error.html.haml2
-rw-r--r--app/views/doorkeeper/authorizations/show.html.haml2
-rw-r--r--app/views/events/_event.html.haml2
-rw-r--r--app/views/events/event/_common.html.haml2
-rw-r--r--app/views/groups/edit.html.haml3
-rw-r--r--app/views/groups/new.html.haml2
-rw-r--r--app/views/groups/show.html.haml63
-rw-r--r--app/views/layouts/header/_default.html.haml11
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml2
-rw-r--r--app/views/layouts/nav/_explore.html.haml2
-rw-r--r--app/views/layouts/nav/_group.html.haml70
-rw-r--r--app/views/layouts/nav/_project.html.haml2
-rw-r--r--app/views/layouts/notify.html.haml30
-rw-r--r--app/views/notify/issue_moved_email.html.haml6
-rw-r--r--app/views/notify/issue_moved_email.text.erb4
-rw-r--r--app/views/profiles/keys/_key.html.haml2
-rw-r--r--app/views/projects/_builds_settings.html.haml60
-rw-r--r--app/views/projects/_home_panel.html.haml18
-rw-r--r--app/views/projects/branches/_branch.html.haml4
-rw-r--r--app/views/projects/buttons/_download.html.haml2
-rw-r--r--app/views/projects/buttons/_fork.html.haml4
-rw-r--r--app/views/projects/buttons/_notifications.html.haml2
-rw-r--r--app/views/projects/buttons/_star.html.haml4
-rw-r--r--app/views/projects/compare/_form.html.haml2
-rw-r--r--app/views/projects/diffs/_diffs.html.haml2
-rw-r--r--app/views/projects/diffs/_file.html.haml24
-rw-r--r--app/views/projects/diffs/_image.html.haml5
-rw-r--r--app/views/projects/diffs/_line.html.haml26
-rw-r--r--app/views/projects/diffs/_text_file.html.haml21
-rw-r--r--app/views/projects/edit.html.haml65
-rw-r--r--app/views/projects/forks/new.html.haml4
-rw-r--r--app/views/projects/issues/_issue.html.haml7
-rw-r--r--app/views/projects/issues/show.html.haml14
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml8
-rw-r--r--app/views/projects/merge_requests/show/_mr_box.html.haml5
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml15
-rw-r--r--app/views/projects/merge_requests/widget/open/_wip.html.haml10
-rw-r--r--app/views/projects/milestones/show.html.haml2
-rw-r--r--app/views/projects/notes/_edit_form.html.haml2
-rw-r--r--app/views/projects/notes/_note.html.haml10
-rw-r--r--app/views/projects/repositories/_download_archive.html.haml2
-rw-r--r--app/views/projects/tags/_tag.html.haml4
-rw-r--r--app/views/projects/tags/show.html.haml8
-rw-r--r--app/views/projects/tree/_tree_header.html.haml2
-rw-r--r--app/views/search/results/_issue.html.haml1
-rw-r--r--app/views/search/results/_milestone.html.haml2
-rw-r--r--app/views/shared/_clone_panel.html.haml6
-rw-r--r--app/views/shared/_group_tips.html.haml1
-rw-r--r--app/views/shared/_label_row.html.haml2
-rw-r--r--app/views/shared/groups/_group.html.haml18
-rw-r--r--app/views/shared/issuable/_filter.html.haml67
-rw-r--r--app/views/shared/issuable/_form.html.haml64
-rw-r--r--app/views/shared/issuable/_label_dropdown.html.haml39
-rw-r--r--app/views/shared/issuable/_milestone_dropdown.html.haml16
-rw-r--r--app/views/shared/issuable/_participants.html.haml16
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml48
-rw-r--r--app/views/shared/milestones/_issuable.html.haml4
-rw-r--r--app/views/shared/milestones/_labels_tab.html.haml2
-rw-r--r--app/views/shared/milestones/_milestone.html.haml4
-rw-r--r--app/views/shared/milestones/_summary.html.haml8
-rw-r--r--app/views/shared/milestones/_tabs.html.haml4
-rw-r--r--app/views/shared/milestones/_top.html.haml4
-rw-r--r--app/views/shared/projects/_project.html.haml37
-rw-r--r--app/views/shared/snippets/_header.html.haml2
-rw-r--r--app/views/shared/snippets/_snippet.html.haml4
-rw-r--r--app/views/votes/_votes_block.html.haml2
-rw-r--r--app/workers/gitlab_shell_one_shot_worker.rb10
-rw-r--r--app/workers/repository_fork_worker.rb5
-rw-r--r--config/application.rb1
-rw-r--r--config/initializers/premailer.rb7
-rw-r--r--config/routes.rb7
-rw-r--r--db/migrate/20160223192159_add_confidential_to_issues.rb6
-rw-r--r--db/migrate/20160225090018_add_delete_at_to_issues.rb6
-rw-r--r--db/migrate/20160225101956_add_delete_at_to_merge_requests.rb6
-rw-r--r--db/migrate/20160301124843_add_visibility_level_to_groups.rb29
-rw-r--r--db/migrate/20160308212903_add_default_group_visibility_to_application_settings.rb29
-rw-r--r--db/migrate/20160316192622_change_target_id_to_null_on_todos.rb5
-rw-r--r--db/migrate/20160316204731_add_commit_id_to_todos.rb6
-rw-r--r--db/migrate/20160317092222_add_moved_to_to_issue.rb5
-rw-r--r--db/migrate/20160320204112_index_namespaces_on_visibility_level.rb7
-rw-r--r--db/schema.rb16
-rw-r--r--doc/README.md37
-rw-r--r--doc/api/groups.md1
-rw-r--r--doc/api/issues.md18
-rw-r--r--doc/api/merge_requests.md19
-rw-r--r--doc/ci/yaml/README.md15
-rw-r--r--doc/gitlab-basics/README.md34
-rw-r--r--doc/gitlab-basics/basic-git-commands.md58
-rw-r--r--doc/gitlab-basics/start-using-git.md63
-rw-r--r--doc/hooks/custom_hooks.md2
-rw-r--r--doc/install/installation.md12
-rw-r--r--doc/intro/README.md41
-rw-r--r--doc/public_access/public_access.md23
-rw-r--r--doc/release/security.md2
-rw-r--r--doc/security/two_factor_authentication.md2
-rw-r--r--doc/update/8.2-to-8.3.md9
-rw-r--r--doc/update/8.3-to-8.4.md9
-rw-r--r--doc/update/8.4-to-8.5.md9
-rw-r--r--doc/update/8.5-to-8.6.md11
-rw-r--r--doc/workflow/award_emoji.md48
-rw-r--r--doc/workflow/img/award_emoji_select.pngbin0 -> 65985 bytes
-rw-r--r--doc/workflow/img/award_emoji_votes_least_popular.pngbin0 -> 144501 bytes
-rw-r--r--doc/workflow/img/award_emoji_votes_most_popular.pngbin0 -> 136577 bytes
-rw-r--r--doc/workflow/img/award_emoji_votes_sort_options.pngbin0 -> 162251 bytes
-rw-r--r--doc/workflow/importing/import_projects_from_bitbucket.md2
-rw-r--r--doc/workflow/protected_branches.md2
-rw-r--r--features/project/issues/award_emoji.feature11
-rw-r--r--features/project/issues/issues.feature1
-rw-r--r--features/project/merge_requests.feature8
-rw-r--r--features/steps/dashboard/todos.rb1
-rw-r--r--features/steps/groups.rb2
-rw-r--r--features/steps/project/create.rb4
-rw-r--r--features/steps/project/issues/award_emoji.rb4
-rw-r--r--features/steps/project/issues/issues.rb3
-rw-r--r--lib/api/entities.rb3
-rw-r--r--lib/api/groups.rb2
-rw-r--r--lib/api/helpers.rb4
-rw-r--r--lib/api/issues.rb10
-rw-r--r--lib/api/merge_requests.rb12
-rw-r--r--lib/banzai/filter/issue_reference_filter.rb5
-rw-r--r--lib/banzai/filter/reference_filter.rb1
-rw-r--r--lib/ci/gitlab_ci_yaml_processor.rb17
-rw-r--r--lib/gitlab/diff/file.rb4
-rw-r--r--lib/gitlab/gfm/reference_rewriter.rb79
-rw-r--r--lib/gitlab/project_search_results.rb3
-rw-r--r--lib/gitlab/reference_extractor.rb18
-rw-r--r--lib/gitlab/search_results.rb7
-rw-r--r--lib/gitlab/visibility_level.rb12
-rw-r--r--lib/support/nginx/gitlab11
-rw-r--r--lib/support/nginx/gitlab-ssl10
-rw-r--r--public/404.html44
-rw-r--r--public/422.html45
-rw-r--r--public/500.html44
-rw-r--r--public/502.html44
-rw-r--r--public/deploy.html41
-rw-r--r--public/logo.svg9
-rw-r--r--public/static.css36
-rw-r--r--spec/controllers/application_controller_spec.rb40
-rw-r--r--spec/controllers/groups/avatars_controller_spec.rb3
-rw-r--r--spec/controllers/namespaces_controller_spec.rb31
-rw-r--r--spec/controllers/projects/avatars_controller_spec.rb2
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb185
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb23
-rw-r--r--spec/controllers/uploads_controller_spec.rb20
-rw-r--r--spec/factories/broadcast_messages.rb2
-rw-r--r--spec/factories/groups.rb12
-rw-r--r--spec/factories/issues.rb4
-rw-r--r--spec/factories/merge_requests.rb5
-rw-r--r--spec/factories/todos.rb10
-rw-r--r--spec/features/issues/move_spec.rb87
-rw-r--r--spec/features/issues/new_branch_button_spec.rb2
-rw-r--r--spec/features/merge_requests/filter_by_milestone_spec.rb2
-rw-r--r--spec/features/security/group/internal_access_spec.rb109
-rw-r--r--spec/features/security/group/private_access_spec.rb109
-rw-r--r--spec/features/security/group/public_access_spec.rb109
-rw-r--r--spec/features/security/group_access_spec.rb284
-rw-r--r--spec/features/security/project/internal_access_spec.rb109
-rw-r--r--spec/features/security/project/private_access_spec.rb110
-rw-r--r--spec/features/security/project/public_access_spec.rb111
-rw-r--r--spec/finders/group_projects_finder_spec.rb89
-rw-r--r--spec/finders/groups_finder_spec.rb33
-rw-r--r--spec/finders/joined_groups_finder_spec.rb77
-rw-r--r--spec/finders/personal_projects_finder_spec.rb28
-rw-r--r--spec/finders/projects_finder_spec.rb2
-rw-r--r--spec/finders/snippets_finder_spec.rb2
-rw-r--r--spec/helpers/groups_helper_spec.rb (renamed from spec/helpers/groups_helper.rb)0
-rw-r--r--spec/helpers/labels_helper_spec.rb12
-rw-r--r--spec/helpers/projects_helper_spec.rb29
-rw-r--r--spec/helpers/visibility_level_helper_spec.rb17
-rw-r--r--spec/lib/banzai/filter/label_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/redactor_filter_spec.rb76
-rw-r--r--spec/lib/ci/gitlab_ci_yaml_processor_spec.rb44
-rw-r--r--spec/lib/ci/status_spec.rb23
-rw-r--r--spec/lib/gitlab/closing_issue_extractor_spec.rb1
-rw-r--r--spec/lib/gitlab/diff/file_spec.rb14
-rw-r--r--spec/lib/gitlab/gfm/reference_rewriter_spec.rb89
-rw-r--r--spec/lib/gitlab/project_search_results_spec.rb69
-rw-r--r--spec/lib/gitlab/reference_extractor_spec.rb22
-rw-r--r--spec/lib/gitlab/search_results_spec.rb91
-rw-r--r--spec/mailers/notify_spec.rb27
-rw-r--r--spec/models/commit_spec.rb13
-rw-r--r--spec/models/concerns/mentionable_spec.rb5
-rw-r--r--spec/models/concerns/milestoneish_spec.rb104
-rw-r--r--spec/models/event_spec.rb36
-rw-r--r--spec/models/group_spec.rb17
-rw-r--r--spec/models/issue_spec.rb65
-rw-r--r--spec/models/merge_request_spec.rb81
-rw-r--r--spec/models/milestone_spec.rb20
-rw-r--r--spec/models/project_security_spec.rb10
-rw-r--r--spec/models/project_spec.rb67
-rw-r--r--spec/models/project_wiki_spec.rb12
-rw-r--r--spec/models/repository_spec.rb141
-rw-r--r--spec/models/todo_spec.rb85
-rw-r--r--spec/requests/api/group_members_spec.rb2
-rw-r--r--spec/requests/api/groups_spec.rb2
-rw-r--r--spec/requests/api/issues_spec.rb136
-rw-r--r--spec/requests/api/merge_requests_spec.rb37
-rw-r--r--spec/requests/api/projects_spec.rb1
-rw-r--r--spec/services/create_snippet_service_spec.rb2
-rw-r--r--spec/services/git_push_service_spec.rb13
-rw-r--r--spec/services/groups/create_service_spec.rb20
-rw-r--r--spec/services/groups/update_service_spec.rb52
-rw-r--r--spec/services/issues/move_service_spec.rb213
-rw-r--r--spec/services/projects/autocomplete_service_spec.rb79
-rw-r--r--spec/services/projects/housekeeping_service_spec.rb4
-rw-r--r--spec/services/system_note_service_spec.rb53
-rw-r--r--spec/services/todo_service_spec.rb9
-rw-r--r--spec/services/update_snippet_service_spec.rb2
-rw-r--r--spec/support/matchers/access_matchers.rb2
-rw-r--r--spec/support/mentionable_shared_examples.rb2
-rw-r--r--spec/workers/repository_fork_worker_spec.rb19
378 files changed, 6320 insertions, 2624 deletions
diff --git a/.rubocop.yml b/.rubocop.yml
index 89aa0591c31..71273ce6098 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -1,1041 +1,1061 @@
+AllCops:
+ TargetRubyVersion: 2.1
+ # Cop names are not displayed in offense messages by default. Change behavior
+ # by overriding DisplayCopNames, or by giving the -D/--display-cop-names
+ # option.
+ DisplayCopNames: true
+ # Style guide URLs are not displayed in offense messages by default. Change
+ # behavior by overriding DisplayStyleGuide, or by giving the
+ # -S/--display-style-guide option.
+ DisplayStyleGuide: false
+ # Exclude some GitLab files
+ Exclude:
+ - 'vendor/**/*'
+ - 'db/**/*'
+ - 'tmp/**/*'
+ - 'bin/**/*'
+ - 'lib/backup/**/*'
+ - 'lib/ci/backup/**/*'
+ - 'lib/tasks/**/*'
+ - 'lib/ci/migrate/**/*'
+ - 'lib/email_validator.rb'
+ - 'lib/gitlab/upgrader.rb'
+ - 'lib/gitlab/seeder.rb'
+
+
+##################### Style ##################################
+
+# Check indentation of private/protected visibility modifiers.
Style/AccessModifierIndentation:
- Description: Check indentation of private/protected visibility modifiers.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#indent-public-private-protected'
Enabled: true
+# Check the naming of accessor methods for get_/set_.
Style/AccessorMethodName:
- Description: Check the naming of accessor methods for get_/set_.
Enabled: false
+# Use alias_method instead of alias.
Style/Alias:
- Description: 'Use alias_method instead of alias.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#alias-method'
+ EnforcedStyle: prefer_alias_method
Enabled: true
+# Align the elements of an array literal if they span more than one line.
Style/AlignArray:
- Description: >-
- Align the elements of an array literal if they span more than
- one line.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#align-multiline-arrays'
Enabled: true
+# Align the elements of a hash literal if they span more than one line.
Style/AlignHash:
- Description: >-
- Align the elements of a hash literal if they span more than
- one line.
Enabled: true
+# Align the parameters of a method call if they span more than one line.
Style/AlignParameters:
- Description: >-
- Align the parameters of a method call if they span more
- than one line.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-double-indent'
Enabled: false
+# Use &&/|| instead of and/or.
Style/AndOr:
- Description: 'Use &&/|| instead of and/or.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-and-or-or'
Enabled: false
+# Use `Array#join` instead of `Array#*`.
Style/ArrayJoin:
- Description: 'Use Array#join instead of Array#*.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#array-join'
Enabled: false
+# Use only ascii symbols in comments.
Style/AsciiComments:
- Description: 'Use only ascii symbols in comments.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-comments'
Enabled: true
+# Use only ascii symbols in identifiers.
Style/AsciiIdentifiers:
- Description: 'Use only ascii symbols in identifiers.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-identifiers'
Enabled: true
+# Checks for uses of Module#attr.
Style/Attr:
- Description: 'Checks for uses of Module#attr.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr'
Enabled: false
+# Avoid the use of BEGIN blocks.
Style/BeginBlock:
- Description: 'Avoid the use of BEGIN blocks.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-BEGIN-blocks'
Enabled: true
+# Checks if usage of %() or %Q() matches configuration.
Style/BarePercentLiterals:
- Description: 'Checks if usage of %() or %Q() matches configuration.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-q-shorthand'
Enabled: false
+# Do not use block comments.
Style/BlockComments:
- Description: 'Do not use block comments.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-block-comments'
Enabled: false
+# Put end statement of multiline block on its own line.
Style/BlockEndNewline:
- Description: 'Put end statement of multiline block on its own line.'
Enabled: true
+# Avoid using {...} for multi-line blocks (multiline chaining is # always
+# ugly). Prefer {...} over do...end for single-line blocks.
Style/BlockDelimiters:
- Description: >-
- Avoid using {...} for multi-line blocks (multiline chaining is
- always ugly).
- Prefer {...} over do...end for single-line blocks.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks'
Enabled: true
+# Enforce braces style around hash parameters.
Style/BracesAroundHashParameters:
- Description: 'Enforce braces style around hash parameters.'
Enabled: false
+# Avoid explicit use of the case equality operator(===).
Style/CaseEquality:
- Description: 'Avoid explicit use of the case equality operator(===).'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-case-equality'
Enabled: false
+# Indentation of when in a case/when/[else/]end.
Style/CaseIndentation:
- Description: 'Indentation of when in a case/when/[else/]end.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#indent-when-to-case'
Enabled: true
+# Checks for uses of character literals.
Style/CharacterLiteral:
- Description: 'Checks for uses of character literals.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-character-literals'
Enabled: true
+# Use CamelCase for classes and modules.'
Style/ClassAndModuleCamelCase:
- Description: 'Use CamelCase for classes and modules.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#camelcase-classes'
Enabled: true
+# Checks style of children classes and modules.
Style/ClassAndModuleChildren:
- Description: 'Checks style of children classes and modules.'
Enabled: false
+# Enforces consistent use of `Object#is_a?` or `Object#kind_of?`.
Style/ClassCheck:
- Description: 'Enforces consistent use of `Object#is_a?` or `Object#kind_of?`.'
Enabled: false
+# Use self when defining module/class methods.
Style/ClassMethods:
- Description: 'Use self when defining module/class methods.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#def-self-singletons'
Enabled: false
+# Avoid the use of class variables.
Style/ClassVars:
- Description: 'Avoid the use of class variables.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-class-vars'
Enabled: true
+# Do not use :: for method call.
Style/ColonMethodCall:
- Description: 'Do not use :: for method call.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#double-colons'
Enabled: false
+# Checks formatting of special comments (TODO, FIXME, OPTIMIZE, HACK, REVIEW).
Style/CommentAnnotation:
- Description: >-
- Checks formatting of special comments
- (TODO, FIXME, OPTIMIZE, HACK, REVIEW).
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#annotate-keywords'
Enabled: false
+# Indentation of comments.
Style/CommentIndentation:
- Description: 'Indentation of comments.'
Enabled: true
+# Use the return value of `if` and `case` statements for assignment to a
+# variable and variable comparison instead of assigning that variable
+# inside of each branch.
+Style/ConditionalAssignment:
+ Enabled: false
+
+# Constants should use SCREAMING_SNAKE_CASE.
Style/ConstantName:
- Description: 'Constants should use SCREAMING_SNAKE_CASE.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#screaming-snake-case'
Enabled: true
+# Use def with parentheses when there are arguments.
Style/DefWithParentheses:
- Description: 'Use def with parentheses when there are arguments.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#method-parens'
Enabled: false
+# Checks for use of deprecated Hash methods.
Style/DeprecatedHashMethods:
- Description: 'Checks for use of deprecated Hash methods.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-key'
Enabled: false
+# Document classes and non-namespace modules.
Style/Documentation:
- Description: 'Document classes and non-namespace modules.'
Enabled: false
+# Checks the position of the dot in multi-line method calls.
Style/DotPosition:
- Description: 'Checks the position of the dot in multi-line method calls.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains'
Enabled: false
+# Checks for uses of double negation (!!).
Style/DoubleNegation:
- Description: 'Checks for uses of double negation (!!).'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-bang-bang'
Enabled: false
+# Prefer `each_with_object` over `inject` or `reduce`.
Style/EachWithObject:
- Description: 'Prefer `each_with_object` over `inject` or `reduce`.'
Enabled: false
+# Align elses and elsifs correctly.
Style/ElseAlignment:
- Description: 'Align elses and elsifs correctly.'
Enabled: true
+# Avoid empty else-clauses.
Style/EmptyElse:
- Description: 'Avoid empty else-clauses.'
Enabled: false
+# Use empty lines between defs.
Style/EmptyLineBetweenDefs:
- Description: 'Use empty lines between defs.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#empty-lines-between-methods'
Enabled: false
+# Don't use several empty lines in a row.
Style/EmptyLines:
- Description: "Don't use several empty lines in a row."
Enabled: false
+# Keep blank lines around access modifiers.
Style/EmptyLinesAroundAccessModifier:
- Description: "Keep blank lines around access modifiers."
Enabled: false
+# Keeps track of empty lines around block bodies.
Style/EmptyLinesAroundBlockBody:
- Description: "Keeps track of empty lines around block bodies."
Enabled: false
+# Keeps track of empty lines around class bodies.
Style/EmptyLinesAroundClassBody:
- Description: "Keeps track of empty lines around class bodies."
Enabled: false
+# Keeps track of empty lines around module bodies.
Style/EmptyLinesAroundModuleBody:
- Description: "Keeps track of empty lines around module bodies."
Enabled: false
+# Keeps track of empty lines around method bodies.
Style/EmptyLinesAroundMethodBody:
- Description: "Keeps track of empty lines around method bodies."
Enabled: false
+# Prefer literals to Array.new/Hash.new/String.new.
Style/EmptyLiteral:
- Description: 'Prefer literals to Array.new/Hash.new/String.new.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#literal-array-hash'
Enabled: false
+# Avoid the use of END blocks.
Style/EndBlock:
- Description: 'Avoid the use of END blocks.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-END-blocks'
Enabled: false
+# Use Unix-style line endings.
Style/EndOfLine:
- Description: 'Use Unix-style line endings.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#crlf'
Enabled: false
+# Favor the use of Fixnum#even? && Fixnum#odd?
Style/EvenOdd:
- Description: 'Favor the use of Fixnum#even? && Fixnum#odd?'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods'
Enabled: false
+# Do not use unnecessary spacing.
Style/ExtraSpacing:
- Description: 'Do not use unnecessary spacing.'
Enabled: false
+# Use snake_case for source file names.
Style/FileName:
- Description: 'Use snake_case for source file names.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-files'
Enabled: false
+# Checks for flip flops.
Style/FlipFlop:
- Description: 'Checks for flip flops'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-flip-flops'
Enabled: false
+# Checks use of for or each in multiline loops.
Style/For:
- Description: 'Checks use of for or each in multiline loops.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-for-loops'
Enabled: false
+# Enforce the use of Kernel#sprintf, Kernel#format or String#%.
Style/FormatString:
- Description: 'Enforce the use of Kernel#sprintf, Kernel#format or String#%.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#sprintf'
Enabled: false
+# Do not introduce global variables.
Style/GlobalVars:
- Description: 'Do not introduce global variables.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#instance-vars'
Enabled: false
+# Check for conditionals that can be replaced with guard clauses.
Style/GuardClause:
- Description: 'Check for conditionals that can be replaced with guard clauses'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals'
Enabled: false
+# Prefer Ruby 1.9 hash syntax `{ a: 1, b: 2 }`
+# over 1.8 syntax `{ :a => 1, :b => 2 }`.
Style/HashSyntax:
- Description: >-
- Prefer Ruby 1.9 hash syntax { a: 1, b: 2 } over 1.8 syntax
- { :a => 1, :b => 2 }.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-literals'
Enabled: true
+# Finds if nodes inside else, which can be converted to elsif.
+Style/IfInsideElse:
+ Enabled: false
+
+# Favor modifier if/unless usage when you have a single-line body.
Style/IfUnlessModifier:
- Description: >-
- Favor modifier if/unless usage when you have a
- single-line body.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier'
Enabled: false
+# Do not use if x; .... Use the ternary operator instead.
Style/IfWithSemicolon:
- Description: 'Do not use if x; .... Use the ternary operator instead.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-semicolon-ifs'
Enabled: false
+# Checks that conditional statements do not have an identical line at the
+# end of each branch, which can validly be moved out of the conditional.
+Style/IdenticalConditionalBranches:
+ Enabled: false
+
+# Checks the indentation of the first line of the right-hand-side of a
+# multi-line assignment.
+Style/IndentAssignment:
+ Enabled: false
+
+# Keep indentation straight.
Style/IndentationConsistency:
- Description: 'Keep indentation straight.'
Enabled: true
+# Use 2 spaces for indentation.
Style/IndentationWidth:
- Description: 'Use 2 spaces for indentation.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-indentation'
Enabled: true
+# Checks the indentation of the first element in an array literal.
Style/IndentArray:
- Description: >-
- Checks the indentation of the first element in an array
- literal.
Enabled: false
+# Checks the indentation of the first key in a hash literal.
Style/IndentHash:
- Description: 'Checks the indentation of the first key in a hash literal.'
Enabled: false
+# Use Kernel#loop for infinite loops.
Style/InfiniteLoop:
- Description: 'Use Kernel#loop for infinite loops.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#infinite-loop'
Enabled: false
+# Use the new lambda literal syntax for single-line blocks.
Style/Lambda:
- Description: 'Use the new lambda literal syntax for single-line blocks.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#lambda-multi-line'
Enabled: false
+# Use lambda.call(...) instead of lambda.(...).
Style/LambdaCall:
- Description: 'Use lambda.call(...) instead of lambda.(...).'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc-call'
Enabled: false
+# Comments should start with a space.
Style/LeadingCommentSpace:
- Description: 'Comments should start with a space.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-space'
Enabled: false
+# Use \ instead of + or << to concatenate two string literals at line end.
Style/LineEndConcatenation:
- Description: >-
- Use \ instead of + or << to concatenate two string literals at
- line end.
Enabled: false
+# Do not use parentheses for method calls with no arguments.
Style/MethodCallParentheses:
- Description: 'Do not use parentheses for method calls with no arguments.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-args-no-parens'
Enabled: false
+# Checks if the method definitions have or don't have parentheses.
Style/MethodDefParentheses:
- Description: >-
- Checks if the method definitions have or don't have
- parentheses.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#method-parens'
Enabled: false
+# Use the configured style when naming methods.
Style/MethodName:
- Description: 'Use the configured style when naming methods.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars'
Enabled: false
+# Checks for usage of `extend self` in modules.
Style/ModuleFunction:
- Description: 'Checks for usage of `extend self` in modules.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#module-function'
Enabled: false
+# Avoid multi-line chains of blocks.
Style/MultilineBlockChain:
- Description: 'Avoid multi-line chains of blocks.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks'
Enabled: false
+# Ensures newlines after multiline block do statements.
Style/MultilineBlockLayout:
- Description: 'Ensures newlines after multiline block do statements.'
Enabled: true
+# Do not use then for multi-line if/unless.
Style/MultilineIfThen:
- Description: 'Do not use then for multi-line if/unless.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-then'
Enabled: false
+# Checks indentation of method calls with the dot operator that span more than
+# one line.
+Style/MultilineMethodCallIndentation:
+ Enabled: false
+
+# Checks indentation of binary operations that span more than one line.
Style/MultilineOperationIndentation:
- Description: >-
- Checks indentation of binary operations that span more than
- one line.
Enabled: false
+# Avoid multi-line `? :` (the ternary operator), use if/unless instead.
Style/MultilineTernaryOperator:
- Description: >-
- Avoid multi-line ?: (the ternary operator);
- use if/unless instead.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-multiline-ternary'
Enabled: false
+# Do not assign mutable objects to constants.
+Style/MutableConstant:
+ Enabled: false
+
+# Favor unless over if for negative conditions (or control flow or).
Style/NegatedIf:
- Description: >-
- Favor unless over if for negative conditions
- (or control flow or).
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#unless-for-negatives'
Enabled: false
+# Favor until over while for negative conditions.
Style/NegatedWhile:
- Description: 'Favor until over while for negative conditions.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#until-for-negatives'
Enabled: false
+# Avoid using nested modifiers.
+Style/NestedModifier:
+ Enabled: false
+
+# Parenthesize method calls which are nested inside the argument list of
+# another parenthesized method call.
+Style/NestedParenthesizedCalls:
+ Enabled: false
+
+# Use one expression per branch in a ternary operator.
Style/NestedTernaryOperator:
- Description: 'Use one expression per branch in a ternary operator.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-ternary'
Enabled: true
+# Use `next` to skip iteration instead of a condition at the end.
Style/Next:
- Description: 'Use `next` to skip iteration instead of a condition at the end.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals'
Enabled: false
+# Prefer x.nil? to x == nil.
Style/NilComparison:
- Description: 'Prefer x.nil? to x == nil.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods'
Enabled: true
+# Checks for redundant nil checks.
Style/NonNilCheck:
- Description: 'Checks for redundant nil checks.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-non-nil-checks'
Enabled: true
+# Use ! instead of not.
Style/Not:
- Description: 'Use ! instead of not.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bang-not-not'
Enabled: true
+# Add underscores to large numeric literals to improve their readability.
Style/NumericLiterals:
- Description: >-
- Add underscores to large numeric literals to improve their
- readability.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscores-in-numerics'
Enabled: false
+# Favor the ternary operator(?:) over if/then/else/end constructs.
Style/OneLineConditional:
- Description: >-
- Favor the ternary operator(?:) over
- if/then/else/end constructs.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#ternary-operator'
Enabled: true
+# When defining binary operators, name the argument other.
Style/OpMethod:
- Description: 'When defining binary operators, name the argument other.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#other-arg'
Enabled: false
+# Check for simple usages of parallel assignment. It will only warn when
+# the number of variables matches on both sides of the assignment.
Style/ParallelAssignment:
- Description: >-
- Check for simple usages of parallel assignment.
- It will only warn when the number of variables
- matches on both sides of the assignment.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parallel-assignment'
Enabled: false
+# Don't use parentheses around the condition of an if/unless/while.
Style/ParenthesesAroundCondition:
- Description: >-
- Don't use parentheses around the condition of an
- if/unless/while.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-parens-if'
Enabled: true
+# Use `%`-literal delimiters consistently.
Style/PercentLiteralDelimiters:
- Description: 'Use `%`-literal delimiters consistently'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-literal-braces'
Enabled: false
+# Checks if uses of %Q/%q match the configured preference.
Style/PercentQLiterals:
- Description: 'Checks if uses of %Q/%q match the configured preference.'
Enabled: false
+# Avoid Perl-style regex back references.
Style/PerlBackrefs:
- Description: 'Avoid Perl-style regex back references.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers'
Enabled: false
+# Check the names of predicate methods.
Style/PredicateName:
- Description: 'Check the names of predicate methods.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark'
Enabled: false
+# Use proc instead of Proc.new.
Style/Proc:
- Description: 'Use proc instead of Proc.new.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc'
Enabled: false
+# Checks the arguments passed to raise/fail.
Style/RaiseArgs:
- Description: 'Checks the arguments passed to raise/fail.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#exception-class-messages'
Enabled: false
+# Don't use begin blocks when they are not needed.
Style/RedundantBegin:
- Description: "Don't use begin blocks when they are not needed."
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#begin-implicit'
Enabled: false
+# Checks for an obsolete RuntimeException argument in raise/fail.
Style/RedundantException:
- Description: "Checks for an obsolete RuntimeException argument in raise/fail."
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-explicit-runtimeerror'
Enabled: false
+# Checks usages of Object#freeze on immutable objects.
+Style/RedundantFreeze:
+ Enabled: false
+
+# TODO: Enable RedundantParentheses Cop.
+# Checks for parentheses that seem not to serve any purpose.
+Style/RedundantParentheses:
+ Enabled: false
+
+# Don't use return where it's not required.
Style/RedundantReturn:
- Description: "Don't use return where it's not required."
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-explicit-return'
Enabled: true
+# Don't use self where it's not needed.
Style/RedundantSelf:
- Description: "Don't use self where it's not needed."
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-self-unless-required'
Enabled: false
+# Use %r for regular expressions matching more than `MaxSlashes` '/'
+# characters. Use %r only for regular expressions matching more
+# than `MaxSlashes` '/' character.
Style/RegexpLiteral:
- Description: >-
- Use %r for regular expressions matching more than
- `MaxSlashes` '/' characters.
- Use %r only for regular expressions matching more than
- `MaxSlashes` '/' character.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-r'
Enabled: false
+# Avoid using rescue in its modifier form.
Style/RescueModifier:
- Description: 'Avoid using rescue in its modifier form.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-rescue-modifiers'
Enabled: false
+# Checks for places where self-assignment shorthand should have been used.
Style/SelfAssignment:
- Description: >-
- Checks for places where self-assignment shorthand should have
- been used.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#self-assignment'
Enabled: false
+# Don't use semicolons to terminate expressions.
Style/Semicolon:
- Description: "Don't use semicolons to terminate expressions."
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-semicolon'
Enabled: false
+# Checks for proper usage of fail and raise.
Style/SignalException:
- Description: 'Checks for proper usage of fail and raise.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#fail-method'
Enabled: false
+# Enforces the names of some block params.
Style/SingleLineBlockParams:
- Description: 'Enforces the names of some block params.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#reduce-blocks'
Enabled: false
+# Avoid single-line methods.
Style/SingleLineMethods:
- Description: 'Avoid single-line methods.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-single-line-methods'
- Enabled: false
-
-Style/SingleSpaceBeforeFirstArg:
- Description: >-
- Checks that exactly one space is used between a method name
- and the first argument for method calls without parentheses.
Enabled: false
+# Use spaces after colons.
Style/SpaceAfterColon:
- Description: 'Use spaces after colons.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators'
Enabled: false
+# Use spaces after commas.
Style/SpaceAfterComma:
- Description: 'Use spaces after commas.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators'
- Enabled: false
-
-Style/SpaceAfterControlKeyword:
- Description: 'Use spaces after if/elsif/unless/while/until/case/when.'
Enabled: false
+# Do not put a space between a method name and the opening parenthesis in a
+# method definition.
Style/SpaceAfterMethodName:
- Description: >-
- Do not put a space between a method name and the opening
- parenthesis in a method definition.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces'
Enabled: false
+# Tracks redundant space after the ! operator.
Style/SpaceAfterNot:
- Description: Tracks redundant space after the ! operator.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-space-bang'
Enabled: false
+# Use spaces after semicolons.
Style/SpaceAfterSemicolon:
- Description: 'Use spaces after semicolons.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators'
Enabled: false
-Style/SpaceBeforeBlockBraces:
- Description: >-
- Checks that the left block brace has or doesn't have space
- before it.
+# Checks that the equals signs in parameter default assignments have or don't
+# have surrounding space depending on configuration.
+Style/SpaceAroundEqualsInParameterDefault:
Enabled: false
-Style/SpaceBeforeComma:
- Description: 'No spaces before commas.'
+# TODO: Enable SpaceAroundKeyword Cop.
+# Use a space around keywords if appropriate.
+Style/SpaceAroundKeyword:
Enabled: false
-Style/SpaceBeforeComment:
- Description: >-
- Checks for missing space between code and a comment on the
- same line.
+# Use a single space around operators.
+Style/SpaceAroundOperators:
Enabled: false
-Style/SpaceBeforeSemicolon:
- Description: 'No spaces before semicolons.'
+# Checks that the left block brace has or doesn't have space before it.
+Style/SpaceBeforeBlockBraces:
Enabled: false
-Style/SpaceInsideBlockBraces:
- Description: >-
- Checks that block braces have or don't have surrounding space.
- For blocks taking parameters, checks that the left brace has
- or doesn't have trailing space.
+# No spaces before commas.
+Style/SpaceBeforeComma:
Enabled: false
-Style/SpaceAroundEqualsInParameterDefault:
- Description: >-
- Checks that the equals signs in parameter default assignments
- have or don't have surrounding space depending on
- configuration.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-around-equals'
+# Checks for missing space between code and a comment on the same line.
+Style/SpaceBeforeComment:
Enabled: false
-Style/SpaceAroundOperators:
- Description: 'Use spaces around operators.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators'
+# Checks that exactly one space is used between a method name and the first
+# argument for method calls without parentheses.
+Style/SpaceBeforeFirstArg:
+ Enabled: false
+
+# No spaces before semicolons.
+Style/SpaceBeforeSemicolon:
Enabled: false
-Style/SpaceBeforeModifierKeyword:
- Description: 'Put a space before the modifier keyword.'
+# Checks that block braces have or don't have surrounding space.
+# For blocks taking parameters, checks that the left brace has or doesn't
+# have trailing space.
+Style/SpaceInsideBlockBraces:
Enabled: false
+# No spaces after [ or before ].
Style/SpaceInsideBrackets:
- Description: 'No spaces after [ or before ].'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-spaces-braces'
Enabled: false
+# Use spaces inside hash literal braces - or don't.
Style/SpaceInsideHashLiteralBraces:
- Description: "Use spaces inside hash literal braces - or don't."
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators'
Enabled: true
+# No spaces after ( or before ).
Style/SpaceInsideParens:
- Description: 'No spaces after ( or before ).'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-spaces-braces'
Enabled: false
+# No spaces inside range literals.
Style/SpaceInsideRangeLiteral:
- Description: 'No spaces inside range literals.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-space-inside-range-literals'
Enabled: false
+# Checks for padding/surrounding spaces inside string interpolation.
+Style/SpaceInsideStringInterpolation:
+ Enabled: false
+
+# Avoid Perl-style global variables.
Style/SpecialGlobalVars:
- Description: 'Avoid Perl-style global variables.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-cryptic-perlisms'
Enabled: false
+# Check for the usage of parentheses around stabby lambda arguments.
+Style/StabbyLambdaParentheses:
+ Enabled: false
+
+# Checks if uses of quotes match the configured preference.
Style/StringLiterals:
- Description: 'Checks if uses of quotes match the configured preference.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-string-literals'
Enabled: false
+# Checks if uses of quotes inside expressions in interpolated strings match the
+# configured preference.
Style/StringLiteralsInInterpolation:
- Description: >-
- Checks if uses of quotes inside expressions in interpolated
- strings match the configured preference.
Enabled: false
+# Checks if configured preferred methods are used over non-preferred.
+Style/StringMethods:
+ Enabled: false
+
+# Use %i or %I for arrays of symbols.
+Style/SymbolArray:
+ Enabled: false
+
+# Use symbols as procs instead of blocks when possible.
Style/SymbolProc:
- Description: 'Use symbols as procs instead of blocks when possible.'
Enabled: false
+# No hard tabs.
Style/Tab:
- Description: 'No hard tabs.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-indentation'
Enabled: true
+# Checks trailing blank lines and final newline.
Style/TrailingBlankLines:
- Description: 'Checks trailing blank lines and final newline.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#newline-eof'
Enabled: true
-Style/TrailingComma:
- Description: 'Checks for trailing comma in parameter lists and literals.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas'
+# Checks for trailing comma in array and hash literals.
+Style/TrailingCommaInLiteral:
Enabled: false
+# Checks for trailing comma in argument lists.
+Style/TrailingCommaInArguments:
+ Enabled: false
+
+# Avoid trailing whitespace.
Style/TrailingWhitespace:
- Description: 'Avoid trailing whitespace.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-whitespace'
Enabled: false
+# Checks for the usage of unneeded trailing underscores at the end of
+# parallel variable assignment.
Style/TrailingUnderscoreVariable:
- Description: >-
- Checks for the usage of unneeded trailing underscores at the
- end of parallel variable assignment.
- AllowNamedUnderscoreVariables: true
Enabled: false
+# Prefer attr_* methods to trivial readers/writers.
Style/TrivialAccessors:
- Description: 'Prefer attr_* methods to trivial readers/writers.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr_family'
Enabled: false
+# Do not use unless with else. Rewrite these with the positive case first.
Style/UnlessElse:
- Description: >-
- Do not use unless with else. Rewrite these with the positive
- case first.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-else-with-unless'
Enabled: false
+# Checks for %W when interpolation is not needed.
Style/UnneededCapitalW:
- Description: 'Checks for %W when interpolation is not needed.'
Enabled: false
+# TODO: Enable UnneededInterpolation Cop.
+# Checks for strings that are just an interpolated expression.
+Style/UnneededInterpolation:
+ Enabled: false
+
+# Checks for %q/%Q when single quotes or double quotes would do.
Style/UnneededPercentQ:
- Description: 'Checks for %q/%Q when single quotes or double quotes would do.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-q'
Enabled: false
+# Don't interpolate global, instance and class variables directly in strings.
Style/VariableInterpolation:
- Description: >-
- Don't interpolate global, instance and class variables
- directly in strings.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#curlies-interpolate'
Enabled: false
+# Use the configured style when naming variables.
Style/VariableName:
- Description: 'Use the configured style when naming variables.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars'
Enabled: false
+# Use when x then ... for one-line cases.
Style/WhenThen:
- Description: 'Use when x then ... for one-line cases.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#one-line-cases'
Enabled: false
+# Checks for redundant do after while or until.
Style/WhileUntilDo:
- Description: 'Checks for redundant do after while or until.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-multiline-while-do'
Enabled: false
+# Favor modifier while/until usage when you have a single-line body.
Style/WhileUntilModifier:
- Description: >-
- Favor modifier while/until usage when you have a
- single-line body.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#while-as-a-modifier'
Enabled: false
+# Use %w or %W for arrays of words.
Style/WordArray:
- Description: 'Use %w or %W for arrays of words.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-w'
Enabled: false
+# TODO: Enable ZeroLengthPredicate Cop.
+# Use #empty? when testing for objects of length 0.
+Style/ZeroLengthPredicate:
+ Enabled: false
+
+
#################### Metrics ################################
+# A calculated magnitude based on number of assignments,
+# branches, and conditions.
Metrics/AbcSize:
- Description: >-
- A calculated magnitude based on number of assignments,
- branches, and conditions.
Enabled: true
Max: 70
-Metrics/CyclomaticComplexity:
- Description: >-
- A complexity metric that is strongly correlated to the number
- of test cases needed to validate a method.
- Enabled: true
- Max: 17
-
-Metrics/PerceivedComplexity:
- Description: >-
- A complexity metric geared towards measuring complexity for a
- human reader.
- Enabled: true
- Max: 17
-
-Metrics/ParameterLists:
- Description: 'Avoid parameter lists longer than three or four parameters.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#too-many-params'
- Enabled: true
- Max: 8
-
+# Avoid excessive block nesting.
Metrics/BlockNesting:
- Description: 'Avoid excessive block nesting'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#three-is-the-number-thou-shalt-count'
Enabled: true
Max: 4
+# Avoid classes longer than 100 lines of code.
Metrics/ClassLength:
- Description: 'Avoid classes longer than 100 lines of code.'
Enabled: false
+# A complexity metric that is strongly correlated to the number
+# of test cases needed to validate a method.
+Metrics/CyclomaticComplexity:
+ Enabled: true
+ Max: 17
+
+# Limit lines to 80 characters.
Metrics/LineLength:
- Description: 'Limit lines to 80 characters.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits'
Enabled: false
+# Avoid methods longer than 10 lines of code.
Metrics/MethodLength:
- Description: 'Avoid methods longer than 10 lines of code.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#short-methods'
Enabled: false
+# Avoid modules longer than 100 lines of code.
Metrics/ModuleLength:
- Description: 'Avoid modules longer than 100 lines of code.'
Enabled: false
+# Avoid parameter lists longer than three or four parameters.
+Metrics/ParameterLists:
+ Enabled: true
+ Max: 8
+
+# A complexity metric geared towards measuring complexity for a human reader.
+Metrics/PerceivedComplexity:
+ Enabled: true
+ Max: 17
+
+
#################### Lint ################################
-### Warnings
+# Checks for ambiguous operators in the first argument of a method invocation
+# without parentheses.
Lint/AmbiguousOperator:
- Description: >-
- Checks for ambiguous operators in the first argument of a
- method invocation without parentheses.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-as-args'
Enabled: false
+# Checks for ambiguous regexp literals in the first argument of a method
+# invocation without parentheses.
Lint/AmbiguousRegexpLiteral:
- Description: >-
- Checks for ambiguous regexp literals in the first argument of
- a method invocation without parenthesis.
Enabled: false
+# Don't use assignment in conditions.
Lint/AssignmentInCondition:
- Description: "Don't use assignment in conditions."
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#safe-assignment-in-condition'
Enabled: false
+# Align block ends correctly.
Lint/BlockAlignment:
- Description: 'Align block ends correctly.'
Enabled: false
+# Default values in optional keyword arguments and optional ordinal arguments
+# should not refer back to the name of the argument.
+Lint/CircularArgumentReference:
+ Enabled: false
+
+# Checks for condition placed in a confusing position relative to the keyword.
Lint/ConditionPosition:
- Description: >-
- Checks for condition placed in a confusing position relative to
- the keyword.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#same-line-condition'
Enabled: false
+# Check for debugger calls.
Lint/Debugger:
- Description: 'Check for debugger calls.'
Enabled: false
+# Align ends corresponding to defs correctly.
Lint/DefEndAlignment:
- Description: 'Align ends corresponding to defs correctly.'
Enabled: false
+# Check for deprecated class method calls.
Lint/DeprecatedClassMethods:
- Description: 'Check for deprecated class method calls.'
Enabled: false
+# Check for duplicate method definitions.
+Lint/DuplicateMethods:
+ Enabled: false
+
+# Check for duplicate keys in hash literals.
+Lint/DuplicatedKey:
+ Enabled: false
+
+# Check for immutable argument given to each_with_object.
+Lint/EachWithObjectArgument:
+ Enabled: false
+
+# Check for odd code arrangement in an else block.
Lint/ElseLayout:
- Description: 'Check for odd code arrangement in an else block.'
Enabled: false
+# Checks for empty ensure block.
Lint/EmptyEnsure:
- Description: 'Checks for empty ensure block.'
Enabled: false
+# Checks for empty string interpolation.
Lint/EmptyInterpolation:
- Description: 'Checks for empty string interpolation.'
Enabled: false
+# Align ends correctly.
Lint/EndAlignment:
- Description: 'Align ends correctly.'
Enabled: false
+# END blocks should not be placed inside method definitions.
Lint/EndInMethod:
- Description: 'END blocks should not be placed inside method definitions.'
Enabled: false
+# Do not use return in an ensure block.
Lint/EnsureReturn:
- Description: 'Do not use return in an ensure block.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-return-ensure'
Enabled: false
+# The use of eval represents a serious security risk.
Lint/Eval:
- Description: 'The use of eval represents a serious security risk.'
Enabled: false
+# Catches floating-point literals too large or small for Ruby to represent.
+Lint/FloatOutOfRange:
+ Enabled: false
+
+# The number of parameters to format/sprint must match the fields.
+Lint/FormatParameterMismatch:
+ Enabled: false
+
+# Don't suppress exception.
Lint/HandleExceptions:
- Description: "Don't suppress exception."
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions'
Enabled: false
+# TODO: Enable ImplicitStringConcatenation Cop.
+# Checks for adjacent string literals on the same line, which could better be
+# represented as a single string literal.
+Lint/ImplicitStringConcatenation:
+ Enabled: false
+
+# TODO: Enable IneffectiveAccessModifier Cop.
+# Checks for attempts to use `private` or `protected` to set the visibility
+# of a class method, which does not work.
+Lint/IneffectiveAccessModifier:
+ Enabled: false
+
+# Checks for invalid character literals with a non-escaped whitespace
+# character.
Lint/InvalidCharacterLiteral:
- Description: >-
- Checks for invalid character literals with a non-escaped
- whitespace character.
Enabled: false
+# Checks of literals used in conditions.
Lint/LiteralInCondition:
- Description: 'Checks of literals used in conditions.'
Enabled: false
+# Checks for literals used in interpolation.
Lint/LiteralInInterpolation:
- Description: 'Checks for literals used in interpolation.'
Enabled: false
+# Use Kernel#loop with break rather than begin/end/until or begin/end/while
+# for post-loop tests.
Lint/Loop:
- Description: >-
- Use Kernel#loop with break rather than begin/end/until or
- begin/end/while for post-loop tests.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#loop-with-break'
Enabled: false
+# Do not use nested method definitions.
+Lint/NestedMethodDefinition:
+ Enabled: false
+
+# Do not omit the accumulator when calling `next` in a `reduce`/`inject` block.
+Lint/NextWithoutAccumulator:
+ Enabled: false
+
+# Checks for method calls with a space before the opening parenthesis.
Lint/ParenthesesAsGroupedExpression:
- Description: >-
- Checks for method calls with a space before the opening
- parenthesis.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces'
Enabled: true
+# Checks for `rand(1)` calls. Such calls always return `0` and most likely
+# a mistake.
+Lint/RandOne:
+ Enabled: false
+
+# Use parentheses in the method call to avoid confusion about precedence.
Lint/RequireParentheses:
- Description: >-
- Use parentheses in the method call to avoid confusion
- about precedence.
Enabled: false
+# Avoid rescuing the Exception class.
Lint/RescueException:
- Description: 'Avoid rescuing the Exception class.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-blind-rescues'
Enabled: true
+# Do not use the same name as outer local variable for block arguments
+# or block local variables.
Lint/ShadowingOuterLocalVariable:
- Description: >-
- Do not use the same name as outer local variable
- for block arguments or block local variables.
- Enabled: false
-
-Lint/SpaceBeforeFirstArg:
- Description: >-
- Put a space between a method name and the first argument
- in a method call without parentheses.
Enabled: false
+# 'Checks for Object#to_s usage in string interpolation.
Lint/StringConversionInInterpolation:
- Description: 'Checks for Object#to_s usage in string interpolation.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-to-s'
Enabled: false
+# Do not use prefix `_` for a variable that is used.
Lint/UnderscorePrefixedVariableName:
- Description: 'Do not use prefix `_` for a variable that is used.'
Enabled: true
+# Checks for rubocop:disable comments that can be removed.
+# Note: this cop is not disabled when disabling all cops.
+# It must be explicitly disabled.
+Lint/UnneededDisable:
+ Enabled: false
+
+# Checks for unused block arguments.
Lint/UnusedBlockArgument:
- Description: 'Checks for unused block arguments.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars'
Enabled: false
+# Checks for unused method arguments.
Lint/UnusedMethodArgument:
- Description: 'Checks for unused method arguments.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars'
Enabled: false
+# Unreachable code.
Lint/UnreachableCode:
- Description: 'Unreachable code.'
Enabled: false
+# Checks for useless access modifiers.
Lint/UselessAccessModifier:
- Description: 'Checks for useless access modifiers.'
Enabled: false
+# Checks for useless assignment to a local variable.
Lint/UselessAssignment:
- Description: 'Checks for useless assignment to a local variable.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars'
Enabled: true
+# Checks for comparison of something with itself.
Lint/UselessComparison:
- Description: 'Checks for comparison of something with itself.'
Enabled: false
+# Checks for useless `else` in `begin..end` without `rescue`.
Lint/UselessElseWithoutRescue:
- Description: 'Checks for useless `else` in `begin..end` without `rescue`.'
Enabled: false
+# Checks for useless setter call to a local variable.
Lint/UselessSetterCall:
- Description: 'Checks for useless setter call to a local variable.'
Enabled: false
+# Possible use of operator/literal/variable in void context.
Lint/Void:
- Description: 'Possible use of operator/literal/variable in void context.'
Enabled: false
+
+##################### Performance ############################
+
+# TODO: Enable Casecmp Cop.
+# Use `casecmp` rather than `downcase ==`.
+Performance/Casecmp:
+ Enabled: false
+
+# TODO: Enable DoubleStartEndWith Cop.
+# Use `str.{start,end}_with?(x, ..., y, ...)` instead of
+# `str.{start,end}_with?(x, ...) || str.{start,end}_with?(y, ...)`.
+Performance/DoubleStartEndWith:
+ Enabled: false
+
+# TODO: Enable EndWith Cop.
+# Use `end_with?` instead of a regex match anchored to the end of a string.
+Performance/EndWith:
+ Enabled: false
+
+# TODO: Enable LstripRstrip Cop.
+# Use `strip` instead of `lstrip.rstrip`.
+Performance/LstripRstrip:
+ Enabled: false
+
+# TODO: Enable RangeInclude Cop.
+# Use `Range#cover?` instead of `Range#include?`.
+Performance/RangeInclude:
+ Enabled: false
+
+# TODO: Enable RedundantBlockCall Cop.
+# Use `yield` instead of `block.call`.
+Performance/RedundantBlockCall:
+ Enabled: false
+
+# TODO: Enable RedundantMatch Cop.
+# Use `=~` instead of `String#match` or `Regexp#match` in a context where the
+# returned `MatchData` is not needed.
+Performance/RedundantMatch:
+ Enabled: false
+
+# TODO: Enable RedundantMerge Cop.
+# Use `Hash#[]=`, rather than `Hash#merge!` with a single key-value pair.
+Performance/RedundantMerge:
+ # Max number of key-value pairs to consider an offense
+ MaxKeyValuePairs: 2
+ Enabled: false
+
+# TODO: Enable RedundantSortBy Cop.
+# Use `sort` instead of `sort_by { |x| x }`.
+Performance/RedundantSortBy:
+ Enabled: false
+
+# TODO: Enable StartWith Cop.
+# Use `start_with?` instead of a regex match anchored to the beginning of a
+# string.
+Performance/StartWith:
+ Enabled: false
+# Use `tr` instead of `gsub` when you are replacing the same number of
+# characters. Use `delete` instead of `gsub` when you are deleting
+# characters.
+Performance/StringReplacement:
+ Enabled: false
+
+# TODO: Enable TimesMap Cop.
+# Checks for `.times.map` calls.
+Performance/TimesMap:
+ Enabled: false
+
+
##################### Rails ##################################
+# Enables Rails cops.
+Rails:
+ Enabled: true
+
+# Enforces consistent use of action filter methods.
Rails/ActionFilter:
- Description: 'Enforces consistent use of action filter methods.'
Enabled: true
+ EnforcedStyle: action
+# Checks the correct usage of date aware methods, such as `Date.today`,
+# `Date.current`, etc.
Rails/Date:
- Description: >-
- Checks the correct usage of date aware methods,
- such as Date.today, Date.current etc.
Enabled: false
-Rails/DefaultScope:
- Description: 'Checks if the argument passed to default_scope is a block.'
+# Prefer delegate method for delegations.
+Rails/Delegate:
Enabled: false
-Rails/Delegate:
- Description: 'Prefer delegate method for delegations.'
+# Prefer `find_by` over `where.first`.
+Rails/FindBy:
+ Enabled: false
+
+# Prefer `all.find_each` over `all.find`.
+Rails/FindEach:
Enabled: false
+# Prefer has_many :through to has_and_belongs_to_many.
Rails/HasAndBelongsToMany:
- Description: 'Prefer has_many :through to has_and_belongs_to_many.'
Enabled: true
+# Checks for calls to puts, print, etc.
Rails/Output:
- Description: 'Checks for calls to puts, print, etc.'
Enabled: true
+# Checks for incorrect grammar when using methods like `3.day.ago`.
+Rails/PluralizationGrammar:
+ Enabled: false
+
+# Checks for `read_attribute(:attr)` and `write_attribute(:attr, val)`.
Rails/ReadWriteAttribute:
- Description: >-
- Checks for read_attribute(:attr) and
- write_attribute(:attr, val).
Enabled: false
+# Checks the arguments of ActiveRecord scopes.
Rails/ScopeArgs:
- Description: 'Checks the arguments of ActiveRecord scopes.'
Enabled: false
+# Checks the correct usage of time zone aware methods.
+# http://danilenko.org/2012/7/6/rails_timezones
Rails/TimeZone:
- Description: 'Checks the correct usage of time zone aware methods.'
- StyleGuide: 'https://github.com/bbatsov/rails-style-guide#time'
- Reference: 'http://danilenko.org/2012/7/6/rails_timezones'
Enabled: false
+# Use validates :attribute, hash of validations.
Rails/Validation:
- Description: 'Use validates :attribute, hash of validations.'
Enabled: false
-
-
-# Exclude some of GitLab files
-#
-#
-AllCops:
- RunRailsCops: true
- Exclude:
- - 'vendor/**/*'
- - 'db/**/*'
- - 'tmp/**/*'
- - 'bin/**/*'
- - 'lib/backup/**/*'
- - 'lib/ci/backup/**/*'
- - 'lib/tasks/**/*'
- - 'lib/ci/migrate/**/*'
- - 'lib/email_validator.rb'
- - 'lib/gitlab/upgrader.rb'
- - 'lib/gitlab/seeder.rb'
diff --git a/CHANGELOG b/CHANGELOG
index 72e91a1dac1..9a102b8f7ec 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,23 +1,40 @@
Please view this file on the master branch, on stable branches it's out of date.
-v 8.6.0 (unreleased)
+v 8.7.0 (unreleased)
+ - Make HTTP(s) label consistent on clone bar (Stan Hu)
+
+v 8.6.1 (unreleased)
+
+
+v 8.6.0
+ - Add ability to move issue to another project
+ - Prevent tokens in the import URL to be showed by the UI
+ - Fix bug where wrong commit ID was being used in a merge request diff to show old image (Stan Hu)
+ - Add confidential issues
- Bump gitlab_git to 9.0.3 (Stan Hu)
+ - Fix diff image view modes (2-up, swipe, onion skin) not working (Stan Hu)
- Support Golang subpackage fetching (Stan Hu)
- Bump Capybara gem to 2.6.2 (Stan Hu)
- New branch button appears on issues where applicable
- Contributions to forked projects are included in calendar
- Improve the formatting for the user page bio (Connor Shea)
+ - Easily (un)mark merge request as WIP using link
+ - Use specialized system notes when MR is (un)marked as WIP
- Removed the default password from the initial admin account created during
setup. A password can be provided during setup (see installation docs), or
GitLab will ask the user to create a new one upon first visit.
- Fix issue when pushing to projects ending in .wiki
+ - Properly display YAML front matter in Markdown
- Add support for wiki with UTF-8 page names (Hiroyuki Sato)
- Fix wiki search results point to raw source (Hiroyuki Sato)
- Don't load all of GitLab in mail_room
+ - Add information about `image` and `services` field at `job` level in the `.gitlab-ci.yml` documentation (Pat Turner)
+ - HTTP error pages work independently from location and config (Artem Sidorenko)
- Update `omniauth-saml` to 1.5.0 to allow for custom response attributes to be set
- Memoize @group in Admin::GroupsController (Yatish Mehta)
- Indicate how much an MR diverged from the target branch (Pierre de La Morinerie)
- Added omniauth-auth0 Gem (Daniel Carraro)
+ - Add label description in tooltip to labels in issue index and sidebar
- Strip leading and trailing spaces in URL validator (evuez)
- Add "last_sign_in_at" and "confirmed_at" to GET /users/* API endpoints for admins (evuez)
- Return empty array instead of 404 when commit has no statuses in commit status API
@@ -25,6 +42,7 @@ v 8.6.0 (unreleased)
- Rewrite logo to simplify SVG code (Sean Lang)
- Allow to use YAML anchors when parsing the `.gitlab-ci.yml` (Pascal Bach)
- Ignore jobs that start with `.` (hidden jobs)
+ - Hide builds from project's settings when the feature is disabled
- Allow to pass name of created artifacts archive in `.gitlab-ci.yml`
- Refactor and greatly improve search performance
- Add support for cross-project label references
@@ -36,15 +54,24 @@ v 8.6.0 (unreleased)
- Fix bug where Bitbucket `closed` issues were imported as `opened` (Iuri de Silvio)
- Don't show Issues/MRs from archived projects in Groups view
- Fix wrong "iid of max iid" in Issuable sidebar for some merged MRs
+ - Fix empty source_sha on Merge Request when there is no diff (Pierre de La Morinerie)
- Increase the notes polling timeout over time (Roberto Dip)
- Add shortcut to toggle markdown preview (Florent Baldino)
- Show labels in dashboard and group milestone views
+ - Fix an issue when the target branch of a MR had been deleted
- Add main language of a project in the list of projects (Tiago Botelho)
+ - Add #upcoming filter to Milestone filter (Tiago Botelho)
- Add ability to show archived projects on dashboard, explore and group pages
- Move group activity to separate page
- Create external users which are excluded of internal and private projects unless access was explicitly granted
- Continue parameters are checked to ensure redirection goes to the same instance
- User deletion is now done in the background so the request can not time out
+ - Canceled builds are now ignored in compound build status if marked as `allowed to fail`
+ - Trigger a todo for mentions on commits page
+ - Let project owners and admins soft delete issues and merge requests
+
+v 8.5.8
+ - Bump Git version requirement to 2.7.4
v 8.5.7
- Bump Git version requirement to 2.7.3
@@ -57,7 +84,6 @@ v 8.5.5
- Prevent a 500 error in Todos when author was removed
- Fix pagination for filtered dashboard and explore pages
- Fix "Show all" link behavior
- - Add #upcoming filter to Milestone filter (Tiago Botelho)
v 8.5.4
- Do not cache requests for badges (including builds badge)
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index ef5e4454454..39e898a4f95 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-0.6.5
+0.7.1
diff --git a/Gemfile b/Gemfile
index a3fb6779e9a..cebd76e7370 100644
--- a/Gemfile
+++ b/Gemfile
@@ -51,7 +51,7 @@ gem "browser", '~> 1.0.0'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
-gem "gitlab_git", '~> 9.0'
+gem "gitlab_git", '~> 10.0'
# LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes
@@ -222,6 +222,8 @@ gem 'net-ssh', '~> 3.0.1'
# Sentry integration
gem 'sentry-raven', '~> 0.15'
+gem 'premailer-rails', '~> 1.9.0'
+
# Metrics
group :metrics do
gem 'allocations', '~> 1.0', require: false, platform: :mri
@@ -285,7 +287,7 @@ group :development, :test do
gem 'spring-commands-spinach', '~> 1.0.0'
gem 'spring-commands-teaspoon', '~> 0.0.2'
- gem 'rubocop', '~> 0.35.0', require: false
+ gem 'rubocop', '~> 0.38.0', require: false
gem 'scss_lint', '~> 0.47.0', require: false
gem 'coveralls', '~> 0.8.2', require: false
gem 'simplecov', '~> 0.10.0', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 7b0dd83da52..16c09ab6e6d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -61,9 +61,7 @@ GEM
faraday_middleware-multi_json (~> 0.0)
oauth2 (~> 1.0)
asciidoctor (1.5.3)
- ast (2.1.0)
- astrolabe (1.3.1)
- parser (~> 2.2)
+ ast (2.2.0)
attr_encrypted (1.3.4)
encryptor (>= 1.3.0)
attr_required (1.0.0)
@@ -150,6 +148,8 @@ GEM
crack (0.4.3)
safe_yaml (~> 1.0.0)
creole (0.5.0)
+ css_parser (1.3.7)
+ addressable
d3_rails (3.5.11)
railties (>= 3.1.0)
daemons (1.2.3)
@@ -359,11 +359,11 @@ GEM
posix-spawn (~> 0.3)
gitlab_emoji (0.3.1)
gemojione (~> 2.2, >= 2.2.1)
- gitlab_git (9.0.3)
+ gitlab_git (10.0.0)
activesupport (~> 4.0)
charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0)
- rugged (~> 0.24.0b13)
+ rugged (~> 0.24.0)
gitlab_meta (7.0)
gitlab_omniauth-ldap (1.2.1)
net-ldap (~> 0.9)
@@ -423,6 +423,7 @@ GEM
haml (~> 4.0.0)
nokogiri (~> 1.6.0)
ruby_parser (~> 3.5)
+ htmlentities (4.3.4)
http-cookie (1.0.2)
domain_name (~> 0.5)
http_parser.rb (0.5.3)
@@ -554,8 +555,8 @@ GEM
orm_adapter (0.5.0)
paranoia (2.1.4)
activerecord (~> 4.0)
- parser (2.2.3.0)
- ast (>= 1.1, < 3.0)
+ parser (2.3.0.6)
+ ast (~> 2.2)
pg (0.18.4)
poltergeist (1.9.0)
capybara (~> 2.1)
@@ -564,6 +565,12 @@ GEM
websocket-driver (>= 0.2.0)
posix-spawn (0.3.11)
powerpack (0.1.1)
+ premailer (1.8.6)
+ css_parser (>= 1.3.6)
+ htmlentities (>= 4.0.0)
+ premailer-rails (1.9.0)
+ actionmailer (>= 3, < 5)
+ premailer (~> 1.7, >= 1.7.9)
pry (0.10.3)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
@@ -615,7 +622,7 @@ GEM
activesupport (= 4.2.5.2)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
- rainbow (2.0.0)
+ rainbow (2.1.0)
raindrops (0.15.0)
rake (10.5.0)
raphael-rails (2.1.2)
@@ -687,13 +694,12 @@ GEM
rspec-retry (0.4.5)
rspec-core
rspec-support (3.3.0)
- rubocop (0.35.1)
- astrolabe (~> 1.3)
- parser (>= 2.2.3.0, < 3.0)
+ rubocop (0.38.0)
+ parser (>= 2.3.0.6, < 3.0)
powerpack (~> 0.1)
rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.7)
- tins (<= 1.6.0)
+ unicode-display_width (~> 1.0, >= 1.0.1)
ruby-fogbugz (0.2.1)
crack (~> 0.4)
ruby-progressbar (1.7.5)
@@ -843,6 +849,7 @@ GEM
unf (0.1.4)
unf_ext
unf_ext (0.0.7.1)
+ unicode-display_width (1.0.2)
unicorn (4.9.0)
kgio (~> 2.6)
rack
@@ -942,7 +949,7 @@ DEPENDENCIES
github-markup (~> 1.3.1)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab_emoji (~> 0.3.0)
- gitlab_git (~> 9.0)
+ gitlab_git (~> 10.0)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.1.0)
@@ -992,6 +999,7 @@ DEPENDENCIES
paranoia (~> 2.0)
pg (~> 0.18.2)
poltergeist (~> 1.9.0)
+ premailer-rails (~> 1.9.0)
pry-rails
quiet_assets (~> 1.0.2)
rack-attack (~> 4.3.1)
@@ -1013,7 +1021,7 @@ DEPENDENCIES
rqrcode-rails3 (~> 0.1.7)
rspec-rails (~> 3.3.0)
rspec-retry
- rubocop (~> 0.35.0)
+ rubocop (~> 0.38.0)
ruby-fogbugz (~> 0.2.1)
sanitize (~> 2.0)
sass-rails (~> 5.0.0)
@@ -1056,6 +1064,3 @@ DEPENDENCIES
web-console (~> 2.0)
webmock (~> 1.21.0)
wikicloth (= 0.8.1)
-
-BUNDLED WITH
- 1.11.2
diff --git a/README.md b/README.md
index 208427fcf8c..afa60116ebb 100644
--- a/README.md
+++ b/README.md
@@ -68,7 +68,7 @@ GitLab is a Ruby on Rails application that runs on the following software:
- Ubuntu/Debian/CentOS/RHEL
- Ruby (MRI) 2.1
-- Git 2.7.3+
+- Git 2.7.4+
- Redis 2.8+
- MySQL or PostgreSQL
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index d415bbd3476..01451830653 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -7,6 +7,7 @@
#= require jquery
#= require jquery-ui/autocomplete
#= require jquery-ui/datepicker
+#= require jquery-ui/draggable
#= require jquery-ui/effect-highlight
#= require jquery-ui/sortable
#= require jquery_ujs
@@ -138,7 +139,7 @@ $ ->
# Initialize tooltips
$('body').tooltip(
- selector: '.has_tooltip, [data-toggle="tooltip"]'
+ selector: '.has-tooltip, [data-toggle="tooltip"]'
placement: (_, el) ->
$el = $(el)
$el.data('placement') || 'bottom'
diff --git a/app/assets/javascripts/aside.js.coffee b/app/assets/javascripts/aside.js.coffee
index 85473101944..66ab5054326 100644
--- a/app/assets/javascripts/aside.js.coffee
+++ b/app/assets/javascripts/aside.js.coffee
@@ -5,7 +5,6 @@ class @Aside
e.preventDefault()
btn = $(e.currentTarget)
icon = btn.find('i')
- console.log('1')
if icon.hasClass('fa-angle-left')
btn.parent().find('section').hide()
diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee
index 03a44874161..47b080406d4 100644
--- a/app/assets/javascripts/awards_handler.coffee
+++ b/app/assets/javascripts/awards_handler.coffee
@@ -122,7 +122,7 @@ class @AwardsHandler
nodes = []
nodes.push(
- "<button class='btn award-control js-emoji-btn has_tooltip active' title='me'>",
+ "<button class='btn award-control js-emoji-btn has-tooltip active' title='me'>",
"<div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>",
"<span class='award-control-text js-counter'>1</span>",
"</button>"
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index 1be86e3b820..f5e1ca9860d 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -14,7 +14,6 @@ class Dispatcher
path = page.split(':')
shortcut_handler = null
-
switch page
when 'projects:issues:index'
Issues.init()
@@ -25,6 +24,8 @@ class Dispatcher
new ZenMode()
when 'projects:milestones:show', 'groups:milestones:show', 'dashboard:milestones:show'
new Milestone()
+ when 'dashboard:todos:index'
+ new Todos()
when 'projects:milestones:new', 'projects:milestones:edit'
new ZenMode()
new DropzoneInput($('.milestone-form'))
diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee
index 4f038477755..960585245d7 100644
--- a/app/assets/javascripts/gl_dropdown.js.coffee
+++ b/app/assets/javascripts/gl_dropdown.js.coffee
@@ -167,7 +167,11 @@ class GitLabDropdown
hidden: =>
if @options.filterable
- @dropdown.find(".dropdown-input-field").blur().val("")
+ @dropdown
+ .find(".dropdown-input-field")
+ .blur()
+ .val("")
+ .trigger("keyup")
if @dropdown.find(".dropdown-toggle-page").length
$('.dropdown-menu', @dropdown).removeClass PAGE_TWO_CLASS
@@ -246,11 +250,15 @@ class GitLabDropdown
if oldValue
value = "#{oldValue},#{value}"
else
- @dropdown.find(ACTIVE_CLASS).removeClass ACTIVE_CLASS
+ @dropdown.find(".#{ACTIVE_CLASS}").removeClass ACTIVE_CLASS
# Toggle active class for the tick mark
el.toggleClass "is-active"
+ # Toggle the dropdown label
+ if @options.toggleLabel
+ $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject)
+
if value?
if !field.length
# Create hidden input for form
diff --git a/app/assets/javascripts/issuable_form.js.coffee b/app/assets/javascripts/issuable_form.js.coffee
index 48c249943f2..7a788f761b7 100644
--- a/app/assets/javascripts/issuable_form.js.coffee
+++ b/app/assets/javascripts/issuable_form.js.coffee
@@ -1,4 +1,7 @@
class @IssuableForm
+ issueMoveConfirmMsg: 'Are you sure you want to move this issue to another project?'
+ wipRegex: /^\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i
+
constructor: (@form) ->
GitLab.GfmAutoComplete.setup()
new UsersSelect()
@@ -6,14 +9,17 @@ class @IssuableForm
@titleField = @form.find("input[name*='[title]']")
@descriptionField = @form.find("textarea[name*='[description]']")
+ @issueMoveField = @form.find("#move_to_project_id")
return unless @titleField.length && @descriptionField.length
@initAutosave()
- @form.on "submit", @resetAutosave
+ @form.on "submit", @handleSubmit
@form.on "click", ".btn-cancel", @resetAutosave
+ @initWip()
+
initAutosave: ->
new Autosave @titleField, [
document.location.pathname,
@@ -27,6 +33,50 @@ class @IssuableForm
"description"
]
+ handleSubmit: =>
+ if (parseInt(@issueMoveField?.val()) ? 0) > 0
+ return false unless confirm(@issueMoveConfirmMsg)
+
+ @resetAutosave()
+
resetAutosave: =>
@titleField.data("autosave").reset()
@descriptionField.data("autosave").reset()
+
+ initWip: ->
+ @$wipExplanation = @form.find(".js-wip-explanation")
+ @$noWipExplanation = @form.find(".js-no-wip-explanation")
+ return unless @$wipExplanation.length and @$noWipExplanation.length
+
+ @form.on "click", ".js-toggle-wip", @toggleWip
+
+ @titleField.on "keyup blur", @renderWipExplanation
+
+ @renderWipExplanation()
+
+ workInProgress: ->
+ @wipRegex.test @titleField.val()
+
+ renderWipExplanation: =>
+ if @workInProgress()
+ @$wipExplanation.show()
+ @$noWipExplanation.hide()
+ else
+ @$wipExplanation.hide()
+ @$noWipExplanation.show()
+
+ toggleWip: (event) =>
+ event.preventDefault()
+
+ if @workInProgress()
+ @removeWip()
+ else
+ @addWip()
+
+ @renderWipExplanation()
+
+ removeWip: ->
+ @titleField.val @titleField.val().replace(@wipRegex, "")
+
+ addWip: ->
+ @titleField.val "WIP: #{@titleField.val()}"
diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee
index d663e34871c..f50df1f5ea3 100644
--- a/app/assets/javascripts/issue.js.coffee
+++ b/app/assets/javascripts/issue.js.coffee
@@ -7,6 +7,7 @@ class @Issue
# Prevent duplicate event bindings
@disableTaskList()
@fixAffixScroll()
+ @initParticipants()
if $('a.btn-close').length
@initTaskList()
@initIssueBtnEventListeners()
@@ -84,3 +85,27 @@ class @Issue
type: 'PATCH'
url: $('form.js-issuable-update').attr('action')
data: patchData
+
+ initParticipants: ->
+ _this = @
+ $(document).on "click", ".js-participants-more", @toggleHiddenParticipants
+
+ $(".js-participants-author").each (i) ->
+ if i >= _this.PARTICIPANTS_ROW_COUNT
+ $(@)
+ .addClass "js-participants-hidden"
+ .hide()
+
+ toggleHiddenParticipants: (e) ->
+ e.preventDefault()
+
+ currentText = $(this).text().trim()
+ lessText = $(this).data("less-text")
+ originalText = $(this).data("original-text")
+
+ if currentText is originalText
+ $(this).text(lessText)
+ else
+ $(this).text(originalText)
+
+ $(".js-participants-hidden").toggle()
diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee
index a0acf3028bf..1127b289264 100644
--- a/app/assets/javascripts/issues.js.coffee
+++ b/app/assets/javascripts/issues.js.coffee
@@ -41,24 +41,28 @@
@timer = null
$("#issue_search").keyup ->
clearTimeout(@timer)
- @timer = setTimeout(Issues.filterResults, 500)
+ @timer = setTimeout( ->
+ Issues.filterResults $("#issue_search_form")
+ , 500)
- filterResults: =>
- form = $("#issue_search_form")
- search = $("#issue_search").val()
- $('.issues-holder').css("opacity", '0.5')
- issues_url = form.attr('action') + '?' + form.serialize()
+ filterResults: (form) =>
+ $('.issues-holder, .merge-requests-holder').css("opacity", '0.5')
+ formAction = form.attr('action')
+ formData = form.serialize()
+ issuesUrl = formAction
+ issuesUrl += ("#{if formAction.indexOf("?") < 0 then '?' else '&'}")
+ issuesUrl += formData
$.ajax
type: "GET"
- url: form.attr('action')
- data: form.serialize()
+ url: formAction
+ data: formData
complete: ->
- $('.issues-holder').css("opacity", '1.0')
+ $('.issues-holder, .merge-requests-holder').css("opacity", '1.0')
success: (data) ->
- $('.issues-holder').html(data.html)
+ $('.issues-holder, .merge-requests-holder').html(data.html)
# Change url so if user reload a page - search results are saved
- history.replaceState {page: issues_url}, document.title, issues_url
+ history.replaceState {page: issuesUrl}, document.title, issuesUrl
Issues.reload()
dataType: "json"
diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee
index 5ade2cb66cb..4a0c18a99a6 100644
--- a/app/assets/javascripts/labels_select.js.coffee
+++ b/app/assets/javascripts/labels_select.js.coffee
@@ -1,30 +1,32 @@
class @LabelsSelect
constructor: ->
$('.js-label-select').each (i, dropdown) ->
- projectId = $(dropdown).data('project-id')
- labelUrl = $(dropdown).data("labels")
- selectedLabel = $(dropdown).data('selected')
+ $dropdown = $(dropdown)
+ projectId = $dropdown.data('project-id')
+ labelUrl = $dropdown.data('labels')
+ selectedLabel = $dropdown.data('selected')
if selectedLabel
- selectedLabel = selectedLabel.split(",")
+ selectedLabel = selectedLabel.split(',')
newLabelField = $('#new_label_name')
newColorField = $('#new_label_color')
- showNo = $(dropdown).data('show-no')
- showAny = $(dropdown).data('show-any')
+ showNo = $dropdown.data('show-no')
+ showAny = $dropdown.data('show-any')
+ defaultLabel = $dropdown.data('default-label')
if newLabelField.length
- $('.suggest-colors-dropdown a').on "click", (e) ->
+ $('.suggest-colors-dropdown a').on 'click', (e) ->
e.preventDefault()
e.stopPropagation()
- newColorField.val $(this).data("color")
+ newColorField.val $(this).data('color')
$('.js-dropdown-label-color-preview')
- .css 'background-color', $(this).data("color")
+ .css 'background-color', $(this).data('color')
.addClass 'is-active'
- $('.js-new-label-btn').on "click", (e) ->
+ $('.js-new-label-btn').on 'click', (e) ->
e.preventDefault()
e.stopPropagation()
- if newLabelField.val() isnt "" && newColorField.val() isnt ""
+ if newLabelField.val() isnt '' and newColorField.val() isnt ''
$('.js-new-label-btn').disable()
# Create new label with API
@@ -33,46 +35,38 @@ class @LabelsSelect
color: newColorField.val()
}, (label) ->
$('.js-new-label-btn').enable()
- $('.dropdown-menu-back', $(dropdown).parent()).trigger "click"
+ $('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
- $(dropdown).glDropdown(
+ $dropdown.glDropdown(
data: (term, callback) ->
- # We have to fetch the JS version of the labels list because there is no
- # public facing JSON url for labels
$.ajax(
url: labelUrl
).done (data) ->
- html = $(data)
- data = []
- html.find('.label-row a').each ->
- data.push(
- title: $(@).text().trim()
- )
-
if showNo
data.unshift(
- id: "0"
- title: 'No label'
+ id: 0
+ title: 'No Label'
)
if showAny
data.unshift(
- title: 'Any label'
+ isAny: true
+ title: 'Any Label'
)
if data.length > 2
- data.splice 2, 0, "divider"
+ data.splice 2, 0, 'divider'
callback data
renderRow: (label) ->
if $.isArray(selectedLabel)
- selected = ""
+ selected = ''
$.each selectedLabel, (i, selectedLbl) ->
selectedLbl = selectedLbl.trim()
- if selected is "" && label.title is selectedLbl
- selected = "is-active"
+ if selected is '' and label.title is selectedLbl
+ selected = 'is-active'
else
- selected = if label.title is selectedLabel then "is-active" else ""
+ selected = if label.title is selectedLabel then 'is-active' else ''
"<li>
<a href='#' class='#{selected}'>
@@ -83,10 +77,24 @@ class @LabelsSelect
search:
fields: ['title']
selectable: true
- fieldName: $(dropdown).data('field-name')
+ toggleLabel: (selected) ->
+ if selected and selected.title isnt 'Any Label'
+ selected.title
+ else
+ defaultLabel
+ fieldName: $dropdown.data('field-name')
id: (label) ->
- label.title
+ if label.isAny?
+ ''
+ else
+ label.title
clicked: ->
- if $(dropdown).hasClass "js-filter-submit"
- $(dropdown).parents('form').submit()
+ page = $('body').data 'page'
+ isIssueIndex = page is 'projects:issues:index'
+ isMRIndex = page is page is 'projects:merge_requests:index'
+
+ if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
+ Issues.filterResults $dropdown.closest('form')
+ else if $dropdown.hasClass 'js-filter-submit'
+ $dropdown.closest('form').submit()
)
diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee
index 8322b4c46ad..839e6ec2c08 100644
--- a/app/assets/javascripts/merge_request_tabs.js.coffee
+++ b/app/assets/javascripts/merge_request_tabs.js.coffee
@@ -3,6 +3,8 @@
# Handles persisting and restoring the current tab selection and lazily-loading
# content on the MergeRequests#show page.
#
+#= require jquery.cookie
+#
# ### Example Markup
#
# <ul class="nav-links merge-request-tabs">
@@ -68,11 +70,15 @@ class @MergeRequestTabs
if action == 'commits'
@loadCommits($target.attr('href'))
+ @expandView()
else if action == 'diffs'
@loadDiff($target.attr('href'))
@shrinkView()
else if action == 'builds'
@loadBuilds($target.attr('href'))
+ @expandView()
+ else
+ @expandView()
@setCurrentAction(action)
@@ -189,11 +195,24 @@ class @MergeRequestTabs
$('.container-fluid').removeClass('container-limited')
shrinkView: ->
- $gutterIcon = $('.js-sidebar-toggle i')
+ $gutterIcon = $('.js-sidebar-toggle i:visible')
# Wait until listeners are set
setTimeout( ->
- # Only when sidebar is collapsed
+ # Only when sidebar is expanded
if $gutterIcon.is('.fa-angle-double-right')
- $gutterIcon.closest('a').trigger('click',[true])
+ $gutterIcon.closest('a').trigger('click', [true])
+ , 0)
+
+ # Expand the issuable sidebar unless the user explicitly collapsed it
+ expandView: ->
+ return if $.cookie('collapsed_gutter') == 'true'
+
+ $gutterIcon = $('.js-sidebar-toggle i:visible')
+
+ # Wait until listeners are set
+ setTimeout( ->
+ # Only when sidebar is collapsed
+ if $gutterIcon.is('.fa-angle-double-left')
+ $gutterIcon.closest('a').trigger('click', [true])
, 0)
diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee
index 5e884454a65..e17a1adb648 100644
--- a/app/assets/javascripts/milestone_select.js.coffee
+++ b/app/assets/javascripts/milestone_select.js.coffee
@@ -1,60 +1,65 @@
class @MilestoneSelect
constructor: ->
$('.js-milestone-select').each (i, dropdown) ->
- projectId = $(dropdown).data('project-id')
- milestonesUrl = $(dropdown).data('milestones')
- selectedMilestone = $(dropdown).data('selected')
- showNo = $(dropdown).data('show-no')
- showAny = $(dropdown).data('show-any')
- useId = $(dropdown).data('use-id')
+ $dropdown = $(dropdown)
+ projectId = $dropdown.data('project-id')
+ milestonesUrl = $dropdown.data('milestones')
+ selectedMilestone = $dropdown.data('selected')
+ showNo = $dropdown.data('show-no')
+ showAny = $dropdown.data('show-any')
+ useId = $dropdown.data('use-id')
+ defaultLabel = $dropdown.data('default-label')
- $(dropdown).glDropdown(
+ $dropdown.glDropdown(
data: (term, callback) ->
$.ajax(
url: milestonesUrl
).done (data) ->
- html = $(data)
- data = []
- html.find('.milestone strong a').each ->
- link = $(@).attr("href").split("/")
- data.push(
- id: link[link.length - 1]
- title: $(@).text().trim()
- )
-
if showNo
data.unshift(
- id: "0"
+ id: '0'
title: 'No Milestone'
)
if showAny
data.unshift(
+ isAny: true
title: 'Any Milestone'
)
if data.length > 2
- data.splice 2, 0, "divider"
+ data.splice 2, 0, 'divider'
callback(data)
filterable: true
search:
fields: ['title']
selectable: true
- fieldName: $(dropdown).data('field-name')
+ toggleLabel: (selected) ->
+ if selected && 'id' of selected
+ selected.title
+ else
+ defaultLabel
+ fieldName: $dropdown.data('field-name')
text: (milestone) ->
milestone.title
id: (milestone) ->
if !useId
- if milestone.title isnt "Any milestone"
+ if !milestone.isAny?
milestone.title
else
- ""
+ ''
else
milestone.id
isSelected: (milestone) ->
milestone.title is selectedMilestone
clicked: ->
- if $(dropdown).hasClass "js-filter-submit"
- $(dropdown).parents('form').submit()
+ page = $('body').data 'page'
+ isIssueIndex = page is 'projects:issues:index'
+ isMRIndex = page is page is 'projects:merge_requests:index'
+
+ if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
+ Issues.filterResults $dropdown.closest('form')
+ else if $dropdown.hasClass 'js-filter-submit'
+ $dropdown.closest('form').submit()
)
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 75d7f52bbb6..ff06c57f2b5 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -343,6 +343,7 @@ class @Notes
updateNote: (_xhr, note, _status) =>
# Convert returned HTML to a jQuery object so we can modify it further
$html = $(note.html)
+ $('.js-timeago', $html).timeago()
$html.syntaxHighlight()
$html.find('.js-task-list-container').taskList('enable')
@@ -360,14 +361,12 @@ class @Notes
showEditForm: (e) ->
e.preventDefault()
note = $(this).closest(".note")
- note.find(".note-body > .note-text").hide()
- note.find(".note-header").hide()
+ note.addClass "is-editting"
form = note.find(".note-edit-form")
isNewForm = form.is(':not(.gfm-form)')
if isNewForm
form.addClass('gfm-form')
form.addClass('current-note-edit-form')
- form.show()
# Show the attachment delete link
note.find(".js-note-attachment-delete").show()
@@ -401,11 +400,9 @@ class @Notes
cancelEdit: (e) ->
e.preventDefault()
note = $(this).closest(".note")
- note.find(".note-body > .note-text").show()
- note.find(".note-header").show()
+ note.removeClass "is-editting"
note.find(".current-note-edit-form")
.removeClass("current-note-edit-form")
- .hide()
###
Called in response to deleting a note of any kind.
@@ -626,10 +623,10 @@ class @Notes
if closebtn.text() isnt closetext
closebtn.text(closetext)
- if reopenbtn.is(':not(.btn-comment-and-reopen)')
+ if reopenbtn.is('.btn-comment-and-reopen')
reopenbtn.removeClass('btn-comment-and-reopen')
- if closebtn.is(':not(.btn-comment-and-close)')
+ if closebtn.is('.btn-comment-and-close')
closebtn.removeClass('btn-comment-and-close')
if discardbtn.is(':visible')
diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee
index 76bc4ff42a2..87d313ed67c 100644
--- a/app/assets/javascripts/project.js.coffee
+++ b/app/assets/javascripts/project.js.coffee
@@ -11,7 +11,6 @@ class @Project
$(@).toggleClass('active')
url = $("#project_clone").val()
- console.log("url",url)
# Update the input field
$('#project_clone').val(url)
diff --git a/app/assets/javascripts/project_new.js.coffee b/app/assets/javascripts/project_new.js.coffee
index fecdb9fc2e7..63dee4ed5d7 100644
--- a/app/assets/javascripts/project_new.js.coffee
+++ b/app/assets/javascripts/project_new.js.coffee
@@ -3,3 +3,16 @@ class @ProjectNew
$('.project-edit-container').on 'ajax:before', =>
$('.project-edit-container').hide()
$('.save-project-loader').show()
+ @toggleSettings()
+ @toggleSettingsOnclick()
+
+
+ toggleSettings: ->
+ checked = $("#project_builds_enabled").prop("checked")
+ if checked
+ $('.builds-feature').show()
+ else
+ $('.builds-feature').hide()
+
+ toggleSettingsOnclick: ->
+ $("#project_builds_enabled").on 'click', @toggleSettings
diff --git a/app/assets/javascripts/stat_graph_contributors_util.js.coffee b/app/assets/javascripts/stat_graph_contributors_util.js.coffee
index f5584bcfe4b..31617c88b4a 100644
--- a/app/assets/javascripts/stat_graph_contributors_util.js.coffee
+++ b/app/assets/javascripts/stat_graph_contributors_util.js.coffee
@@ -95,4 +95,4 @@ window.ContributorsStatGraphUtil =
if date_range is null || date_range[0] <= new Date(date) <= date_range[1]
true
else
- false \ No newline at end of file
+ false
diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee
new file mode 100644
index 00000000000..b6b4bd90e6a
--- /dev/null
+++ b/app/assets/javascripts/todos.js.coffee
@@ -0,0 +1,56 @@
+class @Todos
+ constructor: (@name) ->
+ @clearListeners()
+ @initBtnListeners()
+
+ clearListeners: ->
+ $('.done-todo').off('click')
+ $('.js-todos-mark-all').off('click')
+
+ initBtnListeners: ->
+ $('.done-todo').on('click', @doneClicked)
+ $('.js-todos-mark-all').on('click', @allDoneClicked)
+
+ doneClicked: (e) =>
+ e.preventDefault()
+ e.stopImmediatePropagation()
+
+ $this = $(e.currentTarget)
+ $this.disable()
+
+ $.ajax
+ type: 'POST'
+ url: $this.attr('href')
+ dataType: 'json'
+ data: '_method': 'delete'
+ success: (data) =>
+ @clearDone $this.closest('li')
+ @updateBadges data
+
+ allDoneClicked: (e) =>
+ e.preventDefault()
+ e.stopImmediatePropagation()
+
+ $this = $(e.currentTarget)
+ $this.disable()
+
+ $.ajax
+ type: 'POST'
+ url: $this.attr('href')
+ dataType: 'json'
+ data: '_method': 'delete'
+ success: (data) =>
+ $this.remove()
+ $('.js-todos-list').remove()
+ @updateBadges data
+
+ clearDone: ($row) ->
+ $ul = $row.closest('ul')
+ $row.remove()
+
+ if not $ul.find('li').length
+ $ul.parents('.panel').remove()
+
+ updateBadges: (data) ->
+ $('.todos-pending .badge, .todos-pending-count').text data.count
+ $('.todos-done .badge').text data.done_count
diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
index 987c6f4b8d2..3d6452d2f46 100644
--- a/app/assets/javascripts/users_select.js.coffee
+++ b/app/assets/javascripts/users_select.js.coffee
@@ -4,14 +4,16 @@ class @UsersSelect
@userPath = "/autocomplete/users/:id.json"
$('.js-user-search').each (i, dropdown) =>
- @projectId = $(dropdown).data('project-id')
- @showCurrentUser = $(dropdown).data('current-user')
- showNullUser = $(dropdown).data('null-user')
- showAnyUser = $(dropdown).data('any-user')
- firstUser = $(dropdown).data('first-user')
- selectedId = $(dropdown).data('selected')
-
- $(dropdown).glDropdown(
+ $dropdown = $(dropdown)
+ @projectId = $dropdown.data('project-id')
+ @showCurrentUser = $dropdown.data('current-user')
+ showNullUser = $dropdown.data('null-user')
+ showAnyUser = $dropdown.data('any-user')
+ firstUser = $dropdown.data('first-user')
+ selectedId = $dropdown.data('selected')
+ defaultLabel = $dropdown.data('default-label')
+
+ $dropdown.glDropdown(
data: (term, callback) =>
@users term, (users) =>
if term.length is 0
@@ -52,10 +54,21 @@ class @UsersSelect
search:
fields: ['name', 'username']
selectable: true
- fieldName: $(dropdown).data('field-name')
+ fieldName: $dropdown.data('field-name')
+ toggleLabel: (selected) ->
+ if selected && 'id' of selected
+ selected.name
+ else
+ defaultLabel
clicked: ->
- if $(dropdown).hasClass "js-filter-submit"
- $(dropdown).parents('form').submit()
+ page = $('body').data 'page'
+ isIssueIndex = page is 'projects:issues:index'
+ isMRIndex = page is page is 'projects:merge_requests:index'
+
+ if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
+ Issues.filterResults $dropdown.closest('form')
+ else if $dropdown.hasClass 'js-filter-submit'
+ $dropdown.closest('form').submit()
renderRow: (user) ->
username = if user.username then "@#{user.username}" else ""
avatar = if user.avatar_url then user.avatar_url else false
diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index b7ffa3e6ffb..5aa425dab6c 100644
--- a/app/assets/stylesheets/framework/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -16,7 +16,7 @@
}
&.group-avatar, &.project-avatar, &.avatar-tile {
- @include border-radius(0px);
+ @include border-radius(0);
}
&.s16 { width: 16px; height: 16px; margin-right: 6px; }
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 90c3ce0e84c..62b2af0dbf7 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -28,10 +28,6 @@
border-bottom: 1px solid $border-color;
color: $gl-gray;
- a {
- color: $md-link-color;
- }
-
&.oneline-block {
line-height: 42px;
}
@@ -111,10 +107,28 @@
margin: 0;
font-size: 23px;
font-weight: normal;
- margin: 16px 0 5px 0;
+ margin: 16px 0 5px;
color: #4c4e54;
font-size: 23px;
line-height: 1.1;
+
+ h1 {
+ color: #313236;
+ margin-bottom: 6px;
+ font-size: 23px;
+ }
+
+ .visibility-icon {
+ display: inline-block;
+ margin-left: 5px;
+ font-size: 18px;
+ color: $gray;
+ }
+
+ p {
+ padding: 0 $gl-padding;
+ color: #5c5d5e;
+ }
}
.cover-desc {
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index fa115a4bf56..657c5f033c7 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -208,3 +208,13 @@
background-color: #e4e7ed !important;
}
}
+
+.btn-loading {
+ &:not(.disabled) .fa {
+ display: none;
+ }
+
+ .fa {
+ margin-right: 5px;
+ }
+}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 3197ea84460..d92cf6e6c44 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -9,6 +9,12 @@
border-left: $caret-width-base solid transparent;
}
+.btn-group {
+ .caret {
+ margin-left: 0;
+ }
+}
+
.dropdown {
position: relative;
}
@@ -69,7 +75,7 @@
width: 240px;
margin-top: 2px;
margin-bottom: 0;
- padding: 10px 10px;
+ padding: 10px;
font-size: 14px;
font-weight: normal;
background-color: $dropdown-bg;
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index c431e2b0df3..40a508c1ebc 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -3,22 +3,11 @@
vertical-align: top;
}
-@media (min-width: 800px) {
+@media (min-width: $screen-sm-min) {
.issues-filters,
.issues_bulk_update {
- select, .select2-container {
- width: 120px !important;
- display: inline-block;
- }
- }
-}
-
-@media (min-width: 1200px) {
- .issues-filters,
- .issues_bulk_update {
- select, .select2-container {
- width: 150px !important;
- display: inline-block;
+ .dropdown-menu-toggle {
+ width: 132px;
}
}
}
diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss
index 2a4cf4fc335..c83cf881596 100644
--- a/app/assets/stylesheets/framework/gitlab-theme.scss
+++ b/app/assets/stylesheets/framework/gitlab-theme.scss
@@ -117,4 +117,4 @@ body {
&.ui_violet {
@include gitlab-theme(#98c, $theme-violet, #436, #325);
}
-} \ No newline at end of file
+}
diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss
index e901c78d02f..8bb047db2dd 100644
--- a/app/assets/stylesheets/framework/layout.scss
+++ b/app/assets/stylesheets/framework/layout.scss
@@ -16,7 +16,7 @@ body {
}
.container .content {
- margin: 0 0;
+ margin: 0;
}
.navless-container {
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index bfec0911b3c..b17c8bcbb1e 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -111,14 +111,17 @@ ul.content-list {
> li {
border-color: $table-border-color;
- color: $list-text-color;
font-size: $list-font-size;
+ color: $list-text-color;
.title {
- color: $list-title-color;
font-weight: 600;
}
+ a {
+ color: $gl-dark-link-color;
+ }
+
.description {
p {
@include str-truncated;
@@ -141,6 +144,10 @@ ul.content-list {
}
}
+.panel > .content-list > li {
+ padding: $gl-padding-top $gl-padding;
+}
+
ul.controls {
padding-top: 1px;
float: right;
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 377bfa174bd..250d6309291 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -1,7 +1,7 @@
/**
* Generic mixins
*/
- @mixin box-shadow($shadow) {
+@mixin box-shadow($shadow) {
-webkit-box-shadow: $shadow;
-moz-box-shadow: $shadow;
-ms-box-shadow: $shadow;
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index b3371229d5a..fa7944cdabe 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -41,7 +41,7 @@
}
.select2-drop {
- @include box-shadow(rgba(76, 86, 103, 0.247059) 0px 0px 1px 0px, rgba(31, 37, 50, 0.317647) 0px 2px 18px 0px);
+ @include box-shadow(rgba(76, 86, 103, 0.247059) 0 0 1px 0, rgba(31, 37, 50, 0.317647) 0 2px 18px 0);
@include border-radius ($border-radius-default);
border: none;
}
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 949295a1d0c..b1886fbe67b 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -39,8 +39,8 @@
h1 {
font-size: 1.3em;
font-weight: 600;
- margin: 24px 0 12px 0;
- padding: 0 0 10px 0;
+ margin: 24px 0 12px;
+ padding: 0 0 10px;
border-bottom: 1px solid #e7e9ed;
color: #313236;
}
@@ -48,27 +48,27 @@
h2 {
font-size: 1.2em;
font-weight: 600;
- margin: 24px 0 12px 0;
+ margin: 24px 0 12px;
color: #313236;
}
h3 {
- margin: 24px 0 12px 0;
+ margin: 24px 0 12px;
font-size: 1.1em;
}
h4 {
- margin: 24px 0 12px 0;
+ margin: 24px 0 12px;
font-size: 0.98em;
}
h5 {
- margin: 24px 0 12px 0;
+ margin: 24px 0 12px;
font-size: 0.95em;
}
h6 {
- margin: 24px 0 12px 0;
+ margin: 24px 0 12px;
font-size: 0.90em;
}
@@ -76,7 +76,7 @@
color: #7f8fa4;
font-size: inherit;
padding: 8px 21px;
- margin: 12px 0 12px;
+ margin: 12px 0;
border-left: 3px solid #e7e9ed;
}
@@ -88,13 +88,13 @@
p {
color: #5c5d5e;
- margin: 6px 0 0 0;
+ margin: 6px 0 0;
}
table {
@extend .table;
@extend .table-bordered;
- margin: 12px 0 12px 0;
+ margin: 12px 0;
color: #5c5d5e;
th {
background: #f8fafc;
@@ -102,7 +102,7 @@
}
pre {
- margin: 12px 0 12px 0;
+ margin: 12px 0;
font-size: 13px;
line-height: 1.6em;
overflow-x: auto;
@@ -191,7 +191,7 @@ body {
line-height: 1.3;
font-size: 1.25em;
font-weight: 600;
- margin: 12px 7px 12px 7px;
+ margin: 12px 7px;
}
h1, h2, h3, h4, h5, h6 {
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 211ead7319d..be626678bd7 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -1,45 +1,75 @@
-$row-hover: #f4f8fe;
-$gl-text-color: #54565b;
-$gl-text-green: #4a2;
-$gl-text-red: #d12f19;
-$gl-text-orange: #d90;
-$gl-header-color: #323232;
-$gl-link-color: #333c48;
-$md-text-color: #444;
-$md-link-color: #3084bb;
-$progress-color: #c0392b;
-$gl-font-size: 15px;
-$list-font-size: 15px;
+/*
+ * Layout
+ */
$sidebar_collapsed_width: 62px;
$sidebar_width: 230px;
$gutter_collapsed_width: 62px;
$gutter_width: 290px;
$gutter_inner_width: 258px;
-$avatar_radius: 50%;
+
+/*
+ * UI elements
+ */
+$border-color: #efeff1;
+$table-border-color: #eef0f2;
+$background-color: #faf9f9;
+
+/*
+ * Text
+ */
+$gl-font-size: 15px;
+$gl-title-color: #333;
+$gl-text-color: #555;
+$gl-text-green: #4a2;
+$gl-text-red: #d12f19;
+$gl-text-orange: #d90;
+$gl-link-color: #3084bb;
+$gl-dark-link-color: #333;
+$gl-placeholder-color: #8f8f8f;
+$gl-gray: $gl-text-color;
+$gl-header-color: $gl-title-color;
+
+/*
+ * Lists
+ */
+$list-font-size: $gl-font-size;
+$list-title-color: $gl-title-color;
+$list-text-color: $gl-text-color;
+
+/*
+ * Markdown
+ */
+$md-text-color: $gl-text-color;
+$md-link-color: $gl-link-color;
+
+/*
+ * Code
+ */
$code_font_size: 13px;
$code_line_height: 1.5;
-$border-color: #efeff1;
-$table-border-color: #eef0f2;
-$background-color: #faf9f9;
-$header-height: 58px;
-$fixed-layout-width: 1280px;
-$gl-gray: #5a5a5a;
+
+/*
+ * Padding
+ */
$gl-padding: 16px;
$gl-btn-padding: 10px;
$gl-vert-padding: 6px;
$gl-padding-top: 10px;
+
+/*
+ * Misc
+ */
+$row-hover: #f4f8fe;
+$progress-color: #c0392b;
+$avatar_radius: 50%;
+$header-height: 58px;
+$fixed-layout-width: 1280px;
$gl-avatar-size: 40px;
-$secondary-text: #7f8fa4;
$error-exclamation-point: #e62958;
$border-radius-default: 3px;
-$list-title-color: #333;
-$list-text-color: #555;
-
$btn-transparent-color: #8f8f8f;
-
$ssh-key-icon-color: #8f8f8f;
$ssh-key-icon-size: 18px;
-
$provider-btn-group-border: #e5e5e5;
$provider-btn-not-active-color: #4688f1;
diff --git a/app/assets/stylesheets/notify.scss b/app/assets/stylesheets/notify.scss
new file mode 100644
index 00000000000..f1d42f80f56
--- /dev/null
+++ b/app/assets/stylesheets/notify.scss
@@ -0,0 +1,24 @@
+img {
+ max-width: 100%;
+ height: auto;
+}
+p.details {
+ font-style:italic;
+ color:#777
+}
+.footer p {
+ font-size:small;
+ color:#777
+}
+pre.commit-message {
+ white-space: pre-wrap;
+}
+.file-stats a {
+ text-decoration: none;
+}
+.file-stats .new-file {
+ color: #090;
+}
+.file-stats .deleted-file {
+ color: #B00;
+}
diff --git a/app/assets/stylesheets/pages/admin.scss b/app/assets/stylesheets/pages/admin.scss
index a61161810a3..e05f14e7496 100644
--- a/app/assets/stylesheets/pages/admin.scss
+++ b/app/assets/stylesheets/pages/admin.scss
@@ -34,9 +34,9 @@
background: #fff
}
- .visibility-levels {
- .controls {
- margin-bottom: 9px;
+ .visibility-levels {
+ .controls {
+ margin-bottom: 9px;
}
i {
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index d57be1b2daa..33b3c7558ed 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -55,7 +55,7 @@ li.commit {
}
.commit-row-message {
- color: $gl-link-color;
+ color: $gl-dark-link-color;
&:hover {
text-decoration: underline;
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index db06b8288c2..f1368d74b3b 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -1,7 +1,7 @@
// Common
.diff-file {
border: 1px solid $border-color;
- border-top: none;
+ margin-bottom: $gl-padding;
.diff-header {
position: relative;
@@ -132,7 +132,7 @@
}
.image-info {
font-size: 12px;
- margin: 5px 0 0 0;
+ margin: 5px 0 0;
color: grey;
}
@@ -361,3 +361,11 @@
border-color: $border;
}
}
+
+.files {
+ margin-top: -1px;
+
+ .diff-file:last-child {
+ margin-bottom: 0;
+ }
+}
diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss
index b39a9abf40f..84eefd01cfe 100644
--- a/app/assets/stylesheets/pages/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -6,7 +6,7 @@
font-size: $gl-font-size;
padding: $gl-padding-top 0 $gl-padding-top ($gl-avatar-size + $gl-padding-top);
border-bottom: 1px solid $table-border-color;
- color: #7f8fa4;
+ color: $list-text-color;
&.event-inline {
.avatar {
@@ -21,7 +21,7 @@
}
a {
- color: #4c4e54;
+ color: $gl-dark-link-color;
}
.avatar {
@@ -31,10 +31,7 @@
.event-title {
@include str-truncated(calc(100% - 174px));
font-weight: 600;
-
- .author_name {
- color: #333;
- }
+ color: $list-text-color;
}
.event-body {
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index c975ca0ce43..5300bb52a1b 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -1,34 +1,3 @@
-@media (max-width: $screen-sm-max) {
- .issuable-affix {
- margin-top: 20px;
- }
-}
-
-@media (max-width: $screen-md-max) {
- .issuable-affix {
- position: static;
- }
-}
-
-@media (min-width: $screen-md-max) {
- .issuable-affix {
- &.affix-top {
- position: static;
- }
-
- &.affix {
- position: fixed;
- top: 70px;
- margin-right: 35px;
-
- &.no-affix {
- position: relative;
- top: 0;
- }
- }
- }
-}
-
.issuable-details {
section {
.issuable-discussion {
@@ -54,6 +23,10 @@
padding: 6px 10px;
}
}
+
+ &.has-labels {
+ margin-bottom: -5px;
+ }
}
.issuable-sidebar {
@@ -66,8 +39,9 @@
width: $gutter_inner_width;
// --
- &:first-child {
- padding-top: 5px;
+ &.issuable-sidebar-header {
+ padding-top: 0;
+ padding-bottom: 10px;
}
&:last-child {
@@ -75,7 +49,6 @@
}
span {
- margin-top: 7px;
display: inline-block;
}
@@ -84,7 +57,7 @@
}
.issuable-count {
-
+ margin-top: 7px;
}
.gutter-toggle {
@@ -99,19 +72,19 @@
.title {
color: $gl-text-color;
- margin-bottom: 8px;
+ margin-bottom: 10px;
+ line-height: 1;
.avatar {
margin-left: 0;
}
- label {
- font-weight: normal;
- margin-right: 4px;
- }
-
.edit-link {
color: $gl-gray;
+
+ &:hover {
+ color: $md-link-color;
+ }
}
}
@@ -144,11 +117,6 @@
.btn-clipboard {
color: $gl-gray;
}
-
- .participants .avatar {
- margin-top: 6px;
- margin-right: 2px;
- }
}
.right-sidebar {
@@ -163,8 +131,12 @@
&.right-sidebar-expanded {
width: $gutter_width;
- hr {
- display: none;
+ .value {
+ line-height: 1;
+ }
+
+ .bold {
+ font-weight: 600;
}
.sidebar-collapsed-icon {
@@ -172,8 +144,23 @@
}
.gutter-toggle {
+ margin-top: 7px;
border-left: 1px solid $border-gray-light;
}
+
+ .assignee .avatar {
+ float: left;
+ margin-right: 10px;
+ margin-bottom: 0;
+ margin-left: 0;
+ }
+
+ .username {
+ display: block;
+ margin-top: 4px;
+ font-size: 13px;
+ font-weight: normal;
+ }
}
.subscribe-button {
@@ -193,28 +180,26 @@
width: $sidebar_collapsed_width;
padding-top: 0;
- hr {
- margin: 0;
- color: $gray-normal;
- border-color: $gray-normal;
- width: 62px;
- margin-left: -20px
- }
-
.block {
width: $sidebar_collapsed_width - 1px;
margin-left: -19px;
- padding: 15px 0 0 0;
+ padding: 15px 0 0;
border-bottom: none;
overflow: hidden;
}
+ .participants {
+ border-bottom: 1px solid $border-gray-light;
+ }
+
.hide-collapsed {
display: none;
}
.gutter-toggle {
- margin-left: -36px;
+ width: 100%;
+ margin-left: 0;
+ padding-left: 25px;
}
.sidebar-collapsed-icon {
@@ -229,6 +214,10 @@
margin-top: 0;
}
+ .author {
+ display: none;
+ }
+
.btn-clipboard {
border: none;
@@ -241,6 +230,11 @@
}
}
}
+
+ .sidebar-collapsed-user {
+ padding-bottom: 0;
+ margin-bottom: 10px;
+ }
}
.btn {
@@ -251,6 +245,13 @@
border: 1px solid $border-gray-dark;
}
}
+
+ a:not(.btn) {
+ &:hover {
+ color: $md-link-color;
+ text-decoration: none;
+ }
+ }
}
.btn-default.gutter-toggle {
@@ -262,3 +263,37 @@
color: $gray-darkest;
}
}
+
+.edited-text {
+ color: $gray-darkest;
+
+ .author_link {
+ color: $gray-darkest;
+ }
+}
+
+.participants-list {
+ margin: -5px;
+}
+
+.participants-author {
+ display: inline-block;
+ padding: 5px;
+
+ .author_link {
+ display: block;
+ }
+
+ .avatar.avatar-inline {
+ margin: 0;
+ }
+}
+
+.participants-more {
+ margin-top: 5px;
+ margin-left: 5px;
+
+ a {
+ color: #8c8c8c;
+ }
+}
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 7ac4bc468d6..6a1d28590c2 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -3,7 +3,7 @@
padding: 10px $gl-padding;
position: relative;
- .issue-title {
+ .title {
margin-bottom: 2px;
}
@@ -130,14 +130,14 @@ form.edit-issue {
}
.issue-closed-by-widget {
- color: $secondary-text;
+ color: $gl-text-color;
margin-left: 52px;
}
.editor-details {
display: block;
-
+
@media (min-width: $screen-sm-min) {
display: inline-block;
}
-} \ No newline at end of file
+}
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
index bc41f7d306f..777bcbca5c3 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -45,7 +45,7 @@
.login-heading h3 {
font-weight: 300;
line-height: 1.5;
- margin: 0 0 10px 0;
+ margin: 0 0 10px;
}
.login-footer {
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 61783ec46aa..daf2651425f 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -26,7 +26,7 @@
display: none;
}
-.new_note, .edit_note {
+.new_note, .note-edit-form {
.note-form-actions {
margin-top: $gl-padding;
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index d408853cc80..4bd2016bdcf 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -100,6 +100,18 @@ ul.notes {
display: block;
position: relative;
+ &.is-editting {
+ .note-header,
+ .note-text,
+ .edited-text {
+ display: none;
+ }
+
+ .note-edit-form {
+ display: block;
+ }
+ }
+
.note-body {
overflow: auto;
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 3831f88f01e..d8c991356af 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -54,7 +54,7 @@
}
.account-well {
- padding: 10px 10px;
+ padding: 10px;
background-color: $help-well-bg;
border: 1px solid $help-well-border;
border-radius: $border-radius-base;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 6c600c99d51..c68bd673a67 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -33,6 +33,13 @@
.project-settings-dropdown {
margin-left: 10px;
display: inline-block;
+
+ .dropdown-menu {
+ left: auto;
+ width: auto;
+ right: 0;
+ max-width: 240px;
+ }
}
}
@@ -61,28 +68,6 @@
}
}
- .project-home-desc {
- h1 {
- color: #313236;
- margin: 0;
- margin-bottom: 6px;
- font-size: 23px;
- font-weight: normal;
- }
-
- .visibility-icon {
- display: inline-block;
- margin-left: 5px;
- font-size: 18px;
- color: $gray;
- }
-
- p {
- padding: 0 $gl-padding;
- color: #5c5d5e;
- }
- }
-
.project-repo-buttons {
margin-top: 20px;
margin-bottom: 0;
@@ -326,7 +311,7 @@ pre.light-well {
}
.git-empty {
- margin: 0 7px 0 7px;
+ margin: 0 7px;
h5 {
color: #5c5d5e;
diff --git a/app/assets/stylesheets/pages/stat_graph.scss b/app/assets/stylesheets/pages/stat_graph.scss
index b9be47e7700..85a0304196c 100644
--- a/app/assets/stylesheets/pages/stat_graph.scss
+++ b/app/assets/stylesheets/pages/stat_graph.scss
@@ -16,7 +16,7 @@
#contributors {
.contributors-list {
- margin: 0 0 10px 0;
+ margin: 0 0 10px;
list-style: none;
padding: 0;
}
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index 27970eba159..f983e9829e6 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -14,25 +14,8 @@
}
.todo-item {
- font-size: $gl-font-size;
- padding-left: $gl-avatar-size + $gl-padding-top;
- color: $secondary-text;
-
- a {
- color: #4c4e54;
- }
-
- .avatar {
- margin-left: -($gl-avatar-size + $gl-padding-top);
- }
-
.todo-title {
@include str-truncated(calc(100% - 174px));
- font-weight: 600;
-
- .author-name {
- color: #333;
- }
}
.todo-body {
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 73c7c9f687c..25b5e95583e 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -41,7 +41,7 @@
vertical-align: middle;
i, a {
- color: $gl-link-color;
+ color: $gl-dark-link-color;
}
img {
diff --git a/app/assets/stylesheets/pages/xterm.scss b/app/assets/stylesheets/pages/xterm.scss
index 8886c1dff56..3f28e402929 100644
--- a/app/assets/stylesheets/pages/xterm.scss
+++ b/app/assets/stylesheets/pages/xterm.scss
@@ -21,19 +21,19 @@
$l-white: #fff;
.term-bold {
- font-weight: bold;
+ font-weight: bold;
}
.term-italic {
- font-style: italic;
+ font-style: italic;
}
.term-conceal {
- visibility: hidden;
+ visibility: hidden;
}
.term-underline {
- text-decoration: underline;
+ text-decoration: underline;
}
.term-cross {
- text-decoration: line-through;
+ text-decoration: line-through;
}
.term-fg-black {
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 04a99d8c84a..ed9f6031389 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -61,6 +61,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:session_expire_delay,
:default_project_visibility,
:default_snippet_visibility,
+ :default_group_visibility,
:restricted_signup_domains_raw,
:version_check_enabled,
:admin_notification_email,
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index 668396a0f20..a6db4690df0 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -5,12 +5,12 @@ class Admin::GroupsController < Admin::ApplicationController
@groups = Group.all
@groups = @groups.sort(@sort = params[:sort])
@groups = @groups.search(params[:name]) if params[:name].present?
- @groups = @groups.page(params[:page]).per(PER_PAGE)
+ @groups = @groups.page(params[:page])
end
def show
- @members = @group.members.order("access_level DESC").page(params[:members_page]).per(PER_PAGE)
- @projects = @group.projects.page(params[:projects_page]).per(PER_PAGE)
+ @members = @group.members.order("access_level DESC").page(params[:members_page])
+ @projects = @group.projects.page(params[:projects_page])
end
def new
@@ -59,6 +59,6 @@ class Admin::GroupsController < Admin::ApplicationController
end
def group_params
- params.require(:group).permit(:name, :description, :path, :avatar)
+ params.require(:group).permit(:name, :description, :path, :avatar, :visibility_level)
end
end
diff --git a/app/controllers/admin/labels_controller.rb b/app/controllers/admin/labels_controller.rb
index d79ce2b10fe..d496f08a598 100644
--- a/app/controllers/admin/labels_controller.rb
+++ b/app/controllers/admin/labels_controller.rb
@@ -2,7 +2,7 @@ class Admin::LabelsController < Admin::ApplicationController
before_action :set_label, only: [:show, :edit, :update, :destroy]
def index
- @labels = Label.templates.page(params[:page]).per(PER_PAGE)
+ @labels = Label.templates.page(params[:page])
end
def show
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index ae1de06b983..4089091d569 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -1,7 +1,6 @@
class Admin::ProjectsController < Admin::ApplicationController
before_action :project, only: [:show, :transfer]
before_action :group, only: [:show, :transfer]
- before_action :repository, only: [:show, :transfer]
def index
@projects = Project.all
@@ -12,15 +11,15 @@ class Admin::ProjectsController < Admin::ApplicationController
@projects = @projects.non_archived unless params[:with_archived].present?
@projects = @projects.search(params[:name]) if params[:name].present?
@projects = @projects.sort(@sort = params[:sort])
- @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(PER_PAGE)
+ @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page])
end
def show
if @group
- @group_members = @group.members.order("access_level DESC").page(params[:group_members_page]).per(PER_PAGE)
+ @group_members = @group.members.order("access_level DESC").page(params[:group_members_page])
end
- @project_members = @project.project_members.page(params[:project_members_page]).per(PER_PAGE)
+ @project_members = @project.project_members.page(params[:project_members_page])
end
def transfer
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 1f55b18e0b1..c81cb85dc1b 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -6,8 +6,6 @@ class ApplicationController < ActionController::Base
include GitlabRoutingHelper
include PageLayoutHelper
- PER_PAGE = 20
-
before_action :authenticate_user_from_token!
before_action :authenticate_user!
before_action :validate_user_service_ticket!
@@ -25,7 +23,6 @@ class ApplicationController < ActionController::Base
helper_method :abilities, :can?, :current_application_settings
helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?
- helper_method :repository, :can_collaborate_with_project?
rescue_from Encoding::CompatibilityError do |exception|
log_exception(exception)
@@ -118,47 +115,6 @@ class ApplicationController < ActionController::Base
abilities.allowed?(object, action, subject)
end
- def project
- unless @project
- namespace = params[:namespace_id]
- id = params[:project_id] || params[:id]
-
- # Redirect from
- # localhost/group/project.git
- # to
- # localhost/group/project
- #
- if id =~ /\.git\Z/
- redirect_to request.original_url.gsub(/\.git\/?\Z/, '') and return
- end
-
- project_path = "#{namespace}/#{id}"
- @project = Project.find_with_namespace(project_path)
-
- if @project and can?(current_user, :read_project, @project)
- if @project.path_with_namespace != project_path
- redirect_to request.original_url.gsub(project_path, @project.path_with_namespace) and return
- end
- @project
- elsif current_user.nil?
- @project = nil
- authenticate_user!
- else
- @project = nil
- render_404 and return
- end
- end
- @project
- end
-
- def repository
- @repository ||= project.repository
- end
-
- def authorize_project!(action)
- return access_denied! unless can?(current_user, action, project)
- end
-
def access_denied!
render "errors/access_denied", layout: "errors", status: 404
end
@@ -167,14 +123,6 @@ class ApplicationController < ActionController::Base
render "errors/git_not_found.html", layout: "errors", status: 404
end
- def method_missing(method_sym, *arguments, &block)
- if method_sym.to_s =~ /\Aauthorize_(.*)!\z/
- authorize_project!($1.to_sym)
- else
- super
- end
- end
-
def render_403
head :forbidden
end
@@ -183,10 +131,6 @@ class ApplicationController < ActionController::Base
render file: Rails.root.join("public", "404"), layout: false, status: "404"
end
- def require_non_empty_project
- redirect_to @project if @project.empty_repo?
- end
-
def no_cache_headers
response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
response.headers["Pragma"] = "no-cache"
@@ -412,13 +356,6 @@ class ApplicationController < ActionController::Base
current_user.nil? && root_path == request.path
end
- def can_collaborate_with_project?(project = nil)
- project ||= @project
-
- can?(current_user, :push_code, project) ||
- (current_user && current_user.already_forked?(project))
- end
-
private
def set_default_sort
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index 77c8dafc012..81ba58ce49c 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -7,7 +7,7 @@ class AutocompleteController < ApplicationController
@users = @users.search(params[:search]) if params[:search].present?
@users = @users.active
@users = @users.reorder(:name)
- @users = @users.page(params[:page]).per(PER_PAGE)
+ @users = @users.page(params[:page])
if params[:search].blank?
# Include current user if available to filter by "Me"
diff --git a/app/controllers/concerns/global_milestones.rb b/app/controllers/concerns/global_milestones.rb
index 3e4c0e63601..54ea1e454fc 100644
--- a/app/controllers/concerns/global_milestones.rb
+++ b/app/controllers/concerns/global_milestones.rb
@@ -6,7 +6,7 @@ module GlobalMilestones
@milestones = MilestonesFinder.new.execute(@projects, params)
@milestones = GlobalMilestone.build_collection(@milestones)
@milestones = @milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date }
- @milestones = Kaminari.paginate_array(@milestones).page(params[:page]).per(ApplicationController::PER_PAGE)
+ @milestones = Kaminari.paginate_array(@milestones).page(params[:page])
end
def milestone
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
new file mode 100644
index 00000000000..f40b62446e5
--- /dev/null
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -0,0 +1,23 @@
+module IssuableActions
+ extend ActiveSupport::Concern
+
+ included do
+ before_action :authorize_destroy_issuable!, only: :destroy
+ end
+
+ def destroy
+ issuable.destroy
+
+ name = issuable.class.name.titleize.downcase
+ flash[:notice] = "The #{name} was successfully deleted."
+ redirect_to polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class])
+ end
+
+ private
+
+ def authorize_destroy_issuable!
+ unless current_user.can?(:"destroy_#{issuable.to_ability_name}", issuable)
+ return access_denied!
+ end
+ end
+end
diff --git a/app/controllers/concerns/issues_action.rb b/app/controllers/concerns/issues_action.rb
index ef8e74a4641..4feabc32b1c 100644
--- a/app/controllers/concerns/issues_action.rb
+++ b/app/controllers/concerns/issues_action.rb
@@ -3,7 +3,7 @@ module IssuesAction
def issues
@issues = get_issues_collection.non_archived
- @issues = @issues.page(params[:page]).per(ApplicationController::PER_PAGE)
+ @issues = @issues.page(params[:page])
@issues = @issues.preload(:author, :project)
@label = @issuable_finder.labels.first
diff --git a/app/controllers/concerns/merge_requests_action.rb b/app/controllers/concerns/merge_requests_action.rb
index 9c49596bd0b..06a6b065e7e 100644
--- a/app/controllers/concerns/merge_requests_action.rb
+++ b/app/controllers/concerns/merge_requests_action.rb
@@ -3,7 +3,7 @@ module MergeRequestsAction
def merge_requests
@merge_requests = get_merge_requests_collection.non_archived
- @merge_requests = @merge_requests.page(params[:page]).per(ApplicationController::PER_PAGE)
+ @merge_requests = @merge_requests.page(params[:page])
@merge_requests = @merge_requests.preload(:author, :target_project)
@label = @issuable_finder.labels.first
diff --git a/app/controllers/dashboard/groups_controller.rb b/app/controllers/dashboard/groups_controller.rb
index 3bc94ff2187..71ba6153021 100644
--- a/app/controllers/dashboard/groups_controller.rb
+++ b/app/controllers/dashboard/groups_controller.rb
@@ -1,5 +1,5 @@
class Dashboard::GroupsController < Dashboard::ApplicationController
def index
- @group_members = current_user.group_members.page(params[:page]).per(PER_PAGE)
+ @group_members = current_user.group_members.page(params[:page])
end
end
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index 0e8b63872ca..71acc244a91 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -8,7 +8,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@projects = filter_projects(@projects)
@projects = @projects.includes(:namespace)
@projects = @projects.sort(@sort = params[:sort])
- @projects = @projects.page(params[:page]).per(PER_PAGE)
+ @projects = @projects.page(params[:page])
@last_push = current_user.recent_push
@@ -32,7 +32,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@projects = filter_projects(@projects)
@projects = @projects.includes(:namespace, :forked_from_project, :tags)
@projects = @projects.sort(@sort = params[:sort])
- @projects = @projects.page(params[:page]).per(PER_PAGE)
+ @projects = @projects.page(params[:page])
@last_push = current_user.recent_push
@groups = []
diff --git a/app/controllers/dashboard/snippets_controller.rb b/app/controllers/dashboard/snippets_controller.rb
index b3594d82530..bcfdbe14be9 100644
--- a/app/controllers/dashboard/snippets_controller.rb
+++ b/app/controllers/dashboard/snippets_controller.rb
@@ -6,6 +6,6 @@ class Dashboard::SnippetsController < Dashboard::ApplicationController
user: current_user,
scope: params[:scope]
)
- @snippets = @snippets.page(params[:page]).per(PER_PAGE)
+ @snippets = @snippets.page(params[:page])
end
end
diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb
index 43cf8fa71af..5abf97342c3 100644
--- a/app/controllers/dashboard/todos_controller.rb
+++ b/app/controllers/dashboard/todos_controller.rb
@@ -1,25 +1,34 @@
class Dashboard::TodosController < Dashboard::ApplicationController
- before_action :find_todos, only: [:index, :destroy_all]
+ before_action :find_todos, only: [:index, :destroy, :destroy_all]
def index
- @todos = @todos.page(params[:page]).per(PER_PAGE)
+ @todos = @todos.page(params[:page])
end
def destroy
- todo.done!
+ todo.done
+
+ todo_notice = 'Todo was successfully marked as done.'
respond_to do |format|
- format.html { redirect_to dashboard_todos_path, notice: 'Todo was successfully marked as done.' }
+ format.html { redirect_to dashboard_todos_path, notice: todo_notice }
format.js { render nothing: true }
+ format.json do
+ render json: { count: @todos.size, done_count: current_user.todos.done.count }
+ end
end
end
def destroy_all
- @todos.each(&:done!)
+ @todos.each(&:done)
respond_to do |format|
format.html { redirect_to dashboard_todos_path, notice: 'All todos were marked as done.' }
format.js { render nothing: true }
+ format.json do
+ find_todos
+ render json: { count: @todos.size, done_count: current_user.todos.done.count }
+ end
end
end
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index 139e40db180..b538c7d1608 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -3,7 +3,7 @@ class DashboardController < Dashboard::ApplicationController
include MergeRequestsAction
before_action :event_filter, only: :activity
- before_action :projects, only: [:issues, :merge_requests]
+ before_action :projects, only: [:issues, :merge_requests, :labels, :milestones]
respond_to :html
@@ -20,6 +20,29 @@ class DashboardController < Dashboard::ApplicationController
end
end
+ def labels
+ labels = Label.where(project_id: @projects).select(:title, :color).uniq(:title)
+
+ respond_to do |format|
+ format.json do
+ render json: labels
+ end
+ end
+ end
+
+ def milestones
+ milestones = Milestone.where(project_id: @projects).active
+ epoch = DateTime.parse('1970-01-01')
+ grouped_milestones = GlobalMilestone.build_collection(milestones)
+ grouped_milestones = grouped_milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date }
+
+ respond_to do |format|
+ format.json do
+ render json: grouped_milestones
+ end
+ end
+ end
+
protected
def load_events
diff --git a/app/controllers/explore/groups_controller.rb b/app/controllers/explore/groups_controller.rb
index a9bf4321f73..a962f9a0937 100644
--- a/app/controllers/explore/groups_controller.rb
+++ b/app/controllers/explore/groups_controller.rb
@@ -1,8 +1,8 @@
class Explore::GroupsController < Explore::ApplicationController
def index
- @groups = Group.order_id_desc
+ @groups = GroupsFinder.new.execute(current_user)
@groups = @groups.search(params[:search]) if params[:search].present?
@groups = @groups.sort(@sort = params[:sort])
- @groups = @groups.page(params[:page]).per(PER_PAGE)
+ @groups = @groups.page(params[:page])
end
end
diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb
index 8271ca87436..88a0c18180b 100644
--- a/app/controllers/explore/projects_controller.rb
+++ b/app/controllers/explore/projects_controller.rb
@@ -8,7 +8,7 @@ class Explore::ProjectsController < Explore::ApplicationController
@projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present?
@projects = filter_projects(@projects)
@projects = @projects.sort(@sort = params[:sort])
- @projects = @projects.includes(:namespace).page(params[:page]).per(PER_PAGE)
+ @projects = @projects.includes(:namespace).page(params[:page])
respond_to do |format|
format.html
@@ -23,7 +23,7 @@ class Explore::ProjectsController < Explore::ApplicationController
def trending
@projects = TrendingProjectsFinder.new.execute(current_user)
@projects = filter_projects(@projects)
- @projects = @projects.page(params[:page]).per(PER_PAGE)
+ @projects = @projects.page(params[:page])
respond_to do |format|
format.html
@@ -39,7 +39,7 @@ class Explore::ProjectsController < Explore::ApplicationController
@projects = ProjectsFinder.new.execute(current_user)
@projects = filter_projects(@projects)
@projects = @projects.reorder('star_count DESC')
- @projects = @projects.page(params[:page]).per(PER_PAGE)
+ @projects = @projects.page(params[:page])
respond_to do |format|
format.html
diff --git a/app/controllers/explore/snippets_controller.rb b/app/controllers/explore/snippets_controller.rb
index b70ac51d06e..28760c3f84b 100644
--- a/app/controllers/explore/snippets_controller.rb
+++ b/app/controllers/explore/snippets_controller.rb
@@ -1,6 +1,6 @@
class Explore::SnippetsController < Explore::ApplicationController
def index
@snippets = SnippetsFinder.new.execute(current_user, filter: :all)
- @snippets = @snippets.page(params[:page]).per(PER_PAGE)
+ @snippets = @snippets.page(params[:page])
end
end
diff --git a/app/controllers/groups/application_controller.rb b/app/controllers/groups/application_controller.rb
index be801858eaf..949b4a6c25a 100644
--- a/app/controllers/groups/application_controller.rb
+++ b/app/controllers/groups/application_controller.rb
@@ -1,21 +1,32 @@
class Groups::ApplicationController < ApplicationController
layout 'group'
+
+ skip_before_action :authenticate_user!
before_action :group
private
def group
- @group ||= Group.find_by(path: params[:group_id])
- end
+ unless @group
+ id = params[:group_id] || params[:id]
+ @group = Group.find_by(path: id)
+
+ unless @group && can?(current_user, :read_group, @group)
+ @group = nil
- def authorize_read_group!
- unless @group and can?(current_user, :read_group, @group)
- if current_user.nil?
- return authenticate_user!
- else
- return render_404
+ if current_user.nil?
+ authenticate_user!
+ else
+ render_404
+ end
end
end
+
+ @group
+ end
+
+ def group_projects
+ @projects ||= GroupProjectsFinder.new(group).execute(current_user)
end
def authorize_admin_group!
diff --git a/app/controllers/groups/avatars_controller.rb b/app/controllers/groups/avatars_controller.rb
index 76c87366baa..ad2c20b42db 100644
--- a/app/controllers/groups/avatars_controller.rb
+++ b/app/controllers/groups/avatars_controller.rb
@@ -1,4 +1,6 @@
class Groups::AvatarsController < Groups::ApplicationController
+ before_action :authorize_admin_group!
+
def destroy
@group.remove_avatar!
@group.save
diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb
index 0e902c4bb43..d5ef33888c6 100644
--- a/app/controllers/groups/group_members_controller.rb
+++ b/app/controllers/groups/group_members_controller.rb
@@ -1,8 +1,5 @@
class Groups::GroupMembersController < Groups::ApplicationController
- skip_before_action :authenticate_user!, only: [:index]
-
# Authorize
- before_action :authorize_read_group!
before_action :authorize_admin_group_member!, except: [:index, :leave]
def index
diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb
index 0c2a350bc39..0028f072d5b 100644
--- a/app/controllers/groups/milestones_controller.rb
+++ b/app/controllers/groups/milestones_controller.rb
@@ -1,10 +1,10 @@
class Groups::MilestonesController < Groups::ApplicationController
include GlobalMilestones
- before_action :projects
+ before_action :group_projects
before_action :milestones, only: [:index]
before_action :milestone, only: [:show, :update]
- before_action :authorize_group_milestone!, only: [:create, :update]
+ before_action :authorize_admin_milestones!, only: [:new, :create, :update]
def index
end
@@ -17,7 +17,7 @@ class Groups::MilestonesController < Groups::ApplicationController
project_ids = params[:milestone][:project_ids]
title = milestone_params[:title]
- @group.projects.where(id: project_ids).each do |project|
+ @projects.where(id: project_ids).each do |project|
Milestones::CreateService.new(project, current_user, milestone_params).execute
end
@@ -37,7 +37,7 @@ class Groups::MilestonesController < Groups::ApplicationController
private
- def authorize_group_milestone!
+ def authorize_admin_milestones!
return render_404 unless can?(current_user, :admin_milestones, group)
end
@@ -48,8 +48,4 @@ class Groups::MilestonesController < Groups::ApplicationController
def milestone_path(title)
group_milestone_path(@group, title.to_slug.to_s, title: title)
end
-
- def projects
- @projects ||= @group.projects
- end
end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 06c5c8be9a5..c1adc999567 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -5,16 +5,15 @@ class GroupsController < Groups::ApplicationController
respond_to :html
- skip_before_action :authenticate_user!, only: [:index, :show, :issues, :merge_requests]
+ before_action :authenticate_user!, only: [:new, :create]
before_action :group, except: [:index, :new, :create]
# Authorize
- before_action :authorize_read_group!, except: [:index, :show, :new, :create, :autocomplete]
before_action :authorize_admin_group!, only: [:edit, :update, :destroy, :projects]
before_action :authorize_create_group!, only: [:new, :create]
# Load group projects
- before_action :load_projects, except: [:index, :new, :create, :projects, :edit, :update, :autocomplete]
+ before_action :group_projects, only: [:show, :projects, :activity, :issues, :merge_requests]
before_action :event_filter, only: [:activity]
layout :determine_layout
@@ -28,11 +27,9 @@ class GroupsController < Groups::ApplicationController
end
def create
- @group = Group.new(group_params)
- @group.name = @group.path.dup unless @group.name
+ @group = Groups::CreateService.new(current_user, group_params).execute
- if @group.save
- @group.add_owner(current_user)
+ if @group.persisted?
redirect_to @group, notice: "Group '#{@group.name}' was successfully created."
else
render action: "new"
@@ -41,12 +38,13 @@ class GroupsController < Groups::ApplicationController
def show
@last_push = current_user.recent_push if current_user
+
@projects = @projects.includes(:namespace)
@projects = filter_projects(@projects)
@projects = @projects.sort(@sort = params[:sort])
- @projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
+ @projects = @projects.page(params[:page]) if params[:filter_projects].blank?
- @shared_projects = @group.shared_projects
+ @shared_projects = GroupProjectsFinder.new(group, only_shared: true).execute(current_user)
respond_to do |format|
format.html
@@ -83,7 +81,7 @@ class GroupsController < Groups::ApplicationController
end
def update
- if @group.update_attributes(group_params)
+ if Groups::UpdateService.new(@group, current_user, group_params).execute
redirect_to edit_group_path(@group), notice: "Group '#{@group.name}' was successfully updated."
else
render action: "edit"
@@ -98,26 +96,6 @@ class GroupsController < Groups::ApplicationController
protected
- def group
- @group ||= Group.find_by(path: params[:id])
- @group || render_404
- end
-
- def load_projects
- @projects ||= ProjectsFinder.new.execute(current_user, group: group).sorted_by_activity
- end
-
- # Dont allow unauthorized access to group
- def authorize_read_group!
- unless @group and (@projects.present? or can?(current_user, :read_group, @group))
- if current_user.nil?
- return authenticate_user!
- else
- return render_404
- end
- end
- end
-
def authorize_create_group!
unless can?(current_user, :create_group, nil)
return render_404
@@ -135,7 +113,7 @@ class GroupsController < Groups::ApplicationController
end
def group_params
- params.require(:group).permit(:name, :description, :path, :avatar, :public, :share_with_group_lock)
+ params.require(:group).permit(:name, :description, :path, :avatar, :public, :visibility_level, :share_with_group_lock)
end
def load_events
diff --git a/app/controllers/namespaces_controller.rb b/app/controllers/namespaces_controller.rb
index 282012c60a1..5a94dcb0dbd 100644
--- a/app/controllers/namespaces_controller.rb
+++ b/app/controllers/namespaces_controller.rb
@@ -14,7 +14,7 @@ class NamespacesController < ApplicationController
if user
redirect_to user_path(user)
- elsif group
+ elsif group && can?(current_user, :read_group, namespace)
redirect_to group_path(group)
elsif current_user.nil?
authenticate_user!
diff --git a/app/controllers/oauth/authorizations_controller.rb b/app/controllers/oauth/authorizations_controller.rb
index 24025d8c723..c721dca58d9 100644
--- a/app/controllers/oauth/authorizations_controller.rb
+++ b/app/controllers/oauth/authorizations_controller.rb
@@ -7,6 +7,7 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
if pre_auth.authorizable?
if skip_authorization? || matching_token?
auth = authorization.authorize
+ session.delete(:user_return_to)
redirect_to auth.redirect_uri
else
render "doorkeeper/authorizations/new"
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index 30e2886caca..c5fa756d02b 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -35,8 +35,7 @@ class ProfilesController < Profiles::ApplicationController
def audit_log
@events = AuditEvent.where(entity_type: "User", entity_id: current_user.id).
order("created_at DESC").
- page(params[:page]).
- per(PER_PAGE)
+ page(params[:page])
end
def update_username
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index a326bc58215..657ee94cfd7 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -1,20 +1,74 @@
class Projects::ApplicationController < ApplicationController
+ skip_before_action :authenticate_user!
before_action :project
before_action :repository
layout 'project'
- def authenticate_user!
- # Restrict access to Projects area only
- # for non-signed users
- if !current_user
+ helper_method :repository, :can_collaborate_with_project?
+
+ private
+
+ def project
+ unless @project
+ namespace = params[:namespace_id]
id = params[:project_id] || params[:id]
- project_with_namespace = "#{params[:namespace_id]}/#{id}"
- @project = Project.find_with_namespace(project_with_namespace)
- return if @project && @project.public?
+ # Redirect from
+ # localhost/group/project.git
+ # to
+ # localhost/group/project
+ #
+ if id =~ /\.git\Z/
+ redirect_to request.original_url.gsub(/\.git\/?\Z/, '')
+ return
+ end
+
+ project_path = "#{namespace}/#{id}"
+ @project = Project.find_with_namespace(project_path)
+
+ if @project && can?(current_user, :read_project, @project)
+ if @project.path_with_namespace != project_path
+ redirect_to request.original_url.gsub(project_path, @project.path_with_namespace)
+ end
+ else
+ @project = nil
+
+ if current_user.nil?
+ authenticate_user!
+ else
+ render_404
+ end
+ end
+ end
+
+ @project
+ end
+
+ def repository
+ @repository ||= project.repository
+ end
+
+ def can_collaborate_with_project?(project = nil)
+ project ||= @project
+
+ can?(current_user, :push_code, project) ||
+ (current_user && current_user.already_forked?(project))
+ end
+
+ def authorize_project!(action)
+ return access_denied! unless can?(current_user, action, project)
+ end
+
+ def method_missing(method_sym, *arguments, &block)
+ if method_sym.to_s =~ /\Aauthorize_(.*)!\z/
+ authorize_project!($1.to_sym)
+ else
+ super
end
+ end
- super
+ def require_non_empty_project
+ redirect_to namespace_project_path(@project.namespace, @project) if @project.empty_repo?
end
def require_branch_head
@@ -26,8 +80,6 @@ class Projects::ApplicationController < ApplicationController
end
end
- private
-
def apply_diff_view_cookie!
view = params[:view] || cookies[:diff_view]
cookies.permanent[:diff_view] = params[:view] = view if view
diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb
index a6bebc46b06..72921b3aa14 100644
--- a/app/controllers/projects/avatars_controller.rb
+++ b/app/controllers/projects/avatars_controller.rb
@@ -1,7 +1,7 @@
class Projects::AvatarsController < Projects::ApplicationController
include BlobHelper
- before_action :project
+ before_action :authorize_admin_project!, only: [:destroy]
def show
@blob = @repository.blob_at_branch('master', @project.avatar_in_git)
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index 43ea717cbd2..c0a53734921 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -8,7 +8,7 @@ class Projects::BranchesController < Projects::ApplicationController
def index
@sort = params[:sort] || 'name'
@branches = @repository.branches_sorted_by(@sort)
- @branches = Kaminari.paginate_array(@branches).page(params[:page]).per(PER_PAGE)
+ @branches = Kaminari.paginate_array(@branches).page(params[:page])
@max_commits = @branches.reduce(0) do |memo, branch|
diverging_commit_counts = repository.diverging_commit_counts(branch)
diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb
index a1b8632df98..ade01c706a7 100644
--- a/app/controllers/projects/forks_controller.rb
+++ b/app/controllers/projects/forks_controller.rb
@@ -15,7 +15,7 @@ class Projects::ForksController < Projects::ApplicationController
@sort = params[:sort] || 'id_desc'
@forks = @forks.search(params[:filter_projects]) if params[:filter_projects].present?
- @forks = @forks.order_by(@sort).page(params[:page]).per(PER_PAGE)
+ @forks = @forks.order_by(@sort).page(params[:page])
respond_to do |format|
format.html
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index aa7a178dcf4..877b39c9b1b 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -1,11 +1,12 @@
class Projects::IssuesController < Projects::ApplicationController
include ToggleSubscriptionAction
+ include IssuableActions
before_action :module_enabled
before_action :issue, only: [:edit, :update, :show]
# Allow read any issue
- before_action :authorize_read_issue!
+ before_action :authorize_read_issue!, only: [:show]
# Allow write(create) issue
before_action :authorize_create_issue!, only: [:new, :create]
@@ -33,7 +34,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
- @issues = @issues.page(params[:page]).per(PER_PAGE)
+ @issues = @issues.page(params[:page])
@label = @project.labels.find_by(title: params[:label_name])
respond_to do |format|
@@ -90,6 +91,12 @@ class Projects::IssuesController < Projects::ApplicationController
def update
@issue = Issues::UpdateService.new(project, current_user, issue_params).execute(issue)
+ if params[:move_to_project_id].to_i > 0
+ new_project = Project.find(params[:move_to_project_id])
+ move_service = Issues::MoveService.new(project, current_user)
+ @issue = move_service.execute(@issue, new_project)
+ end
+
respond_to do |format|
format.js
format.html do
@@ -127,6 +134,11 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
alias_method :subscribable_resource, :issue
+ alias_method :issuable, :issue
+
+ def authorize_read_issue!
+ return render_404 unless can?(current_user, :read_issue, @issue)
+ end
def authorize_update_issue!
return render_404 unless can?(current_user, :update_issue, @issue)
@@ -158,7 +170,7 @@ class Projects::IssuesController < Projects::ApplicationController
def issue_params
params.require(:issue).permit(
- :title, :assignee_id, :position, :description,
+ :title, :assignee_id, :position, :description, :confidential,
:milestone_id, :state_event, :task_num, label_ids: []
)
end
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index 40d8098690a..ff771ea6d9c 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -11,7 +11,14 @@ class Projects::LabelsController < Projects::ApplicationController
respond_to :js, :html
def index
- @labels = @project.labels.page(params[:page]).per(PER_PAGE)
+ @labels = @project.labels.page(params[:page])
+
+ respond_to do |format|
+ format.html
+ format.json do
+ render json: @project.labels
+ end
+ end
end
def new
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 61b82c9db46..b830d777752 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -1,11 +1,12 @@
class Projects::MergeRequestsController < Projects::ApplicationController
include ToggleSubscriptionAction
include DiffHelper
+ include IssuableActions
before_action :module_enabled
before_action :merge_request, only: [
:edit, :update, :show, :diffs, :commits, :builds, :merge, :merge_check,
- :ci_status, :cancel_merge_when_build_succeeds
+ :ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip
]
before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits, :builds]
before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds]
@@ -20,7 +21,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_action :authorize_create_merge_request!, only: [:new, :create]
# Allow modify merge_request
- before_action :authorize_update_merge_request!, only: [:close, :edit, :update, :sort]
+ before_action :authorize_update_merge_request!, only: [:close, :edit, :update, :remove_wip, :sort]
def index
terms = params['issue_search']
@@ -34,7 +35,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
end
- @merge_requests = @merge_requests.page(params[:page]).per(PER_PAGE)
+ @merge_requests = @merge_requests.page(params[:page])
@merge_requests = @merge_requests.preload(:target_project)
@label = @project.labels.find_by(title: params[:label_name])
@@ -164,6 +165,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
end
+ def remove_wip
+ MergeRequests::UpdateService.new(project, current_user, title: @merge_request.wipless_title).execute(@merge_request)
+
+ redirect_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request),
+ notice: "The merge request can now be merged."
+ end
+
def merge_check
@merge_request.check_if_can_be_merged
@@ -248,6 +256,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request ||= @project.merge_requests.find_by!(iid: params[:id])
end
alias_method :subscribable_resource, :merge_request
+ alias_method :issuable, :merge_request
def closes_issues
@closes_issues ||= @merge_request.closes_issues
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index da46731d945..b2e974eff17 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -19,7 +19,15 @@ class Projects::MilestonesController < Projects::ApplicationController
end
@milestones = @milestones.includes(:project)
- @milestones = @milestones.page(params[:page]).per(PER_PAGE)
+
+ respond_to do |format|
+ format.html do
+ @milestones = @milestones.page(params[:page])
+ end
+ format.json do
+ render json: @milestones
+ end
+ end
end
def new
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index 92b0caa2efb..b578b419a46 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -21,7 +21,7 @@ class Projects::SnippetsController < Projects::ApplicationController
filter: :by_project,
project: @project
})
- @snippets = @snippets.page(params[:page]).per(PER_PAGE)
+ @snippets = @snippets.page(params[:page])
end
def new
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index e580487a2c6..46b242aa5ff 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -7,7 +7,7 @@ class Projects::TagsController < Projects::ApplicationController
def index
sorted = VersionSorter.rsort(@repository.tag_names)
- @tags = Kaminari.paginate_array(sorted).page(params[:page]).per(PER_PAGE)
+ @tags = Kaminari.paginate_array(sorted).page(params[:page])
@releases = project.releases.where(tag: @tags)
end
diff --git a/app/controllers/projects/uploads_controller.rb b/app/controllers/projects/uploads_controller.rb
index e1fe7ea2114..caed064dfbc 100644
--- a/app/controllers/projects/uploads_controller.rb
+++ b/app/controllers/projects/uploads_controller.rb
@@ -1,7 +1,9 @@
class Projects::UploadsController < Projects::ApplicationController
- skip_before_action :authenticate_user!, :reject_blocked!, :project,
+ skip_before_action :reject_blocked!, :project,
:repository, if: -> { action_name == 'show' && image? }
+ before_action :authorize_upload_file!, only: [:create]
+
def create
link_to_file = ::Projects::UploadService.new(project, params[:file]).
execute
@@ -26,6 +28,8 @@ class Projects::UploadsController < Projects::ApplicationController
send_file uploader.file.path, disposition: disposition
end
+ private
+
def uploader
return @uploader if defined?(@uploader)
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index 88fccfed509..02ceb8f4334 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -7,7 +7,7 @@ class Projects::WikisController < Projects::ApplicationController
before_action :load_project_wiki
def pages
- @wiki_pages = Kaminari.paginate_array(@project_wiki.pages).page(params[:page]).per(PER_PAGE)
+ @wiki_pages = Kaminari.paginate_array(@project_wiki.pages).page(params[:page])
end
def show
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 36f37221c58..928817ba811 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -1,7 +1,7 @@
-class ProjectsController < ApplicationController
+class ProjectsController < Projects::ApplicationController
include ExtractsPath
- skip_before_action :authenticate_user!, only: [:show, :activity]
+ before_action :authenticate_user!, except: [:show, :activity]
before_action :project, except: [:new, :create]
before_action :repository, except: [:new, :create]
before_action :assign_ref_vars, :tree, only: [:show], if: :repo_exists?
@@ -134,7 +134,7 @@ class ProjectsController < ApplicationController
def autocomplete_sources
note_type = params['type']
note_id = params['type_id']
- autocomplete = ::Projects::AutocompleteService.new(@project)
+ autocomplete = ::Projects::AutocompleteService.new(@project, current_user)
participants = ::Projects::ParticipantsService.new(@project, current_user).execute(note_type, note_id)
@suggestions = {
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index c72df73af46..2daceed039b 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -25,7 +25,7 @@ class SnippetsController < ApplicationController
filter: :by_user,
user: @user,
scope: params[:scope] }).
- page(params[:page]).per(PER_PAGE)
+ page(params[:page])
render 'index'
else
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index e10c633690f..8e7956da48f 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -100,7 +100,7 @@ class UsersController < ApplicationController
def load_projects
@projects =
PersonalProjectsFinder.new(@user).execute(current_user)
- .page(params[:page]).per(PER_PAGE)
+ .page(params[:page])
end
def load_contributed_projects
@@ -108,7 +108,7 @@ class UsersController < ApplicationController
end
def load_groups
- @groups = @user.groups.order_id_desc
+ @groups = JoinedGroupsFinder.new(@user).execute(current_user)
end
def projects_for_current_user
diff --git a/app/finders/contributed_projects_finder.rb b/app/finders/contributed_projects_finder.rb
index 0209649b017..a685719555c 100644
--- a/app/finders/contributed_projects_finder.rb
+++ b/app/finders/contributed_projects_finder.rb
@@ -1,4 +1,4 @@
-class ContributedProjectsFinder
+class ContributedProjectsFinder < UnionFinder
def initialize(user)
@user = user
end
@@ -11,27 +11,19 @@ class ContributedProjectsFinder
#
# Returns an ActiveRecord::Relation.
def execute(current_user = nil)
- if current_user
- relation = projects_visible_to_user(current_user)
- else
- relation = public_projects
- end
+ segments = all_projects(current_user)
- relation.includes(:namespace).order_id_desc
+ find_union(segments, Project).includes(:namespace).order_id_desc
end
private
- def projects_visible_to_user(current_user)
- authorized = @user.contributed_projects.visible_to_user(current_user)
+ def all_projects(current_user)
+ projects = []
- union = Gitlab::SQL::Union.
- new([authorized.select(:id), public_projects.select(:id)])
+ projects << @user.contributed_projects.visible_to_user(current_user) if current_user
+ projects << @user.contributed_projects.public_to_user(current_user)
- Project.where("projects.id IN (#{union.to_sql})")
- end
-
- def public_projects
- @user.contributed_projects.public_only
+ projects
end
end
diff --git a/app/finders/group_projects_finder.rb b/app/finders/group_projects_finder.rb
new file mode 100644
index 00000000000..3b9a421b118
--- /dev/null
+++ b/app/finders/group_projects_finder.rb
@@ -0,0 +1,42 @@
+class GroupProjectsFinder < UnionFinder
+ def initialize(group, options = {})
+ @group = group
+ @options = options
+ end
+
+ def execute(current_user = nil)
+ segments = group_projects(current_user)
+ find_union(segments, Project)
+ end
+
+ private
+
+ def group_projects(current_user)
+ only_owned = @options.fetch(:only_owned, false)
+ only_shared = @options.fetch(:only_shared, false)
+
+ projects = []
+
+ if current_user
+ if @group.users.include?(current_user)
+ projects << @group.projects unless only_shared
+ projects << @group.shared_projects unless only_owned
+ else
+ unless only_shared
+ projects << @group.projects.visible_to_user(current_user)
+ projects << @group.projects.public_to_user(current_user)
+ end
+
+ unless only_owned
+ projects << @group.shared_projects.visible_to_user(current_user)
+ projects << @group.shared_projects.public_to_user(current_user)
+ end
+ end
+ else
+ projects << @group.projects.public_only unless only_shared
+ projects << @group.shared_projects.public_only unless only_owned
+ end
+
+ projects
+ end
+end
diff --git a/app/finders/groups_finder.rb b/app/finders/groups_finder.rb
new file mode 100644
index 00000000000..4e43f42e9e1
--- /dev/null
+++ b/app/finders/groups_finder.rb
@@ -0,0 +1,18 @@
+class GroupsFinder < UnionFinder
+ def execute(current_user = nil)
+ segments = all_groups(current_user)
+
+ find_union(segments, Group).order_id_desc
+ end
+
+ private
+
+ def all_groups(current_user)
+ groups = []
+
+ groups << current_user.authorized_groups if current_user
+ groups << Group.unscoped.public_to_user(current_user)
+
+ groups
+ end
+end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 19e8c7a92be..046286dd9e1 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -80,9 +80,10 @@ class IssuableFinder
@projects = project
elsif current_user && params[:authorized_only].presence && !current_user_related?
@projects = current_user.authorized_projects.reorder(nil)
+ elsif group
+ @projects = GroupProjectsFinder.new(group).execute(current_user).reorder(nil)
else
- @projects = ProjectsFinder.new.execute(current_user, group: group).
- reorder(nil)
+ @projects = ProjectsFinder.new.execute(current_user).reorder(nil)
end
end
@@ -171,14 +172,12 @@ class IssuableFinder
def by_scope(items)
case params[:scope]
- when 'created-by-me', 'authored' then
+ when 'created-by-me', 'authored'
items.where(author_id: current_user.id)
- when 'all' then
- items
- when 'assigned-to-me' then
+ when 'assigned-to-me'
items.where(assignee_id: current_user.id)
else
- raise 'You must specify default scope'
+ items
end
end
@@ -198,8 +197,7 @@ class IssuableFinder
end
def by_group(items)
- items = items.of_group(group) if group
-
+ # Selection by group is already covered by `by_project` and `projects`
items
end
diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb
index 20a2b0ce8f0..c2befa5a5b3 100644
--- a/app/finders/issues_finder.rb
+++ b/app/finders/issues_finder.rb
@@ -19,4 +19,10 @@ class IssuesFinder < IssuableFinder
def klass
Issue
end
+
+ private
+
+ def init_collection
+ Issue.visible_to_user(current_user)
+ end
end
diff --git a/app/finders/joined_groups_finder.rb b/app/finders/joined_groups_finder.rb
new file mode 100644
index 00000000000..47174980258
--- /dev/null
+++ b/app/finders/joined_groups_finder.rb
@@ -0,0 +1,24 @@
+class JoinedGroupsFinder < UnionFinder
+ def initialize(user)
+ @user = user
+ end
+
+ # Finds the groups of the source user, optionally limited to those visible to
+ # the current user.
+ def execute(current_user = nil)
+ segments = all_groups(current_user)
+
+ find_union(segments, Group).order_id_desc
+ end
+
+ private
+
+ def all_groups(current_user)
+ groups = []
+
+ groups << @user.authorized_groups.visible_to_user(current_user) if current_user
+ groups << @user.authorized_groups.public_to_user(current_user)
+
+ groups
+ end
+end
diff --git a/app/finders/personal_projects_finder.rb b/app/finders/personal_projects_finder.rb
index a61ffa22990..3ad4bd5f066 100644
--- a/app/finders/personal_projects_finder.rb
+++ b/app/finders/personal_projects_finder.rb
@@ -1,4 +1,4 @@
-class PersonalProjectsFinder
+class PersonalProjectsFinder < UnionFinder
def initialize(user)
@user = user
end
@@ -11,31 +11,19 @@ class PersonalProjectsFinder
#
# Returns an ActiveRecord::Relation.
def execute(current_user = nil)
- if current_user
- relation = projects_visible_to_user(current_user)
- else
- relation = public_projects
- end
+ segments = all_projects(current_user)
- relation.includes(:namespace).order_id_desc
+ find_union(segments, Project).includes(:namespace).order_id_desc
end
private
- def projects_visible_to_user(current_user)
- authorized = @user.personal_projects.visible_to_user(current_user)
+ def all_projects(current_user)
+ projects = []
- union = Gitlab::SQL::Union.
- new([authorized.select(:id), public_and_internal_projects.select(:id)])
+ projects << @user.personal_projects.visible_to_user(current_user) if current_user
+ projects << @user.personal_projects.public_to_user(current_user)
- Project.where("projects.id IN (#{union.to_sql})")
- end
-
- def public_projects
- @user.personal_projects.public_only
- end
-
- def public_and_internal_projects
- @user.personal_projects.public_and_internal_only
+ projects
end
end
diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb
index 3a5fc5b5907..2f0a9659d15 100644
--- a/app/finders/projects_finder.rb
+++ b/app/finders/projects_finder.rb
@@ -1,81 +1,18 @@
-class ProjectsFinder
- # Returns all projects, optionally including group projects a user has access
- # to.
- #
- # ## Examples
- #
- # Retrieving all public projects:
- #
- # ProjectsFinder.new.execute
- #
- # Retrieving all public/internal projects and those the given user has access
- # to:
- #
- # ProjectsFinder.new.execute(some_user)
- #
- # Retrieving all public/internal projects as well as the group's projects the
- # user has access to:
- #
- # ProjectsFinder.new.execute(some_user, group: some_group)
- #
- # Returns an ActiveRecord::Relation.
+class ProjectsFinder < UnionFinder
def execute(current_user = nil, options = {})
- group = options[:group]
+ segments = all_projects(current_user)
- if group
- segments = group_projects(current_user, group)
- else
- segments = all_projects(current_user)
- end
-
- if segments.length > 1
- union = Gitlab::SQL::Union.new(segments.map { |s| s.select(:id) })
-
- Project.where("projects.id IN (#{union.to_sql})")
- else
- segments.first
- end
+ find_union(segments, Project)
end
private
- def group_projects(current_user, group)
- return [group.projects.public_only] unless current_user
-
- user_group_projects = [
- group_projects_for_user(current_user, group),
- group.shared_projects.visible_to_user(current_user)
- ]
- if current_user.external?
- user_group_projects << group.projects.public_only
- else
- user_group_projects << group.projects.public_and_internal_only
- end
- end
-
def all_projects(current_user)
- return [public_projects] unless current_user
+ projects = []
- if current_user.external?
- [current_user.authorized_projects, public_projects]
- else
- [current_user.authorized_projects, public_and_internal_projects]
- end
- end
-
- def group_projects_for_user(current_user, group)
- if group.users.include?(current_user)
- group.projects
- else
- group.projects.visible_to_user(current_user)
- end
- end
-
- def public_projects
- Project.unscoped.public_only
- end
+ projects << current_user.authorized_projects if current_user
+ projects << Project.unscoped.public_to_user(current_user)
- def public_and_internal_projects
- Project.unscoped.public_and_internal_only
+ projects
end
end
diff --git a/app/finders/union_finder.rb b/app/finders/union_finder.rb
new file mode 100644
index 00000000000..33cd1a491f3
--- /dev/null
+++ b/app/finders/union_finder.rb
@@ -0,0 +1,11 @@
+class UnionFinder
+ def find_union(segments, klass)
+ if segments.length > 1
+ union = Gitlab::SQL::Union.new(segments.map { |s| s.select(:id) })
+
+ klass.where("#{klass.table_name}.id IN (#{union.to_sql})")
+ else
+ segments.first
+ end
+ end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index d1b1c61b710..e6ceb213532 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -182,7 +182,7 @@ module ApplicationHelper
# Returns an HTML-safe String
def time_ago_with_tooltip(time, placement: 'top', html_class: 'time_ago', skip_js: false)
element = content_tag :time, time.to_s,
- class: "#{html_class} js-timeago js-timeago-pending",
+ class: "#{html_class} js-timeago #{"js-timeago-pending" unless skip_js}",
datetime: time.to_time.getutc.iso8601,
title: time.in_time_zone.to_s(:medium),
data: { toggle: 'tooltip', placement: placement, container: 'body' }
@@ -196,6 +196,22 @@ module ApplicationHelper
element
end
+ def edited_time_ago_with_tooltip(object, placement: 'top', html_class: 'time_ago', include_author: false)
+ return if object.updated_at == object.created_at
+
+ content_tag :small, class: "edited-text" do
+ output = content_tag(:span, "Edited ")
+ output << time_ago_with_tooltip(object.updated_at, placement: placement, html_class: html_class)
+
+ if include_author && object.updated_by && object.updated_by != object.author
+ output << content_tag(:span, " by ")
+ output << link_to_member(object.project, object.updated_by, avatar: false, author_class: nil)
+ end
+
+ output
+ end
+ end
+
def render_markup(file_name, file_content)
if gitlab_markdown?(file_name)
Haml::Helpers.preserve(markdown(file_content))
@@ -285,7 +301,7 @@ module ApplicationHelper
if project.nil?
nil
elsif current_controller?(:issues)
- project.issues.send(entity).count
+ project.issues.visible_to_user(current_user).send(entity).count
elsif current_controller?(:merge_requests)
project.merge_requests.send(entity).count
end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 0f77b3b299a..820d69c230b 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -27,7 +27,7 @@ module BlobHelper
link_opts)
if !on_top_of_branch?(project, ref)
- button_tag "Edit", class: "btn btn-default disabled has_tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' }
+ button_tag "Edit", class: "btn btn-default disabled has-tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' }
elsif can_edit_blob?(blob, project, ref)
link_to "Edit", edit_path, class: 'btn'
elsif can?(current_user, :fork_project, project)
@@ -50,9 +50,9 @@ module BlobHelper
return unless blob
if !on_top_of_branch?(project, ref)
- button_tag label, class: "btn btn-#{btn_class} disabled has_tooltip", title: "You can only #{action} files when you are on a branch", data: { container: 'body' }
+ button_tag label, class: "btn btn-#{btn_class} disabled has-tooltip", title: "You can only #{action} files when you are on a branch", data: { container: 'body' }
elsif blob.lfs_pointer?
- button_tag label, class: "btn btn-#{btn_class} disabled has_tooltip", title: "It is not possible to #{action} files that are stored in LFS using the web interface", data: { container: 'body' }
+ button_tag label, class: "btn btn-#{btn_class} disabled has-tooltip", title: "It is not possible to #{action} files that are stored in LFS using the web interface", data: { container: 'body' }
elsif can_edit_blob?(blob, project, ref)
button_tag label, class: "btn btn-#{btn_class}", 'data-target' => "#modal-#{modal_type}-blob", 'data-toggle' => 'modal'
elsif can?(current_user, :fork_project, project)
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
index d6c05843743..a9047ede8c5 100644
--- a/app/helpers/button_helper.rb
+++ b/app/helpers/button_helper.rb
@@ -23,36 +23,34 @@ module ButtonHelper
end
def http_clone_button(project)
- klass = 'btn js-protocol-switch'
- klass << ' active' if default_clone_protocol == 'http'
- klass << ' has_tooltip' if current_user.try(:require_password?)
+ klass = 'http-selector'
+ klass << ' has-tooltip' if current_user.try(:require_password?)
protocol = gitlab_config.protocol.upcase
- content_tag :button, protocol,
+ content_tag :a, protocol,
class: klass,
+ href: @project.http_url_to_repo,
data: {
- clone: project.http_url_to_repo,
+ html: true,
+ placement: 'right',
container: 'body',
- html: 'true',
title: "Set a password on your account<br>to pull or push via #{protocol}"
- },
- type: :button
+ }
end
def ssh_clone_button(project)
- klass = 'btn js-protocol-switch'
- klass << ' active' if default_clone_protocol == 'ssh'
- klass << ' has_tooltip' if current_user.try(:require_ssh_key?)
+ klass = 'ssh-selector'
+ klass << ' has-tooltip' if current_user.try(:require_ssh_key?)
- content_tag :button, 'SSH',
+ content_tag :a, 'SSH',
class: klass,
+ href: project.ssh_url_to_repo,
data: {
- clone: project.ssh_url_to_repo,
+ html: true,
+ placement: 'right',
container: 'body',
- html: 'true',
title: 'Add an SSH key to your profile<br>to pull or push via SSH.'
- },
- type: :button
+ }
end
end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index f994c9e6170..bde0799f3de 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -182,7 +182,7 @@ module CommitsHelper
end
options = {
- class: "commit-#{options[:source]}-link has_tooltip",
+ class: "commit-#{options[:source]}-link has-tooltip",
data: { 'original-title'.to_sym => sanitize(source_email) }
}
diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb
index 74f326e0b83..ceff1fbb161 100644
--- a/app/helpers/dropdowns_helper.rb
+++ b/app/helpers/dropdowns_helper.rb
@@ -24,7 +24,7 @@ module DropdownsHelper
capture(&block) if block && !options.has_key?(:footer_content)
end
- if block && options.has_key?(:footer_content)
+ if block && options[:footer_content]
output << content_tag(:div, class: "dropdown-footer") do
capture(&block)
end
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index 37a888d9c60..a67a6b208e2 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -194,7 +194,7 @@ module EventsHelper
end
def event_to_atom(xml, event)
- if event.proper?
+ if event.proper?(current_user)
xml.entry do
event_link = event_feed_url(event)
event_title = event_feed_title(event)
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 1d36969cd62..b1f0a765bb9 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -19,6 +19,10 @@ module GroupsHelper
end
end
+ def can_change_group_visibility_level?(group)
+ can?(current_user, :change_visibility_level, group)
+ end
+
def group_icon(group)
if group.is_a?(String)
group = Group.find_by(path: group)
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 2dfeddf7368..81df2094392 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -20,6 +20,23 @@ module IssuablesHelper
base_issuable_scope(issuable).where('iid < ?', issuable.iid).first
end
+ def user_dropdown_label(user_id, default_label)
+ return "Unassigned" if user_id == "0"
+
+ if @project
+ member = @project.team.find_member(user_id)
+ user = member.user if member
+ else
+ user = User.find_by(id: user_id)
+ end
+
+ if user
+ user.name
+ else
+ default_label
+ end
+ end
+
private
def sidebar_gutter_collapsed?
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index ae4ebc0854a..24b90fef4fe 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -57,6 +57,19 @@ module IssuesHelper
options_from_collection_for_select(milestones, 'id', 'title', object.milestone_id)
end
+ def project_options(issuable, current_user, ability: :read_project)
+ projects = current_user.authorized_projects
+ projects = projects.select do |project|
+ current_user.can?(ability, project)
+ end
+
+ no_project = OpenStruct.new(id: 0, name_with_namespace: 'No project')
+ projects.unshift(no_project)
+ projects.delete(issuable.project)
+
+ options_from_collection_for_select(projects, :id, :name_with_namespace)
+ end
+
def status_box_class(item)
if item.respond_to?(:expired?) && item.expired?
'status-box-expired'
@@ -98,6 +111,10 @@ module IssuesHelper
end.sort.to_sentence(last_word_connector: ', or ')
end
+ def confidential_icon(issue)
+ icon('eye-slash') if issue.confidential?
+ end
+
def emoji_icon(name, unicode = nil, aliases = [])
unicode ||= Emoji.emoji_filename(name) rescue ""
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 4455dcd0e20..e0a8552dfa7 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -32,7 +32,7 @@ module LabelsHelper
# link_to_label(label) { "My Custom Label Text" }
#
# Returns a String
- def link_to_label(label, project: nil, type: :issue, &block)
+ def link_to_label(label, project: nil, type: :issue, tooltip: true, &block)
project ||= @project || label.project
link = send("namespace_project_#{type.to_s.pluralize}_path",
project.namespace,
@@ -42,7 +42,7 @@ module LabelsHelper
if block_given?
link_to link, &block
else
- link_to render_colored_label(label), link
+ link_to render_colored_label(label, tooltip: tooltip), link
end
end
@@ -50,23 +50,24 @@ module LabelsHelper
@project.labels.pluck(:title)
end
- def render_colored_label(label, label_suffix = '')
+ def render_colored_label(label, label_suffix = '', tooltip: true)
label_color = label.color || Label::DEFAULT_COLOR
text_color = text_color_for_bg(label_color)
# Intentionally not using content_tag here so that this method can be called
# by LabelReferenceFilter
- span = %(<span class="label color-label") +
- %(style="background-color: #{label_color}; color: #{text_color}">) +
+ span = %(<span class="label color-label #{"has-tooltip" if tooltip}" ) +
+ %(style="background-color: #{label_color}; color: #{text_color}" ) +
+ %(title="#{escape_once(label.description)}" data-container="body">) +
%(#{escape_once(label.name)}#{label_suffix}</span>)
span.html_safe
end
- def render_colored_cross_project_label(label)
+ def render_colored_cross_project_label(label, tooltip: true)
label_suffix = label.project.name_with_namespace
label_suffix = " <i>in #{escape_once(label_suffix)}</i>"
- render_colored_label(label, label_suffix)
+ render_colored_label(label, label_suffix, tooltip: tooltip)
end
def suggested_colors
@@ -109,19 +110,12 @@ module LabelsHelper
end
end
- def projects_labels_options
- labels =
- if @project
- @project.labels
- else
- Label.where(project_id: @projects)
- end
-
- grouped_labels = GlobalLabel.build_collection(labels)
- grouped_labels.unshift(Label::None)
- grouped_labels.unshift(Label::Any)
-
- options_from_collection_for_select(grouped_labels, 'name', 'title', params[:label_name])
+ def labels_filter_path
+ if @project
+ namespace_project_labels_path(@project.namespace, @project, :json)
+ else
+ labels_dashboard_path(:json)
+ end
end
def label_subscription_status(label)
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index e8ac8788d9d..c9d8787bd19 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -38,7 +38,7 @@ module MilestonesHelper
def milestone_progress_bar(milestone)
options = {
class: 'progress-bar progress-bar-success',
- style: "width: #{milestone.percent_complete}%;"
+ style: "width: #{milestone.percent_complete(current_user)}%;"
}
content_tag :div, class: 'progress' do
@@ -46,22 +46,12 @@ module MilestonesHelper
end
end
- def projects_milestones_options
- milestones =
- if @project
- @project.milestones
- else
- Milestone.where(project_id: @projects)
- end.active
-
- epoch = DateTime.parse('1970-01-01')
- grouped_milestones = GlobalMilestone.build_collection(milestones)
- grouped_milestones = grouped_milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date }
- grouped_milestones.unshift(Milestone::None)
- grouped_milestones.unshift(Milestone::Any)
- grouped_milestones.unshift(Milestone::Upcoming)
-
- options_from_collection_for_select(grouped_milestones, 'name', 'title', params[:milestone_title])
+ def milestones_filter_dropdown_path
+ if @project
+ namespace_project_milestones_path(@project.namespace, @project, :json)
+ else
+ milestones_dashboard_path(:json)
+ end
end
def milestone_remaining_days(milestone)
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index b5acb80b720..4e4c6e301d5 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -26,7 +26,7 @@ module ProjectsHelper
image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar]
end
- def link_to_member(project, author, opts = {})
+ def link_to_member(project, author, opts = {}, &block)
default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" }
opts = default_opts.merge(opts)
@@ -44,13 +44,15 @@ module ProjectsHelper
author_html << content_tag(:span, sanitize(author.name), class: opts[:author_class]) if opts[:name]
end
+ author_html << capture(&block) if block
+
author_html = author_html.html_safe
if opts[:name]
link_to(author_html, user_path(author), class: "author_link #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}").html_safe
else
title = opts[:title].sub(":name", sanitize(author.name))
- link_to(author_html, user_path(author), class: "author_link has_tooltip", data: { 'original-title'.to_sym => title, container: 'body' } ).html_safe
+ link_to(author_html, user_path(author), class: "author_link has-tooltip", data: { 'original-title'.to_sym => title, container: 'body' } ).html_safe
end
end
@@ -207,7 +209,7 @@ module ProjectsHelper
def default_clone_protocol
if !current_user || current_user.require_ssh_key?
- "http"
+ gitlab_config.protocol
else
"ssh"
end
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index 07ddc691d85..edc5686cf08 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -16,14 +16,19 @@ module TodosHelper
def todo_target_link(todo)
target = todo.target_type.titleize.downcase
- link_to "#{target} #{todo.target.to_reference}", todo_target_path(todo), { title: h(todo.target.title) }
+ link_to "#{target} #{todo.target_reference}", todo_target_path(todo), { title: todo.target.title }
end
def todo_target_path(todo)
anchor = dom_id(todo.note) if todo.note.present?
- polymorphic_path([todo.project.namespace.becomes(Namespace),
- todo.project, todo.target], anchor: anchor)
+ if todo.for_commit?
+ namespace_project_commit_path(todo.project.namespace.becomes(Namespace), todo.project,
+ todo.target, anchor: anchor)
+ else
+ polymorphic_path([todo.project.namespace.becomes(Namespace),
+ todo.project, todo.target], anchor: anchor)
+ end
end
def todos_filter_params
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
index 71d33b445c2..3a83ae15dd8 100644
--- a/app/helpers/visibility_level_helper.rb
+++ b/app/helpers/visibility_level_helper.rb
@@ -19,6 +19,8 @@ module VisibilityLevelHelper
case form_model
when Project
project_visibility_level_description(level)
+ when Group
+ group_visibility_level_description(level)
when Snippet
snippet_visibility_level_description(level, form_model)
end
@@ -35,6 +37,17 @@ module VisibilityLevelHelper
end
end
+ def group_visibility_level_description(level)
+ case level
+ when Gitlab::VisibilityLevel::PRIVATE
+ "The group and its projects can only be viewed by members."
+ when Gitlab::VisibilityLevel::INTERNAL
+ "The group and any internal projects can be viewed by any logged in user."
+ when Gitlab::VisibilityLevel::PUBLIC
+ "The group and any public projects can be viewed without any authentication."
+ end
+ end
+
def snippet_visibility_level_description(level, snippet = nil)
case level
when Gitlab::VisibilityLevel::PRIVATE
@@ -50,6 +63,23 @@ module VisibilityLevelHelper
end
end
+ def visibility_icon_description(form_model)
+ case form_model
+ when Project
+ project_visibility_icon_description(form_model.visibility_level)
+ when Group
+ group_visibility_icon_description(form_model.visibility_level)
+ end
+ end
+
+ def group_visibility_icon_description(level)
+ "#{visibility_level_label(level)} - #{group_visibility_level_description(level)}"
+ end
+
+ def project_visibility_icon_description(level)
+ "#{visibility_level_label(level)} - #{project_visibility_level_description(level)}"
+ end
+
def visibility_level_label(level)
Project.visibility_levels.key(level)
end
@@ -67,8 +97,11 @@ module VisibilityLevelHelper
current_application_settings.default_snippet_visibility
end
+ def default_group_visibility
+ current_application_settings.default_group_visibility
+ end
+
def skip_level?(form_model, level)
- form_model.is_a?(Project) &&
- !form_model.visibility_level_allowed?(level)
+ form_model.is_a?(Project) && !form_model.visibility_level_allowed?(level)
end
end
diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb
index 5f9adb32e00..6f54c42146c 100644
--- a/app/mailers/emails/issues.rb
+++ b/app/mailers/emails/issues.rb
@@ -36,6 +36,14 @@ module Emails
mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id))
end
+ def issue_moved_email(recipient, issue, new_issue, updated_by_user)
+ setup_issue_mail(issue.id, recipient.id)
+
+ @new_issue = new_issue
+ @new_project = new_issue.project
+ mail_answer_thread(issue, issue_thread_options(updated_by_user.id, recipient.id))
+ end
+
private
def setup_issue_mail(issue_id, recipient_id)
diff --git a/app/models/ability.rb b/app/models/ability.rb
index ccac08b7d3f..fa2345f6faa 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -49,7 +49,6 @@ class Ability
rules = [
:read_project,
:read_wiki,
- :read_issue,
:read_label,
:read_milestone,
:read_project_snippet,
@@ -63,6 +62,9 @@ class Ability
# Allow to read builds by anonymous user if guests are allowed
rules << :read_build if project.public_builds?
+ # Allow to read issues by anonymous user if issue is not confidential
+ rules << :read_issue unless subject.is_a?(Issue) && subject.confidential?
+
rules - project_disabled_features_rules(project)
else
[]
@@ -83,7 +85,7 @@ class Ability
subject.group
end
- if group && group.projects.public_only.any?
+ if group && group.public?
[:read_group]
else
[]
@@ -112,6 +114,13 @@ class Ability
# Push abilities on the users team role
rules.push(*project_team_rules(project.team, user))
+ if project.owner == user ||
+ (project.group && project.group.has_owner?(user)) ||
+ user.admin?
+
+ rules.push(*project_owner_rules)
+ end
+
if project.public? || (project.internal? && !user.external?)
rules.push(*public_project_rules)
@@ -119,14 +128,6 @@ class Ability
rules << :read_build if project.public_builds?
end
- if project.owner == user || user.admin?
- rules.push(*project_admin_rules)
- end
-
- if project.group && project.group.has_owner?(user)
- rules.push(*project_admin_rules)
- end
-
if project.archived?
rules -= project_archived_rules
end
@@ -169,7 +170,8 @@ class Ability
:read_note,
:create_project,
:create_issue,
- :create_note
+ :create_note,
+ :upload_file
]
end
@@ -226,14 +228,16 @@ class Ability
]
end
- def project_admin_rules
- @project_admin_rules ||= project_master_rules + [
+ def project_owner_rules
+ @project_owner_rules ||= project_master_rules + [
:change_namespace,
:change_visibility_level,
:rename_project,
:remove_project,
:archive_project,
- :remove_fork_project
+ :remove_fork_project,
+ :destroy_merge_request,
+ :destroy_issue
]
end
@@ -271,11 +275,9 @@ class Ability
def group_abilities(user, group)
rules = []
- if user.admin? || group.users.include?(user) || ProjectsFinder.new.execute(user, group: group).any?
- rules << :read_group
- end
+ rules << :read_group if can_read_group?(user, group)
- # Only group masters and group owners can create new projects in group
+ # Only group masters and group owners can create new projects
if group.has_master?(user) || group.has_owner?(user) || user.admin?
rules += [
:create_projects,
@@ -288,13 +290,23 @@ class Ability
rules += [
:admin_group,
:admin_namespace,
- :admin_group_member
+ :admin_group_member,
+ :change_visibility_level
]
end
rules.flatten
end
+ def can_read_group?(user, group)
+ return true if user.admin?
+ return true if group.public?
+ return true if group.internal? && !user.external?
+ return true if group.users.include?(user)
+
+ GroupProjectsFinder.new(group).execute(user).any?
+ end
+
def namespace_abilities(user, namespace)
rules = []
@@ -321,6 +333,7 @@ class Ability
end
rules += project_abilities(user, subject.project)
+ rules = filter_confidential_issues_abilities(user, subject, rules) if subject.is_a?(Issue)
rules
end
end
@@ -439,5 +452,17 @@ class Ability
:"admin_#{name}"
]
end
+
+ def filter_confidential_issues_abilities(user, issue, rules)
+ return rules if user.admin? || !issue.confidential?
+
+ unless issue.author == user || issue.assignee == user || issue.project.team.member?(user.id)
+ rules.delete(:admin_issue)
+ rules.delete(:read_issue)
+ rules.delete(:update_issue)
+ end
+
+ rules
+ end
end
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 269056e0e77..c4879598c4e 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -18,6 +18,7 @@
# max_attachment_size :integer default(10), not null
# default_project_visibility :integer
# default_snippet_visibility :integer
+# default_group_visibility :integer
# restricted_signup_domains :text
# user_oauth_applications :boolean default(TRUE)
# after_sign_out_path :string(255)
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 3b1aa0f5c80..3377a85a55a 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -114,7 +114,7 @@ class CommitStatus < ActiveRecord::Base
end
def ignored?
- failed? && allow_failure?
+ allow_failure? && (failed? || canceled?)
end
def duration
diff --git a/app/models/concerns/internal_id.rb b/app/models/concerns/internal_id.rb
index 821ed54fb98..51288094ef1 100644
--- a/app/models/concerns/internal_id.rb
+++ b/app/models/concerns/internal_id.rb
@@ -7,7 +7,10 @@ module InternalId
end
def set_iid
- max_iid = project.send(self.class.name.tableize).maximum(:iid)
+ records = project.send(self.class.name.tableize)
+ records = records.with_deleted if self.paranoid?
+ max_iid = records.maximum(:iid)
+
self.iid = max_iid.to_i + 1
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 86ab84615ba..476e1ce7af0 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -58,6 +58,8 @@ module Issuable
attr_mentionable :description, cache: true
participant :author, :assignee, :notes_with_associations
strip_attributes :title
+
+ acts_as_paranoid
end
module ClassMethods
@@ -209,4 +211,13 @@ module Issuable
Taskable.get_updated_tasks(old_content: previous_changes['description'].first,
new_content: description)
end
+
+ ##
+ # Method that checks if issuable can be moved to another project.
+ #
+ # Should be overridden if issuable can be moved.
+ #
+ def can_move?(*)
+ false
+ end
end
diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb
index d67df7c1d9c..5b8e3f654ea 100644
--- a/app/models/concerns/milestoneish.rb
+++ b/app/models/concerns/milestoneish.rb
@@ -1,18 +1,18 @@
module Milestoneish
- def closed_items_count
- issues.closed.size + merge_requests.closed_and_merged.size
+ def closed_items_count(user = nil)
+ issues_visible_to_user(user).closed.size + merge_requests.closed_and_merged.size
end
- def total_items_count
- issues.size + merge_requests.size
+ def total_items_count(user = nil)
+ issues_visible_to_user(user).size + merge_requests.size
end
- def complete?
- total_items_count == closed_items_count
+ def complete?(user = nil)
+ total_items_count(user) == closed_items_count(user)
end
- def percent_complete
- ((closed_items_count * 100) / total_items_count).abs
+ def percent_complete(user = nil)
+ ((closed_items_count(user) * 100) / total_items_count(user)).abs
rescue ZeroDivisionError
0
end
@@ -22,4 +22,8 @@ module Milestoneish
(due_date - Date.today).to_i
end
+
+ def issues_visible_to_user(user = nil)
+ issues.visible_to_user(user)
+ end
end
diff --git a/app/models/event.rb b/app/models/event.rb
index 9a0bbf50f8b..a5cfeaf388e 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -73,15 +73,17 @@ class Event < ActiveRecord::Base
end
end
- def proper?
+ def proper?(user = nil)
if push?
true
elsif membership_changed?
true
elsif created_project?
true
+ elsif issue?
+ Ability.abilities.allowed?(user, :read_issue, issue)
else
- ((issue? || merge_request? || note?) && target) || milestone?
+ ((merge_request? || note?) && target) || milestone?
end
end
diff --git a/app/models/group.rb b/app/models/group.rb
index 9919ca112dc..b332601c59b 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -2,15 +2,16 @@
#
# Table name: namespaces
#
-# id :integer not null, primary key
-# name :string(255) not null
-# path :string(255) not null
-# owner_id :integer
-# created_at :datetime
-# updated_at :datetime
-# type :string(255)
-# description :string(255) default(""), not null
-# avatar :string(255)
+# id :integer not null, primary key
+# name :string(255) not null
+# path :string(255) not null
+# owner_id :integer
+# visibility_level :integer default(20), not null
+# created_at :datetime
+# updated_at :datetime
+# type :string(255)
+# description :string(255) default(""), not null
+# avatar :string(255)
#
require 'carrierwave/orm/activerecord'
@@ -18,6 +19,7 @@ require 'file_size_validator'
class Group < Namespace
include Gitlab::ConfigHelper
+ include Gitlab::VisibilityLevel
include Referable
has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember'
@@ -27,6 +29,8 @@ class Group < Namespace
has_many :shared_projects, through: :project_group_links, source: :project
validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
+ validate :visibility_level_allowed_by_projects
+
validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
mount_uploader :avatar, AvatarUploader
@@ -74,6 +78,21 @@ class Group < Namespace
name
end
+ def visibility_level_field
+ visibility_level
+ end
+
+ def visibility_level_allowed_by_projects
+ allowed_by_projects = self.projects.where('visibility_level > ?', self.visibility_level).none?
+
+ unless allowed_by_projects
+ level_name = Gitlab::VisibilityLevel.level_name(visibility_level).downcase
+ self.errors.add(:visibility_level, "#{level_name} is not allowed since there are projects with higher visibility.")
+ end
+
+ allowed_by_projects
+ end
+
def avatar_url(size = nil)
if avatar.present?
[gitlab_config.url, avatar.url].join
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 2447f860c5a..f32db59ac9f 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -16,6 +16,7 @@
# state :string(255)
# iid :integer
# updated_by_id :integer
+# moved_to_id :integer
#
require 'carrierwave/orm/activerecord'
@@ -31,10 +32,9 @@ class Issue < ActiveRecord::Base
ActsAsTaggableOn.strict_case_match = true
belongs_to :project
- validates :project, presence: true
+ belongs_to :moved_to, class_name: 'Issue'
- scope :of_group,
- ->(group) { where(project_id: group.projects.select(:id).reorder(nil)) }
+ validates :project, presence: true
scope :cared, ->(user) { where(assignee_id: user) }
scope :open_for, ->(user) { opened.assigned_to(user) }
@@ -58,6 +58,13 @@ class Issue < ActiveRecord::Base
attributes
end
+ def self.visible_to_user(user)
+ return where(confidential: false) if user.blank?
+ return all if user.admin?
+
+ where('issues.confidential = false OR (issues.confidential = true AND (issues.author_id = :user_id OR issues.assignee_id = :user_id OR issues.project_id IN(:project_ids)))', user_id: user.id, project_ids: user.authorized_projects.select(:id))
+ end
+
def self.reference_prefix
'#'
end
@@ -98,9 +105,8 @@ class Issue < ActiveRecord::Base
end
def related_branches
- return [] if self.project.empty_repo?
- self.project.repository.branch_names.select do |branch|
- branch =~ /\A#{iid}-(?!\d+-stable)/i
+ project.repository.branch_names.select do |branch|
+ branch.end_with?("-#{iid}")
end
end
@@ -131,8 +137,20 @@ class Issue < ActiveRecord::Base
end.uniq.select { |mr| mr.open? && mr.closes_issue?(self) }
end
+ def moved?
+ !moved_to.nil?
+ end
+
+ def can_move?(user, to_project = nil)
+ if to_project
+ return false unless user.can?(:admin_issue, to_project)
+ end
+
+ !moved? && user.can?(:admin_issue, self.project)
+ end
+
def to_branch_name
- "#{iid}-#{title.parameterize}"
+ "#{title.parameterize}-#{iid}"
end
def can_be_worked_on?(current_user)
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 188325045e2..ef48207f956 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -131,7 +131,6 @@ class MergeRequest < ActiveRecord::Base
validate :validate_branches
validate :validate_fork
- scope :of_group, ->(group) { where("source_project_id in (:group_project_ids) OR target_project_id in (:group_project_ids)", group_project_ids: group.projects.select(:id).reorder(nil)) }
scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) }
scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
@@ -277,8 +276,14 @@ class MergeRequest < ActiveRecord::Base
self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last
end
+ WIP_REGEX = /\A\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i.freeze
+
def work_in_progress?
- !!(title =~ /\A\[?WIP(\]|:| )/i)
+ title =~ WIP_REGEX
+ end
+
+ def wipless_title
+ self.title.sub(WIP_REGEX, "")
end
def mergeable?
@@ -516,11 +521,15 @@ class MergeRequest < ActiveRecord::Base
end
def target_sha
- @target_sha ||= target_project.repository.commit(target_branch).sha
+ @target_sha ||= target_project.repository.commit(target_branch).try(:sha)
end
def source_sha
- last_commit.try(:sha)
+ last_commit.try(:sha) || source_tip.try(:sha)
+ end
+
+ def source_tip
+ source_branch && source_project.repository.commit(source_branch)
end
def fetch_ref
@@ -568,8 +577,11 @@ class MergeRequest < ActiveRecord::Base
end
def compute_diverged_commits_count
+ return 0 unless source_sha && target_sha
+
Gitlab::Git::Commit.between(target_project.repository.raw_repository, source_sha, target_sha).size
end
+ private :compute_diverged_commits_count
def diverged_from_target_branch?
diverged_commits_count > 0
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 374590ba0c5..de7183bf6b4 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -121,8 +121,8 @@ class Milestone < ActiveRecord::Base
active? && issues.opened.count.zero?
end
- def is_empty?
- total_items_count.zero?
+ def is_empty?(user = nil)
+ total_items_count(user).zero?
end
def author_id
diff --git a/app/models/project.rb b/app/models/project.rb
index ab4913e99a8..9c8246e8ac0 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -73,7 +73,7 @@ class Project < ActiveRecord::Base
update_column(:last_activity_at, self.created_at)
end
- # update visibility_levet of forks
+ # update visibility_level of forks
after_update :update_forks_visibility_level
def update_forks_visibility_level
return unless visibility_level < visibility_level_was
@@ -197,6 +197,8 @@ class Project < ActiveRecord::Base
validate :avatar_type,
if: ->(project) { project.avatar.present? && project.avatar_changed? }
validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
+ validate :visibility_level_allowed_by_group
+ validate :visibility_level_allowed_as_fork
add_authentication_token_field :runners_token
before_save :ensure_runners_token
@@ -215,8 +217,6 @@ class Project < ActiveRecord::Base
scope :in_group_namespace, -> { joins(:group) }
scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
scope :joined, ->(user) { where('namespace_id != ?', user.namespace_id) }
- scope :public_only, -> { where(visibility_level: Project::PUBLIC) }
- scope :public_and_internal_only, -> { where(visibility_level: Project.public_and_internal_levels) }
scope :non_archived, -> { where(archived: false) }
scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct }
@@ -246,10 +246,6 @@ class Project < ActiveRecord::Base
end
class << self
- def public_and_internal_levels
- [Project::PUBLIC, Project::INTERNAL]
- end
-
def abandoned
where('projects.last_activity_at < ?', 6.months.ago)
end
@@ -435,6 +431,7 @@ class Project < ActiveRecord::Base
def safe_import_url
result = URI.parse(self.import_url)
result.password = '*****' unless result.password.nil?
+ result.user = '*****' unless result.user.nil? || result.user == "git" #tokens or other data may be saved as user
result.to_s
rescue
self.import_url
@@ -442,10 +439,25 @@ class Project < ActiveRecord::Base
def check_limit
unless creator.can_create_project? or namespace.kind == 'group'
- errors[:limit_reached] << ("Your project limit is #{creator.projects_limit} projects! Please contact your administrator to increase it")
+ self.errors.add(:limit_reached, "Your project limit is #{creator.projects_limit} projects! Please contact your administrator to increase it")
end
rescue
- errors[:base] << ("Can't check your ability to create project")
+ self.errors.add(:base, "Can't check your ability to create project")
+ end
+
+ def visibility_level_allowed_by_group
+ return if visibility_level_allowed_by_group?
+
+ level_name = Gitlab::VisibilityLevel.level_name(self.visibility_level).downcase
+ group_level_name = Gitlab::VisibilityLevel.level_name(self.group.visibility_level).downcase
+ self.errors.add(:visibility_level, "#{level_name} is not allowed in a #{group_level_name} group.")
+ end
+
+ def visibility_level_allowed_as_fork
+ return if visibility_level_allowed_as_fork?
+
+ level_name = Gitlab::VisibilityLevel.level_name(self.visibility_level).downcase
+ self.errors.add(:visibility_level, "#{level_name} is not allowed since the fork source project has lower visibility.")
end
def to_param
@@ -571,10 +583,7 @@ class Project < ActiveRecord::Base
end
def avatar_in_git
- @avatar_file ||= 'logo.png' if repository.blob_at_branch('master', 'logo.png')
- @avatar_file ||= 'logo.jpg' if repository.blob_at_branch('master', 'logo.jpg')
- @avatar_file ||= 'logo.gif' if repository.blob_at_branch('master', 'logo.gif')
- @avatar_file
+ repository.avatar
end
def avatar_url
@@ -879,6 +888,7 @@ class Project < ActiveRecord::Base
# Forked import is handled asynchronously
unless forked?
if gitlab_shell.add_repository(path_with_namespace)
+ repository.after_create
true
else
errors.add(:base, 'Failed to create repository via gitlab-shell')
@@ -963,9 +973,25 @@ class Project < ActiveRecord::Base
issues.opened.count
end
- def visibility_level_allowed?(level)
+ def visibility_level_allowed_as_fork?(level = self.visibility_level)
return true unless forked?
- Gitlab::VisibilityLevel.allowed_fork_levels(forked_from_project.visibility_level).include?(level.to_i)
+
+ # 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
+ return true unless original_project
+
+ level <= original_project.visibility_level
+ end
+
+ def visibility_level_allowed_by_group?(level = self.visibility_level)
+ return true unless group
+
+ level <= group.visibility_level
+ end
+
+ def visibility_level_allowed?(level = self.visibility_level)
+ visibility_level_allowed_as_fork?(level) && visibility_level_allowed_by_group?(level)
end
def runners_token
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index 59b1b86d1fb..7c1a61bb0bf 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -123,23 +123,27 @@ class ProjectWiki
end
def repository
- Repository.new(path_with_namespace, @project)
+ @repository ||= Repository.new(path_with_namespace, @project)
end
def default_branch
wiki.class.default_ref
end
- private
-
def create_repo!
if init_repo(path_with_namespace)
- Gollum::Wiki.new(path_to_repo)
+ wiki = Gollum::Wiki.new(path_to_repo)
else
raise CouldNotCreateWikiError
end
+
+ repository.after_create
+
+ wiki
end
+ private
+
def init_repo(path_with_namespace)
gitlab_shell.add_repository(path_with_namespace)
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index e555e97689d..13154eb4205 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -3,6 +3,10 @@ require 'securerandom'
class Repository
class CommitError < StandardError; end
+ # Files to use as a project avatar in case no avatar was uploaded via the web
+ # UI.
+ AVATAR_FILES = %w{logo.png logo.jpg logo.gif}
+
include Gitlab::ShellAdapter
attr_accessor :path_with_namespace, :project
@@ -38,12 +42,15 @@ class Repository
end
def exists?
- return false unless raw_repository
+ return @exists unless @exists.nil?
- raw_repository.rugged
- true
- rescue Gitlab::Git::Repository::NoRepository
- false
+ @exists = cache.fetch(:exists?) do
+ begin
+ raw_repository && raw_repository.rugged ? true : false
+ rescue Gitlab::Git::Repository::NoRepository
+ false
+ end
+ end
end
def empty?
@@ -223,12 +230,6 @@ class Repository
send(key)
end
end
-
- branches.each do |branch|
- unless cache.exist?(:"diverging_commit_counts_#{branch.name}")
- send(:diverging_commit_counts, branch)
- end
- end
end
def expire_tags_cache
@@ -241,12 +242,13 @@ class Repository
@branches = nil
end
- def expire_cache(branch_name = nil)
+ def expire_cache(branch_name = nil, revision = nil)
cache_keys.each do |key|
cache.expire(key)
end
expire_branch_cache(branch_name)
+ expire_avatar_cache(branch_name, revision)
# This ensures this particular cache is flushed after the first commit to a
# new repository.
@@ -296,18 +298,6 @@ class Repository
@tag_count = nil
end
- def rebuild_cache
- cache_keys.each do |key|
- cache.expire(key)
- send(key)
- end
-
- branches.each do |branch|
- cache.expire(:"diverging_commit_counts_#{branch.name}")
- diverging_commit_counts(branch)
- end
- end
-
def lookup_cache
@lookup_cache ||= {}
end
@@ -316,12 +306,40 @@ class Repository
cache.expire(:branch_names)
end
+ def expire_avatar_cache(branch_name = nil, revision = nil)
+ # Avatars are pulled from the default branch, thus if somebody pushes to a
+ # different branch there's no need to expire anything.
+ return if branch_name && branch_name != root_ref
+
+ # We don't want to flush the cache if the commit didn't actually make any
+ # changes to any of the possible avatar files.
+ if revision && commit = self.commit(revision)
+ return unless commit.diffs.
+ any? { |diff| AVATAR_FILES.include?(diff.new_path) }
+ end
+
+ cache.expire(:avatar)
+
+ @avatar = nil
+ end
+
+ def expire_exists_cache
+ cache.expire(:exists?)
+ @exists = nil
+ end
+
+ # Runs code after a repository has been created.
+ def after_create
+ expire_exists_cache
+ end
+
# Runs code just before a repository is deleted.
def before_delete
expire_cache if exists?
expire_root_ref_cache
expire_emptiness_caches
+ expire_exists_cache
end
# Runs code just before the HEAD of a repository is changed.
@@ -347,11 +365,12 @@ class Repository
# Runs code after a repository has been forked/imported.
def after_import
expire_emptiness_caches
+ expire_exists_cache
end
# Runs code after a new commit has been pushed.
- def after_push_commit(branch_name)
- expire_cache(branch_name)
+ def after_push_commit(branch_name, revision)
+ expire_cache(branch_name, revision)
end
# Runs code after a new branch has been created.
@@ -857,6 +876,14 @@ class Repository
end
end
+ def avatar
+ @avatar ||= cache.fetch(:avatar) do
+ AVATAR_FILES.find do |file|
+ blob_at_branch('master', file)
+ end
+ end
+ end
+
private
def cache
diff --git a/app/models/todo.rb b/app/models/todo.rb
index 5f91991f781..d85f7bfdf57 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -5,14 +5,15 @@
# id :integer not null, primary key
# user_id :integer not null
# project_id :integer not null
-# target_id :integer not null
+# target_id :integer
# target_type :string not null
# author_id :integer
-# note_id :integer
# action :integer not null
# state :string not null
# created_at :datetime
# updated_at :datetime
+# note_id :integer
+# commit_id :string
#
class Todo < ActiveRecord::Base
@@ -27,7 +28,9 @@ class Todo < ActiveRecord::Base
delegate :name, :email, to: :author, prefix: true, allow_nil: true
- validates :action, :project, :target, :user, presence: true
+ validates :action, :project, :target_type, :user, presence: true
+ validates :target_id, presence: true, unless: :for_commit?
+ validates :commit_id, presence: true, if: :for_commit?
default_scope { reorder(id: :desc) }
@@ -36,7 +39,7 @@ class Todo < ActiveRecord::Base
state_machine :state, initial: :pending do
event :done do
- transition [:pending, :done] => :done
+ transition [:pending] => :done
end
state :pending
@@ -50,4 +53,25 @@ class Todo < ActiveRecord::Base
target.title
end
end
+
+ def for_commit?
+ target_type == "Commit"
+ end
+
+ # override to return commits, which are not active record
+ def target
+ if for_commit?
+ project.commit(commit_id) rescue nil
+ else
+ super
+ end
+ end
+
+ def target_reference
+ if for_commit?
+ target.short_id
+ else
+ target.to_reference
+ end
+ end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index c011af03591..9c315cfe966 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -435,7 +435,7 @@ class User < ActiveRecord::Base
Group.where("namespaces.id IN (#{union.to_sql})")
end
- # Returns the groups a user is authorized to access.
+ # Returns projects user is authorized to access.
def authorized_projects
Project.where("projects.id IN (#{projects_union.to_sql})")
end
diff --git a/app/services/base_service.rb b/app/services/base_service.rb
index 8563633816c..0d55ba5a981 100644
--- a/app/services/base_service.rb
+++ b/app/services/base_service.rb
@@ -43,12 +43,9 @@ class BaseService
def deny_visibility_level(model, denied_visibility_level = nil)
denied_visibility_level ||= model.visibility_level
- level_name = Gitlab::VisibilityLevel.level_name(denied_visibility_level)
+ level_name = Gitlab::VisibilityLevel.level_name(denied_visibility_level).downcase
- model.errors.add(
- :visibility_level,
- "#{level_name} visibility has been restricted by your GitLab administrator"
- )
+ model.errors.add(:visibility_level, "#{level_name} has been restricted by your GitLab administrator")
end
private
diff --git a/app/services/ci/create_builds_service.rb b/app/services/ci/create_builds_service.rb
index 002f7ba1278..2cd51a7610f 100644
--- a/app/services/ci/create_builds_service.rb
+++ b/app/services/ci/create_builds_service.rb
@@ -1,7 +1,7 @@
module Ci
class CreateBuildsService
def execute(commit, stage, ref, tag, user, trigger_request, status)
- builds_attrs = commit.config_processor.builds_for_stage_and_ref(stage, ref, tag)
+ builds_attrs = commit.config_processor.builds_for_stage_and_ref(stage, ref, tag, trigger_request)
# check when to create next build
builds_attrs = builds_attrs.select do |build_attrs|
diff --git a/app/services/commits/revert_service.rb b/app/services/commits/revert_service.rb
index 9cb918d7a2e..a3c950ede1f 100644
--- a/app/services/commits/revert_service.rb
+++ b/app/services/commits/revert_service.rb
@@ -9,7 +9,8 @@ module Commits
@commit = params[:commit]
@create_merge_request = params[:create_merge_request].present?
- validate and commit
+ check_push_permissions unless @create_merge_request
+ commit
rescue Repository::CommitError, Gitlab::Git::Repository::InvalidBlobName, GitHooksService::PreReceiveError,
ValidationError, ReversionError => ex
error(ex.message)
@@ -45,11 +46,11 @@ module Commits
end
end
- def validate
+ def check_push_permissions
allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(@target_branch)
unless allowed
- raise_error('You are not allowed to push into this branch')
+ raise ValidationError.new('You are not allowed to push into this branch')
end
true
diff --git a/app/services/create_snippet_service.rb b/app/services/create_snippet_service.rb
index 101a3df5eee..9884cb96661 100644
--- a/app/services/create_snippet_service.rb
+++ b/app/services/create_snippet_service.rb
@@ -6,8 +6,7 @@ class CreateSnippetService < BaseService
snippet = project.snippets.build(params)
end
- unless Gitlab::VisibilityLevel.allowed_for?(current_user,
- params[:visibility_level])
+ unless Gitlab::VisibilityLevel.allowed_for?(current_user, params[:visibility_level])
deny_visibility_level(snippet)
return snippet
end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index d840ab5e340..c007d648dd6 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -17,7 +17,7 @@ class GitPushService < BaseService
# 6. Checks if the project's main language has changed
#
def execute
- @project.repository.after_push_commit(branch_name)
+ @project.repository.after_push_commit(branch_name, params[:newrev])
if push_remove_branch?
@project.repository.after_remove_branch
@@ -120,7 +120,7 @@ class GitPushService < BaseService
closed_issues = commit.closes_issues(current_user)
closed_issues.each do |issue|
if can?(current_user, :update_issue, issue)
- Issues::CloseService.new(project, authors[commit], {}).execute(issue, commit)
+ Issues::CloseService.new(project, authors[commit], {}).execute(issue, commit: commit)
end
end
end
diff --git a/app/services/groups/base_service.rb b/app/services/groups/base_service.rb
new file mode 100644
index 00000000000..a8fa098246a
--- /dev/null
+++ b/app/services/groups/base_service.rb
@@ -0,0 +1,9 @@
+module Groups
+ class BaseService < ::BaseService
+ attr_accessor :group, :current_user, :params
+
+ def initialize(group, user, params = {})
+ @group, @current_user, @params = group, user, params.dup
+ end
+ end
+end
diff --git a/app/services/groups/create_service.rb b/app/services/groups/create_service.rb
new file mode 100644
index 00000000000..2bccd584dde
--- /dev/null
+++ b/app/services/groups/create_service.rb
@@ -0,0 +1,21 @@
+module Groups
+ class CreateService < Groups::BaseService
+ def initialize(user, params = {})
+ @current_user, @params = user, params.dup
+ end
+
+ def execute
+ @group = Group.new(params)
+
+ unless Gitlab::VisibilityLevel.allowed_for?(current_user, params[:visibility_level])
+ deny_visibility_level(@group)
+ return @group
+ end
+
+ @group.name ||= @group.path.dup
+ @group.save
+ @group.add_owner(current_user)
+ @group
+ end
+ end
+end
diff --git a/app/services/groups/update_service.rb b/app/services/groups/update_service.rb
new file mode 100644
index 00000000000..99ad12b1003
--- /dev/null
+++ b/app/services/groups/update_service.rb
@@ -0,0 +1,20 @@
+module Groups
+ class UpdateService < Groups::BaseService
+ def execute
+ # check that user is allowed to set specified visibility_level
+ new_visibility = params[:visibility_level]
+ if new_visibility && new_visibility.to_i != group.visibility_level
+ unless can?(current_user, :change_visibility_level, group) &&
+ Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
+
+ deny_visibility_level(group, new_visibility)
+ return group
+ end
+ end
+
+ group.assign_attributes(params)
+
+ group.save
+ end
+ end
+end
diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb
index 78254b49af3..859c934ea3b 100644
--- a/app/services/issues/close_service.rb
+++ b/app/services/issues/close_service.rb
@@ -1,6 +1,6 @@
module Issues
class CloseService < Issues::BaseService
- def execute(issue, commit = nil)
+ def execute(issue, commit: nil, notifications: true, system_note: true)
if project.jira_tracker? && project.jira_service.active
project.jira_service.execute(commit, issue)
todo_service.close_issue(issue, current_user)
@@ -9,8 +9,8 @@ module Issues
if project.default_issues_tracker? && issue.close
event_service.close_issue(issue, current_user)
- create_note(issue, commit)
- notification_service.close_issue(issue, current_user)
+ create_note(issue, commit) if system_note
+ notification_service.close_issue(issue, current_user) if notifications
todo_service.close_issue(issue, current_user)
execute_hooks(issue, 'close')
end
diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb
index 10787e8873c..e63e1af8766 100644
--- a/app/services/issues/create_service.rb
+++ b/app/services/issues/create_service.rb
@@ -4,7 +4,7 @@ module Issues
filter_params
label_params = params[:label_ids]
issue = project.issues.new(params.except(:label_ids))
- issue.author = current_user
+ issue.author = params[:author] || current_user
if issue.save
issue.update_attributes(label_ids: label_params)
diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb
new file mode 100644
index 00000000000..3cfbafe1576
--- /dev/null
+++ b/app/services/issues/move_service.rb
@@ -0,0 +1,94 @@
+module Issues
+ class MoveService < Issues::BaseService
+ class MoveError < StandardError; end
+
+ def execute(issue, new_project)
+ @old_issue = issue
+ @old_project = @project
+ @new_project = new_project
+
+ unless issue.can_move?(current_user, new_project)
+ raise MoveError, 'Cannot move issue due to insufficient permissions!'
+ end
+
+ if @project == new_project
+ raise MoveError, 'Cannot move issue to project it originates from!'
+ end
+
+ # Using transaction because of a high resources footprint
+ # on rewriting notes (unfolding references)
+ #
+ ActiveRecord::Base.transaction do
+ # New issue tasks
+ #
+ @new_issue = create_new_issue
+
+ rewrite_notes
+ add_note_moved_from
+
+ # Old issue tasks
+ #
+ add_note_moved_to
+ close_issue
+ mark_as_moved
+ end
+
+ notify_participants
+
+ @new_issue
+ end
+
+ private
+
+ def create_new_issue
+ new_params = { id: nil, iid: nil, label_ids: [], milestone: nil,
+ project: @new_project, author: @old_issue.author,
+ description: unfold_references(@old_issue.description) }
+
+ new_params = @old_issue.serializable_hash.merge(new_params)
+ CreateService.new(@new_project, @current_user, new_params).execute
+ end
+
+ def rewrite_notes
+ @old_issue.notes.find_each do |note|
+ new_note = note.dup
+ new_params = { project: @new_project, noteable: @new_issue,
+ note: unfold_references(new_note.note),
+ created_at: note.created_at }
+
+ new_note.update(new_params)
+ end
+ end
+
+ def close_issue
+ close_service = CloseService.new(@old_project, @current_user)
+ close_service.execute(@old_issue, notifications: false, system_note: false)
+ end
+
+ def add_note_moved_from
+ SystemNoteService.noteable_moved(@new_issue, @new_project,
+ @old_issue, @current_user,
+ direction: :from)
+ end
+
+ def add_note_moved_to
+ SystemNoteService.noteable_moved(@old_issue, @old_project,
+ @new_issue, @current_user,
+ direction: :to)
+ end
+
+ def unfold_references(content)
+ rewriter = Gitlab::Gfm::ReferenceRewriter.new(content, @old_project,
+ @current_user)
+ rewriter.rewrite(@new_project)
+ end
+
+ def notify_participants
+ notification_service.issue_moved(@old_issue, @new_issue, @current_user)
+ end
+
+ def mark_as_moved
+ @old_issue.update(moved_to: @new_issue)
+ end
+ end
+end
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index 7b306a8a531..ac5b58db862 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -5,6 +5,19 @@ module MergeRequests
SystemNoteService.change_status(merge_request, merge_request.target_project, current_user, merge_request.state, nil)
end
+ def create_title_change_note(issuable, old_title)
+ removed_wip = old_title =~ MergeRequest::WIP_REGEX && !issuable.work_in_progress?
+ added_wip = old_title !~ MergeRequest::WIP_REGEX && issuable.work_in_progress?
+
+ if removed_wip
+ SystemNoteService.remove_merge_request_wip(issuable, issuable.project, current_user)
+ elsif added_wip
+ SystemNoteService.add_merge_request_wip(issuable, issuable.project, current_user)
+ else
+ super
+ end
+ end
+
def hook_data(merge_request, action)
hook_data = merge_request.to_hook_data(current_user)
merge_request_url = Gitlab::UrlBuilder.new(:merge_request).build(merge_request.id)
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index fa34753c4fd..6e9152e444e 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -51,7 +51,7 @@ module MergeRequests
# be interpreted as the use wants to close that issue on this project
# Pattern example: 112-fix-mep-mep
# Will lead to appending `Closes #112` to the description
- if match = merge_request.source_branch.match(/\A(\d+)-/)
+ if match = merge_request.source_branch.match(/-(\d+)\z/)
iid = match[1]
closes_issue = "Closes ##{iid}"
diff --git a/app/services/merge_requests/post_merge_service.rb b/app/services/merge_requests/post_merge_service.rb
index ebb67c7db65..064910f81f7 100644
--- a/app/services/merge_requests/post_merge_service.rb
+++ b/app/services/merge_requests/post_merge_service.rb
@@ -22,7 +22,7 @@ module MergeRequests
closed_issues = merge_request.closes_issues(current_user)
closed_issues.each do |issue|
if can?(current_user, :update_issue, issue)
- Issues::CloseService.new(project, current_user, {}).execute(issue, merge_request)
+ Issues::CloseService.new(project, current_user, {}).execute(issue, commit: merge_request)
end
end
end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 19a6779dea9..3bdf00a8291 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -236,6 +236,16 @@ class NotificationService
end
end
+ def issue_moved(issue, new_issue, current_user)
+ recipients = build_recipients(issue, issue.project, current_user)
+
+ recipients.map do |recipient|
+ email = mailer.issue_moved_email(recipient, issue, new_issue, current_user)
+ email.deliver_later
+ email
+ end
+ end
+
protected
# Get project users with WATCH notification level
diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb
index 7408e09ed1e..ba50305dbd5 100644
--- a/app/services/projects/autocomplete_service.rb
+++ b/app/services/projects/autocomplete_service.rb
@@ -1,11 +1,7 @@
module Projects
class AutocompleteService < BaseService
- def initialize(project)
- @project = project
- end
-
def issues
- @project.issues.opened.select([:iid, :title])
+ @project.issues.visible_to_user(current_user).opened.select([:iid, :title])
end
def merge_requests
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index a6820183bee..501e58c1407 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -9,10 +9,8 @@ module Projects
@project = Project.new(params)
- # Make sure that the user is allowed to use the specified visibility
- # level
- unless Gitlab::VisibilityLevel.allowed_for?(current_user,
- params[:visibility_level])
+ # Make sure that the user is allowed to use the specified visibility level
+ unless Gitlab::VisibilityLevel.allowed_for?(current_user, params[:visibility_level])
deny_visibility_level(@project)
return @project
end
@@ -55,9 +53,7 @@ module Projects
@project.save
if @project.persisted? && !@project.import?
- unless @project.create_repository
- raise 'Failed to create repository'
- end
+ raise 'Failed to create repository' unless @project.create_repository
end
end
diff --git a/app/services/projects/housekeeping_service.rb b/app/services/projects/housekeeping_service.rb
index bccd67d3dbf..a0973c5d260 100644
--- a/app/services/projects/housekeeping_service.rb
+++ b/app/services/projects/housekeeping_service.rb
@@ -24,7 +24,7 @@ module Projects
def execute
raise LeaseTaken if !try_obtain_lease
- GitlabShellWorker.perform_async(:gc, @project.path_with_namespace)
+ GitlabShellOneShotWorker.perform_async(:gc, @project.path_with_namespace)
ensure
@project.update_column(:pushes_since_gc, 0)
end
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index 895e089bea3..941df08995c 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -3,16 +3,13 @@ module Projects
def execute
# check that user is allowed to set specified visibility_level
new_visibility = params[:visibility_level]
- if new_visibility
- if new_visibility.to_i != project.visibility_level
- unless can?(current_user, :change_visibility_level, project) &&
- Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
- deny_visibility_level(project, new_visibility)
- return project
- end
+ if new_visibility && new_visibility.to_i != project.visibility_level
+ unless can?(current_user, :change_visibility_level, project) &&
+ Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
+
+ deny_visibility_level(project, new_visibility)
+ return project
end
-
- return false unless visibility_level_allowed?(new_visibility)
end
new_branch = params[:default_branch]
@@ -27,19 +24,5 @@ module Projects
end
end
end
-
- private
-
- def visibility_level_allowed?(level)
- return true if project.visibility_level_allowed?(level)
-
- level_name = Gitlab::VisibilityLevel.level_name(level)
- project.errors.add(
- :visibility_level,
- "#{level_name} could not be set as visibility level of this project - parent project settings are more restrictive"
- )
-
- false
- end
end
end
diff --git a/app/services/search/global_service.rb b/app/services/search/global_service.rb
index e1e94c5cc38..aa9837038a6 100644
--- a/app/services/search/global_service.rb
+++ b/app/services/search/global_service.rb
@@ -11,7 +11,7 @@ module Search
projects = ProjectsFinder.new.execute(current_user)
projects = projects.in_namespace(group.id) if group
- Gitlab::SearchResults.new(projects, params[:search])
+ Gitlab::SearchResults.new(current_user, projects, params[:search])
end
end
end
diff --git a/app/services/search/project_service.rb b/app/services/search/project_service.rb
index c08881dce4b..4b500914cfb 100644
--- a/app/services/search/project_service.rb
+++ b/app/services/search/project_service.rb
@@ -7,7 +7,8 @@ module Search
end
def execute
- Gitlab::ProjectSearchResults.new(project,
+ Gitlab::ProjectSearchResults.new(current_user,
+ project,
params[:search],
params[:repository_ref])
end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index f09b77c4a57..e022a046c48 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -144,6 +144,18 @@ class SystemNoteService
create_note(noteable: noteable, project: project, author: author, note: body)
end
+ def self.remove_merge_request_wip(noteable, project, author)
+ body = 'Unmarked this merge request as a Work In Progress'
+
+ create_note(noteable: noteable, project: project, author: author, note: body)
+ end
+
+ def self.add_merge_request_wip(noteable, project, author)
+ body = 'Marked this merge request as a **Work In Progress**'
+
+ create_note(noteable: noteable, project: project, author: author, note: body)
+ end
+
# Called when the title of a Noteable is changed
#
# noteable - Noteable object that responds to `title`
@@ -210,7 +222,7 @@ class SystemNoteService
# Called when a branch is created from the 'new branch' button on a issue
# Example note text:
#
- # "Started branch `201-issue-branch-button`"
+ # "Started branch `issue-branch-button-201`"
def self.new_issue_branch(issue, project, author, branch)
h = Gitlab::Application.routes.url_helpers
link = h.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch)
@@ -399,4 +411,26 @@ class SystemNoteService
body = "Marked the task **#{new_task.source}** as #{status_label}"
create_note(noteable: noteable, project: project, author: author, note: body)
end
+
+ # Called when noteable has been moved to another project
+ #
+ # direction - symbol, :to or :from
+ # noteable - Noteable object
+ # noteable_ref - Referenced noteable
+ # author - User performing the move
+ #
+ # Example Note text:
+ #
+ # "Moved to some_namespace/project_new#11"
+ #
+ # Returns the created Note object
+ def self.noteable_moved(noteable, project, noteable_ref, author, direction:)
+ unless [:to, :from].include?(direction)
+ raise ArgumentError, "Invalid direction `#{direction}`"
+ end
+
+ cross_reference = noteable_ref.to_reference(project)
+ body = "Moved #{direction} #{cross_reference}"
+ create_note(noteable: noteable, project: project, author: author, note: body)
+ end
end
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index 4392e2d17fe..f2662922e90 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -103,24 +103,16 @@ class TodoService
# * mark all pending todos related to the target for the current user as done
#
def mark_pending_todos_as_done(target, user)
- pending_todos(user, target.project, target).update_all(state: :done)
+ attributes = attributes_for_target(target)
+ pending_todos(user, attributes).update_all(state: :done)
end
private
- def create_todos(project, target, author, users, action, note = nil)
+ def create_todos(users, attributes)
Array(users).each do |user|
- next if pending_todos(user, project, target).exists?
-
- Todo.create(
- project: project,
- user_id: user.id,
- author_id: author.id,
- target_id: target.id,
- target_type: target.class.name,
- action: action,
- note: note
- )
+ next if pending_todos(user, attributes).exists?
+ Todo.create(attributes.merge(user_id: user.id))
end
end
@@ -130,8 +122,8 @@ class TodoService
end
def handle_note(note, author)
- # Skip system notes, notes on commit, and notes on project snippet
- return if note.system? || ['Commit', 'Snippet'].include?(note.noteable_type)
+ # Skip system notes, and notes on project snippet
+ return if note.system? || note.for_project_snippet?
project = note.project
target = note.noteable
@@ -142,13 +134,39 @@ class TodoService
def create_assignment_todo(issuable, author)
if issuable.assignee && issuable.assignee != author
- create_todos(issuable.project, issuable, author, issuable.assignee, Todo::ASSIGNED)
+ attributes = attributes_for_todo(issuable.project, issuable, author, Todo::ASSIGNED)
+ create_todos(issuable.assignee, attributes)
end
end
- def create_mention_todos(project, issuable, author, note = nil)
- mentioned_users = filter_mentioned_users(project, note || issuable, author)
- create_todos(project, issuable, author, mentioned_users, Todo::MENTIONED, note)
+ def create_mention_todos(project, target, author, note = nil)
+ mentioned_users = filter_mentioned_users(project, note || target, author)
+ attributes = attributes_for_todo(project, target, author, Todo::MENTIONED, note)
+ create_todos(mentioned_users, attributes)
+ end
+
+ def attributes_for_target(target)
+ attributes = {
+ project_id: target.project.id,
+ target_id: target.id,
+ target_type: target.class.name,
+ commit_id: nil
+ }
+
+ if target.is_a?(Commit)
+ attributes.merge!(target_id: nil, commit_id: target.id)
+ end
+
+ attributes
+ end
+
+ def attributes_for_todo(project, target, author, action, note = nil)
+ attributes_for_target(target).merge!(
+ project_id: project.id,
+ author_id: author.id,
+ action: action,
+ note: note
+ )
end
def filter_mentioned_users(project, target, author)
@@ -160,11 +178,8 @@ class TodoService
mentioned_users.uniq
end
- def pending_todos(user, project, target)
- user.todos.pending.where(
- project_id: project.id,
- target_id: target.id,
- target_type: target.class.name
- )
+ def pending_todos(user, criteria = {})
+ valid_keys = [:project_id, :target_id, :target_type, :commit_id]
+ user.todos.pending.where(criteria.slice(*valid_keys))
end
end
diff --git a/app/services/update_snippet_service.rb b/app/services/update_snippet_service.rb
index e9328bb7323..93af8f21972 100644
--- a/app/services/update_snippet_service.rb
+++ b/app/services/update_snippet_service.rb
@@ -9,7 +9,6 @@ class UpdateSnippetService < BaseService
def execute
# check that user is allowed to set specified visibility_level
new_visibility = params[:visibility_level]
-
if new_visibility && new_visibility.to_i != snippet.visibility_level
unless Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
deny_visibility_level(snippet, new_visibility)
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index b30dfd109ea..0350995d03d 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -19,6 +19,10 @@
= f.label :default_snippet_visibility, class: 'control-label col-sm-2'
.col-sm-10
= render('shared/visibility_radios', model_method: :default_snippet_visibility, form: f, selected_level: @application_setting.default_snippet_visibility, form_model: ProjectSnippet.new)
+ .form-group.group-visibility-level-holder
+ = f.label :default_group_visibility, class: 'control-label col-sm-2'
+ .col-sm-10
+ = render('shared/visibility_radios', model_method: :default_group_visibility, form: f, selected_level: @application_setting.default_group_visibility, form_model: Group.new)
.form-group
= f.label :restricted_visibility_levels, class: 'control-label col-sm-2'
.col-sm-10
diff --git a/app/views/admin/applications/_delete_form.html.haml b/app/views/admin/applications/_delete_form.html.haml
index 3147cbd659f..042971e1eed 100644
--- a/app/views/admin/applications/_delete_form.html.haml
+++ b/app/views/admin/applications/_delete_form.html.haml
@@ -1,4 +1,4 @@
- submit_btn_css ||= 'btn btn-link btn-remove btn-sm'
= form_tag admin_application_path(application) do
%input{:name => "_method", :type => "hidden", :value => "delete"}/
- = submit_tag 'Destroy', onclick: "return confirm('Are you sure?')", class: submit_btn_css \ No newline at end of file
+ = submit_tag 'Destroy', onclick: "return confirm('Are you sure?')", class: submit_btn_css
diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml
index 198026a1f75..7f2b1cd235d 100644
--- a/app/views/admin/groups/_form.html.haml
+++ b/app/views/admin/groups/_form.html.haml
@@ -10,6 +10,8 @@
.col-sm-10
= render 'shared/choose_group_avatar_button', f: f
+ = render 'shared/visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group
+
- if @group.new_record?
.form-group
.col-sm-offset-2.col-sm-10
diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml
index 118d3cfea07..6bdc885a312 100644
--- a/app/views/admin/groups/index.html.haml
+++ b/app/views/admin/groups/index.html.haml
@@ -46,6 +46,9 @@
%h4
= link_to [:admin, group] do
+ %span{ class: visibility_level_color(group.visibility_level) }
+ = visibility_level_icon(group.visibility_level)
+
%i.fa.fa-folder
= group.name
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index 264fa1bf0cd..f309e80a39a 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -28,6 +28,11 @@
= @group.description
%li
+ %span.light Visibility level:
+ %strong
+ = visibility_level_label(@group.visibility_level)
+
+ %li
%span.light Created on:
%strong
= @group.created_at.to_s(:medium)
diff --git a/app/views/admin/labels/_label.html.haml b/app/views/admin/labels/_label.html.haml
index 5736a301910..f417b2e44a4 100644
--- a/app/views/admin/labels/_label.html.haml
+++ b/app/views/admin/labels/_label.html.haml
@@ -1,6 +1,6 @@
%li{id: dom_id(label)}
.label-row
- = render_colored_label(label)
+ = render_colored_label(label, tooltip: false)
= markdown(label.description, pipeline: :single_line)
.pull-right
= link_to 'Edit', edit_admin_label_path(label), class: 'btn btn-sm'
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index d734e60682a..c638c32a654 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -52,7 +52,7 @@
%li
%span.light fs:
%strong
- = @repository.path_to_repo
+ = @project.repository.path_to_repo
%li
%span.light Size
diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml
index 45cfe3da188..e3a4d64df01 100644
--- a/app/views/dashboard/todos/_todo.html.haml
+++ b/app/views/dashboard/todos/_todo.html.haml
@@ -2,7 +2,7 @@
.todo-item.todo-block
= image_tag avatar_icon(todo.author_email, 40), class: 'avatar s40', alt:''
- .todo-title
+ .todo-title.title
%span.author-name
- if todo.author
= link_to_author(todo)
@@ -16,7 +16,9 @@
- if todo.pending?
.todo-actions.pull-right
- = link_to 'Done', [:dashboard, todo], method: :delete, class: 'btn'
+ = link_to [:dashboard, todo], method: :delete, class: 'btn btn-loading done-todo' do
+ Done
+ = icon('spinner spin')
.todo-body
.todo-note
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index 946d7df3933..f9ec3a89158 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -3,13 +3,15 @@
.top-area
%ul.nav-links
- %li{class: ('active' if params[:state].blank? || params[:state] == 'pending')}
+ - todo_pending_active = ('active' if params[:state].blank? || params[:state] == 'pending')
+ %li{class: "todos-pending #{todo_pending_active}"}
= link_to todos_filter_path(state: 'pending') do
%span
To do
%span{class: 'badge'}
= todos_pending_count
- %li{class: ('active' if params[:state] == 'done')}
+ - todo_done_active = ('active' if params[:state] == 'done')
+ %li{class: "todos-done #{todo_done_active}"}
= link_to todos_filter_path(state: 'done') do
%span
Done
@@ -18,7 +20,9 @@
.nav-controls
- if @todos.any?(&:pending?)
- = link_to 'Mark all as done', destroy_all_dashboard_todos_path(todos_filter_params), class: 'btn', method: :delete
+ = link_to destroy_all_dashboard_todos_path(todos_filter_params), class: 'btn btn-loading js-todos-mark-all', method: :delete do
+ Mark all as done
+ = icon('spinner spin')
.todos-filters
.gray-content-block.second-block
@@ -42,12 +46,12 @@
.prepend-top-default
- if @todos.any?
- @todos.group_by(&:project).each do |group|
- .panel.panel-default.panel-small
+ .panel.panel-default.panel-small.js-todos-list
- project = group[0]
.panel-heading
= link_to project.name_with_namespace, namespace_project_path(project.namespace, project)
- %ul.well-list.todos-list
+ %ul.content-list.todos-list
= render group[1]
= paginate @todos, theme: "gitlab"
- else
diff --git a/app/views/devise/sessions/_new_crowd.html.haml b/app/views/devise/sessions/_new_crowd.html.haml
index 4974bb7f7fb..8e81671b7e7 100644
--- a/app/views/devise/sessions/_new_crowd.html.haml
+++ b/app/views/devise/sessions/_new_crowd.html.haml
@@ -6,4 +6,4 @@
%label{for: "remember_me"}
= check_box_tag :remember_me, '1', false, id: 'remember_me'
%span Remember me
- = button_tag "Sign in", class: "btn-save btn" \ No newline at end of file
+ = button_tag "Sign in", class: "btn-save btn"
diff --git a/app/views/doorkeeper/applications/new.html.haml b/app/views/doorkeeper/applications/new.html.haml
index fd32a468b45..d3692d1f759 100644
--- a/app/views/doorkeeper/applications/new.html.haml
+++ b/app/views/doorkeeper/applications/new.html.haml
@@ -4,4 +4,4 @@
%hr
-= render 'form', application: @application \ No newline at end of file
+= render 'form', application: @application
diff --git a/app/views/doorkeeper/authorizations/error.html.haml b/app/views/doorkeeper/authorizations/error.html.haml
index 7561ec85ed9..a4c607cea60 100644
--- a/app/views/doorkeeper/authorizations/error.html.haml
+++ b/app/views/doorkeeper/authorizations/error.html.haml
@@ -1,3 +1,3 @@
%h3.page-title An error has occurred
%main{:role => "main"}
- %pre= @pre_auth.error_response.body[:error_description] \ No newline at end of file
+ %pre= @pre_auth.error_response.body[:error_description]
diff --git a/app/views/doorkeeper/authorizations/show.html.haml b/app/views/doorkeeper/authorizations/show.html.haml
index 9a402007194..01f9e46f142 100644
--- a/app/views/doorkeeper/authorizations/show.html.haml
+++ b/app/views/doorkeeper/authorizations/show.html.haml
@@ -1,3 +1,3 @@
%h3.page-title Authorization code:
%main{:role => "main"}
- %code#authorization_code= params[:code] \ No newline at end of file
+ %code#authorization_code= params[:code]
diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml
index 36fb2d51629..2d9d9dd6342 100644
--- a/app/views/events/_event.html.haml
+++ b/app/views/events/_event.html.haml
@@ -1,4 +1,4 @@
-- if event.proper?
+- if event.proper?(current_user)
.event-item{class: "#{event.body? ? "event-block" : "event-inline" }"}
.event-item-timestamp
#{time_ago_with_tooltip(event.created_at)}
diff --git a/app/views/events/event/_common.html.haml b/app/views/events/event/_common.html.haml
index e9e16a7646f..c994e3b997d 100644
--- a/app/views/events/event/_common.html.haml
+++ b/app/views/events/event/_common.html.haml
@@ -4,7 +4,7 @@
= event_action_name(event)
- if event.target
- %strong= link_to event.target.to_reference, [event.project.namespace.becomes(Namespace), event.project, event.target]
+ %strong= link_to event.target.reference_link_text, [event.project.namespace.becomes(Namespace), event.project, event.target]
= event_preposition(event)
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index 83936d39b16..ea5a0358392 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -23,6 +23,8 @@
%hr
= link_to 'Remove avatar', group_avatar_path(@group.to_param), data: { confirm: "Group avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
+ = render 'shared/visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group
+
.form-group
%hr
= f.label :share_with_group_lock, class: 'control-label' do
@@ -32,6 +34,7 @@
= f.check_box :share_with_group_lock
%span.descr Prevent sharing a project with another group within this group
+
.form-actions
= f.submit 'Save group', class: "btn btn-save"
diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml
index 4bc31cabea6..30ab8aeba13 100644
--- a/app/views/groups/new.html.haml
+++ b/app/views/groups/new.html.haml
@@ -17,6 +17,8 @@
.col-sm-10
= render 'shared/choose_group_avatar_button', f: f
+ = render 'shared/visibility_level', f: f, visibility_level: default_group_visibility, can_change_visibility_level: true, form_model: @group
+
.form-group
.col-sm-offset-2.col-sm-10
= render 'shared/group_tips'
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 23a34ac36dd..820743dc8dd 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -1,8 +1,5 @@
- @no_container = true
-- unless can?(current_user, :read_group, @group)
- - @disable_search_panel = true
-
= content_for :meta_tags do
- if current_user
= auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity")
@@ -18,7 +15,10 @@
= link_to group_icon(@group), target: '_blank' do
= image_tag group_icon(@group), class: "avatar group-avatar s90"
.cover-title
- = @group.name
+ %h1
+ = @group.name
+ %span.visibility-icon.has_tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) }
+ = visibility_level_icon(@group.visibility_level, fw: false)
.cover-desc.username
@#{@group.path}
@@ -27,34 +27,29 @@
.cover-desc.description
= markdown(@group.description, pipeline: :description)
-- if can?(current_user, :read_group, @group)
- %div{ class: container_class }
- .top-area
- %ul.nav-links
- %li.active
- = link_to "#projects", 'data-toggle' => 'tab' do
- All Projects
- - if @shared_projects.present?
- %li
- = link_to "#shared", 'data-toggle' => 'tab' do
- Shared Projects
- .nav-controls
- = form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
- = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
- = render 'shared/projects/dropdown'
- - if can? current_user, :create_projects, @group
- = link_to new_project_path(namespace_id: @group.id), class: 'btn btn-new pull-right' do
- = icon('plus')
- New Project
-
- .tab-content
- .tab-pane.active#projects
- = render "projects", projects: @projects
-
+%div{ class: container_class }
+ .top-area
+ %ul.nav-links
+ %li.active
+ = link_to "#projects", 'data-toggle' => 'tab' do
+ All Projects
- if @shared_projects.present?
- .tab-pane#shared
- = render "shared_projects", projects: @shared_projects
-
-- else
- %p.nav-links.no-top
- No projects to show
+ %li
+ = link_to "#shared", 'data-toggle' => 'tab' do
+ Shared Projects
+ .nav-controls
+ = form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
+ = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
+ = render 'shared/projects/dropdown'
+ - if can? current_user, :create_projects, @group
+ = link_to new_project_path(namespace_id: @group.id), class: 'btn btn-new pull-right' do
+ = icon('plus')
+ New Project
+
+ .tab-content
+ .tab-pane.active#projects
+ = render "projects", projects: @projects
+
+ - if @shared_projects.present?
+ .tab-pane#shared
+ = render "shared_projects", projects: @shared_projects
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 77d01a7736c..bfa5937cf3f 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -7,9 +7,8 @@
.navbar-collapse.collapse
%ul.nav.navbar-nav.pull-right
- - unless @disable_search_panel
- %li.hidden-sm.hidden-xs
- = render 'layouts/search'
+ %li.hidden-sm.hidden-xs
+ = render 'layouts/search'
%li.visible-sm.visible-xs
= link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('search')
@@ -46,6 +45,8 @@
%h1.title= title
= render 'shared/outdated_browser'
+
- if @project && !@project.empty_repo?
- :javascript
- var findFileURL = "#{namespace_project_find_file_path(@project.namespace, @project, @ref || @project.repository.root_ref)}";
+ - if ref = @ref || @project.repository.root_ref
+ :javascript
+ var findFileURL = "#{namespace_project_find_file_path(@project.namespace, @project, ref)}";
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index db0cf393922..4a0069f18f8 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -1,7 +1,7 @@
%ul.nav.nav-sidebar
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: 'home'}) do
= link_to dashboard_projects_path, title: 'Projects' do
- = icon('home fw')
+ = icon('bookmark fw')
%span
Projects
= nav_link(controller: :todos) do
diff --git a/app/views/layouts/nav/_explore.html.haml b/app/views/layouts/nav/_explore.html.haml
index 48039ca2918..f08c5edf99c 100644
--- a/app/views/layouts/nav/_explore.html.haml
+++ b/app/views/layouts/nav/_explore.html.haml
@@ -1,7 +1,7 @@
%ul.nav.nav-sidebar
= nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do
= link_to explore_root_path, title: 'Projects' do
- = icon('home fw')
+ = icon('bookmark fw')
%span
Projects
= nav_link(controller: :groups) do
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index 59411ae1da1..55940741dc0 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -12,40 +12,38 @@
= icon('group fw')
%span
Group
- - if can?(current_user, :read_group, @group)
- = nav_link(path: 'groups#activity') do
- = link_to activity_group_path(@group), title: 'Activity' do
- = icon('dashboard fw')
- %span
- Activity
- - if current_user
- = nav_link(controller: [:group, :milestones]) do
- = link_to group_milestones_path(@group), title: 'Milestones' do
- = icon('clock-o fw')
- %span
- Milestones
- = nav_link(path: 'groups#issues') do
- = link_to issues_group_path(@group), title: 'Issues' do
- = icon('exclamation-circle fw')
- %span
- Issues
- - if current_user
- %span.count= number_with_delimiter(Issue.opened.of_group(@group).count)
- = nav_link(path: 'groups#merge_requests') do
- = link_to merge_requests_group_path(@group), title: 'Merge Requests' do
- = icon('tasks fw')
- %span
- Merge Requests
- - if current_user
- %span.count= number_with_delimiter(MergeRequest.opened.of_group(@group).count)
- = nav_link(controller: [:group_members]) do
- = link_to group_group_members_path(@group), title: 'Members' do
- = icon('users fw')
+ = nav_link(path: 'groups#activity') do
+ = link_to activity_group_path(@group), title: 'Activity' do
+ = icon('dashboard fw')
+ %span
+ Activity
+ = nav_link(controller: [:group, :milestones]) do
+ = link_to group_milestones_path(@group), title: 'Milestones' do
+ = icon('clock-o fw')
+ %span
+ Milestones
+ = nav_link(path: 'groups#issues') do
+ = link_to issues_group_path(@group), title: 'Issues' do
+ = icon('exclamation-circle fw')
+ %span
+ Issues
+ - issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
+ %span.count= number_with_delimiter(issues.count)
+ = nav_link(path: 'groups#merge_requests') do
+ = link_to merge_requests_group_path(@group), title: 'Merge Requests' do
+ = icon('tasks fw')
+ %span
+ Merge Requests
+ - merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened').execute
+ %span.count= number_with_delimiter(merge_requests.count)
+ = nav_link(controller: [:group_members]) do
+ = link_to group_group_members_path(@group), title: 'Members' do
+ = icon('users fw')
+ %span
+ Members
+ - if can?(current_user, :admin_group, @group)
+ = nav_link(html_options: { class: "separate-item" }) do
+ = link_to edit_group_path(@group), title: 'Settings' do
+ = icon ('cogs fw')
%span
- Members
- - if can?(current_user, :admin_group, @group)
- = nav_link(html_options: { class: "separate-item" }) do
- = link_to edit_group_path(@group), title: 'Settings' do
- = icon ('cogs fw')
- %span
- Settings
+ Settings
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 0ae83ee01eb..86b46e8c75e 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -67,7 +67,7 @@
%span
Issues
- if @project.default_issues_tracker?
- %span.count.issue_counter= number_with_delimiter(@project.issues.opened.count)
+ %span.count.issue_counter= number_with_delimiter(@project.issues.visible_to_user(current_user).opened.count)
- if project_nav_tab? :merge_requests
= nav_link(controller: :merge_requests) do
diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml
index 37b4d562966..2997f59d946 100644
--- a/app/views/layouts/notify.html.haml
+++ b/app/views/layouts/notify.html.haml
@@ -1,33 +1,9 @@
%html{lang: "en"}
%head
%meta{content: "text/html; charset=utf-8", "http-equiv" => "Content-Type"}
- %title
- GitLab
- :css
- img {
- max-width: 100%;
- height: auto;
- }
- p.details {
- font-style:italic;
- color:#777
- }
- .footer p {
- font-size:small;
- color:#777
- }
- pre.commit-message {
- white-space: pre-wrap;
- }
- .file-stats a {
- text-decoration: none;
- }
- .file-stats .new-file {
- color: #090;
- }
- .file-stats .deleted-file {
- color: #B00;
- }
+ %title
+ GitLab
+ = stylesheet_link_tag 'notify'
%body
%div.content
= yield
diff --git a/app/views/notify/issue_moved_email.html.haml b/app/views/notify/issue_moved_email.html.haml
new file mode 100644
index 00000000000..40f7d61fe19
--- /dev/null
+++ b/app/views/notify/issue_moved_email.html.haml
@@ -0,0 +1,6 @@
+%p
+ Issue was moved to another project.
+%p
+ New issue:
+ = link_to namespace_project_issue_url(@new_project.namespace, @new_project, @new_issue) do
+ = @new_issue.title
diff --git a/app/views/notify/issue_moved_email.text.erb b/app/views/notify/issue_moved_email.text.erb
new file mode 100644
index 00000000000..b3bd43c2055
--- /dev/null
+++ b/app/views/notify/issue_moved_email.text.erb
@@ -0,0 +1,4 @@
+Issue was moved to another project.
+
+New issue location:
+<%= namespace_project_issue_url(@new_project.namespace, @new_project, @new_issue) %>
diff --git a/app/views/profiles/keys/_key.html.haml b/app/views/profiles/keys/_key.html.haml
index 25e9e8ff008..4dbaa662b66 100644
--- a/app/views/profiles/keys/_key.html.haml
+++ b/app/views/profiles/keys/_key.html.haml
@@ -8,7 +8,7 @@
= key.fingerprint
.pull-right
%span.key-created-at
- created #{time_ago_with_tooltip(key.created_at)} ago
+ created #{time_ago_with_tooltip(key.created_at)}
= link_to path_to_key(key, is_admin), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-transparent prepend-left-10" do
%span.sr-only Remove
= icon('trash')
diff --git a/app/views/projects/_builds_settings.html.haml b/app/views/projects/_builds_settings.html.haml
new file mode 100644
index 00000000000..95ab9ecf3e8
--- /dev/null
+++ b/app/views/projects/_builds_settings.html.haml
@@ -0,0 +1,60 @@
+%fieldset.builds-feature
+ %legend
+ Builds:
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ %p Get recent application code using the following command:
+ .radio
+ = f.label :build_allow_git_fetch_false do
+ = f.radio_button :build_allow_git_fetch, 'false'
+ %strong git clone
+ %br
+ %span.descr Slower but makes sure you have a clean dir before every build
+ .radio
+ = f.label :build_allow_git_fetch_true do
+ = f.radio_button :build_allow_git_fetch, 'true'
+ %strong git fetch
+ %br
+ %span.descr Faster
+
+ .form-group
+ = f.label :build_timeout_in_minutes, 'Timeout', class: 'control-label'
+ .col-sm-10
+ = f.number_field :build_timeout_in_minutes, class: 'form-control', min: '0'
+ %p.help-block per build in minutes
+ .form-group
+ = f.label :build_coverage_regex, "Test coverage parsing", class: 'control-label'
+ .col-sm-10
+ .input-group
+ %span.input-group-addon /
+ = f.text_field :build_coverage_regex, class: 'form-control', placeholder: '\(\d+.\d+\%\) covered'
+ %span.input-group-addon /
+ %p.help-block
+ We will use this regular expression to find test coverage output in build trace.
+ Leave blank if you want to disable this feature
+ .bs-callout.bs-callout-info
+ %p Below are examples of regex for existing tools:
+ %ul
+ %li
+ Simplecov (Ruby) -
+ %code \(\d+.\d+\%\) covered
+ %li
+ pytest-cov (Python) -
+ %code \d+\%\s*$
+ %li
+ phpunit --coverage-text --colors=never (PHP) -
+ %code ^\s*Lines:\s*\d+.\d+\%
+
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :public_builds do
+ = f.check_box :public_builds
+ %strong Public builds
+ .help-block Allow everyone to access builds for Public and Internal projects
+
+ .form-group
+ = f.label :runners_token, "Runners token", class: 'control-label'
+ .col-sm-10
+ = f.text_field :runners_token, class: "form-control", placeholder: 'xEeFCaDAB89'
+ %p.help-block The secure token used to checkout project.
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index b45df44f270..514cbfa339d 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -2,21 +2,21 @@
.project-home-panel.cover-block.clearfix{:class => ("empty-project" if empty_repo)}
.project-identicon-holder
= project_icon(@project, alt: '', class: 'project-avatar avatar s90')
- .project-home-desc
+ .cover-title.project-home-desc
%h1
= @project.name
- %span.visibility-icon.has_tooltip{data: { container: 'body' },
- title: "#{visibility_level_label(@project.visibility_level)} - #{project_visibility_level_description(@project.visibility_level)}"}
+ %span.visibility-icon.has_tooltip{data: { container: 'body' }, title: visibility_icon_description(@project)}
= visibility_level_icon(@project.visibility_level, fw: false)
- - if @project.description.present?
+ - if @project.description.present?
+ .cover-desc.project-home-desc
= markdown(@project.description, pipeline: :description)
- - if forked_from_project = @project.forked_from_project
- %p
- Forked from
- = link_to project_path(forked_from_project) do
- = forked_from_project.namespace.try(:name)
+ - if forked_from_project = @project.forked_from_project
+ .cover-desc
+ Forked from
+ = link_to project_path(forked_from_project) do
+ = forked_from_project.namespace.try(:name)
.cover-controls
- if current_user
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 76a823d3828..57e507e68c8 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -11,7 +11,7 @@
- if branch.name == @repository.root_ref
%span.label.label-primary default
- elsif @repository.merged_to_root_ref? branch.name
- %span.label.label-info.has_tooltip(title="Merged into #{@repository.root_ref}")
+ %span.label.label-info.has-tooltip(title="Merged into #{@repository.root_ref}")
merged
- if @project.protected_branch? branch.name
@@ -30,7 +30,7 @@
Compare
- if can_remove_branch?(@project, branch.name)
- = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-grouped btn-xs btn-remove remove-row has_tooltip', title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do
+ = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-grouped btn-xs btn-remove remove-row has-tooltip', title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do
= icon("trash-o")
- if branch.name != @repository.root_ref
diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml
index 6a60cfeff76..58f43ecb5d5 100644
--- a/app/views/projects/buttons/_download.html.haml
+++ b/app/views/projects/buttons/_download.html.haml
@@ -1,4 +1,4 @@
- unless @project.empty_repo?
- if can? current_user, :download_code, @project
- = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn has_tooltip', data: {container: "body"}, rel: 'nofollow', title: "Download ZIP" do
+ = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn has-tooltip', data: {container: "body"}, rel: 'nofollow', title: "Download ZIP" do
= icon('download')
diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml
index 133531887a2..88cbb7c03c5 100644
--- a/app/views/projects/buttons/_fork.html.haml
+++ b/app/views/projects/buttons/_fork.html.haml
@@ -1,7 +1,7 @@
- unless @project.empty_repo?
- if current_user && can?(current_user, :fork_project, @project)
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
- = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn has_tooltip' do
+ = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn has-tooltip' do
= icon('code-fork fw')
Fork
%div.count-with-arrow
@@ -9,7 +9,7 @@
%span.count
= @project.forks_count
- else
- = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn has_tooltip' do
+ = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn has-tooltip' do
= icon('code-fork fw')
Fork
%div.count-with-arrow
diff --git a/app/views/projects/buttons/_notifications.html.haml b/app/views/projects/buttons/_notifications.html.haml
index 3e83ec3912f..a3786c35a1f 100644
--- a/app/views/projects/buttons/_notifications.html.haml
+++ b/app/views/projects/buttons/_notifications.html.haml
@@ -14,7 +14,7 @@
= notification_list_item(level, @membership)
- when GroupMember
- .btn.disabled.notifications-btn.has_tooltip{title: "To change the notification level, you need to be a member of the project itself, not only its group."}
+ .btn.disabled.notifications-btn.has-tooltip{title: "To change the notification level, you need to be a member of the project itself, not only its group."}
= icon('bell')
= notification_label(@membership)
= icon('angle-down')
diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml
index 21ba426aaa1..02dbb2985a4 100644
--- a/app/views/projects/buttons/_star.html.haml
+++ b/app/views/projects/buttons/_star.html.haml
@@ -1,5 +1,5 @@
- if current_user
- = link_to toggle_star_namespace_project_path(@project.namespace, @project), class: 'btn star-btn toggle-star has_tooltip', method: :post, remote: true, title: "Star project" do
+ = link_to toggle_star_namespace_project_path(@project.namespace, @project), class: 'btn star-btn toggle-star has-tooltip', method: :post, remote: true, title: "Star project" do
- if current_user.starred?(@project)
= icon('star fw')
%span.starred Unstar
@@ -12,7 +12,7 @@
= @project.star_count
- else
- = link_to new_user_session_path, class: 'btn has_tooltip star-btn', title: 'You must sign in to star a project' do
+ = link_to new_user_session_path, class: 'btn has-tooltip star-btn', title: 'You must sign in to star a project' do
= icon('star fw')
Star
%div.count-with-arrow
diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml
index 4ab81f3635c..dd590a4b8ec 100644
--- a/app/views/projects/compare/_form.html.haml
+++ b/app/views/projects/compare/_form.html.haml
@@ -1,7 +1,7 @@
= form_tag namespace_project_compare_index_path(@project.namespace, @project), method: :post, class: 'form-inline js-requires-input' do
.clearfix
- if params[:to] && params[:from]
- = link_to 'switch', {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has_tooltip', title: 'Switch base of comparison'}
+ = link_to 'switch', {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has-tooltip', title: 'Switch base of comparison'}
.form-group
.input-group.inline-input-group
%span.input-group-addon from
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index 6086ad3661e..2e1a37aa06d 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -20,4 +20,4 @@
- next unless blob
= render 'projects/diffs/file', i: index, project: project,
- diff_file: diff_file, diff_commit: diff_commit, blob: blob
+ diff_file: diff_file, diff_commit: diff_commit, blob: blob, diff_refs: diff_refs
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index 3ac058a3bf8..698ed02ea0e 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -28,7 +28,7 @@
.file-actions.hidden-xs
- if blob_text_viewable?(blob)
- = link_to '#', class: 'js-toggle-diff-comments btn active has_tooltip', title: "Toggle comments for this file" do
+ = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip', title: "Toggle comments for this file" do
= icon('comments')
\
@@ -42,13 +42,17 @@
.diff-content.diff-wrap-lines
-# Skipp all non non-supported blobs
- return unless blob.respond_to?('text?')
- - if blob_text_viewable?(blob)
- - if diff_view == 'parallel'
- = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i
- - else
- = render "projects/diffs/text_file", diff_file: diff_file, index: i
- - elsif blob.image?
- - old_file = project.repository.prev_blob_for_diff(diff_commit, diff_file)
- = render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i
+ - if diff_file.too_large?
+ .nothing-here-block
+ This diff could not be displayed because it is too large.
- else
- .nothing-here-block No preview for this file type
+ - if blob_text_viewable?(blob)
+ - if diff_view == 'parallel'
+ = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i
+ - else
+ = render "projects/diffs/text_file", diff_file: diff_file, index: i
+ - elsif blob.image?
+ - old_file = project.repository.prev_blob_for_diff(diff_commit, diff_file)
+ = render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i, diff_refs: diff_refs
+ - else
+ .nothing-here-block No preview for this file type
diff --git a/app/views/projects/diffs/_image.html.haml b/app/views/projects/diffs/_image.html.haml
index 752e92e2e6b..8367112a9cb 100644
--- a/app/views/projects/diffs/_image.html.haml
+++ b/app/views/projects/diffs/_image.html.haml
@@ -1,6 +1,7 @@
- diff = diff_file.diff
- file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, diff.new_path))
-- old_file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.parent_id, diff.old_path))
+- old_commit_id = diff_refs.first.id
+- old_file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(old_commit_id, diff.old_path))
- if diff.renamed_file || diff.new_file || diff.deleted_file
.image
%span.wrap
@@ -12,7 +13,7 @@
%div.two-up.view
%span.wrap
.frame.deleted
- %a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(@commit.parent_id, diff.old_path))}
+ %a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(old_commit_id, diff.old_path))}
%img{src: old_file_raw_path}
%p.image-info.hide
%span.meta-filesize= "#{number_to_human_size old_file.size}"
diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml
new file mode 100644
index 00000000000..9464c8dc996
--- /dev/null
+++ b/app/views/projects/diffs/_line.html.haml
@@ -0,0 +1,26 @@
+- type = line.type
+%tr.line_holder{id: line_code, class: type}
+ - case type
+ - when 'match'
+ = render "projects/diffs/match_line", {line: line.text,
+ line_old: line.old_pos, line_new: line.new_pos, bottom: false, new_file: diff_file.new_file}
+ - when 'nonewline'
+ %td.old_line.diff-line-num
+ %td.new_line.diff-line-num
+ %td.line_content.match= line.text
+ - else
+ %td.old_line.diff-line-num{class: type}
+ - link_text = raw(type == "new" ? "&nbsp;" : line.old_pos)
+ - if defined?(plain) && plain
+ = link_text
+ - else
+ = link_to link_text, "##{line_code}", id: line_code
+ - if @comments_allowed && can?(current_user, :create_note, @project)
+ = link_to_new_diff_note(line_code)
+ %td.new_line.diff-line-num{class: type, data: {linenumber: line.new_pos}}
+ - link_text = raw(type == "old" ? "&nbsp;" : line.new_pos)
+ - if defined?(plain) && plain
+ = link_text
+ - else
+ = link_to link_text, "##{line_code}", id: line_code
+ %td.line_content{class: "noteable_line #{type} #{line_code}", data: { line_code: line_code }}= diff_line_content(line.text)
diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml
index 9a8208202e4..e7169d7b599 100644
--- a/app/views/projects/diffs/_text_file.html.haml
+++ b/app/views/projects/diffs/_text_file.html.haml
@@ -8,26 +8,9 @@
- last_line = 0
- raw_diff_lines = diff_file.diff_lines.to_a
- diff_file.highlighted_diff_lines.each_with_index do |line, index|
- - type = line.type
- - last_line = line.new_pos
- line_code = generate_line_code(diff_file.file_path, line)
- - line_old = line.old_pos
- %tr.line_holder{ id: line_code, class: "#{type}" }
- - if type == "match"
- = render "projects/diffs/match_line", {line: line.text,
- line_old: line_old, line_new: line.new_pos, bottom: false, new_file: diff_file.new_file}
- - elsif type == 'nonewline'
- %td.old_line.diff-line-num
- %td.new_line.diff-line-num
- %td.line_content.match= line.text
- - else
- %td.old_line.diff-line-num{class: type}
- = link_to raw(type == "new" ? "&nbsp;" : line_old), "##{line_code}", id: line_code
- - if @comments_allowed && can?(current_user, :create_note, @project)
- = link_to_new_diff_note(line_code)
- %td.new_line.diff-line-num{class: type, data: {linenumber: line.new_pos}}
- = link_to raw(type == "old" ? "&nbsp;" : line.new_pos), "##{line_code}", id: line_code
- %td.line_content{class: "noteable_line #{type} #{line_code}", data: { line_code: line_code }}= diff_line_content(line.text)
+ - last_line = line.new_pos
+ = render "projects/diffs/line", {line: line, diff_file: diff_file, line_code: line_code}
- if @reply_allowed
- comments = @line_notes.select { |n| n.line_code == line_code && n.active? }.sort_by(&:created_at)
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index f2e56081afe..6d872cd0b21 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -84,6 +84,8 @@
%br
%span.descr Share code pastes with others out of git repository
+ = render 'builds_settings', f: f
+
%fieldset.features
%legend
Project avatar:
@@ -110,69 +112,6 @@
%hr
= link_to 'Remove avatar', namespace_project_avatar_path(@project.namespace, @project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
- %fieldset.features
- %legend
- Continuous Integration
- .form-group
- .col-sm-offset-2.col-sm-10
- %p Get recent application code using the following command:
- .radio
- = f.label :build_allow_git_fetch_false do
- = f.radio_button :build_allow_git_fetch, 'false'
- %strong git clone
- %br
- %span.descr Slower but makes sure you have a clean dir before every build
- .radio
- = f.label :build_allow_git_fetch_true do
- = f.radio_button :build_allow_git_fetch, 'true'
- %strong git fetch
- %br
- %span.descr Faster
-
- .form-group
- = f.label :build_timeout_in_minutes, 'Timeout', class: 'control-label'
- .col-sm-10
- = f.number_field :build_timeout_in_minutes, class: 'form-control', min: '0'
- %p.help-block per build in minutes
- .form-group
- = f.label :build_coverage_regex, "Test coverage parsing", class: 'control-label'
- .col-sm-10
- .input-group
- %span.input-group-addon /
- = f.text_field :build_coverage_regex, class: 'form-control', placeholder: '\(\d+.\d+\%\) covered'
- %span.input-group-addon /
- %p.help-block
- We will use this regular expression to find test coverage output in build trace.
- Leave blank if you want to disable this feature
- .bs-callout.bs-callout-info
- %p Below are examples of regex for existing tools:
- %ul
- %li
- Simplecov (Ruby) -
- %code \(\d+.\d+\%\) covered
- %li
- pytest-cov (Python) -
- %code \d+\%\s*$
- %li
- phpunit --coverage-text --colors=never (PHP) -
- %code ^\s*Lines:\s*\d+.\d+\%
-
- .form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- = f.label :public_builds do
- = f.check_box :public_builds
- %strong Public builds
- .help-block Allow everyone to access builds for Public and Internal projects
-
- %fieldset.features
- %legend
- Advanced settings
- .form-group
- = f.label :runners_token, "CI token", class: 'control-label'
- .col-sm-10
- = f.text_field :runners_token, class: "form-control", placeholder: 'xEeFCaDAB89'
- %p.help-block The secure token used to checkout project.
.form-actions
= f.submit 'Save changes', class: "btn btn-save"
diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml
index edabc2d3b44..73a7fc0e1ac 100644
--- a/app/views/projects/forks/new.html.haml
+++ b/app/views/projects/forks/new.html.haml
@@ -12,7 +12,7 @@
.col-md-2.col-sm-3
- if fork = namespace.find_fork_of(@project)
.fork-thumbnail
- = link_to project_path(fork), title: "Visit project fork", class: 'has_tooltip' do
+ = link_to project_path(fork), title: "Visit project fork", class: 'has-tooltip' do
= image_tag namespace_icon(namespace, 100)
.caption
%strong
@@ -22,7 +22,7 @@
- else
.fork-thumbnail
- = link_to namespace_project_forks_path(@project.namespace, @project, namespace_key: namespace.id), title: "Fork here", method: "POST", class: 'has_tooltip' do
+ = link_to namespace_project_forks_path(@project.namespace, @project, namespace_key: namespace.id), title: "Fork here", method: "POST", class: 'has-tooltip' do
= image_tag namespace_icon(namespace, 100)
.caption
%strong
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index a44f34c2a68..4aa92d0b39e 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -3,10 +3,11 @@
.issue-check
= check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue"
- .issue-title
+ .issue-title.title
%span.issue-title-text
- = link_to_gfm issue.title, issue_path(issue), class: "title"
- %ul.controls.light
+ = confidential_icon(issue)
+ = link_to_gfm issue.title, issue_path(issue)
+ %ul.controls
- if issue.closed?
%li
CLOSED
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 1e8308277cc..6fa059cbe68 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -22,20 +22,20 @@
= icon('angle-double-left')
.issue-meta
+ = confidential_icon(@issue)
%strong.identifier
Issue ##{@issue.iid}
%span.creator
- by
+ opened
.editor-details
.editor-details
+ = time_ago_with_tooltip(@issue.created_at)
+ by
%strong
= link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-xs")
- %span.hidden-xs
- = '@' + @issue.author.username
%strong
= link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg",
by_username: true, avatar: false)
- = time_ago_with_tooltip(@issue.created_at)
.pull-right.issue-btn-group
- if can?(current_user, :create_issue, @project)
@@ -45,7 +45,6 @@
- if can?(current_user, :update_issue, @issue)
= link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
= link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
-
= link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-nr btn-grouped issuable-edit' do
= icon('pencil-square-o')
Edit
@@ -63,10 +62,7 @@
= markdown(@issue.description, cache_key: [@issue, "description"])
%textarea.hidden.js-task-list-field
= @issue.description
- - if @issue.updated_at != @issue.created_at
- %small
- Edited
- = time_ago_with_tooltip(@issue.updated_at, placement: 'bottom', html_class: 'issue_edited_ago')
+ = edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago')
.merge-requests
= render 'merge_requests'
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 18cf3f14f0b..391193eed6c 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -1,8 +1,8 @@
%li{ class: mr_css_classes(merge_request) }
- .merge-request-title
+ .merge-request-title.title
%span.merge-request-title-text
- = link_to_gfm merge_request.title, merge_request_path(merge_request), class: "title"
- %ul.controls.light
+ = link_to_gfm merge_request.title, merge_request_path(merge_request)
+ %ul.controls
- if merge_request.merged?
%li
MERGED
@@ -17,7 +17,7 @@
- if merge_request.open? && merge_request.broken?
%li
- = link_to merge_request_path(merge_request), class: "has_tooltip", title: "Cannot be merged automatically", data: { container: 'body' } do
+ = link_to merge_request_path(merge_request), class: "has-tooltip", title: "Cannot be merged automatically", data: { container: 'body' } do
= icon('exclamation-triangle')
- if merge_request.assignee
diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml
index 602f787e6cf..a23bd8d18d0 100644
--- a/app/views/projects/merge_requests/show/_mr_box.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_box.html.haml
@@ -11,7 +11,4 @@
%textarea.hidden.js-task-list-field
= @merge_request.description
- - if @merge_request.updated_at != @merge_request.created_at
- %small
- Edited
- = time_ago_with_tooltip(@merge_request.updated_at, placement: 'bottom')
+ = edited_time_ago_with_tooltip(@merge_request, placement: 'bottom')
diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml
index a75c0d96c57..ab4b1f14be5 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -8,25 +8,28 @@
= icon('angle-double-left')
.issue-meta
%strong.identifier
- Merge Request ##{@merge_request.iid}
+ %span.hidden-sm.hidden-md.hidden-lg
+ MR
+ %span.hidden-xs
+ Merge Request
+ !#{@merge_request.iid}
%span.creator
- by
+ opened
.editor-details
+ = time_ago_with_tooltip(@merge_request.created_at)
+ by
%strong
= link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-xs")
- %span.hidden-xs
- = '@' + @merge_request.author.username
%strong
= link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg",
by_username: true, avatar: false)
- = time_ago_with_tooltip(@merge_request.created_at)
.issue-btn-group.pull-right
- if can?(current_user, :update_merge_request, @merge_request)
- if @merge_request.open?
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: 'btn btn-nr btn-grouped btn-close', title: 'Close merge request'
= link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-nr btn-grouped issuable-edit', id: 'edit_merge_request' do
- %i.fa.fa-pencil-square-o
+ = icon('pencil-square-o')
Edit
- if @merge_request.closed?
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'btn btn-nr btn-grouped btn-reopen reopen-mr-link', title: 'Reopen merge request'
diff --git a/app/views/projects/merge_requests/widget/open/_wip.html.haml b/app/views/projects/merge_requests/widget/open/_wip.html.haml
index 0cf16542cc1..c296422a9cf 100644
--- a/app/views/projects/merge_requests/widget/open/_wip.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_wip.html.haml
@@ -1,5 +1,11 @@
%h4
This merge request is currently a Work In Progress
-%p
- When this merge request is ready, remove the "WIP" prefix from the title to allow it to be merged.
+- if can?(current_user, :update_merge_request, @merge_request)
+ %p
+ When this merge request is ready,
+ = link_to remove_wip_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), method: :post do
+ remove the
+ %code WIP:
+ prefix from the title
+ to allow it to be merged.
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index b4597043a27..be63875ab34 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -42,7 +42,7 @@
= preserve do
= markdown @milestone.description
-- if @milestone.complete? && @milestone.active?
+- if @milestone.complete?(current_user) && @milestone.active?
.alert.alert-success.prepend-top-default
%span All issues for this milestone are closed. You may close milestone now.
diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml
index 13e624764d9..2999befffc6 100644
--- a/app/views/projects/notes/_edit_form.html.haml
+++ b/app/views/projects/notes/_edit_form.html.haml
@@ -5,6 +5,6 @@
= render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-task-list-field'
= render 'projects/notes/hints'
- .note-form-actions
+ .note-form-actions.clearfix
= f.submit 'Save Comment', class: 'btn btn-nr btn-save btn-grouped js-comment-button'
= link_to 'Cancel', '#', class: 'btn btn-nr btn-cancel note-edit-cancel'
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 52972576aff..2cf32e6093d 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -27,20 +27,13 @@
%span.note-last-update
%a{name: dom_id(note), href: "##{dom_id(note)}", title: 'Link here'}
= time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note_created_ago')
- - if note.updated_at != note.created_at
- %span.note-updated-at
- &middot;
- = icon('edit', title: 'edited')
- = time_ago_with_tooltip(note.updated_at, placement: 'bottom', html_class: 'note_edited_ago')
- - if note.updated_by && note.updated_by != note.author
- by #{link_to_member(note.project, note.updated_by, avatar: false, author_class: nil)}
-
.note-body{class: note_editable?(note) ? 'js-task-list-container' : ''}
.note-text
= preserve do
= markdown(note.note, pipeline: :note, cache_key: [note, "note"])
- if note_editable?(note)
= render 'projects/notes/edit_form', note: note
+ = edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true)
- if note.attachment.url
.note-attachment
@@ -54,4 +47,3 @@
= link_to delete_attachment_namespace_project_note_path(note.project.namespace, note.project, note),
title: 'Delete this attachment', method: :delete, remote: true, data: { confirm: 'Are you sure you want to remove the attachment?' }, class: 'danger js-note-attachment-delete' do
= icon('trash-o', class: 'cred')
- .clear
diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml
index b9486a9b492..24658319060 100644
--- a/app/views/projects/repositories/_download_archive.html.haml
+++ b/app/views/projects/repositories/_download_archive.html.haml
@@ -10,7 +10,7 @@
%span.caret
%span.sr-only
Select Archive Format
- %ul.col-xs-10.dropdown-menu{ role: 'menu' }
+ %ul.col-xs-10.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
%li
= link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), rel: 'nofollow' do
%i.fa.fa-download
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
index 399782273d3..dbc35c16feb 100644
--- a/app/views/projects/tags/_tag.html.haml
+++ b/app/views/projects/tags/_tag.html.haml
@@ -15,11 +15,11 @@
= render 'projects/tags/download', ref: tag.name, project: @project
- if can?(current_user, :push_code, @project)
- = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn-grouped btn has_tooltip', title: "Edit release notes" do
+ = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn-grouped btn has-tooltip', title: "Edit release notes" do
= icon("pencil")
- if can?(current_user, :admin_project, @project)
- = link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: 'btn btn-grouped btn-xs btn-remove remove-row has_tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{tag.name}' tag cannot be undone. Are you sure?", container: 'body' }, remote: true do
+ = link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: 'btn btn-grouped btn-xs btn-remove remove-row has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{tag.name}' tag cannot be undone. Are you sure?", container: 'body' }, remote: true do
= icon("trash-o")
- if commit
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index 8c7f93f93b6..1dc9b799a95 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -5,17 +5,17 @@
.gray-content-block
.pull-right
- if can?(current_user, :push_code, @project)
- = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn-grouped btn has_tooltip', title: 'Edit release notes' do
+ = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn-grouped btn has-tooltip', title: 'Edit release notes' do
= icon("pencil")
- = link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped has_tooltip', title: 'Browse files' do
+ = link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped has-tooltip', title: 'Browse files' do
= icon('files-o')
- = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped has_tooltip', title: 'Browse commits' do
+ = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped has-tooltip', title: 'Browse commits' do
= icon('history')
- if can? current_user, :download_code, @project
= render 'projects/tags/download', ref: @tag.name, project: @project
- if can?(current_user, :admin_project, @project)
.pull-right
- = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has_tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
+ = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
%i.fa.fa-trash-o
.title
%span.item-title= @tag.name
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index 3eb626e6dca..ba69569b1e7 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -15,7 +15,7 @@
- if current_user
%li
- if !on_top_of_branch?
- %span.btn.btn-sm.add-to-tree.disabled.has_tooltip{title: "You can only add files when you are on a branch", data: { container: 'body' }}
+ %span.btn.btn-sm.add-to-tree.disabled.has-tooltip{title: "You can only add files when you are on a branch", data: { container: 'body' }}
= icon('plus')
- else
%span.dropdown
diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml
index 45d700781f3..710f5613c81 100644
--- a/app/views/search/results/_issue.html.haml
+++ b/app/views/search/results/_issue.html.haml
@@ -1,5 +1,6 @@
.search-result-row
%h4
+ = confidential_icon(issue)
= link_to [issue.project.namespace.becomes(Namespace), issue.project, issue] do
%span.term.str-truncated= issue.title
.pull-right ##{issue.iid}
diff --git a/app/views/search/results/_milestone.html.haml b/app/views/search/results/_milestone.html.haml
index e0b18733d74..b31595d8d1c 100644
--- a/app/views/search/results/_milestone.html.haml
+++ b/app/views/search/results/_milestone.html.haml
@@ -6,4 +6,4 @@
- if milestone.description.present?
.description.term
= preserve do
- = search_md_sanitize(markdown(milestone.description)) \ No newline at end of file
+ = search_md_sanitize(markdown(milestone.description))
diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index faf7e49ed29..974751d9970 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -8,11 +8,9 @@
= icon('angle-down')
%ul.dropdown-menu.dropdown-menu-right.clone-options-dropdown
%li
- %a#ssh-selector{href: @project.ssh_url_to_repo}
- SSH
+ = ssh_clone_button(project)
%li
- %a#http-selector{href: @project.http_url_to_repo}
- HTTPS
+ = http_clone_button(project)
= text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true
.input-group-btn
diff --git a/app/views/shared/_group_tips.html.haml b/app/views/shared/_group_tips.html.haml
index e5cf783beb7..46e4340511a 100644
--- a/app/views/shared/_group_tips.html.haml
+++ b/app/views/shared/_group_tips.html.haml
@@ -1,6 +1,5 @@
%ul
%li A group is a collection of several projects
- %li Groups are private by default
%li Members of a group may only view projects they have permission to access
%li Group project URLs are prefixed with the group namespace
%li Existing projects may be moved into a group
diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml
index 8134b15d245..4b47b0291be 100644
--- a/app/views/shared/_label_row.html.haml
+++ b/app/views/shared/_label_row.html.haml
@@ -1,4 +1,4 @@
%span.label-row
- = link_to_label(label)
+ = link_to_label(label, tooltip: false)
%span.prepend-left-10
= markdown(label.description, pipeline: :single_line)
diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml
index fb9a8db0889..66b7ef99650 100644
--- a/app/views/shared/groups/_group.html.haml
+++ b/app/views/shared/groups/_group.html.haml
@@ -10,24 +10,28 @@
%i.fa.fa-cogs
= link_to leave_group_group_members_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-sm btn btn-grouped", title: 'Leave this group' do
- %i.fa.fa-sign-out
+ = icon('sign-out')
.stats
%span
- = icon('home')
+ = icon('bookmark')
= number_with_delimiter(group.projects.count)
%span
= icon('users')
= number_with_delimiter(group.users.count)
+ %span.visibility-icon.has_tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group)}
+ = visibility_level_icon(group.visibility_level, fw: false)
+
= image_tag group_icon(group), class: "avatar s40 hidden-xs"
- = link_to group, class: 'group-name title' do
- = group.name
+ .title
+ = link_to group, class: 'group-name' do
+ = group.name
- - if group_member
- as
- %span #{group_member.human_access}
+ - if group_member
+ as
+ %span #{group_member.human_access}
- if group.description.present?
.description
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index 3eb0db276b2..ac20f7d1f7e 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -9,75 +9,20 @@
.filter-item.inline
- if params[:author_id]
= hidden_field_tag(:author_id, params[:author_id])
- = dropdown_tag("Author", options: { toggle_class: "js-user-search js-filter-submit js-author-search", title: "Filter by author", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-author",
- placeholder: "Search authors", data: { any_user: "Any Author", first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: "author_id" } })
+ = dropdown_tag(user_dropdown_label(params[:author_id], "Author"), options: { toggle_class: "js-user-search js-filter-submit js-author-search", title: "Filter by author", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-author",
+ placeholder: "Search authors", data: { any_user: "Any Author", first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: "author_id", default_label: "Author" } })
.filter-item.inline
- if params[:assignee_id]
= hidden_field_tag(:assignee_id, params[:assignee_id])
- = dropdown_tag("Assignee", options: { toggle_class: "js-user-search js-filter-submit js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee",
- placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (@project.id if @project), selected: params[:assignee_id], field_name: "assignee_id" } })
+ = dropdown_tag(user_dropdown_label(params[:assignee_id], "Assignee"), options: { toggle_class: "js-user-search js-filter-submit js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee",
+ placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (@project.id if @project), selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } })
.filter-item.inline.milestone-filter
- - if params[:milestone_title]
- = hidden_field_tag(:milestone_title, params[:milestone_title])
- = dropdown_tag("Milestone", options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit', filter: true, dropdown_class: "dropdown-menu-selectable",
- placeholder: "Search milestones", footer_content: true, data: { show_no: true, show_any: true, field_name: "milestone_title", selected: params[:milestone_title], project_id: (@project.id if @project), milestones: (namespace_project_milestones_path(@project.namespace, @project, :js) if @project) } }) do
- - if @project
- %ul.dropdown-footer-list
- - if can? current_user, :admin_milestone, @project
- %li
- = link_to new_namespace_project_milestone_path(@project.namespace, @project), title: "New Milestone" do
- Create new
- %li
- = link_to namespace_project_milestones_path(@project.namespace, @project) do
- - if can? current_user, :admin_milestone, @project
- Manage milestones
- - else
- View milestones
+ = render "shared/issuable/milestone_dropdown"
.filter-item.inline.labels-filter
- - if params[:label_name]
- = hidden_field_tag(:label_name, params[:label_name])
- .dropdown
- %button.dropdown-menu-toggle.js-label-select.js-filter-submit{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: (@project.id if @project), labels: (namespace_project_labels_path(@project.namespace, @project, :js) if @project)}}
- %span.dropdown-toggle-text
- Label
- = icon('chevron-down')
- .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
- .dropdown-page-one
- = dropdown_title("Filter by label")
- = dropdown_filter("Search labels")
- = dropdown_content
- - if @project
- = dropdown_footer do
- %ul.dropdown-footer-list
- - if can? current_user, :admin_label, @project
- %li
- %a.dropdown-toggle-page{href: "#"}
- Create new
- %li
- = link_to namespace_project_labels_path(@project.namespace, @project) do
- - if can? current_user, :admin_label, @project
- Manage labels
- - else
- View labels
- - if can? current_user, :admin_label, @project
- .dropdown-page-two
- = dropdown_title("Create new label", back: true)
- = dropdown_content do
- %input#new_label_color{type: "hidden"}
- %input#new_label_name.dropdown-input-field{type: "text", placeholder: "Name new label"}
- .dropdown-label-color-preview.js-dropdown-label-color-preview
- .suggest-colors.suggest-colors-dropdown
- - suggested_colors.each do |color|
- = link_to '#', style: "background-color: #{color}", data: { color: color } do
- &nbsp
- %button.btn.btn-primary.js-new-label-btn{type: "button"}
- Create
- = dropdown_loading
- .dropdown-loading
- = icon('spinner spin')
+ = render "shared/issuable/label_dropdown"
.pull-right
= render 'shared/sort_dropdown'
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index d5a4aad05d9..b01a36265f9 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -13,12 +13,21 @@
- if issuable.is_a?(MergeRequest)
%p.help-block
- - if issuable.work_in_progress?
- Remove the <code>WIP</code> prefix from the title to allow this
- <strong>Work In Progress</strong> merge request to be merged when it's ready.
- - else
- Start the title with <code>[WIP]</code> or <code>WIP:</code> to prevent a
- <strong>Work In Progress</strong> merge request from being merged before it's ready.
+ .js-wip-explanation
+ %a.js-toggle-wip{href: ""}
+ Remove the
+ %code WIP:
+ prefix from the title
+ to allow this
+ %strong Work In Progress
+ merge request to be merged when it's ready.
+ .js-no-wip-explanation
+ %a.js-toggle-wip{href: ""}
+ Start the title with
+ %code WIP:
+ to prevent a
+ %strong Work In Progress
+ merge request from being merged before it's ready.
.form-group.detail-page-description
= f.label :description, 'Description', class: 'control-label'
.col-sm-10
@@ -29,6 +38,15 @@
= render 'projects/notes/hints'
.clearfix
.error-alert
+
+- if issuable.is_a?(Issue) && !issuable.project.private?
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :confidential do
+ = f.check_box :confidential
+ This issue is confidential and should only be visible to team members
+
- if can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project)
%hr
.form-group
@@ -67,13 +85,26 @@
- if can? current_user, :admin_label, issuable.project
= link_to 'Create new label', new_namespace_project_label_path(issuable.project.namespace, issuable.project), target: :blank
+- if issuable.can_move?(current_user)
+ %hr
+ .form-group
+ = label_tag :move_to_project_id, 'Move', class: 'control-label'
+ .col-sm-10
+ - projects = project_options(issuable, current_user, ability: :admin_issue)
+ = select_tag(:move_to_project_id, projects, include_blank: true,
+ class: 'select2', data: { placeholder: 'Select project' })
+ &nbsp;
+ %span{ data: { toggle: 'tooltip', placement: 'auto top' }, style: 'cursor: default',
+ title: 'Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location.' }
+ = icon('question-circle')
+
- if issuable.is_a?(MergeRequest)
%hr
- - if @merge_request.new_record?
- .form-group
- = f.label :source_branch, class: 'control-label'
- .col-sm-10
- = f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true })
+ - if @merge_request.new_record?
+ .form-group
+ = f.label :source_branch, class: 'control-label'
+ .col-sm-10
+ = f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true })
.form-group
= f.label :target_branch, class: 'control-label'
.col-sm-10
@@ -96,7 +127,12 @@
for this project.
- if issuable.new_record?
- - cancel_project = issuable.source_project
+ = link_to 'Cancel', namespace_project_issues_path(@project.namespace, @project), class: 'btn btn-cancel'
- else
- - cancel_project = issuable.project
- = link_to 'Cancel', [cancel_project.namespace.becomes(Namespace), cancel_project, issuable], class: 'btn btn-cancel'
+ .pull-right
+ - if current_user.can?(:"destroy_#{issuable.to_ability_name}", @project)
+ = link_to polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.class.name.titleize} will be removed! Are you sure?" },
+ method: :delete, class: 'btn btn-grouped' do
+ = icon('trash-o')
+ Delete
+ = link_to 'Cancel', namespace_project_issue_path(@project.namespace, @project, issuable), class: 'btn btn-grouped btn-cancel'
diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml
new file mode 100644
index 00000000000..87617315181
--- /dev/null
+++ b/app/views/shared/issuable/_label_dropdown.html.haml
@@ -0,0 +1,39 @@
+- if params[:label_name]
+ = hidden_field_tag(:label_name, params[:label_name])
+.dropdown
+ %button.dropdown-menu-toggle.js-label-select.js-filter-submit{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}}
+ %span.dropdown-toggle-text
+ = h(params[:label_name].presence || "Label")
+ = icon('chevron-down')
+ .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
+ .dropdown-page-one
+ = dropdown_title("Filter by label")
+ = dropdown_filter("Search labels")
+ = dropdown_content
+ - if @project
+ = dropdown_footer do
+ %ul.dropdown-footer-list
+ - if can? current_user, :admin_label, @project
+ %li
+ %a.dropdown-toggle-page{href: "#"}
+ Create new
+ %li
+ = link_to namespace_project_labels_path(@project.namespace, @project) do
+ - if can? current_user, :admin_label, @project
+ Manage labels
+ - else
+ View labels
+ - if can? current_user, :admin_label, @project and @project
+ .dropdown-page-two
+ = dropdown_title("Create new label", back: true)
+ = dropdown_content do
+ %input#new_label_color{type: "hidden"}
+ %input#new_label_name.dropdown-input-field{type: "text", placeholder: "Name new label"}
+ .dropdown-label-color-preview.js-dropdown-label-color-preview
+ .suggest-colors.suggest-colors-dropdown
+ - suggested_colors.each do |color|
+ = link_to '#', style: "background-color: #{color}", data: { color: color } do
+ &nbsp
+ %button.btn.btn-primary.js-new-label-btn{type: "button"}
+ Create
+ = dropdown_loading
diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml
new file mode 100644
index 00000000000..0434506c8d7
--- /dev/null
+++ b/app/views/shared/issuable/_milestone_dropdown.html.haml
@@ -0,0 +1,16 @@
+- if params[:milestone_title]
+ = hidden_field_tag(:milestone_title, params[:milestone_title])
+= dropdown_tag(h(params[:milestone_title].presence || "Milestone"), options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit', filter: true, dropdown_class: "dropdown-menu-selectable",
+ placeholder: "Search milestones", footer_content: @project.present?, data: { show_no: true, show_any: true, field_name: "milestone_title", selected: params[:milestone_title], project_id: @project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do
+ - if @project
+ %ul.dropdown-footer-list
+ - if can? current_user, :admin_milestone, @project
+ %li
+ = link_to new_namespace_project_milestone_path(@project.namespace, @project), title: "New Milestone" do
+ Create new
+ %li
+ = link_to namespace_project_milestones_path(@project.namespace, @project) do
+ - if can? current_user, :admin_milestone, @project
+ Manage milestones
+ - else
+ View milestones
diff --git a/app/views/shared/issuable/_participants.html.haml b/app/views/shared/issuable/_participants.html.haml
index f1d92ef48b2..3fb409ff727 100644
--- a/app/views/shared/issuable/_participants.html.haml
+++ b/app/views/shared/issuable/_participants.html.haml
@@ -1,3 +1,6 @@
+- participants_row = 7
+- participants_size = participants.size
+- participants_extra = participants_size - participants_row
.block.participants
.sidebar-collapsed-icon
= icon('users')
@@ -5,6 +8,13 @@
= participants.count
.title.hide-collapsed
= pluralize participants.count, "participant"
- - participants.each do |participant|
- %span.hide-collapsed
- = link_to_member(@project, participant, name: false, size: 24)
+ .hide-collapsed.participants-list
+ - participants.each do |participant|
+ .participants-author.js-participants-author
+ = link_to_member(@project, participant, name: false, size: 24)
+ - if participants_extra > 0
+ %div.participants-more
+ %a.js-participants-more{href: "#", data: {original_text: "+ #{participants_size - 7} more", less_text: "- show less"}}
+ + #{participants_extra} more
+:javascript
+ Issue.prototype.PARTICIPANTS_ROW_COUNT = #{participants_row};
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 23b1ed1e51b..2b95b19facc 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -1,13 +1,12 @@
%aside.right-sidebar{ class: sidebar_gutter_collapsed_class }
.issuable-sidebar
- .block
+ .block.issuable-sidebar-header
%span.issuable-count.hide-collapsed.pull-left
= issuable.iid
of
= issuables_count(issuable)
- %span.pull-right
- %a.gutter-toggle.js-sidebar-toggle{href: '#'}
- = sidebar_gutter_toggle_icon
+ %a.gutter-toggle.pull-right.js-sidebar-toggle{href: '#'}
+ = sidebar_gutter_toggle_icon
.issuable-nav.hide-collapsed.pull-right.btn-group{role: 'group', "aria-label" => '...'}
- if prev_issuable = prev_issuable_for(issuable)
= link_to 'Prev', [@project.namespace.becomes(Namespace), @project, prev_issuable], class: 'btn btn-default prev-btn'
@@ -22,20 +21,20 @@
= form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
.block.assignee
- .sidebar-collapsed-icon
+ .sidebar-collapsed-icon.sidebar-collapsed-user{data: {toggle: "tooltip", placement: "left", container: "body"}, title: (issuable.assignee.to_reference if issuable.assignee)}
- if issuable.assignee
- = link_to_member_avatar(issuable.assignee, size: 24)
+ = link_to_member(@project, issuable.assignee, size: 24)
- else
= icon('user')
.title.hide-collapsed
- %label
- Assignee
+ Assignee
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
- .pull-right
- = link_to 'Edit', '#', class: 'edit-link'
- .value.hide-collapsed
+ = link_to 'Edit', '#', class: 'edit-link pull-right'
+ .value.bold.hide-collapsed
- if issuable.assignee
- %strong= link_to_member(@project, issuable.assignee, size: 24)
+ = link_to_member(@project, issuable.assignee, size: 32) do
+ %span.username
+ = issuable.assignee.to_reference
- if issuable.instance_of?(MergeRequest) && !issuable.can_be_merged_by?(issuable.assignee)
%a.pull-right.cannot-be-merged{href: '#', data: {toggle: 'tooltip'}, title: 'Not allowed to merge'}
= icon('exclamation-triangle')
@@ -54,18 +53,13 @@
- else
No
.title.hide-collapsed
- %label
- Milestone
+ Milestone
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
- .pull-right
- = link_to 'Edit', '#', class: 'edit-link'
- .value.hide-collapsed
+ = link_to 'Edit', '#', class: 'edit-link pull-right'
+ .value.bold.hide-collapsed
- if issuable.milestone
- %span.back-to-milestone
- = link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do
- %strong
- = icon('clock-o')
- = issuable.milestone.title
+ = link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do
+ = issuable.milestone.title
- else
.light None
.selectbox.hide-collapsed
@@ -80,11 +74,10 @@
%span
= issuable.labels.count
.title.hide-collapsed
- %label Labels
+ Labels
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
- .pull-right
- = link_to 'Edit', '#', class: 'edit-link'
- .value.issuable-show-labels.hide-collapsed
+ = link_to 'Edit', '#', class: 'edit-link pull-right'
+ .value.issuable-show-labels.hide-collapsed{class: ("has-labels" if issuable.labels.any?)}
- if issuable.labels.any?
- issuable.labels.each do |label|
= link_to_label(label, type: issuable.to_ability_name)
@@ -95,14 +88,13 @@
{ selected: issuable.label_ids }, multiple: true, class: 'select2 js-select2', data: { placeholder: "Select labels" }
= render "shared/issuable/participants", participants: issuable.participants(current_user)
- %hr
- if current_user
- subscribed = issuable.subscribed?(current_user)
.block.light.subscription{data: {url: toggle_subscription_path(issuable)}}
.sidebar-collapsed-icon
= icon('rss')
.title.hide-collapsed
- %label.light Notifications
+ Notifications
- subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed'
%button.btn.btn-block.btn-gray.subscribe-button.hide-collapsed{:type => 'button'}
%span= subscribed ? 'Unsubscribe' : 'Subscribe'
diff --git a/app/views/shared/milestones/_issuable.html.haml b/app/views/shared/milestones/_issuable.html.haml
index f7c6fc14adf..e1127b2311c 100644
--- a/app/views/shared/milestones/_issuable.html.haml
+++ b/app/views/shared/milestones/_issuable.html.haml
@@ -10,6 +10,8 @@
%strong #{project.name} &middot;
- elsif show_full_project_name
%strong #{project.name_with_namespace} &middot;
+ - if issuable.is_a?(Issue)
+ = confidential_icon(issuable)
= link_to_gfm issuable.title, [project.namespace.becomes(Namespace), project, issuable], title: issuable.title
%div{class: 'issuable-detail'}
= link_to [project.namespace.becomes(Namespace), project, issuable] do
@@ -21,5 +23,5 @@
- if assignee
= link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, assignee_id: issuable.assignee_id, state: 'all' }),
- class: 'has_tooltip', data: { 'original-title' => "Assigned to #{sanitize(assignee.name)}", container: 'body' } do
+ class: 'has-tooltip', data: { 'original-title' => "Assigned to #{sanitize(assignee.name)}", container: 'body' } do
- image_tag(avatar_icon(issuable.assignee, 16), class: "avatar s16", alt: '')
diff --git a/app/views/shared/milestones/_labels_tab.html.haml b/app/views/shared/milestones/_labels_tab.html.haml
index ba27bafd1bc..868b2357003 100644
--- a/app/views/shared/milestones/_labels_tab.html.haml
+++ b/app/views/shared/milestones/_labels_tab.html.haml
@@ -5,7 +5,7 @@
%li
%span.label-row
= link_to milestones_label_path(options) do
- - render_colored_label(label)
+ - render_colored_label(label, tooltip: false)
%span.prepend-left-10
= markdown(label.description, pipeline: :single_line)
diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml
index f01138af3f0..6b25745c554 100644
--- a/app/views/shared/milestones/_milestone.html.haml
+++ b/app/views/shared/milestones/_milestone.html.haml
@@ -6,10 +6,10 @@
.col-sm-6
%strong= link_to_gfm truncate(milestone.title, length: 100), milestone_path
.col-sm-6
- .pull-right.light #{milestone.percent_complete}% complete
+ .pull-right.light #{milestone.percent_complete(current_user)}% complete
.row
.col-sm-6
- = link_to pluralize(milestone.issues.size, 'Issue'), issues_path
+ = link_to pluralize(milestone.issues_visible_to_user(current_user).size, 'Issue'), issues_path
&middot;
= link_to pluralize(milestone.merge_requests.size, 'Merge Request'), merge_requests_path
.col-sm-6= milestone_progress_bar(milestone)
diff --git a/app/views/shared/milestones/_summary.html.haml b/app/views/shared/milestones/_summary.html.haml
index 59d4ae29f79..385c6596606 100644
--- a/app/views/shared/milestones/_summary.html.haml
+++ b/app/views/shared/milestones/_summary.html.haml
@@ -3,15 +3,15 @@
.context.prepend-top-default
.milestone-summary
%h4 Progress
- %strong= milestone.issues.size
+ %strong= milestone.issues_visible_to_user(current_user).size
issues:
%span.milestone-stat
- %strong= milestone.issues.opened.size
+ %strong= milestone.issues_visible_to_user(current_user).opened.size
open and
- %strong= milestone.issues.closed.size
+ %strong= milestone.issues_visible_to_user(current_user).closed.size
closed
%span.milestone-stat
- %strong== #{milestone.percent_complete}%
+ %strong== #{milestone.percent_complete(current_user)}%
complete
%span.milestone-stat
diff --git a/app/views/shared/milestones/_tabs.html.haml b/app/views/shared/milestones/_tabs.html.haml
index 57d7ee85a3b..2b6ce2d7e7a 100644
--- a/app/views/shared/milestones/_tabs.html.haml
+++ b/app/views/shared/milestones/_tabs.html.haml
@@ -2,7 +2,7 @@
%li.active
= link_to '#tab-issues', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do
Issues
- %span.badge= milestone.issues.size
+ %span.badge= milestone.issues_visible_to_user(current_user).size
%li
= link_to '#tab-merge-requests', 'data-toggle' => 'tab', 'data-show' => '.tab-merge-requests-buttons' do
Merge Requests
@@ -21,7 +21,7 @@
.tab-content.milestone-content
.tab-pane.active#tab-issues
- = render 'shared/milestones/issues_tab', issues: milestone.issues, show_project_name: show_project_name, show_full_project_name: show_full_project_name
+ = render 'shared/milestones/issues_tab', issues: milestone.issues_visible_to_user(current_user), show_project_name: show_project_name, show_full_project_name: show_full_project_name
.tab-pane#tab-merge-requests
= render 'shared/milestones/merge_requests_tab', merge_requests: milestone.merge_requests, show_project_name: show_project_name, show_full_project_name: show_full_project_name
.tab-pane#tab-participants
diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml
index 4cf1d948b5b..cab8743a077 100644
--- a/app/views/shared/milestones/_top.html.haml
+++ b/app/views/shared/milestones/_top.html.haml
@@ -28,7 +28,7 @@
%h2.title
= markdown escape_once(milestone.title), pipeline: :single_line
-- if milestone.complete? && milestone.active?
+- if milestone.complete?(current_user) && milestone.active?
.alert.alert-success.prepend-top-default
- close_msg = group ? 'You may close the milestone now.' : 'Navigate to the project to close the milestone.'
%span All issues for this milestone are closed. #{close_msg}
@@ -47,7 +47,7 @@
- project_name = group ? ms.project.name : ms.project.name_with_namespace
= link_to project_name, namespace_project_milestone_path(ms.project.namespace, ms.project, ms)
%td
- = ms.issues.opened.count
+ = ms.issues_visible_to_user(current_user).opened.count
%td
- if ms.closed?
Closed
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 97cfb76cdb0..803dd95bc65 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -7,26 +7,11 @@
- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && project.commit
- css_class += " no-description" if project.description.blank? && !show_last_commit_as_description
- ci_commit = project.ci_commit(project.commit.sha) if ci && !project.empty_repo? && project.commit
-- cache_key = [project.namespace, project, controller.controller_name, controller.action_name, current_application_settings, 'v2.2']
+- cache_key = [project.namespace, project, controller.controller_name, controller.action_name, current_application_settings, 'v2.3']
- cache_key.push(ci_commit.status) if ci_commit
%li.project-row{ class: css_class }
= cache(cache_key) do
- = link_to project_path(project), class: dom_class(project) do
- - if avatar
- .dash-project-avatar
- - if use_creator_avatar
- = image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:''
- - else
- = project_icon(project, alt: '', class: 'avatar project-avatar s40')
- %span.project-full-name.title
- %span.namespace-name
- - if project.namespace && !skip_namespace
- = project.namespace.human_name
- \/
- %span.project-name.filter-title
- = project.name
-
.controls
- if project.main_language
%span
@@ -42,9 +27,25 @@
%span
= icon('star')
= project.star_count
- %span.visibility-icon.has_tooltip{data: { container: 'body', placement: 'left' },
- title: "#{visibility_level_label(project.visibility_level)} - #{project_visibility_level_description(project.visibility_level)}"}
+ %span.visibility-icon.has_tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(project)}
= visibility_level_icon(project.visibility_level, fw: false)
+
+ .title
+ = link_to project_path(project), class: dom_class(project) do
+ - if avatar
+ .dash-project-avatar
+ - if use_creator_avatar
+ = image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:''
+ - else
+ = project_icon(project, alt: '', class: 'avatar project-avatar s40')
+ %span.project-full-name
+ %span.namespace-name
+ - if project.namespace && !skip_namespace
+ = project.namespace.human_name
+ \/
+ %span.project-name.filter-title
+ = project.name
+
- if show_last_commit_as_description
.description
= link_to_gfm project.commit.title, namespace_project_commit_path(project.namespace, project, project.commit),
diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml
index aa5acee9c14..3c445f67236 100644
--- a/app/views/shared/snippets/_header.html.haml
+++ b/app/views/shared/snippets/_header.html.haml
@@ -1,5 +1,5 @@
.detail-page-header
- .snippet-box.has_tooltip{class: visibility_level_color(@snippet.visibility_level), title: snippet_visibility_level_description(@snippet.visibility_level, @snippet), data: { container: 'body' }}
+ .snippet-box.has-tooltip{class: visibility_level_color(@snippet.visibility_level), title: snippet_visibility_level_description(@snippet.visibility_level, @snippet), data: { container: 'body' }}
= visibility_level_icon(@snippet.visibility_level, fw: false)
= visibility_level_label(@snippet.visibility_level)
%span.identifier
diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml
index a316a085107..c96dfefe17f 100644
--- a/app/views/shared/snippets/_snippet.html.haml
+++ b/app/views/shared/snippets/_snippet.html.haml
@@ -1,8 +1,8 @@
%li.snippet-row
= image_tag avatar_icon(snippet.author_email), class: "avatar s40 hidden-xs", alt: ''
- .snippet-title
- = link_to reliable_snippet_path(snippet), class: 'title' do
+ .title
+ = link_to reliable_snippet_path(snippet) do
= truncate(snippet.title, length: 60)
- if snippet.private?
%span.label.label-gray
diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml
index 20d2d5f317b..02647229776 100644
--- a/app/views/votes/_votes_block.html.haml
+++ b/app/views/votes/_votes_block.html.haml
@@ -1,6 +1,6 @@
.awards.votes-block
- awards_sort(votable.notes.awards.grouped_awards).each do |emoji, notes|
- %button.btn.award-control.js-emoji-btn.has_tooltip{class: (note_active_class(notes, current_user)), title: emoji_author_list(notes, current_user), data: {placement: "top"}}
+ %button.btn.award-control.js-emoji-btn.has-tooltip{class: (note_active_class(notes, current_user)), title: emoji_author_list(notes, current_user), data: {placement: "top"}}
= emoji_icon(emoji)
%span.award-control-text.js-counter
= notes.count
diff --git a/app/workers/gitlab_shell_one_shot_worker.rb b/app/workers/gitlab_shell_one_shot_worker.rb
new file mode 100644
index 00000000000..4ddbcf574d5
--- /dev/null
+++ b/app/workers/gitlab_shell_one_shot_worker.rb
@@ -0,0 +1,10 @@
+class GitlabShellOneShotWorker
+ include Sidekiq::Worker
+ include Gitlab::ShellAdapter
+
+ sidekiq_options queue: :gitlab_shell, retry: false
+
+ def perform(action, *arg)
+ gitlab_shell.send(action, *arg)
+ end
+end
diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb
index 21d311579e3..f9e32337983 100644
--- a/app/workers/repository_fork_worker.rb
+++ b/app/workers/repository_fork_worker.rb
@@ -20,14 +20,15 @@ class RepositoryForkWorker
return
end
+ project.repository.after_import
+
unless project.valid_repo?
- logger.error("Project #{id} had an invalid repository after fork")
+ logger.error("Project #{project_id} had an invalid repository after fork")
project.update(import_error: "The forked repository is invalid.")
project.import_fail
return
end
- project.repository.after_import
project.import_finish
end
end
diff --git a/config/application.rb b/config/application.rb
index 2b103c4592d..5a0ac70aa2a 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -49,6 +49,7 @@ module Gitlab
config.assets.paths << Gemojione.index.images_path
config.assets.precompile << "*.png"
config.assets.precompile << "print.css"
+ config.assets.precompile << "notify.css"
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'
diff --git a/config/initializers/premailer.rb b/config/initializers/premailer.rb
new file mode 100644
index 00000000000..a44316bc3a4
--- /dev/null
+++ b/config/initializers/premailer.rb
@@ -0,0 +1,7 @@
+# See https://github.com/fphilipe/premailer-rails#configuration
+Premailer::Rails.config.merge!(
+ generate_text_part: false,
+ preserve_styles: true,
+ remove_comments: true,
+ remove_ids: true
+)
diff --git a/config/routes.rb b/config/routes.rb
index 2ae282f48a6..90d858d7fc1 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -351,6 +351,8 @@ Rails.application.routes.draw do
get :issues
get :merge_requests
get :activity
+ get :labels
+ get :milestones
scope module: :dashboard do
resources :milestones, only: [:index, :show]
@@ -611,7 +613,7 @@ Rails.application.routes.draw do
end
end
- resources :merge_requests, constraints: { id: /\d+/ }, except: [:destroy] do
+ resources :merge_requests, constraints: { id: /\d+/ } do
member do
get :commits
get :diffs
@@ -621,6 +623,7 @@ Rails.application.routes.draw do
post :cancel_merge_when_build_succeeds
get :ci_status
post :toggle_subscription
+ post :remove_wip
end
collection do
@@ -681,7 +684,7 @@ Rails.application.routes.draw do
end
end
- resources :issues, constraints: { id: /\d+/ }, except: [:destroy] do
+ resources :issues, constraints: { id: /\d+/ } do
member do
post :toggle_subscription
end
diff --git a/db/migrate/20160223192159_add_confidential_to_issues.rb b/db/migrate/20160223192159_add_confidential_to_issues.rb
new file mode 100644
index 00000000000..e9d47fd589a
--- /dev/null
+++ b/db/migrate/20160223192159_add_confidential_to_issues.rb
@@ -0,0 +1,6 @@
+class AddConfidentialToIssues < ActiveRecord::Migration
+ def change
+ add_column :issues, :confidential, :boolean, default: false
+ add_index :issues, :confidential
+ end
+end
diff --git a/db/migrate/20160225090018_add_delete_at_to_issues.rb b/db/migrate/20160225090018_add_delete_at_to_issues.rb
new file mode 100644
index 00000000000..3ddbef92978
--- /dev/null
+++ b/db/migrate/20160225090018_add_delete_at_to_issues.rb
@@ -0,0 +1,6 @@
+class AddDeleteAtToIssues < ActiveRecord::Migration
+ def change
+ add_column :issues, :deleted_at, :datetime
+ add_index :issues, :deleted_at
+ end
+end
diff --git a/db/migrate/20160225101956_add_delete_at_to_merge_requests.rb b/db/migrate/20160225101956_add_delete_at_to_merge_requests.rb
new file mode 100644
index 00000000000..9d09105f17d
--- /dev/null
+++ b/db/migrate/20160225101956_add_delete_at_to_merge_requests.rb
@@ -0,0 +1,6 @@
+class AddDeleteAtToMergeRequests < ActiveRecord::Migration
+ def change
+ add_column :merge_requests, :deleted_at, :datetime
+ add_index :merge_requests, :deleted_at
+ end
+end
diff --git a/db/migrate/20160301124843_add_visibility_level_to_groups.rb b/db/migrate/20160301124843_add_visibility_level_to_groups.rb
new file mode 100644
index 00000000000..d1b921bb208
--- /dev/null
+++ b/db/migrate/20160301124843_add_visibility_level_to_groups.rb
@@ -0,0 +1,29 @@
+class AddVisibilityLevelToGroups < ActiveRecord::Migration
+ def up
+ add_column :namespaces, :visibility_level, :integer, null: false, default: Gitlab::VisibilityLevel::PUBLIC
+ add_index :namespaces, :visibility_level
+
+ # Unfortunately, this is needed on top of the `default`, since we don't want the configuration specific
+ # `allowed_visibility_level` to end up in schema.rb
+ if allowed_visibility_level < Gitlab::VisibilityLevel::PUBLIC
+ execute("UPDATE namespaces SET visibility_level = #{allowed_visibility_level}")
+ end
+ end
+
+ def down
+ remove_column :namespaces, :visibility_level
+ end
+
+ private
+
+ def allowed_visibility_level
+ application_settings = select_one("SELECT restricted_visibility_levels FROM application_settings ORDER BY id DESC LIMIT 1")
+ if application_settings
+ restricted_visibility_levels = YAML.safe_load(application_settings["restricted_visibility_levels"]) rescue nil
+ end
+ restricted_visibility_levels ||= []
+
+ allowed_levels = Gitlab::VisibilityLevel.values - restricted_visibility_levels
+ allowed_levels.max
+ end
+end
diff --git a/db/migrate/20160308212903_add_default_group_visibility_to_application_settings.rb b/db/migrate/20160308212903_add_default_group_visibility_to_application_settings.rb
new file mode 100644
index 00000000000..75de5f70fa2
--- /dev/null
+++ b/db/migrate/20160308212903_add_default_group_visibility_to_application_settings.rb
@@ -0,0 +1,29 @@
+# Create visibility level field on DB
+# Sets default_visibility_level to value on settings if not restricted
+# If value is restricted takes higher visibility level allowed
+
+class AddDefaultGroupVisibilityToApplicationSettings < ActiveRecord::Migration
+ def up
+ add_column :application_settings, :default_group_visibility, :integer
+ # Unfortunately, this can't be a `default`, since we don't want the configuration specific
+ # `allowed_visibility_level` to end up in schema.rb
+ execute("UPDATE application_settings SET default_group_visibility = #{allowed_visibility_level}")
+ end
+
+ def down
+ remove_column :application_settings, :default_group_visibility
+ end
+
+ private
+
+ def allowed_visibility_level
+ application_settings = select_one("SELECT restricted_visibility_levels FROM application_settings ORDER BY id DESC LIMIT 1")
+ if application_settings
+ restricted_visibility_levels = YAML.safe_load(application_settings["restricted_visibility_levels"]) rescue nil
+ end
+ restricted_visibility_levels ||= []
+
+ allowed_levels = Gitlab::VisibilityLevel.values - restricted_visibility_levels
+ allowed_levels.max
+ end
+end
diff --git a/db/migrate/20160316192622_change_target_id_to_null_on_todos.rb b/db/migrate/20160316192622_change_target_id_to_null_on_todos.rb
new file mode 100644
index 00000000000..6871b3920df
--- /dev/null
+++ b/db/migrate/20160316192622_change_target_id_to_null_on_todos.rb
@@ -0,0 +1,5 @@
+class ChangeTargetIdToNullOnTodos < ActiveRecord::Migration
+ def change
+ change_column_null :todos, :target_id, true
+ end
+end
diff --git a/db/migrate/20160316204731_add_commit_id_to_todos.rb b/db/migrate/20160316204731_add_commit_id_to_todos.rb
new file mode 100644
index 00000000000..ae19fdd1abd
--- /dev/null
+++ b/db/migrate/20160316204731_add_commit_id_to_todos.rb
@@ -0,0 +1,6 @@
+class AddCommitIdToTodos < ActiveRecord::Migration
+ def change
+ add_column :todos, :commit_id, :string
+ add_index :todos, :commit_id
+ end
+end
diff --git a/db/migrate/20160317092222_add_moved_to_to_issue.rb b/db/migrate/20160317092222_add_moved_to_to_issue.rb
new file mode 100644
index 00000000000..461e7fb3a9b
--- /dev/null
+++ b/db/migrate/20160317092222_add_moved_to_to_issue.rb
@@ -0,0 +1,5 @@
+class AddMovedToToIssue < ActiveRecord::Migration
+ def change
+ add_reference :issues, :moved_to, references: :issues
+ end
+end
diff --git a/db/migrate/20160320204112_index_namespaces_on_visibility_level.rb b/db/migrate/20160320204112_index_namespaces_on_visibility_level.rb
new file mode 100644
index 00000000000..370b339d45c
--- /dev/null
+++ b/db/migrate/20160320204112_index_namespaces_on_visibility_level.rb
@@ -0,0 +1,7 @@
+class IndexNamespacesOnVisibilityLevel < ActiveRecord::Migration
+ def change
+ unless index_exists?(:namespaces, :visibility_level)
+ add_index :namespaces, :visibility_level
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 2f075677b30..dce2bfe62ca 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: 20160316123110) do
+ActiveRecord::Schema.define(version: 20160320204112) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -77,6 +77,7 @@ ActiveRecord::Schema.define(version: 20160316123110) do
t.boolean "akismet_enabled", default: false
t.string "akismet_api_key"
t.boolean "email_author_in_body", default: false
+ t.integer "default_group_visibility"
end
create_table "audit_events", force: :cascade do |t|
@@ -416,12 +417,17 @@ ActiveRecord::Schema.define(version: 20160316123110) do
t.string "state"
t.integer "iid"
t.integer "updated_by_id"
+ t.integer "moved_to_id"
+ t.boolean "confidential", default: false
+ t.datetime "deleted_at"
end
add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree
add_index "issues", ["author_id"], name: "index_issues_on_author_id", using: :btree
+ add_index "issues", ["confidential"], name: "index_issues_on_confidential", using: :btree
add_index "issues", ["created_at", "id"], name: "index_issues_on_created_at_and_id", using: :btree
add_index "issues", ["created_at"], name: "index_issues_on_created_at", using: :btree
+ add_index "issues", ["deleted_at"], name: "index_issues_on_deleted_at", using: :btree
add_index "issues", ["description"], name: "index_issues_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
add_index "issues", ["milestone_id"], name: "index_issues_on_milestone_id", using: :btree
add_index "issues", ["project_id", "iid"], name: "index_issues_on_project_id_and_iid", unique: true, using: :btree
@@ -544,12 +550,14 @@ ActiveRecord::Schema.define(version: 20160316123110) do
t.boolean "merge_when_build_succeeds", default: false, null: false
t.integer "merge_user_id"
t.string "merge_commit_sha"
+ t.datetime "deleted_at"
end
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
add_index "merge_requests", ["author_id"], name: "index_merge_requests_on_author_id", using: :btree
add_index "merge_requests", ["created_at", "id"], name: "index_merge_requests_on_created_at_and_id", using: :btree
add_index "merge_requests", ["created_at"], name: "index_merge_requests_on_created_at", using: :btree
+ add_index "merge_requests", ["deleted_at"], name: "index_merge_requests_on_deleted_at", using: :btree
add_index "merge_requests", ["description"], name: "index_merge_requests_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
add_index "merge_requests", ["milestone_id"], name: "index_merge_requests_on_milestone_id", using: :btree
add_index "merge_requests", ["source_branch"], name: "index_merge_requests_on_source_branch", using: :btree
@@ -588,6 +596,7 @@ ActiveRecord::Schema.define(version: 20160316123110) do
t.string "description", default: "", null: false
t.string "avatar"
t.boolean "share_with_group_lock", default: false
+ t.integer "visibility_level", default: 20, null: false
end
add_index "namespaces", ["created_at", "id"], name: "index_namespaces_on_created_at_and_id", using: :btree
@@ -597,6 +606,7 @@ ActiveRecord::Schema.define(version: 20160316123110) do
add_index "namespaces", ["path"], name: "index_namespaces_on_path", unique: true, using: :btree
add_index "namespaces", ["path"], name: "index_namespaces_on_path_trigram", using: :gin, opclasses: {"path"=>"gin_trgm_ops"}
add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree
+ add_index "namespaces", ["visibility_level"], name: "index_namespaces_on_visibility_level", using: :btree
create_table "notes", force: :cascade do |t|
t.text "note"
@@ -865,7 +875,7 @@ ActiveRecord::Schema.define(version: 20160316123110) do
create_table "todos", force: :cascade do |t|
t.integer "user_id", null: false
t.integer "project_id", null: false
- t.integer "target_id", null: false
+ t.integer "target_id"
t.string "target_type", null: false
t.integer "author_id"
t.integer "action", null: false
@@ -873,9 +883,11 @@ ActiveRecord::Schema.define(version: 20160316123110) do
t.datetime "created_at"
t.datetime "updated_at"
t.integer "note_id"
+ t.string "commit_id"
end
add_index "todos", ["author_id"], name: "index_todos_on_author_id", using: :btree
+ add_index "todos", ["commit_id"], name: "index_todos_on_commit_id", using: :btree
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", ["state"], name: "index_todos_on_state", using: :btree
diff --git a/doc/README.md b/doc/README.md
index db19c3de8d1..08d0a6a5bfb 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -3,6 +3,7 @@
## User documentation
- [API](api/README.md) Automate GitLab via a simple and powerful API.
+- [CI](ci/README.md)
- [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab.
- [GitLab Basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab.
- [Importing to GitLab](workflow/importing/README.md).
@@ -16,42 +17,6 @@
- [Webhooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project.
- [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN.
-## CI User documentation
-
-- [Get started with GitLab CI](ci/quick_start/README.md)
-- [Learn how to enable or disable GitLab CI](ci/enable_or_disable_ci.md)
-- [Learn how `.gitlab-ci.yml` works](ci/yaml/README.md)
-- [Configure a Runner, the application that runs your builds](ci/runners/README.md)
-- [Use Docker images with GitLab Runner](ci/docker/using_docker_images.md)
-- [Use CI to build Docker images](ci/docker/using_docker_build.md)
-- [Use variables in your `.gitlab-ci.yml`](ci/variables/README.md)
-- [Use SSH keys in your build environment](ci/ssh_keys/README.md)
-- [Trigger builds through the API](ci/triggers/README.md)
-- [Build artifacts](ci/build_artifacts/README.md)
-- [User permissions](ci/permissions/README.md)
-- [API](ci/api/README.md)
-
-### CI Examples
-
-- [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
-- [Test your PHP applications](ci/examples/php.md)
-- [Test and deploy Ruby applications to Heroku](ci/examples/test-and-deploy-ruby-application-to-heroku.md)
-- [Test and deploy Python applications to Heroku](ci/examples/test-and-deploy-python-application-to-heroku.md)
-- [Test Clojure applications](ci/examples/test-clojure-application.md)
-- [Using `dpl` as deployment tool](ci/deployment/README.md)
-- Help your favorite programming language and GitLab by sending a merge request
- with a guide for that language.
-
-### CI Services
-
-GitLab CI uses the `services` keyword to define what docker containers should
-be linked with your base image. Below is a list of examples you may use:
-
-- [Using MySQL](ci/services/mysql.md)
-- [Using PostgreSQL](ci/services/postgres.md)
-- [Using Redis](ci/services/redis.md)
-- [Using Other Services](ci/docker/using_docker_images.md#how-to-use-other-images-as-services)
-
## Administrator documentation
- [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when webhooks aren't enough.
diff --git a/doc/api/groups.md b/doc/api/groups.md
index d47e79ba47f..d1b5c9f5f04 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -111,6 +111,7 @@ Parameters:
- `name` (required) - The name of the group
- `path` (required) - The path of the group
- `description` (optional) - The group's description
+- `visibility_level` (optional) - The group's visibility. 0 for private, 10 for internal, 20 for public.
## Transfer project to group
diff --git a/doc/api/issues.md b/doc/api/issues.md
index 9e704648b25..18d64c41986 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -326,17 +326,25 @@ Example response:
}
```
-## Delete existing issue (**Deprecated**)
+## Delete an issue
-This call is deprecated and returns a `405 Method Not Allowed` error if called.
-An issue gets now closed and is done by calling
-`PUT /projects/:id/issues/:issue_id` with the parameter `state_event` set to
-`close`. See [edit issue](#edit-issue) for more details.
+Only for admins and project owners. Soft deletes the issue in question.
+If the operation is successful, a status code `200` is returned. In case you cannot
+destroy this issue, or it is not present, code `404` is given.
```
DELETE /projects/:id/issues/:issue_id
```
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `issue_id` | integer | yes | The ID of a project's issue |
+
+```bash
+curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/85
+```
+
## Comments on issues
Comments are done via the [notes](notes.md) resource.
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 5c527d55481..b20a6300b7a 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -380,6 +380,25 @@ Parameters:
If the operation is successful, 200 and the updated merge request is returned.
If an error occurs, an error number and a message explaining the reason is returned.
+## Delete a merge request
+
+Only for admins and project owners. Soft deletes the merge request in question.
+If the operation is successful, a status code `200` is returned. In case you cannot
+destroy this merge request, or it is not present, code `404` is given.
+
+```
+DELETE /projects/:id/merge_requests/:merge_request_id
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `merge_request_id` | integer | yes | The ID of a project's merge request |
+
+```bash
+curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/merge_request/85
+```
+
## Accept MR
Merge changes submitted with MR using this API.
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index a9b79bbdb1b..4316f3c1f64 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -279,6 +279,8 @@ job_name:
| Keyword | Required | Description |
|---------------|----------|-------------|
| script | yes | Defines a shell script which is executed by runner |
+| image | no | Use docker image, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) |
+| services | no | Use docker services, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) |
| stage | no | Defines a build stage (default: `test`) |
| type | no | Alias for `stage` |
| only | no | Defines a list of git refs for which build is created |
@@ -329,7 +331,7 @@ There are a few rules that apply to the usage of refs policy:
* `only` and `except` are inclusive. If both `only` and `except` are defined
in a job specification, the ref is filtered by `only` and `except`.
* `only` and `except` allow the use of regular expressions.
-* `only` and `except` allow the use of special keywords: `branches` and `tags`.
+* `only` and `except` allow the use of special keywords: `branches`, `tags`, and `triggers`.
* `only` and `except` allow to specify a repository path to filter jobs for
forks.
@@ -346,6 +348,17 @@ job:
- branches
```
+In this example, `job` will run only for refs that are tagged, or if a build is explicitly requested
+via an API trigger.
+
+```yaml
+job:
+ # use special keywords
+ only:
+ - tags
+ - triggers
+```
+
The repository path can be used to have jobs executed only for the parent
repository and not forks:
diff --git a/doc/gitlab-basics/README.md b/doc/gitlab-basics/README.md
index 493e1d1b09c..3aa83975ace 100644
--- a/doc/gitlab-basics/README.md
+++ b/doc/gitlab-basics/README.md
@@ -2,26 +2,14 @@
Step-by-step guides on the basics of working with Git and GitLab.
-* [Start using Git on the command line](start-using-git.md)
-
-* [Create and add your SSH Keys](create-your-ssh-keys.md)
-
-* [Command Line basic commands](command-line-commands.md)
-
-* [Basic Git commands](basic-git-commands.md)
-
-* [Create a project](create-project.md)
-
-* [Create a group](create-group.md)
-
-* [Create a branch](create-branch.md)
-
-* [Fork a project](fork-project.md)
-
-* [Add a file](add-file.md)
-
-* [Add an image](add-image.md)
-
-* [Create a Merge Request](add-merge-request.md)
-
-* [Create an Issue](create-issue.md)
+- [Start using Git on the command line](start-using-git.md)
+- [Create and add your SSH Keys](create-your-ssh-keys.md)
+- [Command Line basics](command-line-commands.md)
+- [Create a project](create-project.md)
+- [Create a group](create-group.md)
+- [Create a branch](create-branch.md)
+- [Fork a project](fork-project.md)
+- [Add a file](add-file.md)
+- [Add an image](add-image.md)
+- [Create a Merge Request](add-merge-request.md)
+- [Create an Issue](create-issue.md)
diff --git a/doc/gitlab-basics/basic-git-commands.md b/doc/gitlab-basics/basic-git-commands.md
index 2b5767dd2d3..c2a3415cbc4 100644
--- a/doc/gitlab-basics/basic-git-commands.md
+++ b/doc/gitlab-basics/basic-git-commands.md
@@ -1,59 +1,3 @@
# Basic Git commands
-### Go to the master branch to pull the latest changes from there
-```
-git checkout master
-```
-
-### Download the latest changes in the project
-This is for you to work on an up-to-date copy (it is important to do every time you work on a project), while you setup tracking branches.
-```
-git pull REMOTE NAME-OF-BRANCH -u
-```
-(REMOTE: origin) (NAME-OF-BRANCH: could be "master" or an existing branch)
-
-### Create a branch
-Spaces won't be recognized, so you need to use a hyphen or underscore.
-```
-git checkout -b NAME-OF-BRANCH
-```
-
-### Work on a branch that has already been created
-```
-git checkout NAME-OF-BRANCH
-```
-
-### View the changes you've made
-It's important to be aware of what's happening and what's the status of your changes.
-```
-git status
-```
-
-### Add changes to commit
-You'll see your changes in red when you type "git status".
-```
-git add CHANGES IN RED
-git commit -m "DESCRIBE THE INTENTION OF THE COMMIT"
-```
-
-### Send changes to gitlab.com
-```
-git push REMOTE NAME-OF-BRANCH
-```
-
-### Delete all changes in the Git repository, but leave unstaged things
-```
-git checkout .
-```
-
-### Delete all changes in the Git repository, including untracked files
-```
-git clean -f
-```
-
-### Merge created branch with master branch
-You need to be in the created branch.
-```
-git checkout NAME-OF-BRANCH
-git merge master
-```
+This section is now merged into [Start using Git](start-using-git.md).
diff --git a/doc/gitlab-basics/start-using-git.md b/doc/gitlab-basics/start-using-git.md
index b2ceda025c0..89ce8bcc3e8 100644
--- a/doc/gitlab-basics/start-using-git.md
+++ b/doc/gitlab-basics/start-using-git.md
@@ -1,6 +1,7 @@
# Start using Git on the command line
-If you want to start using a Git and GitLab, make sure that you have created an account on GitLab.
+If you want to start using a Git and GitLab, make sure that you have created an
+account on GitLab.
## Open a shell
@@ -59,3 +60,63 @@ To view the information that you entered, type:
```
git config --global --list
```
+## Basic Git commands
+
+### Go to the master branch to pull the latest changes from there
+
+```
+git checkout master
+```
+
+### Download the latest changes in the project
+This is for you to work on an up-to-date copy (it is important to do every time you work on a project), while you setup tracking branches.
+```
+git pull REMOTE NAME-OF-BRANCH -u
+```
+(REMOTE: origin) (NAME-OF-BRANCH: could be "master" or an existing branch)
+
+### Create a branch
+Spaces won't be recognized, so you need to use a hyphen or underscore.
+```
+git checkout -b NAME-OF-BRANCH
+```
+
+### Work on a branch that has already been created
+```
+git checkout NAME-OF-BRANCH
+```
+
+### View the changes you've made
+It's important to be aware of what's happening and what's the status of your changes.
+```
+git status
+```
+
+### Add changes to commit
+You'll see your changes in red when you type "git status".
+```
+git add CHANGES IN RED
+git commit -m "DESCRIBE THE INTENTION OF THE COMMIT"
+```
+
+### Send changes to gitlab.com
+```
+git push REMOTE NAME-OF-BRANCH
+```
+
+### Delete all changes in the Git repository, but leave unstaged things
+```
+git checkout .
+```
+
+### Delete all changes in the Git repository, including untracked files
+```
+git clean -f
+```
+
+### Merge created branch with master branch
+You need to be in the created branch.
+```
+git checkout NAME-OF-BRANCH
+git merge master
+```
diff --git a/doc/hooks/custom_hooks.md b/doc/hooks/custom_hooks.md
index 15051dd76f9..dcdf49d3379 100644
--- a/doc/hooks/custom_hooks.md
+++ b/doc/hooks/custom_hooks.md
@@ -2,7 +2,7 @@
**Note: Custom git hooks must be configured on the filesystem of the GitLab
server. Only GitLab server administrators will be able to complete these tasks.
-Please explore [webhooks](doc/web_hooks/web_hooks.md) as an option if you do not have filesystem access. For a user configurable Git Hooks interface, please see [GitLab Enterprise Edition Git Hooks](http://doc.gitlab.com/ee/git_hooks/git_hooks.html).**
+Please explore [webhooks](../web_hooks/web_hooks.md) as an option if you do not have filesystem access. For a user configurable Git Hooks interface, please see [GitLab Enterprise Edition Git Hooks](http://doc.gitlab.com/ee/git_hooks/git_hooks.html).**
Git natively supports hooks that are executed on different actions.
Examples of server-side git hooks include pre-receive, post-receive, and update.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index aa989417c4b..bffbc776500 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -76,7 +76,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.7.3 or higher
+ # Make sure Git is version 2.7.4 or higher
git --version
Is the system packaged Git too old? Remove it and compile from source.
@@ -89,9 +89,9 @@ Is the system packaged Git too old? Remove it and compile from source.
# Download and compile from source
cd /tmp
- curl -O --progress https://www.kernel.org/pub/software/scm/git/git-2.7.3.tar.gz
- echo '30d067499b61caddedaf1a407b4947244f14d10842d100f7c7c6ea1c288280cd git-2.7.3.tar.gz' | shasum -a256 -c - && tar -xzf git-2.7.3.tar.gz
- cd git-2.7.3/
+ curl -O --progress https://www.kernel.org/pub/software/scm/git/git-2.7.4.tar.gz
+ echo '7104c4f5d948a75b499a954524cb281fe30c6649d8abe20982936f75ec1f275b git-2.7.4.tar.gz' | shasum -a256 -c - && tar -xzf git-2.7.4.tar.gz
+ cd git-2.7.4/
./configure
make prefix=/usr/local all
@@ -161,7 +161,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
# Install the database packages
sudo apt-get install -y postgresql postgresql-client libpq-dev
-
+
# Create a user for GitLab
sudo -u postgres psql -d template1 -c "CREATE USER git CREATEDB;"
@@ -348,7 +348,7 @@ GitLab Shell is an SSH access and repository management software developed speci
cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git
cd gitlab-workhorse
- sudo -u git -H git checkout 0.6.5
+ sudo -u git -H git checkout v0.7.1
sudo -u git -H make
### Initialize Database and Activate Advanced Features
diff --git a/doc/intro/README.md b/doc/intro/README.md
new file mode 100644
index 00000000000..fecbbe6317b
--- /dev/null
+++ b/doc/intro/README.md
@@ -0,0 +1,41 @@
+# Get started with GitLab
+
+## Organize
+
+Create projects and groups.
+
+- [Create a new project](../gitlab-basics/create-project.md)
+- [Create a new group](../gitlab-basics/create-group.md)
+
+## Prioritize
+
+Create issues, labels, milestones, cast your vote, and review issues.
+
+- [Create a new issue](../gitlab-basics/create-issue.md)
+- [Assign labels to issues](../workflow/labels.md)
+- [Use milestones as an overview of your project's tracker](../workflow/milestones.md)
+- [Use voting to express your like/dislike to issues and merge requests](../workflow/award_emoji.md)
+
+## Collaborate
+
+Create merge requests and review code.
+
+- [Fork a project and contribute to it](../workflow/forking_workflow.md)
+- [Create a new merge request](../gitlab-basics/add-merge-request.md)
+- [Automatically close issues from merge requests](../customization/issue_closing.md)
+- [Automatically merge when your builds succeed](../workflow/merge_when_build_succeeds.md)
+- [Revert any commit](../workflow/revert_changes.md)
+
+## Test and Deploy
+
+Use the built-in continuous integration in GitLab.
+
+- [Get started with GitLab CI](../ci/quick_start/README.md)
+
+## Install and Update
+
+Install and update your GitLab installation.
+
+- [Install GitLab](https://about.gitlab.com/installation/)
+- [Update GitLab](https://about.gitlab.com/update/)
+- [Explore Omnibus GitLab configuration options](http://doc.gitlab.com/omnibus/settings/configuration.html)
diff --git a/doc/public_access/public_access.md b/doc/public_access/public_access.md
index 6e22ea7b72a..20aa90f0d69 100644
--- a/doc/public_access/public_access.md
+++ b/doc/public_access/public_access.md
@@ -35,6 +35,21 @@ the repository.
1. Go to your project's **Settings**
1. Change "Visibility Level" to either Public, Internal or Private
+## Visibility of groups
+
+>**Note:**
+[Starting with][3323] GitLab 8.6, the group visibility has changed and can be
+configured the same way as projects. In previous versions, a group's page was
+always visible to all users.
+
+Like with projects, the visibility of a group can be set to dictate whether
+anonymous users, all signed in users, or only explicit group members can view
+it. The restriction for visibility levels on the application setting level also
+applies to groups, so if that's set to internal, the explore page will be empty
+for anonymous users. The group page now has a visibility level icon.
+
+[3323]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3323
+
## Visibility of users
The public page of a user, located at `/u/username`, is always visible whether
@@ -43,14 +58,6 @@ you are logged in or not.
When visiting the public page of a user, you can only see the projects which
you are privileged to.
-## Visibility of groups
-
-The public page of a group, located at `/groups/groupname`, is always visible
-to everyone.
-
-Logged out users will be able to see the description and the avatar of the
-group as well as all public projects belonging to that group.
-
## Restricting the use of public or internal projects
In the Admin area under **Settings** (`/admin/application_settings`), you can
diff --git a/doc/release/security.md b/doc/release/security.md
index b1a62b333e6..118c016ba4f 100644
--- a/doc/release/security.md
+++ b/doc/release/security.md
@@ -15,7 +15,7 @@ Please report suspected security vulnerabilities in private to <support@gitlab.c
1. Verify that the issue can be reproduced
1. Acknowledge the issue to the researcher that disclosed it
1. Inform the release manager that there needs to be a security release
-1. Do the steps from [patch release document](doc/release/patch.md), starting with "Create an issue on private GitLab development server"
+1. Do the steps from [patch release document](../release/patch.md), starting with "Create an issue on private GitLab development server"
1. The MR with the security fix should get a 'security' label and be assigned to the release manager
1. Build the package for GitLab.com and do a deploy
1. Build the package for ci.gitLab.com and do a deploy
diff --git a/doc/security/two_factor_authentication.md b/doc/security/two_factor_authentication.md
index 8365bdb7b1b..c8499380c18 100644
--- a/doc/security/two_factor_authentication.md
+++ b/doc/security/two_factor_authentication.md
@@ -6,7 +6,7 @@ password to login, they'll be prompted for a code generated by an application on
their phone.
You can read more about it here:
-[Two-factor Authentication (2FA)](doc/profile/two_factor_authentication.md)
+[Two-factor Authentication (2FA)](../profile/two_factor_authentication.md)
## Enabling 2FA
diff --git a/doc/update/8.2-to-8.3.md b/doc/update/8.2-to-8.3.md
index 2ca4e1f3770..9f5c6c4dc84 100644
--- a/doc/update/8.2-to-8.3.md
+++ b/doc/update/8.2-to-8.3.md
@@ -1,5 +1,14 @@
# From 8.2 to 8.3
+Make sure you view this update guide from the tag (version) of GitLab you would
+like to install. In most cases this should be the highest numbered production
+tag (without rc in it). You can select the tag in the version dropdown at the
+top left corner of GitLab (below the menu bar).
+
+If the highest number stable branch is unclear please check the
+[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
+guide links by version.
+
**NOTE:** GitLab 8.0 introduced several significant changes related to
installation and configuration which *are not duplicated here*. Be sure you're
already running a working version of at least 8.0 before proceeding with this
diff --git a/doc/update/8.3-to-8.4.md b/doc/update/8.3-to-8.4.md
index 269deec7a9c..9f6517d9487 100644
--- a/doc/update/8.3-to-8.4.md
+++ b/doc/update/8.3-to-8.4.md
@@ -1,5 +1,14 @@
# From 8.3 to 8.4
+Make sure you view this update guide from the tag (version) of GitLab you would
+like to install. In most cases this should be the highest numbered production
+tag (without rc in it). You can select the tag in the version dropdown at the
+top left corner of GitLab (below the menu bar).
+
+If the highest number stable branch is unclear please check the
+[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
+guide links by version.
+
### 1. Stop server
sudo service gitlab stop
diff --git a/doc/update/8.4-to-8.5.md b/doc/update/8.4-to-8.5.md
index 0a9cb5683e7..0cb137a03cc 100644
--- a/doc/update/8.4-to-8.5.md
+++ b/doc/update/8.4-to-8.5.md
@@ -1,5 +1,14 @@
# From 8.4 to 8.5
+Make sure you view this update guide from the tag (version) of GitLab you would
+like to install. In most cases this should be the highest numbered production
+tag (without rc in it). You can select the tag in the version dropdown at the
+top left corner of GitLab (below the menu bar).
+
+If the highest number stable branch is unclear please check the
+[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
+guide links by version.
+
### 1. Stop server
sudo service gitlab stop
diff --git a/doc/update/8.5-to-8.6.md b/doc/update/8.5-to-8.6.md
index 024f6e8a433..712e9fdf93a 100644
--- a/doc/update/8.5-to-8.6.md
+++ b/doc/update/8.5-to-8.6.md
@@ -1,5 +1,14 @@
# From 8.5 to 8.6
+Make sure you view this update guide from the tag (version) of GitLab you would
+like to install. In most cases this should be the highest numbered production
+tag (without rc in it). You can select the tag in the version dropdown at the
+top left corner of GitLab (below the menu bar).
+
+If the highest number stable branch is unclear please check the
+[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
+guide links by version.
+
### 1. Stop server
sudo service gitlab stop
@@ -49,7 +58,7 @@ GitLab 8.1.
```bash
cd /home/git/gitlab-workhorse
sudo -u git -H git fetch --all
-sudo -u git -H git checkout 0.6.5
+sudo -u git -H git checkout v0.7.1
sudo -u git -H make
```
diff --git a/doc/workflow/award_emoji.md b/doc/workflow/award_emoji.md
new file mode 100644
index 00000000000..70b35c58be6
--- /dev/null
+++ b/doc/workflow/award_emoji.md
@@ -0,0 +1,48 @@
+# Award emojis
+
+>**Note:**
+This feature was [introduced][1825] in GitLab 8.2.
+
+When you're collaborating online, you get fewer opportunities for high-fives
+and thumbs-ups. In order to make virtual celebrations easier, you can now vote
+on issues and merge requests using emoji!
+
+![Award emoji](img/award_emoji_select.png)
+
+This makes it much easier to give and receive feedback, without a long comment
+thread. Any comment that contains only the thumbs up or down emojis is
+converted to a vote and depicted in the emoji area.
+
+You can then use that functionality to sort issues and merge requests based on
+popularity.
+
+## Sort issues and merge requests on vote count
+
+>**Note:**
+This feature was [introduced][2871] in GitLab 8.5.
+
+You can quickly sort the issues or merge requests by the number of votes they
+have received. The sort option can be found in the right dropdown menu.
+
+![Votes sort options](img/award_emoji_votes_sort_options.png)
+
+---
+
+Sort by most popular issues/merge requests.
+
+![Votes sort by most popular](img/award_emoji_votes_most_popular.png)
+
+---
+
+Sort by least popular issues/merge requests.
+
+![Votes sort by least popular](img/award_emoji_votes_least_popular.png)
+
+---
+
+The number of upvotes and downvotes is not summed up. That means that an issue
+with 18 upvotes and 5 downvotes is considered more popular than an issue with
+17 upvotes and no downvotes.
+
+[2871]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2781
+[1825]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/1825
diff --git a/doc/workflow/img/award_emoji_select.png b/doc/workflow/img/award_emoji_select.png
new file mode 100644
index 00000000000..fffdfedda5d
--- /dev/null
+++ b/doc/workflow/img/award_emoji_select.png
Binary files differ
diff --git a/doc/workflow/img/award_emoji_votes_least_popular.png b/doc/workflow/img/award_emoji_votes_least_popular.png
new file mode 100644
index 00000000000..2ef5be7154f
--- /dev/null
+++ b/doc/workflow/img/award_emoji_votes_least_popular.png
Binary files differ
diff --git a/doc/workflow/img/award_emoji_votes_most_popular.png b/doc/workflow/img/award_emoji_votes_most_popular.png
new file mode 100644
index 00000000000..5b089730d93
--- /dev/null
+++ b/doc/workflow/img/award_emoji_votes_most_popular.png
Binary files differ
diff --git a/doc/workflow/img/award_emoji_votes_sort_options.png b/doc/workflow/img/award_emoji_votes_sort_options.png
new file mode 100644
index 00000000000..9bbf3f82a0b
--- /dev/null
+++ b/doc/workflow/img/award_emoji_votes_sort_options.png
Binary files differ
diff --git a/doc/workflow/importing/import_projects_from_bitbucket.md b/doc/workflow/importing/import_projects_from_bitbucket.md
index 1e9825e2e10..520c4216295 100644
--- a/doc/workflow/importing/import_projects_from_bitbucket.md
+++ b/doc/workflow/importing/import_projects_from_bitbucket.md
@@ -1,6 +1,6 @@
# Import your project from Bitbucket to GitLab
-It takes just a few steps to import your existing Bitbucket projects to GitLab. But keep in mind that it is possible only if Bitbucket support is enabled on your GitLab instance. You can read more about Bitbucket support [here](doc/integration/bitbucket.md).
+It takes just a few steps to import your existing Bitbucket projects to GitLab. But keep in mind that it is possible only if Bitbucket support is enabled on your GitLab instance. You can read more about Bitbucket support [here](../../integration/bitbucket.md).
* Sign in to GitLab.com and go to your dashboard
diff --git a/doc/workflow/protected_branches.md b/doc/workflow/protected_branches.md
index fdf9a8d391c..d854ec1e025 100644
--- a/doc/workflow/protected_branches.md
+++ b/doc/workflow/protected_branches.md
@@ -12,7 +12,7 @@ A protected branch does three simple things:
You can make any branch a protected branch. GitLab makes the master branch a protected branch by default.
-To protect a branch, user needs to have at least a Master permission level, see [permissions document](doc/permissions/permissions.md).
+To protect a branch, user needs to have at least a Master permission level, see [permissions document](../permissions/permissions.md).
![protected branches page](protected_branches/protected_branches1.png)
diff --git a/features/project/issues/award_emoji.feature b/features/project/issues/award_emoji.feature
index 2945bb3753a..f0fd414a9f9 100644
--- a/features/project/issues/award_emoji.feature
+++ b/features/project/issues/award_emoji.feature
@@ -18,21 +18,24 @@ Feature: Award Emoji
@javascript
Scenario: I add and remove custom award in the issue
Given I click to emoji-picker
- Then The search field is focused
- And I click to emoji in the picker
+ Then The emoji menu is visible
+ And The search field is focused
+ Then I click to emoji in the picker
Then I have award added
And I can remove it by clicking to icon
@javascript
Scenario: I can see the list of emoji categories
Given I click to emoji-picker
- Then The search field is focused
+ Then The emoji menu is visible
+ And The search field is focused
Then I can see the activity and food categories
@javascript
Scenario: I can search emoji
Given I click to emoji-picker
- Then The search field is focused
+ Then The emoji menu is visible
+ And The search field is focused
And I search "hand"
Then I see search result for "hand"
diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature
index ff21c7d1b83..de7e2b37725 100644
--- a/features/project/issues/issues.feature
+++ b/features/project/issues/issues.feature
@@ -160,6 +160,7 @@ Feature: Project Issues
Scenario: Issues on empty project
Given empty project "Empty Project"
+ And I have an ssh key
When I visit empty project page
And I see empty project details with ssh clone info
When I visit empty project's issues page
diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature
index 74685d24a7d..823658b4f24 100644
--- a/features/project/merge_requests.feature
+++ b/features/project/merge_requests.feature
@@ -325,3 +325,11 @@ Feature: Project Merge Requests
When I click the "Target branch" dropdown
And I select a new target branch
Then I should see new target branch changes
+
+ @javascript
+ Scenario: I can close merge request after commenting
+ Given I visit merge request page "Bug NS-04"
+ And I leave a comment like "XML attached"
+ Then I should see comment "XML attached"
+ And I click link "Close"
+ Then I should see closed merge request "Bug NS-04"
diff --git a/features/steps/dashboard/todos.rb b/features/steps/dashboard/todos.rb
index 9722a5a848c..963e4f21365 100644
--- a/features/steps/dashboard/todos.rb
+++ b/features/steps/dashboard/todos.rb
@@ -41,7 +41,6 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
click_link 'Done'
end
- expect(page).to have_content 'Todo was successfully marked as done.'
expect(page).to have_content 'To do 3'
expect(page).to have_content 'Done 1'
should_not_see_todo "John Doe assigned you merge request !#{merge_request.iid}"
diff --git a/features/steps/groups.rb b/features/steps/groups.rb
index 7a6ae15ffa5..e5b7db4c5e3 100644
--- a/features/steps/groups.rb
+++ b/features/steps/groups.rb
@@ -35,7 +35,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
end
step 'I should see projects activity feed' do
- expect(page).to have_content 'closed issue'
+ expect(page).to have_content 'joined project'
end
step 'I should see issues from group "Owned" assigned to me' do
diff --git a/features/steps/project/create.rb b/features/steps/project/create.rb
index 8a0e8fc2b6c..422b151eaa2 100644
--- a/features/steps/project/create.rb
+++ b/features/steps/project/create.rb
@@ -27,7 +27,7 @@ class Spinach::Features::ProjectCreate < Spinach::FeatureSteps
step 'I click on HTTP' do
find('#clone-dropdown').click
- find('#http-selector').click
+ find('.http-selector').click
end
step 'Remote url should update to http link' do
@@ -36,7 +36,7 @@ class Spinach::Features::ProjectCreate < Spinach::FeatureSteps
step 'If I click on SSH' do
find('#clone-dropdown').click
- find('#ssh-selector').click
+ find('.ssh-selector').click
end
step 'Remote url should update to ssh link' do
diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb
index ce2554bc80d..c5d45709b44 100644
--- a/features/steps/project/issues/award_emoji.rb
+++ b/features/steps/project/issues/award_emoji.rb
@@ -92,6 +92,10 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
end
end
+ step 'The emoji menu is visible' do
+ page.find(".emoji-menu.is-visible")
+ end
+
step 'The search field is focused' do
expect(page).to have_selector('#emoji_search')
expect(page.evaluate_script('document.activeElement.id')).to eq('emoji_search')
diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb
index 8c31fa890b2..aff5ca676be 100644
--- a/features/steps/project/issues/issues.rb
+++ b/features/steps/project/issues/issues.rb
@@ -5,6 +5,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
include SharedNote
include SharedPaths
include SharedMarkdown
+ include SharedUser
step 'I should see "Release 0.4" in issues' do
expect(page).to have_content "Release 0.4"
@@ -240,7 +241,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end
step 'empty project "Empty Project"' do
- create :empty_project, name: 'Empty Project', namespace: @user.namespace
+ create :project_empty_repo, name: 'Empty Project', namespace: @user.namespace
end
When 'I visit empty project page' do
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 71197205f34..197e826e5bc 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -85,7 +85,7 @@ module API
end
class Group < Grape::Entity
- expose :id, :name, :path, :description
+ expose :id, :name, :path, :description, :visibility_level
expose :avatar_url
expose :web_url do |group, options|
@@ -340,6 +340,7 @@ module API
expose :session_expire_delay
expose :default_project_visibility
expose :default_snippet_visibility
+ expose :default_group_visibility
expose :restricted_signup_domains
expose :user_oauth_applications
expose :after_sign_out_path
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 1a14d870a4a..c165de21a75 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -31,7 +31,7 @@ module API
authorize! :create_group, current_user
required_attributes! [:name, :path]
- attrs = attributes_for_keys [:name, :path, :description]
+ attrs = attributes_for_keys [:name, :path, :description, :visibility_level]
@group = Group.new(attrs)
if @group.save
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index a72044e8058..4921ae99e78 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -118,9 +118,7 @@ module API
end
def authorize!(action, subject)
- unless abilities.allowed?(current_user, action, subject)
- forbidden!
- end
+ forbidden! unless abilities.allowed?(current_user, action, subject)
end
def authorize_push_project
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 252744515da..e5ae88eb96f 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -82,7 +82,7 @@ module API
# GET /projects/:id/issues?milestone=1.0.0&state=closed
# GET /issues?iid=42
get ":id/issues" do
- issues = user_project.issues
+ issues = user_project.issues.visible_to_user(current_user)
issues = filter_issues_state(issues, params[:state]) unless params[:state].nil?
issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil?
issues = filter_by_iid(issues, params[:iid]) unless params[:iid].nil?
@@ -104,6 +104,7 @@ module API
# GET /projects/:id/issues/:issue_id
get ":id/issues/:issue_id" do
@issue = user_project.issues.find(params[:issue_id])
+ not_found! unless can?(current_user, :read_issue, @issue)
present @issue, with: Entities::Issue
end
@@ -190,7 +191,7 @@ module API
end
end
- # Delete a project issue (deprecated)
+ # Delete a project issue
#
# Parameters:
# id (required) - The ID of a project
@@ -198,7 +199,10 @@ module API
# Example Request:
# DELETE /projects/:id/issues/:issue_id
delete ":id/issues/:issue_id" do
- not_allowed!
+ issue = user_project.issues.find_by(id: params[:issue_id])
+
+ authorize!(:destroy_issue, issue)
+ issue.destroy
end
end
end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index c5e5d57ed4d..93052fba06b 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -100,6 +100,18 @@ module API
end
end
+ # Delete a MR
+ #
+ # Parameters:
+ # id (required) - The ID of the project
+ # merge_request_id (required) - The MR id
+ delete ":id/merge_requests/:merge_request_id" do
+ merge_request = user_project.merge_requests.find_by(id: params[:merge_request_id])
+
+ authorize!(:destroy_merge_request, merge_request)
+ merge_request.destroy
+ end
+
# Routing "merge_request/:merge_request_id/..." is DEPRECATED and WILL BE REMOVED in version 9.0
# Use "merge_requests/:merge_request_id/..." instead.
#
diff --git a/lib/banzai/filter/issue_reference_filter.rb b/lib/banzai/filter/issue_reference_filter.rb
index 9f08aa36e8b..2732e0b5145 100644
--- a/lib/banzai/filter/issue_reference_filter.rb
+++ b/lib/banzai/filter/issue_reference_filter.rb
@@ -9,6 +9,11 @@ module Banzai
Issue
end
+ def self.user_can_see_reference?(user, node, context)
+ issue = Issue.find(node.attr('data-issue')) rescue nil
+ Ability.abilities.allowed?(user, :read_issue, issue)
+ end
+
def find_object(project, id)
project.get_issue(id)
end
diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb
index 3637b1bac94..132f0a4bd93 100644
--- a/lib/banzai/filter/reference_filter.rb
+++ b/lib/banzai/filter/reference_filter.rb
@@ -47,6 +47,7 @@ module Banzai
# Returns a String
def data_attribute(attributes = {})
attributes[:reference_filter] = self.class.name.demodulize
+ attributes.delete(:original) if context[:no_original_data]
attributes.map { |key, value| %Q(data-#{key.to_s.dasherize}="#{escape_once(value)}") }.join(" ")
end
diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb
index c89e1b51019..2228425076b 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -26,8 +26,8 @@ module Ci
validate!
end
- def builds_for_stage_and_ref(stage, ref, tag = false)
- builds.select{|build| build[:stage] == stage && process?(build[:only], build[:except], ref, tag)}
+ def builds_for_stage_and_ref(stage, ref, tag = false, trigger_request = nil)
+ builds.select{|build| build[:stage] == stage && process?(build[:only], build[:except], ref, tag, trigger_request)}
end
def builds
@@ -266,29 +266,30 @@ module Ci
value.in?([true, false])
end
- def process?(only_params, except_params, ref, tag)
+ def process?(only_params, except_params, ref, tag, trigger_request)
if only_params.present?
- return false unless matching?(only_params, ref, tag)
+ return false unless matching?(only_params, ref, tag, trigger_request)
end
if except_params.present?
- return false if matching?(except_params, ref, tag)
+ return false if matching?(except_params, ref, tag, trigger_request)
end
true
end
- def matching?(patterns, ref, tag)
+ def matching?(patterns, ref, tag, trigger_request)
patterns.any? do |pattern|
- match_ref?(pattern, ref, tag)
+ match_ref?(pattern, ref, tag, trigger_request)
end
end
- def match_ref?(pattern, ref, tag)
+ def match_ref?(pattern, ref, tag, trigger_request)
pattern, path = pattern.split('@', 2)
return false if path && path != self.path
return true if tag && pattern == 'tags'
return true if !tag && pattern == 'branches'
+ return true if trigger_request.present? && pattern == 'triggers'
if pattern.first == "/" && pattern.last == "/"
Regexp.new(pattern[1...-1]) =~ ref
diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb
index faa2830c16e..d2e85cabf72 100644
--- a/lib/gitlab/diff/file.rb
+++ b/lib/gitlab/diff/file.rb
@@ -24,6 +24,10 @@ module Gitlab
@lines ||= parser.parse(raw_diff.each_line).to_a
end
+ def too_large?
+ diff.too_large?
+ end
+
def highlighted_diff_lines
Gitlab::Diff::Highlight.new(self).highlight
end
diff --git a/lib/gitlab/gfm/reference_rewriter.rb b/lib/gitlab/gfm/reference_rewriter.rb
new file mode 100644
index 00000000000..a1c6ee7bd69
--- /dev/null
+++ b/lib/gitlab/gfm/reference_rewriter.rb
@@ -0,0 +1,79 @@
+module Gitlab
+ module Gfm
+ ##
+ # Class that unfolds local references in text.
+ #
+ # The initializer takes text in Markdown and project this text is valid
+ # in context of.
+ #
+ # `unfold` method tries to find all local references and unfold each of
+ # those local references to cross reference format, assuming that the
+ # argument passed to this method is a project that references will be
+ # viewed from (see `Referable#to_reference method).
+ #
+ # Examples:
+ #
+ # 'Hello, this issue is related to #123 and
+ # other issues labeled with ~"label"', will be converted to:
+ #
+ # 'Hello, this issue is related to gitlab-org/gitlab-ce#123 and
+ # other issue labeled with gitlab-org/gitlab-ce~"label"'.
+ #
+ # It does respect markdown lexical rules, so text in code block will not be
+ # replaced, see another example:
+ #
+ # 'Merge request for issue #1234, see also link:
+ # http://gitlab.com/some/link/#1234, and code `puts #1234`' =>
+ #
+ # 'Merge request for issue gitlab-org/gitlab-ce#1234, se also link:
+ # http://gitlab.com/some/link/#1234, and code `puts #1234`'
+ #
+ class ReferenceRewriter
+ def initialize(text, source_project, current_user)
+ @text = text
+ @source_project = source_project
+ @current_user = current_user
+ @original_html = markdown(text)
+ end
+
+ def rewrite(target_project)
+ pattern = Gitlab::ReferenceExtractor.references_pattern
+
+ @text.gsub(pattern) do |reference|
+ unfold_reference(reference, Regexp.last_match, target_project)
+ end
+ end
+
+ private
+
+ def unfold_reference(reference, match, target_project)
+ before = @text[0...match.begin(0)]
+ after = @text[match.end(0)..-1]
+
+ referable = find_referable(reference)
+ return reference unless referable
+
+ cross_reference = referable.to_reference(target_project)
+ return reference if reference == cross_reference
+
+ new_text = before + cross_reference + after
+ substitution_valid?(new_text) ? cross_reference : reference
+ end
+
+ def find_referable(reference)
+ extractor = Gitlab::ReferenceExtractor.new(@source_project,
+ @current_user)
+ extractor.analyze(reference)
+ extractor.all.first
+ end
+
+ def substitution_valid?(substituted)
+ @original_html == markdown(substituted)
+ end
+
+ def markdown(text)
+ Banzai.render(text, project: @source_project, no_original_data: true)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb
index 0607a8b9592..71c5b6801fb 100644
--- a/lib/gitlab/project_search_results.rb
+++ b/lib/gitlab/project_search_results.rb
@@ -2,7 +2,8 @@ module Gitlab
class ProjectSearchResults < SearchResults
attr_reader :project, :repository_ref
- def initialize(project, query, repository_ref = nil)
+ def initialize(current_user, project, query, repository_ref = nil)
+ @current_user = current_user
@project = project
@repository_ref = if repository_ref.present?
repository_ref
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index 4d830aa45e1..13c4d64c99b 100644
--- a/lib/gitlab/reference_extractor.rb
+++ b/lib/gitlab/reference_extractor.rb
@@ -1,6 +1,7 @@
module Gitlab
# Extract possible GFM references from an arbitrary String for further processing.
class ReferenceExtractor < Banzai::ReferenceExtractor
+ REFERABLES = %i(user issue label milestone merge_request snippet commit commit_range)
attr_accessor :project, :current_user, :author
def initialize(project, current_user = nil, author = nil)
@@ -17,7 +18,7 @@ module Gitlab
super(text, context.merge(project: project))
end
- %i(user label milestone merge_request snippet commit commit_range).each do |type|
+ REFERABLES.each do |type|
define_method("#{type}s") do
@references[type] ||= references(type, reference_context)
end
@@ -31,6 +32,21 @@ module Gitlab
end
end
+ def all
+ REFERABLES.each { |referable| send(referable.to_s.pluralize) }
+ @references.values.flatten
+ end
+
+ def self.references_pattern
+ return @pattern if @pattern
+
+ patterns = REFERABLES.map do |ref|
+ ref.to_s.classify.constantize.try(:reference_pattern)
+ end
+
+ @pattern = Regexp.union(patterns.compact)
+ end
+
private
def reference_context
diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb
index f13528a2eea..f8ab2b1f09e 100644
--- a/lib/gitlab/search_results.rb
+++ b/lib/gitlab/search_results.rb
@@ -1,12 +1,13 @@
module Gitlab
class SearchResults
- attr_reader :query
+ attr_reader :current_user, :query
# Limit search results by passed projects
# It allows us to search only for projects user has access to
attr_reader :limit_projects
- def initialize(limit_projects, query)
+ def initialize(current_user, limit_projects, query)
+ @current_user = current_user
@limit_projects = limit_projects || Project.all
@query = Shellwords.shellescape(query) if query.present?
end
@@ -58,7 +59,7 @@ module Gitlab
end
def issues
- issues = Issue.where(project_id: project_ids_relation)
+ issues = Issue.visible_to_user(current_user).where(project_id: project_ids_relation)
if query =~ /#(\d+)\z/
issues = issues.where(iid: $1)
diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb
index 3160a3c7582..a1ee1cba216 100644
--- a/lib/gitlab/visibility_level.rb
+++ b/lib/gitlab/visibility_level.rb
@@ -6,6 +6,14 @@
module Gitlab
module VisibilityLevel
extend CurrentSettings
+ extend ActiveSupport::Concern
+
+ included do
+ scope :public_only, -> { where(visibility_level: PUBLIC) }
+ scope :public_and_internal_only, -> { where(visibility_level: [PUBLIC, INTERNAL] ) }
+
+ scope :public_to_user, -> (user) { user && !user.external ? public_and_internal_only : public_only }
+ end
PRIVATE = 0 unless const_defined?(:PRIVATE)
INTERNAL = 10 unless const_defined?(:INTERNAL)
@@ -48,10 +56,6 @@ module Gitlab
options.has_value?(level)
end
- def allowed_fork_levels(origin_level)
- [PRIVATE, INTERNAL, PUBLIC].select{ |level| level <= origin_level }
- end
-
def level_name(level)
level_name = 'Unknown'
options.each do |name, lvl|
diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab
index fc5475c4eef..1324e4cd267 100644
--- a/lib/support/nginx/gitlab
+++ b/lib/support/nginx/gitlab
@@ -30,7 +30,6 @@ server {
listen [::]:80 default_server;
server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com
server_tokens off; ## Don't show the nginx version number, a security best practice
- root /home/git/gitlab/public;
## See app/controllers/application_controller.rb for headers set
@@ -57,4 +56,14 @@ server {
proxy_pass http://gitlab-workhorse;
}
+
+ error_page 404 /404.html;
+ error_page 422 /422.html;
+ error_page 500 /500.html;
+ error_page 502 /502.html;
+ location ~ ^/(404|422|500|502)\.html$ {
+ root /home/git/gitlab/public;
+ internal;
+ }
+
}
diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl
index 1e5f85413ec..af6ea9ed706 100644
--- a/lib/support/nginx/gitlab-ssl
+++ b/lib/support/nginx/gitlab-ssl
@@ -45,7 +45,6 @@ server {
listen [::]:443 ipv6only=on ssl default_server;
server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com
server_tokens off; ## Don't show the nginx version number, a security best practice
- root /home/git/gitlab/public;
## Strong SSL Security
## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html & https://cipherli.st/
@@ -101,4 +100,13 @@ server {
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://gitlab-workhorse;
}
+
+ error_page 404 /404.html;
+ error_page 422 /422.html;
+ error_page 500 /500.html;
+ error_page 502 /502.html;
+ location ~ ^/(404|422|500|502)\.html$ {
+ root /home/git/gitlab/public;
+ internal;
+ }
}
diff --git a/public/404.html b/public/404.html
index a0106bc760d..4862770cc2a 100644
--- a/public/404.html
+++ b/public/404.html
@@ -2,11 +2,51 @@
<html>
<head>
<title>The page you're looking for could not be found (404)</title>
- <link href="/static.css" media="screen" rel="stylesheet" type="text/css" />
+ <style>
+ body {
+ color: #666;
+ text-align: center;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ margin: 0;
+ width: 800px;
+ margin: auto;
+ font-size: 14px;
+ }
+
+ h1 {
+ font-size: 56px;
+ line-height: 100px;
+ font-weight: normal;
+ color: #456;
+ }
+
+ h2 {
+ font-size: 24px;
+ color: #666;
+ line-height: 1.5em;
+ }
+
+ h3 {
+ color: #456;
+ font-size: 20px;
+ font-weight: normal;
+ line-height: 28px;
+ }
+
+ hr {
+ margin: 18px 0;
+ border: 0;
+ border-top: 1px solid #EEE;
+ border-bottom: 1px solid white;
+ }
+ </style>
</head>
<body>
- <h1>404</h1>
+ <h1>
+ <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" /><br />
+ 404
+ </h1>
<h3>The page you're looking for could not be found.</h3>
<hr/>
<p>Make sure the address is correct and that the page hasn't moved.</p>
diff --git a/public/422.html b/public/422.html
index 026997b48e3..055b0bde165 100644
--- a/public/422.html
+++ b/public/422.html
@@ -2,12 +2,51 @@
<html>
<head>
<title>The change you requested was rejected (422)</title>
- <link href="/static.css" media="screen" rel="stylesheet" type="text/css" />
+ <style>
+ body {
+ color: #666;
+ text-align: center;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ margin: 0;
+ width: 800px;
+ margin: auto;
+ font-size: 14px;
+ }
+
+ h1 {
+ font-size: 56px;
+ line-height: 100px;
+ font-weight: normal;
+ color: #456;
+ }
+
+ h2 {
+ font-size: 24px;
+ color: #666;
+ line-height: 1.5em;
+ }
+
+ h3 {
+ color: #456;
+ font-size: 20px;
+ font-weight: normal;
+ line-height: 28px;
+ }
+
+ hr {
+ margin: 18px 0;
+ border: 0;
+ border-top: 1px solid #EEE;
+ border-bottom: 1px solid white;
+ }
+ </style>
</head>
<body>
- <!-- This file lives in public/422.html -->
- <h1>422</h1>
+ <h1>
+ <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" /><br />
+ 422
+ </h1>
<h3>The change you requested was rejected.</h3>
<hr />
<p>Make sure you have access to the thing you tried to change.</p>
diff --git a/public/500.html b/public/500.html
index 08c11bbd05a..3d59d1392f5 100644
--- a/public/500.html
+++ b/public/500.html
@@ -2,10 +2,50 @@
<html>
<head>
<title>Something went wrong (500)</title>
- <link href="/static.css" media="screen" rel="stylesheet" type="text/css" />
+ <style>
+ body {
+ color: #666;
+ text-align: center;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ margin: 0;
+ width: 800px;
+ margin: auto;
+ font-size: 14px;
+ }
+
+ h1 {
+ font-size: 56px;
+ line-height: 100px;
+ font-weight: normal;
+ color: #456;
+ }
+
+ h2 {
+ font-size: 24px;
+ color: #666;
+ line-height: 1.5em;
+ }
+
+ h3 {
+ color: #456;
+ font-size: 20px;
+ font-weight: normal;
+ line-height: 28px;
+ }
+
+ hr {
+ margin: 18px 0;
+ border: 0;
+ border-top: 1px solid #EEE;
+ border-bottom: 1px solid white;
+ }
+ </style>
</head>
<body>
- <h1>500</h1>
+ <h1>
+ <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" /><br />
+ 500
+ </h1>
<h3>Whoops, something went wrong on our end.</h3>
<hr/>
<p>Try refreshing the page, or going back and attempting the action again.</p>
diff --git a/public/502.html b/public/502.html
index 9480a928439..67dfd8a2743 100644
--- a/public/502.html
+++ b/public/502.html
@@ -2,10 +2,50 @@
<html>
<head>
<title>GitLab is not responding (502)</title>
- <link href="/static.css" media="screen" rel="stylesheet" type="text/css" />
+ <style>
+ body {
+ color: #666;
+ text-align: center;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ margin: 0;
+ width: 800px;
+ margin: auto;
+ font-size: 14px;
+ }
+
+ h1 {
+ font-size: 56px;
+ line-height: 100px;
+ font-weight: normal;
+ color: #456;
+ }
+
+ h2 {
+ font-size: 24px;
+ color: #666;
+ line-height: 1.5em;
+ }
+
+ h3 {
+ color: #456;
+ font-size: 20px;
+ font-weight: normal;
+ line-height: 28px;
+ }
+
+ hr {
+ margin: 18px 0;
+ border: 0;
+ border-top: 1px solid #EEE;
+ border-bottom: 1px solid white;
+ }
+ </style>
</head>
<body>
- <h1>502</h1>
+ <h1>
+ <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" /><br />
+ 502
+ </h1>
<h3>Whoops, GitLab is taking too much time to respond.</h3>
<hr/>
<p>Try refreshing the page, or going back and attempting the action again.</p>
diff --git a/public/deploy.html b/public/deploy.html
index 3822ed4b64d..48976dacf41 100644
--- a/public/deploy.html
+++ b/public/deploy.html
@@ -2,12 +2,49 @@
<html>
<head>
<title>Deploy in progress</title>
- <link href="/static.css" media="screen" rel="stylesheet" type="text/css" />
+ <style>
+ body {
+ color: #666;
+ text-align: center;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ margin: 0;
+ width: 800px;
+ margin: auto;
+ font-size: 14px;
+ }
+
+ h1 {
+ font-size: 56px;
+ line-height: 100px;
+ font-weight: normal;
+ color: #456;
+ }
+
+ h2 {
+ font-size: 24px;
+ color: #666;
+ line-height: 1.5em;
+ }
+
+ h3 {
+ color: #456;
+ font-size: 20px;
+ font-weight: normal;
+ line-height: 28px;
+ }
+
+ hr {
+ margin: 18px 0;
+ border: 0;
+ border-top: 1px solid #EEE;
+ border-bottom: 1px solid white;
+ }
+ </style>
</head>
<body>
<h1>
- <img src="/logo.svg" /><br />
+ <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" /><br />
Deploy in progress
</h1>
<h3>Please try again in a few minutes.</h3>
diff --git a/public/logo.svg b/public/logo.svg
deleted file mode 100644
index fc4553137f7..00000000000
--- a/public/logo.svg
+++ /dev/null
@@ -1,9 +0,0 @@
-<svg width="210" height="210" viewBox="0 0 210 210" xmlns="http://www.w3.org/2000/svg">
- <path d="M105.0614 203.655l38.64-118.921h-77.28l38.64 118.921z" fill="#e24329"/>
- <path d="M105.0614 203.6548l-38.64-118.921h-54.153l92.793 118.921z" fill="#fc6d26"/>
- <path d="M12.2685 84.7341l-11.742 36.139c-1.071 3.296.102 6.907 2.906 8.944l101.629 73.838-92.793-118.921z" fill="#fca326"/>
- <path d="M12.2685 84.7342h54.153l-23.273-71.625c-1.197-3.686-6.411-3.685-7.608 0l-23.272 71.625z" fill="#e24329"/>
- <path d="M105.0614 203.6548l38.64-118.921h54.153l-92.793 118.921z" fill="#fc6d26"/>
- <path d="M197.8544 84.7341l11.742 36.139c1.071 3.296-.102 6.907-2.906 8.944l-101.629 73.838 92.793-118.921z" fill="#fca326"/>
- <path d="M197.8544 84.7342h-54.153l23.273-71.625c1.197-3.686 6.411-3.685 7.608 0l23.272 71.625z" fill="#e24329"/>
-</svg>
diff --git a/public/static.css b/public/static.css
deleted file mode 100644
index 0a2b6060d48..00000000000
--- a/public/static.css
+++ /dev/null
@@ -1,36 +0,0 @@
-body {
- color: #666;
- text-align: center;
- font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
- margin: 0;
- width: 800px;
- margin: auto;
- font-size: 14px;
-}
-
-h1 {
- font-size: 56px;
- line-height: 100px;
- font-weight: normal;
- color: #456;
-}
-
-h2 {
- font-size: 24px;
- color: #666;
- line-height: 1.5em;
-}
-
-h3 {
- color: #456;
- font-size: 20px;
- font-weight: normal;
- line-height: 28px;
-}
-
-hr {
- margin: 18px 0;
- border: 0;
- border-top: 1px solid #EEE;
- border-bottom: 1px solid white;
-}
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 55851befc8c..186239d3096 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -30,44 +30,4 @@ describe ApplicationController do
controller.send(:check_password_expiration)
end
end
-
- describe 'check labels authorization' do
- let(:project) { create(:project) }
- let(:user) { create(:user) }
- let(:controller) { ApplicationController.new }
-
- before do
- project.team << [user, :guest]
- allow(controller).to receive(:current_user).and_return(user)
- allow(controller).to receive(:project).and_return(project)
- end
-
- it 'should succeed if issues and MRs are enabled' do
- project.issues_enabled = true
- project.merge_requests_enabled = true
- controller.send(:authorize_read_label!)
- expect(response.status).to eq(200)
- end
-
- it 'should succeed if issues are enabled, MRs are disabled' do
- project.issues_enabled = true
- project.merge_requests_enabled = false
- controller.send(:authorize_read_label!)
- expect(response.status).to eq(200)
- end
-
- it 'should succeed if issues are disabled, MRs are enabled' do
- project.issues_enabled = false
- project.merge_requests_enabled = true
- controller.send(:authorize_read_label!)
- expect(response.status).to eq(200)
- end
-
- it 'should fail if issues and MRs are disabled' do
- project.issues_enabled = false
- project.merge_requests_enabled = false
- expect(controller).to receive(:access_denied!)
- controller.send(:authorize_read_label!)
- end
- end
end
diff --git a/spec/controllers/groups/avatars_controller_spec.rb b/spec/controllers/groups/avatars_controller_spec.rb
index 3dac134a731..91d639218e5 100644
--- a/spec/controllers/groups/avatars_controller_spec.rb
+++ b/spec/controllers/groups/avatars_controller_spec.rb
@@ -2,9 +2,10 @@ require 'spec_helper'
describe Groups::AvatarsController do
let(:user) { create(:user) }
- let(:group) { create(:group, owner: user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
+ let(:group) { create(:group, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
before do
+ group.add_owner(user)
sign_in(user)
end
diff --git a/spec/controllers/namespaces_controller_spec.rb b/spec/controllers/namespaces_controller_spec.rb
index 77436958711..27e9afe582e 100644
--- a/spec/controllers/namespaces_controller_spec.rb
+++ b/spec/controllers/namespaces_controller_spec.rb
@@ -15,14 +15,9 @@ describe NamespacesController do
end
context "when the namespace belongs to a group" do
- let!(:group) { create(:group) }
- let!(:project) { create(:project, namespace: group) }
-
- context "when the group has public projects" do
- before do
- project.update_attribute(:visibility_level, Project::PUBLIC)
- end
+ let!(:group) { create(:group) }
+ context "when the group is public" do
context "when not signed in" do
it "redirects to the group's page" do
get :show, id: group.path
@@ -44,27 +39,31 @@ describe NamespacesController do
end
end
- context "when the project doesn't have public projects" do
+ context "when the group is private" do
+ before do
+ group.update_attribute(:visibility_level, Group::PRIVATE)
+ end
+
context "when not signed in" do
- it "does not redirect to the sign in page" do
+ it "redirects to the sign in page" do
get :show, id: group.path
- expect(response).not_to redirect_to(new_user_session_path)
+ expect(response).to redirect_to(new_user_session_path)
end
end
+
context "when signed in" do
before do
sign_in(user)
end
- context "when the user has access to the project" do
+ context "when the user has access to the group" do
before do
- project.team << [user, :master]
+ group.add_developer(user)
end
context "when the user is blocked" do
before do
user.block
- project.team << [user, :master]
end
it "redirects to the sign in page" do
@@ -83,11 +82,11 @@ describe NamespacesController do
end
end
- context "when the user doesn't have access to the project" do
- it "redirects to the group's page" do
+ context "when the user doesn't have access to the group" do
+ it "responds with status 404" do
get :show, id: group.path
- expect(response).to redirect_to(group_path(group))
+ expect(response.status).to eq(404)
end
end
end
diff --git a/spec/controllers/projects/avatars_controller_spec.rb b/spec/controllers/projects/avatars_controller_spec.rb
index e79b46a3504..4d724ca9ed0 100644
--- a/spec/controllers/projects/avatars_controller_spec.rb
+++ b/spec/controllers/projects/avatars_controller_spec.rb
@@ -6,7 +6,7 @@ describe Projects::AvatarsController do
before do
sign_in(user)
- project.team << [user, :developer]
+ project.team << [user, :master]
controller.instance_variable_set(:@project, project)
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 76d56bc989d..d6e4cd71ce6 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -1,16 +1,16 @@
require('spec_helper')
describe Projects::IssuesController do
- let(:project) { create(:project) }
+ let(:project) { create(:project_empty_repo) }
let(:user) { create(:user) }
- let(:issue) { create(:issue, project: project) }
-
- before do
- sign_in(user)
- project.team << [user, :developer]
- end
+ let(:issue) { create(:issue, project: project) }
describe "GET #index" do
+ before do
+ sign_in(user)
+ project.team << [user, :developer]
+ end
+
it "returns index" do
get :index, namespace_id: project.namespace.path, project_id: project.path
@@ -38,6 +38,177 @@ describe Projects::IssuesController do
get :index, namespace_id: project.namespace.path, project_id: project.path
expect(response.status).to eq(404)
end
+ end
+
+ describe 'Confidential Issues' do
+ let(:project) { create(:project_empty_repo, :public) }
+ let(:assignee) { create(:assignee) }
+ let(:author) { create(:user) }
+ let(:non_member) { create(:user) }
+ let(:member) { create(:user) }
+ let(:admin) { create(:admin) }
+ let!(:issue) { create(:issue, project: project) }
+ let!(:unescaped_parameter_value) { create(:issue, :confidential, project: project, author: author) }
+ let!(:request_forgery_timing_attack) { create(:issue, :confidential, project: project, assignee: assignee) }
+
+ describe 'GET #index' do
+ it 'should not list confidential issues for guests' do
+ sign_out(:user)
+ get_issues
+
+ expect(assigns(:issues)).to eq [issue]
+ end
+
+ it 'should not list confidential issues for non project members' do
+ sign_in(non_member)
+ get_issues
+
+ expect(assigns(:issues)).to eq [issue]
+ end
+
+ it 'should list confidential issues for author' do
+ sign_in(author)
+ get_issues
+
+ expect(assigns(:issues)).to include unescaped_parameter_value
+ expect(assigns(:issues)).not_to include request_forgery_timing_attack
+ end
+
+ it 'should list confidential issues for assignee' do
+ sign_in(assignee)
+ get_issues
+
+ expect(assigns(:issues)).not_to include unescaped_parameter_value
+ expect(assigns(:issues)).to include request_forgery_timing_attack
+ end
+
+ it 'should list confidential issues for project members' do
+ sign_in(member)
+ project.team << [member, :developer]
+
+ get_issues
+
+ expect(assigns(:issues)).to include unescaped_parameter_value
+ expect(assigns(:issues)).to include request_forgery_timing_attack
+ end
+
+ it 'should list confidential issues for admin' do
+ sign_in(admin)
+ get_issues
+
+ expect(assigns(:issues)).to include unescaped_parameter_value
+ expect(assigns(:issues)).to include request_forgery_timing_attack
+ end
+
+ def get_issues
+ get :index,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param
+ end
+ end
+
+ shared_examples_for 'restricted action' do |http_status|
+ it 'returns 404 for guests' do
+ sign_out :user
+ go(id: unescaped_parameter_value.to_param)
+
+ expect(response).to have_http_status :not_found
+ end
+
+ it 'returns 404 for non project members' do
+ sign_in(non_member)
+ go(id: unescaped_parameter_value.to_param)
+
+ expect(response).to have_http_status :not_found
+ end
+
+ it "returns #{http_status[:success]} for author" do
+ sign_in(author)
+ go(id: unescaped_parameter_value.to_param)
+
+ expect(response).to have_http_status http_status[:success]
+ end
+
+ it "returns #{http_status[:success]} for assignee" do
+ sign_in(assignee)
+ go(id: request_forgery_timing_attack.to_param)
+
+ expect(response).to have_http_status http_status[:success]
+ end
+
+ it "returns #{http_status[:success]} for project members" do
+ sign_in(member)
+ project.team << [member, :developer]
+ go(id: unescaped_parameter_value.to_param)
+
+ expect(response).to have_http_status http_status[:success]
+ end
+
+ it "returns #{http_status[:success]} for admin" do
+ sign_in(admin)
+ go(id: unescaped_parameter_value.to_param)
+
+ expect(response).to have_http_status http_status[:success]
+ end
+ end
+
+ describe 'GET #show' do
+ it_behaves_like 'restricted action', success: 200
+
+ def go(id:)
+ get :show,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: id
+ end
+ end
+
+ describe 'GET #edit' do
+ it_behaves_like 'restricted action', success: 200
+ def go(id:)
+ get :edit,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: id
+ end
+ end
+
+ describe 'PUT #update' do
+ it_behaves_like 'restricted action', success: 302
+
+ def go(id:)
+ put :update,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: id,
+ issue: { title: 'New title' }
+ end
+ end
+ end
+
+ describe "DELETE #destroy" do
+ context "when the user is a developer" do
+ before { sign_in(user) }
+ it "rejects a developer to destroy an issue" do
+ delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: issue.iid
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context "when the user is owner" do
+ let(:owner) { create(:user) }
+ let(:namespace) { create(:namespace, owner: owner) }
+ let(:project) { create(:project, namespace: namespace) }
+
+ before { sign_in(owner) }
+
+ it "deletes the issue" do
+ delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: issue.iid
+
+ expect(response.status).to eq(302)
+ expect(controller).to set_flash[:notice].to(/The issue was successfully deleted\./).now
+ end
+ end
end
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index e82fe26c7a6..c5b034dc064 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -157,6 +157,29 @@ describe Projects::MergeRequestsController do
end
end
+ describe "DELETE #destroy" do
+ it "denies access to users unless they're admin or project owner" do
+ delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: merge_request.iid
+
+ expect(response.status).to eq(404)
+ end
+
+ context "when the user is owner" do
+ let(:owner) { create(:user) }
+ let(:namespace) { create(:namespace, owner: owner) }
+ let(:project) { create(:project, namespace: namespace) }
+
+ before { sign_in owner }
+
+ it "deletes the merge request" do
+ delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: merge_request.iid
+
+ expect(response.status).to eq(302)
+ expect(controller).to set_flash[:notice].to(/The merge request was successfully deleted\./).now
+ end
+ end
+ end
+
describe 'GET diffs' do
def go(format: 'html')
get :diffs,
diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb
index af5d043cf02..73858e6f063 100644
--- a/spec/controllers/uploads_controller_spec.rb
+++ b/spec/controllers/uploads_controller_spec.rb
@@ -30,7 +30,7 @@ describe UploadsController do
end
end
end
-
+
context "when not signed in" do
it "responds with status 200" do
get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "image.png"
@@ -126,14 +126,9 @@ describe UploadsController do
end
context "when viewing a group avatar" do
- let!(:group) { create(:group, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
- let!(:project) { create(:project, namespace: group) }
-
- context "when the group has public projects" do
- before do
- project.update_attribute(:visibility_level, Project::PUBLIC)
- end
+ let!(:group) { create(:group, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
+ context "when the group is public" do
context "when not signed in" do
it "responds with status 200" do
get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png"
@@ -155,7 +150,11 @@ describe UploadsController do
end
end
- context "when the project doesn't have public projects" do
+ context "when the group is private" do
+ before do
+ group.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
+ end
+
context "when signed in" do
before do
sign_in(user)
@@ -163,13 +162,12 @@ describe UploadsController do
context "when the user has access to the project" do
before do
- project.team << [user, :master]
+ group.add_developer(user)
end
context "when the user is blocked" do
before do
user.block
- project.team << [user, :master]
end
it "redirects to the sign in page" do
diff --git a/spec/factories/broadcast_messages.rb b/spec/factories/broadcast_messages.rb
index 373ca75467e..c80e7366551 100644
--- a/spec/factories/broadcast_messages.rb
+++ b/spec/factories/broadcast_messages.rb
@@ -15,7 +15,7 @@
FactoryGirl.define do
factory :broadcast_message do
message "MyText"
- starts_at Date.today
+ starts_at Date.yesterday
ends_at Date.tomorrow
trait :expired do
diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb
index 4a3a155d7ff..2d47a6f6c4c 100644
--- a/spec/factories/groups.rb
+++ b/spec/factories/groups.rb
@@ -3,5 +3,17 @@ FactoryGirl.define do
sequence(:name) { |n| "group#{n}" }
path { name.downcase.gsub(/\s/, '_') }
type 'Group'
+
+ trait :public do
+ visibility_level Gitlab::VisibilityLevel::PUBLIC
+ end
+
+ trait :internal do
+ visibility_level Gitlab::VisibilityLevel::INTERNAL
+ end
+
+ trait :private do
+ visibility_level Gitlab::VisibilityLevel::PRIVATE
+ end
end
end
diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb
index 722095de590..e72aa9479b7 100644
--- a/spec/factories/issues.rb
+++ b/spec/factories/issues.rb
@@ -4,6 +4,10 @@ FactoryGirl.define do
author
project
+ trait :confidential do
+ confidential true
+ end
+
trait :closed do
state :closed
end
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index a9df5fa1d3a..e281e2f227b 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -51,6 +51,11 @@ FactoryGirl.define do
trait :with_diffs do
end
+ trait :without_diffs do
+ source_branch "improve/awesome"
+ target_branch "master"
+ end
+
trait :conflict do
source_branch "feature_conflict"
target_branch "feature"
diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb
index bd85b1d798a..7ae06c27840 100644
--- a/spec/factories/todos.rb
+++ b/spec/factories/todos.rb
@@ -5,14 +5,15 @@
# id :integer not null, primary key
# user_id :integer not null
# project_id :integer not null
-# target_id :integer not null
+# target_id :integer
# target_type :string not null
# author_id :integer
-# note_id :integer
# action :integer not null
# state :string not null
# created_at :datetime
# updated_at :datetime
+# note_id :integer
+# commit_id :string
#
FactoryGirl.define do
@@ -30,5 +31,10 @@ FactoryGirl.define do
trait :mentioned do
action { Todo::MENTIONED }
end
+
+ trait :on_commit do
+ commit_id RepoHelpers.sample_commit.id
+ target_type "Commit"
+ end
end
end
diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb
new file mode 100644
index 00000000000..6fda0c31866
--- /dev/null
+++ b/spec/features/issues/move_spec.rb
@@ -0,0 +1,87 @@
+require 'rails_helper'
+
+feature 'issue move to another project' do
+ let(:user) { create(:user) }
+ let(:old_project) { create(:project) }
+ let(:text) { 'Some issue description' }
+
+ let(:issue) do
+ create(:issue, description: text, project: old_project, author: user)
+ end
+
+ background { login_as(user) }
+
+ context 'user does not have permission to move issue' do
+ background do
+ old_project.team << [user, :guest]
+
+ edit_issue(issue)
+ end
+
+ scenario 'moving issue to another project not allowed' do
+ expect(page).to have_no_select('move_to_project_id')
+ end
+ end
+
+ context 'user has permission to move issue' do
+ let!(:mr) { create(:merge_request, source_project: old_project) }
+ let(:new_project) { create(:project) }
+ let(:text) { 'Text with !1' }
+ let(:cross_reference) { old_project.to_reference }
+
+ background do
+ old_project.team << [user, :reporter]
+ new_project.team << [user, :reporter]
+
+ edit_issue(issue)
+ end
+
+ scenario 'moving issue to another project' do
+ select(new_project.name_with_namespace, from: 'move_to_project_id')
+ click_button('Save changes')
+
+ expect(current_url).to include project_path(new_project)
+
+ page.within('.issue') do
+ expect(page).to have_content("Text with #{cross_reference}!1")
+ expect(page).to have_content("Moved from #{cross_reference}#1")
+ expect(page).to have_content(issue.title)
+ end
+ end
+
+ context 'projects user does not have permission to move issue to exist' do
+ let!(:private_project) { create(:project, :private) }
+ let(:another_project) { create(:project) }
+ background { another_project.team << [user, :guest] }
+
+ scenario 'browsing projects in projects select' do
+ options = [ '', 'No project', new_project.name_with_namespace ]
+ expect(page).to have_select('move_to_project_id', options: options)
+ end
+ end
+
+ context 'issue has been already moved' do
+ let(:new_issue) { create(:issue, project: new_project) }
+ let(:issue) do
+ create(:issue, project: old_project, author: user, moved_to: new_issue)
+ end
+
+ scenario 'user wants to move issue that has already been moved' do
+ expect(page).to have_no_select('move_to_project_id')
+ end
+ end
+ end
+
+ def edit_issue(issue)
+ visit issue_path(issue)
+ page.within('.issuable-header') { click_link 'Edit' }
+ end
+
+ def issue_path(issue)
+ namespace_project_issue_path(issue.project.namespace, issue.project, issue)
+ end
+
+ def project_path(project)
+ namespace_project_path(new_project.namespace, new_project)
+ end
+end
diff --git a/spec/features/issues/new_branch_button_spec.rb b/spec/features/issues/new_branch_button_spec.rb
index 1f3bd915f48..9219b767547 100644
--- a/spec/features/issues/new_branch_button_spec.rb
+++ b/spec/features/issues/new_branch_button_spec.rb
@@ -24,7 +24,7 @@ feature 'Start new branch from an issue', feature: true do
end
let(:referenced_mr) do
create(:merge_request, :simple, source_project: project, target_project: project,
- description: "Fixes ##{issue.iid}")
+ description: "Fixes ##{issue.iid}", author: user)
end
before do
diff --git a/spec/features/merge_requests/filter_by_milestone_spec.rb b/spec/features/merge_requests/filter_by_milestone_spec.rb
index 1b2fd1bab10..b76e4c74c79 100644
--- a/spec/features/merge_requests/filter_by_milestone_spec.rb
+++ b/spec/features/merge_requests/filter_by_milestone_spec.rb
@@ -30,8 +30,6 @@ feature 'Merge Request filtering by Milestone', feature: true do
def filter_by_milestone(title)
find(".js-milestone-select").click
- sleep 0.5
find(".milestone-filter a", text: title).click
- sleep 1
end
end
diff --git a/spec/features/security/group/internal_access_spec.rb b/spec/features/security/group/internal_access_spec.rb
new file mode 100644
index 00000000000..71b783b7276
--- /dev/null
+++ b/spec/features/security/group/internal_access_spec.rb
@@ -0,0 +1,109 @@
+require 'rails_helper'
+
+describe 'Internal Group access', feature: true do
+ include AccessMatchers
+
+ let(:group) { create(:group, :internal) }
+ let(:project) { create(:project, :internal, group: group) }
+
+ let(:owner) { create(:user) }
+ let(:master) { create(:user) }
+ let(:developer) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:guest) { create(:user) }
+
+ let(:project_guest) { create(:user) }
+
+ before do
+ group.add_owner(owner)
+ group.add_master(master)
+ group.add_developer(developer)
+ group.add_reporter(reporter)
+ group.add_guest(guest)
+
+ project.team << [project_guest, :guest]
+ end
+
+ describe "Group should be internal" do
+ describe '#internal?' do
+ subject { group.internal? }
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ describe 'GET /groups/:path' do
+ subject { group_path(group) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for project_guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ describe 'GET /groups/:path/issues' do
+ subject { issues_group_path(group) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for project_guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ describe 'GET /groups/:path/merge_requests' do
+ subject { merge_requests_group_path(group) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for project_guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+
+ describe 'GET /groups/:path/group_members' do
+ subject { group_group_members_path(group) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for project_guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ describe 'GET /groups/:path/edit' do
+ subject { edit_group_path(group) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_denied_for master }
+ it { is_expected.to be_denied_for developer }
+ it { is_expected.to be_denied_for reporter }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for project_guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
+ it { is_expected.to be_denied_for :external }
+ end
+end
diff --git a/spec/features/security/group/private_access_spec.rb b/spec/features/security/group/private_access_spec.rb
new file mode 100644
index 00000000000..cc9aee802f9
--- /dev/null
+++ b/spec/features/security/group/private_access_spec.rb
@@ -0,0 +1,109 @@
+require 'rails_helper'
+
+describe 'Private Group access', feature: true do
+ include AccessMatchers
+
+ let(:group) { create(:group, :private) }
+ let(:project) { create(:project, :private, group: group) }
+
+ let(:owner) { create(:user) }
+ let(:master) { create(:user) }
+ let(:developer) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:guest) { create(:user) }
+
+ let(:project_guest) { create(:user) }
+
+ before do
+ group.add_owner(owner)
+ group.add_master(master)
+ group.add_developer(developer)
+ group.add_reporter(reporter)
+ group.add_guest(guest)
+
+ project.team << [project_guest, :guest]
+ end
+
+ describe "Group should be private" do
+ describe '#private?' do
+ subject { group.private? }
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ describe 'GET /groups/:path' do
+ subject { group_path(group) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for project_guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ describe 'GET /groups/:path/issues' do
+ subject { issues_group_path(group) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for project_guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ describe 'GET /groups/:path/merge_requests' do
+ subject { merge_requests_group_path(group) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for project_guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+
+ describe 'GET /groups/:path/group_members' do
+ subject { group_group_members_path(group) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for project_guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ describe 'GET /groups/:path/edit' do
+ subject { edit_group_path(group) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_denied_for master }
+ it { is_expected.to be_denied_for developer }
+ it { is_expected.to be_denied_for reporter }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for project_guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
+ it { is_expected.to be_denied_for :external }
+ end
+end
diff --git a/spec/features/security/group/public_access_spec.rb b/spec/features/security/group/public_access_spec.rb
new file mode 100644
index 00000000000..db986683dbe
--- /dev/null
+++ b/spec/features/security/group/public_access_spec.rb
@@ -0,0 +1,109 @@
+require 'rails_helper'
+
+describe 'Public Group access', feature: true do
+ include AccessMatchers
+
+ let(:group) { create(:group, :public) }
+ let(:project) { create(:project, :public, group: group) }
+
+ let(:owner) { create(:user) }
+ let(:master) { create(:user) }
+ let(:developer) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:guest) { create(:user) }
+
+ let(:project_guest) { create(:user) }
+
+ before do
+ group.add_owner(owner)
+ group.add_master(master)
+ group.add_developer(developer)
+ group.add_reporter(reporter)
+ group.add_guest(guest)
+
+ project.team << [project_guest, :guest]
+ end
+
+ describe "Group should be public" do
+ describe '#public?' do
+ subject { group.public? }
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ describe 'GET /groups/:path' do
+ subject { group_path(group) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for project_guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
+ it { is_expected.to be_allowed_for :visitor }
+ end
+
+ describe 'GET /groups/:path/issues' do
+ subject { issues_group_path(group) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for project_guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
+ it { is_expected.to be_allowed_for :visitor }
+ end
+
+ describe 'GET /groups/:path/merge_requests' do
+ subject { merge_requests_group_path(group) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for project_guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
+ it { is_expected.to be_allowed_for :visitor }
+ end
+
+
+ describe 'GET /groups/:path/group_members' do
+ subject { group_group_members_path(group) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for project_guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
+ it { is_expected.to be_allowed_for :visitor }
+ end
+
+ describe 'GET /groups/:path/edit' do
+ subject { edit_group_path(group) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_denied_for master }
+ it { is_expected.to be_denied_for developer }
+ it { is_expected.to be_denied_for reporter }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for project_guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
+ it { is_expected.to be_denied_for :external }
+ end
+end
diff --git a/spec/features/security/group_access_spec.rb b/spec/features/security/group_access_spec.rb
deleted file mode 100644
index 65f8073c693..00000000000
--- a/spec/features/security/group_access_spec.rb
+++ /dev/null
@@ -1,284 +0,0 @@
-require 'rails_helper'
-
-describe 'Group access', feature: true do
- include AccessMatchers
-
- def group
- @group ||= create(:group)
- end
-
- def create_project(access_level)
- if access_level == :mixed
- create(:empty_project, :public, group: group)
- create(:empty_project, :internal, group: group)
- else
- create(:empty_project, access_level, group: group)
- end
- end
-
- def group_member(access_level, grp = group())
- level = Object.const_get("Gitlab::Access::#{access_level.upcase}")
-
- create(:user).tap do |user|
- grp.add_user(user, level)
- end
- end
-
- describe 'GET /groups/new' do
- subject { new_group_path }
-
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for :user }
- it { is_expected.to be_denied_for :visitor }
- end
-
- describe 'GET /groups/:path' do
- subject { group_path(group) }
-
- context 'with public projects' do
- let!(:project) { create_project(:public) }
-
- it { is_expected.to be_allowed_for group_member(:owner) }
- it { is_expected.to be_allowed_for group_member(:master) }
- it { is_expected.to be_allowed_for group_member(:reporter) }
- it { is_expected.to be_allowed_for group_member(:guest) }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for :user }
- it { is_expected.to be_allowed_for :visitor }
- end
-
- context 'with mixed projects' do
- let!(:project) { create_project(:mixed) }
-
- it { is_expected.to be_allowed_for group_member(:owner) }
- it { is_expected.to be_allowed_for group_member(:master) }
- it { is_expected.to be_allowed_for group_member(:reporter) }
- it { is_expected.to be_allowed_for group_member(:guest) }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for :user }
- it { is_expected.to be_allowed_for :visitor }
- end
-
- context 'with internal projects' do
- let!(:project) { create_project(:internal) }
-
- it { is_expected.to be_allowed_for group_member(:owner) }
- it { is_expected.to be_allowed_for group_member(:master) }
- it { is_expected.to be_allowed_for group_member(:reporter) }
- it { is_expected.to be_allowed_for group_member(:guest) }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for :user }
- it { is_expected.to be_allowed_for :visitor }
- end
-
- context 'with no projects' do
- it { is_expected.to be_allowed_for group_member(:owner) }
- it { is_expected.to be_allowed_for group_member(:master) }
- it { is_expected.to be_allowed_for group_member(:reporter) }
- it { is_expected.to be_allowed_for group_member(:guest) }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for :user }
- it { is_expected.to be_allowed_for :visitor }
- end
- end
-
- describe 'GET /groups/:path/issues' do
- subject { issues_group_path(group) }
-
- context 'with public projects' do
- let!(:project) { create_project(:public) }
-
- it { is_expected.to be_allowed_for group_member(:owner) }
- it { is_expected.to be_allowed_for group_member(:master) }
- it { is_expected.to be_allowed_for group_member(:reporter) }
- it { is_expected.to be_allowed_for group_member(:guest) }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for :user }
- it { is_expected.to be_allowed_for :visitor }
- end
-
- context 'with mixed projects' do
- let!(:project) { create_project(:mixed) }
-
- it { is_expected.to be_allowed_for group_member(:owner) }
- it { is_expected.to be_allowed_for group_member(:master) }
- it { is_expected.to be_allowed_for group_member(:reporter) }
- it { is_expected.to be_allowed_for group_member(:guest) }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for :user }
- it { is_expected.to be_allowed_for :visitor }
- end
-
- context 'with internal projects' do
- let!(:project) { create_project(:internal) }
-
- it { is_expected.to be_allowed_for group_member(:owner) }
- it { is_expected.to be_allowed_for group_member(:master) }
- it { is_expected.to be_allowed_for group_member(:reporter) }
- it { is_expected.to be_allowed_for group_member(:guest) }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for :user }
- it { is_expected.to be_denied_for :visitor }
- end
-
- context 'with no projects' do
- it { is_expected.to be_allowed_for group_member(:owner) }
- it { is_expected.to be_allowed_for group_member(:master) }
- it { is_expected.to be_allowed_for group_member(:reporter) }
- it { is_expected.to be_allowed_for group_member(:guest) }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_denied_for :user }
- it { is_expected.to be_denied_for :visitor }
- end
- end
-
- describe 'GET /groups/:path/merge_requests' do
- subject { merge_requests_group_path(group) }
-
- context 'with public projects' do
- let!(:project) { create_project(:public) }
-
- it { is_expected.to be_allowed_for group_member(:owner) }
- it { is_expected.to be_allowed_for group_member(:master) }
- it { is_expected.to be_allowed_for group_member(:reporter) }
- it { is_expected.to be_allowed_for group_member(:guest) }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for :user }
- it { is_expected.to be_allowed_for :visitor }
- end
-
- context 'with mixed projects' do
- let!(:project) { create_project(:mixed) }
-
- it { is_expected.to be_allowed_for group_member(:owner) }
- it { is_expected.to be_allowed_for group_member(:master) }
- it { is_expected.to be_allowed_for group_member(:reporter) }
- it { is_expected.to be_allowed_for group_member(:guest) }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for :user }
- it { is_expected.to be_allowed_for :visitor }
- end
-
- context 'with internal projects' do
- let!(:project) { create_project(:internal) }
-
- it { is_expected.to be_allowed_for group_member(:owner) }
- it { is_expected.to be_allowed_for group_member(:master) }
- it { is_expected.to be_allowed_for group_member(:reporter) }
- it { is_expected.to be_allowed_for group_member(:guest) }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for :user }
- it { is_expected.to be_denied_for :visitor }
- end
-
- context 'with no projects' do
- it { is_expected.to be_allowed_for group_member(:owner) }
- it { is_expected.to be_allowed_for group_member(:master) }
- it { is_expected.to be_allowed_for group_member(:reporter) }
- it { is_expected.to be_allowed_for group_member(:guest) }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_denied_for :user }
- it { is_expected.to be_denied_for :visitor }
- end
- end
-
- describe 'GET /groups/:path/group_members' do
- subject { group_group_members_path(group) }
-
- context 'with public projects' do
- let!(:project) { create_project(:public) }
-
- it { is_expected.to be_allowed_for group_member(:owner) }
- it { is_expected.to be_allowed_for group_member(:master) }
- it { is_expected.to be_allowed_for group_member(:reporter) }
- it { is_expected.to be_allowed_for group_member(:guest) }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for :user }
- it { is_expected.to be_allowed_for :visitor }
- end
-
- context 'with mixed projects' do
- let!(:project) { create_project(:mixed) }
-
- it { is_expected.to be_allowed_for group_member(:owner) }
- it { is_expected.to be_allowed_for group_member(:master) }
- it { is_expected.to be_allowed_for group_member(:reporter) }
- it { is_expected.to be_allowed_for group_member(:guest) }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for :user }
- it { is_expected.to be_allowed_for :visitor }
- end
-
- context 'with internal projects' do
- let!(:project) { create_project(:internal) }
-
- it { is_expected.to be_allowed_for group_member(:owner) }
- it { is_expected.to be_allowed_for group_member(:master) }
- it { is_expected.to be_allowed_for group_member(:reporter) }
- it { is_expected.to be_allowed_for group_member(:guest) }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for :user }
- it { is_expected.to be_denied_for :visitor }
- end
-
- context 'with no projects' do
- it { is_expected.to be_allowed_for group_member(:owner) }
- it { is_expected.to be_allowed_for group_member(:master) }
- it { is_expected.to be_allowed_for group_member(:reporter) }
- it { is_expected.to be_allowed_for group_member(:guest) }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_denied_for :user }
- it { is_expected.to be_denied_for :visitor }
- end
- end
-
- describe 'GET /groups/:path/edit' do
- subject { edit_group_path(group) }
-
- context 'with public projects' do
- let!(:project) { create_project(:public) }
-
- it { is_expected.to be_allowed_for group_member(:owner) }
- it { is_expected.to be_denied_for group_member(:master) }
- it { is_expected.to be_denied_for group_member(:reporter) }
- it { is_expected.to be_denied_for group_member(:guest) }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_denied_for :user }
- it { is_expected.to be_denied_for :visitor }
- end
-
- context 'with mixed projects' do
- let!(:project) { create_project(:mixed) }
-
- it { is_expected.to be_allowed_for group_member(:owner) }
- it { is_expected.to be_denied_for group_member(:master) }
- it { is_expected.to be_denied_for group_member(:reporter) }
- it { is_expected.to be_denied_for group_member(:guest) }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_denied_for :user }
- it { is_expected.to be_denied_for :visitor }
- end
-
- context 'with internal projects' do
- let!(:project) { create_project(:internal) }
-
- it { is_expected.to be_allowed_for group_member(:owner) }
- it { is_expected.to be_denied_for group_member(:master) }
- it { is_expected.to be_denied_for group_member(:reporter) }
- it { is_expected.to be_denied_for group_member(:guest) }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_denied_for :user }
- it { is_expected.to be_denied_for :visitor }
- end
-
- context 'with no projects' do
- it { is_expected.to be_allowed_for group_member(:owner) }
- it { is_expected.to be_denied_for group_member(:master) }
- it { is_expected.to be_denied_for group_member(:reporter) }
- it { is_expected.to be_denied_for group_member(:guest) }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_denied_for :user }
- it { is_expected.to be_denied_for :visitor }
- end
- end
-end
diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb
index f88c591d897..79d5bf4cf06 100644
--- a/spec/features/security/project/internal_access_spec.rb
+++ b/spec/features/security/project/internal_access_spec.rb
@@ -5,25 +5,22 @@ describe "Internal Project Access", feature: true do
let(:project) { create(:project, :internal) }
- let(:master) { create(:user) }
- let(:guest) { create(:user) }
- let(:reporter) { create(:user) }
- let(:external_team_member) { create(:user, external: true) }
+ let(:owner) { project.owner }
+ let(:master) { create(:user) }
+ let(:developer) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:guest) { create(:user) }
before do
- # full access
project.team << [master, :master]
- project.team << [external_team_member, :master]
-
- # readonly
+ project.team << [developer, :developer]
project.team << [reporter, :reporter]
+ project.team << [guest, :guest]
end
describe "Project should be internal" do
- subject { project }
-
describe '#internal?' do
- subject { super().internal? }
+ subject { project.internal? }
it { is_expected.to be_truthy }
end
end
@@ -31,78 +28,84 @@ describe "Internal Project Access", feature: true do
describe "GET /:project_path" do
subject { namespace_project_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/tree/master" do
subject { namespace_project_tree_path(project.namespace, project, project.repository.root_ref) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/commits/master" do
subject { namespace_project_commits_path(project.namespace, project, project.repository.root_ref, limit: 1) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/commit/:sha" do
subject { namespace_project_commit_path(project.namespace, project, project.repository.commit) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/compare" do
subject { namespace_project_compare_index_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/project_members" do
subject { namespace_project_project_members_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_denied_for developer }
it { is_expected.to be_denied_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -110,52 +113,56 @@ describe "Internal Project Access", feature: true do
let(:commit) { project.repository.commit }
subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore')) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/edit" do
subject { edit_namespace_project_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_denied_for developer }
it { is_expected.to be_denied_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/deploy_keys" do
subject { namespace_project_deploy_keys_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_denied_for developer }
it { is_expected.to be_denied_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/issues" do
subject { namespace_project_issues_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -163,65 +170,70 @@ describe "Internal Project Access", feature: true do
let(:issue) { create(:issue, project: project) }
subject { edit_namespace_project_issue_path(project.namespace, project, issue) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/snippets" do
subject { namespace_project_snippets_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/snippets/new" do
subject { new_namespace_project_snippet_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/merge_requests" do
subject { namespace_project_merge_requests_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/merge_requests/new" do
subject { new_namespace_project_merge_request_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_denied_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -233,13 +245,14 @@ describe "Internal Project Access", feature: true do
allow_any_instance_of(Project).to receive(:branches).and_return([])
end
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -251,26 +264,28 @@ describe "Internal Project Access", feature: true do
allow_any_instance_of(Project).to receive(:tags).and_return([])
end
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/hooks" do
subject { namespace_project_hooks_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_denied_for developer }
it { is_expected.to be_denied_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
end
diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb
index 19f287ce7a4..0a89193eb67 100644
--- a/spec/features/security/project/private_access_spec.rb
+++ b/spec/features/security/project/private_access_spec.rb
@@ -3,27 +3,24 @@ require 'spec_helper'
describe "Private Project Access", feature: true do
include AccessMatchers
- let(:project) { create(:project) }
+ let(:project) { create(:project, :private) }
- let(:master) { create(:user) }
- let(:guest) { create(:user) }
- let(:reporter) { create(:user) }
- let(:external_team_member) { create(:user, external: true) }
+ let(:owner) { project.owner }
+ let(:master) { create(:user) }
+ let(:developer) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:guest) { create(:user) }
before do
- # full access
project.team << [master, :master]
- project.team << [external_team_member, :master]
-
- # readonly
+ project.team << [developer, :developer]
project.team << [reporter, :reporter]
+ project.team << [guest, :guest]
end
describe "Project should be private" do
- subject { project }
-
describe '#private?' do
- subject { super().private? }
+ subject { project.private? }
it { is_expected.to be_truthy }
end
end
@@ -31,77 +28,84 @@ describe "Private Project Access", feature: true do
describe "GET /:project_path" do
subject { namespace_project_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_allowed_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/tree/master" do
subject { namespace_project_tree_path(project.namespace, project, project.repository.root_ref) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/commits/master" do
subject { namespace_project_commits_path(project.namespace, project, project.repository.root_ref, limit: 1) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/commit/:sha" do
subject { namespace_project_commit_path(project.namespace, project, project.repository.commit) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
- it { is_expected.to be_allowed_for external_team_member }
+ it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/compare" do
subject { namespace_project_compare_index_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/project_members" do
subject { namespace_project_project_members_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_denied_for developer }
it { is_expected.to be_denied_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -109,52 +113,56 @@ describe "Private Project Access", feature: true do
let(:commit) { project.repository.commit }
subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore'))}
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/edit" do
subject { edit_namespace_project_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_denied_for developer }
it { is_expected.to be_denied_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/deploy_keys" do
subject { namespace_project_deploy_keys_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_denied_for developer }
it { is_expected.to be_denied_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/issues" do
subject { namespace_project_issues_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_allowed_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -162,39 +170,42 @@ describe "Private Project Access", feature: true do
let(:issue) { create(:issue, project: project) }
subject { edit_namespace_project_issue_path(project.namespace, project, issue) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/snippets" do
subject { namespace_project_snippets_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_allowed_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/merge_requests" do
subject { namespace_project_merge_requests_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_allowed_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -206,13 +217,14 @@ describe "Private Project Access", feature: true do
allow_any_instance_of(Project).to receive(:branches).and_return([])
end
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -224,26 +236,28 @@ describe "Private Project Access", feature: true do
allow_any_instance_of(Project).to receive(:tags).and_return([])
end
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/hooks" do
subject { namespace_project_hooks_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_denied_for developer }
it { is_expected.to be_denied_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
- it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
end
diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
index 4e135076367..40daac89d40 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -3,29 +3,24 @@ require 'spec_helper'
describe "Public Project Access", feature: true do
include AccessMatchers
- let(:project) { create(:project) }
+ let(:project) { create(:project, :public) }
- let(:master) { create(:user) }
- let(:guest) { create(:user) }
- let(:reporter) { create(:user) }
+ let(:owner) { project.owner }
+ let(:master) { create(:user) }
+ let(:developer) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:guest) { create(:user) }
before do
- # public project
- project.visibility_level = Gitlab::VisibilityLevel::PUBLIC
- project.save!
-
- # full access
project.team << [master, :master]
-
- # readonly
+ project.team << [developer, :developer]
project.team << [reporter, :reporter]
+ project.team << [guest, :guest]
end
describe "Project should be public" do
- subject { project }
-
describe '#public?' do
- subject { super().public? }
+ subject { project.public? }
it { is_expected.to be_truthy }
end
end
@@ -33,9 +28,11 @@ describe "Public Project Access", feature: true do
describe "GET /:project_path" do
subject { namespace_project_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_allowed_for :external }
@@ -45,9 +42,11 @@ describe "Public Project Access", feature: true do
describe "GET /:project_path/tree/master" do
subject { namespace_project_tree_path(project.namespace, project, project.repository.root_ref) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_allowed_for :external }
@@ -57,9 +56,11 @@ describe "Public Project Access", feature: true do
describe "GET /:project_path/commits/master" do
subject { namespace_project_commits_path(project.namespace, project, project.repository.root_ref, limit: 1) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_allowed_for :external }
@@ -69,9 +70,11 @@ describe "Public Project Access", feature: true do
describe "GET /:project_path/commit/:sha" do
subject { namespace_project_commit_path(project.namespace, project, project.repository.commit) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_allowed_for :external }
@@ -81,9 +84,11 @@ describe "Public Project Access", feature: true do
describe "GET /:project_path/compare" do
subject { namespace_project_compare_index_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_allowed_for :external }
@@ -93,9 +98,11 @@ describe "Public Project Access", feature: true do
describe "GET /:project_path/project_members" do
subject { namespace_project_project_members_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_denied_for developer }
it { is_expected.to be_denied_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
@@ -108,9 +115,11 @@ describe "Public Project Access", feature: true do
context "when allowed for public" do
before { project.update(public_builds: true) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_allowed_for :external }
@@ -120,9 +129,11 @@ describe "Public Project Access", feature: true do
context "when disallowed for public" do
before { project.update(public_builds: false) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
@@ -138,9 +149,11 @@ describe "Public Project Access", feature: true do
context "when allowed for public" do
before { project.update(public_builds: true) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_allowed_for :external }
@@ -150,9 +163,11 @@ describe "Public Project Access", feature: true do
context "when disallowed for public" do
before { project.update(public_builds: false) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
@@ -165,9 +180,11 @@ describe "Public Project Access", feature: true do
subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore')) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_allowed_for :visitor }
@@ -176,9 +193,11 @@ describe "Public Project Access", feature: true do
describe "GET /:project_path/edit" do
subject { edit_namespace_project_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_denied_for developer }
it { is_expected.to be_denied_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
@@ -188,9 +207,11 @@ describe "Public Project Access", feature: true do
describe "GET /:project_path/deploy_keys" do
subject { namespace_project_deploy_keys_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_denied_for developer }
it { is_expected.to be_denied_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
@@ -200,9 +221,11 @@ describe "Public Project Access", feature: true do
describe "GET /:project_path/issues" do
subject { namespace_project_issues_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_allowed_for :external }
@@ -213,9 +236,11 @@ describe "Public Project Access", feature: true do
let(:issue) { create(:issue, project: project) }
subject { edit_namespace_project_issue_path(project.namespace, project, issue) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
@@ -225,9 +250,11 @@ describe "Public Project Access", feature: true do
describe "GET /:project_path/snippets" do
subject { namespace_project_snippets_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_allowed_for :external }
@@ -237,9 +264,11 @@ describe "Public Project Access", feature: true do
describe "GET /:project_path/snippets/new" do
subject { new_namespace_project_snippet_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
@@ -249,9 +278,11 @@ describe "Public Project Access", feature: true do
describe "GET /:project_path/merge_requests" do
subject { namespace_project_merge_requests_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_allowed_for :external }
@@ -261,9 +292,11 @@ describe "Public Project Access", feature: true do
describe "GET /:project_path/merge_requests/new" do
subject { new_namespace_project_merge_request_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_denied_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
@@ -278,9 +311,11 @@ describe "Public Project Access", feature: true do
allow_any_instance_of(Project).to receive(:branches).and_return([])
end
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_allowed_for :external }
@@ -295,9 +330,11 @@ describe "Public Project Access", feature: true do
allow_any_instance_of(Project).to receive(:tags).and_return([])
end
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_allowed_for :external }
@@ -307,9 +344,11 @@ describe "Public Project Access", feature: true do
describe "GET /:project_path/hooks" do
subject { namespace_project_hooks_path(project.namespace, project) }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_denied_for developer }
it { is_expected.to be_denied_for reporter }
- it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
diff --git a/spec/finders/group_projects_finder_spec.rb b/spec/finders/group_projects_finder_spec.rb
new file mode 100644
index 00000000000..fdd3849816f
--- /dev/null
+++ b/spec/finders/group_projects_finder_spec.rb
@@ -0,0 +1,89 @@
+require 'spec_helper'
+
+describe GroupProjectsFinder do
+ let(:group) { create(:group) }
+ let(:current_user) { create(:user) }
+
+ let(:finder) { described_class.new(source_user) }
+
+ let!(:public_project) { create(:project, :public, group: group, path: '1') }
+ let!(:private_project) { create(:project, :private, group: group, path: '2') }
+ let!(:shared_project_1) { create(:project, :public, path: '3') }
+ let!(:shared_project_2) { create(:project, :private, path: '4') }
+ let!(:shared_project_3) { create(:project, :internal, path: '5') }
+
+
+ before do
+ shared_project_1.project_group_links.create(group_access: Gitlab::Access::MASTER, group: group)
+ shared_project_2.project_group_links.create(group_access: Gitlab::Access::MASTER, group: group)
+ shared_project_3.project_group_links.create(group_access: Gitlab::Access::MASTER, group: group)
+ end
+
+
+ describe 'with a group member current user' do
+ before { group.add_user(current_user, Gitlab::Access::MASTER) }
+
+ context "only shared" do
+ subject { described_class.new(group, only_shared: true).execute(current_user) }
+ it { is_expected.to eq([shared_project_3, shared_project_2, shared_project_1]) }
+ end
+
+ context "only owned" do
+ subject { described_class.new(group, only_owned: true).execute(current_user) }
+ it { is_expected.to eq([private_project, public_project]) }
+ end
+
+ context "all" do
+ subject { described_class.new(group).execute(current_user) }
+ it { is_expected.to eq([shared_project_3, shared_project_2, shared_project_1, private_project, public_project]) }
+ end
+ end
+
+ describe 'without group member current_user' do
+ before { shared_project_2.team << [current_user, Gitlab::Access::MASTER] }
+
+ context "only shared" do
+ context "without external user" do
+ subject { described_class.new(group, only_shared: true).execute(current_user) }
+ it { is_expected.to eq([shared_project_3, shared_project_2, shared_project_1]) }
+ end
+
+ context "with external user" do
+ before { current_user.update_attributes(external: true) }
+ subject { described_class.new(group, only_shared: true).execute(current_user) }
+ it { is_expected.to eq([shared_project_2, shared_project_1]) }
+ end
+ end
+
+ context "only owned" do
+ context "without external user" do
+ before { private_project.team << [current_user, Gitlab::Access::MASTER] }
+ subject { described_class.new(group, only_owned: true).execute(current_user) }
+ it { is_expected.to eq([private_project, public_project]) }
+ end
+
+ context "with external user" do
+ before { current_user.update_attributes(external: true) }
+ subject { described_class.new(group, only_owned: true).execute(current_user) }
+ it { is_expected.to eq([public_project]) }
+ end
+
+ context "all" do
+ subject { described_class.new(group).execute(current_user) }
+ it { is_expected.to eq([shared_project_3, shared_project_2, shared_project_1, public_project]) }
+ end
+ end
+ end
+
+ describe "no user" do
+ context "only shared" do
+ subject { described_class.new(group, only_shared: true).execute(current_user) }
+ it { is_expected.to eq([shared_project_3, shared_project_1]) }
+ end
+
+ context "only owned" do
+ subject { described_class.new(group, only_owned: true).execute(current_user) }
+ it { is_expected.to eq([public_project]) }
+ end
+ end
+end
diff --git a/spec/finders/groups_finder_spec.rb b/spec/finders/groups_finder_spec.rb
new file mode 100644
index 00000000000..d5d111e8d15
--- /dev/null
+++ b/spec/finders/groups_finder_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe GroupsFinder do
+ describe '#execute' do
+ let(:user) { create(:user) }
+ let!(:private_group) { create(:group, :private) }
+ let!(:internal_group) { create(:group, :internal) }
+ let!(:public_group) { create(:group, :public) }
+ let(:finder) { described_class.new }
+
+ describe 'execute' do
+ describe 'without a user' do
+ subject { finder.execute }
+
+ it { is_expected.to eq([public_group]) }
+ end
+
+ describe 'with a user' do
+ subject { finder.execute(user) }
+
+ context 'normal user' do
+ it { is_expected.to eq([public_group, internal_group]) }
+ end
+
+ context 'external user' do
+ let(:user) { create(:user, external: true) }
+
+ it { is_expected.to eq([public_group]) }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/finders/joined_groups_finder_spec.rb b/spec/finders/joined_groups_finder_spec.rb
new file mode 100644
index 00000000000..f90a8e007c8
--- /dev/null
+++ b/spec/finders/joined_groups_finder_spec.rb
@@ -0,0 +1,77 @@
+require 'spec_helper'
+
+describe JoinedGroupsFinder do
+ describe '#execute' do
+ let!(:profile_owner) { create(:user) }
+ let!(:profile_visitor) { create(:user) }
+
+ let!(:private_group) { create(:group, :private) }
+ let!(:private_group_2) { create(:group, :private) }
+ let!(:internal_group) { create(:group, :internal) }
+ let!(:internal_group_2) { create(:group, :internal) }
+ let!(:public_group) { create(:group, :public) }
+ let!(:public_group_2) { create(:group, :public) }
+ let!(:finder) { described_class.new(profile_owner) }
+
+ context 'without a user' do
+ before do
+ public_group.add_master(profile_owner)
+ end
+
+ it 'only shows public groups from profile owner' do
+ expect(finder.execute).to eq([public_group])
+ end
+ end
+
+ context "with a user" do
+ before do
+ private_group.add_master(profile_owner)
+ internal_group.add_master(profile_owner)
+ public_group.add_master(profile_owner)
+ end
+
+ context "when the profile visitor is in the private group" do
+ before do
+ private_group.add_developer(profile_visitor)
+ end
+
+ it 'only shows groups where both users are authorized to see' do
+ expect(finder.execute(profile_visitor)).to eq([public_group, internal_group, private_group])
+ end
+ end
+
+ context 'if profile visitor is in one of the private group projects' do
+ before do
+ project = create(:project, :private, group: private_group, name: 'B', path: 'B')
+ project.team.add_user(profile_visitor, Gitlab::Access::DEVELOPER)
+ end
+
+ it 'shows group' do
+ expect(finder.execute(profile_visitor)).to eq([public_group, internal_group, private_group])
+ end
+ end
+
+ context 'external users' do
+ before do
+ profile_visitor.update_attributes(external: true)
+ end
+
+ context 'if not a member' do
+ it "does not show internal groups" do
+ expect(finder.execute(profile_visitor)).to eq([public_group])
+ end
+ end
+
+ context "if authorized" do
+ before do
+ internal_group.add_master(profile_visitor)
+ end
+
+ it "shows internal groups if authorized" do
+ expect(finder.execute(profile_visitor)).to eq([public_group, internal_group])
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/finders/personal_projects_finder_spec.rb b/spec/finders/personal_projects_finder_spec.rb
index 38817add456..a4681fe59d8 100644
--- a/spec/finders/personal_projects_finder_spec.rb
+++ b/spec/finders/personal_projects_finder_spec.rb
@@ -1,19 +1,17 @@
require 'spec_helper'
describe PersonalProjectsFinder do
- let(:source_user) { create(:user) }
- let(:current_user) { create(:user) }
+ let(:source_user) { create(:user) }
+ let(:current_user) { create(:user) }
+ let(:finder) { described_class.new(source_user) }
+ let!(:public_project) { create(:project, :public, namespace: source_user.namespace) }
- let(:finder) { described_class.new(source_user) }
-
- let!(:public_project) do
- create(:project, :public, namespace: source_user.namespace, name: 'A',
- path: 'A')
+ let!(:private_project) do
+ create(:project, :private, namespace: source_user.namespace, path: 'mepmep')
end
- let!(:private_project) do
- create(:project, :private, namespace: source_user.namespace, name: 'B',
- path: 'B')
+ let!(:internal_project) do
+ create(:project, :internal, namespace: source_user.namespace, path: 'C')
end
before do
@@ -29,6 +27,14 @@ describe PersonalProjectsFinder do
describe 'with a current user' do
subject { finder.execute(current_user) }
- it { is_expected.to eq([private_project, public_project]) }
+ context 'normal user' do
+ it { is_expected.to eq([internal_project, private_project, public_project]) }
+ end
+
+ context 'external' do
+ before { current_user.update_attributes(external: true) }
+
+ it { is_expected.to eq([private_project, public_project]) }
+ end
end
end
diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb
index fae0da9d898..0a1cc3b3df7 100644
--- a/spec/finders/projects_finder_spec.rb
+++ b/spec/finders/projects_finder_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe ProjectsFinder do
describe '#execute' do
let(:user) { create(:user) }
- let(:group) { create(:group) }
+ let(:group) { create(:group, :public) }
let!(:private_project) do
create(:project, :private, name: 'A', path: 'A')
diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb
index 7fdc5e5d7aa..810016c9658 100644
--- a/spec/finders/snippets_finder_spec.rb
+++ b/spec/finders/snippets_finder_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe SnippetsFinder do
let(:user) { create :user }
let(:user1) { create :user }
- let(:group) { create :group }
+ let(:group) { create :group, :public }
let(:project1) { create(:empty_project, :public, group: group) }
let(:project2) { create(:empty_project, :private, group: group) }
diff --git a/spec/helpers/groups_helper.rb b/spec/helpers/groups_helper_spec.rb
index 4ea90a80a92..4ea90a80a92 100644
--- a/spec/helpers/groups_helper.rb
+++ b/spec/helpers/groups_helper_spec.rb
diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index 4f129eca183..39042ff7e91 100644
--- a/spec/helpers/labels_helper_spec.rb
+++ b/spec/helpers/labels_helper_spec.rb
@@ -11,7 +11,7 @@ describe LabelsHelper do
end
it 'uses the instance variable' do
- expect(link_to_label(label)).to match %r{<a href="/#{@project.to_reference}/issues\?label_name=#{label.name}">.*</a>}
+ expect(link_to_label(label)).to match %r{<a href="/#{@project.to_reference}/issues\?label_name=#{label.name}"><span class="[\w\s\-]*has-tooltip".*</span></a>}
end
end
@@ -39,6 +39,14 @@ describe LabelsHelper do
end
end
+ context 'with a tooltip argument' do
+ context 'set to false' do
+ it 'does not include the has-tooltip class' do
+ expect(link_to_label(label, tooltip: false)).not_to match %r{has-tooltip}
+ end
+ end
+ end
+
context 'with block' do
it 'passes the block to link_to' do
link = link_to_label(label) { 'Foo' }
@@ -49,7 +57,7 @@ describe LabelsHelper do
context 'without block' do
it 'uses render_colored_label as the link content' do
expect(self).to receive(:render_colored_label).
- with(label).and_return('Foo')
+ with(label, tooltip: true).and_return('Foo')
expect(link_to_label(label)).to match('Foo')
end
end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 53207767581..c258cfebd73 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -11,16 +11,8 @@ describe ProjectsHelper do
describe "can_change_visibility_level?" do
let(:project) { create(:project) }
-
- let(:fork_project) do
- fork_project = create(:forked_project_with_submodules)
- fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
- fork_project.save
-
- fork_project
- end
-
let(:user) { create(:user) }
+ let(:fork_project) { Projects::ForkService.new(project, user).execute }
it "returns false if there are no appropriate permissions" do
allow(helper).to receive(:can?) { false }
@@ -94,4 +86,23 @@ describe ProjectsHelper do
end
end
end
+
+ describe 'default_clone_protocol' do
+ describe 'using HTTP' do
+ it 'returns HTTP' do
+ expect(helper).to receive(:current_user).and_return(nil)
+
+ expect(helper.send(:default_clone_protocol)).to eq('http')
+ end
+ end
+
+ describe 'using HTTPS' do
+ it 'returns HTTPS' do
+ allow(Gitlab.config.gitlab).to receive(:protocol).and_return('https')
+ expect(helper).to receive(:current_user).and_return(nil)
+
+ expect(helper.send(:default_clone_protocol)).to eq('https')
+ end
+ end
+ end
end
diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb
index cd7596a763d..ff98249570d 100644
--- a/spec/helpers/visibility_level_helper_spec.rb
+++ b/spec/helpers/visibility_level_helper_spec.rb
@@ -8,6 +8,7 @@ describe VisibilityLevelHelper do
end
let(:project) { build(:project) }
+ let(:group) { build(:group) }
let(:personal_snippet) { build(:personal_snippet) }
let(:project_snippet) { build(:project_snippet) }
@@ -19,6 +20,13 @@ describe VisibilityLevelHelper do
end
end
+ context 'used with a Group' do
+ it 'delegates groups to #group_visibility_level_description' do
+ expect(visibility_level_description(Gitlab::VisibilityLevel::PRIVATE, group))
+ .to match /group/i
+ end
+ end
+
context 'called with a Snippet' do
it 'delegates snippets to #snippet_visibility_level_description' do
expect(visibility_level_description(Gitlab::VisibilityLevel::INTERNAL, project_snippet))
@@ -58,13 +66,8 @@ describe VisibilityLevelHelper do
describe "skip_level?" do
describe "forks" do
- let(:project) { create(:project, :internal) }
- let(:fork_project) { create(:forked_project_with_submodules) }
-
- before do
- fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
- fork_project.save
- end
+ let(:project) { create(:project, :internal) }
+ let(:fork_project) { create(:project, forked_from_project: project) }
it "skips levels" do
expect(skip_level?(fork_project, Gitlab::VisibilityLevel::PUBLIC)).to be_truthy
diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb
index e2d21f53b7e..94468abcbb3 100644
--- a/spec/lib/banzai/filter/label_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb
@@ -56,7 +56,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
describe 'label span element' do
it 'includes default classes' do
doc = reference_filter("Label #{reference}")
- expect(doc.css('a span').first.attr('class')).to eq 'label color-label'
+ expect(doc.css('a span').first.attr('class')).to eq 'label color-label has-tooltip'
end
it 'includes a style attribute' do
diff --git a/spec/lib/banzai/filter/redactor_filter_spec.rb b/spec/lib/banzai/filter/redactor_filter_spec.rb
index e9bb388e361..c2c2fd0eb6a 100644
--- a/spec/lib/banzai/filter/redactor_filter_spec.rb
+++ b/spec/lib/banzai/filter/redactor_filter_spec.rb
@@ -44,12 +44,82 @@ describe Banzai::Filter::RedactorFilter, lib: true do
end
end
- context "for user references" do
+ context 'with data-issue' do
+ context 'for confidential issues' do
+ it 'removes references for non project members' do
+ non_member = create(:user)
+ project = create(:empty_project, :public)
+ issue = create(:issue, :confidential, project: project)
+
+ link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
+ doc = filter(link, current_user: non_member)
+
+ expect(doc.css('a').length).to eq 0
+ end
+
+ it 'allows references for author' do
+ author = create(:user)
+ project = create(:empty_project, :public)
+ issue = create(:issue, :confidential, project: project, author: author)
+
+ link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
+ doc = filter(link, current_user: author)
+
+ expect(doc.css('a').length).to eq 1
+ end
+
+ it 'allows references for assignee' do
+ assignee = create(:user)
+ project = create(:empty_project, :public)
+ issue = create(:issue, :confidential, project: project, assignee: assignee)
+
+ link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
+ doc = filter(link, current_user: assignee)
+ expect(doc.css('a').length).to eq 1
+ end
+
+ it 'allows references for project members' do
+ member = create(:user)
+ project = create(:empty_project, :public)
+ project.team << [member, :developer]
+ issue = create(:issue, :confidential, project: project)
+
+ link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
+ doc = filter(link, current_user: member)
+
+ expect(doc.css('a').length).to eq 1
+ end
+
+ it 'allows references for admin' do
+ admin = create(:admin)
+ project = create(:empty_project, :public)
+ issue = create(:issue, :confidential, project: project)
+
+ link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
+ doc = filter(link, current_user: admin)
+
+ expect(doc.css('a').length).to eq 1
+ end
+ end
+
+ it 'allows references for non confidential issues' do
+ user = create(:user)
+ project = create(:empty_project, :public)
+ issue = create(:issue, project: project)
+
+ link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
+ doc = filter(link, current_user: user)
+
+ expect(doc.css('a').length).to eq 1
+ end
+ end
+
+ context "for user references" do
context 'with data-group' do
it 'removes unpermitted Group references' do
user = create(:user)
- group = create(:group)
+ group = create(:group, :private)
link = reference_link(group: group.id, reference_filter: 'UserReferenceFilter')
doc = filter(link, current_user: user)
@@ -59,7 +129,7 @@ describe Banzai::Filter::RedactorFilter, lib: true do
it 'allows permitted Group references' do
user = create(:user)
- group = create(:group)
+ group = create(:group, :private)
group.add_developer(user)
link = reference_link(group: group.id, reference_filter: 'UserReferenceFilter')
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index fab6412d29f..b79b8147ce0 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -97,6 +97,28 @@ module Ci
expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0)
end
+ it "returns builds if only has a triggers keyword specified and a trigger is provided" do
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec", type: type, only: ["triggers"] }
+ })
+
+ config_processor = GitlabCiYamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref(type, "deploy", false, true).size).to eq(1)
+ end
+
+ it "does not return builds if only has a triggers keyword specified and no trigger is provided" do
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec", type: type, only: ["triggers"] }
+ })
+
+ config_processor = GitlabCiYamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0)
+ end
+
it "returns builds if only has current repository path" do
config = YAML.dump({
before_script: ["pwd"],
@@ -203,6 +225,28 @@ module Ci
expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1)
end
+ it "does not return builds if except has a triggers keyword specified and a trigger is provided" do
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec", type: type, except: ["triggers"] }
+ })
+
+ config_processor = GitlabCiYamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref(type, "deploy", false, true).size).to eq(0)
+ end
+
+ it "returns builds if except has a triggers keyword specified and no trigger is provided" do
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec", type: type, except: ["triggers"] }
+ })
+
+ config_processor = GitlabCiYamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1)
+ end
+
it "does not return builds if except has current repository path" do
config = YAML.dump({
before_script: ["pwd"],
diff --git a/spec/lib/ci/status_spec.rb b/spec/lib/ci/status_spec.rb
index 1539720bb8d..47f3df6e3ce 100644
--- a/spec/lib/ci/status_spec.rb
+++ b/spec/lib/ci/status_spec.rb
@@ -48,6 +48,29 @@ describe Ci::Status do
it { is_expected.to eq 'success' }
end
+ context 'success and canceled' do
+ let(:statuses) do
+ [create(type, status: :success), create(type, status: :canceled)]
+ end
+ it { is_expected.to eq 'failed' }
+ end
+
+ context 'all canceled' do
+ let(:statuses) do
+ [create(type, status: :canceled), create(type, status: :canceled)]
+ end
+ it { is_expected.to eq 'canceled' }
+ end
+
+ context 'success and canceled but allowed to fail' do
+ let(:statuses) do
+ [create(type, status: :success),
+ create(type, status: :canceled, allow_failure: true)]
+ end
+
+ it { is_expected.to eq 'success' }
+ end
+
context 'one finished and second running but allowed to fail' do
let(:statuses) do
[create(type, status: :success),
diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb
index 04cf11fc6f1..844fd79c991 100644
--- a/spec/lib/gitlab/closing_issue_extractor_spec.rb
+++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb
@@ -11,6 +11,7 @@ describe Gitlab::ClosingIssueExtractor, lib: true do
subject { described_class.new(project, project.creator) }
before do
+ project.team << [project.creator, :developer]
project2.team << [project.creator, :master]
end
diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb
index 0d9694f2c13..a0cbef6e6a4 100644
--- a/spec/lib/gitlab/diff/file_spec.rb
+++ b/spec/lib/gitlab/diff/file_spec.rb
@@ -18,4 +18,18 @@ describe Gitlab::Diff::File, lib: true do
describe :mode_changed? do
it { expect(diff_file.mode_changed?).to be_falsey }
end
+
+ describe '#too_large?' do
+ it 'returns true for a file that is too large' do
+ expect(diff).to receive(:too_large?).and_return(true)
+
+ expect(diff_file.too_large?).to eq(true)
+ end
+
+ it 'returns false for a file that is small enough' do
+ expect(diff).to receive(:too_large?).and_return(false)
+
+ expect(diff_file.too_large?).to eq(false)
+ end
+ end
end
diff --git a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb
new file mode 100644
index 00000000000..0a7ca3ec848
--- /dev/null
+++ b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb
@@ -0,0 +1,89 @@
+require 'spec_helper'
+
+describe Gitlab::Gfm::ReferenceRewriter do
+ let(:text) { 'some text' }
+ let(:old_project) { create(:project) }
+ let(:new_project) { create(:project) }
+ let(:user) { create(:user) }
+
+ before { old_project.team << [user, :guest] }
+
+ describe '#rewrite' do
+ subject do
+ described_class.new(text, old_project, user).rewrite(new_project)
+ end
+
+ context 'multiple issues and merge requests referenced' do
+ let!(:issue_first) { create(:issue, project: old_project) }
+ let!(:issue_second) { create(:issue, project: old_project) }
+ let!(:merge_request) { create(:merge_request, source_project: old_project) }
+
+ context 'plain text description' do
+ let(:text) { 'Description that references #1, #2 and !1' }
+
+ it { is_expected.to include issue_first.to_reference(new_project) }
+ it { is_expected.to include issue_second.to_reference(new_project) }
+ it { is_expected.to include merge_request.to_reference(new_project) }
+ end
+
+ context 'description with ignored elements' do
+ let(:text) do
+ "Hi. This references #1, but not `#2`\n" +
+ '<pre>and not !1</pre>'
+ end
+
+ it { is_expected.to include issue_first.to_reference(new_project) }
+ it { is_expected.to_not include issue_second.to_reference(new_project) }
+ it { is_expected.to_not include merge_request.to_reference(new_project) }
+ end
+
+ context 'description ambigous elements' do
+ context 'url' do
+ let(:url) { 'http://gitlab.com/#1' }
+ let(:text) { "This references #1, but not #{url}" }
+
+ it { is_expected.to include url }
+ end
+
+ context 'code' do
+ let(:text) { "#1, but not `[#1]`" }
+ it { is_expected.to eq "#{issue_first.to_reference(new_project)}, but not `[#1]`" }
+ end
+
+ context 'code reverse' do
+ let(:text) { "not `#1`, but #1" }
+ it { is_expected.to eq "not `#1`, but #{issue_first.to_reference(new_project)}" }
+ end
+
+ context 'code in random order' do
+ let(:text) { "#1, `#1`, #1, `#1`" }
+ let(:ref) { issue_first.to_reference(new_project) }
+
+ it { is_expected.to eq "#{ref}, `#1`, #{ref}, `#1`" }
+ end
+
+ context 'description with labels' do
+ let!(:label) { create(:label, id: 123, name: 'test', project: old_project) }
+ let(:project_ref) { old_project.to_reference }
+
+ context 'label referenced by id' do
+ let(:text) { '#1 and ~123' }
+ it { is_expected.to eq %Q{#{project_ref}#1 and #{project_ref}~123} }
+ end
+
+ context 'label referenced by text' do
+ let(:text) { '#1 and ~"test"' }
+ it { is_expected.to eq %Q{#{project_ref}#1 and #{project_ref}~123} }
+ end
+ end
+ end
+
+ context 'reference contains milestone' do
+ let(:milestone) { create(:milestone) }
+ let(:text) { "milestone ref: #{milestone.to_reference}" }
+
+ it { is_expected.to eq text }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb
index 09adbc07dcb..db0ff95b4f5 100644
--- a/spec/lib/gitlab/project_search_results_spec.rb
+++ b/spec/lib/gitlab/project_search_results_spec.rb
@@ -1,11 +1,12 @@
require 'spec_helper'
describe Gitlab::ProjectSearchResults, lib: true do
+ let(:user) { create(:user) }
let(:project) { create(:project) }
let(:query) { 'hello world' }
describe 'initialize with empty ref' do
- let(:results) { Gitlab::ProjectSearchResults.new(project, query, '') }
+ let(:results) { Gitlab::ProjectSearchResults.new(user, project, query, '') }
it { expect(results.project).to eq(project) }
it { expect(results.repository_ref).to be_nil }
@@ -14,10 +15,74 @@ describe Gitlab::ProjectSearchResults, lib: true do
describe 'initialize with ref' do
let(:ref) { 'refs/heads/test' }
- let(:results) { Gitlab::ProjectSearchResults.new(project, query, ref) }
+ let(:results) { Gitlab::ProjectSearchResults.new(user, project, query, ref) }
it { expect(results.project).to eq(project) }
it { expect(results.repository_ref).to eq(ref) }
it { expect(results.query).to eq('hello world') }
end
+
+ describe 'confidential issues' do
+ let(:query) { 'issue' }
+ let(:author) { create(:user) }
+ let(:assignee) { create(:user) }
+ let(:non_member) { create(:user) }
+ let(:member) { create(:user) }
+ let(:admin) { create(:admin) }
+ let!(:issue) { create(:issue, project: project, title: 'Issue 1') }
+ let!(:security_issue_1) { create(:issue, :confidential, project: project, title: 'Security issue 1', author: author) }
+ let!(:security_issue_2) { create(:issue, :confidential, title: 'Security issue 2', project: project, assignee: assignee) }
+
+ it 'should not list project confidential issues for non project members' do
+ results = described_class.new(non_member, project, query)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).not_to include security_issue_1
+ expect(issues).not_to include security_issue_2
+ expect(results.issues_count).to eq 1
+ end
+
+ it 'should list project confidential issues for author' do
+ results = described_class.new(author, project, query)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).to include security_issue_1
+ expect(issues).not_to include security_issue_2
+ expect(results.issues_count).to eq 2
+ end
+
+ it 'should list project confidential issues for assignee' do
+ results = described_class.new(assignee, project.id, query)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).not_to include security_issue_1
+ expect(issues).to include security_issue_2
+ expect(results.issues_count).to eq 2
+ end
+
+ it 'should list project confidential issues for project members' do
+ project.team << [member, :developer]
+
+ results = described_class.new(member, project, query)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).to include security_issue_1
+ expect(issues).to include security_issue_2
+ expect(results.issues_count).to eq 3
+ end
+
+ it 'should list all project issues for admin' do
+ results = described_class.new(admin, project, query)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).to include security_issue_1
+ expect(issues).to include security_issue_2
+ expect(results.issues_count).to eq 3
+ end
+ end
end
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index 7d963795e17..7c617723e6d 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
describe Gitlab::ReferenceExtractor, lib: true do
let(:project) { create(:project) }
+
subject { Gitlab::ReferenceExtractor.new(project, project.creator) }
it 'accesses valid user objects' do
@@ -41,6 +42,7 @@ describe Gitlab::ReferenceExtractor, lib: true do
end
it 'accesses valid issue objects' do
+ project.team << [project.creator, :developer]
@i0 = create(:issue, project: project)
@i1 = create(:issue, project: project)
@@ -122,4 +124,24 @@ describe Gitlab::ReferenceExtractor, lib: true do
expect(extracted).to match_array([issue])
end
end
+
+ describe '#all' do
+ let(:issue) { create(:issue, project: project) }
+ let(:label) { create(:label, project: project) }
+ let(:text) { "Ref. #{issue.to_reference} and #{label.to_reference}" }
+
+ before do
+ project.team << [project.creator, :developer]
+ subject.analyze(text)
+ end
+
+ it 'returns all referables' do
+ expect(subject.all).to match_array([issue, label])
+ end
+ end
+
+ describe '.references_pattern' do
+ subject { described_class.references_pattern }
+ it { is_expected.to be_kind_of Regexp }
+ end
end
diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb
index bb18f417858..f4afe597e8d 100644
--- a/spec/lib/gitlab/search_results_spec.rb
+++ b/spec/lib/gitlab/search_results_spec.rb
@@ -1,6 +1,7 @@
require 'spec_helper'
describe Gitlab::SearchResults do
+ let(:user) { create(:user) }
let!(:project) { create(:project, name: 'foo') }
let!(:issue) { create(:issue, project: project, title: 'foo') }
@@ -9,7 +10,7 @@ describe Gitlab::SearchResults do
end
let!(:milestone) { create(:milestone, project: project, title: 'foo') }
- let(:results) { described_class.new(Project.all, 'foo') }
+ let(:results) { described_class.new(user, Project.all, 'foo') }
describe '#total_count' do
it 'returns the total amount of search hits' do
@@ -52,4 +53,92 @@ describe Gitlab::SearchResults do
expect(results.empty?).to eq(false)
end
end
+
+ describe 'confidential issues' do
+ let(:project_1) { create(:empty_project) }
+ let(:project_2) { create(:empty_project) }
+ let(:project_3) { create(:empty_project) }
+ let(:project_4) { create(:empty_project) }
+ let(:query) { 'issue' }
+ let(:limit_projects) { Project.where(id: [project_1.id, project_2.id, project_3.id]) }
+ let(:author) { create(:user) }
+ let(:assignee) { create(:user) }
+ let(:non_member) { create(:user) }
+ let(:member) { create(:user) }
+ let(:admin) { create(:admin) }
+ let!(:issue) { create(:issue, project: project_1, title: 'Issue 1') }
+ let!(:security_issue_1) { create(:issue, :confidential, project: project_1, title: 'Security issue 1', author: author) }
+ let!(:security_issue_2) { create(:issue, :confidential, title: 'Security issue 2', project: project_1, assignee: assignee) }
+ let!(:security_issue_3) { create(:issue, :confidential, project: project_2, title: 'Security issue 3', author: author) }
+ let!(:security_issue_4) { create(:issue, :confidential, project: project_3, title: 'Security issue 4', assignee: assignee) }
+ let!(:security_issue_5) { create(:issue, :confidential, project: project_4, title: 'Security issue 5') }
+
+ it 'should not list confidential issues for non project members' do
+ results = described_class.new(non_member, limit_projects, query)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).not_to include security_issue_1
+ expect(issues).not_to include security_issue_2
+ expect(issues).not_to include security_issue_3
+ expect(issues).not_to include security_issue_4
+ expect(issues).not_to include security_issue_5
+ expect(results.issues_count).to eq 1
+ end
+
+ it 'should list confidential issues for author' do
+ results = described_class.new(author, limit_projects, query)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).to include security_issue_1
+ expect(issues).not_to include security_issue_2
+ expect(issues).to include security_issue_3
+ expect(issues).not_to include security_issue_4
+ expect(issues).not_to include security_issue_5
+ expect(results.issues_count).to eq 3
+ end
+
+ it 'should list confidential issues for assignee' do
+ results = described_class.new(assignee, limit_projects, query)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).not_to include security_issue_1
+ expect(issues).to include security_issue_2
+ expect(issues).not_to include security_issue_3
+ expect(issues).to include security_issue_4
+ expect(issues).not_to include security_issue_5
+ expect(results.issues_count).to eq 3
+ end
+
+ it 'should list confidential issues for project members' do
+ project_1.team << [member, :developer]
+ project_2.team << [member, :developer]
+
+ results = described_class.new(member, limit_projects, query)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).to include security_issue_1
+ expect(issues).to include security_issue_2
+ expect(issues).to include security_issue_3
+ expect(issues).not_to include security_issue_4
+ expect(issues).not_to include security_issue_5
+ expect(results.issues_count).to eq 4
+ end
+
+ it 'should list all issues for admin' do
+ results = described_class.new(admin, limit_projects, query)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).to include security_issue_1
+ expect(issues).to include security_issue_2
+ expect(issues).to include security_issue_3
+ expect(issues).to include security_issue_4
+ expect(issues).not_to include security_issue_5
+ expect(results.issues_count).to eq 5
+ end
+ end
end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index f910424d85b..9b47acfe0cd 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -158,6 +158,33 @@ describe Notify do
is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/
end
end
+
+ describe 'moved to another project' do
+ let(:new_issue) { create(:issue) }
+ subject { Notify.issue_moved_email(recipient, issue, new_issue, current_user) }
+
+ it_behaves_like 'an answer to an existing thread', 'issue'
+ it_behaves_like 'it should show Gmail Actions View Issue link'
+ it_behaves_like 'an unsubscribeable thread'
+
+ it 'contains description about action taken' do
+ is_expected.to have_body_text 'Issue was moved to another project'
+ end
+
+ it 'has the correct subject' do
+ is_expected.to have_subject /#{issue.title} \(##{issue.iid}\)/i
+ end
+
+ it 'contains link to new issue' do
+ new_issue_url = namespace_project_issue_path(new_issue.project.namespace,
+ new_issue.project, new_issue)
+ is_expected.to have_body_text new_issue_url
+ end
+
+ it 'contains a link to the original issue' do
+ is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/
+ end
+ end
end
context 'for merge requests' do
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 253902512c3..0e9111c8029 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -86,10 +86,21 @@ eos
let(:issue) { create :issue, project: project }
let(:other_project) { create :project, :public }
let(:other_issue) { create :issue, project: other_project }
+ let(:commiter) { create :user }
+
+ before do
+ project.team << [commiter, :developer]
+ other_project.team << [commiter, :developer]
+ end
it 'detects issues that this commit is marked as closing' do
ext_ref = "#{other_project.path_with_namespace}##{other_issue.iid}"
- allow(commit).to receive(:safe_message).and_return("Fixes ##{issue.iid} and #{ext_ref}")
+
+ allow(commit).to receive_messages(
+ safe_message: "Fixes ##{issue.iid} and #{ext_ref}",
+ committer_email: commiter.email
+ )
+
expect(commit.closes_issues).to include(issue)
expect(commit.closes_issues).to include(other_issue)
end
diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb
index 20f0c561e44..cb33edde820 100644
--- a/spec/models/concerns/mentionable_spec.rb
+++ b/spec/models/concerns/mentionable_spec.rb
@@ -48,7 +48,8 @@ describe Issue, "Mentionable" do
describe '#create_new_cross_references!' do
let(:project) { create(:project) }
- let(:issues) { create_list(:issue, 2, project: project) }
+ let(:author) { create(:author) }
+ let(:issues) { create_list(:issue, 2, project: project, author: author) }
context 'before changes are persisted' do
it 'ignores pre-existing references' do
@@ -91,7 +92,7 @@ describe Issue, "Mentionable" do
end
def create_issue(description:)
- create(:issue, project: project, description: description)
+ create(:issue, project: project, description: description, author: author)
end
end
end
diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb
new file mode 100644
index 00000000000..47c3be673c5
--- /dev/null
+++ b/spec/models/concerns/milestoneish_spec.rb
@@ -0,0 +1,104 @@
+require 'spec_helper'
+
+describe Milestone, 'Milestoneish' do
+ let(:author) { create(:user) }
+ let(:assignee) { create(:user) }
+ let(:non_member) { create(:user) }
+ let(:member) { create(:user) }
+ let(:admin) { create(:admin) }
+ let(:project) { create(:project, :public) }
+ let(:milestone) { create(:milestone, project: project) }
+ let!(:issue) { create(:issue, project: project, milestone: milestone) }
+ let!(:security_issue_1) { create(:issue, :confidential, project: project, author: author, milestone: milestone) }
+ let!(:security_issue_2) { create(:issue, :confidential, project: project, assignee: assignee, milestone: milestone) }
+ let!(:closed_issue_1) { create(:issue, :closed, project: project, milestone: milestone) }
+ let!(:closed_issue_2) { create(:issue, :closed, project: project, milestone: milestone) }
+ let!(:closed_security_issue_1) { create(:issue, :confidential, :closed, project: project, author: author, milestone: milestone) }
+ let!(:closed_security_issue_2) { create(:issue, :confidential, :closed, project: project, assignee: assignee, milestone: milestone) }
+ let!(:closed_security_issue_3) { create(:issue, :confidential, :closed, project: project, author: author, milestone: milestone) }
+ let!(:closed_security_issue_4) { create(:issue, :confidential, :closed, project: project, assignee: assignee, milestone: milestone) }
+ let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, milestone: milestone) }
+
+ before do
+ project.team << [member, :developer]
+ end
+
+ describe '#closed_items_count' do
+ it 'should not count confidential issues for non project members' do
+ expect(milestone.closed_items_count(non_member)).to eq 2
+ end
+
+ it 'should count confidential issues for author' do
+ expect(milestone.closed_items_count(author)).to eq 4
+ end
+
+ it 'should count confidential issues for assignee' do
+ expect(milestone.closed_items_count(assignee)).to eq 4
+ end
+
+ it 'should count confidential issues for project members' do
+ expect(milestone.closed_items_count(member)).to eq 6
+ end
+
+ it 'should count all issues for admin' do
+ expect(milestone.closed_items_count(admin)).to eq 6
+ end
+ end
+
+ describe '#total_items_count' do
+ it 'should not count confidential issues for non project members' do
+ expect(milestone.total_items_count(non_member)).to eq 4
+ end
+
+ it 'should count confidential issues for author' do
+ expect(milestone.total_items_count(author)).to eq 7
+ end
+
+ it 'should count confidential issues for assignee' do
+ expect(milestone.total_items_count(assignee)).to eq 7
+ end
+
+ it 'should count confidential issues for project members' do
+ expect(milestone.total_items_count(member)).to eq 10
+ end
+
+ it 'should count all issues for admin' do
+ expect(milestone.total_items_count(admin)).to eq 10
+ end
+ end
+
+ describe '#complete?' do
+ it 'returns false when has items opened' do
+ expect(milestone.complete?(non_member)).to eq false
+ end
+
+ it 'returns true when all items are closed' do
+ issue.close
+ merge_request.close
+
+ expect(milestone.complete?(non_member)).to eq true
+ end
+ end
+
+ describe '#percent_complete' do
+ it 'should not count confidential issues for non project members' do
+ expect(milestone.percent_complete(non_member)).to eq 50
+ end
+
+ it 'should count confidential issues for author' do
+ expect(milestone.percent_complete(author)).to eq 57
+ end
+
+ it 'should count confidential issues for assignee' do
+ expect(milestone.percent_complete(assignee)).to eq 57
+ end
+
+ it 'should count confidential issues for project members' do
+ expect(milestone.percent_complete(member)).to eq 60
+ end
+
+ it 'should count confidential issues for admin' do
+ expect(milestone.percent_complete(admin)).to eq 60
+ end
+ end
+end
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index ec2a923f91b..5fe44246738 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -65,6 +65,42 @@ describe Event, models: true do
it { expect(@event.author).to eq(@user) }
end
+ describe '#proper?' do
+ context 'issue event' do
+ let(:project) { create(:empty_project, :public) }
+ let(:non_member) { create(:user) }
+ let(:member) { create(:user) }
+ let(:author) { create(:author) }
+ let(:assignee) { create(:user) }
+ let(:admin) { create(:admin) }
+ let(:event) { Event.new(project: project, action: Event::CREATED, target: issue, author_id: author.id) }
+
+ before do
+ project.team << [member, :developer]
+ end
+
+ context 'for non confidential issues' do
+ let(:issue) { create(:issue, project: project, author: author, assignee: assignee) }
+
+ it { expect(event.proper?(non_member)).to eq true }
+ it { expect(event.proper?(author)).to eq true }
+ it { expect(event.proper?(assignee)).to eq true }
+ it { expect(event.proper?(member)).to eq true }
+ it { expect(event.proper?(admin)).to eq true }
+ end
+
+ context 'for confidential issues' do
+ let(:issue) { create(:issue, :confidential, project: project, author: author, assignee: assignee) }
+
+ it { expect(event.proper?(non_member)).to eq false }
+ it { expect(event.proper?(author)).to eq true }
+ it { expect(event.proper?(assignee)).to eq true }
+ it { expect(event.proper?(member)).to eq true }
+ it { expect(event.proper?(admin)).to eq true }
+ end
+ end
+ end
+
describe '.limit_recent' do
let!(:event1) { create(:closed_issue_event) }
let!(:event2) { create(:closed_issue_event) }
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index c9245fc9535..7bfca1e72c3 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -56,6 +56,23 @@ describe Group, models: true do
end
end
+ describe 'scopes' do
+ let!(:private_group) { create(:group, :private) }
+ let!(:internal_group) { create(:group, :internal) }
+
+ describe 'public_only' do
+ subject { described_class.public_only.to_a }
+
+ it{ is_expected.to eq([group]) }
+ end
+
+ describe 'public_and_internal_only' do
+ subject { described_class.public_and_internal_only.to_a }
+
+ it{ is_expected.to match_array([group, internal_group]) }
+ end
+ end
+
describe '#to_reference' do
it 'returns a String reference to the object' do
expect(group.to_reference).to eq "@#{group.name}"
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 2ccdec1eeff..3c34b1d397f 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -37,6 +37,11 @@ describe Issue, models: true do
subject { create(:issue) }
+ describe "act_as_paranoid" do
+ it { is_expected.to have_db_column(:deleted_at) }
+ it { is_expected.to have_db_index(:deleted_at) }
+ end
+
describe '#to_reference' do
it 'returns a String reference to the object' do
expect(subject.to_reference).to eq "##{subject.iid}"
@@ -130,12 +135,62 @@ describe Issue, models: true do
end
end
+ describe '#can_move?' do
+ let(:user) { create(:user) }
+ let(:issue) { create(:issue) }
+ subject { issue.can_move?(user) }
+
+ context 'user is not a member of project issue belongs to' do
+ it { is_expected.to eq false}
+ end
+
+ context 'user is reporter in project issue belongs to' do
+ let(:project) { create(:project) }
+ let(:issue) { create(:issue, project: project) }
+
+ before { project.team << [user, :reporter] }
+
+ it { is_expected.to eq true }
+
+ context 'checking destination project also' do
+ subject { issue.can_move?(user, to_project) }
+ let(:to_project) { create(:project) }
+
+ context 'destination project allowed' do
+ before { to_project.team << [user, :reporter] }
+ it { is_expected.to eq true }
+ end
+
+ context 'destination project not allowed' do
+ before { to_project.team << [user, :guest] }
+ it { is_expected.to eq false }
+ end
+ end
+ end
+ end
+
+ describe '#moved?' do
+ let(:issue) { create(:issue) }
+ subject { issue.moved? }
+
+ context 'issue not moved' do
+ it { is_expected.to eq false }
+ end
+
+ context 'issue already moved' do
+ let(:moved_to_issue) { create(:issue) }
+ let(:issue) { create(:issue, moved_to: moved_to_issue) }
+
+ it { is_expected.to eq true }
+ end
+ end
+
describe '#related_branches' do
- it "should " do
+ it "selects the right branches" do
allow(subject.project.repository).to receive(:branch_names).
- and_return(["mpempe", "#{subject.iid}mepmep", subject.to_branch_name])
+ and_return(["mpempe", "#{subject.iid}mepmep", subject.to_branch_name])
- expect(subject.related_branches).to eq [subject.to_branch_name]
+ expect(subject.related_branches).to eq([subject.to_branch_name])
end
end
@@ -151,10 +206,10 @@ describe Issue, models: true do
end
describe "#to_branch_name" do
- let(:issue) { build(:issue, title: 'a' * 30) }
+ let(:issue) { create(:issue, title: 'a' * 30) }
it "starts with the issue iid" do
- expect(issue.to_branch_name).to match /\A#{issue.iid}-a+\z/
+ expect(issue.to_branch_name).to match /-#{issue.iid}\z/
end
end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 8bf68013fd2..bd0a4ebe337 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -49,6 +49,11 @@ describe MergeRequest, models: true do
it { is_expected.to include_module(Taskable) }
end
+ describe "act_as_paranoid" do
+ it { is_expected.to have_db_column(:deleted_at) }
+ it { is_expected.to have_db_index(:deleted_at) }
+ end
+
describe 'validation' do
it { is_expected.to validate_presence_of(:target_branch) }
it { is_expected.to validate_presence_of(:source_branch) }
@@ -86,6 +91,41 @@ describe MergeRequest, models: true do
end
end
+ describe '#target_sha' do
+ context 'when the target branch does not exist anymore' do
+ subject { create(:merge_request).tap { |mr| mr.update_attribute(:target_branch, 'deleted') } }
+
+ it 'returns nil' do
+ expect(subject.target_sha).to be_nil
+ end
+ end
+ end
+
+ describe '#source_sha' do
+ let(:last_branch_commit) { subject.source_project.repository.commit(subject.source_branch) }
+
+ context 'with diffs' do
+ subject { create(:merge_request, :with_diffs) }
+ it 'returns the sha of the source branch last commit' do
+ expect(subject.source_sha).to eq(last_branch_commit.sha)
+ end
+ end
+
+ context 'without diffs' do
+ subject { create(:merge_request, :without_diffs) }
+ it 'returns the sha of the source branch last commit' do
+ expect(subject.source_sha).to eq(last_branch_commit.sha)
+ end
+ end
+
+ context 'when the merge request is being created' do
+ subject { build(:merge_request, source_branch: nil, compare_commits: []) }
+ it 'returns nil' do
+ expect(subject.source_sha).to be_nil
+ end
+ end
+ end
+
describe '#to_reference' do
it 'returns a String reference to the object' do
expect(subject.to_reference).to eq "!#{subject.iid}"
@@ -150,6 +190,7 @@ describe MergeRequest, models: true do
let(:commit2) { double('commit2', safe_message: "Fixes #{issue1.to_reference}") }
before do
+ subject.project.team << [subject.author, :developer]
allow(subject).to receive(:commits).and_return([commit0, commit1, commit2])
end
@@ -180,24 +221,11 @@ describe MergeRequest, models: true do
end
describe "#work_in_progress?" do
- it "detects the 'WIP ' prefix" do
- subject.title = "WIP #{subject.title}"
- expect(subject).to be_work_in_progress
- end
-
- it "detects the 'WIP: ' prefix" do
- subject.title = "WIP: #{subject.title}"
- expect(subject).to be_work_in_progress
- end
-
- it "detects the '[WIP] ' prefix" do
- subject.title = "[WIP] #{subject.title}"
- expect(subject).to be_work_in_progress
- end
-
- it "detects the '[WIP]' prefix" do
- subject.title = "[WIP]#{subject.title}"
- expect(subject).to be_work_in_progress
+ ['WIP ', 'WIP:', 'WIP: ', '[WIP]', '[WIP] ', ' [WIP] WIP [WIP] WIP: WIP '].each do |wip_prefix|
+ it "detects the '#{wip_prefix}' prefix" do
+ subject.title = "#{wip_prefix}#{subject.title}"
+ expect(subject).to be_work_in_progress
+ end
end
it "doesn't detect WIP for words starting with WIP" do
@@ -205,6 +233,11 @@ describe MergeRequest, models: true do
expect(subject).not_to be_work_in_progress
end
+ it "doesn't detect WIP for words containing with WIP" do
+ subject.title = "WupWipwap #{subject.title}"
+ expect(subject).not_to be_work_in_progress
+ end
+
it "doesn't detect WIP by default" do
expect(subject).not_to be_work_in_progress
end
@@ -284,6 +317,18 @@ describe MergeRequest, models: true do
let(:project) { create(:project) }
let(:fork_project) { create(:project, forked_from_project: project) }
+ context 'when the target branch does not exist anymore' do
+ subject { create(:merge_request).tap { |mr| mr.update_attribute(:target_branch, 'deleted') } }
+
+ it 'does not crash' do
+ expect{ subject.diverged_commits_count }.not_to raise_error
+ end
+
+ it 'returns 0' do
+ expect(subject.diverged_commits_count).to eq(0)
+ end
+ end
+
context 'diverged on same repository' do
subject(:merge_request_with_divergence) { create(:merge_request, :diverged, source_project: project, target_project: project) }
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index de1757bf67a..72a4ea70228 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -32,6 +32,7 @@ describe Milestone, models: true do
let(:milestone) { create(:milestone) }
let(:issue) { create(:issue) }
+ let(:user) { create(:user) }
describe "unique milestone title per project" do
it "shouldn't accept the same title in a project twice" do
@@ -50,18 +51,17 @@ describe Milestone, models: true do
describe "#percent_complete" do
it "should not count open issues" do
milestone.issues << issue
- expect(milestone.percent_complete).to eq(0)
+ expect(milestone.percent_complete(user)).to eq(0)
end
it "should count closed issues" do
issue.close
milestone.issues << issue
- expect(milestone.percent_complete).to eq(100)
+ expect(milestone.percent_complete(user)).to eq(100)
end
it "should recover from dividing by zero" do
- expect(milestone.issues).to receive(:size).and_return(0)
- expect(milestone.percent_complete).to eq(0)
+ expect(milestone.percent_complete(user)).to eq(0)
end
end
@@ -103,7 +103,7 @@ describe Milestone, models: true do
)
end
- it { expect(milestone.percent_complete).to eq(75) }
+ it { expect(milestone.percent_complete(user)).to eq(75) }
end
describe :items_count do
@@ -113,23 +113,23 @@ describe Milestone, models: true do
milestone.merge_requests << create(:merge_request)
end
- it { expect(milestone.closed_items_count).to eq(1) }
- it { expect(milestone.total_items_count).to eq(3) }
- it { expect(milestone.is_empty?).to be_falsey }
+ it { expect(milestone.closed_items_count(user)).to eq(1) }
+ it { expect(milestone.total_items_count(user)).to eq(3) }
+ it { expect(milestone.is_empty?(user)).to be_falsey }
end
describe :can_be_closed? do
it { expect(milestone.can_be_closed?).to be_truthy }
end
- describe :is_empty? do
+ describe :total_items_count do
before do
create :closed_issue, milestone: milestone
create :merge_request, milestone: milestone
end
it 'Should return total count of issues and merge requests assigned to milestone' do
- expect(milestone.total_items_count).to eq 2
+ expect(milestone.total_items_count(user)).to eq 2
end
end
diff --git a/spec/models/project_security_spec.rb b/spec/models/project_security_spec.rb
index 3643ad1b052..e12258c0874 100644
--- a/spec/models/project_security_spec.rb
+++ b/spec/models/project_security_spec.rb
@@ -18,11 +18,11 @@ describe Project, models: true do
let(:report_actions) { Ability.project_report_rules }
let(:dev_actions) { Ability.project_dev_rules }
let(:master_actions) { Ability.project_master_rules }
- let(:admin_actions) { Ability.project_admin_rules }
+ let(:owner_actions) { Ability.project_owner_rules }
describe "Non member rules" do
it "should deny for non-project users any actions" do
- admin_actions.each do |action|
+ owner_actions.each do |action|
expect(@abilities.allowed?(@u1, action, @p1)).to be_falsey
end
end
@@ -90,20 +90,20 @@ describe Project, models: true do
end
end
- describe "Admin Rules" do
+ describe "Owner Rules" do
before do
@p1.project_members.create(project: @p1, user: @u2, access_level: ProjectMember::DEVELOPER)
@p1.project_members.create(project: @p1, user: @u3, access_level: ProjectMember::MASTER)
end
it "should deny for masters admin-specific actions" do
- [admin_actions - master_actions].each do |action|
+ [owner_actions - master_actions].each do |action|
expect(@abilities.allowed?(@u2, action, @p1)).to be_falsey
end
end
it "should allow for project owner any admin actions" do
- admin_actions.each do |action|
+ owner_actions.each do |action|
expect(@abilities.allowed?(@u4, action, @p1)).to be_truthy
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index b8b9a455b83..20f06f4b7e1 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -442,7 +442,7 @@ describe Project, models: true do
end
describe '.trending' do
- let(:group) { create(:group) }
+ let(:group) { create(:group, :public) }
let(:project1) { create(:empty_project, :public, group: group) }
let(:project2) { create(:empty_project, :public, group: group) }
@@ -571,12 +571,8 @@ describe Project, models: true do
end
context 'when checking on forked project' do
- let(:forked_project) { create :forked_project_with_submodules }
-
- before do
- forked_project.build_forked_project_link(forked_to_project_id: forked_project.id, forked_from_project_id: project.id)
- forked_project.save
- end
+ let(:project) { create(:project, :internal) }
+ let(:forked_project) { create(:project, forked_from_project: project) }
it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::PRIVATE)).to be_truthy }
it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::INTERNAL)).to be_truthy }
@@ -720,4 +716,61 @@ describe Project, models: true do
expect(described_class.search_by_title('KITTENS')).to eq([project])
end
end
+
+ context 'when checking projects from groups' do
+ let(:private_group) { create(:group, visibility_level: 0) }
+ let(:internal_group) { create(:group, visibility_level: 10) }
+
+ let(:private_project) { create :project, :private, group: private_group }
+ let(:internal_project) { create :project, :internal, group: internal_group }
+
+ context 'when group is private project can not be internal' do
+ it { expect(private_project.visibility_level_allowed?(Gitlab::VisibilityLevel::INTERNAL)).to be_falsey }
+ end
+
+ context 'when group is internal project can not be public' do
+ it { expect(internal_project.visibility_level_allowed?(Gitlab::VisibilityLevel::PUBLIC)).to be_falsey }
+ end
+ end
+
+ describe '#create_repository' do
+ let(:project) { create(:project) }
+ let(:shell) { Gitlab::Shell.new }
+
+ before do
+ allow(project).to receive(:gitlab_shell).and_return(shell)
+ end
+
+ context 'using a regular repository' do
+ it 'creates the repository' do
+ expect(shell).to receive(:add_repository).
+ with(project.path_with_namespace).
+ and_return(true)
+
+ expect(project.repository).to receive(:after_create)
+
+ expect(project.create_repository).to eq(true)
+ end
+
+ it 'adds an error if the repository could not be created' do
+ expect(shell).to receive(:add_repository).
+ with(project.path_with_namespace).
+ and_return(false)
+
+ expect(project.repository).not_to receive(:after_create)
+
+ expect(project.create_repository).to eq(false)
+ expect(project.errors).not_to be_empty
+ end
+ end
+
+ context 'using a forked repository' do
+ it 'does nothing' do
+ expect(project).to receive(:forked?).and_return(true)
+ expect(shell).not_to receive(:add_repository)
+
+ project.create_repository
+ end
+ end
+ end
end
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index a2085df5bcd..532e3f013fd 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -244,6 +244,18 @@ describe ProjectWiki, models: true do
end
end
+ describe '#create_repo!' do
+ it 'creates a repository' do
+ expect(subject).to receive(:init_repo).
+ with(subject.path_with_namespace).
+ and_return(true)
+
+ expect(subject.repository).to receive(:after_create)
+
+ expect(subject.create_repo!).to be_an_instance_of(Gollum::Wiki)
+ end
+ end
+
private
def create_temp_repo(path)
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index fc2ab2d9931..7eac70ae948 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -537,6 +537,12 @@ describe Repository, models: true do
repository.before_delete
end
+
+ it 'flushes the exists cache' do
+ expect(repository).to receive(:expire_exists_cache)
+
+ repository.before_delete
+ end
end
describe 'when a repository exists' do
@@ -593,13 +599,19 @@ describe Repository, models: true do
repository.after_import
end
+
+ it 'flushes the exists cache' do
+ expect(repository).to receive(:expire_exists_cache)
+
+ repository.after_import
+ end
end
describe '#after_push_commit' do
it 'flushes the cache' do
- expect(repository).to receive(:expire_cache).with('master')
+ expect(repository).to receive(:expire_cache).with('master', '123')
- repository.after_push_commit('master')
+ repository.after_push_commit('master', '123')
end
end
@@ -619,6 +631,14 @@ describe Repository, models: true do
end
end
+ describe '#after_create' do
+ it 'flushes the exists cache' do
+ expect(repository).to receive(:expire_exists_cache)
+
+ repository.after_create
+ end
+ end
+
describe "#main_language" do
it 'shows the main language of the project' do
expect(repository.main_language).to eq("Ruby")
@@ -703,4 +723,121 @@ describe Repository, models: true do
repository.rm_tag('8.5')
end
end
+
+ describe '#avatar' do
+ it 'returns the first avatar file found in the repository' do
+ expect(repository).to receive(:blob_at_branch).
+ with('master', 'logo.png').
+ and_return(true)
+
+ expect(repository.avatar).to eq('logo.png')
+ end
+
+ it 'caches the output' do
+ allow(repository).to receive(:blob_at_branch).
+ with('master', 'logo.png').
+ and_return(true)
+
+ expect(repository.avatar).to eq('logo.png')
+
+ expect(repository).to_not receive(:blob_at_branch)
+ expect(repository.avatar).to eq('logo.png')
+ end
+ end
+
+ describe '#expire_avatar_cache' do
+ let(:cache) { repository.send(:cache) }
+
+ before do
+ allow(repository).to receive(:cache).and_return(cache)
+ end
+
+ context 'without a branch or revision' do
+ it 'flushes the cache' do
+ expect(cache).to receive(:expire).with(:avatar)
+
+ repository.expire_avatar_cache
+ end
+ end
+
+ context 'with a branch' do
+ it 'does not flush the cache if the branch is not the default branch' do
+ expect(cache).not_to receive(:expire)
+
+ repository.expire_avatar_cache('cats')
+ end
+
+ it 'flushes the cache if the branch equals the default branch' do
+ expect(cache).to receive(:expire).with(:avatar)
+
+ repository.expire_avatar_cache(repository.root_ref)
+ end
+ end
+
+ context 'with a branch and revision' do
+ let(:commit) { double(:commit) }
+
+ before do
+ allow(repository).to receive(:commit).and_return(commit)
+ end
+
+ it 'does not flush the cache if the commit does not change any logos' do
+ diff = double(:diff, new_path: 'test.txt')
+
+ expect(commit).to receive(:diffs).and_return([diff])
+ expect(cache).not_to receive(:expire)
+
+ repository.expire_avatar_cache(repository.root_ref, '123')
+ end
+
+ it 'flushes the cache if the commit changes any of the logos' do
+ diff = double(:diff, new_path: Repository::AVATAR_FILES[0])
+
+ expect(commit).to receive(:diffs).and_return([diff])
+ expect(cache).to receive(:expire).with(:avatar)
+
+ repository.expire_avatar_cache(repository.root_ref, '123')
+ end
+ end
+ end
+
+ describe '#expire_exists_cache' do
+ let(:cache) { repository.send(:cache) }
+
+ it 'expires the cache' do
+ expect(cache).to receive(:expire).with(:exists?)
+
+ repository.expire_exists_cache
+ end
+ end
+
+ describe '#build_cache' do
+ let(:cache) { repository.send(:cache) }
+
+ it 'builds the caches if they do not already exist' do
+ expect(cache).to receive(:exist?).
+ exactly(repository.cache_keys.length).
+ times.
+ and_return(false)
+
+ repository.cache_keys.each do |key|
+ expect(repository).to receive(key)
+ end
+
+ repository.build_cache
+ end
+
+ it 'does not build any caches that already exist' do
+ expect(cache).to receive(:exist?).
+ exactly(repository.cache_keys.length).
+ times.
+ and_return(true)
+
+ repository.cache_keys.each do |key|
+ expect(repository).to_not receive(key)
+ end
+
+ repository.build_cache
+ end
+ end
end
diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb
index fe9ea7e7d1e..d9b86b9368f 100644
--- a/spec/models/todo_spec.rb
+++ b/spec/models/todo_spec.rb
@@ -5,19 +5,24 @@
# id :integer not null, primary key
# user_id :integer not null
# project_id :integer not null
-# target_id :integer not null
+# target_id :integer
# target_type :string not null
# author_id :integer
-# note_id :integer
# action :integer not null
# state :string not null
# created_at :datetime
# updated_at :datetime
+# note_id :integer
+# commit_id :string
#
require 'spec_helper'
describe Todo, models: true do
+ let(:project) { create(:project) }
+ let(:commit) { project.commit }
+ let(:issue) { create(:issue) }
+
describe 'relationships' do
it { is_expected.to belong_to(:author).class_name("User") }
it { is_expected.to belong_to(:note) }
@@ -33,8 +38,22 @@ describe Todo, models: true do
describe 'validations' do
it { is_expected.to validate_presence_of(:action) }
- it { is_expected.to validate_presence_of(:target) }
+ it { is_expected.to validate_presence_of(:target_type) }
it { is_expected.to validate_presence_of(:user) }
+
+ context 'for commits' do
+ subject { described_class.new(target_type: 'Commit') }
+
+ it { is_expected.to validate_presence_of(:commit_id) }
+ it { is_expected.not_to validate_presence_of(:target_id) }
+ end
+
+ context 'for issuables' do
+ subject { described_class.new(target: issue) }
+
+ it { is_expected.to validate_presence_of(:target_id) }
+ it { is_expected.not_to validate_presence_of(:commit_id) }
+ end
end
describe '#body' do
@@ -55,15 +74,69 @@ describe Todo, models: true do
end
end
- describe '#done!' do
+ describe '#done' do
it 'changes state to done' do
todo = create(:todo, state: :pending)
- expect { todo.done! }.to change(todo, :state).from('pending').to('done')
+ expect { todo.done }.to change(todo, :state).from('pending').to('done')
end
it 'does not raise error when is already done' do
todo = create(:todo, state: :done)
- expect { todo.done! }.not_to raise_error
+ expect { todo.done }.not_to raise_error
+ end
+ end
+
+ describe '#for_commit?' do
+ it 'returns true when target is a commit' do
+ subject.target_type = 'Commit'
+ expect(subject.for_commit?).to eq true
+ end
+
+ it 'returns false when target is an issuable' do
+ subject.target_type = 'Issue'
+ expect(subject.for_commit?).to eq false
+ end
+ end
+
+ describe '#target' do
+ context 'for commits' do
+ it 'returns an instance of Commit when exists' do
+ subject.project = project
+ subject.target_type = 'Commit'
+ subject.commit_id = commit.id
+
+ expect(subject.target).to be_a(Commit)
+ expect(subject.target).to eq commit
+ end
+
+ it 'returns nil when does not exists' do
+ subject.project = project
+ subject.target_type = 'Commit'
+ subject.commit_id = 'xxxx'
+
+ expect(subject.target).to be_nil
+ end
+ end
+
+ it 'returns the issuable for issuables' do
+ subject.target_id = issue.id
+ subject.target_type = issue.class.name
+ expect(subject.target).to eq issue
+ end
+ end
+
+ describe '#target_reference' do
+ it 'returns the short commit id for commits' do
+ subject.project = project
+ subject.target_type = 'Commit'
+ subject.commit_id = commit.id
+
+ expect(subject.target_reference).to eq commit.short_id
+ end
+
+ it 'returns reference for issuables' do
+ subject.target = issue
+ expect(subject.target_reference).to eq issue.to_reference
end
end
end
diff --git a/spec/requests/api/group_members_spec.rb b/spec/requests/api/group_members_spec.rb
index dd5baa44cb2..3e8b4aa1f88 100644
--- a/spec/requests/api/group_members_spec.rb
+++ b/spec/requests/api/group_members_spec.rb
@@ -11,7 +11,7 @@ describe API::API, api: true do
let(:stranger) { create(:user) }
let!(:group_with_members) do
- group = create(:group)
+ group = create(:group, :private)
group.add_users([reporter.id], GroupMember::REPORTER)
group.add_users([developer.id], GroupMember::DEVELOPER)
group.add_users([master.id], GroupMember::MASTER)
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 4cfa49d1566..41c9cacd455 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -9,7 +9,7 @@ describe API::API, api: true do
let(:admin) { create(:admin) }
let(:avatar_file_path) { File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') }
let!(:group1) { create(:group, avatar: File.open(avatar_file_path)) }
- let!(:group2) { create(:group) }
+ let!(:group2) { create(:group, :private) }
let!(:project1) { create(:project, namespace: group1) }
let!(:project2) { create(:project, namespace: group2) }
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 571ea2dae4c..ce55cb7b0ae 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -2,8 +2,12 @@ require 'spec_helper'
describe API::API, api: true do
include ApiHelpers
- let(:user) { create(:user) }
- let!(:project) { create(:project, namespace: user.namespace ) }
+ let(:user) { create(:user) }
+ let(:non_member) { create(:user) }
+ let(:author) { create(:author) }
+ let(:assignee) { create(:assignee) }
+ let(:admin) { create(:user, :admin) }
+ let!(:project) { create(:project, :public, namespace: user.namespace ) }
let!(:closed_issue) do
create :closed_issue,
author: user,
@@ -12,6 +16,13 @@ describe API::API, api: true do
state: :closed,
milestone: milestone
end
+ let!(:confidential_issue) do
+ create :issue,
+ :confidential,
+ project: project,
+ author: author,
+ assignee: assignee
+ end
let!(:issue) do
create :issue,
author: user,
@@ -123,10 +134,43 @@ describe API::API, api: true do
let(:base_url) { "/projects/#{project.id}" }
let(:title) { milestone.title }
- it "should return project issues" do
+ it 'should return project issues without confidential issues for non project members' do
+ get api("#{base_url}/issues", non_member)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(2)
+ expect(json_response.first['title']).to eq(issue.title)
+ end
+
+ it 'should return project confidential issues for author' do
+ get api("#{base_url}/issues", author)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
+ expect(json_response.first['title']).to eq(issue.title)
+ end
+
+ it 'should return project confidential issues for assignee' do
+ get api("#{base_url}/issues", assignee)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
+ expect(json_response.first['title']).to eq(issue.title)
+ end
+
+ it 'should return project issues with confidential issues for project members' do
get api("#{base_url}/issues", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
+ expect(json_response.first['title']).to eq(issue.title)
+ end
+
+ it 'should return project confidential issues for admin' do
+ get api("#{base_url}/issues", admin)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
expect(json_response.first['title']).to eq(issue.title)
end
@@ -206,6 +250,41 @@ describe API::API, api: true do
get api("/projects/#{project.id}/issues/54321", user)
expect(response.status).to eq(404)
end
+
+ context 'confidential issues' do
+ it "should return 404 for non project members" do
+ get api("/projects/#{project.id}/issues/#{confidential_issue.id}", non_member)
+ expect(response.status).to eq(404)
+ end
+
+ it "should return confidential issue for project members" do
+ get api("/projects/#{project.id}/issues/#{confidential_issue.id}", user)
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq(confidential_issue.title)
+ expect(json_response['iid']).to eq(confidential_issue.iid)
+ end
+
+ it "should return confidential issue for author" do
+ get api("/projects/#{project.id}/issues/#{confidential_issue.id}", author)
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq(confidential_issue.title)
+ expect(json_response['iid']).to eq(confidential_issue.iid)
+ end
+
+ it "should return confidential issue for assignee" do
+ get api("/projects/#{project.id}/issues/#{confidential_issue.id}", assignee)
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq(confidential_issue.title)
+ expect(json_response['iid']).to eq(confidential_issue.iid)
+ end
+
+ it "should return confidential issue for admin" do
+ get api("/projects/#{project.id}/issues/#{confidential_issue.id}", admin)
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq(confidential_issue.title)
+ expect(json_response['iid']).to eq(confidential_issue.iid)
+ end
+ end
end
describe "POST /projects/:id/issues" do
@@ -294,6 +373,35 @@ describe API::API, api: true do
expect(response.status).to eq(400)
expect(json_response['message']['labels']['?']['title']).to eq(['is invalid'])
end
+
+ context 'confidential issues' do
+ it "should return 403 for non project members" do
+ put api("/projects/#{project.id}/issues/#{confidential_issue.id}", non_member),
+ title: 'updated title'
+ expect(response.status).to eq(403)
+ end
+
+ it "should update a confidential issue for project members" do
+ put api("/projects/#{project.id}/issues/#{confidential_issue.id}", user),
+ title: 'updated title'
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq('updated title')
+ end
+
+ it "should update a confidential issue for author" do
+ put api("/projects/#{project.id}/issues/#{confidential_issue.id}", author),
+ title: 'updated title'
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq('updated title')
+ end
+
+ it "should update a confidential issue for admin" do
+ put api("/projects/#{project.id}/issues/#{confidential_issue.id}", admin),
+ title: 'updated title'
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq('updated title')
+ end
+ end
end
describe 'PUT /projects/:id/issues/:issue_id to update labels' do
@@ -361,9 +469,25 @@ describe API::API, api: true do
end
describe "DELETE /projects/:id/issues/:issue_id" do
- it "should delete a project issue" do
- delete api("/projects/#{project.id}/issues/#{issue.id}", user)
- expect(response.status).to eq(405)
+ it "rejects a non member from deleting an issue" do
+ delete api("/projects/#{project.id}/issues/#{issue.id}", non_member)
+ expect(response.status).to be(403)
+ end
+
+ it "rejects a developer from deleting an issue" do
+ delete api("/projects/#{project.id}/issues/#{issue.id}", author)
+ expect(response.status).to be(403)
+ end
+
+ context "when the user is project owner" do
+ let(:owner) { create(:user) }
+ let(:project) { create(:project, namespace: owner.namespace) }
+
+ it "deletes the issue if an admin requests it" do
+ delete api("/projects/#{project.id}/issues/#{issue.id}", owner)
+ expect(response.status).to eq(200)
+ expect(json_response['state']).to eq 'opened'
+ end
end
end
end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 4fd1df25568..c9175a4d6eb 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -2,15 +2,17 @@ require "spec_helper"
describe API::API, api: true do
include ApiHelpers
- let(:base_time) { Time.now }
- let(:user) { create(:user) }
- let!(:project) {create(:project, creator_id: user.id, namespace: user.namespace) }
+ let(:base_time) { Time.now }
+ let(:user) { create(:user) }
+ let(:admin) { create(:user, :admin) }
+ let(:non_member) { create(:user) }
+ let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time) }
let!(:merge_request_closed) { create(:merge_request, state: "closed", author: user, assignee: user, source_project: project, target_project: project, title: "Closed test", created_at: base_time + 1.second) }
let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds) }
- let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
- let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") }
- let(:milestone) { create(:milestone, title: '1.0.0', project: project) }
+ let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
+ let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") }
+ let(:milestone) { create(:milestone, title: '1.0.0', project: project) }
before do
project.team << [user, :reporters]
@@ -315,6 +317,29 @@ describe API::API, api: true do
end
end
+ describe "DELETE /projects/:id/merge_requests/:merge_request_id" do
+ context "when the user is developer" do
+ let(:developer) { create(:user) }
+
+ before do
+ project.team << [developer, :developer]
+ end
+
+ it "denies the deletion of the merge request" do
+ delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}", developer)
+ expect(response.status).to be(403)
+ end
+ end
+
+ context "when the user is project owner" do
+ it "destroys the merge request owners can destroy" do
+ delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user)
+
+ expect(response.status).to eq(200)
+ end
+ end
+ end
+
describe "PUT /projects/:id/merge_requests/:merge_request_id to close MR" do
it "should return merge_request" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: "close"
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index a6699cdc81c..a5d4985dc78 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -275,6 +275,7 @@ describe API::API, api: true do
it 'should not allow a non-admin to use a restricted visibility level' do
post api('/projects', user), @project
+
expect(response.status).to eq(400)
expect(json_response['message']['visibility_level'].first).to(
match('restricted by your GitLab administrator')
diff --git a/spec/services/create_snippet_service_spec.rb b/spec/services/create_snippet_service_spec.rb
index c800dea04fa..7a850066bf8 100644
--- a/spec/services/create_snippet_service_spec.rb
+++ b/spec/services/create_snippet_service_spec.rb
@@ -23,7 +23,7 @@ describe CreateSnippetService, services: true do
snippet = create_snippet(nil, @user, @opts)
expect(snippet.errors.messages).to have_key(:visibility_level)
expect(snippet.errors.messages[:visibility_level].first).to(
- match('Public visibility has been restricted')
+ match('has been restricted')
)
end
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index 145bc937560..8490a729e51 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -29,7 +29,8 @@ describe GitPushService, services: true do
it { is_expected.to be_truthy }
it 'flushes general cached data' do
- expect(project.repository).to receive(:expire_cache).with('master')
+ expect(project.repository).to receive(:expire_cache).
+ with('master', newrev)
subject
end
@@ -46,7 +47,8 @@ describe GitPushService, services: true do
it { is_expected.to be_truthy }
it 'flushes general cached data' do
- expect(project.repository).to receive(:expire_cache).with('master')
+ expect(project.repository).to receive(:expire_cache).
+ with('master', newrev)
subject
end
@@ -65,7 +67,8 @@ describe GitPushService, services: true do
end
it 'flushes general cached data' do
- expect(project.repository).to receive(:expire_cache).with('master')
+ expect(project.repository).to receive(:expire_cache).
+ with('master', newrev)
subject
end
@@ -212,12 +215,16 @@ describe GitPushService, services: true do
let(:commit) { project.commit }
before do
+ project.team << [commit_author, :developer]
+ project.team << [user, :developer]
+
allow(commit).to receive_messages(
safe_message: "this commit \n mentions #{issue.to_reference}",
references: [issue],
author_name: commit_author.name,
author_email: commit_author.email
)
+
allow(project.repository).to receive(:commits_between).and_return([commit])
end
diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb
new file mode 100644
index 00000000000..6aefb48a4e8
--- /dev/null
+++ b/spec/services/groups/create_service_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe Groups::CreateService, services: true do
+ let!(:user) { create(:user) }
+ let!(:group_params) { { path: "group_path", visibility_level: Gitlab::VisibilityLevel::PUBLIC } }
+
+ describe "execute" do
+ let!(:service) { described_class.new(user, group_params ) }
+ subject { service.execute }
+
+ context "create groups without restricted visibility level" do
+ it { is_expected.to be_persisted }
+ end
+
+ context "cannot create group with restricted visibility level" do
+ before { allow(current_application_settings).to receive(:restricted_visibility_levels).and_return([Gitlab::VisibilityLevel::PUBLIC]) }
+ it { is_expected.to_not be_persisted }
+ end
+ end
+end
diff --git a/spec/services/groups/update_service_spec.rb b/spec/services/groups/update_service_spec.rb
new file mode 100644
index 00000000000..9c2331144a0
--- /dev/null
+++ b/spec/services/groups/update_service_spec.rb
@@ -0,0 +1,52 @@
+require 'spec_helper'
+
+describe Groups::UpdateService, services: true do
+ let!(:user) { create(:user) }
+ let!(:private_group) { create(:group, :private) }
+ let!(:internal_group) { create(:group, :internal) }
+ let!(:public_group) { create(:group, :public) }
+
+ describe "#execute" do
+ context "project visibility_level validation" do
+ context "public group with public projects" do
+ let!(:service) { described_class.new(public_group, user, visibility_level: Gitlab::VisibilityLevel::INTERNAL ) }
+
+ before do
+ public_group.add_user(user, Gitlab::Access::MASTER)
+ create(:project, :public, group: public_group)
+ end
+
+ it "does not change permission level" do
+ service.execute
+ expect(public_group.errors.count).to eq(1)
+ end
+ end
+
+ context "internal group with internal project" do
+ let!(:service) { described_class.new(internal_group, user, visibility_level: Gitlab::VisibilityLevel::PRIVATE ) }
+
+ before do
+ internal_group.add_user(user, Gitlab::Access::MASTER)
+ create(:project, :internal, group: internal_group)
+ end
+
+ it "does not change permission level" do
+ service.execute
+ expect(internal_group.errors.count).to eq(1)
+ end
+ end
+ end
+ end
+
+ context "unauthorized visibility_level validation" do
+ let!(:service) { described_class.new(internal_group, user, visibility_level: 99 ) }
+ before do
+ internal_group.add_user(user, Gitlab::Access::MASTER)
+ end
+
+ it "does not change permission level" do
+ service.execute
+ expect(internal_group.errors.count).to eq(1)
+ end
+ end
+end
diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb
new file mode 100644
index 00000000000..14cc20e529a
--- /dev/null
+++ b/spec/services/issues/move_service_spec.rb
@@ -0,0 +1,213 @@
+require 'spec_helper'
+
+describe Issues::MoveService, services: true do
+ let(:user) { create(:user) }
+ let(:author) { create(:user) }
+ let(:title) { 'Some issue' }
+ let(:description) { 'Some issue description' }
+ let(:old_project) { create(:project) }
+ let(:new_project) { create(:project) }
+
+ let(:old_issue) do
+ create(:issue, title: title, description: description,
+ project: old_project, author: author)
+ end
+
+ let(:move_service) do
+ described_class.new(old_project, user)
+ end
+
+ shared_context 'user can move issue' do
+ before do
+ old_project.team << [user, :reporter]
+ new_project.team << [user, :reporter]
+ end
+ end
+
+ describe '#execute' do
+ shared_context 'issue move executed' do
+ let!(:new_issue) { move_service.execute(old_issue, new_project) }
+ end
+
+ context 'issue movable' do
+ include_context 'user can move issue'
+
+ context 'generic issue' do
+ include_context 'issue move executed'
+
+ it 'creates a new issue in a new project' do
+ expect(new_issue.project).to eq new_project
+ end
+
+ it 'rewrites issue title' do
+ expect(new_issue.title).to eq title
+ end
+
+ it 'rewrites issue description' do
+ expect(new_issue.description).to eq description
+ end
+
+ it 'adds system note to old issue at the end' do
+ expect(old_issue.notes.last.note).to match /^Moved to/
+ end
+
+ it 'adds system note to new issue at the end' do
+ expect(new_issue.notes.last.note).to match /^Moved from/
+ end
+
+ it 'closes old issue' do
+ expect(old_issue.closed?).to be true
+ end
+
+ it 'persists new issue' do
+ expect(new_issue.persisted?).to be true
+ end
+
+ it 'persists all changes' do
+ expect(old_issue.changed?).to be false
+ expect(new_issue.changed?).to be false
+ end
+
+ it 'preserves author' do
+ expect(new_issue.author).to eq author
+ end
+
+ it 'removes data that is invalid in new context' do
+ expect(new_issue.milestone).to be_nil
+ expect(new_issue.labels).to be_empty
+ end
+
+ it 'creates a new internal id for issue' do
+ expect(new_issue.iid).to be 1
+ end
+
+ it 'marks issue as moved' do
+ expect(old_issue.moved?).to eq true
+ expect(old_issue.moved_to).to eq new_issue
+ end
+ end
+
+ context 'issue with notes' do
+ context 'notes without references' do
+ let(:notes_params) do
+ [{ system: false, note: 'Some comment 1' },
+ { system: true, note: 'Some system note' },
+ { system: false, note: 'Some comment 2' }]
+ end
+
+ let(:notes_contents) { notes_params.map { |n| n[:note] } }
+
+ before do
+ note_params = { noteable: old_issue, project: old_project, author: author }
+ notes_params.each do |note|
+ create(:note, note_params.merge(note))
+ end
+ end
+
+ include_context 'issue move executed'
+
+ let(:all_notes) { new_issue.notes.order('id ASC') }
+ let(:system_notes) { all_notes.system }
+ let(:user_notes) { all_notes.user }
+
+ it 'rewrites existing notes in valid order' do
+ expect(all_notes.pluck(:note).first(3)).to eq notes_contents
+ end
+
+ it 'adds a system note about move after rewritten notes' do
+ expect(system_notes.last.note).to match /^Moved from/
+ end
+
+ it 'preserves orignal author of comment' do
+ expect(user_notes.pluck(:author_id)).to all(eq(author.id))
+ end
+
+ it 'preserves time when note has been created at' do
+ expect(old_issue.notes.first.created_at)
+ .to eq new_issue.notes.first.created_at
+ end
+ end
+
+ context 'notes with references' do
+ before do
+ create(:merge_request, source_project: old_project)
+ create(:note, noteable: old_issue, project: old_project, author: author,
+ note: 'Note with reference to merge request !1')
+ end
+
+ include_context 'issue move executed'
+ let(:new_note) { new_issue.notes.first }
+
+ it 'rewrites references using a cross reference to old project' do
+ expect(new_note.note)
+ .to eq "Note with reference to merge request #{old_project.to_reference}!1"
+ end
+ end
+ end
+
+ describe 'rewritting references' do
+ include_context 'issue move executed'
+
+ context 'issue reference' do
+ let(:another_issue) { create(:issue, project: old_project) }
+ let(:description) { "Some description #{another_issue.to_reference}" }
+
+ it 'rewrites referenced issues creating cross project reference' do
+ expect(new_issue.description)
+ .to eq "Some description #{old_project.to_reference}#{another_issue.to_reference}"
+ end
+ end
+ end
+
+ context 'moving to same project' do
+ let(:new_project) { old_project }
+
+ it 'raises error' do
+ expect { move_service.execute(old_issue, new_project) }
+ .to raise_error(StandardError, /Cannot move issue/)
+ end
+ end
+ end
+
+ describe 'move permissions' do
+ let(:move) { move_service.execute(old_issue, new_project) }
+
+ context 'user is reporter in both projects' do
+ include_context 'user can move issue'
+ it { expect { move }.to_not raise_error }
+ end
+
+ context 'user is reporter only in new project' do
+ before { new_project.team << [user, :reporter] }
+ it { expect { move }.to raise_error(StandardError, /permissions/) }
+ end
+
+ context 'user is reporter only in old project' do
+ before { old_project.team << [user, :reporter] }
+ it { expect { move }.to raise_error(StandardError, /permissions/) }
+ end
+
+ context 'user is reporter in one project and guest in another' do
+ before do
+ new_project.team << [user, :guest]
+ old_project.team << [user, :reporter]
+ end
+
+ it { expect { move }.to raise_error(StandardError, /permissions/) }
+ end
+
+ context 'issue has already been moved' do
+ include_context 'user can move issue'
+
+ let(:moved_to_issue) { create(:issue) }
+
+ let(:old_issue) do
+ create(:issue, project: old_project, author: author,
+ moved_to: moved_to_issue)
+ end
+
+ it { expect { move }.to raise_error(StandardError, /permissions/) }
+ end
+ end
+ end
+end
diff --git a/spec/services/projects/autocomplete_service_spec.rb b/spec/services/projects/autocomplete_service_spec.rb
new file mode 100644
index 00000000000..6108c26a78b
--- /dev/null
+++ b/spec/services/projects/autocomplete_service_spec.rb
@@ -0,0 +1,79 @@
+require 'spec_helper'
+
+describe Projects::AutocompleteService, services: true do
+ describe '#issues' do
+ describe 'confidential issues' do
+ let(:author) { create(:user) }
+ let(:assignee) { create(:user) }
+ let(:non_member) { create(:user) }
+ let(:member) { create(:user) }
+ let(:admin) { create(:admin) }
+ let(:project) { create(:empty_project, :public) }
+ let!(:issue) { create(:issue, project: project, title: 'Issue 1') }
+ let!(:security_issue_1) { create(:issue, :confidential, project: project, title: 'Security issue 1', author: author) }
+ let!(:security_issue_2) { create(:issue, :confidential, title: 'Security issue 2', project: project, assignee: assignee) }
+
+ it 'should not list project confidential issues for guests' do
+ autocomplete = described_class.new(project, nil)
+ issues = autocomplete.issues.map(&:iid)
+
+ expect(issues).to include issue.iid
+ expect(issues).not_to include security_issue_1.iid
+ expect(issues).not_to include security_issue_2.iid
+ expect(issues.count).to eq 1
+ end
+
+ it 'should not list project confidential issues for non project members' do
+ autocomplete = described_class.new(project, non_member)
+ issues = autocomplete.issues.map(&:iid)
+
+ expect(issues).to include issue.iid
+ expect(issues).not_to include security_issue_1.iid
+ expect(issues).not_to include security_issue_2.iid
+ expect(issues.count).to eq 1
+ end
+
+ it 'should list project confidential issues for author' do
+ autocomplete = described_class.new(project, author)
+ issues = autocomplete.issues.map(&:iid)
+
+ expect(issues).to include issue.iid
+ expect(issues).to include security_issue_1.iid
+ expect(issues).not_to include security_issue_2.iid
+ expect(issues.count).to eq 2
+ end
+
+ it 'should list project confidential issues for assignee' do
+ autocomplete = described_class.new(project, assignee)
+ issues = autocomplete.issues.map(&:iid)
+
+ expect(issues).to include issue.iid
+ expect(issues).not_to include security_issue_1.iid
+ expect(issues).to include security_issue_2.iid
+ expect(issues.count).to eq 2
+ end
+
+ it 'should list project confidential issues for project members' do
+ project.team << [member, :developer]
+
+ autocomplete = described_class.new(project, member)
+ issues = autocomplete.issues.map(&:iid)
+
+ expect(issues).to include issue.iid
+ expect(issues).to include security_issue_1.iid
+ expect(issues).to include security_issue_2.iid
+ expect(issues.count).to eq 3
+ end
+
+ it 'should list all project issues for admin' do
+ autocomplete = described_class.new(project, admin)
+ issues = autocomplete.issues.map(&:iid)
+
+ expect(issues).to include issue.iid
+ expect(issues).to include security_issue_1.iid
+ expect(issues).to include security_issue_2.iid
+ expect(issues.count).to eq 3
+ end
+ end
+ end
+end
diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb
index 93bf1b81fbe..4c5ced7e746 100644
--- a/spec/services/projects/housekeeping_service_spec.rb
+++ b/spec/services/projects/housekeeping_service_spec.rb
@@ -12,7 +12,7 @@ describe Projects::HousekeepingService do
it 'enqueues a sidekiq job' do
expect(subject).to receive(:try_obtain_lease).and_return(true)
- expect(GitlabShellWorker).to receive(:perform_async).with(:gc, project.path_with_namespace)
+ expect(GitlabShellOneShotWorker).to receive(:perform_async).with(:gc, project.path_with_namespace)
subject.execute
expect(project.pushes_since_gc).to eq(0)
@@ -20,7 +20,7 @@ describe Projects::HousekeepingService do
it 'does not enqueue a job when no lease can be obtained' do
expect(subject).to receive(:try_obtain_lease).and_return(false)
- expect(GitlabShellWorker).not_to receive(:perform_async)
+ expect(GitlabShellOneShotWorker).not_to receive(:perform_async)
expect { subject.execute }.to raise_error(Projects::HousekeepingService::LeaseTaken)
expect(project.pushes_since_gc).to eq(0)
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 8e6292014d4..240eae10052 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -453,6 +453,59 @@ describe SystemNoteService, services: true do
end
end
+ describe '.noteable_moved' do
+ let(:new_project) { create(:project) }
+ let(:new_noteable) { create(:issue, project: new_project) }
+
+ subject do
+ described_class.noteable_moved(noteable, project, new_noteable, author, direction: direction)
+ end
+
+ shared_examples 'cross project mentionable' do
+ include GitlabMarkdownHelper
+
+ it 'should contain cross reference to new noteable' do
+ expect(subject.note).to include cross_project_reference(new_project, new_noteable)
+ end
+
+ it 'should mention referenced noteable' do
+ expect(subject.note).to include new_noteable.to_reference
+ end
+
+ it 'should mention referenced project' do
+ expect(subject.note).to include new_project.to_reference
+ end
+ end
+
+ context 'moved to' do
+ let(:direction) { :to }
+
+ it_behaves_like 'cross project mentionable'
+
+ it 'should notify about noteable being moved to' do
+ expect(subject.note).to match /Moved to/
+ end
+ end
+
+ context 'moved from' do
+ let(:direction) { :from }
+
+ it_behaves_like 'cross project mentionable'
+
+ it 'should notify about noteable being moved from' do
+ expect(subject.note).to match /Moved from/
+ end
+ end
+
+ context 'invalid direction' do
+ let(:direction) { :invalid }
+
+ it 'should raise error' do
+ expect { subject }.to raise_error StandardError, /Invalid direction/
+ end
+ end
+ end
+
include JiraServiceHelper
describe 'JIRA integration' do
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
index 96420acb31d..b4728807b8b 100644
--- a/spec/services/todo_service_spec.rb
+++ b/spec/services/todo_service_spec.rb
@@ -148,8 +148,13 @@ describe TodoService, services: true do
should_not_create_todo(user: stranger, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
end
- it 'does not create todo when leaving a note on commit' do
- should_not_create_any_todo { service.new_note(note_on_commit, john_doe) }
+ it 'creates a todo for each valid mentioned user when leaving a note on commit' do
+ service.new_note(note_on_commit, john_doe)
+
+ should_create_todo(user: michael, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
+ should_create_todo(user: author, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
+ should_not_create_todo(user: john_doe, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
+ should_not_create_todo(user: stranger, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
end
it 'does not create todo when leaving a note on snippet' do
diff --git a/spec/services/update_snippet_service_spec.rb b/spec/services/update_snippet_service_spec.rb
index 48d114896d0..37c2e861362 100644
--- a/spec/services/update_snippet_service_spec.rb
+++ b/spec/services/update_snippet_service_spec.rb
@@ -25,7 +25,7 @@ describe UpdateSnippetService, services: true do
update_snippet(@project, @user, @snippet, @opts)
expect(@snippet.errors.messages).to have_key(:visibility_level)
expect(@snippet.errors.messages[:visibility_level].first).to(
- match('Public visibility has been restricted')
+ match('has been restricted')
)
expect(@snippet.visibility_level).to eq(old_visibility)
end
diff --git a/spec/support/matchers/access_matchers.rb b/spec/support/matchers/access_matchers.rb
index 4e007c777e3..0497e391860 100644
--- a/spec/support/matchers/access_matchers.rb
+++ b/spec/support/matchers/access_matchers.rb
@@ -28,7 +28,7 @@ module AccessMatchers
if user.kind_of?(User)
# User#inspect displays too much information for RSpec's description
# messages
- "be #{type} for supplied User"
+ "be #{type} for the specified user"
else
"be #{type} for #{user}"
end
diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb
index fce91015fd4..e876d44c166 100644
--- a/spec/support/mentionable_shared_examples.rb
+++ b/spec/support/mentionable_shared_examples.rb
@@ -52,6 +52,8 @@ shared_context 'mentionable context' do
end
set_mentionable_text.call(ref_string)
+
+ project.team << [author, :developer]
end
end
diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb
index 172537474ee..4ef05eb29d2 100644
--- a/spec/workers/repository_fork_worker_spec.rb
+++ b/spec/workers/repository_fork_worker_spec.rb
@@ -3,12 +3,17 @@ require 'spec_helper'
describe RepositoryForkWorker do
let(:project) { create(:project) }
let(:fork_project) { create(:project, forked_from_project: project) }
+ let(:shell) { Gitlab::Shell.new }
subject { RepositoryForkWorker.new }
+ before do
+ allow(subject).to receive(:gitlab_shell).and_return(shell)
+ end
+
describe "#perform" do
it "creates a new repository from a fork" do
- expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository).with(
+ expect(shell).to receive(:fork_repository).with(
project.path_with_namespace,
fork_project.namespace.path
).and_return(true)
@@ -19,20 +24,26 @@ describe RepositoryForkWorker do
fork_project.namespace.path)
end
- it 'flushes the empty caches' do
- expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository).
+ it 'flushes various caches' do
+ expect(shell).to receive(:fork_repository).
with(project.path_with_namespace, fork_project.namespace.path).
and_return(true)
expect_any_instance_of(Repository).to receive(:expire_emptiness_caches).
and_call_original
+ expect_any_instance_of(Repository).to receive(:expire_exists_cache).
+ and_call_original
+
subject.perform(project.id, project.path_with_namespace,
fork_project.namespace.path)
end
it "handles bad fork" do
- expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository).and_return(false)
+ expect(shell).to receive(:fork_repository).and_return(false)
+
+ expect(subject.logger).to receive(:error)
+
subject.perform(
project.id,
project.path_with_namespace,