summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZeger-Jan van de Weg <mail@zjvandeweg.nl>2015-12-07 10:06:07 +0100
committerZeger-Jan van de Weg <mail@zjvandeweg.nl>2015-12-07 10:06:07 +0100
commit0e96d6eb10f92f14d9c06e1728b1a3597a856d2a (patch)
treee003ee5ba773743fb856f9899b2a2c311139f048
parent46278ec7ba7c618acaf7381ad466742ce84e33db (diff)
parent234f4bf20fb338f2164976fd39203fbc671afd29 (diff)
downloadgitlab-ce-0e96d6eb10f92f14d9c06e1728b1a3597a856d2a.tar.gz
Merge branch 'master' into merge-if-green
-rw-r--r--.gitlab-ci.yml9
-rw-r--r--.rubocop.yml2
-rw-r--r--.ruby-version2
-rw-r--r--CHANGELOG40
-rw-r--r--CONTRIBUTING.md24
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--GITLAB_WORKHORSE1
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile29
-rw-r--r--Gemfile.lock285
-rw-r--r--PROCESS.md1
-rw-r--r--Procfile6
-rw-r--r--README.md2
-rw-r--r--app/assets/images/icon-link.pngbin726 -> 1128 bytes
-rw-r--r--app/assets/javascripts/application.js.coffee26
-rw-r--r--app/assets/javascripts/awards_handler.coffee91
-rw-r--r--app/assets/javascripts/blob/blob_file_dropzone.js.coffee17
-rw-r--r--app/assets/javascripts/copy_to_clipboard.js.coffee48
-rw-r--r--app/assets/javascripts/dropzone_input.js.coffee80
-rw-r--r--app/assets/javascripts/issues.js.coffee2
-rw-r--r--app/assets/javascripts/markdown_preview.js.coffee87
-rw-r--r--app/assets/javascripts/merge_request_widget.js.coffee9
-rw-r--r--app/assets/javascripts/new_commit_form.js.coffee21
-rw-r--r--app/assets/javascripts/notes.js.coffee9
-rw-r--r--app/assets/javascripts/project.js.coffee24
-rw-r--r--app/assets/javascripts/sidebar.js.coffee1
-rw-r--r--app/assets/javascripts/user.js.coffee6
-rw-r--r--app/assets/javascripts/users_select.js.coffee28
-rw-r--r--app/assets/stylesheets/framework.scss3
-rw-r--r--app/assets/stylesheets/framework/blocks.scss13
-rw-r--r--app/assets/stylesheets/framework/common.scss20
-rw-r--r--app/assets/stylesheets/framework/files.scss4
-rw-r--r--app/assets/stylesheets/framework/forms.scss21
-rw-r--r--app/assets/stylesheets/framework/header.scss8
-rw-r--r--app/assets/stylesheets/framework/issue_box.scss5
-rw-r--r--app/assets/stylesheets/framework/layout.scss11
-rw-r--r--app/assets/stylesheets/framework/lists.scss5
-rw-r--r--app/assets/stylesheets/framework/pagination.scss4
-rw-r--r--app/assets/stylesheets/framework/panels.scss15
-rw-r--r--app/assets/stylesheets/framework/selects.scss60
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss191
-rw-r--r--app/assets/stylesheets/framework/tables.scss2
-rw-r--r--app/assets/stylesheets/framework/tw_bootstrap.scss6
-rw-r--r--app/assets/stylesheets/framework/typography.scss10
-rw-r--r--app/assets/stylesheets/pages/builds.scss4
-rw-r--r--app/assets/stylesheets/pages/commit.scss20
-rw-r--r--app/assets/stylesheets/pages/editor.scss46
-rw-r--r--app/assets/stylesheets/pages/groups.scss7
-rw-r--r--app/assets/stylesheets/pages/issuable.scss85
-rw-r--r--app/assets/stylesheets/pages/issues.scss11
-rw-r--r--app/assets/stylesheets/pages/login.scss1
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss40
-rw-r--r--app/assets/stylesheets/pages/note_form.scss11
-rw-r--r--app/assets/stylesheets/pages/projects.scss27
-rw-r--r--app/assets/stylesheets/pages/snippets.scss44
-rw-r--r--app/assets/stylesheets/pages/wiki.scss5
-rw-r--r--app/controllers/abuse_reports_controller.rb2
-rw-r--r--app/controllers/admin/impersonation_controller.rb16
-rw-r--r--app/controllers/autocomplete_controller.rb51
-rw-r--r--app/controllers/ci/lints_controller.rb4
-rw-r--r--app/controllers/concerns/creates_merge_request_for_commit.rb28
-rw-r--r--app/controllers/profiles_controller.rb1
-rw-r--r--app/controllers/projects/blob_controller.rb87
-rw-r--r--app/controllers/projects/branches_controller.rb2
-rw-r--r--app/controllers/projects/commit_controller.rb7
-rw-r--r--app/controllers/projects/imports_controller.rb4
-rw-r--r--app/controllers/projects/issues_controller.rb6
-rw-r--r--app/controllers/projects/merge_requests_controller.rb6
-rw-r--r--app/controllers/projects/notes_controller.rb29
-rw-r--r--app/controllers/projects/tags_controller.rb2
-rw-r--r--app/controllers/projects/tree_controller.rb13
-rw-r--r--app/controllers/projects_controller.rb2
-rw-r--r--app/controllers/snippets_controller.rb9
-rw-r--r--app/controllers/users_controller.rb29
-rw-r--r--app/finders/contributed_projects_finder.rb37
-rw-r--r--app/finders/groups_finder.rb69
-rw-r--r--app/finders/issuable_finder.rb20
-rw-r--r--app/finders/joined_groups_finder.rb49
-rw-r--r--app/finders/notes_finder.rb4
-rw-r--r--app/finders/personal_projects_finder.rb41
-rw-r--r--app/finders/projects_finder.rb121
-rw-r--r--app/helpers/application_helper.rb8
-rw-r--r--app/helpers/blob_helper.rb2
-rw-r--r--app/helpers/button_helper.rb58
-rw-r--r--app/helpers/clipboard_helper.rb8
-rw-r--r--app/helpers/commits_helper.rb4
-rw-r--r--app/helpers/diff_helper.rb4
-rw-r--r--app/helpers/emails_helper.rb2
-rw-r--r--app/helpers/gitlab_routing_helper.rb2
-rw-r--r--app/helpers/icons_helper.rb22
-rw-r--r--app/helpers/issues_helper.rb46
-rw-r--r--app/helpers/merge_requests_helper.rb25
-rw-r--r--app/helpers/namespaces_helper.rb6
-rw-r--r--app/helpers/nav_helper.rb8
-rw-r--r--app/helpers/projects_helper.rb16
-rw-r--r--app/helpers/selects_helper.rb14
-rw-r--r--app/helpers/visibility_level_helper.rb44
-rw-r--r--app/mailers/base_mailer.rb4
-rw-r--r--app/models/ability.rb82
-rw-r--r--app/models/application_setting.rb12
-rw-r--r--app/models/ci/application_setting.rb9
-rw-r--r--app/models/ci/build.rb2
-rw-r--r--app/models/ci/commit.rb8
-rw-r--r--app/models/commit.rb16
-rw-r--r--app/models/commit_range.rb110
-rw-r--r--app/models/concerns/issuable.rb53
-rw-r--r--app/models/concerns/mentionable.rb11
-rw-r--r--app/models/concerns/referable.rb23
-rw-r--r--app/models/concerns/sortable.rb3
-rw-r--r--app/models/concerns/strip_attribute.rb34
-rw-r--r--app/models/concerns/taskable.rb33
-rw-r--r--app/models/event.rb12
-rw-r--r--app/models/group.rb8
-rw-r--r--app/models/issue.rb4
-rw-r--r--app/models/label.rb2
-rw-r--r--app/models/merge_request.rb11
-rw-r--r--app/models/milestone.rb9
-rw-r--r--app/models/note.rb58
-rw-r--r--app/models/project.rb25
-rw-r--r--app/models/project_services/buildkite_service.rb2
-rw-r--r--app/models/project_services/ci/mail_service.rb6
-rw-r--r--app/models/project_services/drone_ci_service.rb4
-rw-r--r--app/models/project_services/gitlab_ci_service.rb3
-rw-r--r--app/models/repository.rb57
-rw-r--r--app/models/sent_notification.rb5
-rw-r--r--app/models/snippet.rb4
-rw-r--r--app/models/user.rb90
-rw-r--r--app/models/users_star_project.rb2
-rw-r--r--app/services/create_release_service.rb31
-rw-r--r--app/services/create_tag_service.rb10
-rw-r--r--app/services/git_push_service.rb12
-rw-r--r--app/services/gravatar_service.rb4
-rw-r--r--app/services/issuable_base_service.rb30
-rw-r--r--app/services/issues/update_service.rb4
-rw-r--r--app/services/merge_requests/merge_service.rb2
-rw-r--r--app/services/merge_requests/update_service.rb4
-rw-r--r--app/services/notes/create_service.rb17
-rw-r--r--app/services/notification_service.rb77
-rw-r--r--app/services/projects/create_service.rb8
-rw-r--r--app/services/system_note_service.rb20
-rw-r--r--app/services/update_release_service.rb29
-rw-r--r--app/uploaders/artifact_uploader.rb6
-rw-r--r--app/views/admin/labels/_form.html.haml2
-rw-r--r--app/views/admin/labels/_label.html.haml2
-rw-r--r--app/views/admin/labels/edit.html.haml8
-rw-r--r--app/views/admin/labels/new.html.haml6
-rw-r--r--app/views/admin/users/_head.html.haml2
-rw-r--r--app/views/admin/users/_projects.html.haml13
-rw-r--r--app/views/admin/users/edit.html.haml3
-rw-r--r--app/views/admin/users/projects.html.haml2
-rw-r--r--app/views/ci/admin/runners/show.html.haml2
-rw-r--r--app/views/ci/notify/build_fail_email.html.haml2
-rw-r--r--app/views/ci/notify/build_success_email.html.haml2
-rw-r--r--app/views/dashboard/_projects_head.html.haml3
-rw-r--r--app/views/dashboard/groups/index.html.haml4
-rw-r--r--app/views/dashboard/milestones/show.html.haml52
-rw-r--r--app/views/dashboard/projects/index.atom.builder2
-rw-r--r--app/views/dashboard/projects/index.html.haml2
-rw-r--r--app/views/dashboard/projects/starred.html.haml2
-rw-r--r--app/views/explore/projects/_filter.html.haml2
-rw-r--r--app/views/explore/projects/index.html.haml2
-rw-r--r--app/views/explore/projects/starred.html.haml2
-rw-r--r--app/views/explore/projects/trending.html.haml2
-rw-r--r--app/views/groups/edit.html.haml6
-rw-r--r--app/views/groups/group_members/_group_member.html.haml6
-rw-r--r--app/views/groups/group_members/index.html.haml57
-rw-r--r--app/views/groups/group_members/update.js.haml2
-rw-r--r--app/views/groups/milestones/new.html.haml3
-rw-r--r--app/views/groups/milestones/show.html.haml61
-rw-r--r--app/views/groups/new.html.haml8
-rw-r--r--app/views/groups/show.atom.builder2
-rw-r--r--app/views/import/bitbucket/status.html.haml1
-rw-r--r--app/views/import/fogbugz/new.html.haml1
-rw-r--r--app/views/import/fogbugz/new_user_map.html.haml1
-rw-r--r--app/views/import/fogbugz/status.html.haml1
-rw-r--r--app/views/import/github/status.html.haml1
-rw-r--r--app/views/import/gitlab/status.html.haml1
-rw-r--r--app/views/import/gitorious/status.html.haml1
-rw-r--r--app/views/import/google_code/new.html.haml3
-rw-r--r--app/views/import/google_code/new_user_map.html.haml17
-rw-r--r--app/views/import/google_code/status.html.haml1
-rw-r--r--app/views/layouts/_page.html.haml10
-rw-r--r--app/views/layouts/admin.html.haml4
-rw-r--r--app/views/layouts/ci/_page.html.haml10
-rw-r--r--app/views/layouts/devise.html.haml4
-rw-r--r--app/views/layouts/errors.html.haml2
-rw-r--r--app/views/layouts/header/_default.html.haml12
-rw-r--r--app/views/layouts/nav/_admin.html.haml28
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml18
-rw-r--r--app/views/layouts/nav/_explore.html.haml8
-rw-r--r--app/views/layouts/nav/_group.html.haml14
-rw-r--r--app/views/layouts/nav/_group_settings.html.haml6
-rw-r--r--app/views/layouts/nav/_profile.html.haml20
-rw-r--r--app/views/layouts/nav/_project.html.haml34
-rw-r--r--app/views/layouts/nav/_project_settings.html.haml24
-rw-r--r--app/views/profiles/accounts/show.html.haml22
-rw-r--r--app/views/profiles/applications.html.haml84
-rw-r--r--app/views/profiles/keys/_form.html.haml7
-rw-r--r--app/views/profiles/keys/_key_table.html.haml16
-rw-r--r--app/views/profiles/keys/index.html.haml4
-rw-r--r--app/views/profiles/notifications/show.html.haml6
-rw-r--r--app/views/profiles/preferences/show.html.haml2
-rw-r--r--app/views/profiles/show.html.haml10
-rw-r--r--app/views/projects/_commit_button.html.haml4
-rw-r--r--app/views/projects/_home_panel.html.haml30
-rw-r--r--app/views/projects/_md_preview.html.haml2
-rw-r--r--app/views/projects/_readme.html.haml25
-rw-r--r--app/views/projects/_zen.html.haml9
-rw-r--r--app/views/projects/blame/show.html.haml3
-rw-r--r--app/views/projects/blob/_actions.html.haml2
-rw-r--r--app/views/projects/blob/_editor.html.haml22
-rw-r--r--app/views/projects/blob/_new_dir.html.haml24
-rw-r--r--app/views/projects/blob/_remove.html.haml16
-rw-r--r--app/views/projects/blob/_upload.html.haml25
-rw-r--r--app/views/projects/blob/edit.html.haml17
-rw-r--r--app/views/projects/blob/new.html.haml17
-rw-r--r--app/views/projects/blob/show.html.haml4
-rw-r--r--app/views/projects/branches/_branch.html.haml2
-rw-r--r--app/views/projects/branches/_commit.html.haml2
-rw-r--r--app/views/projects/branches/index.html.haml2
-rw-r--r--app/views/projects/branches/new.html.haml16
-rw-r--r--app/views/projects/buttons/_download.html.haml4
-rw-r--r--app/views/projects/buttons/_dropdown.html.haml2
-rw-r--r--app/views/projects/buttons/_fork.html.haml25
-rw-r--r--app/views/projects/buttons/_star.html.haml2
-rw-r--r--app/views/projects/commit/_ci_menu.html.haml2
-rw-r--r--app/views/projects/commit/_commit_box.html.haml5
-rw-r--r--app/views/projects/commit/show.html.haml5
-rw-r--r--app/views/projects/commits/_commit.html.haml4
-rw-r--r--app/views/projects/compare/show.html.haml4
-rw-r--r--app/views/projects/deploy_keys/_form.html.haml9
-rw-r--r--app/views/projects/deploy_keys/new.html.haml2
-rw-r--r--app/views/projects/diffs/_diffs.html.haml2
-rw-r--r--app/views/projects/diffs/_file.html.haml20
-rw-r--r--app/views/projects/edit.html.haml36
-rw-r--r--app/views/projects/empty.html.haml13
-rw-r--r--app/views/projects/imports/new.html.haml25
-rw-r--r--app/views/projects/imports/show.html.haml2
-rw-r--r--app/views/projects/issues/_closed_by_box.html.haml2
-rw-r--r--app/views/projects/issues/_discussion.html.haml13
-rw-r--r--app/views/projects/issues/_form.html.haml8
-rw-r--r--app/views/projects/issues/_issue.html.haml28
-rw-r--r--app/views/projects/issues/edit.html.haml6
-rw-r--r--app/views/projects/issues/new.html.haml4
-rw-r--r--app/views/projects/issues/show.html.haml3
-rw-r--r--app/views/projects/labels/_form.html.haml10
-rw-r--r--app/views/projects/labels/_label.html.haml2
-rw-r--r--app/views/projects/labels/edit.html.haml8
-rw-r--r--app/views/projects/labels/index.html.haml1
-rw-r--r--app/views/projects/labels/new.html.haml6
-rw-r--r--app/views/projects/merge_requests/_discussion.html.haml15
-rw-r--r--app/views/projects/merge_requests/_form.html.haml3
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml42
-rw-r--r--app/views/projects/merge_requests/_new_compare.html.haml14
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml18
-rw-r--r--app/views/projects/merge_requests/_show.html.haml14
-rw-r--r--app/views/projects/merge_requests/edit.html.haml2
-rw-r--r--app/views/projects/merge_requests/merge.js.haml4
-rw-r--r--app/views/projects/merge_requests/new.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_commits.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml2
-rw-r--r--app/views/projects/merge_requests/widget/_merged.html.haml8
-rw-r--r--app/views/projects/milestones/_form.html.haml10
-rw-r--r--app/views/projects/milestones/_milestone.html.haml2
-rw-r--r--app/views/projects/milestones/edit.html.haml6
-rw-r--r--app/views/projects/milestones/new.html.haml6
-rw-r--r--app/views/projects/milestones/show.html.haml113
-rw-r--r--app/views/projects/new.html.haml58
-rw-r--r--app/views/projects/notes/_edit_form.html.haml5
-rw-r--r--app/views/projects/notes/_form.html.haml9
-rw-r--r--app/views/projects/notes/_note.html.haml20
-rw-r--r--app/views/projects/project_members/_group_members.html.haml8
-rw-r--r--app/views/projects/project_members/_project_member.html.haml4
-rw-r--r--app/views/projects/project_members/_team.html.haml16
-rw-r--r--app/views/projects/project_members/index.html.haml45
-rw-r--r--app/views/projects/protected_branches/index.html.haml3
-rw-r--r--app/views/projects/releases/edit.html.haml5
-rw-r--r--app/views/projects/repositories/_download_archive.html.haml4
-rw-r--r--app/views/projects/runners/edit.html.haml4
-rw-r--r--app/views/projects/services/_form.html.haml7
-rw-r--r--app/views/projects/show.atom.builder2
-rw-r--r--app/views/projects/show.html.haml2
-rw-r--r--app/views/projects/snippets/edit.html.haml2
-rw-r--r--app/views/projects/snippets/new.html.haml2
-rw-r--r--app/views/projects/tags/index.html.haml2
-rw-r--r--app/views/projects/tags/new.html.haml34
-rw-r--r--app/views/projects/tags/show.html.haml2
-rw-r--r--app/views/projects/tree/_tree_content.html.haml4
-rw-r--r--app/views/projects/wikis/_form.html.haml16
-rw-r--r--app/views/projects/wikis/_main_links.html.haml11
-rw-r--r--app/views/projects/wikis/_nav.html.haml25
-rw-r--r--app/views/projects/wikis/_new.html.haml4
-rw-r--r--app/views/projects/wikis/edit.html.haml24
-rw-r--r--app/views/projects/wikis/git_access.html.haml2
-rw-r--r--app/views/projects/wikis/pages.html.haml7
-rw-r--r--app/views/projects/wikis/show.html.haml12
-rw-r--r--app/views/shared/_clone_panel.html.haml25
-rw-r--r--app/views/shared/_commit_message_container.html.haml8
-rw-r--r--app/views/shared/_confirm_modal.html.haml7
-rw-r--r--app/views/shared/_group_form.html.haml2
-rw-r--r--app/views/shared/_import_form.html.haml16
-rw-r--r--app/views/shared/_issues.html.haml7
-rw-r--r--app/views/shared/_merge_requests.html.haml7
-rw-r--r--app/views/shared/_new_commit_form.html.haml17
-rw-r--r--app/views/shared/_project_limit.html.haml8
-rw-r--r--app/views/shared/issuable/_context.html.haml27
-rw-r--r--app/views/shared/issuable/_filter.html.haml16
-rw-r--r--app/views/shared/issuable/_form.html.haml52
-rw-r--r--app/views/shared/snippets/_form.html.haml7
-rw-r--r--app/views/shared/snippets/_header.html.haml11
-rw-r--r--app/views/sherlock/transactions/_general.html.haml6
-rw-r--r--app/views/snippets/edit.html.haml2
-rw-r--r--app/views/snippets/new.html.haml2
-rw-r--r--app/views/users/show.atom.builder2
-rw-r--r--app/views/votes/_votes_block.html.haml42
-rw-r--r--app/views/votes/_votes_inline.html.haml9
-rw-r--r--app/workers/email_receiver_worker.rb2
-rw-r--r--app/workers/emails_on_push_worker.rb2
-rw-r--r--app/workers/repository_fork_worker.rb14
-rw-r--r--app/workers/repository_import_worker.rb62
-rw-r--r--app/workers/stuck_ci_builds_worker.rb3
-rwxr-xr-xbin/background_jobs2
-rwxr-xr-x[-rw-r--r--]bin/ci/upgrade.rb0
-rwxr-xr-xbin/rails6
-rwxr-xr-xbin/rake9
-rwxr-xr-xbin/setup29
-rwxr-xr-x[-rw-r--r--]bin/upgrade.rb0
-rw-r--r--config/application.rb4
-rw-r--r--config/environment.rb2
-rw-r--r--config/environments/development.rb2
-rw-r--r--config/environments/production.rb6
-rw-r--r--config/environments/test.rb6
-rw-r--r--config/gitlab.yml.example8
-rw-r--r--config/initializers/1_settings.rb30
-rw-r--r--config/initializers/cookies_serializer.rb2
-rw-r--r--config/initializers/default_url_options.rb2
-rw-r--r--config/initializers/rack_attack.rb.example14
-rw-r--r--config/initializers/rack_lineprof.rb2
-rw-r--r--config/initializers/secret_token.rb8
-rw-r--r--config/initializers/session_store.rb9
-rw-r--r--config/initializers/sherlock.rb2
-rw-r--r--config/initializers/smtp_settings.rb.sample2
-rw-r--r--config/initializers/static_files.rb4
-rw-r--r--config/locales/sherlock.en.yml1
-rw-r--r--config/routes.rb6
-rw-r--r--db/migrate/20151106000015_add_is_award_to_notes.rb6
-rw-r--r--db/migrate/20151109134526_add_issues_state_index.rb5
-rw-r--r--db/migrate/20151109134916_add_projects_visibility_level_index.rb5
-rw-r--r--db/migrate/20151110125604_add_import_error_to_project.rb5
-rw-r--r--db/migrate/20151118162244_add_projects_public_index.rb5
-rw-r--r--db/migrate/20151203162133_add_hide_project_limit_to_users.rb5
-rw-r--r--db/schema.rb128
-rw-r--r--doc/README.md1
-rw-r--r--doc/api/README.md1
-rw-r--r--doc/api/merge_requests.md59
-rw-r--r--doc/api/notes.md20
-rw-r--r--doc/api/projects.md71
-rw-r--r--doc/api/tags.md39
-rw-r--r--doc/ci/docker/using_docker_images.md4
-rw-r--r--doc/ci/img/builds_tab.pngbin0 -> 3845 bytes
-rw-r--r--doc/ci/quick_start/README.md204
-rw-r--r--doc/ci/quick_start/build_status.pngbin62140 -> 0 bytes
-rw-r--r--doc/ci/quick_start/commit_status.pngbin33492 -> 0 bytes
-rw-r--r--doc/ci/quick_start/img/build_log.pngbin0 -> 63272 bytes
-rw-r--r--doc/ci/quick_start/img/builds_status.pngbin0 -> 49121 bytes
-rw-r--r--doc/ci/quick_start/img/new_commit.pngbin0 -> 9033 bytes
-rw-r--r--doc/ci/quick_start/img/runners_activated.pngbin0 -> 27597 bytes
-rw-r--r--doc/ci/quick_start/img/single_commit_status_pending.pngbin0 -> 36431 bytes
-rw-r--r--doc/ci/quick_start/img/status_pending.pngbin0 -> 19782 bytes
-rw-r--r--doc/ci/quick_start/new_commit.pngbin47527 -> 0 bytes
-rw-r--r--doc/ci/quick_start/projects.pngbin37014 -> 0 bytes
-rw-r--r--doc/ci/quick_start/runners.pngbin123048 -> 0 bytes
-rw-r--r--doc/ci/quick_start/runners_activated.pngbin60769 -> 0 bytes
-rw-r--r--doc/ci/yaml/README.md90
-rw-r--r--doc/install/installation.md8
-rw-r--r--doc/permissions/permissions.md7
-rw-r--r--doc/public_access/public_access.md51
-rw-r--r--doc/raketasks/backup_restore.md5
-rw-r--r--doc/release/monthly.md2
-rw-r--r--doc/release/patch.md40
-rw-r--r--doc/ssh/README.md5
-rw-r--r--doc/update/8.1-to-8.2.md4
-rw-r--r--doc/update/patch_versions.md45
-rw-r--r--doc/workflow/README.md1
-rw-r--r--doc/workflow/award_emoji.pngbin0 -> 6620 bytes
-rw-r--r--doc/workflow/gitlab_flow.md9
-rw-r--r--doc/workflow/lfs/lfs_administration.md41
-rw-r--r--doc/workflow/lfs/manage_large_binaries_with_git_lfs.md (renamed from doc/workflow/git_lfs.md)78
-rw-r--r--doc/workflow/voting_slider.pngbin5329 -> 0 bytes
-rw-r--r--features/group/members.feature105
-rw-r--r--features/group/milestones.feature30
-rw-r--r--features/groups.feature125
-rw-r--r--features/project/issues/award_emoji.feature14
-rw-r--r--features/project/merge_requests/accept.feature17
-rw-r--r--features/project/source/browse_files.feature14
-rw-r--r--features/project/team_management.feature6
-rw-r--r--features/steps/admin/labels.rb10
-rw-r--r--features/steps/dashboard/new_project.rb1
-rw-r--r--features/steps/group/members.rb147
-rw-r--r--features/steps/group/milestones.rb90
-rw-r--r--features/steps/groups.rb261
-rw-r--r--features/steps/project/forked_merge_requests.rb9
-rw-r--r--features/steps/project/issues/award_emoji.rb41
-rw-r--r--features/steps/project/issues/issues.rb8
-rw-r--r--features/steps/project/issues/labels.rb18
-rw-r--r--features/steps/project/issues/milestones.rb2
-rw-r--r--features/steps/project/merge_requests.rb2
-rw-r--r--features/steps/project/merge_requests/acceptance.rb35
-rw-r--r--features/steps/project/source/browse_files.rb40
-rw-r--r--features/steps/project/source/markdown_render.rb6
-rw-r--r--features/steps/project/team_management.rb4
-rw-r--r--features/steps/project/wiki.rb14
-rw-r--r--features/steps/shared/group.rb4
-rw-r--r--features/steps/shared/user.rb4
-rw-r--r--lib/api/entities.rb11
-rw-r--r--lib/api/merge_requests.rb16
-rw-r--r--lib/api/tags.rb37
-rw-r--r--lib/award_emoji.rb12
-rw-r--r--lib/backup/lfs.rb13
-rw-r--r--lib/backup/manager.rb14
-rw-r--r--lib/ci/api/builds.rb2
-rw-r--r--lib/gitlab/backend/shell.rb7
-rw-r--r--lib/gitlab/closing_issue_extractor.rb16
-rw-r--r--lib/gitlab/current_settings.rb2
-rw-r--r--lib/gitlab/git/hook.rb17
-rw-r--r--lib/gitlab/github_import/client.rb2
-rw-r--r--lib/gitlab/github_import/importer.rb2
-rw-r--r--lib/gitlab/gitlab_import/client.rb2
-rw-r--r--lib/gitlab/lfs/response.rb153
-rw-r--r--lib/gitlab/lfs/router.rb6
-rw-r--r--lib/gitlab/markdown.rb3
-rw-r--r--lib/gitlab/markdown/abstract_reference_filter.rb85
-rw-r--r--lib/gitlab/markdown/commit_range_reference_filter.rb74
-rw-r--r--lib/gitlab/markdown/commit_reference_filter.rb72
-rw-r--r--lib/gitlab/markdown/external_issue_reference_filter.rb10
-rw-r--r--lib/gitlab/markdown/external_link_filter.rb4
-rw-r--r--lib/gitlab/markdown/label_reference_filter.rb21
-rw-r--r--lib/gitlab/markdown/merge_request_reference_filter.rb10
-rw-r--r--lib/gitlab/markdown/redactor_filter.rb5
-rw-r--r--lib/gitlab/markdown/reference_filter.rb74
-rw-r--r--lib/gitlab/markdown/relative_link_filter.rb3
-rw-r--r--lib/gitlab/markdown/user_reference_filter.rb28
-rw-r--r--lib/gitlab/reference_extractor.rb14
-rw-r--r--lib/gitlab/seeder.rb2
-rw-r--r--lib/gitlab/sherlock/transaction.rb5
-rw-r--r--lib/gitlab/sql/union.rb34
-rwxr-xr-xlib/support/init.d/gitlab2
-rw-r--r--lib/support/nginx/gitlab22
-rw-r--r--lib/support/nginx/gitlab-ssl22
-rw-r--r--lib/tasks/gitlab/backup.rake21
-rw-r--r--lib/tasks/gitlab/task_helpers.rake12
-rwxr-xr-xscripts/prepare_build.sh4
-rw-r--r--spec/benchmarks/finders/issues_finder_spec.rb55
-rw-r--r--spec/controllers/abuse_reports_controller_spec.rb36
-rw-r--r--spec/controllers/admin/impersonation_controller_spec.rb19
-rw-r--r--spec/controllers/autocomplete_controller_spec.rb9
-rw-r--r--spec/controllers/commit_controller_spec.rb15
-rw-r--r--spec/controllers/projects_controller_spec.rb16
-rw-r--r--spec/controllers/snippets_controller_spec.rb233
-rw-r--r--spec/controllers/users_controller_spec.rb23
-rw-r--r--spec/factories.rb12
-rw-r--r--spec/features/admin/admin_users_spec.rb17
-rw-r--r--spec/features/issues_spec.rb2
-rw-r--r--spec/finders/contributed_projects_finder_spec.rb35
-rw-r--r--spec/finders/group_finder_spec.rb15
-rw-r--r--spec/finders/groups_finder_spec.rb48
-rw-r--r--spec/finders/joined_groups_finder_spec.rb49
-rw-r--r--spec/finders/personal_projects_finder_spec.rb34
-rw-r--r--spec/finders/projects_finder_spec.rb82
-rw-r--r--spec/fixtures/markdown.md.erb17
-rw-r--r--spec/helpers/application_helper_spec.rb18
-rw-r--r--spec/helpers/issues_helper_spec.rb26
-rw-r--r--spec/lib/ci/gitlab_ci_yaml_processor_spec.rb6
-rw-r--r--spec/lib/gitlab/closing_issue_extractor_spec.rb53
-rw-r--r--spec/lib/gitlab/lfs/lfs_router_spec.rb545
-rw-r--r--spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb85
-rw-r--r--spec/lib/gitlab/markdown/commit_reference_filter_spec.rb60
-rw-r--r--spec/lib/gitlab/markdown/issue_reference_filter_spec.rb102
-rw-r--r--spec/lib/gitlab/markdown/label_reference_filter_spec.rb71
-rw-r--r--spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb54
-rw-r--r--spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb56
-rw-r--r--spec/lib/gitlab/markdown/user_reference_filter_spec.rb51
-rw-r--r--spec/lib/gitlab/sherlock/transaction_spec.rb13
-rw-r--r--spec/lib/gitlab/sql/union_spec.rb16
-rw-r--r--spec/lib/votes_spec.rb188
-rw-r--r--spec/mailers/notify_spec.rb77
-rw-r--r--spec/models/ci/project_services/mail_service_spec.rb62
-rw-r--r--spec/models/commit_range_spec.rb127
-rw-r--r--spec/models/commit_spec.rb21
-rw-r--r--spec/models/concerns/strip_attribute_spec.rb20
-rw-r--r--spec/models/event_spec.rb38
-rw-r--r--spec/models/group_spec.rb27
-rw-r--r--spec/models/merge_request_spec.rb25
-rw-r--r--spec/models/note_spec.rb87
-rw-r--r--spec/models/project_services/jira_service_spec.rb6
-rw-r--r--spec/models/project_spec.rb34
-rw-r--r--spec/models/user_spec.rb34
-rw-r--r--spec/requests/api/merge_requests_spec.rb17
-rw-r--r--spec/requests/api/projects_spec.rb30
-rw-r--r--spec/requests/api/tags_spec.rb71
-rw-r--r--spec/services/ci/create_commit_service_spec.rb24
-rw-r--r--spec/services/create_release_service_spec.rb34
-rw-r--r--spec/services/issues/close_service_spec.rb4
-rw-r--r--spec/services/issues/update_service_spec.rb88
-rw-r--r--spec/services/merge_requests/close_service_spec.rb4
-rw-r--r--spec/services/merge_requests/merge_service_spec.rb4
-rw-r--r--spec/services/merge_requests/reopen_service_spec.rb4
-rw-r--r--spec/services/merge_requests/update_service_spec.rb57
-rw-r--r--spec/services/notes/create_service_spec.rb34
-rw-r--r--spec/services/notification_service_spec.rb348
-rw-r--r--spec/services/projects/fork_service_spec.rb17
-rw-r--r--spec/services/update_release_service_spec.rb34
-rw-r--r--spec/spec_helper.rb1
-rw-r--r--spec/support/filter_spec_helper.rb19
-rw-r--r--spec/support/markdown_feature.rb4
-rw-r--r--spec/support/matchers/markdown_matchers.rb14
-rw-r--r--spec/support/mentionable_shared_examples.rb17
-rw-r--r--spec/tasks/gitlab/backup_rake_spec.rb17
-rw-r--r--spec/workers/email_receiver_worker_spec.rb14
-rw-r--r--spec/workers/repository_fork_worker_spec.rb1
520 files changed, 7500 insertions, 4387 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 94753093540..e8290fb36b2 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -87,3 +87,12 @@ flay:
tags:
- ruby
- mysql
+
+bundler:audit:
+ script:
+ - "bundle exec bundle-audit update"
+ - "bundle exec bundle-audit check"
+ tags:
+ - ruby
+ - mysql
+ allow_failure: true
diff --git a/.rubocop.yml b/.rubocop.yml
index 11e4502849a..d59edbc8b17 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -888,7 +888,7 @@ Lint/RequireParentheses:
Lint/RescueException:
Description: 'Avoid rescuing the Exception class.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-blind-rescues'
- Enabled: false
+ Enabled: true
Lint/ShadowingOuterLocalVariable:
Description: >-
diff --git a/.ruby-version b/.ruby-version
index 399088bf465..04b10b4f150 100644
--- a/.ruby-version
+++ b/.ruby-version
@@ -1 +1 @@
-2.1.6
+2.1.7
diff --git a/CHANGELOG b/CHANGELOG
index 18f5f270ec1..6c2fa75899c 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,16 +2,50 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.3.0 (unreleased)
- Merge when build succeeds (Zeger-Jan van de Weg)
+ - Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera)
+ - Fix 500 error when update group member permission
+ - Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera)
+ - Recognize issue/MR/snippet/commit links as references
+ - Add ignore whitespace change option to commit view
+ - Fire update hook from GitLab
+ - Don't show project fork event as "imported"
+ - Add API endpoint to fetch merge request commits list
+ - Expose events API with comment information and author info
+ - Fix: Ensure "Remove Source Branch" button is not shown when branch is being deleted. #3583
+
+v 8.2.3
+ - Fix application settings cache not expiring after changes (Stan Hu)
+
+v 8.2.2
+ - Fix 404 in redirection after removing a project (Stan Hu)
+ - Ensure cached application settings are refreshed at startup (Stan Hu)
+ - Fix Error 500 when viewing user's personal projects from admin page (Stan Hu)
+ - Fix: Raw private snippets access workflow
+ - Prevent "413 Request entity too large" errors when pushing large files with LFS
+ - Fix invalid links within projects dashboard header
+ - Make current user the first user in assignee dropdown in issues detail page (Stan Hu)
+ - Fix: duplicate email notifications on issue comments
+
+v 8.2.1
+ - Forcefully update builds that didn't want to update with state machine
+ - Fix: saving GitLabCiService as Admin Template
v 8.2.0
+ - Improved performance of finding projects and groups in various places
+ - Improved performance of rendering user profile pages and Atom feeds
+ - Expose build artifacts path as config option
- Fix grouping of contributors by email in graph.
- - Remove CSS property preventing hard tabs from rendering in Chromium 45 (Stan Hu)
+ - Improved performance of finding issues with/without labels
- Fix Drone CI service template not saving properly (Stan Hu)
- Fix avatars not showing in Atom feeds and project issues when Gravatar disabled (Stan Hu)
- Added a GitLab specific profiling tool called "Sherlock" (see GitLab CE merge request #1749)
- Upgrade gitlab_git to 7.2.20 and rugged to 0.23.3 (Stan Hu)
- Improved performance of finding users by one of their Email addresses
- Add allow_failure field to commit status API (Stan Hu)
+ - Commits without .gitlab-ci.yml are marked as skipped
+ - Save detailed error when YAML syntax is invalid
+ - Since GitLab CI is enabled by default, remove enabling it by pushing .gitlab-ci.yml
+ - Added build artifacts
- Improved performance of replacing references in comments
- Show last project commit to default branch on project home page
- Highlight comment based on anchor in URL
@@ -29,6 +63,7 @@ v 8.2.0
- Allow to define cache in `.gitlab-ci.yml`
- Fix: 500 error returned if destroy request without HTTP referer (Kazuki Shimizu)
- Remove deprecated CI events from project settings page
+ - Improve personal snippet access workflow (Douglas Alexandre)
- [API] Add ability to fetch the commit ID of the last commit that actually touched a file
- Fix omniauth documentation setting for omnibus configuration (Jon Cairns)
- Add "New file" link to dropdown on project page
@@ -50,7 +85,9 @@ v 8.2.0
- Fix trailing whitespace issue in merge request/issue title
- Fix bug when milestone/label filter was empty for dashboard issues page
- Add ability to create milestone in group projects from single form
+ - Add option to create merge request when editing/creating a file (Dirceu Tiegs)
- Prevent the last owner of a group from being able to delete themselves by 'adding' themselves as a master (James Lopez)
+ - Add Award Emoji to issue and merge request pages
v 8.1.4
- Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu)
@@ -120,7 +157,6 @@ v 8.1.0
- Show CI status on Your projects page and Starred projects page
- Remove "Continuous Integration" page from dashboard
- Add notes and SSL verification entries to hook APIs (Ben Boeckel)
- - Added build artifacts
- Fix grammar in admin area "labels" .nothing-here-block when no labels exist.
- Move CI runners page to project settings area
- Move CI variables page to project settings area
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 5d85c9f3fca..7f5da063fd4 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -23,10 +23,20 @@ Issues and merge requests should be in English and contain appropriate language
## Helping others
Please help other GitLab users when you can.
-The channnels people will reach out on can be found on the [getting help page](https://about.gitlab.com/getting-help/).
-Sign up for the mailinglist, answer GitLab questions on StackOverflow or respond in the irc channel.
+The channels people will reach out on can be found on the [getting help page](https://about.gitlab.com/getting-help/).
+Sign up for the mailinglist, answer GitLab questions on StackOverflow or respond in the IRC channel.
You can also sign up on [CodeTriage](http://www.codetriage.com/gitlabhq/gitlabhq) to help with one issue every day.
+## I want to contribute!
+
+If you want to contribute to GitLab, but are not sure where to start,
+look for [issues](https://gitlab.com/gitlab-org/gitlab-ce/issues?milestone_id=&scope=all&sort=created_desc&state=opened&utf8=%E2%9C%93&assignee_id=&author_id=&milestone_title=&label_name=up-for-grabs)
+with the label `up-for-grabs`.
+These issues will be of reasonable size and challenge, for anyone to start
+contributing to GitLab.
+
+This was inspired by [an article by Kent C. Dodds](https://medium.com/@kentcdodds/first-timers-only-78281ea47455#.i2f363mx4).
+
## Issue tracker
To get support for your particular problem please use the [getting help channels](https://about.gitlab.com/getting-help/).
@@ -47,10 +57,10 @@ Please send a merge request with a tested solution or a merge request with a fai
1. **Observed behavior**
1. **Relevant logs and/or screenshots:** Please use code blocks (\`\`\`) to format console output, logs, and code as it's very hard to read otherwise.
1. **Output of checks**
- * Results of GitLab [Application Check](doc/install/installation.md#check-application-status) (`sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true`); we will only investigate if the tests are passing
+ * Results of GitLab [Application Check](doc/install/installation.md#check-application-status) (For installations with omnibus-gitlab package: `sudo gitlab-rake gitlab:check SANITIZE=true`); For installations from source: `sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true`); we will only investigate if the tests are passing
* Version of GitLab you are running; we will only investigate issues in the latest stable and development releases as per the [maintenance policy](MAINTENANCE.md)
* Add the last commit SHA-1 of the GitLab version you used to replicate the issue (obtainable from the help page)
- * Describe your setup (use relevant parts from `sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`)
+ * Describe your setup (use relevant parts from the env info: For installations with omnibus-gitlab package: `sudo gitlab-rake gitlab:env:info`; For installations from source: `sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`)
1. **Possible fixes**: If you can, link to the line of code that might be responsible for the problem
## Merge requests
@@ -59,7 +69,7 @@ We welcome merge requests with fixes and improvements to GitLab code, tests, and
Merge requests can be filed either at [gitlab.com](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests) or [github.com](https://github.com/gitlabhq/gitlabhq/pulls).
-If you are new to GitLab development (or web development in general), search for the label `easyfix` ([gitlab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=easyfix), [github](https://github.com/gitlabhq/gitlabhq/labels/easyfix)). Those are issues easy to fix, marked by the GitLab core-team. If you are unsure how to proceed but want to help, mention one of the core-team members to give you a hint.
+If you are new to GitLab development (or web development in general), search for the label `easyfix` ([GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=easyfix), [GitHub](https://github.com/gitlabhq/gitlabhq/labels/easyfix)). Those are issues easy to fix, marked by the GitLab core-team. If you are unsure how to proceed but want to help, mention one of the core-team members to give you a hint.
To start with GitLab download the [GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit) and see [Development section](doc/development/README.md) in the help file.
@@ -99,7 +109,7 @@ If you contribute to GitLab please know that changes involve more than just code
We have the following [definition of done](http://guide.agilealliance.org/guide/definition-of-done.html).
Please ensure you support the feature you contribute through all of these steps.
-1. Description explaning the relevancy (see following item)
+1. Description explaining the relevancy (see following item)
1. Working and clean code that is commented where needed
1. Unit and integration tests that pass on the CI server
1. Documented in the /doc directory
@@ -163,7 +173,7 @@ If you add a dependency in GitLab (such as an operating system package) please c
1. [Markdown](http://www.cirosantilli.com/markdown-styleguide)
1. [Database Migrations](doc/development/migration_style_guide.md)
1. [Documentation styleguide](doc_styleguide.md)
-1. Interface text should be written subjectively instead of objectively. It should be the gitlab core team addressing a person. It should be written in present time and never use past tense (has been/was). For example instead of "prohibited this user from being saved due to the following errors:" the text should be "sorry, we could not create your account because:". Also these [excellent writing guidelines](https://github.com/NARKOZ/guides#writing).
+1. Interface text should be written subjectively instead of objectively. It should be the GitLab core team addressing a person. It should be written in present time and never use past tense (has been/was). For example instead of "prohibited this user from being saved due to the following errors:" the text should be "sorry, we could not create your account because:". Also these [excellent writing guidelines](https://github.com/NARKOZ/guides#writing).
This is also the style used by linting tools such as [RuboCop](https://github.com/bbatsov/rubocop), [PullReview](https://www.pullreview.com/) and [Hound CI](https://houndci.com).
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 338a5b5d8fe..743af5e1251 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-2.6.6
+2.6.8
diff --git a/GITLAB_WORKHORSE b/GITLAB_WORKHORSE
deleted file mode 100644
index 267577d47e4..00000000000
--- a/GITLAB_WORKHORSE
+++ /dev/null
@@ -1 +0,0 @@
-0.4.1
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index 9e11b32fcaa..2b7c5ae0184 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-0.3.1
+0.4.2
diff --git a/Gemfile b/Gemfile
index 8a19885bcb1..67640bb9ae0 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,6 +1,10 @@
source "https://rubygems.org"
-gem 'rails', '4.1.12'
+gem 'rails', '4.2.4'
+gem 'rails-deprecated_sanitizer', '~> 1.0.3'
+
+# Responders respond_to and respond_with
+gem 'responders', '~> 2.0'
# Specify a sprockets version due to security issue
# See https://groups.google.com/forum/#!topic/rubyonrails-security/doAVp0YaTqY
@@ -16,7 +20,7 @@ gem "pg", '~> 0.18.2', group: :postgres
# Authentication libraries
gem 'devise', '~> 3.5.2'
gem 'devise-async', '~> 0.9.0'
-gem 'doorkeeper', '~> 2.1.3'
+gem 'doorkeeper', '~> 2.2.0'
gem 'omniauth', '~> 1.2.2'
gem 'omniauth-bitbucket', '~> 0.0.2'
gem 'omniauth-facebook', '~> 3.0.0'
@@ -28,7 +32,7 @@ gem 'omniauth-saml', '~> 1.4.0'
gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd'
-gem 'rack-oauth2', '~> 1.0.5'
+gem 'rack-oauth2', '~> 1.2.1'
# Two-factor authentication
gem 'devise-two-factor', '~> 2.0.0'
@@ -62,9 +66,6 @@ gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
# based on human-friendly examples
gem "stamp", '~> 0.6.0'
-# Enumeration fields
-gem 'enumerize', '~> 0.7.0'
-
# Pagination
gem "kaminari", "~> 0.16.3"
@@ -95,9 +96,10 @@ gem 'redcarpet', '~> 3.3.3'
gem 'RedCloth', '~> 4.2.9'
gem 'rdoc', '~>3.6'
gem 'org-ruby', '~> 0.9.12'
-gem 'creole', '~>0.3.6'
+gem 'creole', '~> 0.5.0'
gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 1.5.2'
+gem 'net-ssh', '~> 3.0.1'
# Diffs
gem 'diffy', '~> 3.0.3'
@@ -125,8 +127,7 @@ gem 'sidetiq', '~> 0.6.3'
gem "httparty", '~> 0.13.3'
# Colored output to console
-gem "colored", '~> 1.2'
-gem "colorize", '~> 0.5.8'
+gem "colorize", '~> 0.7.0'
# GitLab settings
gem 'settingslogic', '~> 2.0.9'
@@ -154,7 +155,7 @@ gem "gemnasium-gitlab-service", "~> 0.2"
gem "slack-notifier", "~> 1.2.0"
# Asana integration
-gem 'asana', '~> 0.0.6'
+gem 'asana', '~> 0.4.0'
# FogBugz integration
gem 'ruby-fogbugz', '~> 0.2.1'
@@ -187,13 +188,13 @@ gem "sass-rails", '~> 4.0.5'
gem "coffee-rails", '~> 4.1.0'
gem "uglifier", '~> 2.7.2'
gem 'turbolinks', '~> 2.5.0'
-gem 'jquery-turbolinks', '~> 2.0.1'
+gem 'jquery-turbolinks', '~> 2.1.0'
gem 'addressable', '~> 2.3.8'
gem 'bootstrap-sass', '~> 3.0'
gem 'font-awesome-rails', '~> 4.2'
gem 'gitlab_emoji', '~> 0.1'
-gem 'gon', '~> 5.0.0'
+gem 'gon', '~> 6.0.1'
gem 'jquery-atwho-rails', '~> 1.3.2'
gem 'jquery-rails', '~> 3.1.3'
gem 'jquery-scrollto-rails', '~> 1.4.3'
@@ -214,6 +215,7 @@ group :development do
gem 'rerun', '~> 0.10.0'
gem 'bullet', require: false
gem 'rblineprof', platform: :mri, require: false
+ gem 'web-console', '~> 2.0'
# Better errors handler
gem 'better_errors', '~> 1.0.1'
@@ -261,6 +263,7 @@ group :development, :test do
gem 'simplecov', '~> 0.10.0', require: false
gem 'flog', require: false
gem 'flay', require: false
+ gem 'bundler-audit', require: false
gem 'benchmark-ips', require: false
end
@@ -269,7 +272,7 @@ group :test do
gem 'shoulda-matchers', '~> 2.8.0', require: false
gem 'email_spec', '~> 1.6.0'
gem 'webmock', '~> 1.21.0'
- gem 'test_after_commit', '~> 0.2.2'
+ gem 'test_after_commit', '~> 0.4.2'
gem 'sham_rack'
end
diff --git a/Gemfile.lock b/Gemfile.lock
index 99cdc2a50ae..cd1855758b2 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,63 +1,71 @@
GEM
remote: https://rubygems.org/
specs:
- CFPropertyList (2.3.1)
+ CFPropertyList (2.3.2)
RedCloth (4.2.9)
ace-rails-ap (2.0.1)
- actionmailer (4.1.12)
- actionpack (= 4.1.12)
- actionview (= 4.1.12)
+ actionmailer (4.2.4)
+ actionpack (= 4.2.4)
+ actionview (= 4.2.4)
+ activejob (= 4.2.4)
mail (~> 2.5, >= 2.5.4)
- actionpack (4.1.12)
- actionview (= 4.1.12)
- activesupport (= 4.1.12)
- rack (~> 1.5.2)
+ rails-dom-testing (~> 1.0, >= 1.0.5)
+ actionpack (4.2.4)
+ actionview (= 4.2.4)
+ activesupport (= 4.2.4)
+ rack (~> 1.6)
rack-test (~> 0.6.2)
- actionview (4.1.12)
- activesupport (= 4.1.12)
+ rails-dom-testing (~> 1.0, >= 1.0.5)
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
+ actionview (4.2.4)
+ activesupport (= 4.2.4)
builder (~> 3.1)
erubis (~> 2.7.0)
- activemodel (4.1.12)
- activesupport (= 4.1.12)
+ rails-dom-testing (~> 1.0, >= 1.0.5)
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
+ activejob (4.2.4)
+ activesupport (= 4.2.4)
+ globalid (>= 0.3.0)
+ activemodel (4.2.4)
+ activesupport (= 4.2.4)
builder (~> 3.1)
- activerecord (4.1.12)
- activemodel (= 4.1.12)
- activesupport (= 4.1.12)
- arel (~> 5.0.0)
+ activerecord (4.2.4)
+ activemodel (= 4.2.4)
+ activesupport (= 4.2.4)
+ arel (~> 6.0)
activerecord-deprecated_finders (1.0.4)
- activerecord-session_store (0.1.1)
+ activerecord-session_store (0.1.2)
actionpack (>= 4.0.0, < 5)
activerecord (>= 4.0.0, < 5)
railties (>= 4.0.0, < 5)
- activeresource (4.0.0)
- activemodel (~> 4.0)
- activesupport (~> 4.0)
- rails-observers (~> 0.1.1)
- activesupport (4.1.12)
- i18n (~> 0.6, >= 0.6.9)
+ activesupport (4.2.4)
+ i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
- thread_safe (~> 0.1)
+ thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
acts-as-taggable-on (3.5.0)
activerecord (>= 3.2, < 5)
addressable (2.3.8)
- after_commit_queue (1.1.0)
- rails (>= 3.0)
+ after_commit_queue (1.3.0)
+ activerecord (>= 3.0)
annotate (2.6.10)
activerecord (>= 3.2, <= 4.3)
rake (~> 10.4)
- arel (5.0.1.20140414130214)
- asana (0.0.6)
- activeresource (>= 3.2.3)
- asciidoctor (1.5.2)
+ arel (6.0.3)
+ asana (0.4.0)
+ faraday (~> 0.9)
+ faraday_middleware (~> 0.9)
+ faraday_middleware-multi_json (~> 0.0)
+ oauth2 (~> 1.0)
+ asciidoctor (1.5.3)
ast (2.1.0)
astrolabe (1.3.1)
parser (~> 2.2)
attr_encrypted (1.3.4)
encryptor (>= 1.3.0)
attr_required (1.0.0)
- autoprefixer-rails (5.2.1.2)
+ autoprefixer-rails (6.1.1)
execjs
json
awesome_print (1.2.0)
@@ -85,12 +93,15 @@ GEM
ruby_parser (~> 3.5.0)
sass (~> 3.0)
terminal-table (~> 1.4)
- browser (1.0.0)
+ browser (1.0.1)
builder (3.2.2)
- bullet (4.14.9)
+ bullet (4.14.10)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.9.0)
- byebug (6.0.2)
+ bundler-audit (0.4.0)
+ bundler (~> 1.2)
+ thor (~> 0.18)
+ byebug (8.2.0)
cal-heatmap-rails (0.0.1)
capybara (2.4.4)
mime-types (>= 1.16)
@@ -108,7 +119,7 @@ GEM
celluloid (0.16.0)
timers (~> 4.0.0)
charlock_holmes (0.7.3)
- chunky_png (1.3.4)
+ chunky_png (1.3.5)
cliver (0.3.2)
coderay (1.1.0)
coercible (1.0.0)
@@ -119,19 +130,19 @@ GEM
coffee-script (2.4.1)
coffee-script-source
execjs
- coffee-script-source (1.9.1.1)
- colored (1.2)
- colorize (0.5.8)
+ coffee-script-source (1.10.0)
+ colorize (0.7.7)
connection_pool (2.2.0)
- coveralls (0.8.2)
+ coveralls (0.8.9)
json (~> 1.8)
rest-client (>= 1.6.8, < 2)
simplecov (~> 0.10.0)
term-ansicolor (~> 1.3)
thor (~> 0.19.1)
+ tins (~> 1.6.0)
crack (0.4.2)
safe_yaml (~> 1.0.0)
- creole (0.3.8)
+ creole (0.5.0)
d3_rails (3.5.6)
railties (>= 3.1.0)
daemons (1.2.3)
@@ -151,7 +162,7 @@ GEM
warden (~> 1.2.3)
devise-async (0.9.0)
devise (~> 3.2)
- devise-two-factor (2.0.0)
+ devise-two-factor (2.0.1)
activesupport
attr_encrypted (~> 1.3.2)
devise (~> 3.5.0)
@@ -160,19 +171,17 @@ GEM
diff-lcs (1.2.5)
diffy (3.0.7)
docile (1.1.5)
- domain_name (0.5.24)
+ domain_name (0.5.25)
unf (>= 0.0.5, < 1.0.0)
- doorkeeper (2.1.4)
+ doorkeeper (2.2.2)
railties (>= 3.2)
- dropzonejs-rails (0.7.1)
+ dropzonejs-rails (0.7.2)
rails (> 3.1)
email_reply_parser (0.5.8)
email_spec (1.6.0)
launchy (~> 2.1)
mail (~> 2.2)
encryptor (1.3.0)
- enumerize (0.7.0)
- activesupport (>= 3.2)
equalizer (0.0.11)
erubis (2.7.0)
escape_utils (1.1.0)
@@ -189,6 +198,9 @@ GEM
multipart-post (>= 1.2, < 3)
faraday_middleware (0.10.0)
faraday (>= 0.7.4, < 0.10)
+ faraday_middleware-multi_json (0.0.6)
+ faraday_middleware
+ multi_json
fastercsv (1.5.5)
ffaker (2.0.0)
ffi (1.9.10)
@@ -200,7 +212,7 @@ GEM
flog (4.3.2)
ruby_parser (~> 3.1, > 3.1.0)
sexp_processor (~> 4.4)
- flowdock (0.7.0)
+ flowdock (0.7.1)
httparty (~> 0.7)
multi_json
fog (1.25.0)
@@ -222,13 +234,10 @@ GEM
fog-core (~> 1.22)
fog-json
inflecto (~> 0.0.2)
- fog-core (1.32.1)
+ fog-core (1.35.0)
builder
excon (~> 0.45)
formatador (~> 0.2)
- mime-types
- net-scp (~> 1.1)
- net-ssh (>= 2.1.3)
fog-json (1.0.2)
fog-core (~> 1.0)
multi_json (~> 1.10)
@@ -240,10 +249,10 @@ GEM
fog-core (>= 1.21.0)
fog-json
fog-xml (>= 0.0.1)
- fog-sakuracloud (1.0.1)
+ fog-sakuracloud (1.4.0)
fog-core
fog-json
- fog-softlayer (0.4.7)
+ fog-softlayer (1.0.2)
fog-core
fog-json
fog-terremark (0.1.0)
@@ -258,7 +267,7 @@ GEM
fog-xml (0.1.2)
fog-core
nokogiri (~> 1.5, >= 1.5.11)
- font-awesome-rails (4.4.0.0)
+ font-awesome-rails (4.5.0.0)
railties (>= 3.2, < 5.0)
foreman (0.78.0)
thor (~> 0.19.1)
@@ -268,11 +277,11 @@ GEM
ruby-progressbar (~> 1.4)
gemnasium-gitlab-service (0.2.6)
rugged (~> 0.21)
- gemojione (2.0.1)
+ gemojione (2.1.0)
json
get_process_mem (0.2.0)
gherkin-ruby (0.3.2)
- github-linguist (4.7.0)
+ github-linguist (4.7.2)
charlock_holmes (~> 0.7.3)
escape_utils (~> 1.1.0)
mime-types (>= 1.19)
@@ -287,8 +296,8 @@ GEM
diff-lcs (~> 1.1)
mime-types (~> 1.15)
posix-spawn (~> 0.3)
- gitlab_emoji (0.1.1)
- gemojione (~> 2.0)
+ gitlab_emoji (0.2.0)
+ gemojione (~> 2.1)
gitlab_git (7.2.20)
activesupport (~> 4.0)
charlock_holmes (~> 0.7.3)
@@ -300,6 +309,8 @@ GEM
omniauth (~> 1.0)
pyu-ruby-sasl (~> 0.0.3.1)
rubyntlm (~> 0.3)
+ globalid (0.3.6)
+ activesupport (>= 4.1.0)
gollum-grit_adapter (1.0.0)
gitlab-grit (~> 2.7, >= 2.7.1)
gollum-lib (4.0.3)
@@ -309,9 +320,11 @@ GEM
rouge (~> 1.10.1)
sanitize (~> 2.1.0)
stringex (~> 2.5.1)
- gon (5.0.4)
- actionpack (>= 2.3.0)
+ gon (6.0.1)
+ actionpack (>= 3.0)
json
+ multi_json
+ request_store (>= 1.0)
grape (0.13.0)
activesupport
builder
@@ -333,7 +346,7 @@ GEM
haml (>= 4.0.6, < 5.0)
html2haml (>= 1.0.1)
railties (>= 4.0.1)
- hashie (3.4.2)
+ hashie (3.4.3)
highline (1.6.21)
hike (1.2.3)
hipchat (1.5.2)
@@ -351,40 +364,42 @@ GEM
http-cookie (1.0.2)
domain_name (~> 0.5)
http_parser.rb (0.5.3)
- httparty (0.13.5)
+ httparty (0.13.7)
json (~> 1.8)
multi_xml (>= 0.5.2)
- httpclient (2.6.0.1)
+ httpclient (2.7.0.1)
i18n (0.7.0)
ice_cube (0.11.1)
ice_nine (0.11.1)
inflecto (0.0.2)
ipaddress (0.8.0)
jquery-atwho-rails (1.3.2)
- jquery-rails (3.1.3)
+ jquery-rails (3.1.4)
railties (>= 3.0, < 5.0)
thor (>= 0.14, < 2.0)
jquery-scrollto-rails (1.4.3)
railties (> 3.1, < 5.0)
- jquery-turbolinks (2.0.2)
+ jquery-turbolinks (2.1.0)
railties (>= 3.1.0)
turbolinks
jquery-ui-rails (4.2.1)
railties (>= 3.2.16)
json (1.8.3)
- jwt (1.5.1)
+ jwt (1.5.2)
kaminari (0.16.3)
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
- kgio (2.9.3)
+ kgio (2.10.0)
launchy (2.4.3)
addressable (~> 2.3)
letter_opener (1.1.2)
launchy (~> 2.2)
- listen (2.10.1)
- celluloid (~> 0.16.0)
+ listen (2.9.0)
+ celluloid (>= 0.15.2)
rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9)
+ loofah (2.0.3)
+ nokogiri (>= 1.5.9)
macaddr (1.7.1)
systemu (~> 2.6.2)
mail (2.6.3)
@@ -401,16 +416,14 @@ GEM
multipart-post (2.0.0)
mysql2 (0.3.20)
nested_form (0.3.2)
- net-ldap (0.11)
- net-scp (1.2.1)
- net-ssh (>= 2.6.5)
- net-ssh (2.9.2)
- netrc (0.10.3)
+ net-ldap (0.12.1)
+ net-ssh (3.0.1)
+ netrc (0.11.0)
newrelic-grape (2.0.0)
grape
newrelic_rpm
newrelic_rpm (3.9.4.245)
- nokogiri (1.6.6.2)
+ nokogiri (1.6.6.4)
mini_portile (~> 0.6.0)
nprogress-rails (0.1.6.7)
oauth (0.4.7)
@@ -434,12 +447,15 @@ GEM
omniauth-github (1.1.2)
omniauth (~> 1.0)
omniauth-oauth2 (~> 1.1)
- omniauth-gitlab (1.0.0)
+ omniauth-gitlab (1.0.1)
omniauth (~> 1.0)
omniauth-oauth2 (~> 1.0)
- omniauth-google-oauth2 (0.2.6)
- omniauth (> 1.0)
- omniauth-oauth2 (~> 1.1)
+ omniauth-google-oauth2 (0.2.10)
+ addressable (~> 2.3)
+ jwt (~> 1.0)
+ multi_json (~> 1.3)
+ omniauth (>= 1.1.1)
+ omniauth-oauth2 (~> 1.3.1)
omniauth-kerberos (0.3.0)
omniauth-multipassword
timfel-krb5-auth (~> 0.8)
@@ -463,18 +479,18 @@ GEM
activesupport
nokogiri (>= 1.4.4)
omniauth (~> 1.0)
- opennebula (4.12.1)
+ opennebula (4.14.2)
json
nokogiri
rbvmomi
org-ruby (0.9.12)
rubypants (~> 0.2)
orm_adapter (0.5.0)
- paranoia (2.1.3)
+ paranoia (2.1.4)
activerecord (~> 4.0)
- parser (2.2.2.6)
+ parser (2.2.3.0)
ast (>= 1.1, < 3.0)
- pg (0.18.2)
+ pg (0.18.4)
poltergeist (1.6.0)
capybara (~> 2.1)
cliver (~> 0.3.1)
@@ -482,7 +498,7 @@ GEM
websocket-driver (>= 0.2.0)
posix-spawn (0.3.11)
powerpack (0.0.9)
- pry (0.10.1)
+ pry (0.10.3)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
slop (~> 3.4)
@@ -491,7 +507,7 @@ GEM
pyu-ruby-sasl (0.0.3.3)
quiet_assets (1.0.3)
railties (>= 3.1, < 5.0)
- rack (1.5.5)
+ rack (1.6.4)
rack-accept (0.4.5)
rack (>= 0.4)
rack-attack (4.3.0)
@@ -499,7 +515,7 @@ GEM
rack-cors (0.4.0)
rack-mount (0.8.3)
rack (>= 1.0.0)
- rack-oauth2 (1.0.10)
+ rack-oauth2 (1.2.1)
activesupport (>= 2.3)
attr_required (>= 0.0.5)
httpclient (>= 2.4)
@@ -509,28 +525,35 @@ GEM
rack
rack-test (0.6.3)
rack (>= 1.0)
- rails (4.1.12)
- actionmailer (= 4.1.12)
- actionpack (= 4.1.12)
- actionview (= 4.1.12)
- activemodel (= 4.1.12)
- activerecord (= 4.1.12)
- activesupport (= 4.1.12)
+ rails (4.2.4)
+ actionmailer (= 4.2.4)
+ actionpack (= 4.2.4)
+ actionview (= 4.2.4)
+ activejob (= 4.2.4)
+ activemodel (= 4.2.4)
+ activerecord (= 4.2.4)
+ activesupport (= 4.2.4)
bundler (>= 1.3.0, < 2.0)
- railties (= 4.1.12)
- sprockets-rails (~> 2.0)
- rails-observers (0.1.2)
- activemodel (~> 4.0)
- railties (4.1.12)
- actionpack (= 4.1.12)
- activesupport (= 4.1.12)
+ railties (= 4.2.4)
+ sprockets-rails
+ rails-deprecated_sanitizer (1.0.3)
+ activesupport (>= 4.2.0.alpha)
+ rails-dom-testing (1.0.7)
+ activesupport (>= 4.2.0.beta, < 5.0)
+ nokogiri (~> 1.6.0)
+ rails-deprecated_sanitizer (>= 1.0.1)
+ rails-html-sanitizer (1.0.2)
+ loofah (~> 2.0)
+ railties (4.2.4)
+ actionpack (= 4.2.4)
+ activesupport (= 4.2.4)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rainbow (2.0.0)
raindrops (0.15.0)
rake (10.4.2)
raphael-rails (2.1.2)
- rb-fsevent (0.9.5)
+ rb-fsevent (0.9.6)
rb-inotify (0.9.5)
ffi (>= 0.5.0)
rblineprof (0.3.6)
@@ -542,13 +565,13 @@ GEM
rdoc (3.12.2)
json (~> 1.4)
redcarpet (3.3.3)
- redis (3.2.1)
- redis-actionpack (4.0.0)
+ redis (3.2.2)
+ redis-actionpack (4.0.1)
actionpack (~> 4)
redis-rack (~> 1.5.0)
redis-store (~> 1.1.0)
- redis-activesupport (4.1.1)
- activesupport (~> 4)
+ redis-activesupport (4.1.5)
+ activesupport (>= 3, < 5)
redis-store (~> 1.1.0)
redis-namespace (1.5.2)
redis (~> 3.0, >= 3.0.4)
@@ -559,13 +582,13 @@ GEM
redis-actionpack (~> 4)
redis-activesupport (~> 4)
redis-store (~> 1.1.0)
- redis-store (1.1.6)
+ redis-store (1.1.7)
redis (>= 2.2)
- request_store (1.2.0)
+ request_store (1.2.1)
rerun (0.10.0)
listen (~> 2.7, >= 2.7.3)
- responders (1.1.2)
- railties (>= 3.2, < 4.2)
+ responders (2.1.0)
+ railties (>= 4.2.0, < 5)
rest-client (1.8.0)
http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 3.0)
@@ -687,7 +710,7 @@ GEM
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
- sprockets-rails (2.3.2)
+ sprockets-rails (2.3.3)
actionpack (>= 3.0)
activesupport (>= 3.0)
sprockets (>= 2.8, < 4.0)
@@ -710,11 +733,11 @@ GEM
term-ansicolor (1.3.2)
tins (~> 1.0)
terminal-table (1.5.2)
- test_after_commit (0.2.7)
+ test_after_commit (0.4.2)
activerecord (>= 3.2)
- thin (1.6.3)
+ thin (1.6.4)
daemons (~> 1.0, >= 1.0.9)
- eventmachine (~> 1.0)
+ eventmachine (~> 1.0, >= 1.0.4)
rack (~> 1.0)
thor (0.19.1)
thread_safe (0.3.5)
@@ -752,9 +775,9 @@ GEM
kgio (~> 2.6)
rack
raindrops (~> 0.7)
- unicorn-worker-killer (0.4.3)
+ unicorn-worker-killer (0.4.4)
get_process_mem (~> 0)
- unicorn (~> 4)
+ unicorn (>= 4, < 6)
uniform_notifier (1.9.0)
uuid (2.3.8)
macaddr (~> 1.0)
@@ -766,10 +789,15 @@ GEM
equalizer (~> 0.0, >= 0.0.9)
warden (1.2.3)
rack (>= 1.0)
+ web-console (2.2.1)
+ activemodel (>= 4.0)
+ binding_of_caller (>= 0.7.2)
+ railties (>= 4.0)
+ sprockets-rails (>= 2.0, < 4.0)
webmock (1.21.0)
addressable (>= 2.3.6)
crack (>= 0.3.2)
- websocket-driver (0.6.2)
+ websocket-driver (0.6.3)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.2)
wikicloth (0.8.1)
@@ -791,7 +819,7 @@ DEPENDENCIES
addressable (~> 2.3.8)
after_commit_queue
annotate (~> 2.6.0)
- asana (~> 0.0.6)
+ asana (~> 0.4.0)
asciidoctor (~> 1.5.2)
attr_encrypted (~> 1.3.4)
awesome_print (~> 1.2.0)
@@ -802,6 +830,7 @@ DEPENDENCIES
brakeman (= 3.0.1)
browser (~> 1.0.0)
bullet
+ bundler-audit
byebug
cal-heatmap-rails (~> 0.0.1)
capybara (~> 2.4.0)
@@ -809,10 +838,9 @@ DEPENDENCIES
carrierwave (~> 0.9.0)
charlock_holmes (~> 0.7.3)
coffee-rails (~> 4.1.0)
- colored (~> 1.2)
- colorize (~> 0.5.8)
+ colorize (~> 0.7.0)
coveralls (~> 0.8.2)
- creole (~> 0.3.6)
+ creole (~> 0.5.0)
d3_rails (~> 3.5.5)
database_cleaner (~> 1.4.0)
default_value_for (~> 3.0.0)
@@ -820,11 +848,10 @@ DEPENDENCIES
devise-async (~> 0.9.0)
devise-two-factor (~> 2.0.0)
diffy (~> 3.0.3)
- doorkeeper (~> 2.1.3)
+ doorkeeper (~> 2.2.0)
dropzonejs-rails (~> 0.7.1)
email_reply_parser (~> 0.5.8)
email_spec (~> 1.6.0)
- enumerize (~> 0.7.0)
factory_girl_rails (~> 4.3.0)
ffaker (~> 2.0.0)
flay
@@ -842,7 +869,7 @@ DEPENDENCIES
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.0.2)
- gon (~> 5.0.0)
+ gon (~> 6.0.1)
grape (~> 0.13.0)
grape-entity (~> 0.4.2)
haml-rails (~> 0.9.0)
@@ -852,7 +879,7 @@ DEPENDENCIES
jquery-atwho-rails (~> 1.3.2)
jquery-rails (~> 3.1.3)
jquery-scrollto-rails (~> 1.4.3)
- jquery-turbolinks (~> 2.0.1)
+ jquery-turbolinks (~> 2.1.0)
jquery-ui-rails (~> 4.2.1)
kaminari (~> 0.16.3)
letter_opener (~> 1.1.2)
@@ -861,6 +888,7 @@ DEPENDENCIES
mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.3.16)
nested_form (~> 0.3.2)
+ net-ssh (~> 3.0.1)
newrelic-grape
newrelic_rpm (~> 3.9.4.245)
nprogress-rails (~> 0.1.6.7)
@@ -885,8 +913,9 @@ DEPENDENCIES
quiet_assets (~> 1.0.2)
rack-attack (~> 4.3.0)
rack-cors (~> 0.4.0)
- rack-oauth2 (~> 1.0.5)
- rails (= 4.1.12)
+ rack-oauth2 (~> 1.2.1)
+ rails (= 4.2.4)
+ rails-deprecated_sanitizer (~> 1.0.3)
raphael-rails (~> 2.1.2)
rblineprof
rdoc (~> 3.6)
@@ -894,6 +923,7 @@ DEPENDENCIES
redis-rails (~> 4.0.0)
request_store (~> 1.2.0)
rerun (~> 0.10.0)
+ responders (~> 2.0)
rqrcode-rails3 (~> 0.1.7)
rspec-rails (~> 3.3.0)
rubocop (~> 0.28.0)
@@ -923,7 +953,7 @@ DEPENDENCIES
task_list (~> 1.0.2)
teaspoon (~> 1.0.0)
teaspoon-jasmine (~> 2.2.0)
- test_after_commit (~> 0.2.2)
+ test_after_commit (~> 0.4.2)
thin (~> 1.6.1)
tinder (~> 1.10.0)
turbolinks (~> 2.5.0)
@@ -934,6 +964,7 @@ DEPENDENCIES
unicorn-worker-killer (~> 0.4.2)
version_sorter (~> 2.0.0)
virtus (~> 1.0.1)
+ web-console (~> 2.0)
webmock (~> 1.21.0)
wikicloth (= 0.8.1)
diff --git a/PROCESS.md b/PROCESS.md
index 482ad5fe9e1..72fc3481447 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -44,6 +44,7 @@ Workflow labels are purposely not very detailed since that would be hard to keep
- *UX* needs needs help from a UX designer
- *Frontend* needs help from a Front-end engineer
- *Graphics* needs help from a Graphics designer
+- *up-for-grabs* is an issue suitable for first-time contributors, of reasonable difficulty and size. Not exclusive with other labels.
Example workflow: when a UX designer provided a design but it needs frontend work they remove the UX label and add the frontend label.
diff --git a/Procfile b/Procfile
index 08880b9c425..9cfdee7040f 100644
--- a/Procfile
+++ b/Procfile
@@ -1,3 +1,7 @@
+# For DEVELOPMENT only. Production uses Runit in
+# https://gitlab.com/gitlab-org/omnibus-gitlab or the init scripts in
+# lib/support/init.d, which call scripts in bin/ .
+#
web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"}
-worker: bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default
+worker: bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default
# mail_room: bundle exec mail_room -q -c config/mail_room.yml
diff --git a/README.md b/README.md
index 52e2d977620..c59c8593eba 100644
--- a/README.md
+++ b/README.md
@@ -27,8 +27,6 @@ There are two editions of GitLab:
- GitLab Community Edition (CE) is available freely under the MIT Expat license.
- GitLab Enterprise Edition (EE) includes [extra features](https://about.gitlab.com/features/#compare) that are more useful for organizations with more than 100 users. To use EE and get official support please [become a subscriber](https://about.gitlab.com/pricing/).
-Included with the GitLab Omnibus Packages is [GitLab CI](https://about.gitlab.com/gitlab-ci/) that can easily build, test and deploy code.
-
## Website
On [about.gitlab.com](https://about.gitlab.com/) you can find more information about:
diff --git a/app/assets/images/icon-link.png b/app/assets/images/icon-link.png
index 60021d5ac47..7d89da97e11 100644
--- a/app/assets/images/icon-link.png
+++ b/app/assets/images/icon-link.png
Binary files differ
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index 945ffb660e6..1539eba0faa 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -135,17 +135,25 @@ $ ->
), 1
# Initialize tooltips
- $('body').tooltip({
- selector: '.has_tooltip, [data-toggle="tooltip"], .page-sidebar-collapsed .nav-sidebar a'
+ $('body').tooltip(
+ selector: '.has_tooltip, [data-toggle="tooltip"]'
placement: (_, el) ->
$el = $(el)
- if $el.attr('id') == 'js-shortcuts-home'
- # Place the logo tooltip on the right when collapsed, bottom when expanded
- $el.parents('header').hasClass('header-collapsed') and 'right' or 'bottom'
- else
- # Otherwise use the data-placement attribute, or 'bottom' if undefined
- $el.data('placement') or 'bottom'
- })
+ $el.data('placement') || 'bottom'
+ )
+
+ $('.header-logo .home').tooltip(
+ placement: (_, el) ->
+ $el = $(el)
+ if $('.page-with-sidebar').hasClass('page-sidebar-collapsed') then 'right' else 'bottom'
+ container: 'body'
+ )
+
+ $('.page-with-sidebar').tooltip(
+ selector: '.sidebar-collapsed .nav-sidebar a, .sidebar-collapsed a.sidebar-user'
+ placement: 'right'
+ container: 'body'
+ )
# Form submitter
$('.trigger-submit').on 'change', ->
diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee
new file mode 100644
index 00000000000..09b48fe5572
--- /dev/null
+++ b/app/assets/javascripts/awards_handler.coffee
@@ -0,0 +1,91 @@
+class @AwardsHandler
+ constructor: (@post_emoji_url, @noteable_type, @noteable_id) ->
+
+ addAward: (emoji) ->
+ @postEmoji emoji, =>
+ @addAwardToEmojiBar(emoji)
+
+ addAwardToEmojiBar: (emoji, custom_path = '') ->
+ if @exist(emoji)
+ if @isActive(emoji)
+ @decrementCounter(emoji)
+ else
+ counter = @findEmojiIcon(emoji).siblings(".counter")
+ counter.text(parseInt(counter.text()) + 1)
+ counter.parent().addClass("active")
+ @addMeToAuthorList(emoji)
+ else
+ @createEmoji(emoji, custom_path)
+
+ exist: (emoji) ->
+ @findEmojiIcon(emoji).length > 0
+
+ isActive: (emoji) ->
+ @findEmojiIcon(emoji).parent().hasClass("active")
+
+ decrementCounter: (emoji) ->
+ counter = @findEmojiIcon(emoji).siblings(".counter")
+
+ if parseInt(counter.text()) > 1
+ counter.text(parseInt(counter.text()) - 1)
+ counter.parent().removeClass("active")
+ @removeMeFromAuthorList(emoji)
+ else
+ award = counter.parent()
+ award.tooltip("destroy")
+ award.remove()
+
+ removeMeFromAuthorList: (emoji) ->
+ award_block = @findEmojiIcon(emoji).parent()
+ authors = award_block.attr("data-original-title").split(", ")
+ authors = _.without(authors, "me").join(", ")
+ award_block.attr("title", authors)
+ @resetTooltip(award_block)
+
+ addMeToAuthorList: (emoji) ->
+ award_block = @findEmojiIcon(emoji).parent()
+ authors = award_block.attr("data-original-title").split(", ")
+ authors.push("me")
+ award_block.attr("title", authors.join(", "))
+ @resetTooltip(award_block)
+
+ resetTooltip: (award) ->
+ award.tooltip("destroy")
+
+ # "destroy" call is asynchronous, this is why we need to set timeout.
+ setTimeout (->
+ award.tooltip()
+ ), 200
+
+
+ createEmoji: (emoji, custom_path) ->
+ nodes = []
+ nodes.push("<div class='award active' title='me'>")
+ nodes.push("<div class='icon' data-emoji='" + emoji + "'>")
+ nodes.push(@getImage(emoji, custom_path))
+ nodes.push("</div>")
+ nodes.push("<div class='counter'>1")
+ nodes.push("</div></div>")
+
+ $(".awards-controls").before(nodes.join("\n"))
+
+ $(".award").tooltip()
+
+ getImage: (emoji, custom_path) ->
+ if custom_path
+ $("<img>").attr({src: custom_path, width: 20, height: 20}).wrap("<div>").parent().html()
+ else
+ $("li[data-emoji='" + emoji + "']").html()
+
+
+ postEmoji: (emoji, callback) ->
+ $.post @post_emoji_url, { note: {
+ note: ":" + emoji + ":"
+ noteable_type: @noteable_type
+ noteable_id: @noteable_id
+ }},(data) ->
+ if data.ok
+ callback.call()
+
+ findEmojiIcon: (emoji) ->
+ $(".icon[data-emoji='" + emoji + "']") \ No newline at end of file
diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js.coffee b/app/assets/javascripts/blob/blob_file_dropzone.js.coffee
index 5b604adbbb1..195f8b11e5d 100644
--- a/app/assets/javascripts/blob/blob_file_dropzone.js.coffee
+++ b/app/assets/javascripts/blob/blob_file_dropzone.js.coffee
@@ -23,18 +23,6 @@ class @BlobFileDropzone
init: ->
this.on 'addedfile', (file) ->
$('.dropzone-alerts').html('').hide()
- commit_message = form.find('#commit_message')[0]
-
- if /^Upload/.test(commit_message.placeholder)
- commit_message.placeholder = 'Upload ' + file.name
-
- return
-
- this.on 'removedfile', (file) ->
- commit_message = form.find('#commit_message')[0]
-
- if /^Upload/.test(commit_message.placeholder)
- commit_message.placeholder = 'Upload new file'
return
@@ -47,8 +35,9 @@ class @BlobFileDropzone
return
this.on 'sending', (file, xhr, formData) ->
- formData.append('new_branch', form.find('#new_branch').val())
- formData.append('commit_message', form.find('#commit_message').val())
+ formData.append('new_branch', form.find('.js-new-branch').val())
+ formData.append('create_merge_request', form.find('.js-create-merge-request').val())
+ formData.append('commit_message', form.find('.js-commit-message').val())
return
# Override behavior of adding error underneath preview
diff --git a/app/assets/javascripts/copy_to_clipboard.js.coffee b/app/assets/javascripts/copy_to_clipboard.js.coffee
index ec4b80cca6f..24301e01b10 100644
--- a/app/assets/javascripts/copy_to_clipboard.js.coffee
+++ b/app/assets/javascripts/copy_to_clipboard.js.coffee
@@ -1,21 +1,37 @@
#= require clipboard
-$ ->
- clipboard = new Clipboard '.js-clipboard-trigger',
- text: (trigger) ->
- $target = $(trigger.nextElementSibling || trigger.previousElementSibling)
- $target.data('clipboard-text') || $target.text().trim()
+genericSuccess = (e) ->
+ showTooltip(e.trigger, 'Copied!')
+
+ # Clear the selection and blur the trigger so it loses its border
+ e.clearSelection()
+ $(e.trigger).blur()
- clipboard.on 'success', (e) ->
- $(e.trigger).
- tooltip(trigger: 'manual', placement: 'auto bottom', title: 'Copied!').
- tooltip('show')
+# Safari doesn't support `execCommand`, so instead we inform the user to
+# copy manually.
+#
+# See http://clipboardjs.com/#browser-support
+genericError = (e) ->
+ if /Mac/i.test(navigator.userAgent)
+ key = '&#8984;' # Command
+ else
+ key = 'Ctrl'
- # Clear the selection and blur the trigger so it loses its border
- e.clearSelection()
- $(e.trigger).blur()
+ showTooltip(e.trigger, "Press #{key}-C to copy")
- # Manually hide the tooltip after 1 second
- setTimeout(->
- $(e.trigger).tooltip('hide')
- , 1000)
+showTooltip = (target, title) ->
+ $(target).
+ tooltip(
+ container: 'body'
+ html: 'true'
+ placement: 'auto bottom'
+ title: title
+ trigger: 'manual'
+ ).
+ tooltip('show').
+ one('mouseleave', -> $(this).tooltip('hide'))
+
+$ ->
+ clipboard = new Clipboard '[data-clipboard-target], [data-clipboard-text]'
+ clipboard.on 'success', genericSuccess
+ clipboard.on 'error', genericError
diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee
index 6f789e668af..30a35a04339 100644
--- a/app/assets/javascripts/dropzone_input.js.coffee
+++ b/app/assets/javascripts/dropzone_input.js.coffee
@@ -1,3 +1,5 @@
+#= require markdown_preview
+
class @DropzoneInput
constructor: (form) ->
Dropzone.autoDiscover = false
@@ -11,17 +13,14 @@ class @DropzoneInput
uploadProgress = $("<div class=\"div-dropzone-progress\"></div>")
btnAlert = "<button type=\"button\"" + alertAttr + ">&times;</button>"
project_uploads_path = window.project_uploads_path or null
- markdown_preview_path = window.markdown_preview_path or null
max_file_size = gon.max_file_size or 10
form_textarea = $(form).find("textarea.markdown-area")
form_textarea.wrap "<div class=\"div-dropzone\"></div>"
form_textarea.on 'paste', (event) =>
handlePaste(event)
- form_textarea.on "input", ->
- hideReferencedUsers()
- form_textarea.on "blur", ->
- renderMarkdown()
+
+ $(form).setupMarkdownPreview()
form_dropzone = $(form).find('.div-dropzone')
form_dropzone.parent().addClass "div-dropzone-wrapper"
@@ -34,42 +33,6 @@ class @DropzoneInput
"opacity": 0
"display": "none"
- # Preview button
- $(document).off "click", ".js-md-preview-button"
- $(document).on "click", ".js-md-preview-button", (e) ->
- ###
- Shows the Markdown preview.
-
- Lets the server render GFM into Html and displays it.
- ###
- e.preventDefault()
- form = $(this).closest("form")
- # toggle tabs
- form.find(".js-md-write-button").parent().removeClass "active"
- form.find(".js-md-preview-button").parent().addClass "active"
-
- # toggle content
- form.find(".md-write-holder").hide()
- form.find(".md-preview-holder").show()
-
- renderMarkdown()
-
- # Write button
- $(document).off "click", ".js-md-write-button"
- $(document).on "click", ".js-md-write-button", (e) ->
- ###
- Shows the Markdown textarea.
- ###
- e.preventDefault()
- form = $(this).closest("form")
- # toggle tabs
- form.find(".js-md-write-button").parent().addClass "active"
- form.find(".js-md-preview-button").parent().removeClass "active"
-
- # toggle content
- form.find(".md-write-holder").show()
- form.find(".md-preview-holder").hide()
-
dropzone = form_dropzone.dropzone(
url: project_uploads_path
dictDefaultMessage: ""
@@ -136,41 +99,6 @@ class @DropzoneInput
child = $(dropzone[0]).children("textarea")
- hideReferencedUsers = ->
- referencedUsers = form.find(".referenced-users")
- referencedUsers.hide()
-
- renderReferencedUsers = (users) ->
- referencedUsers = form.find(".referenced-users")
-
- if referencedUsers.length
- if users.length >= 10
- referencedUsers.show()
- referencedUsers.find(".js-referenced-users-count").text users.length
- else
- referencedUsers.hide()
-
- renderMarkdown = ->
- preview = form.find(".js-md-preview")
- mdText = form.find(".markdown-area").val()
- if mdText.trim().length is 0
- preview.text "Nothing to preview."
- hideReferencedUsers()
- else
- preview.text "Loading..."
- $.ajax(
- type: "POST",
- url: markdown_preview_path,
- data: {
- text: mdText
- },
- dataType: "json"
- ).success (data) ->
- preview.html data.body
- preview.syntaxHighlight()
-
- renderReferencedUsers data.references.users
-
formatLink = (link) ->
text = "[#{link.alt}](#{link.url})"
text = "!#{text}" if link.is_image
diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee
index 40bb9e9cb0c..ac9e022e727 100644
--- a/app/assets/javascripts/issues.js.coffee
+++ b/app/assets/javascripts/issues.js.coffee
@@ -29,7 +29,7 @@
$('#filter_issue_search').val($('#issue_search').val())
initSelects: ->
- $("select#update_status").select2(width: 'resolve', dropdownAutoWidth: true)
+ $("select#update_state_event").select2(width: 'resolve', dropdownAutoWidth: true)
$("select#update_assignee_id").select2(width: 'resolve', dropdownAutoWidth: true)
$("select#update_milestone_id").select2(width: 'resolve', dropdownAutoWidth: true)
$("select#label_name").select2(width: 'resolve', dropdownAutoWidth: true)
diff --git a/app/assets/javascripts/markdown_preview.js.coffee b/app/assets/javascripts/markdown_preview.js.coffee
new file mode 100644
index 00000000000..98fc8f17340
--- /dev/null
+++ b/app/assets/javascripts/markdown_preview.js.coffee
@@ -0,0 +1,87 @@
+# MarkdownPreview
+#
+# Handles toggling the "Write" and "Preview" tab clicks, rendering the preview,
+# and showing a warning when more than `x` users are referenced.
+#
+class @MarkdownPreview
+ # Minimum number of users referenced before triggering a warning
+ referenceThreshold: 10
+
+ showPreview: (form) ->
+ preview = form.find('.js-md-preview')
+ mdText = form.find('textarea.markdown-area').val()
+
+ if mdText.trim().length == 0
+ preview.text('Nothing to preview.')
+ @hideReferencedUsers(form)
+ else
+ preview.text('Loading...')
+ @renderMarkdown mdText, (response) =>
+ preview.html(response.body)
+ preview.syntaxHighlight()
+ @renderReferencedUsers(response.references.users, form)
+
+ renderMarkdown: (text, success) ->
+ return unless window.markdown_preview_path
+
+ $.ajax
+ type: 'POST'
+ url: window.markdown_preview_path
+ data: { text: text }
+ dataType: 'json'
+ success: success
+
+ hideReferencedUsers: (form) ->
+ referencedUsers = form.find('.referenced-users')
+ referencedUsers.hide()
+
+ renderReferencedUsers: (users, form) ->
+ referencedUsers = form.find('.referenced-users')
+
+ if referencedUsers.length
+ if users.length >= @referenceThreshold
+ referencedUsers.show()
+ referencedUsers.find('.js-referenced-users-count').text(users.length)
+ else
+ referencedUsers.hide()
+
+markdownPreview = new MarkdownPreview()
+
+previewButtonSelector = '.js-md-preview-button'
+writeButtonSelector = '.js-md-write-button'
+
+$.fn.setupMarkdownPreview = ->
+ $form = $(this)
+
+ form_textarea = $form.find('textarea.markdown-area')
+
+ form_textarea.on 'input', -> markdownPreview.hideReferencedUsers($form)
+ form_textarea.on 'blur', -> markdownPreview.showPreview($form)
+
+$(document).on 'click', previewButtonSelector, (e) ->
+ e.preventDefault()
+
+ $form = $(this).closest('form')
+
+ # toggle tabs
+ $form.find(writeButtonSelector).parent().removeClass('active')
+ $form.find(previewButtonSelector).parent().addClass('active')
+
+ # toggle content
+ $form.find('.md-write-holder').hide()
+ $form.find('.md-preview-holder').show()
+
+ markdownPreview.showPreview($form)
+
+$(document).on 'click', writeButtonSelector, (e) ->
+ e.preventDefault()
+
+ $form = $(this).closest('form')
+
+ # toggle tabs
+ $form.find(writeButtonSelector).parent().addClass('active')
+ $form.find(previewButtonSelector).parent().removeClass('active')
+
+ # toggle content
+ $form.find('.md-write-holder').show()
+ $form.find('.md-preview-holder').hide()
diff --git a/app/assets/javascripts/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee
index 3176e5a8965..c4b63966fe7 100644
--- a/app/assets/javascripts/merge_request_widget.js.coffee
+++ b/app/assets/javascripts/merge_request_widget.js.coffee
@@ -10,17 +10,20 @@ class @MergeRequestWidget
constructor: (@opts) ->
modal = $('#modal_merge_info').modal(show: false)
- mergeInProgress: ->
+ mergeInProgress: (deleteSourceBranch = false)->
$.ajax
type: 'GET'
url: $('.merge-request').data('url')
success: (data) =>
if data.state == "merged"
- location.reload()
+ urlSuffix = if deleteSourceBranch then '?delete_source=true' else ''
+
+ window.location.href = window.location.href + urlSuffix
else if data.merge_error
$('.mr-widget-body').html("<h4>" + data.merge_error + "</h4>")
else
- setTimeout(merge_request_widget.mergeInProgress, 2000)
+ callback = -> merge_request_widget.mergeInProgress(deleteSourceBranch)
+ setTimeout(callback, 2000)
dataType: 'json'
getMergeStatus: ->
diff --git a/app/assets/javascripts/new_commit_form.js.coffee b/app/assets/javascripts/new_commit_form.js.coffee
new file mode 100644
index 00000000000..2e561dea3e1
--- /dev/null
+++ b/app/assets/javascripts/new_commit_form.js.coffee
@@ -0,0 +1,21 @@
+class @NewCommitForm
+ constructor: (form) ->
+ @newBranch = form.find('.js-new-branch')
+ @originalBranch = form.find('.js-original-branch')
+ @createMergeRequest = form.find('.js-create-merge-request')
+ @createMergeRequestFormGroup = form.find('.js-create-merge-request-form-group')
+
+ @renderDestination()
+ @newBranch.keyup @renderDestination
+
+ renderDestination: =>
+ different = @newBranch.val() != @originalBranch.val()
+
+ if different
+ @createMergeRequestFormGroup.show()
+ @createMergeRequest.prop('checked', true) unless @wasDifferent
+ else
+ @createMergeRequestFormGroup.hide()
+ @createMergeRequest.prop('checked', false)
+
+ @wasDifferent = different
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index ea75c656bcc..7de7632201d 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -113,13 +113,16 @@ class @Notes
renderNote: (note) ->
# render note if it not present in loaded list
# or skip if rendered
- if @isNewNote(note)
+ if @isNewNote(note) && !note.award
@note_ids.push(note.id)
$('ul.main-notes-list').
append(note.html).
syntaxHighlight()
@initTaskList()
+ if note.award
+ awards_handler.addAwardToEmojiBar(note.note, note.emoji_path)
+
###
Check if note does not exists on page
###
@@ -255,7 +258,6 @@ class @Notes
###
addNote: (xhr, note, status) =>
@renderNote(note)
- @updateVotes()
###
Called in response to the new note form being submitted
@@ -473,9 +475,6 @@ class @Notes
form = $(e.target).closest(".js-discussion-note-form")
@removeDiscussionNoteForm(form)
- updateVotes: ->
- true
-
###
Called after an attachment file has been selected.
diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee
index 0ea8fffce07..ec919f0cd67 100644
--- a/app/assets/javascripts/project.js.coffee
+++ b/app/assets/javascripts/project.js.coffee
@@ -1,13 +1,19 @@
class @Project
constructor: ->
- # Git clone panel switcher
- cloneHolder = $('.git-clone-holder')
- if cloneHolder.length
- $('a, button', cloneHolder).click ->
- $('a, button', cloneHolder).removeClass 'active'
- $(@).addClass 'active'
- $('#project_clone', cloneHolder).val $(@).data 'clone'
- $(".clone").text("").append $(@).data 'clone'
+ # Git protocol switcher
+ $('.js-protocol-switch').click ->
+ return if $(@).hasClass('active')
+
+ # Toggle 'active' for both buttons
+ $('.js-protocol-switch').toggleClass('active')
+
+ url = $(@).data('clone')
+
+ # Update the input field
+ $('#project_clone').val(url)
+
+ # Update the command line instructions
+ $('.clone').text(url)
# Ref switcher
$('.project-refs-select').on 'change', ->
@@ -39,4 +45,4 @@ class @Project
when 4 then label = ' On Mention '
$('#notifications-button').empty().append("<i class='fa fa-bell'></i>" + label + "<i class='fa fa-angle-down'></i>")
$(@).parents('ul').find('li.active').removeClass 'active'
- $(@).parent().addClass 'active' \ No newline at end of file
+ $(@).parent().addClass 'active'
diff --git a/app/assets/javascripts/sidebar.js.coffee b/app/assets/javascripts/sidebar.js.coffee
index fb08016fbae..ae59480af9e 100644
--- a/app/assets/javascripts/sidebar.js.coffee
+++ b/app/assets/javascripts/sidebar.js.coffee
@@ -5,6 +5,7 @@ $(document).on("click", '.toggle-nav-collapse', (e) ->
$('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
$('header').toggleClass("header-collapsed header-expanded")
+ $('.sidebar-wrapper').toggleClass("sidebar-collapsed sidebar-expanded")
$('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left")
$.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' })
)
diff --git a/app/assets/javascripts/user.js.coffee b/app/assets/javascripts/user.js.coffee
index d0d81f96921..ec4271b092c 100644
--- a/app/assets/javascripts/user.js.coffee
+++ b/app/assets/javascripts/user.js.coffee
@@ -2,3 +2,9 @@ class @User
constructor: ->
$('.profile-groups-avatars').tooltip("placement": "top")
new ProjectsList()
+
+ $('.hide-project-limit-message').on 'click', (e) ->
+ path = '/'
+ $.cookie('hide_project_limit_message', 'false', { path: path })
+ $(@).parents('.project-limit-message').remove()
+ e.preventDefault()
diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
index 9157562a5c5..12abf806bfa 100644
--- a/app/assets/javascripts/users_select.js.coffee
+++ b/app/assets/javascripts/users_select.js.coffee
@@ -32,17 +32,15 @@ class @UsersSelect
if showNullUser
nullUser = {
name: 'Unassigned',
- avatar: null,
- username: 'none',
id: 0
}
data.results.unshift(nullUser)
if showAnyUser
+ name = showAnyUser
+ name = 'Any User' if name == true
anyUser = {
- name: 'Any',
- avatar: null,
- username: 'none',
+ name: name,
id: null
}
data.results.unshift(anyUser)
@@ -50,7 +48,6 @@ class @UsersSelect
if showEmailUser && data.results.length == 0 && query.term.match(/^[^@]+@[^@]+$/)
emailUser = {
name: "Invite \"#{query.term}\"",
- avatar: null,
username: query.term,
id: query.term
}
@@ -58,11 +55,8 @@ class @UsersSelect
query.callback(data)
- initSelection: (element, callback) =>
- id = $(element).val()
- if id != "" && id != "0"
- @user(id, callback)
-
+ initSelection: (args...) =>
+ @initSelection(args...)
formatResult: (args...) =>
@formatResult(args...)
formatSelection: (args...) =>
@@ -71,16 +65,24 @@ class @UsersSelect
escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
m
+ initSelection: (element, callback) ->
+ id = $(element).val()
+ if id == "0"
+ nullUser = { name: 'Unassigned' }
+ callback(nullUser)
+ else if id != ""
+ @user(id, callback)
+
formatResult: (user) ->
if user.avatar_url
avatar = user.avatar_url
else
avatar = gon.default_avatar_url
- "<div class='user-result'>
+ "<div class='user-result #{'no-username' unless user.username}'>
<div class='user-image'><img class='avatar s24' src='#{avatar}'></div>
<div class='user-name'>#{user.name}</div>
- <div class='user-username'>#{user.username}</div>
+ <div class='user-username'>#{user.username || ""}</div>
</div>"
formatSelection: (user) ->
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 1ec9d2fd84f..48a4971c8fc 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -1,9 +1,9 @@
@import "framework/fonts";
@import "framework/variables";
@import "framework/mixins";
-@import "framework/layout";
@import 'framework/tw_bootstrap_variables';
@import 'framework/tw_bootstrap';
+@import "framework/layout";
@import "framework/avatar.scss";
@import "framework/blocks.scss";
@@ -25,6 +25,7 @@
@import "framework/markdown_area.scss";
@import "framework/mobile.scss";
@import "framework/pagination.scss";
+@import "framework/panels.scss";
@import "framework/selects.scss";
@import "framework/sidebar.scss";
@import "framework/tables.scss";
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 1635df9c97b..a62c0f62a4c 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -68,6 +68,10 @@
.oneline {
line-height: 42px;
}
+
+ > p:last-child {
+ margin-bottom: 0;
+ }
}
.cover-block {
@@ -112,5 +116,14 @@
position: absolute;
top: 10px;
right: 10px;
+
+ &.left {
+ left: 10px;
+ right: auto;
+ }
}
}
+
+.block-connector {
+ margin-top: -1px;
+}
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index ddbacd7fd41..d2f491daf78 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -7,7 +7,7 @@
/** COMMON CLASSES **/
.prepend-top-10 { margin-top:10px }
-.prepend-top-default { margin-top: $gl-padding; }
+.prepend-top-default { margin-top: $gl-padding !important; }
.prepend-top-20 { margin-top:20px }
.prepend-left-10 { margin-left:10px }
.prepend-left-20 { margin-left:20px }
@@ -52,6 +52,10 @@ pre {
}
}
+hr {
+ margin: $gl-padding 0;
+}
+
.dropdown-menu > li > a {
text-shadow: none;
}
@@ -64,7 +68,7 @@ pre {
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus {
background: $gl-primary;
- color: #FFF
+ color: #FFF;
}
.str-truncated {
@@ -328,15 +332,15 @@ table {
}
}
+.well {
+ margin-bottom: 0;
+}
+
.search_box {
@extend .well;
text-align: center;
}
-.task-status {
- margin-left: 10px;
-}
-
#nprogress .spinner {
top: 15px !important;
right: 10px !important;
@@ -429,3 +433,7 @@ table {
.space-right {
margin-right: 10px;
}
+
+.alert, .progress {
+ margin-bottom: $gl-padding;
+}
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 35db00281e5..6bf2857e83a 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -8,7 +8,6 @@
border: none;
border-top: 1px solid #E7E9EE;
border-bottom: 1px solid #E7E9EE;
- margin-bottom: 1em;
&.readme-holder {
border-bottom: 0;
@@ -25,7 +24,7 @@
text-shadow: 0 1px 1px #fff;
margin: 0;
text-align: left;
- padding: 10px 15px;
+ padding: 10px $gl-padding;
.file-actions {
float: right;
@@ -171,4 +170,3 @@
}
}
}
-
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 0edfe24f195..032d343df44 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -22,9 +22,10 @@ input[type='text'].danger {
}
.form-actions {
- padding: 17px 20px 18px;
- margin-top: 18px;
- margin-bottom: 18px;
+ margin: -$gl-padding;
+ margin-top: 0;
+ margin-bottom: -$gl-padding;
+ padding: $gl-padding;
background-color: $background-color;
border-top: 1px solid $border-color;
}
@@ -73,6 +74,8 @@ label {
.form-control {
@include box-shadow(none);
+ height: 42px;
+ padding: 8px $gl-padding;
}
.wiki-content {
@@ -88,7 +91,19 @@ label {
}
.input-group {
+ .select2-container {
+ display: table-cell;
+ width: 200px !important;
+ }
.input-group-addon {
background-color: #f7f8fa;
}
+ .input-group-addon:not(:first-child):not(:last-child) {
+ border-left: 0;
+ border-right: 0;
+ }
+}
+
+.help-block {
+ margin-bottom: 0;
}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 02ea91602e8..4dbbb56104b 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -6,15 +6,17 @@ header {
transition-duration: .3s;
&.navbar-empty {
+ height: 58px;
background: #FFF;
border-bottom: 1px solid #EEE;
.center-logo {
- margin: 8px 0;
+ margin: 11px 0;
text-align: center;
- img {
- height: 32px;
+ #tanuki-logo, img {
+ width: 36px;
+ height: 36px;
}
}
}
diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss
index 93377e45e70..f12d68b5a1f 100644
--- a/app/assets/stylesheets/framework/issue_box.scss
+++ b/app/assets/stylesheets/framework/issue_box.scss
@@ -7,8 +7,9 @@
.issue-box {
@include border-radius(2px);
- display: inline-block;
- padding: 10px $gl-padding;
+ display: block;
+ float: left;
+ padding: 0 $gl-padding;
font-weight: normal;
margin-right: 10px;
font-size: $gl-font-size;
diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss
index b91c15d8910..aa5acb93cc5 100644
--- a/app/assets/stylesheets/framework/layout.scss
+++ b/app/assets/stylesheets/framework/layout.scss
@@ -2,9 +2,13 @@ html {
overflow-y: scroll;
&.touch .tooltip { display: none !important; }
+}
+
+body {
+ background-color: #EAEBEC !important;
- body {
- padding-top: $header-height;
+ &.navless {
+ background-color: white !important;
}
}
@@ -18,7 +22,8 @@ html {
}
.navless-container {
- margin-top: 30px;
+ margin-top: $header-height;
+ padding-top: $gl-padding * 2;
}
.container-limited {
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index 45f3b5849bf..a798ae812e3 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -127,3 +127,8 @@ ul.content-list {
}
}
+.panel > .content-list {
+ li {
+ margin: 0;
+ }
+}
diff --git a/app/assets/stylesheets/framework/pagination.scss b/app/assets/stylesheets/framework/pagination.scss
index 6677f94dafd..2cd30491bf5 100644
--- a/app/assets/stylesheets/framework/pagination.scss
+++ b/app/assets/stylesheets/framework/pagination.scss
@@ -32,3 +32,7 @@
}
}
}
+
+.panel > .gl-pagination {
+ margin: 0;
+}
diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss
new file mode 100644
index 00000000000..406aff3d72c
--- /dev/null
+++ b/app/assets/stylesheets/framework/panels.scss
@@ -0,0 +1,15 @@
+.panel {
+ margin-bottom: $gl-padding;
+
+ .panel-heading {
+ padding: 10px $gl-padding;
+ }
+ .panel-body {
+ padding: $gl-padding;
+
+ .form-actions {
+ margin: -$gl-padding;
+ margin-top: $gl-padding;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index 78fff58d232..c01e1e32e41 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -15,6 +15,16 @@
border-left: none;
padding-top: 5px;
}
+
+ .select2-chosen {
+ color: $gl-text-color;
+ }
+
+ &.select2-default {
+ .select2-chosen {
+ color: #999;
+ }
+ }
}
}
@@ -23,6 +33,7 @@
border: 1px solid #e7e9ed;
}
+
.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 border-radius (0px);
@@ -48,17 +59,38 @@
color: #313236;
}
+.select2-container-multi {
+ .select2-choices {
+ @include border-radius(2px);
+ border-color: $input-border;
+ background: white;
+ padding-left: $gl-padding / 2;
+
+ .select2-search-field input {
+ padding: $gl-padding / 2;
+ font-size: 13px;
+ height: auto;
+ font-family: inherit;
+ font-size: inherit;
+ }
-.select2-container-multi .select2-choices {
- @include border-radius(2px);
- border-color: #CCC;
-}
-
-.select2-container-multi .select2-choices .select2-search-field input {
- padding: 8px 14px;
- font-size: 13px;
- line-height: 18px;
- height: auto;
+ .select2-search-choice {
+ margin: 8px 0 0 8px;
+ background: white;
+ box-shadow: none;
+ border-color: $input-border;
+ color: $gl-text-color;
+ line-height: 15px;
+
+ .select2-search-choice-close {
+ top: 5px;
+ }
+
+ &.select2-search-choice-focus {
+ border-color: $gl-text-color;
+ }
+ }
+ }
}
.select2-drop-active {
@@ -123,10 +155,16 @@
}
.user-result {
+ min-height: 24px;
+
.user-image {
float: left;
}
- .user-name {
+
+ &.no-username {
+ .user-name {
+ line-height: 24px;
+ }
}
}
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index c1b0129c866..458af76cb75 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -1,4 +1,7 @@
.page-with-sidebar {
+ padding-top: $header-height;
+ transition-duration: .3s;
+
.sidebar-wrapper {
position: fixed;
top: 0;
@@ -14,19 +17,15 @@
.sidebar-wrapper {
z-index: 99;
background: $background-color;
- transition-duration: .3s;
}
.content-wrapper {
- min-height: 100vh;
width: 100%;
padding: 20px;
- background: #EAEBEC;
.container-fluid {
background: #FFF;
padding: $gl-padding;
- min-height: 90vh;
&.container-blank {
background: none;
@@ -36,6 +35,83 @@
}
}
+.sidebar-wrapper {
+ .header-logo {
+ border-bottom: 1px solid transparent;
+ float: left;
+ height: $header-height;
+ width: $sidebar_width;
+ position: fixed;
+ z-index: 999;
+ overflow: hidden;
+ transition-duration: .3s;
+
+ a {
+ float: left;
+ height: $header-height;
+ width: 100%;
+ padding: 11px 0 11px 22px;
+ overflow: hidden;
+ outline: none;
+ transition-duration: .3s;
+
+ img {
+ width: 36px;
+ height: 36px;
+ }
+
+ #tanuki-logo, img {
+ float: left;
+ }
+
+ .gitlab-text-container {
+ width: 230px;
+
+ h3 {
+ width: 158px;
+ float: left;
+ margin: 0;
+ margin-left: 14px;
+ font-size: 19px;
+ line-height: 41px;
+ font-weight: normal;
+ }
+ }
+ }
+
+ &:hover {
+ background-color: #EEE;
+ }
+ }
+
+ .sidebar-user {
+ padding: 9px 22px;
+ position: fixed;
+ bottom: 40px;
+ width: $sidebar_width;
+ overflow: hidden;
+ transition-duration: .3s;
+
+ .username {
+ margin-left: 10px;
+ width: $sidebar_width - 2 * 10px;
+ font-size: 16px;
+ line-height: 34px;
+ }
+ }
+}
+
+
+.tanuki-shape {
+ transition: all 0.8s;
+
+ &:hover {
+ fill: rgb(255, 255, 255);
+ transition: all 0.1s;
+ }
+}
+
+
.nav-sidebar {
margin-top: 14 + $header-height;
margin-bottom: 100px;
@@ -62,7 +138,7 @@
color: $gray;
display: block;
text-decoration: none;
- padding-left: 22px;
+ padding-left: 23px;
font-weight: normal;
outline: none;
@@ -86,6 +162,10 @@
padding: 0px 8px;
@include border-radius(6px);
}
+
+ &.back-link i {
+ transition-duration: .3s;
+ }
}
}
}
@@ -101,7 +181,6 @@
@mixin expanded-sidebar {
padding-left: $sidebar_width;
- transition-duration: .3s;
.sidebar-wrapper {
width: $sidebar_width;
@@ -115,16 +194,15 @@
&.back-link {
i {
- visibility: hidden;
+ opacity: 0;
}
}
}
}
}
-@mixin folded-sidebar {
- padding-left: 60px;
- transition-duration: .3s;
+@mixin collapsed-sidebar {
+ padding-left: $sidebar_collapsed_width;
.sidebar-wrapper {
width: $sidebar_collapsed_width;
@@ -133,7 +211,7 @@
width: $sidebar_collapsed_width;
a {
- padding-left: 12px;
+ padding-left: ($sidebar_collapsed_width - 36) / 2;
.gitlab-text-container {
display: none;
@@ -144,9 +222,13 @@
.nav-sidebar {
width: $sidebar_collapsed_width;
- li a {
- span {
- display: none;
+ li {
+ width: auto;
+
+ a {
+ span {
+ display: none;
+ }
}
}
}
@@ -156,7 +238,7 @@
}
.sidebar-user {
- padding-left: 12px;
+ padding-left: ($sidebar_collapsed_width - 36) / 2;
width: $sidebar_collapsed_width;
.username {
@@ -187,11 +269,11 @@
@media (max-width: $screen-md-max) {
.page-sidebar-collapsed {
- @include folded-sidebar;
+ @include collapsed-sidebar;
}
.page-sidebar-expanded {
- @include folded-sidebar;
+ @include collapsed-sidebar;
}
.collapse-nav {
@@ -201,83 +283,10 @@
@media(min-width: $screen-md-max) {
.page-sidebar-collapsed {
- @include folded-sidebar;
+ @include collapsed-sidebar;
}
.page-sidebar-expanded {
@include expanded-sidebar;
}
}
-
-.sidebar-user {
- padding: 9px 22px;
- position: fixed;
- bottom: 40px;
- width: $sidebar_width;
- overflow: hidden;
- transition-duration: .3s;
-
- .username {
- margin-left: 10px;
- width: $sidebar_width - 2 * 10px;
- font-size: 16px;
- line-height: 34px;
- }
-}
-
-.sidebar-wrapper {
- .header-logo {
- border-bottom: 1px solid transparent;
- float: left;
- height: $header-height;
- width: $sidebar_width;
- overflow: hidden;
- transition-duration: .3s;
-
- a {
- float: left;
- height: $header-height;
- width: 100%;
- padding: 10px 22px;
- overflow: hidden;
- outline: none;
-
- img {
- width: 36px;
- height: 36px;
- }
-
- #tanuki-logo, img {
- float: left;
- }
-
- .gitlab-text-container {
- width: 230px;
-
- h3 {
- width: 158px;
- float: left;
- margin: 0;
- margin-left: 14px;
- font-size: 19px;
- line-height: 41px;
- font-weight: normal;
- }
- }
- }
-
- &:hover {
- background-color: #EEE;
- }
- }
-}
-
-
-.tanuki-shape {
- transition: all 0.8s;
-
- &:hover {
- fill: rgb(255, 255, 255);
- transition: all 0.1s;
- }
-}
diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss
index 66e16e8df75..793ab3d9bb9 100644
--- a/app/assets/stylesheets/framework/tables.scss
+++ b/app/assets/stylesheets/framework/tables.scss
@@ -6,6 +6,8 @@
table {
&.table {
+ margin-bottom: $gl-padding;
+
.dropdown-menu a {
text-decoration: none;
}
diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss
index 99d028d1228..94f0ed761df 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap.scss
@@ -172,7 +172,7 @@
}
.panel-body {
- form {
+ form, pre {
margin: 0;
}
@@ -190,6 +190,10 @@
.btn {
min-width: 124px;
}
+
+ .btn-clipboard {
+ min-width: 0px;
+ }
}
&.panel-small {
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index ba0312ba0db..aef338cfa56 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -181,6 +181,10 @@ body {
line-height: 1.3;
font-size: 1.25em;
font-weight: 600;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
}
.page-title-empty {
@@ -256,3 +260,9 @@ textarea.js-gfm-input {
.strikethrough {
text-decoration: line-through;
}
+
+h1, h2, h3, h4 {
+ small {
+ color: $gl-gray;
+ }
+}
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 74dc3e321c1..da9965f007a 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -21,7 +21,7 @@
.autoscroll-container {
position: fixed;
- bottom: 10px;
+ bottom: 20px;
right: 20px;
z-index: 100;
}
@@ -34,7 +34,7 @@
a {
display: block;
- margin-bottom: 5px;
+ margin-bottom: 10px;
}
}
diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss
index fbd7c363de1..17245d3be7b 100644
--- a/app/assets/stylesheets/pages/commit.scss
+++ b/app/assets/stylesheets/pages/commit.scss
@@ -2,10 +2,6 @@
display: block;
}
-.commit-title{
- margin-bottom: 10px;
-}
-
.commit-author, .commit-committer{
display: block;
color: #999;
@@ -41,6 +37,8 @@
.commit-box {
.commit-title {
margin: 0;
+ font-size: 23px;
+ color: #313236;
}
.commit-description {
@@ -56,6 +54,7 @@
li {
padding: 3px 0px;
+ line-height: 20px;
}
}
.new-file {
@@ -107,16 +106,3 @@
z-index: 2;
}
}
-
-.commit-ci-menu {
- padding: 0;
- margin: 0;
- list-style: none;
- margin-top: 5px;
- height: 56px;
- margin: -16px;
- padding: 16px;
- text-align: center;
- margin-top: 0px;
- margin-bottom: 2px;
-}
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index e2c521af91e..39d916cd336 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -19,48 +19,38 @@
color: #B94A48;
}
}
- .commit-button-annotation {
- display: inline-block;
- margin: 0;
- padding: 2px;
-
- > * {
- float: left;
- }
-
- .message {
- display: inline-block;
- margin: 5px 8px 0 8px;
- }
- }
.file-title {
@extend .monospace;
+
+ line-height: 42px;
+ padding-top: 7px;
+ padding-bottom: 7px;
}
.editor-ref {
background: $background-color;
- padding: 11px 15px;
+ padding-right: $gl-padding;
border-right: 1px solid $border-color;
- display: inline-block;
- margin: -5px -5px;
+ display: block;
+ float: left;
margin-right: 10px;
}
.editor-file-name {
- .new-file-name {
- display: inline-block;
- width: 450px;
- }
+ @extend .monospace;
+
+ float: left;
+ margin-right: 10px;
+ }
- .form-control {
- margin-top: -3px;
- }
+ .new-file-name {
+ display: inline-block;
+ width: 450px;
+ float: left;
}
- .form-actions {
- margin: -$gl-padding;
- margin-top: 0;
- padding: $gl-padding
+ .select2 {
+ float: right;
}
}
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 07a38a19fad..263993f59a5 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -1,8 +1,3 @@
-.new-group-member-holder {
- margin-top: 50px;
- padding-top: 20px;
-}
-
.member-search-form {
float: left;
}
@@ -15,4 +10,4 @@
.form-control {
height: 42px;
}
-} \ No newline at end of file
+}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index abc27a19e32..957da5c182e 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -51,11 +51,12 @@
.issuable-details {
.page-title {
- margin-top: -15px;
- padding: 10px 0;
+ margin-top: -$gl-padding;
+ padding: 7px 0;
margin-bottom: 0;
color: #5c5d5e;
font-size: 16px;
+ line-height: 42px;
.author {
color: #5c5d5e;
@@ -89,6 +90,17 @@
}
}
+.issuable-show-labels {
+ a {
+ margin-right: 5px;
+ margin-bottom: 5px;
+ display: inline-block;
+ .color-label {
+ padding: 6px 10px;
+ }
+ }
+}
+
.cross-project-reference {
text-align: center;
width: 100%;
@@ -101,3 +113,72 @@
background-color: $background-color;
}
}
+
+.awards {
+ @include clearfix;
+ line-height: 34px;
+ margin: 2px 0;
+
+ .award {
+ @include border-radius(5px);
+
+ border: 1px solid;
+ padding: 0px 10px;
+ float: left;
+ margin: 0 5px;
+ border-color: $border-color;
+ cursor: pointer;
+
+ &.active {
+ border-color: $border-gray-light;
+ background-color: $gray-light;
+
+ .counter {
+ font-weight: bold;
+ }
+ }
+
+ .icon {
+ float: left;
+ margin-right: 10px;
+ }
+
+ .counter {
+ float: left;
+ }
+ }
+
+ .awards-controls {
+ margin-left: 10px;
+ float: left;
+
+ .add-award {
+ font-size: 24px;
+ color: $gl-gray;
+ position: relative;
+ top: 2px;
+
+ &:hover,
+ &:link {
+ text-decoration: none;
+ }
+ }
+
+ .awards-menu {
+ padding: $gl-padding;
+ min-width: 214px;
+
+ > li {
+ cursor: pointer;
+ margin: 5px;
+ }
+ }
+ }
+
+ .awards-menu{
+ li {
+ float: left;
+ margin: 3px;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 41c069f0ad3..f5548c5b88b 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -56,17 +56,6 @@
}
}
-.issue-show-labels {
- a {
- margin-right: 5px;
- margin-bottom: 5px;
- display: inline-block;
- .color-label {
- padding: 6px 10px;
- }
- }
-}
-
form.edit-issue {
margin: 0;
}
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
index 83b866c3a64..f9c6f1b39f9 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -19,6 +19,7 @@
h1:first-child {
font-weight: normal;
margin-bottom: 30px;
+ margin-top: 0;
}
img {
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 18f1ef604a4..7785c95563f 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -4,7 +4,6 @@
*/
.mr-state-widget {
background: #F7F8FA;
- margin-bottom: 20px;
color: $gl-gray;
border: 1px solid #dce0e6;
@include border-radius(2px);
@@ -89,7 +88,7 @@
.mr-widget-body,
.ci_widget,
.mr-widget-footer {
- padding: 15px;
+ padding: $gl-padding;
}
.normal {
@@ -118,26 +117,8 @@
}
}
-.merge-request .merge-request-tabs {
- @include nav-menu;
- margin: -$gl-padding;
- padding: $gl-padding;
- text-align: center;
- margin-bottom: 1px;
-}
-
-// Mobile
-@media (max-width: 480px) {
- .merge-request .merge-request-tabs {
- margin: 0;
- padding: 0;
-
- li {
- a {
- padding: 0;
- }
- }
- }
+.merge-request-details {
+ margin-bottom: $gl-padding;
}
.mr_source_commit,
@@ -194,27 +175,12 @@
line-height: 1.1;
}
-.merge-request-form-info {
- padding-top: 15px;
-}
-
// hide mr close link for inline diff comment form
.diff-file .close-mr-link,
.diff-file .reopen-mr-link {
display: none;
}
-.merge-request-show-labels {
- a {
- margin-right: 5px;
- margin-bottom: 5px;
- display: inline-block;
- .color-label {
- padding: 6px 10px;
- }
- }
-}
-
.merge-request-form .select2-container {
width: 250px !important;
}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 268fc995aa7..e1a72af0013 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -7,6 +7,7 @@
}
.reply-btn {
@extend .btn-primary;
+ margin: 10px $gl-padding;
}
.diff-file .diff-content {
tr.line_holder:hover {
@@ -38,9 +39,8 @@
}
.new_note, .edit_note {
- .buttons {
- margin-top: 8px;
- margin-bottom: 3px;
+ .note-form-actions {
+ margin-top: $gl-padding;
}
.note-preview-holder {
@@ -79,8 +79,8 @@
padding: $gl-padding;
margin-left: -$gl-padding;
margin-right: -$gl-padding;
- border-right: 1px solid #ECEEF1;
- border-top: 1px solid #ECEEF1;
+ border-right: 1px solid $border-color;
+ border-top: 1px solid $border-color;
margin-bottom: -$gl-padding;
}
@@ -150,7 +150,6 @@
.discussion-reply-holder {
background: $background-color;
- padding: 10px 15px;
border-top: 1px solid $border-color;
}
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index d3b10040022..2ded32dba12 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -5,7 +5,7 @@
font-weight: normal;
}
}
-.no-ssh-key-message {
+.no-ssh-key-message, .project-limit-message {
background-color: #f28d35;
margin-bottom: 16px;
}
@@ -18,10 +18,6 @@
}
}
-.project-edit-content {
- padding: 7px;
-}
-
.project-name-holder {
.help-inline {
vertical-align: top;
@@ -30,12 +26,6 @@
}
.project-home-panel {
- text-align: center;
- background: #f7f8fa;
- margin: -$gl-padding;
- padding: $gl-padding;
- padding: 44px 0 17px 0;
-
.project-identicon-holder {
margin-bottom: 16px;
@@ -90,7 +80,12 @@
}
.visibility-level-label {
+ @extend .btn;
+ @extend .btn-gray;
+
color: $gray;
+ cursor: default;
+
i {
color: inherit;
}
@@ -100,7 +95,6 @@
display: inline-table;
position: relative;
top: 17px;
- margin-bottom: 44px;
}
.project-repo-buttons {
@@ -178,6 +172,11 @@
&:active {
outline: none;
}
+
+ &.btn-clipboard {
+ padding-left: 15px;
+ padding-right: 15px;
+ }
}
.active {
@@ -366,7 +365,7 @@ table.table.protected-branches-list tr.no-border {
.project-stats {
text-align: center;
- margin-top: 15px;
+ margin-top: $gl-padding;
margin-bottom: 0;
padding-top: 10px;
padding-bottom: 4px;
@@ -552,4 +551,4 @@ pre.light-well {
z-index: 100;
position: relative;
}
-} \ No newline at end of file
+}
diff --git a/app/assets/stylesheets/pages/snippets.scss b/app/assets/stylesheets/pages/snippets.scss
index 242783a7b7e..bb74e50151d 100644
--- a/app/assets/stylesheets/pages/snippets.scss
+++ b/app/assets/stylesheets/pages/snippets.scss
@@ -27,56 +27,22 @@
}
.snippet-holder {
- .snippet-details {
- .page-title {
- margin-top: -15px;
- padding: 10px 0;
- margin-bottom: 0;
- color: #5c5d5e;
- font-size: 16px;
-
- .author {
- color: #5c5d5e;
- }
-
- .snippet-id {
- color: #5c5d5e;
- }
- }
-
- .snippet-title {
- margin: 0;
- font-size: 23px;
- color: #313236;
- }
-
- @media (max-width: $screen-md-max) {
- .new-snippet-link {
- display: none;
- }
- }
-
- @media (max-width: $screen-sm-max) {
- .creator,
- .page-title .btn-close {
- display: none;
- }
- }
- }
+ margin-bottom: -$gl-padding;
.file-holder {
border-top: 0;
}
}
-
.snippet-box {
@include border-radius(2px);
- display: inline-block;
- padding: 10px $gl-padding;
+ display: block;
+ float: left;
+ padding: 0 $gl-padding;
font-weight: normal;
margin-right: 10px;
font-size: $gl-font-size;
border: 1px solid;
+ line-height: 40px;
}
diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss
index dfaeba41cf6..cdf514197cb 100644
--- a/app/assets/stylesheets/pages/wiki.scss
+++ b/app/assets/stylesheets/pages/wiki.scss
@@ -4,3 +4,8 @@
margin-right: auto;
padding-right: 7px;
}
+
+.wiki-last-edit-by {
+ font-size: 80%;
+ font-weight: normal;
+}
diff --git a/app/controllers/abuse_reports_controller.rb b/app/controllers/abuse_reports_controller.rb
index 2f4054eaa11..20bc5173f1d 100644
--- a/app/controllers/abuse_reports_controller.rb
+++ b/app/controllers/abuse_reports_controller.rb
@@ -10,7 +10,7 @@ class AbuseReportsController < ApplicationController
if @abuse_report.save
if current_application_settings.admin_notification_email.present?
- AbuseReportMailer.delay.notify(@abuse_report.id)
+ AbuseReportMailer.notify(@abuse_report.id).deliver_later
end
message = "Thank you for your report. A GitLab administrator will look into it shortly."
diff --git a/app/controllers/admin/impersonation_controller.rb b/app/controllers/admin/impersonation_controller.rb
index 0382402afa6..bf98af78615 100644
--- a/app/controllers/admin/impersonation_controller.rb
+++ b/app/controllers/admin/impersonation_controller.rb
@@ -5,14 +5,20 @@ class Admin::ImpersonationController < Admin::ApplicationController
before_action :authorize_impersonator!
def create
- session[:impersonator_id] = current_user.username
- session[:impersonator_return_to] = request.env['HTTP_REFERER']
+ if @user.blocked?
+ flash[:alert] = "You cannot impersonate a blocked user"
- warden.set_user(user, scope: 'user')
+ redirect_to admin_user_path(@user)
+ else
+ session[:impersonator_id] = current_user.username
+ session[:impersonator_return_to] = admin_user_path(@user)
+
+ warden.set_user(user, scope: 'user')
- flash[:alert] = "You are impersonating #{user.username}."
+ flash[:alert] = "You are impersonating #{user.username}."
- redirect_to root_path
+ redirect_to root_path
+ end
end
def destroy
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index 202e9da9eee..77c8dafc012 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -1,41 +1,15 @@
class AutocompleteController < ApplicationController
skip_before_action :authenticate_user!, only: [:users]
+ before_action :find_users, only: [:users]
def users
- begin
- @users =
- if params[:project_id].present?
- project = Project.find(params[:project_id])
-
- if can?(current_user, :read_project, project)
- project.team.users
- end
- elsif params[:group_id]
- group = Group.find(params[:group_id])
-
- if can?(current_user, :read_group, group)
- group.users
- end
- elsif current_user
- User.all
- end
- rescue ActiveRecord::RecordNotFound
- if current_user
- return render json: {}, status: 404
- end
- end
-
- if @users.nil? && current_user.nil?
- authenticate_user!
- end
-
@users ||= User.none
@users = @users.search(params[:search]) if params[:search].present?
@users = @users.active
@users = @users.reorder(:name)
@users = @users.page(params[:page]).per(PER_PAGE)
- unless params[:search].present?
+ if params[:search].blank?
# Include current user if available to filter by "Me"
if params[:current_user] && current_user
@users = [*@users, current_user].uniq
@@ -49,4 +23,25 @@ class AutocompleteController < ApplicationController
@user = User.find(params[:id])
render json: @user, only: [:name, :username, :id], methods: [:avatar_url]
end
+
+ private
+
+ def find_users
+ @users =
+ if params[:project_id].present?
+ project = Project.find(params[:project_id])
+ return render_404 unless can?(current_user, :read_project, project)
+
+ project.team.users
+ elsif params[:group_id].present?
+ group = Group.find(params[:group_id])
+ return render_404 unless can?(current_user, :read_group, group)
+
+ group.users
+ elsif current_user
+ User.all
+ else
+ User.none
+ end
+ end
end
diff --git a/app/controllers/ci/lints_controller.rb b/app/controllers/ci/lints_controller.rb
index 24dd1b5c93a..a4f6aff49b4 100644
--- a/app/controllers/ci/lints_controller.rb
+++ b/app/controllers/ci/lints_controller.rb
@@ -15,10 +15,10 @@ module Ci
@builds = @config_processor.builds
@status = true
end
- rescue Ci::GitlabCiYamlProcessor::ValidationError => e
+ rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e
@error = e.message
@status = false
- rescue Exception
+ rescue
@error = "Undefined error"
@status = false
end
diff --git a/app/controllers/concerns/creates_merge_request_for_commit.rb b/app/controllers/concerns/creates_merge_request_for_commit.rb
new file mode 100644
index 00000000000..c7527822158
--- /dev/null
+++ b/app/controllers/concerns/creates_merge_request_for_commit.rb
@@ -0,0 +1,28 @@
+module CreatesMergeRequestForCommit
+ extend ActiveSupport::Concern
+
+ def new_merge_request_path
+ if @project.forked?
+ target_project = @project.forked_from_project || @project
+ target_branch = target_project.repository.root_ref
+ else
+ target_project = @project
+ target_branch = @ref
+ end
+
+ new_namespace_project_merge_request_path(
+ @project.namespace,
+ @project,
+ merge_request: {
+ source_project_id: @project.id,
+ target_project_id: target_project.id,
+ source_branch: @new_branch,
+ target_branch: target_branch
+ }
+ )
+ end
+
+ def create_merge_request?
+ params[:create_merge_request] && @new_branch != @ref
+ end
+end
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index 8da7b4d50ea..28803164fcf 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -70,6 +70,7 @@ class ProfilesController < Profiles::ApplicationController
:email,
:hide_no_password,
:hide_no_ssh_key,
+ :hide_project_limit,
:linkedin,
:location,
:name,
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 93738aa1ee5..31a33bfd237 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -1,6 +1,7 @@
# Controller for viewing a file's blame
class Projects::BlobController < Projects::ApplicationController
include ExtractsPath
+ include CreatesMergeRequestForCommit
include ActionView::Helpers::SanitizeHelper
# Raised when given an invalid file path
@@ -22,21 +23,9 @@ class Projects::BlobController < Projects::ApplicationController
end
def create
- result = Files::CreateService.new(@project, current_user, @commit_params).execute
-
- if result[:status] == :success
- flash[:notice] = "The changes have been successfully committed"
- respond_to do |format|
- format.html { redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) }
- format.json { render json: { message: "success", filePath: namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) } }
- end
- else
- flash[:alert] = result[:message]
- respond_to do |format|
- format.html { render :new }
- format.json { render json: { message: "failed", filePath: namespace_project_blob_path(@project.namespace, @project, @id) } }
- end
- end
+ create_commit(Files::CreateService, success_path: after_create_path,
+ failure_view: :new,
+ failure_path: namespace_project_new_blob_path(@project.namespace, @project, @ref))
end
def show
@@ -47,21 +36,9 @@ class Projects::BlobController < Projects::ApplicationController
end
def update
- result = Files::UpdateService.new(@project, current_user, @commit_params).execute
-
- if result[:status] == :success
- flash[:notice] = "Your changes have been successfully committed"
- respond_to do |format|
- format.html { redirect_to after_edit_path }
- format.json { render json: { message: "success", filePath: after_edit_path } }
- end
- else
- flash[:alert] = result[:message]
- respond_to do |format|
- format.html { render :edit }
- format.json { render json: { message: "failed", filePath: namespace_project_new_blob_path(@project.namespace, @project, @id) } }
- end
- end
+ create_commit(Files::UpdateService, success_path: after_edit_path,
+ failure_view: :edit,
+ failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
end
def preview
@@ -77,7 +54,7 @@ class Projects::BlobController < Projects::ApplicationController
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
- redirect_to namespace_project_tree_path(@project.namespace, @project, @target_branch)
+ redirect_to after_destroy_path
else
flash[:alert] = result[:message]
render :show
@@ -131,15 +108,51 @@ class Projects::BlobController < Projects::ApplicationController
render_404
end
+ def create_commit(service, success_path:, failure_view:, failure_path:)
+ result = service.new(@project, current_user, @commit_params).execute
+
+ if result[:status] == :success
+ flash[:notice] = "Your changes have been successfully committed"
+ respond_to do |format|
+ format.html { redirect_to success_path }
+ format.json { render json: { message: "success", filePath: success_path } }
+ end
+ else
+ flash[:alert] = result[:message]
+ respond_to do |format|
+ format.html { render failure_view }
+ format.json { render json: { message: "failed", filePath: failure_path } }
+ end
+ end
+ end
+
+ def after_create_path
+ @after_create_path ||=
+ if create_merge_request?
+ new_merge_request_path
+ else
+ namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @file_path))
+ end
+ end
+
def after_edit_path
@after_edit_path ||=
- if from_merge_request
+ if create_merge_request?
+ new_merge_request_path
+ elsif from_merge_request && @new_branch == @ref
diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
"#file-path-#{hexdigest(@path)}"
- elsif @target_branch.present?
- namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path))
else
- namespace_project_blob_path(@project.namespace, @project, @id)
+ namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @path))
+ end
+ end
+
+ def after_destroy_path
+ @after_destroy_path ||=
+ if create_merge_request?
+ new_merge_request_path
+ else
+ namespace_project_tree_path(@project.namespace, @project, @new_branch)
end
end
@@ -154,7 +167,7 @@ class Projects::BlobController < Projects::ApplicationController
def editor_variables
@current_branch = @ref
- @target_branch = params[:new_branch].present? ? sanitized_new_branch_name : @ref
+ @new_branch = params[:new_branch].present? ? sanitized_new_branch_name : @ref
@file_path =
if action_name.to_s == 'create'
@@ -174,7 +187,7 @@ class Projects::BlobController < Projects::ApplicationController
@commit_params = {
file_path: @file_path,
current_branch: @current_branch,
- target_branch: @target_branch,
+ target_branch: @new_branch,
commit_message: params[:commit_message],
file_content: params[:content],
file_content_encoding: params[:encoding]
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index 3ac0a75fa70..3c2849a7601 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -3,7 +3,7 @@ class Projects::BranchesController < Projects::ApplicationController
# Authorize
before_action :require_non_empty_project
before_action :authorize_download_code!
- before_action :authorize_push_code!, only: [:create, :destroy]
+ before_action :authorize_push_code!, only: [:new, :create, :destroy]
def index
@sort = params[:sort] || 'name'
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index deefdd76667..3f137440e28 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -67,7 +67,12 @@ class Projects::CommitController < Projects::ApplicationController
end
def define_show_vars
- @diffs = commit.diffs
+ if params[:w].to_i == 1
+ @diffs = commit.diffs({ ignore_whitespace_change: true })
+ else
+ @diffs = commit.diffs
+ end
+
@notes_count = commit.notes.count
@builds = ci_commit.builds if ci_commit
diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb
index 066b66014f8..fb8788f0818 100644
--- a/app/controllers/projects/imports_controller.rb
+++ b/app/controllers/projects/imports_controller.rb
@@ -28,8 +28,8 @@ class Projects::ImportsController < Projects::ApplicationController
if @project.import_finished?
redirect_to(project_path(@project)) and return
else
- redirect_to new_namespace_project_import_path(@project.namespace,
- @project) && return
+ redirect_to(new_namespace_project_import_path(@project.namespace,
+ @project)) and return
end
end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index e74c2905e48..ae474cf8d68 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -60,7 +60,7 @@ class Projects::IssuesController < Projects::ApplicationController
def show
@participants = @issue.participants(current_user)
@note = @project.notes.new(noteable: @issue)
- @notes = @issue.notes.with_associations.fresh
+ @notes = @issue.notes.nonawards.with_associations.fresh
@noteable = @issue
respond_with(@issue)
@@ -158,12 +158,10 @@ class Projects::IssuesController < Projects::ApplicationController
end
def issue_params
- permitted = params.require(:issue).permit(
+ params.require(:issue).permit(
:title, :assignee_id, :position, :description,
:milestone_id, :state_event, :task_num, label_ids: []
)
- params[:issue][:title].strip! if params[:issue][:title]
- permitted
end
def bulk_update_params
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index f2e9a34dd2e..71df622eb0e 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -268,7 +268,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
# Build a note object for comment form
@note = @project.notes.new(noteable: @merge_request)
- @notes = @merge_request.mr_and_commit_notes.inc_author.fresh
+ @notes = @merge_request.mr_and_commit_notes.nonawards.inc_author.fresh
@discussions = Note.discussions_from_notes(@notes)
@noteable = @merge_request
@@ -290,13 +290,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def merge_request_params
- permitted = params.require(:merge_request).permit(
+ params.require(:merge_request).permit(
:title, :assignee_id, :source_project_id, :source_branch,
:target_project_id, :target_branch, :milestone_id,
:state_event, :description, :task_num, label_ids: []
)
- params[:merge_request][:title].strip! if params[:merge_request][:title]
- permitted
end
def merge_params
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 41cd08c93c6..5ac18446aa7 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -3,7 +3,7 @@ class Projects::NotesController < Projects::ApplicationController
before_action :authorize_read_note!
before_action :authorize_create_note!, only: [:create]
before_action :authorize_admin_note!, only: [:update, :destroy]
- before_action :find_current_user_notes, except: [:destroy, :delete_attachment]
+ before_action :find_current_user_notes, except: [:destroy, :delete_attachment, :award_toggle]
def index
current_fetched_at = Time.now.to_i
@@ -58,6 +58,30 @@ class Projects::NotesController < Projects::ApplicationController
end
end
+ def award_toggle
+ noteable = if note_params[:noteable_type] == "issue"
+ project.issues.find(note_params[:noteable_id])
+ else
+ project.merge_requests.find(note_params[:noteable_id])
+ end
+
+ data = {
+ author: current_user,
+ is_award: true,
+ note: note_params[:note].gsub(":", '')
+ }
+
+ note = noteable.notes.find_by(data)
+
+ if note
+ note.destroy
+ else
+ Notes::CreateService.new(project, current_user, note_params).execute
+ end
+
+ render json: { ok: true }
+ end
+
private
def note
@@ -111,6 +135,9 @@ class Projects::NotesController < Projects::ApplicationController
id: note.id,
discussion_id: note.discussion_id,
html: note_to_html(note),
+ award: note.is_award,
+ emoji_path: note.is_award ? view_context.image_url(::AwardEmoji.path_to_emoji_image(note.note)) : "",
+ note: note.note,
discussion_html: note_to_discussion_html(note),
discussion_with_diff_html: note_to_discussion_with_diff_html(note)
}
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index cb39c2b8782..280fe12cc7c 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -2,7 +2,7 @@ class Projects::TagsController < Projects::ApplicationController
# Authorize
before_action :require_non_empty_project
before_action :authorize_download_code!
- before_action :authorize_push_code!, only: [:create]
+ before_action :authorize_push_code!, only: [:new, :create]
before_action :authorize_admin_project!, only: [:destroy]
def index
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index bdcb1a3e297..8f272ad1281 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -1,6 +1,7 @@
# Controller for viewing a repository's file structure
class Projects::TreeController < Projects::ApplicationController
include ExtractsPath
+ include CreatesMergeRequestForCommit
include ActionView::Helpers::SanitizeHelper
before_action :require_non_empty_project, except: [:new, :create]
@@ -43,7 +44,7 @@ class Projects::TreeController < Projects::ApplicationController
if result && result[:status] == :success
flash[:notice] = "The directory has been successfully created"
respond_to do |format|
- format.html { redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @dir_name)) }
+ format.html { redirect_to after_create_dir_path }
end
else
flash[:alert] = message
@@ -53,6 +54,8 @@ class Projects::TreeController < Projects::ApplicationController
end
end
+ private
+
def assign_dir_vars
@new_branch = params[:new_branch].present? ? sanitize(strip_tags(params[:new_branch])) : @ref
@dir_name = File.join(@path, params[:dir_name])
@@ -63,4 +66,12 @@ class Projects::TreeController < Projects::ApplicationController
commit_message: params[:commit_message],
}
end
+
+ def after_create_dir_path
+ if create_merge_request?
+ new_merge_request_path
+ else
+ namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @dir_name))
+ end
+ end
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 23453195e85..10c75370d7b 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -123,7 +123,7 @@ class ProjectsController < ApplicationController
::Projects::DestroyService.new(@project, current_user, {}).execute
flash[:alert] = "Project '#{@project.name}' was deleted."
- redirect_back_or_default(default: dashboard_projects_path, options: {})
+ redirect_to dashboard_projects_path
rescue Projects::DestroyService::DestroyError => ex
redirect_to edit_project_path(@project), alert: ex.message
end
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index 9f9f9a92f11..c72df73af46 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -1,6 +1,9 @@
class SnippetsController < ApplicationController
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
+ # Allow read snippet
+ before_action :authorize_read_snippet!, only: [:show, :raw]
+
# Allow modify snippet
before_action :authorize_update_snippet!, only: [:edit, :update]
@@ -79,10 +82,14 @@ class SnippetsController < ApplicationController
[Snippet::PUBLIC, Snippet::INTERNAL]).
find(params[:id])
else
- PersonalSnippet.are_public.find(params[:id])
+ PersonalSnippet.find(params[:id])
end
end
+ def authorize_read_snippet!
+ authenticate_user! unless can?(current_user, :read_personal_snippet, @snippet)
+ end
+
def authorize_update_snippet!
return render_404 unless can?(current_user, :update_personal_snippet, @snippet)
end
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 1484356a7f4..30cb869eb2a 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -3,14 +3,11 @@ class UsersController < ApplicationController
before_action :set_user
def show
- @contributed_projects = contributed_projects.joined(@user).
- reject(&:forked?)
+ @contributed_projects = contributed_projects.joined(@user).reject(&:forked?)
- @projects = @user.personal_projects.
- where(id: authorized_projects_ids).includes(:namespace)
+ @projects = PersonalProjectsFinder.new(@user).execute(current_user)
- # Collect only groups common for both users
- @groups = @user.groups & GroupsFinder.new.execute(current_user)
+ @groups = JoinedGroupsFinder.new(@user).execute(current_user)
respond_to do |format|
format.html
@@ -53,16 +50,8 @@ class UsersController < ApplicationController
@user = User.find_by_username!(params[:username])
end
- def authorized_projects_ids
- # Projects user can view
- @authorized_projects_ids ||=
- ProjectsFinder.new.execute(current_user).pluck(:id)
- end
-
def contributed_projects
- @contributed_projects = Project.
- where(id: authorized_projects_ids & @user.contributed_projects_ids).
- includes(:namespace)
+ ContributedProjectsFinder.new(@user).execute(current_user)
end
def contributions_calendar
@@ -73,9 +62,13 @@ class UsersController < ApplicationController
def load_events
# Get user activity feed for projects common for both users
@events = @user.recent_events.
- where(project_id: authorized_projects_ids).
- with_associations
+ merge(projects_for_current_user).
+ references(:project).
+ with_associations.
+ limit_recent(20, params[:offset])
+ end
- @events = @events.limit(20).offset(params[:offset] || 0)
+ def projects_for_current_user
+ ProjectsFinder.new.execute(current_user)
end
end
diff --git a/app/finders/contributed_projects_finder.rb b/app/finders/contributed_projects_finder.rb
new file mode 100644
index 00000000000..0209649b017
--- /dev/null
+++ b/app/finders/contributed_projects_finder.rb
@@ -0,0 +1,37 @@
+class ContributedProjectsFinder
+ def initialize(user)
+ @user = user
+ end
+
+ # Finds the projects "@user" contributed to, limited to either public projects
+ # or projects visible to the given user.
+ #
+ # current_user - When given the list of the projects is limited to those only
+ # visible by this user.
+ #
+ # Returns an ActiveRecord::Relation.
+ def execute(current_user = nil)
+ if current_user
+ relation = projects_visible_to_user(current_user)
+ else
+ relation = public_projects
+ end
+
+ relation.includes(:namespace).order_id_desc
+ end
+
+ private
+
+ def projects_visible_to_user(current_user)
+ authorized = @user.contributed_projects.visible_to_user(current_user)
+
+ union = Gitlab::SQL::Union.
+ new([authorized.select(:id), public_projects.select(:id)])
+
+ Project.where("projects.id IN (#{union.to_sql})")
+ end
+
+ def public_projects
+ @user.contributed_projects.public_only
+ end
+end
diff --git a/app/finders/groups_finder.rb b/app/finders/groups_finder.rb
index b5f3176461c..91cb0f228f0 100644
--- a/app/finders/groups_finder.rb
+++ b/app/finders/groups_finder.rb
@@ -1,39 +1,44 @@
class GroupsFinder
- def execute(current_user, options = {})
- all_groups(current_user)
+ # Finds the groups available to the given user.
+ #
+ # current_user - The user to find the groups for.
+ #
+ # Returns an ActiveRecord::Relation.
+ def execute(current_user = nil)
+ if current_user
+ relation = groups_visible_to_user(current_user)
+ else
+ relation = public_groups
+ end
+
+ relation.order_id_desc
end
private
- def all_groups(current_user)
- group_ids = if current_user
- if current_user.authorized_groups.any?
- # User has access to groups
- #
- # Return only:
- # groups with public projects
- # groups with internal projects
- # groups with joined projects
- #
- Project.public_and_internal_only.pluck(:namespace_id) +
- current_user.authorized_groups.pluck(:id)
- else
- # User has no group membership
- #
- # Return only:
- # groups with public projects
- # groups with internal projects
- #
- Project.public_and_internal_only.pluck(:namespace_id)
- end
- else
- # Not authenticated
- #
- # Return only:
- # groups with public projects
- Project.public_only.pluck(:namespace_id)
- end
-
- Group.where("public IS TRUE OR id IN(?)", group_ids)
+ # This method returns the groups "current_user" can see.
+ def groups_visible_to_user(current_user)
+ base = groups_for_projects(public_and_internal_projects)
+
+ union = Gitlab::SQL::Union.
+ new([base.select(:id), current_user.authorized_groups.select(:id)])
+
+ Group.where("namespaces.id IN (#{union.to_sql})")
+ end
+
+ def public_groups
+ groups_for_projects(public_projects)
+ end
+
+ def groups_for_projects(projects)
+ Group.public_and_given_groups(projects.select(:namespace_id))
+ end
+
+ def public_projects
+ Project.unscoped.public_only
+ end
+
+ def public_and_internal_projects
+ Project.unscoped.public_and_internal_only
end
end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index c407dfc163a..3d5e8b6fbe7 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -62,10 +62,10 @@ class IssuableFinder
if project?
@project = Project.find(params[:project_id])
-
+
unless Ability.abilities.allowed?(current_user, :read_project, @project)
@project = nil
- end
+ end
else
@project = nil
end
@@ -77,11 +77,11 @@ class IssuableFinder
return @projects if defined?(@projects)
if project?
- project
+ @projects = project
elsif current_user && params[:authorized_only].presence && !current_user_related?
- current_user.authorized_projects
+ @projects = current_user.authorized_projects
else
- ProjectsFinder.new.execute(current_user)
+ @projects = ProjectsFinder.new.execute(current_user)
end
end
@@ -190,8 +190,10 @@ class IssuableFinder
def by_project(items)
items =
- if projects
- items.of_projects(projects).references(:project)
+ if project?
+ items.of_projects(projects).references_project
+ elsif projects
+ items.merge(projects.reorder(nil)).join_project
else
items.none
end
@@ -206,7 +208,9 @@ class IssuableFinder
end
def sort(items)
- items.sort(params[:sort])
+ # Ensure we always have an explicit sort order (instead of inheriting
+ # multiple orders when combining ActiveRecord::Relation objects).
+ params[:sort] ? items.sort(params[:sort]) : items.reorder(id: :desc)
end
def by_assignee(items)
diff --git a/app/finders/joined_groups_finder.rb b/app/finders/joined_groups_finder.rb
new file mode 100644
index 00000000000..e7523136fea
--- /dev/null
+++ b/app/finders/joined_groups_finder.rb
@@ -0,0 +1,49 @@
+# Class for finding the groups a user is a member of.
+class JoinedGroupsFinder
+ def initialize(user = nil)
+ @user = user
+ end
+
+ # Finds the groups of the source user, optionally limited to those visible to
+ # the current user.
+ #
+ # current_user - If given the groups of "@user" will only include the groups
+ # "current_user" can also see.
+ #
+ # Returns an ActiveRecord::Relation.
+ def execute(current_user = nil)
+ if current_user
+ relation = groups_visible_to_user(current_user)
+ else
+ relation = public_groups
+ end
+
+ relation.order_id_desc
+ end
+
+ private
+
+ # Returns the groups the user in "current_user" can see.
+ #
+ # This list includes all public/internal projects as well as the projects of
+ # "@user" that "current_user" also has access to.
+ def groups_visible_to_user(current_user)
+ base = @user.authorized_groups.visible_to_user(current_user)
+ extra = public_and_internal_groups
+ union = Gitlab::SQL::Union.new([base.select(:id), extra.select(:id)])
+
+ Group.where("namespaces.id IN (#{union.to_sql})")
+ end
+
+ def public_groups
+ groups_for_projects(@user.authorized_projects.public_only)
+ end
+
+ def public_and_internal_groups
+ groups_for_projects(@user.authorized_projects.public_and_internal_only)
+ end
+
+ def groups_for_projects(projects)
+ @user.groups.public_and_given_groups(projects.select(:namespace_id))
+ end
+end
diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb
index ab252821b52..fa4c635f55c 100644
--- a/app/finders/notes_finder.rb
+++ b/app/finders/notes_finder.rb
@@ -12,9 +12,9 @@ class NotesFinder
when "commit"
project.notes.for_commit_id(target_id).not_inline
when "issue"
- project.issues.find(target_id).notes.inc_author
+ project.issues.find(target_id).notes.nonawards.inc_author
when "merge_request"
- project.merge_requests.find(target_id).mr_and_commit_notes.inc_author
+ project.merge_requests.find(target_id).mr_and_commit_notes.nonawards.inc_author
when "snippet", "project_snippet"
project.snippets.find(target_id).notes
else
diff --git a/app/finders/personal_projects_finder.rb b/app/finders/personal_projects_finder.rb
new file mode 100644
index 00000000000..a61ffa22990
--- /dev/null
+++ b/app/finders/personal_projects_finder.rb
@@ -0,0 +1,41 @@
+class PersonalProjectsFinder
+ def initialize(user)
+ @user = user
+ end
+
+ # Finds the projects belonging to the user in "@user", limited to either
+ # public projects or projects visible to the given user.
+ #
+ # current_user - When given the list of projects is limited to those only
+ # visible by this user.
+ #
+ # Returns an ActiveRecord::Relation.
+ def execute(current_user = nil)
+ if current_user
+ relation = projects_visible_to_user(current_user)
+ else
+ relation = public_projects
+ end
+
+ relation.includes(:namespace).order_id_desc
+ end
+
+ private
+
+ def projects_visible_to_user(current_user)
+ authorized = @user.personal_projects.visible_to_user(current_user)
+
+ union = Gitlab::SQL::Union.
+ new([authorized.select(:id), public_and_internal_projects.select(:id)])
+
+ 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
+ end
+end
diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb
index c81bb51583a..3b4e0362e04 100644
--- a/app/finders/projects_finder.rb
+++ b/app/finders/projects_finder.rb
@@ -1,11 +1,39 @@
class ProjectsFinder
- def execute(current_user, options = {})
+ # 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.
+ def execute(current_user = nil, options = {})
group = options[:group]
if group
- group_projects(current_user, group)
+ segments = group_projects(current_user, group)
else
- all_projects(current_user)
+ 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
end
@@ -13,77 +41,36 @@ class ProjectsFinder
def group_projects(current_user, group)
if current_user
- if group.users.include?(current_user)
- # User is group member
- #
- # Return ALL group projects
- group.projects
- else
- projects_members = ProjectMember.in_projects(group.projects).
- with_user(current_user)
-
- if projects_members.any?
- # User is a project member
- #
- # Return only:
- # public projects
- # internal projects
- # joined projects
- #
- group.projects.where(
- "projects.id IN (?) OR projects.visibility_level IN (?)",
- projects_members.pluck(:source_id),
- Project.public_and_internal_levels
- )
- else
- # User has no access to group or group projects
- #
- # Return only:
- # public projects
- # internal projects
- #
- group.projects.public_and_internal_only
- end
- end
+ [
+ group_projects_for_user(current_user, group),
+ group.projects.public_and_internal_only
+ ]
else
- # Not authenticated
- #
- # Return only:
- # public projects
- group.projects.public_only
+ [group.projects.public_only]
end
end
def all_projects(current_user)
if current_user
- if current_user.authorized_projects.any?
- # User has access to private projects
- #
- # Return only:
- # public projects
- # internal projects
- # joined projects
- #
- Project.where(
- "projects.id IN (?) OR projects.visibility_level IN (?)",
- current_user.authorized_projects.pluck(:id),
- Project.public_and_internal_levels
- )
- else
- # User has no access to private projects
- #
- # Return only:
- # public projects
- # internal projects
- #
- Project.public_and_internal_only
- end
+ [current_user.authorized_projects, public_and_internal_projects]
else
- # Not authenticated
- #
- # Return only:
- # public projects
- Project.public_only
+ [Project.public_only]
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
+
+ def public_and_internal_projects
+ Project.unscoped.public_and_internal_only
+ end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 8ecdeaf8e76..3230ff1b004 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -68,7 +68,7 @@ module ApplicationHelper
end
end
- def avatar_icon(user_or_email = nil, size = nil)
+ def avatar_icon(user_or_email = nil, size = nil, scale = 2)
if user_or_email.is_a?(User)
user = user_or_email
else
@@ -78,12 +78,12 @@ module ApplicationHelper
if user
user.avatar_url(size) || default_avatar
else
- gravatar_icon(user_or_email, size)
+ gravatar_icon(user_or_email, size, scale)
end
end
- def gravatar_icon(user_email = '', size = nil)
- GravatarService.new.execute(user_email, size) ||
+ def gravatar_icon(user_email = '', size = nil, scale = 2)
+ GravatarService.new.execute(user_email, size, scale) ||
default_avatar
end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 77d99140c43..df5f5fae23c 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -60,7 +60,7 @@ module BlobHelper
if Gitlab::MarkupHelper.previewable?(filename)
'Preview'
else
- 'Preview changes'
+ 'Preview Changes'
end
end
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
new file mode 100644
index 00000000000..313b6dde910
--- /dev/null
+++ b/app/helpers/button_helper.rb
@@ -0,0 +1,58 @@
+module ButtonHelper
+ # Output a "Copy to Clipboard" button
+ #
+ # data - Data attributes passed to `content_tag`
+ #
+ # Examples:
+ #
+ # # Define the clipboard's text
+ # clipboard_button(clipboard_text: "Foo")
+ # # => "<button class='...' data-clipboard-text='Foo'>...</button>"
+ #
+ # # Define the target element
+ # clipboard_button(clipboard_target: "#foo")
+ # # => "<button class='...' data-clipboard-target='#foo'>...</button>"
+ #
+ # See http://clipboardjs.com/#usage
+ def clipboard_button(data = {})
+ content_tag :button,
+ icon('clipboard'),
+ class: 'btn btn-xs btn-clipboard',
+ data: data,
+ type: :button
+ 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?)
+
+ protocol = gitlab_config.protocol.upcase
+
+ content_tag :button, protocol,
+ class: klass,
+ data: {
+ clone: project.http_url_to_repo,
+ 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?)
+
+ content_tag :button, 'SSH',
+ class: klass,
+ data: {
+ clone: project.ssh_url_to_repo,
+ 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/clipboard_helper.rb b/app/helpers/clipboard_helper.rb
deleted file mode 100644
index 3c1d7569fac..00000000000
--- a/app/helpers/clipboard_helper.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-module ClipboardHelper
- def clipboard_button
- content_tag :button,
- icon('clipboard'),
- class: 'btn btn-xs btn-clipboard js-clipboard-trigger',
- type: :button
- end
-end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index 9df20c9fce5..590d20ac7b3 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -109,7 +109,7 @@ module CommitsHelper
)
elsif @path.present?
return link_to(
- "Browse Dir »",
+ "Browse Directory »",
namespace_project_tree_path(project.namespace, project,
tree_join(commit.id, @path)),
class: "pull-right"
@@ -117,7 +117,7 @@ module CommitsHelper
end
end
link_to(
- "Browse Code »",
+ "Browse Files »",
namespace_project_tree_path(project.namespace, project, commit),
class: "pull-right"
)
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index bfd3622a6a9..24134310fc5 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -146,9 +146,9 @@ module DiffHelper
def submodule_link(blob, ref, repository = @repository)
tree, commit = submodule_links(blob, ref, repository)
commit_id = if commit.nil?
- blob.id[0..10]
+ Commit.truncate_sha(blob.id)
else
- link_to "#{blob.id[0..10]}", commit
+ link_to Commit.truncate_sha(blob.id), commit
end
[
diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb
index 45788ba95ac..41b5bd7be90 100644
--- a/app/helpers/emails_helper.rb
+++ b/app/helpers/emails_helper.rb
@@ -28,6 +28,8 @@ module EmailsHelper
return "View #{action.humanize.singularize}"
end
end
+
+ nil
end
def color_email_diff(diffcontent)
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index b0b536d4649..f3fddef01cb 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -6,7 +6,7 @@
#
# For example instead of this:
#
-# namespace_project_merge_request_path(merge_request.project.namespace, merge_request.projects, merge_request)
+# namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
#
# We can simply use shortcut:
#
diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb
index 1cf5b96481a..5724d3aabec 100644
--- a/app/helpers/icons_helper.rb
+++ b/app/helpers/icons_helper.rb
@@ -27,16 +27,20 @@ module IconsHelper
end
end
- def public_icon
- icon('globe fw')
- end
-
- def internal_icon
- icon('shield fw')
- end
+ def visibility_level_icon(level, fw: true)
+ name =
+ case level
+ when Gitlab::VisibilityLevel::PRIVATE
+ 'lock'
+ when Gitlab::VisibilityLevel::INTERNAL
+ 'shield'
+ else # Gitlab::VisibilityLevel::PUBLIC
+ 'globe'
+ end
+
+ name << " fw" if fw
- def private_icon
- icon('lock fw')
+ icon(name)
end
def file_type_icon_class(type, mode, name)
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index beb083d82dc..e66b9c628c7 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -44,14 +44,17 @@ module IssuesHelper
end
def bulk_update_milestone_options
- options_for_select([['None (backlog)', -1]]) +
- options_from_collection_for_select(project_active_milestones, 'id',
- 'title', params[:milestone_id])
+ milestones = project_active_milestones.to_a
+ milestones.unshift(Milestone::None)
+
+ options_from_collection_for_select(milestones, 'id', 'title', params[:milestone_id])
end
def milestone_options(object)
- options_from_collection_for_select(object.project.milestones.active,
- 'id', 'title', object.milestone_id)
+ milestones = object.project.milestones.active.to_a
+ milestones.unshift(Milestone::None)
+
+ options_from_collection_for_select(milestones, 'id', 'title', object.milestone_id)
end
def issue_box_class(item)
@@ -84,7 +87,38 @@ module IssuesHelper
end
def merge_requests_sentence(merge_requests)
- merge_requests.map(&:to_reference).to_sentence(last_word_connector: ', or ')
+ # Sorting based on the `!123` or `group/project!123` reference will sort
+ # local merge requests first.
+ merge_requests.map do |merge_request|
+ merge_request.to_reference(@project)
+ end.sort.to_sentence(last_word_connector: ', or ')
+ end
+
+ def url_to_emoji(name)
+ emoji_path = ::AwardEmoji.path_to_emoji_image(name)
+ url_to_image(emoji_path)
+ rescue StandardError
+ ""
+ end
+
+ def emoji_author_list(notes, current_user)
+ list = notes.map do |note|
+ note.author == current_user ? "me" : note.author.name
+ end
+
+ list.join(", ")
+ end
+
+ def emoji_list
+ ::AwardEmoji::EMOJI_LIST
+ end
+
+ def note_active_class(notes, current_user)
+ if current_user && notes.pluck(:author_id).include?(current_user.id)
+ "active"
+ else
+ ""
+ end
end
# Required for Gitlab::Markdown::IssueReferenceFilter
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 728d877ace2..6c32647594d 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -8,14 +8,6 @@ module MergeRequestsHelper
)
end
- def new_mr_path_for_fork_from_push_event(event)
- new_namespace_project_merge_request_path(
- event.project.namespace,
- event.project,
- new_mr_from_push_event(event, event.project.forked_from_project)
- )
- end
-
def new_mr_from_push_event(event, target_project)
{
merge_request: {
@@ -47,7 +39,11 @@ module MergeRequestsHelper
end
def issues_sentence(issues)
- issues.map(&:to_reference).to_sentence
+ # Sorting based on the `#123` or `group/project#123` reference will sort
+ # local issues first.
+ issues.map do |issue|
+ issue.to_reference(@project)
+ end.sort.to_sentence
end
def mr_change_branches_path(merge_request)
@@ -57,18 +53,21 @@ module MergeRequestsHelper
source_project_id: @merge_request.source_project_id,
target_project_id: @merge_request.target_project_id,
source_branch: @merge_request.source_branch,
- target_branch: nil
- }
+ target_branch: @merge_request.target_branch,
+ },
+ change_branches: true
)
end
def source_branch_with_namespace(merge_request)
+ branch = link_to(merge_request.source_branch, namespace_project_commits_path(merge_request.source_project.namespace, merge_request.source_project, merge_request.source_branch))
+
if merge_request.for_fork?
namespace = link_to(merge_request.source_project_namespace,
project_path(merge_request.source_project))
- namespace + ":#{merge_request.source_branch}"
+ namespace + ":" + branch
else
- merge_request.source_branch
+ branch
end
end
diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb
index e7f3cb21038..faba418c4db 100644
--- a/app/helpers/namespaces_helper.rb
+++ b/app/helpers/namespaces_helper.rb
@@ -1,10 +1,10 @@
module NamespacesHelper
- def namespaces_options(selected = :current_user, scope = :default)
+ def namespaces_options(selected = :current_user, display_path: false)
groups = current_user.owned_groups + current_user.masters_groups
users = [current_user.namespace]
- group_opts = ["Groups", groups.sort_by(&:human_name).map {|g| [g.human_name, g.id]} ]
- users_opts = [ "Users", users.sort_by(&:human_name).map {|u| [u.human_name, u.id]} ]
+ group_opts = ["Groups", groups.sort_by(&:human_name).map {|g| [display_path ? g.path : g.human_name, g.id]} ]
+ users_opts = [ "Users", users.sort_by(&:human_name).map {|u| [display_path ? u.path : u.human_name, u.id]} ]
options = []
options << group_opts
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index 9b1dd8b8e54..e6fb8670e57 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -5,6 +5,14 @@ module NavHelper
def nav_sidebar_class
if nav_menu_collapsed?
+ "sidebar-collapsed"
+ else
+ "sidebar-expanded"
+ end
+ end
+
+ def page_sidebar_class
+ if nav_menu_collapsed?
"page-sidebar-collapsed"
else
"page-sidebar-expanded"
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 690ae2090db..48729e5260e 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -21,7 +21,7 @@ module ProjectsHelper
end
def link_to_member(project, author, opts = {})
- default_opts = { avatar: true, name: true, size: 16, author_class: 'author' }
+ default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" }
opts = default_opts.merge(opts)
return "(deleted)" unless author
@@ -39,7 +39,8 @@ module ProjectsHelper
if opts[:name]
link_to(author_html, user_path(author), class: "author_link").html_safe
else
- link_to(author_html, user_path(author), class: "author_link has_tooltip", data: { :'original-title' => sanitize(author.name) } ).html_safe
+ title = opts[:title].sub(":name", sanitize(author.name))
+ link_to(author_html, user_path(author), class: "author_link has_tooltip", data: { :'original-title' => title, container: 'body' } ).html_safe
end
end
@@ -173,8 +174,7 @@ module ProjectsHelper
'unknown'
end
- def default_url_to_repo(project = nil)
- project = project || @project
+ def default_url_to_repo(project = @project)
current_user ? project.url_to_repo : project.http_url_to_repo
end
@@ -253,14 +253,6 @@ module ProjectsHelper
filename_path(project, :version)
end
- def hidden_pass_url(original_url)
- result = URI(original_url)
- result.password = '*****' unless result.password.nil?
- result
- rescue
- original_url
- end
-
def project_wiki_path_with_version(proj, page, version, is_newest)
url_params = is_newest ? {} : { version_id: version }
namespace_project_wiki_path(proj.namespace, proj, page, url_params)
diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb
index 7e54d4d1b5b..7e175d0de8a 100644
--- a/app/helpers/selects_helper.rb
+++ b/app/helpers/selects_helper.rb
@@ -15,12 +15,14 @@ module SelectsHelper
html = {
class: css_class,
- 'data-placeholder' => placeholder,
- 'data-null-user' => null_user,
- 'data-any-user' => any_user,
- 'data-email-user' => email_user,
- 'data-first-user' => first_user,
- 'data-current-user' => current_user
+ data: {
+ placeholder: placeholder,
+ null_user: null_user,
+ any_user: any_user,
+ email_user: email_user,
+ first_user: first_user,
+ current_user: current_user
+ }
}
unless opts[:scope] == :all
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
index b52cd23aba2..72c65030f94 100644
--- a/app/helpers/visibility_level_helper.rb
+++ b/app/helpers/visibility_level_helper.rb
@@ -25,48 +25,24 @@ module VisibilityLevelHelper
end
def project_visibility_level_description(level)
- capture_haml do
- haml_tag :span do
- case level
- when Gitlab::VisibilityLevel::PRIVATE
- haml_concat "Project access must be granted explicitly for each user."
- when Gitlab::VisibilityLevel::INTERNAL
- haml_concat "The project can be cloned by"
- haml_concat "any logged in user."
- when Gitlab::VisibilityLevel::PUBLIC
- haml_concat "The project can be cloned"
- haml_concat "without any"
- haml_concat "authentication."
- end
- end
+ case level
+ when Gitlab::VisibilityLevel::PRIVATE
+ "Project access must be granted explicitly for each user."
+ when Gitlab::VisibilityLevel::INTERNAL
+ "The project can be cloned by any logged in user."
+ when Gitlab::VisibilityLevel::PUBLIC
+ "The project can be cloned without any authentication."
end
end
def snippet_visibility_level_description(level)
- capture_haml do
- haml_tag :span do
- case level
- when Gitlab::VisibilityLevel::PRIVATE
- haml_concat "The snippet is visible only for me."
- when Gitlab::VisibilityLevel::INTERNAL
- haml_concat "The snippet is visible for any logged in user."
- when Gitlab::VisibilityLevel::PUBLIC
- haml_concat "The snippet can be accessed"
- haml_concat "without any"
- haml_concat "authentication."
- end
- end
- end
- end
-
- def visibility_level_icon(level)
case level
when Gitlab::VisibilityLevel::PRIVATE
- private_icon
+ "The snippet is visible only for me."
when Gitlab::VisibilityLevel::INTERNAL
- internal_icon
+ "The snippet is visible for any logged in user."
when Gitlab::VisibilityLevel::PUBLIC
- public_icon
+ "The snippet can be accessed without any authentication."
end
end
diff --git a/app/mailers/base_mailer.rb b/app/mailers/base_mailer.rb
index aedb0889185..8b83bbd93b7 100644
--- a/app/mailers/base_mailer.rb
+++ b/app/mailers/base_mailer.rb
@@ -8,10 +8,6 @@ class BaseMailer < ActionMailer::Base
default from: Proc.new { default_sender_address.format }
default reply_to: Proc.new { default_reply_to_address.format }
- def self.delay
- delay_for(2.seconds)
- end
-
def can?
Ability.abilities.allowed?(current_user, action, subject)
end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index 500af08d209..07f3a56ec7a 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -1,8 +1,8 @@
class Ability
class << self
def allowed(user, subject)
- return not_auth_abilities(user, subject) if user.nil?
- return [] unless user.kind_of?(User)
+ return anonymous_abilities(user, subject) if user.nil?
+ return [] unless user.is_a?(User)
return [] if user.blocked?
case subject.class.name
@@ -20,15 +20,25 @@ class Ability
end.concat(global_abilities(user))
end
- # List of possible abilities
- # for non-authenticated user
- def not_auth_abilities(user, subject)
- project = if subject.kind_of?(Project)
+ # List of possible abilities for anonymous user
+ def anonymous_abilities(user, subject)
+ case true
+ when subject.is_a?(PersonalSnippet)
+ anonymous_personal_snippet_abilities(subject)
+ when subject.is_a?(Project) || subject.respond_to?(:project)
+ anonymous_project_abilities(subject)
+ when subject.is_a?(Group) || subject.respond_to?(:group)
+ anonymous_group_abilities(subject)
+ else
+ []
+ end
+ end
+
+ def anonymous_project_abilities(subject)
+ project = if subject.is_a?(Project)
subject
- elsif subject.respond_to?(:project)
- subject.project
else
- nil
+ subject.project
end
if project && project.public?
@@ -48,19 +58,29 @@ class Ability
rules - project_disabled_features_rules(project)
else
- group = if subject.kind_of?(Group)
- subject
- elsif subject.respond_to?(:group)
- subject.group
- else
- nil
- end
+ []
+ end
+ end
- if group && group.public_profile?
- [:read_group]
- else
- []
- end
+ def anonymous_group_abilities(subject)
+ group = if subject.is_a?(Group)
+ subject
+ else
+ subject.group
+ end
+
+ if group && group.public_profile?
+ [:read_group]
+ else
+ []
+ end
+ end
+
+ def anonymous_personal_snippet_abilities(snippet)
+ if snippet.public?
+ [:read_personal_snippet]
+ else
+ []
end
end
@@ -280,7 +300,7 @@ class Ability
end
end
- [:note, :project_snippet, :personal_snippet].each do |name|
+ [:note, :project_snippet].each do |name|
define_method "#{name}_abilities" do |user, subject|
rules = []
@@ -300,6 +320,24 @@ class Ability
end
end
+ def personal_snippet_abilities(user, snippet)
+ rules = []
+
+ if snippet.author == user
+ rules += [
+ :read_personal_snippet,
+ :update_personal_snippet,
+ :admin_personal_snippet
+ ]
+ end
+
+ if snippet.public? || snippet.internal?
+ rules << :read_personal_snippet
+ end
+
+ rules
+ end
+
def group_member_abilities(user, subject)
rules = []
target_user = subject.user
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 9e70247ef51..5ddcf3d9a0b 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -30,6 +30,8 @@
#
class ApplicationSetting < ActiveRecord::Base
+ CACHE_KEY = 'application_setting.last'
+
serialize :restricted_visibility_levels
serialize :import_sources
serialize :restricted_signup_domains, Array
@@ -73,15 +75,19 @@ class ApplicationSetting < ActiveRecord::Base
end
after_commit do
- Rails.cache.write('application_setting.last', self)
+ Rails.cache.write(CACHE_KEY, self)
end
def self.current
- Rails.cache.fetch('application_setting.last') do
+ Rails.cache.fetch(CACHE_KEY) do
ApplicationSetting.last
end
end
+ def self.expire
+ Rails.cache.delete(CACHE_KEY)
+ end
+
def self.create_from_defaults
create(
default_projects_limit: Settings.gitlab['default_projects_limit'],
@@ -99,7 +105,7 @@ class ApplicationSetting < ActiveRecord::Base
restricted_signup_domains: Settings.gitlab['restricted_signup_domains'],
import_sources: ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git'],
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
- max_artifacts_size: Settings.gitlab_ci['max_artifacts_size'],
+ max_artifacts_size: Settings.artifacts['max_size'],
)
end
diff --git a/app/models/ci/application_setting.rb b/app/models/ci/application_setting.rb
index 1307fa0b472..7f5df8ce6c4 100644
--- a/app/models/ci/application_setting.rb
+++ b/app/models/ci/application_setting.rb
@@ -12,13 +12,18 @@
module Ci
class ApplicationSetting < ActiveRecord::Base
extend Ci::Model
+ CACHE_KEY = 'ci_application_setting.last'
after_commit do
- Rails.cache.write('ci_application_setting.last', self)
+ Rails.cache.write(CACHE_KEY, self)
+ end
+
+ def self.expire
+ Rails.cache.delete(CACHE_KEY)
end
def self.current
- Rails.cache.fetch('ci_application_setting.last') do
+ Rails.cache.fetch(CACHE_KEY) do
Ci::ApplicationSetting.last
end
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index e78b154084b..52ce1b920fa 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -97,6 +97,8 @@ module Ci
state_machine :status, initial: :pending do
after_transition any => [:success, :failed, :canceled] do |build, transition|
+ return unless build.gl_project
+
project = build.project
if project.web_hooks?
diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb
index f96b479feac..0f74a7f0688 100644
--- a/app/models/ci/commit.rb
+++ b/app/models/ci/commit.rb
@@ -196,13 +196,13 @@ module Ci
end
def config_processor
+ return nil unless ci_yaml_file
@config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file, gl_project.path_with_namespace)
- rescue Ci::GitlabCiYamlProcessor::ValidationError => e
+ rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e
save_yaml_error(e.message)
nil
- rescue Exception => e
- logger.error e.message + "\n" + e.backtrace.join("\n")
- save_yaml_error("Undefined yaml error")
+ rescue
+ save_yaml_error("Undefined error")
nil
end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 492f6be1ce3..c0998a45709 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -78,11 +78,23 @@ class Commit
}x
end
+ def self.link_reference_pattern
+ super("commit", /(?<commit>\h{6,40})/)
+ end
+
def to_reference(from_project = nil)
if cross_project_reference?(from_project)
- "#{project.to_reference}@#{id}"
+ project.to_reference + self.class.reference_prefix + self.id
+ else
+ self.id
+ end
+ end
+
+ def reference_link_text(from_project = nil)
+ if cross_project_reference?(from_project)
+ project.to_reference + self.class.reference_prefix + self.short_id
else
- id
+ self.short_id
end
end
diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb
index 86fc9eb01a3..14e7971fa06 100644
--- a/app/models/commit_range.rb
+++ b/app/models/commit_range.rb
@@ -2,36 +2,38 @@
#
# Examples:
#
-# range = CommitRange.new('f3f85602...e86e1013')
+# range = CommitRange.new('f3f85602...e86e1013', project)
# range.exclude_start? # => false
# range.reference_title # => "Commits f3f85602 through e86e1013"
# range.to_s # => "f3f85602...e86e1013"
#
-# range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae')
+# range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae', project)
# range.exclude_start? # => true
# range.reference_title # => "Commits f3f85602^ through e86e1013"
# range.to_param # => {from: "f3f856029bc5f966c5a7ee24cf7efefdd20e6019^", to: "e86e1013709735be5bb767e2b228930c543f25ae"}
# range.to_s # => "f3f85602..e86e1013"
#
-# # Assuming `project` is a Project with a repository containing both commits:
-# range.project = project
+# # Assuming the specified project has a repository containing both commits:
# range.valid_commits? # => true
#
class CommitRange
include ActiveModel::Conversion
include Referable
- attr_reader :sha_from, :notation, :sha_to
+ attr_reader :commit_from, :notation, :commit_to
+ attr_reader :ref_from, :ref_to
# Optional Project model
attr_accessor :project
- # See `exclude_start?`
- attr_reader :exclude_start
-
- # The beginning and ending SHAs can be between 6 and 40 hex characters, and
+ # The beginning and ending refs can be named or SHAs, and
# the range notation can be double- or triple-dot.
- PATTERN = /\h{6,40}\.{2,3}\h{6,40}/
+ REF_PATTERN = /[0-9a-zA-Z][0-9a-zA-Z_.-]*[0-9a-zA-Z\^]/
+ PATTERN = /#{REF_PATTERN}\.{2,3}#{REF_PATTERN}/
+
+ # In text references, the beginning and ending refs can only be SHAs
+ # between 6 and 40 hex characters.
+ STRICT_PATTERN = /\h{6,40}\.{2,3}\h{6,40}/
def self.reference_prefix
'@'
@@ -43,27 +45,40 @@ class CommitRange
def self.reference_pattern
%r{
(?:#{Project.reference_pattern}#{reference_prefix})?
- (?<commit_range>#{PATTERN})
+ (?<commit_range>#{STRICT_PATTERN})
}x
end
+ def self.link_reference_pattern
+ super("compare", /(?<commit_range>#{PATTERN})/)
+ end
+
# Initialize a CommitRange
#
# range_string - The String commit range.
# project - An optional Project model.
#
# Raises ArgumentError if `range_string` does not match `PATTERN`.
- def initialize(range_string, project = nil)
+ def initialize(range_string, project)
+ @project = project
+
range_string.strip!
- unless range_string.match(/\A#{PATTERN}\z/)
+ unless range_string =~ /\A#{PATTERN}\z/
raise ArgumentError, "invalid CommitRange string format: #{range_string}"
end
- @exclude_start = !range_string.include?('...')
- @sha_from, @notation, @sha_to = range_string.split(/(\.{2,3})/, 2)
+ @ref_from, @notation, @ref_to = range_string.split(/(\.{2,3})/, 2)
- @project = project
+ if project.valid_repo?
+ @commit_from = project.commit(@ref_from)
+ @commit_to = project.commit(@ref_to)
+ end
+
+ if valid_commits?
+ @ref_from = Commit.truncate_sha(sha_from) if sha_from.start_with?(@ref_from)
+ @ref_to = Commit.truncate_sha(sha_to) if sha_to.start_with?(@ref_to)
+ end
end
def inspect
@@ -71,15 +86,24 @@ class CommitRange
end
def to_s
- "#{sha_from[0..7]}#{notation}#{sha_to[0..7]}"
+ sha_from + notation + sha_to
end
+ alias_method :id, :to_s
+
def to_reference(from_project = nil)
- # Not using to_s because we want the full SHAs
- reference = sha_from + notation + sha_to
+ if cross_project_reference?(from_project)
+ project.to_reference + self.class.reference_prefix + self.id
+ else
+ self.id
+ end
+ end
+
+ def reference_link_text(from_project = nil)
+ reference = ref_from + notation + ref_to
if cross_project_reference?(from_project)
- reference = project.to_reference + '@' + reference
+ reference = project.to_reference + self.class.reference_prefix + reference
end
reference
@@ -87,46 +111,58 @@ class CommitRange
# Returns a String for use in a link's title attribute
def reference_title
- "Commits #{suffixed_sha_from} through #{sha_to}"
+ "Commits #{sha_start} through #{sha_to}"
end
# Return a Hash of parameters for passing to a URL helper
#
# See `namespace_project_compare_url`
def to_param
- { from: suffixed_sha_from, to: sha_to }
+ { from: sha_start, to: sha_to }
end
def exclude_start?
- exclude_start
+ @notation == '..'
end
# Check if both the starting and ending commit IDs exist in a project's
# repository
- #
- # project - An optional Project to check (default: `project`)
- def valid_commits?(project = project)
- return nil unless project.present?
- return false unless project.valid_repo?
-
- commit_from.present? && commit_to.present?
+ def valid_commits?
+ commit_start.present? && commit_end.present?
end
def persisted?
true
end
- def commit_from
- @commit_from ||= project.repository.commit(suffixed_sha_from)
+ def sha_from
+ return nil unless @commit_from
+
+ @commit_from.id
+ end
+
+ def sha_to
+ return nil unless @commit_to
+
+ @commit_to.id
end
- def commit_to
- @commit_to ||= project.repository.commit(sha_to)
+ def sha_start
+ return nil unless sha_from
+
+ exclude_start? ? sha_from + '^' : sha_from
end
- private
+ def commit_start
+ return nil unless sha_start
- def suffixed_sha_from
- sha_from + (exclude_start? ? '^' : '')
+ if exclude_start?
+ @commit_start ||= project.commit(sha_start)
+ else
+ commit_from
+ end
end
+
+ alias_method :sha_end, :sha_to
+ alias_method :commit_end, :commit_to
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 492a026add9..badeadfa418 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -8,6 +8,7 @@ module Issuable
extend ActiveSupport::Concern
include Participable
include Mentionable
+ include StripAttribute
included do
belongs_to :author, class_name: "User"
@@ -35,6 +36,9 @@ module Issuable
scope :order_milestone_due_desc, -> { joins(:milestone).reorder('milestones.due_date DESC, milestones.id DESC') }
scope :order_milestone_due_asc, -> { joins(:milestone).reorder('milestones.due_date ASC, milestones.id ASC') }
+ scope :join_project, -> { joins(:project) }
+ scope :references_project, -> { references(:project) }
+
delegate :name,
:email,
to: :author,
@@ -48,6 +52,7 @@ module Issuable
attr_mentionable :title, :description
participant :author, :assignee, :notes_with_associations
+ strip_attributes :title
end
module ClassMethods
@@ -89,39 +94,14 @@ module Issuable
opened? || reopened?
end
- #
- # Votes
- #
-
- # Return the number of -1 comments (downvotes)
+ # Deprecated. Still exists to preserve API compatibility.
def downvotes
- filter_superceded_votes(notes.select(&:downvote?), notes).size
+ 0
end
- def downvotes_in_percent
- if votes_count.zero?
- 0
- else
- 100.0 - upvotes_in_percent
- end
- end
-
- # Return the number of +1 comments (upvotes)
+ # Deprecated. Still exists to preserve API compatibility.
def upvotes
- filter_superceded_votes(notes.select(&:upvote?), notes).size
- end
-
- def upvotes_in_percent
- if votes_count.zero?
- 0
- else
- 100.0 / votes_count * upvotes
- end
- end
-
- # Return the total number of votes
- def votes_count
- upvotes + downvotes
+ 0
end
def subscribed?(user)
@@ -184,17 +164,8 @@ module Issuable
notes.includes(:author, :project)
end
- private
-
- def filter_superceded_votes(votes, notes)
- filteredvotes = [] + votes
-
- votes.each do |vote|
- if vote.superceded?(notes)
- filteredvotes.delete(vote)
- end
- end
-
- filteredvotes
+ def updated_tasks
+ Taskable.get_updated_tasks(old_content: previous_changes['description'].first,
+ new_content: description)
end
end
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index 193c91f1742..634a8d0f274 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -62,13 +62,18 @@ module Mentionable
return [] if text.blank?
refs = all_references(current_user, text, load_lazy_references: load_lazy_references)
- (refs.issues + refs.merge_requests + refs.commits) - [local_reference]
+ refs = (refs.issues + refs.merge_requests + refs.commits)
+
+ # We're using this method instead of Array diffing because that requires
+ # both of the object's `hash` values to be the same, which may not be the
+ # case for otherwise identical Commit objects.
+ refs.reject { |ref| ref == local_reference }
end
# Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
def create_cross_references!(author = self.author, without = [], text = self.mentionable_text)
refs = referenced_mentionables(author, text)
-
+
# We're using this method instead of Array diffing because that requires
# both of the object's `hash` values to be the same, which may not be the
# case for otherwise identical Commit objects.
@@ -111,7 +116,7 @@ module Mentionable
# Only include changed fields that are mentionable
source.select { |key, val| mentionable.include?(key) }
end
-
+
# Determine whether or not a cross-reference Note has already been created between this Mentionable and
# the specified target.
def cross_reference_exists?(target)
diff --git a/app/models/concerns/referable.rb b/app/models/concerns/referable.rb
index cced66cc1e4..ce064f675ae 100644
--- a/app/models/concerns/referable.rb
+++ b/app/models/concerns/referable.rb
@@ -21,6 +21,10 @@ module Referable
''
end
+ def reference_link_text(from_project = nil)
+ to_reference(from_project)
+ end
+
module ClassMethods
# The character that prefixes the actual reference identifier
#
@@ -44,6 +48,25 @@ module Referable
def reference_pattern
raise NotImplementedError, "#{self} does not implement #{__method__}"
end
+
+ def link_reference_pattern(route, pattern)
+ %r{
+ (?<url>
+ #{Regexp.escape(Gitlab.config.gitlab.url)}
+ \/#{Project.reference_pattern}
+ \/#{Regexp.escape(route)}
+ \/#{pattern}
+ (?<path>
+ (\/[a-z0-9_=-]+)*
+ )?
+ (?<query>
+ \?[a-z0-9_=-]+
+ (&[a-z0-9_=-]+)*
+ )?
+ (?<anchor>\#[a-z0-9_-]+)?
+ )
+ }x
+ end
end
private
diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb
index 913c747a1c3..7391a77383c 100644
--- a/app/models/concerns/sortable.rb
+++ b/app/models/concerns/sortable.rb
@@ -8,8 +8,9 @@ module Sortable
included do
# By default all models should be ordered
# by created_at field starting from newest
- default_scope { order(id: :desc) }
+ default_scope { order_id_desc }
+ scope :order_id_desc, -> { reorder(id: :desc) }
scope :order_created_desc, -> { reorder(created_at: :desc) }
scope :order_created_asc, -> { reorder(created_at: :asc) }
scope :order_updated_desc, -> { reorder(updated_at: :desc) }
diff --git a/app/models/concerns/strip_attribute.rb b/app/models/concerns/strip_attribute.rb
new file mode 100644
index 00000000000..8806ebe897a
--- /dev/null
+++ b/app/models/concerns/strip_attribute.rb
@@ -0,0 +1,34 @@
+# == Strip Attribute module
+#
+# Contains functionality to clean attributes before validation
+#
+# Usage:
+#
+# class Milestone < ActiveRecord::Base
+# strip_attributes :title
+# end
+#
+#
+module StripAttribute
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def strip_attributes(*attrs)
+ strip_attrs.concat(attrs)
+ end
+
+ def strip_attrs
+ @strip_attrs ||= []
+ end
+ end
+
+ included do
+ before_validation :strip_attributes
+ end
+
+ def strip_attributes
+ self.class.strip_attrs.each do |attr|
+ self[attr].strip! if self[attr] && self[attr].respond_to?(:strip!)
+ end
+ end
+end
diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb
index 660e58b876d..df2a9e3e84b 100644
--- a/app/models/concerns/taskable.rb
+++ b/app/models/concerns/taskable.rb
@@ -7,14 +7,39 @@ require 'task_list/filter'
#
# Used by MergeRequest and Issue
module Taskable
+ COMPLETED = 'completed'.freeze
+ INCOMPLETE = 'incomplete'.freeze
+ ITEM_PATTERN = /
+ ^
+ (?:\s*[-+*]|(?:\d+\.))? # optional list prefix
+ \s* # optional whitespace prefix
+ (\[\s\]|\[[xX]\]) # checkbox
+ (\s.+) # followed by whitespace and some text.
+ /x
+
+ def self.get_tasks(content)
+ content.to_s.scan(ITEM_PATTERN).map do |checkbox, label|
+ # ITEM_PATTERN strips out the hyphen, but Item requires it. Rabble rabble.
+ TaskList::Item.new("- #{checkbox}", label.strip)
+ end
+ end
+
+ def self.get_updated_tasks(old_content:, new_content:)
+ old_tasks, new_tasks = get_tasks(old_content), get_tasks(new_content)
+
+ new_tasks.select.with_index do |new_task, i|
+ old_task = old_tasks[i]
+ next unless old_task
+
+ new_task.source == old_task.source && new_task.complete? != old_task.complete?
+ end
+ end
+
# Called by `TaskList::Summary`
def task_list_items
return [] if description.blank?
- @task_list_items ||= description.scan(TaskList::Filter::ItemPattern).collect do |item|
- # ItemPattern strips out the hyphen, but Item requires it. Rabble rabble.
- TaskList::Item.new("- #{item}")
- end
+ @task_list_items ||= Taskable.get_tasks(description)
end
def tasks
diff --git a/app/models/event.rb b/app/models/event.rb
index bf64ac29d32..01d008035a5 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -63,6 +63,16 @@ class Event < ActiveRecord::Base
Event::PUSHED, ["MergeRequest", "Issue"],
[Event::CREATED, Event::CLOSED, Event::MERGED])
end
+
+ def latest_update_time
+ row = select(:updated_at, :project_id).reorder(id: :desc).take
+
+ row ? row.updated_at : nil
+ end
+
+ def limit_recent(limit = 20, offset = nil)
+ recent.limit(limit).offset(offset)
+ end
end
def proper?
@@ -191,7 +201,7 @@ class Event < ActiveRecord::Base
elsif commented?
"commented on"
elsif created_project?
- if project.import?
+ if project.external_import?
"imported"
else
"created"
diff --git a/app/models/group.rb b/app/models/group.rb
index 2c9e75496b9..1b5b875a19e 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -49,6 +49,14 @@ class Group < Namespace
def reference_pattern
User.reference_pattern
end
+
+ def public_and_given_groups(ids)
+ where('public IS TRUE OR namespaces.id IN (?)', ids)
+ end
+
+ def visible_to_user(user)
+ where(id: user.authorized_groups.select(:id).reorder(nil))
+ end
end
def to_reference(_from_project = nil)
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 72183108033..187b6482b6c 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -69,6 +69,10 @@ class Issue < ActiveRecord::Base
}x
end
+ def self.link_reference_pattern
+ super("issues", /(?<issue>\d+)/)
+ end
+
def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{iid}"
diff --git a/app/models/label.rb b/app/models/label.rb
index b306aecbac1..bef6063fe88 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -17,7 +17,7 @@ class Label < ActiveRecord::Base
# Requests that have no label assigned.
LabelStruct = Struct.new(:title, :name)
None = LabelStruct.new('No Label', 'No Label')
- Any = LabelStruct.new('Any', '')
+ Any = LabelStruct.new('Any Label', '')
DEFAULT_COLOR = '#428BCA'
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 1f81e23c7a3..4bcbd86a543 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -141,6 +141,9 @@ class MergeRequest < ActiveRecord::Base
scope :closed, -> { with_state(:closed) }
scope :closed_and_merged, -> { with_states(:closed, :merged) }
+ scope :join_project, -> { joins(:target_project) }
+ scope :references_project, -> { references(:target_project) }
+
def self.reference_prefix
'!'
end
@@ -155,6 +158,10 @@ class MergeRequest < ActiveRecord::Base
}x
end
+ def self.link_reference_pattern
+ super("merge_requests", /(?<merge_request>\d+)/)
+ end
+
def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{iid}"
@@ -330,7 +337,7 @@ class MergeRequest < ActiveRecord::Base
issues = commits.flat_map { |c| c.closes_issues(current_user) }
issues.push(*Gitlab::ClosingIssueExtractor.new(project, current_user).
closed_by_message(description))
- issues.uniq.sort_by(&:id)
+ issues.uniq
else
[]
end
@@ -500,6 +507,6 @@ class MergeRequest < ActiveRecord::Base
end
def ci_commit
- @ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit
+ @ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project
end
end
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 2ff16e2825c..d8c7536cd31 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -16,12 +16,13 @@
class Milestone < ActiveRecord::Base
# Represents a "No Milestone" state used for filtering Issues and Merge
# Requests that have no milestone assigned.
- MilestoneStruct = Struct.new(:title, :name)
- None = MilestoneStruct.new('No Milestone', 'No Milestone')
- Any = MilestoneStruct.new('Any', '')
+ MilestoneStruct = Struct.new(:title, :name, :id)
+ None = MilestoneStruct.new('No Milestone', 'No Milestone', 0)
+ Any = MilestoneStruct.new('Any Milestone', '', -1)
include InternalId
include Sortable
+ include StripAttribute
belongs_to :project
has_many :issues
@@ -35,6 +36,8 @@ class Milestone < ActiveRecord::Base
validates :title, presence: true
validates :project, presence: true
+ strip_attributes :title
+
state_machine :state, initial: :active do
event :close do
transition active: :closed
diff --git a/app/models/note.rb b/app/models/note.rb
index 0b3aa30abd7..1c6345e735c 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -40,16 +40,20 @@ class Note < ActiveRecord::Base
delegate :name, :email, to: :author, prefix: true
validates :note, :project, presence: true
+ validates :note, uniqueness: { scope: [:author, :noteable_type, :noteable_id] }, if: ->(n) { n.is_award }
validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true
# Attachments are deprecated and are handled by Markdown uploader
validates :attachment, file_size: { maximum: :max_attachment_size }
validates :noteable_id, presence: true, if: ->(n) { n.noteable_type.present? && n.noteable_type != 'Commit' }
validates :commit_id, presence: true, if: ->(n) { n.noteable_type == 'Commit' }
+ validates :author, presence: true
mount_uploader :attachment, AttachmentUploader
# Scopes
+ scope :awards, ->{ where(is_award: true) }
+ scope :nonawards, ->{ where(is_award: false) }
scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) }
scope :inline, ->{ where("line_code IS NOT NULL") }
scope :not_inline, ->{ where(line_code: [nil, '']) }
@@ -97,6 +101,12 @@ class Note < ActiveRecord::Base
def search(query)
where("LOWER(note) like :query", query: "%#{query.downcase}%")
end
+
+ def grouped_awards
+ awards.select(:note).distinct.map do |note|
+ [ note.note, where(note: note.note) ]
+ end
+ end
end
def cross_reference?
@@ -288,44 +298,6 @@ class Note < ActiveRecord::Base
nil
end
- DOWNVOTES = %w(-1 :-1: :thumbsdown: :thumbs_down_sign:)
-
- # Check if the note is a downvote
- def downvote?
- votable? && note.start_with?(*DOWNVOTES)
- end
-
- UPVOTES = %w(+1 :+1: :thumbsup: :thumbs_up_sign:)
-
- # Check if the note is an upvote
- def upvote?
- votable? && note.start_with?(*UPVOTES)
- end
-
- def superceded?(notes)
- return false unless vote?
-
- notes.each do |note|
- next if note == self
-
- if note.vote? &&
- self[:author_id] == note[:author_id] &&
- self[:created_at] <= note[:created_at]
- return true
- end
- end
-
- false
- end
-
- def vote?
- upvote? || downvote?
- end
-
- def votable?
- for_issue? || (for_merge_request? && !for_diff_line?)
- end
-
# Mentionable override.
def gfm_reference(from_project = nil)
noteable.gfm_reference(from_project)
@@ -363,6 +335,16 @@ class Note < ActiveRecord::Base
read_attribute(:system)
end
+ # Deprecated. Still exists to preserve API compatibility.
+ def downvote?
+ false
+ end
+
+ # Deprecated. Still exists to preserve API compatibility.
+ def upvote?
+ false
+ end
+
def editable?
!system?
end
diff --git a/app/models/project.rb b/app/models/project.rb
index a099a67cf63..6010770a5f2 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -42,9 +42,8 @@ class Project < ActiveRecord::Base
include Sortable
include AfterCommitQueue
include CaseSensitivity
-
+
extend Gitlab::ConfigHelper
- extend Enumerize
UNKNOWN_IMPORT_URL = 'http://unknown.git'
@@ -286,6 +285,10 @@ class Project < ActiveRecord::Base
joins(join_body).reorder('join_note_counts.amount DESC')
end
+
+ def visible_to_user(user)
+ where(id: user.authorized_projects.select(:id).reorder(nil))
+ end
end
def team
@@ -310,15 +313,17 @@ class Project < ActiveRecord::Base
def add_import_job
if forked?
- unless RepositoryForkWorker.perform_async(id, forked_from_project.path_with_namespace, self.namespace.path)
- import_fail
- end
+ RepositoryForkWorker.perform_async(self.id, forked_from_project.path_with_namespace, self.namespace.path)
else
- RepositoryImportWorker.perform_async(id)
+ RepositoryImportWorker.perform_async(self.id)
end
end
def clear_import_data
+ update(import_error: nil)
+
+ ProjectCacheWorker.perform_async(self.id)
+
self.import_data.destroy if self.import_data
end
@@ -346,6 +351,14 @@ class Project < ActiveRecord::Base
import_status == 'finished'
end
+ def safe_import_url
+ result = URI.parse(self.import_url)
+ result.password = '*****' unless result.password.nil?
+ result.to_s
+ rescue
+ original_url
+ end
+
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")
diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb
index 40058b53df5..199ee3a9d0d 100644
--- a/app/models/project_services/buildkite_service.rb
+++ b/app/models/project_services/buildkite_service.rb
@@ -37,7 +37,7 @@ class BuildkiteService < CiService
def compose_service_hook
hook = service_hook || build_service_hook
hook.url = webhook_url
- hook.enable_ssl_verification = enable_ssl_verification
+ hook.enable_ssl_verification = !!enable_ssl_verification
hook.save
end
diff --git a/app/models/project_services/ci/mail_service.rb b/app/models/project_services/ci/mail_service.rb
index d31dd6899c1..bb961d06972 100644
--- a/app/models/project_services/ci/mail_service.rb
+++ b/app/models/project_services/ci/mail_service.rb
@@ -64,9 +64,9 @@ module Ci
build.project_recipients.each do |recipient|
case build.status.to_sym
when :success
- mailer.build_success_email(build.id, recipient)
+ mailer.build_success_email(build.id, recipient).deliver_later
when :failed
- mailer.build_fail_email(build.id, recipient)
+ mailer.build_fail_email(build.id, recipient).deliver_later
end
end
end
@@ -78,7 +78,7 @@ module Ci
end
def mailer
- Ci::Notify.delay
+ Ci::Notify
end
end
end
diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb
index c240213200d..06c3922593c 100644
--- a/app/models/project_services/drone_ci_service.rb
+++ b/app/models/project_services/drone_ci_service.rb
@@ -32,7 +32,9 @@ class DroneCiService < CiService
def compose_service_hook
hook = service_hook || build_service_hook
- hook.enable_ssl_verification = enable_ssl_verification
+ # If using a service template, project may not be available
+ hook.url = [drone_url, "/api/hook", "?owner=#{project.namespace.path}", "&name=#{project.path}", "&access_token=#{token}"].join if project
+ hook.enable_ssl_verification = !!enable_ssl_verification
hook.save
end
diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb
index 095d04e0df4..234e8e8b580 100644
--- a/app/models/project_services/gitlab_ci_service.rb
+++ b/app/models/project_services/gitlab_ci_service.rb
@@ -30,6 +30,7 @@ class GitlabCiService < CiService
end
def ensure_gitlab_ci_project
+ return unless project
project.ensure_gitlab_ci_project
end
@@ -54,7 +55,7 @@ class GitlabCiService < CiService
end
def get_ci_commit(sha, ref)
- Ci::Project.find(project.gitlab_ci_project).commits.find_by_sha!(sha)
+ Ci::Project.find(project.gitlab_ci_project.id).commits.find_by_sha!(sha)
end
def commit_status(sha, ref)
diff --git a/app/models/repository.rb b/app/models/repository.rb
index f76b770e867..d247b0f5012 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -6,7 +6,7 @@ class Repository
include Gitlab::ShellAdapter
- attr_accessor :raw_repository, :path_with_namespace, :project
+ attr_accessor :path_with_namespace, :project
def self.clean_old_archives
repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path
@@ -19,14 +19,18 @@ class Repository
def initialize(path_with_namespace, default_branch = nil, project = nil)
@path_with_namespace = path_with_namespace
@project = project
+ end
- if path_with_namespace
- @raw_repository = Gitlab::Git::Repository.new(path_to_repo)
- @raw_repository.autocrlf = :input
- end
+ def raw_repository
+ return nil unless path_with_namespace
- rescue Gitlab::Git::Repository::NoRepository
- nil
+ @raw_repository ||= begin
+ repo = Gitlab::Git::Repository.new(path_to_repo)
+ repo.autocrlf = :input
+ repo
+ rescue Gitlab::Git::Repository::NoRepository
+ nil
+ end
end
# Return absolute path to repository
@@ -105,29 +109,25 @@ class Repository
end
def add_branch(branch_name, ref)
- cache.expire(:branch_names)
- @branches = nil
+ expire_branches_cache
gitlab_shell.add_branch(path_with_namespace, branch_name, ref)
end
def add_tag(tag_name, ref, message = nil)
- cache.expire(:tag_names)
- @tags = nil
+ expire_tags_cache
gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message)
end
def rm_branch(branch_name)
- cache.expire(:branch_names)
- @branches = nil
+ expire_branches_cache
gitlab_shell.rm_branch(path_with_namespace, branch_name)
end
def rm_tag(tag_name)
- cache.expire(:tag_names)
- @tags = nil
+ expire_tags_cache
gitlab_shell.rm_tag(path_with_namespace, tag_name)
end
@@ -169,6 +169,16 @@ class Repository
end
end
+ def expire_tags_cache
+ cache.expire(:tag_names)
+ @tags = nil
+ end
+
+ def expire_branches_cache
+ cache.expire(:branch_names)
+ @branches = nil
+ end
+
def expire_cache
cache_keys.each do |key|
cache.expire(key)
@@ -484,7 +494,7 @@ class Repository
root_ref_commit = commit(root_ref)
if branch_commit
- rugged.merge_base(root_ref_commit.id, branch_commit.id) == branch_commit.id
+ is_ancestor?(branch_commit.id, root_ref_commit.id)
else
nil
end
@@ -494,6 +504,11 @@ class Repository
rugged.merge_base(first_commit_id, second_commit_id)
end
+ def is_ancestor?(ancestor_id, descendant_id)
+ merge_base(ancestor_id, descendant_id) == ancestor_id
+ end
+
+
def search_files(query, ref)
offset = 2
args = %W(#{Gitlab.config.git.bin_path} grep -i -n --before-context #{offset} --after-context #{offset} -e #{query} #{ref || root_ref})
@@ -556,9 +571,13 @@ class Repository
# Run GitLab pre-receive hook
pre_receive_hook = Gitlab::Git::Hook.new('pre-receive', path_to_repo)
- status = pre_receive_hook.trigger(gl_id, oldrev, newrev, ref)
+ pre_receive_hook_status = pre_receive_hook.trigger(gl_id, oldrev, newrev, ref)
+
+ # Run GitLab update hook
+ update_hook = Gitlab::Git::Hook.new('update', path_to_repo)
+ update_hook_status = update_hook.trigger(gl_id, oldrev, newrev, ref)
- if status
+ if pre_receive_hook_status && update_hook_status
if was_empty
# Create branch
rugged.references.create(ref, newrev)
@@ -581,7 +600,7 @@ class Repository
# Remove tmp ref and return error to user
rugged.references.delete(tmp_ref)
- raise PreReceiveError.new('Commit was rejected by pre-receive hook')
+ raise PreReceiveError.new('Commit was rejected by git hook')
end
end
diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb
index 3eed5c16e45..d8fe65b06f6 100644
--- a/app/models/sent_notification.rb
+++ b/app/models/sent_notification.rb
@@ -17,9 +17,8 @@ class SentNotification < ActiveRecord::Base
belongs_to :noteable, polymorphic: true
belongs_to :recipient, class_name: "User"
- validate :project, :recipient, :reply_key, presence: true
- validate :reply_key, uniqueness: true
-
+ validates :project, :recipient, :reply_key, presence: true
+ validates :reply_key, uniqueness: true
validates :noteable_id, presence: true, unless: :for_commit?
validates :commit_id, presence: true, if: :for_commit?
validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index b0831982aa7..f876be7a4c8 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -65,6 +65,10 @@ class Snippet < ActiveRecord::Base
}x
end
+ def self.link_reference_pattern
+ super("snippets", /(?<snippet>\d+)/)
+ end
+
def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{id}"
diff --git a/app/models/user.rb b/app/models/user.rb
index 61abea1f6ea..719b49b16fe 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -389,42 +389,23 @@ class User < ActiveRecord::Base
end
end
- # Groups user has access to
+ # Returns the groups a user has access to
def authorized_groups
- @authorized_groups ||= begin
- group_ids = (groups.pluck(:id) + authorized_projects.pluck(:namespace_id))
- Group.where(id: group_ids)
- end
- end
+ union = Gitlab::SQL::Union.
+ new([groups.select(:id), authorized_projects.select(:namespace_id)])
- def authorized_projects_id
- @authorized_projects_id ||= begin
- project_ids = personal_projects.pluck(:id)
- project_ids.push(*groups_projects.pluck(:id))
- project_ids.push(*projects.pluck(:id).uniq)
- end
- end
-
- def master_or_owner_projects_id
- @master_or_owner_projects_id ||= begin
- scope = { access_level: [ Gitlab::Access::MASTER, Gitlab::Access::OWNER ] }
- project_ids = personal_projects.pluck(:id)
- project_ids.push(*groups_projects.where(members: scope).pluck(:id))
- project_ids.push(*projects.where(members: scope).pluck(:id).uniq)
- end
+ Group.where("namespaces.id IN (#{union.to_sql})")
end
- # Projects user has access to
+ # Returns the groups a user is authorized to access.
def authorized_projects
- @authorized_projects ||= Project.where(id: authorized_projects_id)
+ Project.where("projects.id IN (#{projects_union.to_sql})")
end
def owned_projects
@owned_projects ||=
- begin
- namespace_ids = owned_groups.pluck(:id).push(namespace.id)
- Project.in_namespace(namespace_ids).joins(:namespace)
- end
+ Project.where('namespace_id IN (?) OR namespace_id = ?',
+ owned_groups.select(:id), namespace.id).joins(:namespace)
end
# Team membership in authorized projects
@@ -656,11 +637,11 @@ class User < ActiveRecord::Base
email.start_with?('temp-email-for-oauth')
end
- def avatar_url(size = nil)
+ def avatar_url(size = nil, scale = 2)
if avatar.present?
[gitlab_config.url, avatar.url].join
else
- GravatarService.new.execute(email, size)
+ GravatarService.new.execute(email, size, scale)
end
end
@@ -709,7 +690,7 @@ class User < ActiveRecord::Base
end
def starred?(project)
- starred_projects.exists?(project)
+ starred_projects.exists?(project.id)
end
def toggle_star(project)
@@ -739,12 +720,25 @@ class User < ActiveRecord::Base
Doorkeeper::AccessToken.where(resource_owner_id: self.id, revoked_at: nil)
end
- def contributed_projects_ids
- Event.contributions.where(author_id: self).
+ # Returns the projects a user contributed to in the last year.
+ #
+ # This method relies on a subquery as this performs significantly better
+ # compared to a JOIN when coupled with, for example,
+ # `Project.visible_to_user`. That is, consider the following code:
+ #
+ # some_user.contributed_projects.visible_to_user(other_user)
+ #
+ # If this method were to use a JOIN the resulting query would take roughly 200
+ # ms on a database with a similar size to GitLab.com's database. On the other
+ # hand, using a subquery means we can get the exact same data in about 40 ms.
+ def contributed_projects
+ events = Event.select(:project_id).
+ contributions.where(author_id: self).
where("created_at > ?", Time.now - 1.year).
- reorder(project_id: :desc).
- select(:project_id).
- uniq.map(&:project_id)
+ uniq.
+ reorder(nil)
+
+ Project.where(id: events)
end
def restricted_signup_domains
@@ -777,8 +771,32 @@ class User < ActiveRecord::Base
def ci_authorized_runners
@ci_authorized_runners ||= begin
runner_ids = Ci::RunnerProject.joins(:project).
- where(ci_projects: { gitlab_id: master_or_owner_projects_id }).select(:runner_id)
+ where("ci_projects.gitlab_id IN (#{ci_projects_union.to_sql})").
+ select(:runner_id)
+
Ci::Runner.specific.where(id: runner_ids)
end
end
+
+ private
+
+ def projects_union
+ Gitlab::SQL::Union.new([personal_projects.select(:id),
+ groups_projects.select(:id),
+ projects.select(:id)])
+ end
+
+ def ci_projects_union
+ scope = { access_level: [Gitlab::Access::MASTER, Gitlab::Access::OWNER] }
+ groups = groups_projects.where(members: scope)
+ other = projects.where(members: scope)
+
+ Gitlab::SQL::Union.new([personal_projects.select(:id), groups.select(:id),
+ other.select(:id)])
+ end
+
+ # Added according to https://github.com/plataformatec/devise/blob/7df57d5081f9884849ca15e4fde179ef164a575f/README.md#activejob-integration
+ def send_devise_notification(notification, *args)
+ devise_mailer.send(notification, self, *args).deliver_later
+ end
end
diff --git a/app/models/users_star_project.rb b/app/models/users_star_project.rb
index 3d49cb05949..413f3f485a8 100644
--- a/app/models/users_star_project.rb
+++ b/app/models/users_star_project.rb
@@ -10,7 +10,7 @@
#
class UsersStarProject < ActiveRecord::Base
- belongs_to :project, counter_cache: :star_count
+ belongs_to :project, counter_cache: :star_count, touch: true
belongs_to :user
validates :user, presence: true
diff --git a/app/services/create_release_service.rb b/app/services/create_release_service.rb
new file mode 100644
index 00000000000..e06a6f2f47a
--- /dev/null
+++ b/app/services/create_release_service.rb
@@ -0,0 +1,31 @@
+require_relative 'base_service'
+
+class CreateReleaseService < BaseService
+ def execute(tag_name, release_description)
+
+ repository = project.repository
+ existing_tag = repository.find_tag(tag_name)
+
+ # Only create a release if the tag exists
+ if existing_tag
+ release = project.releases.find_by(tag: tag_name)
+
+ if release
+ error('Release already exists', 409)
+ else
+ release = project.releases.new({ tag: tag_name, description: release_description })
+ release.save
+
+ success(release)
+ end
+ else
+ error('Tag does not exist', 404)
+ end
+ end
+
+ def success(release)
+ out = super()
+ out[:release] = release
+ out
+ end
+end
diff --git a/app/services/create_tag_service.rb b/app/services/create_tag_service.rb
index 9917119fce2..2452999382a 100644
--- a/app/services/create_tag_service.rb
+++ b/app/services/create_tag_service.rb
@@ -19,16 +19,16 @@ class CreateTagService < BaseService
new_tag = repository.find_tag(tag_name)
if new_tag
- if release_description
- release = project.releases.find_or_initialize_by(tag: tag_name)
- release.update_attributes(description: release_description)
- end
-
push_data = create_push_data(project, current_user, new_tag)
EventCreateService.new.push(project, current_user, push_data)
project.execute_hooks(push_data.dup, :tag_push_hooks)
project.execute_services(push_data.dup, :tag_push_hooks)
+ if release_description
+ CreateReleaseService.new(@project, @current_user).
+ execute(tag_name, release_description)
+ end
+
success(new_tag)
else
error('Invalid reference name')
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index ccb6b97858c..f11690aa3f4 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -58,12 +58,6 @@ class GitPushService
@push_data = build_push_data(oldrev, newrev, ref)
- # If CI was disabled but .gitlab-ci.yml file was pushed
- # we enable CI automatically
- if !project.builds_enabled? && gitlab_ci_yaml?(newrev)
- project.enable_ci
- end
-
EventCreateService.new.push(project, user, @push_data)
project.execute_hooks(@push_data.dup, :push_hooks)
project.execute_services(@push_data.dup, :push_hooks)
@@ -134,10 +128,4 @@ class GitPushService
def commit_user(commit)
commit.author || user
end
-
- def gitlab_ci_yaml?(sha)
- @project.repository.blob_at(sha, '.gitlab-ci.yml')
- rescue Rugged::ReferenceError
- nil
- end
end
diff --git a/app/services/gravatar_service.rb b/app/services/gravatar_service.rb
index 4bee0c26a68..433ecc2df32 100644
--- a/app/services/gravatar_service.rb
+++ b/app/services/gravatar_service.rb
@@ -1,13 +1,13 @@
class GravatarService
include Gitlab::CurrentSettings
- def execute(email, size = nil)
+ def execute(email, size = nil, scale = 2)
if current_application_settings.gravatar_enabled? && email.present?
size = 40 if size.nil? || size <= 0
sprintf gravatar_url,
hash: Digest::MD5.hexdigest(email.strip.downcase),
- size: size,
+ size: size * scale,
email: email.strip
end
end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 11d2b08bba7..2556f06e2d3 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -27,6 +27,12 @@ class IssuableBaseService < BaseService
old_branch, new_branch)
end
+ def create_task_status_note(issuable)
+ issuable.updated_tasks.each do |task|
+ SystemNoteService.change_task_status(issuable, issuable.project, current_user, task)
+ end
+ end
+
def filter_params(issuable_ability_name = :issue)
params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE
params[:milestone_id] = "" if params[:milestone_id] == IssuableFinder::NONE
@@ -47,14 +53,7 @@ class IssuableBaseService < BaseService
if params.present? && issuable.update_attributes(params.merge(updated_by: current_user))
issuable.reset_events_cache
-
- if issuable.labels != old_labels
- create_labels_note(
- issuable,
- issuable.labels - old_labels,
- old_labels - issuable.labels)
- end
-
+ handle_common_system_notes(issuable, old_labels: old_labels)
handle_changes(issuable)
issuable.create_new_cross_references!(current_user)
execute_hooks(issuable, 'update')
@@ -71,4 +70,19 @@ class IssuableBaseService < BaseService
close_service.new(project, current_user, {}).execute(issuable)
end
end
+
+ def handle_common_system_notes(issuable, options = {})
+ if issuable.previous_changes.include?('title')
+ create_title_change_note(issuable, issuable.previous_changes['title'].first)
+ end
+
+ if issuable.previous_changes.include?('description') && issuable.tasks?
+ create_task_status_note(issuable)
+ end
+
+ old_labels = options[:old_labels]
+ if old_labels && (issuable.labels != old_labels)
+ create_labels_note(issuable, issuable.labels - old_labels, old_labels - issuable.labels)
+ end
+ end
end
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index 7c112f731a7..a55a04dd5e0 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -13,10 +13,6 @@ module Issues
create_assignee_note(issue)
notification_service.reassigned_issue(issue, current_user)
end
-
- if issue.previous_changes.include?('title')
- create_title_change_note(issue, issue.previous_changes['title'].first)
- end
end
def reopen_service
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index db8d18a7d84..cabc3d8fabb 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -35,7 +35,7 @@ module MergeRequests
}
repository.merge(current_user, merge_request.source_sha, merge_request.target_branch, options)
- rescue Exception => e
+ rescue StandardError => e
merge_request.update(merge_error: "Something went wrong during merge")
Rails.logger.error(e.message)
return false
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index a5db3776eb6..5ff2cc03dda 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -30,10 +30,6 @@ module MergeRequests
notification_service.reassigned_merge_request(merge_request, current_user)
end
- if merge_request.previous_changes.include?('title')
- create_title_change_note(merge_request, merge_request.previous_changes['title'].first)
- end
-
if merge_request.previous_changes.include?('target_branch') ||
merge_request.previous_changes.include?('source_branch')
merge_request.mark_as_unchecked
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index 2001dc89c33..dbff58dfb9c 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -5,11 +5,16 @@ module Notes
note.author = current_user
note.system = false
+ if contains_emoji_only?(params[:note])
+ note.is_award = true
+ note.note = emoji_name(params[:note])
+ end
+
if note.save
notification_service.new_note(note)
- # Skip system notes, like status changes and cross-references.
- unless note.system
+ # Skip system notes, like status changes and cross-references and awards
+ unless note.system || note.is_award
event_service.leave_note(note, note.author)
note.create_cross_references!
execute_hooks(note)
@@ -28,5 +33,13 @@ module Notes
note.project.execute_hooks(note_data, :note_hooks)
note.project.execute_services(note_data, :note_hooks)
end
+
+ def contains_emoji_only?(note)
+ note =~ /\A:[-_+[:alnum:]]*:\s?\z/
+ end
+
+ def emoji_name(note)
+ note.match(/\A:([-_+[:alnum:]]*):\s?/)[1]
+ end
end
end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index bbfe755f44a..bdf7b3ad2bb 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -13,14 +13,14 @@ class NotificationService
# even if user disabled notifications
def new_key(key)
if key.user
- mailer.new_ssh_key_email(key.id)
+ mailer.new_ssh_key_email(key.id).deliver_later
end
end
# Always notify user about email added to profile
def new_email(email)
if email.user
- mailer.new_email_email(email.id)
+ mailer.new_email_email(email.id).deliver_later
end
end
@@ -79,17 +79,27 @@ class NotificationService
end
def merge_mr(merge_request, current_user)
- close_resource_email(merge_request, merge_request.target_project, current_user, 'merged_merge_request_email')
+ close_resource_email(
+ merge_request,
+ merge_request.target_project,
+ current_user,
+ 'merged_merge_request_email'
+ )
end
def reopen_mr(merge_request, current_user)
- reopen_resource_email(merge_request, merge_request.target_project, current_user, 'merge_request_status_email', 'reopened')
+ reopen_resource_email(
+ merge_request,
+ merge_request.target_project,
+ current_user, 'merge_request_status_email',
+ 'reopened'
+ )
end
# Notify new user with email after creation
def new_user(user, token = nil)
# Don't email omniauth created users
- mailer.new_user_email(user.id, token) unless user.identities.any?
+ mailer.new_user_email(user.id, token).deliver_later unless user.identities.any?
end
# Notify users on new note in system
@@ -102,6 +112,7 @@ class NotificationService
# ignore gitlab service messages
return true if note.note.start_with?('Status changed to closed')
return true if note.cross_reference? && note.system == true
+ return true if note.is_award
target = note.noteable
@@ -134,53 +145,63 @@ class NotificationService
recipients = reject_unsubscribed_users(recipients, note.noteable)
recipients.delete(note.author)
+ recipients = recipients.uniq
# build notify method like 'note_commit_email'
notify_method = "note_#{note.noteable_type.underscore}_email".to_sym
-
recipients.each do |recipient|
- mailer.send(notify_method, recipient.id, note.id)
+ mailer.send(notify_method, recipient.id, note.id).deliver_later
end
end
def invite_project_member(project_member, token)
- mailer.project_member_invited_email(project_member.id, token)
+ mailer.project_member_invited_email(project_member.id, token).deliver_later
end
def accept_project_invite(project_member)
- mailer.project_invite_accepted_email(project_member.id)
+ mailer.project_invite_accepted_email(project_member.id).deliver_later
end
def decline_project_invite(project_member)
- mailer.project_invite_declined_email(project_member.project.id, project_member.invite_email, project_member.access_level, project_member.created_by_id)
+ mailer.project_invite_declined_email(
+ project_member.project.id,
+ project_member.invite_email,
+ project_member.access_level,
+ project_member.created_by_id
+ ).deliver_later
end
def new_project_member(project_member)
- mailer.project_access_granted_email(project_member.id)
+ mailer.project_access_granted_email(project_member.id).deliver_later
end
def update_project_member(project_member)
- mailer.project_access_granted_email(project_member.id)
+ mailer.project_access_granted_email(project_member.id).deliver_later
end
def invite_group_member(group_member, token)
- mailer.group_member_invited_email(group_member.id, token)
+ mailer.group_member_invited_email(group_member.id, token).deliver_later
end
def accept_group_invite(group_member)
- mailer.group_invite_accepted_email(group_member.id)
+ mailer.group_invite_accepted_email(group_member.id).deliver_later
end
def decline_group_invite(group_member)
- mailer.group_invite_declined_email(group_member.group.id, group_member.invite_email, group_member.access_level, group_member.created_by_id)
+ mailer.group_invite_declined_email(
+ group_member.group.id,
+ group_member.invite_email,
+ group_member.access_level,
+ group_member.created_by_id
+ ).deliver_later
end
def new_group_member(group_member)
- mailer.group_access_granted_email(group_member.id)
+ mailer.group_access_granted_email(group_member.id).deliver_later
end
def update_group_member(group_member)
- mailer.group_access_granted_email(group_member.id)
+ mailer.group_access_granted_email(group_member.id).deliver_later
end
def project_was_moved(project, old_path_with_namespace)
@@ -188,7 +209,11 @@ class NotificationService
recipients = reject_muted_users(recipients, project)
recipients.each do |recipient|
- mailer.project_was_moved_email(project.id, recipient.id, old_path_with_namespace)
+ mailer.project_was_moved_email(
+ project.id,
+ recipient.id,
+ old_path_with_namespace
+ ).deliver_later
end
end
@@ -338,7 +363,7 @@ class NotificationService
recipients = build_recipients(target, project, target.author)
recipients.each do |recipient|
- mailer.send(method, recipient.id, target.id)
+ mailer.send(method, recipient.id, target.id).deliver_later
end
end
@@ -346,7 +371,7 @@ class NotificationService
recipients = build_recipients(target, project, current_user)
recipients.each do |recipient|
- mailer.send(method, recipient.id, target.id, current_user.id)
+ mailer.send(method, recipient.id, target.id, current_user.id).deliver_later
end
end
@@ -357,7 +382,13 @@ class NotificationService
recipients = build_recipients(target, project, current_user, [previous_assignee])
recipients.each do |recipient|
- mailer.send(method, recipient.id, target.id, previous_assignee_id, current_user.id)
+ mailer.send(
+ method,
+ recipient.id,
+ target.id,
+ previous_assignee_id,
+ current_user.id
+ ).deliver_later
end
end
@@ -365,7 +396,7 @@ class NotificationService
recipients = build_recipients(target, project, current_user)
recipients.each do |recipient|
- mailer.send(method, recipient.id, target.id, status, current_user.id)
+ mailer.send(method, recipient.id, target.id, status, current_user.id).deliver_later
end
end
@@ -387,7 +418,7 @@ class NotificationService
end
def mailer
- Notify.delay
+ Notify
end
def previous_record(object, attribute)
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 5b84527eccf..700a1db04d8 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -55,7 +55,9 @@ module Projects
@project.save
if @project.persisted? && !@project.import?
- raise 'Failed to create repository' unless @project.create_repository
+ unless @project.create_repository
+ raise 'Failed to create repository'
+ end
end
end
@@ -94,9 +96,7 @@ module Projects
@project.team << [current_user, :master, current_user]
end
- if @project.import?
- @project.import_start
- end
+ @project.import_start if @project.import?
end
end
end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 6d15a49145d..7cb9417b13e 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -125,7 +125,7 @@ class SystemNoteService
# Returns the created Note object
def self.change_status(noteable, project, author, status, source)
body = "Status changed to #{status}"
- body += " by #{source.gfm_reference}" if source
+ body += " by #{source.gfm_reference(project)}" if source
create_note(noteable: noteable, project: project, author: author, note: body)
end
@@ -355,4 +355,22 @@ class SystemNoteService
"* #{commit_ids} - #{commits_text} from branch `#{branch}`\n"
end
+
+ # Called when the status of a Task has changed
+ #
+ # noteable - Noteable object.
+ # project - Project owning noteable
+ # author - User performing the change
+ # new_task - TaskList::Item object.
+ #
+ # Example Note text:
+ #
+ # "Soandso marked the task Whatever as completed."
+ #
+ # Returns the created Note object
+ def self.change_task_status(noteable, project, author, new_task)
+ status_label = new_task.complete? ? Taskable::COMPLETED : Taskable::INCOMPLETE
+ body = "Marked the task **#{new_task.source}** as #{status_label}"
+ create_note(noteable: noteable, project: project, author: author, note: body)
+ end
end
diff --git a/app/services/update_release_service.rb b/app/services/update_release_service.rb
new file mode 100644
index 00000000000..25eb13ef09a
--- /dev/null
+++ b/app/services/update_release_service.rb
@@ -0,0 +1,29 @@
+require_relative 'base_service'
+
+class UpdateReleaseService < BaseService
+ def execute(tag_name, release_description)
+
+ repository = project.repository
+ existing_tag = repository.find_tag(tag_name)
+
+ if existing_tag
+ release = project.releases.find_by(tag: tag_name)
+
+ if release
+ release.update_attributes(description: release_description)
+
+ success(release)
+ else
+ error('Release does not exist', 404)
+ end
+ else
+ error('Tag does not exist', 404)
+ end
+ end
+
+ def success(release)
+ out = super()
+ out[:release] = release
+ out
+ end
+end
diff --git a/app/uploaders/artifact_uploader.rb b/app/uploaders/artifact_uploader.rb
index b4e0fc5772d..1dccc39e7e2 100644
--- a/app/uploaders/artifact_uploader.rb
+++ b/app/uploaders/artifact_uploader.rb
@@ -5,15 +5,15 @@ class ArtifactUploader < CarrierWave::Uploader::Base
attr_accessor :build, :field
def self.artifacts_path
- File.expand_path('shared/artifacts/', Rails.root)
+ Gitlab.config.artifacts.path
end
def self.artifacts_upload_path
- File.expand_path('shared/artifacts/tmp/uploads/', Rails.root)
+ File.join(self.artifacts_path, 'tmp/uploads/')
end
def self.artifacts_cache_path
- File.expand_path('shared/artifacts/tmp/cache/', Rails.root)
+ File.join(self.artifacts_path, 'tmp/cache/')
end
def initialize(build, field)
diff --git a/app/views/admin/labels/_form.html.haml b/app/views/admin/labels/_form.html.haml
index a5ace4e7a3b..eaa94ed9e36 100644
--- a/app/views/admin/labels/_form.html.haml
+++ b/app/views/admin/labels/_form.html.haml
@@ -12,7 +12,7 @@
.col-sm-10
= f.text_field :title, class: "form-control", required: true
.form-group
- = f.label :color, "Background Color", class: 'control-label'
+ = f.label :color, "Background color", class: 'control-label'
.col-sm-10
.input-group
.input-group-addon.label-color-preview &nbsp;
diff --git a/app/views/admin/labels/_label.html.haml b/app/views/admin/labels/_label.html.haml
index 596e06243dd..e3ccbf6c3a8 100644
--- a/app/views/admin/labels/_label.html.haml
+++ b/app/views/admin/labels/_label.html.haml
@@ -2,4 +2,4 @@
= render_colored_label(label)
.pull-right
= link_to 'Edit', edit_admin_label_path(label), class: 'btn btn-sm'
- = link_to 'Remove', admin_label_path(label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"}
+ = link_to 'Delete', admin_label_path(label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Delete this label? Are you sure?"}
diff --git a/app/views/admin/labels/edit.html.haml b/app/views/admin/labels/edit.html.haml
index 45c62a76259..309aedceded 100644
--- a/app/views/admin/labels/edit.html.haml
+++ b/app/views/admin/labels/edit.html.haml
@@ -1,9 +1,5 @@
- page_title "Edit", @label.name, "Labels"
-%h3
- Edit label
- %span.light #{@label.name}
-.back-link
- = link_to admin_labels_path do
- &larr; To labels list
+%h3.page-title
+ Edit Label
%hr
= render 'form'
diff --git a/app/views/admin/labels/new.html.haml b/app/views/admin/labels/new.html.haml
index 8d298ad20f7..0135ad0723d 100644
--- a/app/views/admin/labels/new.html.haml
+++ b/app/views/admin/labels/new.html.haml
@@ -1,7 +1,5 @@
- page_title "New Label"
-%h3 New label
-.back-link
- = link_to admin_labels_path do
- &larr; To labels list
+%h3.page-title
+ New Label
%hr
= render 'form'
diff --git a/app/views/admin/users/_head.html.haml b/app/views/admin/users/_head.html.haml
index 8d1cab4137c..5e17b018163 100644
--- a/app/views/admin/users/_head.html.haml
+++ b/app/views/admin/users/_head.html.haml
@@ -6,7 +6,7 @@
%span.cred (Admin)
.pull-right
- - unless @user == current_user
+ - unless @user == current_user || @user.blocked?
= link_to 'Impersonate', impersonate_admin_user_path(@user), method: :post, class: "btn btn-grouped btn-info"
= link_to edit_admin_user_path(@user), class: "btn btn-grouped" do
%i.fa.fa-pencil-square-o
diff --git a/app/views/admin/users/_projects.html.haml b/app/views/admin/users/_projects.html.haml
new file mode 100644
index 00000000000..a126a858ea8
--- /dev/null
+++ b/app/views/admin/users/_projects.html.haml
@@ -0,0 +1,13 @@
+- if local_assigns.has_key?(:contributed_projects) && contributed_projects.present?
+ .panel.panel-default.contributed-projects
+ .panel-heading Projects contributed to
+ = render 'shared/projects/list',
+ projects: contributed_projects.sort_by(&:star_count).reverse,
+ projects_limit: 5, stars: true, avatar: false
+
+- if local_assigns.has_key?(:projects) && projects.present?
+ .panel.panel-default
+ .panel-heading Personal projects
+ = render 'shared/projects/list',
+ projects: projects.sort_by(&:star_count).reverse,
+ projects_limit: 10, stars: true, avatar: false
diff --git a/app/views/admin/users/edit.html.haml b/app/views/admin/users/edit.html.haml
index a8837d74dd9..3b6fd71500d 100644
--- a/app/views/admin/users/edit.html.haml
+++ b/app/views/admin/users/edit.html.haml
@@ -1,8 +1,5 @@
- page_title "Edit", @user.name, "Users"
%h3.page-title
Edit user: #{@user.name}
-.back-link
- = link_to admin_user_path(@user) do
- &larr; Back to user page
%hr
= render 'form'
diff --git a/app/views/admin/users/projects.html.haml b/app/views/admin/users/projects.html.haml
index 0d7a1a25a80..b655b2a15f5 100644
--- a/app/views/admin/users/projects.html.haml
+++ b/app/views/admin/users/projects.html.haml
@@ -14,7 +14,7 @@
.row
.col-md-6
- if @personal_projects.present?
- = render 'users/projects', projects: @personal_projects
+ = render 'admin/users/projects', projects: @personal_projects
- else
.nothing-here-block This user has no personal projects.
diff --git a/app/views/ci/admin/runners/show.html.haml b/app/views/ci/admin/runners/show.html.haml
index 1498db46a80..fd3d33d657b 100644
--- a/app/views/ci/admin/runners/show.html.haml
+++ b/app/views/ci/admin/runners/show.html.haml
@@ -37,7 +37,7 @@
= label_tag :tag_list, class: 'control-label' do
Tags
.col-sm-10
- = f.text_field :tag_list, class: 'form-control'
+ = f.text_field :tag_list, value: @runner.tag_list.to_s, class: 'form-control'
.help-block You can setup builds to only use runners with specific tags
.form-actions
= f.submit 'Save', class: 'btn btn-save'
diff --git a/app/views/ci/notify/build_fail_email.html.haml b/app/views/ci/notify/build_fail_email.html.haml
index b0aaea89075..de6291aa914 100644
--- a/app/views/ci/notify/build_fail_email.html.haml
+++ b/app/views/ci/notify/build_fail_email.html.haml
@@ -7,7 +7,7 @@
= @project.name
%p
- Commit: #{link_to @build.short_sha, namespace_project_commit_path(@build.gl_project.namespace, @build.gl_project, @build.sha)}
+ Commit: #{link_to @build.short_sha, namespace_project_commit_url(@build.gl_project.namespace, @build.gl_project, @build.sha)}
%p
Author: #{@build.commit.git_author_name}
%p
diff --git a/app/views/ci/notify/build_success_email.html.haml b/app/views/ci/notify/build_success_email.html.haml
index 24c439e50eb..6ef1fd67d89 100644
--- a/app/views/ci/notify/build_success_email.html.haml
+++ b/app/views/ci/notify/build_success_email.html.haml
@@ -8,7 +8,7 @@
= @project.name
%p
- Commit: #{link_to @build.short_sha, namespace_project_commit_path(@build.gl_project.namespace, @build.gl_project, @build.sha)}
+ Commit: #{link_to @build.short_sha, namespace_project_commit_url(@build.gl_project.namespace, @build.gl_project, @build.sha)}
%p
Author: #{@build.commit.git_author_name}
%p
diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml
index ed480b8caf8..991e67b1cd3 100644
--- a/app/views/dashboard/_projects_head.html.haml
+++ b/app/views/dashboard/_projects_head.html.haml
@@ -1,3 +1,6 @@
+= content_for :flash_message do
+ = render 'shared/project_limit'
+
%ul.center-top-menu
= nav_link(path: ['projects#index', 'root#index']) do
= link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml
index f3f3f58111e..d5b7e729e7b 100644
--- a/app/views/dashboard/groups/index.html.haml
+++ b/app/views/dashboard/groups/index.html.haml
@@ -8,8 +8,8 @@
= link_to new_group_path, class: "btn btn-new" do
%i.fa.fa-plus
New Group
- .title Welcome to the groups!
- Group members have access to all group projects.
+ .oneline
+ Group members have access to all group projects.
%ul.content-list
- @group_members.each do |group_member|
diff --git a/app/views/dashboard/milestones/show.html.haml b/app/views/dashboard/milestones/show.html.haml
index 83077a398bd..3536bbeaf4b 100644
--- a/app/views/dashboard/milestones/show.html.haml
+++ b/app/views/dashboard/milestones/show.html.haml
@@ -1,19 +1,23 @@
- page_title @milestone.title, "Milestones"
-%h4.page-title
- .issue-box{ class: "issue-box-#{@milestone.closed? ? 'closed' : 'open'}" }
- - if @milestone.closed?
- Closed
- - else
- Open
- Milestone #{@milestone.title}
+- header_title "Milestones", dashboard_milestones_path
+
+.issuable-details
+ .page-title
+ .issue-box{ class: "issue-box-#{@milestone.closed? ? 'closed' : 'open'}" }
+ - if @milestone.closed?
+ Closed
+ - else
+ Open
+ Milestone #{@milestone.title}
+
+ .gray-content-block.middle-block
+ %h2.issue-title
+ = gfm escape_once(@milestone.title)
-%hr
- if @milestone.complete? && @milestone.active?
- .alert.alert-success
+ .alert.alert-success.prepend-top-default
%span All issues for this milestone are closed. You may close the milestone now.
-.description
-
.table-holder
%table.table
%thead
@@ -44,7 +48,7 @@
#{@milestone.open_items_count} open
= milestone_progress_bar(@milestone)
-%ul.nav.nav-tabs
+%ul.center-top-menu.no-top.no-bottom
%li.active
= link_to '#tab-issues', 'data-toggle' => 'tab' do
Issues
@@ -58,25 +62,39 @@
Participants
%span.badge= @milestone.participants.count
- .pull-right
- = link_to 'Browse Issues', issues_dashboard_path(milestone_title: @milestone.title), class: "btn edit-milestone-link btn-grouped"
-
.tab-content
.tab-pane.active#tab-issues
- .row
+ .gray-content-block.middle-block
+ .pull-right
+ = link_to 'Browse Issues', issues_dashboard_path(milestone_title: @milestone.title), class: "btn btn-grouped"
+
+ .oneline
+ All issues in this milestone
+
+ .row.prepend-top-default
.col-md-6
= render 'issues', title: "Open", issues: @milestone.opened_issues
.col-md-6
= render 'issues', title: "Closed", issues: @milestone.closed_issues
.tab-pane#tab-merge-requests
- .row
+ .gray-content-block.middle-block
+ .pull-right
+ = link_to 'Browse Merge Requests', merge_requests_dashboard_path(milestone_title: @milestone.title), class: "btn btn-grouped"
+
+ .oneline
+ All merge requests in this milestone
+
+ .row.prepend-top-default
.col-md-6
= render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests
.col-md-6
= render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests
.tab-pane#tab-participants
+ .gray-content-block.middle-block
+ .oneline
+ All participants to this milestone
%ul.bordered-list
- @milestone.participants.each do |user|
%li
diff --git a/app/views/dashboard/projects/index.atom.builder b/app/views/dashboard/projects/index.atom.builder
index d2c51486841..c8c219f4cca 100644
--- a/app/views/dashboard/projects/index.atom.builder
+++ b/app/views/dashboard/projects/index.atom.builder
@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: dashboard_projects_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: dashboard_projects_url, rel: "alternate", type: "text/html"
xml.id dashboard_projects_url
- xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
+ xml.updated @events.latest_update_time.strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
@events.each do |event|
event_to_atom(xml, event)
diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml
index 7a16b811f6b..53abf274bdb 100644
--- a/app/views/dashboard/projects/index.html.haml
+++ b/app/views/dashboard/projects/index.html.haml
@@ -3,7 +3,7 @@
= auto_discovery_link_tag(:atom, dashboard_projects_url(format: :atom, private_token: current_user.private_token), title: "All activity")
- page_title "Projects"
-- header_title "Projects", root_path
+- header_title "Projects", dashboard_projects_path
= render 'dashboard/projects_head'
diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml
index f75f2e0a32a..70705923d42 100644
--- a/app/views/dashboard/projects/starred.html.haml
+++ b/app/views/dashboard/projects/starred.html.haml
@@ -1,5 +1,5 @@
- page_title "Starred Projects"
-- header_title "Projects", projects_path
+- header_title "Projects", dashboard_projects_path
= render 'dashboard/projects_head'
diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml
index 2761272aa8a..28b12c8dca8 100644
--- a/app/views/explore/projects/_filter.html.haml
+++ b/app/views/explore/projects/_filter.html.haml
@@ -3,7 +3,7 @@
.form-group
= search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "projects_search", spellcheck: false
.form-group
- = button_tag 'Search', class: "btn btn-success"
+ = button_tag 'Search', class: "btn"
.pull-right.hidden-sm.hidden-xs
- if current_user
diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml
index 67e38ca3127..76bdd68fd76 100644
--- a/app/views/explore/projects/index.html.haml
+++ b/app/views/explore/projects/index.html.haml
@@ -1,5 +1,5 @@
- page_title "Projects"
-- header_title "Projects", root_path
+- header_title "Projects", dashboard_projects_path
- if current_user
= render 'dashboard/projects_head'
diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml
index 596cb0a96cd..e30c3633223 100644
--- a/app/views/explore/projects/starred.html.haml
+++ b/app/views/explore/projects/starred.html.haml
@@ -1,5 +1,5 @@
- page_title "Projects"
-- header_title "Projects", root_path
+- header_title "Projects", dashboard_projects_path
- if current_user
= render 'dashboard/projects_head'
diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml
index 5ea6d81c5b9..1412b19acde 100644
--- a/app/views/explore/projects/trending.html.haml
+++ b/app/views/explore/projects/trending.html.haml
@@ -1,5 +1,5 @@
- page_title "Projects"
-- header_title "Projects", root_path
+- header_title "Projects", dashboard_projects_path
- if current_user
= render 'dashboard/projects_head'
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index 57308a661c0..8daac585960 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -3,8 +3,7 @@
.panel.panel-default
.panel-heading
- %strong= @group.name
- group settings:
+ Group settings
.panel-body
= form_for @group, html: { multipart: true, class: "form-horizontal" }, authenticity_token: true do |f|
- if @group.errors.any?
@@ -45,4 +44,5 @@
%br
%strong Removed group can not be restored!
- = link_to 'Remove Group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove"
+ .form-actions
+ = link_to 'Remove Group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove"
diff --git a/app/views/groups/group_members/_group_member.html.haml b/app/views/groups/group_members/_group_member.html.haml
index be94b1abc11..a79a0fcdc8e 100644
--- a/app/views/groups/group_members/_group_member.html.haml
+++ b/app/views/groups/group_members/_group_member.html.haml
@@ -4,7 +4,7 @@
%li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)}
%span{class: ("list-item-name" if show_controls)}
- if member.user
- = image_tag avatar_icon(user, 16), class: "avatar s16", alt: ''
+ = image_tag avatar_icon(user, 24), class: "avatar s24", alt: ''
%strong
= link_to user.name, user_path(user)
%span.cgray= user.username
@@ -14,7 +14,7 @@
%label.label.label-danger
%strong Blocked
- else
- = image_tag avatar_icon(member.invite_email, 16), class: "avatar s16", alt: ''
+ = image_tag avatar_icon(member.invite_email, 24), class: "avatar s24", alt: ''
%strong
= member.invite_email
%span.cgray
@@ -30,7 +30,7 @@
- if should_user_see_group_roles?(current_user, @group)
%span.pull-right
- %strong= member.human_access
+ %strong.member-access-level= member.human_access
- if show_controls
- if can?(current_user, :update_group_member, member)
= button_tag class: "btn-xs btn js-toggle-button",
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index d4ad33a8bf1..335bf036074 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -1,38 +1,35 @@
- page_title "Members"
- header_title group_title(@group, "Members", group_group_members_path(@group))
-- if should_user_see_group_roles?(current_user, @group)
- %p.light
- Members of group have access to all group projects.
- Read more about permissions
- %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
-
-
-.clearfix.js-toggle-container
- = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do
- .form-group
- = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input', spellcheck: false }
- = button_tag 'Search', class: 'btn'
+- @blank_container = true
+.group-members-page
- if current_user && current_user.can?(:admin_group_member, @group)
- .pull-right
- = button_tag class: 'btn btn-new js-toggle-button', type: 'button' do
- Add members
- %i.fa.fa-chevron-down
-
- .js-toggle-content.hide.new-group-member-holder
- = render "new_group_member"
-
-.panel.panel-default.prepend-top-20
- .panel-heading
- %strong #{@group.name}
- group members
- %small
- (#{@members.total_count})
- %ul.well-list
- - @members.each do |member|
- = render 'groups/group_members/group_member', member: member, show_controls: true
+ .panel.panel-default
+ .panel-heading
+ Add new user to group
+ .panel-body
+ - if should_user_see_group_roles?(current_user, @group)
+ %p.light
+ Members of group have access to all group projects.
+ .new-group-member-holder
+ = render "new_group_member"
-= paginate @members, theme: 'gitlab'
+ .panel.panel-default
+ .panel-heading
+ %strong #{@group.name}
+ group members
+ %small
+ (#{@members.total_count})
+ .pull-right
+ = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do
+ .form-group
+ = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control', spellcheck: false }
+ = button_tag class: 'btn', title: 'Search' do
+ = icon("search")
+ %ul.content-list
+ - @members.each do |member|
+ = render 'groups/group_members/group_member', member: member, show_controls: true
+ = paginate @members, theme: 'gitlab'
:javascript
$('form.member-search-form').on('submit', function(event) {
diff --git a/app/views/groups/group_members/update.js.haml b/app/views/groups/group_members/update.js.haml
index 5bad48abafd..df726e2b2b9 100644
--- a/app/views/groups/group_members/update.js.haml
+++ b/app/views/groups/group_members/update.js.haml
@@ -1,2 +1,2 @@
:plain
- $("##{dom_id(@member)}").replaceWith('#{escape_javascript(render(@member, member: @member, show_controls: true))}');
+ $("##{dom_id(@group_member)}").replaceWith('#{escape_javascript(render(@group_member, member: @group_member, show_controls: true))}');
diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml
index 800bac4ef02..3894a0ece74 100644
--- a/app/views/groups/milestones/new.html.haml
+++ b/app/views/groups/milestones/new.html.haml
@@ -14,8 +14,7 @@
.form-group
= f.label :title, "Title", class: "control-label"
.col-sm-10
- = f.text_field :title, maxlength: 255, class: "form-control js-quick-submit", required: true
- %p.hint Required
+ = f.text_field :title, maxlength: 255, class: "form-control js-quick-submit", required: true, autofocus: true
.form-group.milestone-description
= f.label :description, "Description", class: "control-label"
.col-sm-10
diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml
index d161259e4aa..3c1d8815013 100644
--- a/app/views/groups/milestones/show.html.haml
+++ b/app/views/groups/milestones/show.html.haml
@@ -1,27 +1,29 @@
- page_title @milestone.title, "Milestones"
= render "header_title"
-%h4.page-title
- .issue-box{ class: "issue-box-#{@milestone.closed? ? 'closed' : 'open'}" }
- - if @milestone.closed?
- Closed
- - else
- Open
- Milestone #{@milestone.title}
- .pull-right
- - if can?(current_user, :admin_milestones, @group)
- - if @milestone.active?
- = link_to 'Close Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-sm btn-close"
+.issuable-details
+ .page-title
+ .issue-box{ class: "issue-box-#{@milestone.closed? ? 'closed' : 'open'}" }
+ - if @milestone.closed?
+ Closed
- else
- = link_to 'Reopen Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen"
+ Open
+ Milestone #{@milestone.title}
+ .pull-right
+ - if can?(current_user, :admin_milestones, @group)
+ - if @milestone.active?
+ = link_to 'Close Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close"
+ - else
+ = link_to 'Reopen Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
+
+ .gray-content-block.middle-block
+ %h2.issue-title
+ = gfm escape_once(@milestone.title)
-%hr
- if @milestone.complete? && @milestone.active?
- .alert.alert-success
+ .alert.alert-success.prepend-top-default
%span All issues for this milestone are closed. You may close the milestone now.
-.description
-
.table-holder
%table.table
%thead
@@ -52,7 +54,7 @@
#{@milestone.open_items_count} open
= milestone_progress_bar(@milestone)
-%ul.nav.nav-tabs
+%ul.center-top-menu.no-top.no-bottom
%li.active
= link_to '#tab-issues', 'data-toggle' => 'tab' do
Issues
@@ -66,25 +68,40 @@
Participants
%span.badge= @milestone.participants.count
- .pull-right
- = link_to 'Browse Issues', issues_group_path(@group, milestone_title: @milestone.title), class: "btn edit-milestone-link btn-grouped"
-
.tab-content
.tab-pane.active#tab-issues
- .row
+ .gray-content-block.middle-block
+ .pull-right
+ = link_to 'Browse Issues', issues_group_path(@group, milestone_title: @milestone.title), class: "btn btn-grouped"
+
+ .oneline
+ All issues in this milestone
+
+ .row.prepend-top-default
.col-md-6
= render 'issues', title: "Open", issues: @milestone.opened_issues
.col-md-6
= render 'issues', title: "Closed", issues: @milestone.closed_issues
.tab-pane#tab-merge-requests
- .row
+ .gray-content-block.middle-block
+ .pull-right
+ = link_to 'Browse Merge Requests', merge_requests_group_path(@group, milestone_title: @milestone.title), class: "btn btn-grouped"
+
+ .oneline
+ All merge requests in this milestone
+
+ .row.prepend-top-default
.col-md-6
= render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests
.col-md-6
= render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests
.tab-pane#tab-participants
+ .gray-content-block.middle-block
+ .oneline
+ All participants to this milestone
+
%ul.bordered-list
- @milestone.participants.each do |user|
%li
diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml
index 0665cdf387a..4bc31cabea6 100644
--- a/app/views/groups/new.html.haml
+++ b/app/views/groups/new.html.haml
@@ -1,5 +1,10 @@
- page_title 'New Group'
-- header_title 'New Group'
+- header_title "Groups", dashboard_groups_path
+
+%h3.page-title
+ New Group
+%hr
+
= form_for @group, html: { class: 'group-form form-horizontal' } do |f|
- if @group.errors.any?
.alert.alert-danger
@@ -18,3 +23,4 @@
.form-actions
= f.submit 'Create group', class: "btn btn-create", tabindex: 3
+ = link_to 'Cancel', dashboard_groups_path, class: 'btn btn-cancel'
diff --git a/app/views/groups/show.atom.builder b/app/views/groups/show.atom.builder
index a91d1a6e94b..7ea574434c3 100644
--- a/app/views/groups/show.atom.builder
+++ b/app/views/groups/show.atom.builder
@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: group_url(@group, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: group_url(@group), rel: "alternate", type: "text/html"
xml.id group_url(@group)
- xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
+ xml.updated @events.latest_update_time.strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
@events.each do |event|
event_to_atom(xml, event)
diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml
index 1f09a27e2d6..aec2e836c9f 100644
--- a/app/views/import/bitbucket/status.html.haml
+++ b/app/views/import/bitbucket/status.html.haml
@@ -1,4 +1,5 @@
- page_title "Bitbucket import"
+- header_title "Projects", root_path
%h3.page-title
%i.fa.fa-bitbucket
Import projects from Bitbucket
diff --git a/app/views/import/fogbugz/new.html.haml b/app/views/import/fogbugz/new.html.haml
index e1bb88ca4ed..5515fad6f48 100644
--- a/app/views/import/fogbugz/new.html.haml
+++ b/app/views/import/fogbugz/new.html.haml
@@ -1,4 +1,5 @@
- page_title "FogBugz Import"
+- header_title "Projects", root_path
%h3.page-title
%i.fa.fa-bug
Import projects from FogBugz
diff --git a/app/views/import/fogbugz/new_user_map.html.haml b/app/views/import/fogbugz/new_user_map.html.haml
index bc3c90294e3..07338736bac 100644
--- a/app/views/import/fogbugz/new_user_map.html.haml
+++ b/app/views/import/fogbugz/new_user_map.html.haml
@@ -1,4 +1,5 @@
- page_title 'User map', 'FogBugz import'
+- header_title "Projects", root_path
%h3.page-title
%i.fa.fa-bug
Import projects from FogBugz
diff --git a/app/views/import/fogbugz/status.html.haml b/app/views/import/fogbugz/status.html.haml
index b902006597b..6ee16c8be4b 100644
--- a/app/views/import/fogbugz/status.html.haml
+++ b/app/views/import/fogbugz/status.html.haml
@@ -1,4 +1,5 @@
- page_title "FogBugz import"
+- header_title "Projects", root_path
%h3.page-title
%i.fa.fa-bug
Import projects from FogBugz
diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml
index 0699321c8c0..1416ee5bd5a 100644
--- a/app/views/import/github/status.html.haml
+++ b/app/views/import/github/status.html.haml
@@ -1,4 +1,5 @@
- page_title "GitHub import"
+- header_title "Projects", root_path
%h3.page-title
%i.fa.fa-github
Import projects from GitHub
diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml
index f4a2b33af21..911a55eb85d 100644
--- a/app/views/import/gitlab/status.html.haml
+++ b/app/views/import/gitlab/status.html.haml
@@ -1,4 +1,5 @@
- page_title "GitLab.com import"
+- header_title "Projects", root_path
%h3.page-title
%i.fa.fa-heart
Import projects from GitLab.com
diff --git a/app/views/import/gitorious/status.html.haml b/app/views/import/gitorious/status.html.haml
index 71752d21efa..6b0fa1edf8c 100644
--- a/app/views/import/gitorious/status.html.haml
+++ b/app/views/import/gitorious/status.html.haml
@@ -1,4 +1,5 @@
- page_title "Gitorious import"
+- header_title "Projects", root_path
%h3.page-title
%i.icon-gitorious.icon-gitorious-big
Import projects from Gitorious.org
diff --git a/app/views/import/google_code/new.html.haml b/app/views/import/google_code/new.html.haml
index 9c64e0a009f..5d2f149cd5f 100644
--- a/app/views/import/google_code/new.html.haml
+++ b/app/views/import/google_code/new.html.haml
@@ -1,4 +1,5 @@
- page_title "Google Code import"
+- header_title "Projects", root_path
%h3.page-title
%i.fa.fa-google
Import projects from Google Code
@@ -6,7 +7,7 @@
= form_tag callback_import_google_code_path, class: 'form-horizontal', multipart: true do
%p
- Follow the steps below to export your Google Code project data.
+ Follow the steps below to export your Google Code project data.
In the next step, you'll be able to select the projects you want to import.
%ol
%li
diff --git a/app/views/import/google_code/new_user_map.html.haml b/app/views/import/google_code/new_user_map.html.haml
index e53ebda7dc1..0738b3db1eb 100644
--- a/app/views/import/google_code/new_user_map.html.haml
+++ b/app/views/import/google_code/new_user_map.html.haml
@@ -1,4 +1,5 @@
- page_title "User map", "Google Code import"
+- header_title "Projects", root_path
%h3.page-title
%i.fa.fa-google
Import projects from Google Code
@@ -8,31 +9,31 @@
%p
Customize how Google Code email addresses and usernames are imported into GitLab.
In the next step, you'll be able to select the projects you want to import.
- %p
+ %p
The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side.
%ul
%li
%strong Default: Directly import the Google Code email address or username
%p
- <code>"johnsmith@example.com": "johnsm...@example.com"</code>
- will add "By johnsm...@example.com" to all issues and comments originally created by johnsmith@example.com.
+ <code>"johnsmith@example.com": "johnsm...@example.com"</code>
+ will add "By johnsm...@example.com" to all issues and comments originally created by johnsmith@example.com.
The email address or username is masked to ensure the user's privacy.
%li
%strong Map a Google Code user to a GitLab user
%p
- <code>"johnsmith@example.com": "@johnsmith"</code>
- will add "By <a href="#">@johnsmith</a>" to all issues and comments originally created by johnsmith@example.com,
+ <code>"johnsmith@example.com": "@johnsmith"</code>
+ will add "By <a href="#">@johnsmith</a>" to all issues and comments originally created by johnsmith@example.com,
and will set <a href="#">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com.
%li
%strong Map a Google Code user to a full name
%p
- <code>"johnsmith@example.com": "John Smith"</code>
+ <code>"johnsmith@example.com": "John Smith"</code>
will add "By John Smith" to all issues and comments originally created by johnsmith@example.com.
%li
%strong Map a Google Code user to a full email address
%p
- <code>"johnsmith@example.com": "johnsmith@example.com"</code>
- will add "By <a href="#">johnsmith@example.com</a>" to all issues and comments originally created by johnsmith@example.com.
+ <code>"johnsmith@example.com": "johnsmith@example.com"</code>
+ will add "By <a href="#">johnsmith@example.com</a>" to all issues and comments originally created by johnsmith@example.com.
By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address.
.form-group
diff --git a/app/views/import/google_code/status.html.haml b/app/views/import/google_code/status.html.haml
index 8c64fd27e60..175ef6921cd 100644
--- a/app/views/import/google_code/status.html.haml
+++ b/app/views/import/google_code/status.html.haml
@@ -1,4 +1,5 @@
- page_title "Google Code import"
+- header_title "Projects", root_path
%h3.page-title
%i.fa.fa-google
Import projects from Google Code
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 352b8040cf4..ec7cd79bc54 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -1,8 +1,8 @@
-.page-with-sidebar{ class: nav_sidebar_class }
+.page-with-sidebar{ class: page_sidebar_class }
= render "layouts/broadcast"
- .sidebar-wrapper.nicescroll
+ .sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
.header-logo
- = link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home' do
= brand_header_logo
.gitlab-text-container
%h3 GitLab
@@ -17,8 +17,8 @@
.collapse-nav
= render partial: 'layouts/collapse_button'
- if current_user
- = link_to current_user, class: 'sidebar-user' do
- = image_tag avatar_icon(current_user, 60), alt: 'User activity', class: 'avatar avatar s36'
+ = link_to current_user, class: 'sidebar-user', title: "Profile" do
+ = image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
.username
= current_user.username
.content-wrapper
diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml
index 1c738719bd8..6591c52bdbd 100644
--- a/app/views/layouts/admin.html.haml
+++ b/app/views/layouts/admin.html.haml
@@ -1,5 +1,5 @@
-- page_title "Admin area"
-- header_title "Admin area", admin_root_path
+- page_title "Admin Area"
+- header_title "Admin Area", admin_root_path
- sidebar "admin"
= render template: "layouts/application"
diff --git a/app/views/layouts/ci/_page.html.haml b/app/views/layouts/ci/_page.html.haml
index ab3e29c3f42..7e90af21b21 100644
--- a/app/views/layouts/ci/_page.html.haml
+++ b/app/views/layouts/ci/_page.html.haml
@@ -1,8 +1,8 @@
-.page-with-sidebar{ class: nav_sidebar_class }
+.page-with-sidebar{ class: page_sidebar_class }
= render "layouts/broadcast"
- .sidebar-wrapper.nicescroll
+ .sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
.header-logo
- = link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home' do
= brand_header_logo
.gitlab-text-container
%h3 GitLab
@@ -14,8 +14,8 @@
.collapse-nav
= render partial: 'layouts/collapse_button'
- if current_user
- = link_to current_user, class: 'sidebar-user' do
- = image_tag avatar_icon(current_user, 60), alt: 'User activity', class: 'avatar avatar s36'
+ = link_to current_user, class: 'sidebar-user', title: "Profile" do
+ = image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
.username
= current_user.username
.content-wrapper
diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml
index 95e077c339f..f08cb0a5428 100644
--- a/app/views/layouts/devise.html.haml
+++ b/app/views/layouts/devise.html.haml
@@ -1,13 +1,13 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head"
- %body.ui_charcoal.login-page.application
+ %body.ui_charcoal.login-page.application.navless
= render "layouts/header/empty"
= render "layouts/broadcast"
.container.navless-container
.content
= render "layouts/flash"
- .row.prepend-top-20
+ .row
.col-sm-5.pull-right
= yield
.col-sm-7.brand-holder.pull-left
diff --git a/app/views/layouts/errors.html.haml b/app/views/layouts/errors.html.haml
index 2af265a2296..915acc4612e 100644
--- a/app/views/layouts/errors.html.haml
+++ b/app/views/layouts/errors.html.haml
@@ -1,7 +1,7 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head"
- %body{class: "#{user_application_theme} application"}
+ %body{class: "#{user_application_theme} application navless"}
= render "layouts/header/empty"
.container.navless-container
= render "layouts/flash"
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 3ca30d3baab..3892ef8eefa 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -11,27 +11,27 @@
%li.hidden-sm.hidden-xs
= render 'layouts/search'
%li.visible-sm.visible-xs
- = link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('search')
- if session[:impersonator_id]
%li.impersonation
- = link_to stop_impersonation_admin_users_path, method: :delete, title: 'Stop impersonation', data: { toggle: 'tooltip', placement: 'bottom' } do
+ = link_to stop_impersonation_admin_users_path, method: :delete, title: 'Stop Impersonation', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= icon('user-secret fw')
- if current_user.is_admin?
%li
- = link_to admin_root_path, title: 'Admin area', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = link_to admin_root_path, title: 'Admin Area', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('wrench fw')
- if current_user.can_create_project?
%li
- = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('plus fw')
- if Gitlab::Sherlock.enabled?
%li
= link_to sherlock_transactions_path, title: 'Sherlock Transactions',
- data: {toggle: 'tooltip', placement: 'bottom'} do
+ data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('tachometer fw')
%li
- = link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('sign-out')
%h1.title= title
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index 2079feeeab6..d04a3d1f227 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -5,78 +5,78 @@
%span
Overview
= nav_link(controller: [:admin, :projects]) do
- = link_to admin_namespaces_projects_path, title: 'Projects', data: {placement: 'right'} do
+ = link_to admin_namespaces_projects_path, title: 'Projects' do
= icon('cube fw')
%span
Projects
= nav_link(controller: :users) do
- = link_to admin_users_path, title: 'Users', data: {placement: 'right'} do
+ = link_to admin_users_path, title: 'Users' do
= icon('user fw')
%span
Users
= nav_link(controller: :groups) do
- = link_to admin_groups_path, title: 'Groups', data: {placement: 'right'} do
+ = link_to admin_groups_path, title: 'Groups' do
= icon('group fw')
%span
Groups
= nav_link(controller: :deploy_keys) do
- = link_to admin_deploy_keys_path, title: 'Deploy Keys', data: {placement: 'right'} do
+ = link_to admin_deploy_keys_path, title: 'Deploy Keys' do
= icon('key fw')
%span
Deploy Keys
= nav_link do
- = link_to ci_admin_projects_path, title: 'Continuous Integration', data: {placement: 'right'} do
+ = link_to ci_admin_projects_path, title: 'Continuous Integration' do
= icon('building fw')
%span
Continuous Integration
= nav_link(controller: :logs) do
- = link_to admin_logs_path, title: 'Logs', data: {placement: 'right'} do
+ = link_to admin_logs_path, title: 'Logs' do
= icon('file-text fw')
%span
Logs
= nav_link(controller: :broadcast_messages) do
- = link_to admin_broadcast_messages_path, title: 'Broadcast Messages', data: {placement: 'right'} do
+ = link_to admin_broadcast_messages_path, title: 'Messages' do
= icon('bullhorn fw')
%span
Messages
= nav_link(controller: :hooks) do
- = link_to admin_hooks_path, title: 'Hooks', data: {placement: 'right'} do
+ = link_to admin_hooks_path, title: 'Hooks' do
= icon('external-link fw')
%span
Hooks
= nav_link(controller: :background_jobs) do
- = link_to admin_background_jobs_path, title: 'Background Jobs', data: {placement: 'right'} do
+ = link_to admin_background_jobs_path, title: 'Background Jobs' do
= icon('cog fw')
%span
Background Jobs
= nav_link(controller: :applications) do
- = link_to admin_applications_path, title: 'Applications', data: {placement: 'right'} do
+ = link_to admin_applications_path, title: 'Applications' do
= icon('cloud fw')
%span
Applications
= nav_link(controller: :services) do
- = link_to admin_application_settings_services_path, title: 'Service Templates', data: {placement: 'right'} do
+ = link_to admin_application_settings_services_path, title: 'Service Templates' do
= icon('copy fw')
%span
Service Templates
= nav_link(controller: :labels) do
- = link_to admin_labels_path, title: 'Labels', data: {placement: 'right'} do
+ = link_to admin_labels_path, title: 'Labels' do
= icon('tags fw')
%span
Labels
= nav_link(controller: :abuse_reports) do
- = link_to admin_abuse_reports_path, title: "Abuse reports" do
+ = link_to admin_abuse_reports_path, title: "Abuse Reports" do
= icon('exclamation-circle fw')
%span
Abuse Reports
%span.count= AbuseReport.count(:all)
= nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
- = link_to admin_application_settings_path, title: 'Settings', data: {placement: 'right'} do
+ = link_to admin_application_settings_path, title: 'Settings' do
= icon('cogs fw')
%span
Settings
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index b1a1d531846..da698831300 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -1,50 +1,50 @@
%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', data: {placement: 'right'} do
+ = link_to dashboard_projects_path, title: 'Projects' do
= icon('home fw')
%span
Projects
= nav_link(path: 'dashboard#activity') do
- = link_to activity_dashboard_path, class: 'shortcuts-activity', title: 'Activity', data: {placement: 'right'} do
+ = link_to activity_dashboard_path, class: 'shortcuts-activity', title: 'Activity' do
= icon('dashboard fw')
%span
Activity
= nav_link(controller: :groups) do
- = link_to dashboard_groups_path, title: 'Groups', data: {placement: 'right'} do
+ = link_to dashboard_groups_path, title: 'Groups' do
= icon('group fw')
%span
Groups
= nav_link(controller: :milestones) do
- = link_to dashboard_milestones_path, title: 'Milestones', data: {placement: 'right'} do
+ = link_to dashboard_milestones_path, title: 'Milestones' do
= icon('clock-o fw')
%span
Milestones
= nav_link(path: 'dashboard#issues') do
- = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'shortcuts-issues', data: {placement: 'right'} do
+ = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'shortcuts-issues' do
= icon('exclamation-circle fw')
%span
Issues
%span.count= current_user.assigned_issues.opened.count
= nav_link(path: 'dashboard#merge_requests') do
- = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'shortcuts-merge_requests', data: {placement: 'right'} do
+ = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'shortcuts-merge_requests' do
= icon('tasks fw')
%span
Merge Requests
%span.count= current_user.assigned_merge_requests.opened.count
= nav_link(controller: :snippets) do
- = link_to dashboard_snippets_path, title: 'Your snippets', data: {placement: 'right'} do
+ = link_to dashboard_snippets_path, title: 'Snippets' do
= icon('clipboard fw')
%span
Snippets
= nav_link(controller: :help) do
- = link_to help_path, title: 'Help', data: {placement: 'right'} do
+ = link_to help_path, title: 'Help' do
= icon('question-circle fw')
%span
Help
%li.separate-item
= nav_link(controller: :profile) do
- = link_to profile_path, title: 'Profile settings', data: {placement: 'bottom'} do
+ = link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do
= icon('user fw')
%span
Profile Settings
diff --git a/app/views/layouts/nav/_explore.html.haml b/app/views/layouts/nav/_explore.html.haml
index 21e565972a7..48039ca2918 100644
--- a/app/views/layouts/nav/_explore.html.haml
+++ b/app/views/layouts/nav/_explore.html.haml
@@ -1,21 +1,21 @@
%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', data: {placement: 'right'} do
+ = link_to explore_root_path, title: 'Projects' do
= icon('home fw')
%span
Projects
= nav_link(controller: :groups) do
- = link_to explore_groups_path, title: 'Groups', data: {placement: 'right'} do
+ = link_to explore_groups_path, title: 'Groups' do
= icon('group fw')
%span
Groups
= nav_link(controller: :snippets) do
- = link_to explore_snippets_path, title: 'Snippets', data: {placement: 'right'} do
+ = link_to explore_snippets_path, title: 'Snippets' do
= icon('clipboard fw')
%span
Snippets
= nav_link(controller: :help) do
- = link_to help_path, title: 'Help', data: {placement: 'right'} do
+ = link_to help_path, title: 'Help' do
= icon('question-circle fw')
%span
Help
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index 319352876b4..68da8d5de2a 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -1,6 +1,6 @@
%ul.nav.nav-sidebar
= nav_link do
- = link_to root_path, title: 'Go to dashboard', data: {placement: 'right'}, class: 'back-link' do
+ = link_to root_path, title: 'Go to dashboard', class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Go to dashboard
@@ -8,39 +8,39 @@
%li.separate-item
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do
- = link_to group_path(@group), title: 'Home', data: {placement: 'right'} do
+ = link_to group_path(@group), title: 'Home' do
= icon('dashboard fw')
%span
Group
- if can?(current_user, :read_group, @group)
- if current_user
= nav_link(controller: [:group, :milestones]) do
- = link_to group_milestones_path(@group), title: 'Milestones', data: {placement: 'right'} 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', data: {placement: 'right'} do
+ = link_to issues_group_path(@group), title: 'Issues' do
= icon('exclamation-circle fw')
%span
Issues
- if current_user
%span.count= Issue.opened.of_group(@group).count
= nav_link(path: 'groups#merge_requests') do
- = link_to merge_requests_group_path(@group), title: 'Merge Requests', data: {placement: 'right'} do
+ = link_to merge_requests_group_path(@group), title: 'Merge Requests' do
= icon('tasks fw')
%span
Merge Requests
- if current_user
%span.count= MergeRequest.opened.of_group(@group).count
= nav_link(controller: [:group_members]) do
- = link_to group_group_members_path(@group), title: 'Members', data: {placement: 'right'} 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', data: {placement: 'right'} do
+ = link_to edit_group_path(@group), title: 'Settings' do
= icon ('cogs fw')
%span
Settings
diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml
index c8411521f36..56a92fe9103 100644
--- a/app/views/layouts/nav/_group_settings.html.haml
+++ b/app/views/layouts/nav/_group_settings.html.haml
@@ -1,6 +1,6 @@
%ul.nav.nav-sidebar
= nav_link do
- = link_to group_path(@group), title: 'Go to group', data: {placement: 'right'}, class: 'back-link' do
+ = link_to group_path(@group), title: 'Go to group', class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Go to group
@@ -9,12 +9,12 @@
%ul.sidebar-subnav
= nav_link(path: 'groups#edit') do
- = link_to edit_group_path(@group), title: 'Group Settings', data: {placement: 'right'} do
+ = link_to edit_group_path(@group), title: 'Group Settings' do
= icon ('pencil-square-o fw')
%span
Group Settings
= nav_link(path: 'groups#projects') do
- = link_to projects_group_path(@group), title: 'Projects', data: {placement: 'right'} do
+ = link_to projects_group_path(@group), title: 'Projects' do
= icon('folder fw')
%span
Projects
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index 0f3a793e30b..64b30783c05 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -1,6 +1,6 @@
%ul.nav.nav-sidebar
= nav_link do
- = link_to root_path, title: 'Go to dashboard', data: {placement: 'right'}, class: 'back-link' do
+ = link_to root_path, title: 'Go to dashboard', class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Go to dashboard
@@ -8,52 +8,52 @@
%li.separate-item
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
- = link_to profile_path, title: 'Profile', data: {placement: 'right'} do
+ = link_to profile_path, title: 'Profile Settings' do
= icon('user fw')
%span
Profile Settings
= nav_link(controller: [:accounts, :two_factor_auths]) do
- = link_to profile_account_path, title: 'Account', data: {placement: 'right'} do
+ = link_to profile_account_path, title: 'Account' do
= icon('gear fw')
%span
Account
= nav_link(path: ['profiles#applications', 'applications#edit', 'applications#show', 'applications#new', 'applications#create']) do
- = link_to applications_profile_path, title: 'Applications', data: {placement: 'right'} do
+ = link_to applications_profile_path, title: 'Applications' do
= icon('cloud fw')
%span
Applications
= nav_link(controller: :emails) do
- = link_to profile_emails_path, title: 'Emails', data: {placement: 'right'} do
+ = link_to profile_emails_path, title: 'Emails' do
= icon('envelope-o fw')
%span
Emails
%span.count= current_user.emails.count + 1
- unless current_user.ldap_user?
= nav_link(controller: :passwords) do
- = link_to edit_profile_password_path, title: 'Password', data: {placement: 'right'} do
+ = link_to edit_profile_password_path, title: 'Password' do
= icon('lock fw')
%span
Password
= nav_link(controller: :notifications) do
- = link_to profile_notifications_path, title: 'Notifications', data: {placement: 'right'} do
+ = link_to profile_notifications_path, title: 'Notifications' do
= icon('inbox fw')
%span
Notifications
= nav_link(controller: :keys) do
- = link_to profile_keys_path, title: 'SSH Keys', data: {placement: 'right'} do
+ = link_to profile_keys_path, title: 'SSH Keys' do
= icon('key fw')
%span
SSH Keys
%span.count= current_user.keys.count
= nav_link(controller: :preferences) do
- = link_to profile_preferences_path, title: 'Preferences', data: {placement: 'right'} do
+ = link_to profile_preferences_path, title: 'Preferences' do
-# TODO (rspeicher): Better icon?
= icon('image fw')
%span
Preferences
= nav_link(path: 'profiles#audit_log') do
- = link_to audit_log_profile_path, title: 'Audit Log', data: {placement: 'right'} do
+ = link_to audit_log_profile_path, title: 'Audit Log' do
= icon('history fw')
%span
Audit Log
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 2b91d7721f9..87a7707b095 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -1,13 +1,13 @@
%ul.nav.nav-sidebar
- if @project.group
= nav_link do
- = link_to group_path(@project.group), title: 'Go to group', data: {placement: 'right'}, class: 'back-link' do
+ = link_to group_path(@project.group), title: 'Go to group', class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Go to group
- else
= nav_link do
- = link_to root_path, title: 'Go to dashboard', data: {placement: 'right'}, class: 'back-link' do
+ = link_to root_path, title: 'Go to dashboard', class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Go to dashboard
@@ -15,32 +15,32 @@
%li.separate-item
= nav_link(path: 'projects#show', html_options: {class: 'home'}) do
- = link_to project_path(@project), title: 'Project', class: 'shortcuts-project', data: {placement: 'right'} do
+ = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
= icon('home fw')
%span
Project
= nav_link(path: 'projects#activity') do
- = link_to activity_project_path(@project), title: 'Project Activity', class: 'shortcuts-project-activity', data: {placement: 'right'} do
+ = link_to activity_project_path(@project), title: 'Activity', class: 'shortcuts-project-activity' do
= icon('dashboard fw')
%span
Activity
- if project_nav_tab? :files
= nav_link(controller: %w(tree blob blame edit_tree new_tree)) do
- = link_to project_files_path(@project), title: 'Files', class: 'shortcuts-tree', data: {placement: 'right'} do
+ = link_to project_files_path(@project), title: 'Files', class: 'shortcuts-tree' do
= icon('files-o fw')
%span
Files
- if project_nav_tab? :commits
= nav_link(controller: %w(commit commits compare repositories tags branches releases)) do
- = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits', data: {placement: 'right'} do
+ = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
= icon('history fw')
%span
Commits
- if project_nav_tab? :builds
= nav_link(controller: %w(builds)) do
- = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds', data: {placement: 'right'} do
+ = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
= icon('cubes fw')
%span
Builds
@@ -48,28 +48,28 @@
- if project_nav_tab? :network
= nav_link(controller: %w(network)) do
- = link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network', data: {placement: 'right'} do
+ = link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network' do
= icon('code-fork fw')
%span
Network
- if project_nav_tab? :graphs
= nav_link(controller: %w(graphs)) do
- = link_to namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Graphs', class: 'shortcuts-graphs', data: {placement: 'right'} do
+ = link_to namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Graphs', class: 'shortcuts-graphs' do
= icon('area-chart fw')
%span
Graphs
- if project_nav_tab? :milestones
= nav_link(controller: :milestones) do
- = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones', data: {placement: 'right'} do
+ = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
= icon('clock-o fw')
%span
Milestones
- if project_nav_tab? :issues
= nav_link(controller: :issues) do
- = link_to url_for_project_issues(@project, only_path: true), title: 'Issues', class: 'shortcuts-issues', data: {placement: 'right'} do
+ = link_to url_for_project_issues(@project, only_path: true), title: 'Issues', class: 'shortcuts-issues' do
= icon('exclamation-circle fw')
%span
Issues
@@ -78,7 +78,7 @@
- if project_nav_tab? :merge_requests
= nav_link(controller: :merge_requests) do
- = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests', data: {placement: 'right'} do
+ = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
= icon('tasks fw')
%span
Merge Requests
@@ -86,35 +86,35 @@
- if project_nav_tab? :settings
= nav_link(controller: [:project_members, :teams]) do
- = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab', data: {placement: 'right'} do
+ = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do
= icon('users fw')
%span
Members
- if project_nav_tab? :labels
= nav_link(controller: :labels) do
- = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels', data: {placement: 'right'} do
+ = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
= icon('tags fw')
%span
Labels
- if project_nav_tab? :wiki
= nav_link(controller: :wikis) do
- = link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki', data: {placement: 'right'} do
+ = link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki' do
= icon('book fw')
%span
Wiki
- if project_nav_tab? :snippets
= nav_link(controller: :snippets) do
- = link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets', data: {placement: 'right'} do
+ = link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets' do
= icon('clipboard fw')
%span
Snippets
- if project_nav_tab? :settings
= nav_link(html_options: {class: "#{project_tab_class} separate-item"}) do
- = link_to edit_project_path(@project), title: 'Settings', data: {placement: 'right'} do
+ = link_to edit_project_path(@project), title: 'Settings' do
= icon('cogs fw')
%span
Settings
diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml
index 377a99e719a..f0b3f27b626 100644
--- a/app/views/layouts/nav/_project_settings.html.haml
+++ b/app/views/layouts/nav/_project_settings.html.haml
@@ -1,6 +1,6 @@
%ul.nav.nav-sidebar
= nav_link do
- = link_to project_path(@project), title: 'Go to project', data: {placement: 'right'}, class: 'back-link' do
+ = link_to project_path(@project), title: 'Go to project', class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Go to project
@@ -9,59 +9,59 @@
%ul.sidebar-subnav
= nav_link(path: 'projects#edit') do
- = link_to edit_project_path(@project), title: 'Project Settings', data: {placement: 'right'} do
+ = link_to edit_project_path(@project), title: 'Project Settings' do
= icon('pencil-square-o fw')
%span
Project Settings
= nav_link(controller: :deploy_keys) do
- = link_to namespace_project_deploy_keys_path(@project.namespace, @project), title: 'Deploy Keys', data: {placement: 'right'} do
+ = link_to namespace_project_deploy_keys_path(@project.namespace, @project), title: 'Deploy Keys' do
= icon('key fw')
%span
Deploy Keys
= nav_link(controller: :hooks) do
- = link_to namespace_project_hooks_path(@project.namespace, @project), title: 'Web Hooks', data: {placement: 'right'} do
+ = link_to namespace_project_hooks_path(@project.namespace, @project), title: 'Web Hooks' do
= icon('link fw')
%span
Web Hooks
= nav_link(controller: :services) do
- = link_to namespace_project_services_path(@project.namespace, @project), title: 'Services', data: {placement: 'right'} do
+ = link_to namespace_project_services_path(@project.namespace, @project), title: 'Services' do
= icon('cogs fw')
%span
Services
= nav_link(controller: :protected_branches) do
- = link_to namespace_project_protected_branches_path(@project.namespace, @project), title: 'Protected Branches', data: {placement: 'right'} do
+ = link_to namespace_project_protected_branches_path(@project.namespace, @project), title: 'Protected Branches' do
= icon('lock fw')
%span
Protected Branches
- if @project.builds_enabled?
= nav_link(controller: :runners) do
- = link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners', data: {placement: 'right'} do
+ = link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners' do
= icon('cog fw')
%span
Runners
= nav_link(controller: :variables) do
- = link_to namespace_project_variables_path(@project.namespace, @project) do
+ = link_to namespace_project_variables_path(@project.namespace, @project), title: 'Variables' do
= icon('code fw')
%span
Variables
= nav_link path: 'triggers#index' do
- = link_to namespace_project_triggers_path(@project.namespace, @project) do
+ = link_to namespace_project_triggers_path(@project.namespace, @project), title: 'Triggers' do
= icon('retweet fw')
%span
Triggers
= nav_link path: 'ci_web_hooks#index' do
- = link_to namespace_project_ci_web_hooks_path(@project.namespace, @project) do
+ = link_to namespace_project_ci_web_hooks_path(@project.namespace, @project), title: 'CI Web Hooks' do
= icon('link fw')
%span
CI Web Hooks
= nav_link path: 'ci_settings#edit' do
- = link_to edit_namespace_project_ci_settings_path(@project.namespace, @project) do
+ = link_to edit_namespace_project_ci_settings_path(@project.namespace, @project), title: 'CI Settings' do
= icon('building fw')
%span
CI Settings
= nav_link controller: 'ci_services' do
- = link_to namespace_project_ci_services_path(@project.namespace, @project) do
+ = link_to namespace_project_ci_services_path(@project.namespace, @project), title: 'CI Services' do
= icon('share fw')
%span
CI Services
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index cd7b1b0fe03..319bdd57c39 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -23,10 +23,13 @@
%p.cgray
- if current_user.private_token
= text_field_tag "token", current_user.private_token, class: "form-control"
- %div
- = f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-default btn-build-token"
- else
%span You don`t have one yet. Click generate to fix it.
+
+ .form-actions
+ - if current_user.private_token
+ = f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-default btn-build-token"
+ - else
= f.submit 'Generate', class: "btn btn-default btn-build-token"
- unless current_user.ldap_user?
@@ -54,7 +57,8 @@
%p
Each time you log in you’ll be required to provide your username and
password as usual, plus a randomly-generated code from your phone.
- %div
+
+ .form-actions
= link_to 'Enable Two-factor Authentication', new_profile_two_factor_auth_path, class: 'btn btn-success'
- if button_based_providers.any?
@@ -81,15 +85,16 @@
%p
Changing your username will change path to all personal projects!
%div
- = f.text_field :username, required: true, class: 'form-control'
+ .input-group
+ .input-group-addon
+ = "#{root_url}u/"
+ = f.text_field :username, required: true, class: 'form-control'
&nbsp;
.loading-gif.hide
%p
= icon('spinner spin')
Saving new username
- %p.light
- = user_url(@user)
- %div
+ .form-actions
= f.submit 'Save username', class: "btn btn-warning"
- if signup_enabled?
@@ -104,7 +109,8 @@
- rp = current_user.personal_projects.count
- unless rp.zero?
%li #{pluralize rp, 'personal project'} will be removed and cannot be restored
- = link_to 'Delete account', user_registration_path, data: { confirm: "REMOVE #{current_user.name}? Are you sure?" }, method: :delete, class: "btn btn-remove"
+ .form-actions
+ = link_to 'Delete account', user_registration_path, data: { confirm: "REMOVE #{current_user.name}? Are you sure?" }, method: :delete, class: "btn btn-remove"
- else
- if @user.solo_owned_groups.present?
%p
diff --git a/app/views/profiles/applications.html.haml b/app/views/profiles/applications.html.haml
index 2342936a5d5..0436c2213da 100644
--- a/app/views/profiles/applications.html.haml
+++ b/app/views/profiles/applications.html.haml
@@ -15,24 +15,25 @@
.pull-right
= link_to 'New Application', new_oauth_application_path, class: 'btn btn-success'
- if @applications.any?
- %table.table.table-striped
- %thead
- %tr
- %th Name
- %th Callback URL
- %th Clients
- %th
- %th
- %tbody
- - @applications.each do |application|
- %tr{:id => "application_#{application.id}"}
- %td= link_to application.name, oauth_application_path(application)
- %td
- - application.redirect_uri.split.each do |uri|
- %div= uri
- %td= application.access_tokens.count
- %td= link_to 'Edit', edit_oauth_application_path(application), class: 'btn btn-link btn-sm'
- %td= render 'doorkeeper/applications/delete_form', application: application
+ .table-holder
+ %table.table.table-striped
+ %thead
+ %tr
+ %th Name
+ %th Callback URL
+ %th Clients
+ %th
+ %th
+ %tbody
+ - @applications.each do |application|
+ %tr{:id => "application_#{application.id}"}
+ %td= link_to application.name, oauth_application_path(application)
+ %td
+ - application.redirect_uri.split.each do |uri|
+ %div= uri
+ %td= application.access_tokens.count
+ %td= link_to 'Edit', edit_oauth_application_path(application), class: 'btn btn-link btn-sm'
+ %td= render 'doorkeeper/applications/delete_form', application: application
.oauth-authorized-applications.prepend-top-20
- if user_oauth_applications?
@@ -40,29 +41,30 @@
Authorized applications
- if @authorized_tokens.any?
- %table.table.table-striped
- %thead
- %tr
- %th Name
- %th Authorized At
- %th Scope
- %th
- %tbody
- - @authorized_apps.each do |app|
- - token = app.authorized_tokens.order('created_at desc').first
- %tr{:id => "application_#{app.id}"}
- %td= app.name
- %td= token.created_at
- %td= token.scopes
- %td= render 'doorkeeper/authorized_applications/delete_form', application: app
- - @authorized_anonymous_tokens.each do |token|
+ .table-holder
+ %table.table.table-striped
+ %thead
%tr
- %td
- Anonymous
- %div.help-block
- %em Authorization was granted by entering your username and password in the application.
- %td= token.created_at
- %td= token.scopes
- %td= render 'doorkeeper/authorized_applications/delete_form', token: token
+ %th Name
+ %th Authorized At
+ %th Scope
+ %th
+ %tbody
+ - @authorized_apps.each do |app|
+ - token = app.authorized_tokens.order('created_at desc').first
+ %tr{:id => "application_#{app.id}"}
+ %td= app.name
+ %td= token.created_at
+ %td= token.scopes
+ %td= render 'doorkeeper/authorized_applications/delete_form', application: app
+ - @authorized_anonymous_tokens.each do |token|
+ %tr
+ %td
+ Anonymous
+ %div.help-block
+ %em Authorization was granted by entering your username and password in the application.
+ %td= token.created_at
+ %td= token.scopes
+ %td= render 'doorkeeper/authorized_applications/delete_form', token: token
- else
%p.light You don't have any authorized applications
diff --git a/app/views/profiles/keys/_form.html.haml b/app/views/profiles/keys/_form.html.haml
index b76a5b636ac..2a8800de60e 100644
--- a/app/views/profiles/keys/_form.html.haml
+++ b/app/views/profiles/keys/_form.html.haml
@@ -1,5 +1,5 @@
%div
- = form_for [:profile, @key], html: { class: 'form-horizontal' } do |f|
+ = form_for [:profile, @key], html: { class: 'form-horizontal js-requires-input' } do |f|
- if @key.errors.any?
.alert.alert-danger
%ul
@@ -9,12 +9,11 @@
.form-group
= f.label :key, class: 'control-label'
.col-sm-10
- = f.text_area :key, class: "form-control", rows: 8
+ = f.text_area :key, class: "form-control", rows: 8, autofocus: true, required: true
.form-group
= f.label :title, class: 'control-label'
- .col-sm-10= f.text_field :title, class: "form-control"
+ .col-sm-10= f.text_field :title, class: "form-control", required: true
.form-actions
= f.submit 'Add key', class: "btn btn-create"
= link_to "Cancel", profile_keys_path, class: "btn btn-cancel"
-
diff --git a/app/views/profiles/keys/_key_table.html.haml b/app/views/profiles/keys/_key_table.html.haml
index ef0075aad3b..8c9d546af4c 100644
--- a/app/views/profiles/keys/_key_table.html.haml
+++ b/app/views/profiles/keys/_key_table.html.haml
@@ -1,6 +1,6 @@
- is_admin = defined?(admin) ? true : false
-.panel.panel-default
- - if @keys.any?
+- if @keys.any?
+ .table-holder
%table.table
%thead.panel-heading
%tr
@@ -11,9 +11,9 @@
%tbody
- @keys.each do |key|
= render 'profiles/keys/key', key: key, is_admin: is_admin
- - else
- .nothing-here-block
- - if is_admin
- User has no ssh keys
- - else
- There are no SSH keys with access to your account.
+- else
+ .nothing-here-block
+ - if is_admin
+ User has no ssh keys
+ - else
+ There are no SSH keys with access to your account.
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index 14adba1c797..17a4195030e 100644
--- a/app/views/profiles/keys/index.html.haml
+++ b/app/views/profiles/keys/index.html.haml
@@ -3,7 +3,9 @@
.gray-content-block.top-block
.pull-right
- = link_to "Add SSH Key", new_profile_key_path, class: "btn btn-new"
+ = link_to new_profile_key_path, class: "btn btn-new" do
+ = icon('plus')
+ Add SSH Key
.oneline
Before you can add an SSH key you need to
= link_to "generate it.", help_page_path("ssh", "README")
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index 8eebd96b674..0bcadc965fa 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -50,12 +50,10 @@
Watch
%p You will receive notifications for any activity
- .form-actions
+ .gray-content-block
= f.submit 'Save changes', class: "btn btn-create"
-.clearfix
- %hr
-.row.all-notifications
+.row.all-notifications.prepend-top-default
.col-md-6
%p
You can also specify notification level per group or per project.
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index cc41d7dd813..877589dc390 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -54,4 +54,4 @@
.help-block
Choose what content you want to see on a project's home page.
.panel-footer
- = f.submit 'Save', class: 'btn btn-save'
+ = f.submit 'Save changes', class: 'btn btn-save'
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index ac7355dde1f..9459d8a6295 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -43,7 +43,7 @@
.form-group
= f.label :public_email, class: "control-label"
.col-sm-10
- = f.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email), {include_blank: 'Do not show in profile'}, class: "form-control"
+ = f.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email), {include_blank: 'Do not show on profile'}, class: "select2"
%span.help-block This email will be displayed on your public profile.
.form-group
= f.label :skype, class: "control-label"
@@ -96,8 +96,6 @@
= link_to 'Remove avatar', profile_avatar_path, data: { confirm: "Avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
- .row
- .col-md-7
- .form-group
- .col-sm-offset-2.col-sm-10
- = f.submit 'Save changes', class: "btn btn-success"
+ .form-actions
+ = f.submit 'Save changes', class: "btn btn-success"
+ = link_to "Cancel", user_path(current_user), class: "btn btn-cancel"
diff --git a/app/views/projects/_commit_button.html.haml b/app/views/projects/_commit_button.html.haml
index 35f7e7bb34b..2fd3d9e1be4 100644
--- a/app/views/projects/_commit_button.html.haml
+++ b/app/views/projects/_commit_button.html.haml
@@ -1,6 +1,4 @@
.form-actions
- .commit-button-annotation
- = button_tag 'Commit Changes',
- class: 'btn commit-btn js-commit-button btn-create'
+ = button_tag 'Commit Changes', class: 'btn commit-btn js-commit-button btn-create'
= link_to 'Cancel', cancel_path,
class: 'btn btn-cancel', data: {confirm: leave_edit_message}
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 8c0980369fd..c1669ac046b 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -1,5 +1,5 @@
- empty_repo = @project.empty_repo?
-.project-home-panel.clearfix{:class => ("empty-project" if empty_repo)}
+.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
@@ -12,23 +12,29 @@
Forked from
= link_to project_path(forked_from_project) do
= forked_from_project.namespace.try(:name)
+ .cover-controls.left
+ .visibility-level-label.has_tooltip{title: project_visibility_level_description(@project.visibility_level), data: { container: 'body' } }
+ = visibility_level_icon(@project.visibility_level, fw: false)
+ = visibility_level_label(@project.visibility_level)
-
+ .cover-controls
+ - if can?(current_user, :admin_project, @project)
+ = link_to edit_project_path(@project), class: 'btn btn-gray' do
+ = icon('pencil')
+ - if current_user
+ &nbsp;
+ = link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), class: 'btn btn-gray' do
+ = icon('rss')
.project-repo-buttons
.split-one
= render 'projects/buttons/star'
+ = render 'projects/buttons/fork'
- - unless empty_repo
- = render 'projects/buttons/fork'
-
= render "shared/clone_panel"
- .split-repo-buttons
- - unless 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', rel: 'nofollow' do
- = icon('download fw')
-
+
+ .split-repo-buttons
+ = render "projects/buttons/download"
= render 'projects/buttons/dropdown'
+
= render 'projects/buttons/notifications'
-
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 7b21095ea3e..8218cf11201 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -5,7 +5,7 @@
%a.js-md-write-button(href="#md-write-holder" tabindex="-1")
Write
%li
- %a.js-md-preview-button(href="md-preview-holder" tabindex="-1")
+ %a.js-md-preview-button(href="#md-preview-holder" tabindex="-1")
Preview
- if defined?(referenced_users) && referenced_users
diff --git a/app/views/projects/_readme.html.haml b/app/views/projects/_readme.html.haml
index b5ef0aca540..d1191928d4f 100644
--- a/app/views/projects/_readme.html.haml
+++ b/app/views/projects/_readme.html.haml
@@ -7,15 +7,16 @@
= cache(readme_cache_key) do
= render_readme(readme)
- else
- %h3.page-title
- This project does not have README yet
- - if can?(current_user, :push_code, @project)
- %p.slead
- A
- %code README
- file contains information about other files in a repository and is commonly
- distributed with computer software, forming part of its documentation.
- %br
- We recommend you to
- = link_to "add README", new_readme_path, class: 'underlined-link'
- file to the repository and GitLab will render it here instead of this message.
+ .gray-content-block.second-block.center
+ %h3.page-title
+ This project does not have README yet
+ - if can?(current_user, :push_code, @project)
+ %p
+ A
+ %code README
+ file contains information about other files in a repository and is commonly
+ distributed with computer software, forming part of its documentation.
+ %p
+ We recommend you to
+ = link_to "add README", new_readme_path, class: 'underlined-link'
+ file to the repository and GitLab will render it here instead of this message.
diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml
index 63ebfc9381f..7e6301abde8 100644
--- a/app/views/projects/_zen.html.haml
+++ b/app/views/projects/_zen.html.haml
@@ -2,9 +2,12 @@
%input#zen-toggle-comment.zen-toggle-comment(tabindex="-1" type="checkbox")
.zen-backdrop
- classes << ' js-gfm-input markdown-area'
- = f.text_area attr, class: classes, placeholder: ''
+ - if defined?(f) && f
+ = f.text_area attr, class: classes, placeholder: ''
+ - else
+ = text_area_tag attr, nil, class: classes, placeholder: ''
%a.zen-enter-link(tabindex="-1" href="#")
- %i.fa.fa-expand
+ = icon('expand')
Edit in fullscreen
%a.zen-leave-link(href="#")
- %i.fa.fa-compress
+ = icon('compress')
diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index 6518c4173e1..8d9ec068a43 100644
--- a/app/views/projects/blame/show.html.haml
+++ b/app/views/projects/blame/show.html.haml
@@ -6,7 +6,7 @@
#tree-holder.tree-holder
.file-holder
.file-title
- %i.fa.fa-file
+ = blob_icon @blob.mode, @blob.name
%strong
= @path
%small= number_to_human_size @blob.size
@@ -43,4 +43,3 @@
- blame_group[:lines].each do |line|
:erb
<%= highlight(@blob.name, line, nowrap: true, continue: true).html_safe %>
-
diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml
index 373b3a0c5b0..ba3e0c3c590 100644
--- a/app/views/projects/blob/_actions.html.haml
+++ b/app/views/projects/blob/_actions.html.haml
@@ -19,4 +19,4 @@
- if allowed_tree_edit?
.btn-group{ role: "group" }
%button.btn.btn-default{ 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } Replace
- %button.btn.btn-remove{ 'data-target' => '#modal-remove-blob', 'data-toggle' => 'modal' } Remove
+ %button.btn.btn-remove{ 'data-target' => '#modal-remove-blob', 'data-toggle' => 'modal' } Delete
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index f1ad0c3c403..333f5d470ed 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -1,19 +1,19 @@
-.file-holder.file
- .file-title
+.file-holder.file.append-bottom-default
+ .file-title.clearfix
.editor-ref
- %i.fa.fa-code-fork
+ = icon('code-fork')
= ref
%span.editor-file-name
- - if @path
- %span.monospace
- = @path
+ = @path
- - if current_action?(:new) || current_action?(:create)
+ - if current_action?(:new) || current_action?(:create)
+ %span.editor-file-name
\/
- = text_field_tag 'file_name', params[:file_name], placeholder: "File name",
- required: true, class: 'form-control new-file-name js-quick-submit'
- .pull-right
- = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control'
+ = text_field_tag 'file_name', params[:file_name], placeholder: "File name",
+ required: true, class: 'form-control new-file-name js-quick-submit'
+
+ .pull-right
+ = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2'
.file-content.code
%pre.js-edit-mode-pane#editor
diff --git a/app/views/projects/blob/_new_dir.html.haml b/app/views/projects/blob/_new_dir.html.haml
index a0fc8bbd752..fc6c9f5fd09 100644
--- a/app/views/projects/blob/_new_dir.html.haml
+++ b/app/views/projects/blob/_new_dir.html.haml
@@ -5,21 +5,17 @@
%a.close{href: "#", "data-dismiss" => "modal"} ×
%h3.page-title Create New Directory
.modal-body
- = form_tag namespace_project_create_dir_path(@project.namespace, @project, @id), method: :post, remote: false, id: 'dir-create-form', class: 'form-horizontal' do
+ = form_tag namespace_project_create_dir_path(@project.namespace, @project, @id), method: :post, remote: false, class: 'form-horizontal js-create-dir-form js-requires-input' do
.form-group
- = label_tag :dir_name, 'Directory Name', class: 'control-label'
+ = label_tag :dir_name, 'Directory name', class: 'control-label'
.col-sm-10
- = text_field_tag :dir_name, params[:dir_name], placeholder: "Directory name", required: true, class: 'form-control'
- = render 'shared/commit_message_container', params: params, placeholder: ''
- - unless @project.empty_repo?
- .form-group
- = label_tag :branch_name, 'Branch', class: 'control-label'
- .col-sm-10
- = text_field_tag 'new_branch', @ref, class: "form-control"
- .form-group
- .col-sm-offset-2.col-sm-10
- = submit_tag "Create directory", class: 'btn btn-primary btn-create'
- = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
+ = text_field_tag :dir_name, params[:dir_name], required: true, class: 'form-control'
+
+ = render 'shared/new_commit_form', placeholder: "Add new directory"
+
+ .form-actions
+ = submit_tag "Create directory", class: 'btn btn-create'
+ = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
:javascript
- disableButtonIfAnyEmptyField($("#dir-create-form"), ".form-control", ".btn-create");
+ new NewCommitForm($('.js-create-dir-form'))
diff --git a/app/views/projects/blob/_remove.html.haml b/app/views/projects/blob/_remove.html.haml
index cae5ff01099..1cf19a7d3db 100644
--- a/app/views/projects/blob/_remove.html.haml
+++ b/app/views/projects/blob/_remove.html.haml
@@ -3,16 +3,16 @@
.modal-content
.modal-header
%a.close{href: "#", "data-dismiss" => "modal"} ×
- %h3.page-title Remove #{@blob.name}
- %p.light
- From branch
- %strong= @ref
+ %h3.page-title Delete #{@blob.name}
.modal-body
- = form_tag namespace_project_blob_path(@project.namespace, @project, @id), method: :delete, class: 'form-horizontal js-requires-input' do
- = render 'shared/commit_message_container', params: params,
- placeholder: 'Removed this file because...'
+ = form_tag namespace_project_blob_path(@project.namespace, @project, @id), method: :delete, class: 'form-horizontal js-replace-blob-form js-requires-input' do
+ = render 'shared/new_commit_form', placeholder: "Delete #{@blob.name}"
+
.form-group
.col-sm-offset-2.col-sm-10
- = button_tag 'Remove file', class: 'btn btn-remove btn-remove-file'
+ = button_tag 'Delete file', class: 'btn btn-remove btn-remove-file'
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
+
+:javascript
+ new NewCommitForm($('.js-replace-blob-form'))
diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml
index a1c54e731f0..ecc90a30e78 100644
--- a/app/views/projects/blob/_upload.html.haml
+++ b/app/views/projects/blob/_upload.html.haml
@@ -5,7 +5,7 @@
%a.close{href: "#", "data-dismiss" => "modal"} ×
%h3.page-title #{title}
.modal-body
- = form_tag form_path, method: method, class: 'blob-file-upload-form-js form-horizontal' do
+ = form_tag form_path, method: method, class: 'js-upload-blob-form form-horizontal' do
.dropzone
.dropzone-previews.blob-upload-dropzone-previews
%p.dz-message.light
@@ -13,19 +13,14 @@
= link_to 'click to upload', '#', class: "markdown-selector"
%br
.dropzone-alerts{class: "alert alert-danger data", style: "display:none"}
- = render 'shared/commit_message_container', params: params,
- placeholder: placeholder
- - unless @project.empty_repo?
- .form-group.branch
- = label_tag 'branch', class: 'control-label' do
- Branch
- .col-sm-10
- = text_field_tag 'new_branch', @ref, class: "form-control"
- .form-group
- .col-sm-offset-2.col-sm-10
- = button_tag button_title, class: 'btn btn-small btn-primary btn-upload-file', id: 'submit-all'
- = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
+
+ = render 'shared/new_commit_form', placeholder: placeholder
+
+ .form-actions
+ = button_tag button_title, class: 'btn btn-small btn-create btn-upload-file', id: 'submit-all'
+ = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
:javascript
- disableButtonIfEmptyField($('.blob-file-upload-form-js').find('#commit_message'), '.btn-upload-file');
- new BlobFileDropzone($('.blob-file-upload-form-js'), '#{method}');
+ disableButtonIfEmptyField($('.js-upload-blob-form').find('.js-commit-message'), '.btn-upload-file');
+ new BlobFileDropzone($('.js-upload-blob-form'), '#{method}');
+ new NewCommitForm($('.js-upload-blob-form'))
diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml
index a811adc5094..a47fe7ede80 100644
--- a/app/views/projects/blob/edit.html.haml
+++ b/app/views/projects/blob/edit.html.haml
@@ -5,23 +5,17 @@
%ul.center-top-menu.no-bottom.js-edit-mode
%li.active
= link_to '#editor' do
- %i.fa.fa-edit
- Edit file
+ = icon('edit')
+ Edit File
%li
= link_to '#preview', 'data-preview-url' => namespace_project_preview_blob_path(@project.namespace, @project, @id) do
- %i.fa.fa-eye
+ = icon('eye')
= editing_preview_title(@blob.name)
- = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-requires-input') do
+ = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-requires-input js-edit-blob-form') do
= render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data
- = render 'shared/commit_message_container', params: params, placeholder: "Update #{@blob.name}"
-
- .form-group.branch
- = label_tag 'branch', class: 'control-label' do
- Branch
- .col-sm-10
- = text_field_tag 'new_branch', @ref, class: "form-control"
+ = render 'shared/new_commit_form', placeholder: "Update #{@blob.name}"
= hidden_field_tag 'last_commit', @last_commit
= hidden_field_tag 'content', '', id: "file-content"
@@ -30,3 +24,4 @@
:javascript
blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", "#{@blob.language.try(:ace_mode)}")
+ new NewCommitForm($('.js-edit-blob-form'))
diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml
index 7975137c37f..167fa615182 100644
--- a/app/views/projects/blob/new.html.haml
+++ b/app/views/projects/blob/new.html.haml
@@ -1,21 +1,13 @@
- page_title "New File", @path.presence, @ref
= render "header_title"
-.gray-content-block.top-block
- Create a new file
+%h3.page-title
+ New File
.file-editor
- = form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal form-new-file js-requires-input') do
+ = form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal js-new-blob-form js-requires-input') do
= render 'projects/blob/editor', ref: @ref
- = render 'shared/commit_message_container', params: params,
- placeholder: 'Add new file'
-
- - unless @project.empty_repo?
- .form-group.branch
- = label_tag 'branch', class: 'control-label' do
- Branch
- .col-sm-10
- = text_field_tag 'new_branch', @ref, class: "form-control js-quick-submit"
+ = render 'shared/new_commit_form', placeholder: "Add new file"
= hidden_field_tag 'content', '', id: 'file-content'
= render 'projects/commit_button', ref: @ref,
@@ -23,3 +15,4 @@
:javascript
blob = new NewBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", null)
+ new NewCommitForm($('.js-new-blob-form'))
diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml
index f52b89f6921..b7276868ce6 100644
--- a/app/views/projects/blob/show.html.haml
+++ b/app/views/projects/blob/show.html.haml
@@ -10,6 +10,4 @@
= render 'projects/blob/remove'
- title = "Replace #{@blob.name}"
- = render 'projects/blob/upload', title: title, placeholder: title,
- button_title: 'Replace file', form_path: namespace_project_update_blob_path(@project.namespace, @project, @id),
- method: :put
+ = render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index cc0ec9483d2..3f95e2a1bf6 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -26,7 +26,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', method: :delete, data: { confirm: 'Removed branch cannot be restored. Are you sure?'}, remote: true do
+ = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-grouped btn-xs btn-remove remove-row', method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?" }, remote: true do
= icon("trash-o")
- if commit
diff --git a/app/views/projects/branches/_commit.html.haml b/app/views/projects/branches/_commit.html.haml
index 22d77dda938..9fe65cbb104 100644
--- a/app/views/projects/branches/_commit.html.haml
+++ b/app/views/projects/branches/_commit.html.haml
@@ -1,5 +1,5 @@
.branch-commit
- = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-id"
+ = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-id monospace"
&middot;
%span.str-truncated
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index 03ade02a0c8..204def60794 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -5,7 +5,7 @@
.pull-right
- if can? current_user, :push_code, @project
= link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do
- %i.fa.fa-add-sign
+ = icon('plus')
New branch
&nbsp;
.dropdown.inline
diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml
index f5577042ca4..31943a2407a 100644
--- a/app/views/projects/branches/new.html.haml
+++ b/app/views/projects/branches/new.html.haml
@@ -6,25 +6,27 @@
%button{ type: "button", class: "close", "data-dismiss" => "alert"} &times;
= @error
%h3.page-title
- %i.fa.fa-code-fork
- New branch
+ New Branch
+%hr
+
= form_tag namespace_project_branches_path, method: :post, id: "new-branch-form", class: "form-horizontal js-requires-input" do
.form-group
- = label_tag :branch_name, 'Name for new branch', class: 'control-label'
+ = label_tag :branch_name, nil, class: 'control-label'
.col-sm-10
- = text_field_tag :branch_name, params[:branch_name], placeholder: 'enter new branch name', required: true, tabindex: 1, class: 'form-control'
+ = text_field_tag :branch_name, params[:branch_name], required: true, tabindex: 1, autofocus: true, class: 'form-control'
.form-group
= label_tag :ref, 'Create from', class: 'control-label'
.col-sm-10
- = text_field_tag :ref, params[:ref], placeholder: 'existing branch name, tag or commit SHA', required: true, tabindex: 2, class: 'form-control'
+ = text_field_tag :ref, params[:ref] || @project.default_branch, required: true, tabindex: 2, class: 'form-control'
+ .help-block Existing branch name, tag, or commit SHA
.form-actions
= button_tag 'Create branch', class: 'btn btn-create', tabindex: 3
= link_to 'Cancel', namespace_project_branches_path(@project.namespace, @project), class: 'btn btn-cancel'
:javascript
- var availableTags = #{@project.repository.ref_names.to_json};
+ var availableRefs = #{@project.repository.ref_names.to_json};
$("#ref").autocomplete({
- source: availableTags,
+ source: availableRefs,
minLength: 1
});
diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml
new file mode 100644
index 00000000000..14ee2263b7d
--- /dev/null
+++ b/app/views/projects/buttons/_download.html.haml
@@ -0,0 +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', rel: 'nofollow', title: "Download ZIP" do
+ = icon('download')
diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
index 18cae8ef6d3..b277b765b6b 100644
--- a/app/views/projects/buttons/_dropdown.html.haml
+++ b/app/views/projects/buttons/_dropdown.html.haml
@@ -32,5 +32,3 @@
= link_to new_namespace_project_tag_path(@project.namespace, @project) do
= icon('tags fw')
New tag
-
-
diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml
index 8f2f631eb7d..2d3abf09051 100644
--- a/app/views/projects/buttons/_fork.html.haml
+++ b/app/views/projects/buttons/_fork.html.haml
@@ -1,12 +1,13 @@
-- 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' do
- = icon('code-fork fw')
- Fork
- %span.count
- = @project.forks_count
- - else
- = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn' do
- = icon('code-fork fw')
- %span.count
- = @project.forks_count
+- 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
+ = icon('code-fork fw')
+ Fork
+ %span.count
+ = @project.forks_count
+ - else
+ = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn has_tooltip' do
+ = icon('code-fork fw')
+ %span.count
+ = @project.forks_count
diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml
index 06583902035..41a3ec6d90f 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', method: :post, remote: true 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
= icon('star fw')
%span.count
= @project.star_count
diff --git a/app/views/projects/commit/_ci_menu.html.haml b/app/views/projects/commit/_ci_menu.html.haml
index c73ba74f5ef..76dc87a8824 100644
--- a/app/views/projects/commit/_ci_menu.html.haml
+++ b/app/views/projects/commit/_ci_menu.html.haml
@@ -1,4 +1,4 @@
-%ul.center-top-menu.commit-ci-menu
+%ul.center-top-menu.no-top.no-bottom.commit-ci-menu
= nav_link(path: 'commit#show') do
= link_to namespace_project_commit_path(@project.namespace, @project, @commit.id) do
Changes
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 776768537d0..d8bfe6a07ac 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -13,8 +13,9 @@
- unless @commit.parents.length > 1
%li= link_to "Email Patches", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch)
%li= link_to "Plain Diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff)
- = link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-primary btn-grouped" do
- %span Browse Code »
+ = link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-grouped" do
+ = icon('files-o')
+ Browse Files
%div
%p
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index 85e203cbe57..069b8b1f169 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -1,6 +1,9 @@
- page_title "#{@commit.title} (#{@commit.short_id})", "Commits"
= render "projects/commits/header_title"
= render "commit_box"
-= render "ci_menu" if @ci_commit
+- if @ci_commit
+ = render "ci_menu"
+- else
+ %div.block-connector
= render "projects/diffs/diffs", diffs: @diffs, project: @project
= render "projects/notes/notes_with_form"
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 805be332e64..2e489a0a4d5 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -20,8 +20,8 @@
- if ci_commit
= render_ci_status(ci_commit)
&nbsp;
- = clipboard_button
- = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id", data: {clipboard_text: commit.id}
+ = clipboard_button(clipboard_text: commit.id)
+ = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
.notes_count
- if note_count > 0
diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml
index 39755efd2fd..51088a7dea8 100644
--- a/app/views/projects/compare/show.html.haml
+++ b/app/views/projects/compare/show.html.haml
@@ -7,11 +7,11 @@
= render "form"
- if @commits.present?
- .prepend-top-20
+ .prepend-top-default
= render "projects/commits/commit_list"
= render "projects/diffs/diffs", diffs: @diffs, project: @project
- else
- .light-well.prepend-top-20
+ .light-well.prepend-top-default
.center
%h4
There isn't anything to compare.
diff --git a/app/views/projects/deploy_keys/_form.html.haml b/app/views/projects/deploy_keys/_form.html.haml
index 91675b3738e..5e182af2669 100644
--- a/app/views/projects/deploy_keys/_form.html.haml
+++ b/app/views/projects/deploy_keys/_form.html.haml
@@ -1,5 +1,5 @@
%div
- = form_for [@project.namespace.becomes(Namespace), @project, @key], url: namespace_project_deploy_keys_path, html: { class: 'deploy-key-form form-horizontal' } do |f|
+ = form_for [@project.namespace.becomes(Namespace), @project, @key], url: namespace_project_deploy_keys_path, html: { class: 'deploy-key-form form-horizontal js-requires-input' } do |f|
-if @key.errors.any?
.alert.alert-danger
%ul
@@ -8,16 +8,15 @@
.form-group
= f.label :title, class: "control-label"
- .col-sm-10= f.text_field :title, class: 'form-control'
+ .col-sm-10= f.text_field :title, class: 'form-control', autofocus: true, required: true
.form-group
= f.label :key, class: "control-label"
.col-sm-10
%p.light
Paste a machine public key here. Read more about how to generate it
= link_to "here", help_page_path("ssh", "README")
- = f.text_area :key, class: "form-control thin_area", rows: 5
+ = f.text_area :key, class: "form-control thin_area", rows: 5, required: true
.form-actions
- = f.submit 'Create', class: "btn-create btn"
+ = f.submit 'Create Deploy Key', class: "btn-create btn"
= link_to "Cancel", namespace_project_deploy_keys_path(@project.namespace, @project), class: "btn btn-cancel"
-
diff --git a/app/views/projects/deploy_keys/new.html.haml b/app/views/projects/deploy_keys/new.html.haml
index 01c810aee18..01fab3008a7 100644
--- a/app/views/projects/deploy_keys/new.html.haml
+++ b/app/views/projects/deploy_keys/new.html.haml
@@ -1,5 +1,5 @@
- page_title "New Deploy Key"
-%h3.page-title New Deploy key
+%h3.page-title New Deploy Key
%hr
= render 'form'
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index 416fb4da071..f9d661d59d2 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -3,7 +3,7 @@
- diff_files = safe_diff_files(diffs)
-.gray-content-block.second-block.oneline-block
+.gray-content-block.middle-block.oneline-block
.inline-parallel-buttons
.btn-group
= inline_diff_btn
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index c745b4e69bf..b3392d00e01 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -2,19 +2,27 @@
.diff-header{id: "file-path-#{hexdigest(diff_file.file_path)}"}
- if diff_file.diff.submodule?
%span
+ = icon('archive fw')
- submodule_item = project.repository.blob_at(@commit.id, diff_file.file_path)
- = submodule_link(submodule_item, @commit.id, project.repository)
+ %strong
+ = submodule_link(submodule_item, @commit.id, project.repository)
- else
%span
+ = blob_icon blob.mode, blob.name
+ = link_to "#diff-#{i}" do
+ %strong
+ = diff_file.new_path
+
- if diff_file.deleted_file
- = "#{diff_file.old_path} deleted"
+ deleted
- elsif diff_file.renamed_file
- = "#{diff_file.old_path} renamed to #{diff_file.new_path}"
- - else
- = diff_file.new_path
+ renamed from
+ %strong
+ = diff_file.old_path
- if diff_file.mode_changed?
- %span.file-mode= "#{diff_file.diff.a_mode} → #{diff_file.diff.b_mode}"
+ %small
+ = "#{diff_file.diff.a_mode} → #{diff_file.diff.b_mode}"
.diff-controls
- if blob.text?
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 3ebc175648e..b28ada909b7 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -14,7 +14,7 @@
= f.label :name, class: 'control-label' do
Project name
.col-sm-10
- = f.text_field :name, placeholder: "Example Project", class: "form-control", id: "project_name_edit"
+ = f.text_field :name, class: "form-control", id: "project_name_edit"
.form-group
@@ -22,7 +22,7 @@
Project description
%span.light (optional)
.col-sm-10
- = f.text_area :description, placeholder: "Awesome project", class: "form-control", rows: 3, maxlength: 250
+ = f.text_area :description, class: "form-control", rows: 3, maxlength: 250
- if @project.repository.exists? && @project.repository.branch_names.any?
.form-group
@@ -35,7 +35,7 @@
.form-group
= f.label :tag_list, "Tags", class: 'control-label'
.col-sm-10
- = f.text_field :tag_list, maxlength: 2000, class: "form-control"
+ = f.text_field :tag_list, value: @project.tag_list.to_s, maxlength: 2000, class: "form-control"
%p.help-block Separate tags with commas.
%fieldset.features
@@ -130,9 +130,11 @@
The project can be committed to.
%br
%strong Once active this project shows up in the search and on the dashboard.
- = link_to 'Unarchive', unarchive_namespace_project_path(@project.namespace, @project),
- data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be committed to again." },
- method: :post, class: "btn btn-success"
+
+ .form-actions
+ = link_to 'Unarchive project', unarchive_namespace_project_path(@project.namespace, @project),
+ data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be committed to again." },
+ method: :post, class: "btn btn-success"
- else
.panel.panel-warning
.panel-heading
@@ -144,9 +146,11 @@
It is hidden from the dashboard and doesn't show up in searches.
%br
%strong Archived projects cannot be committed to!
- = link_to 'Archive', archive_namespace_project_path(@project.namespace, @project),
- data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." },
- method: :post, class: "btn btn-warning"
+
+ .form-actions
+ = link_to 'Archive project', archive_namespace_project_path(@project.namespace, @project),
+ data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." },
+ method: :post, class: "btn btn-warning"
- else
.nothing-here-block Only the project owner can archive a project
@@ -160,7 +164,7 @@
Project name
.col-sm-9
.form-group
- = f.text_field :name, placeholder: "Example Project", class: "form-control"
+ = f.text_field :name, class: "form-control"
.form-group
= f.label :path, class: 'control-label' do
%span Path
@@ -170,12 +174,11 @@
.input-group-addon
#{URI.join(root_url, @project.namespace.path)}/
= f.text_field :path, class: 'form-control'
- %span.input-group-addon .git
%ul
%li Be careful. Renaming a project's repository can have unintended side effects.
%li You will need to update your local repositories to point to the new location.
.form-actions
- = f.submit 'Rename', class: "btn btn-warning"
+ = f.submit 'Rename project', class: "btn btn-warning"
- if can?(current_user, :change_namespace, @project)
.panel.panel-default.panel.panel-danger
@@ -194,7 +197,7 @@
%li You can only transfer the project to namespaces you manage.
%li You will need to update your local repositories to point to the new location.
.form-actions
- = f.submit 'Transfer', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) }
+ = f.submit 'Transfer project', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) }
- else
.nothing-here-block Only the project owner can transfer a project
@@ -209,7 +212,8 @@
#{link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project)}.
%br
%strong Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source.
- = button_to 'Remove fork relationship', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_fork_project_message(@project) }
+ .form-actions
+ = button_to 'Remove fork relationship', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_fork_project_message(@project) }
- else
.nothing-here-block Only the project owner can remove the fork relationship.
@@ -222,8 +226,8 @@
Removing the project will delete its repository and all related resources including issues, merge requests etc.
%br
%strong Removed projects cannot be restored!
-
- = button_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) }
+ .form-actions
+ = button_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) }
- else
.nothing-here-block Only the project owner can remove a project.
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index c3858e78cad..950ab33825e 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -5,17 +5,16 @@
= render "home_panel"
-.gray-content-block.center
+.gray-content-block.second-block.center
%h3.page-title
The repository for this project is empty
- - if can?(current_user, :download_code, @project)
+ - if can?(current_user, :push_code, @project)
%p
If you already have files you can push them using command line instructions below.
- %br
- - if can?(current_user, :push_code, @project)
- Otherwise you can start with
- = link_to "adding README", new_readme_path, class: 'underlined-link'
- file to this project.
+ %p
+ Otherwise you can start with
+ = link_to "adding README", new_readme_path, class: 'underlined-link'
+ file to this project.
- if can?(current_user, :download_code, @project)
.prepend-top-20
diff --git a/app/views/projects/imports/new.html.haml b/app/views/projects/imports/new.html.haml
index 92a87690c54..6027fb23360 100644
--- a/app/views/projects/imports/new.html.haml
+++ b/app/views/projects/imports/new.html.haml
@@ -1,22 +1,19 @@
- page_title "Import repository"
%h3.page-title
- - if @project.import_failed?
- Import failed. Retry?
- - else
- Import repository
+ Import repository
%hr
+- if @project.import_failed?
+ .panel.panel-danger
+ .panel-heading The repository could not be imported.
+ .panel-body
+ %pre
+ :preserve
+ #{@project.import_error.try(:strip)}
+
= form_for @project, url: namespace_project_import_path(@project.namespace, @project), method: :post, html: { class: 'form-horizontal' } do |f|
- .form-group.import-url-data
- = f.label :import_url, class: 'control-label' do
- %span Import existing git repo
- .col-sm-10
- = f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git'
- .well.prepend-top-20
- This URL must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git.
- %br
- The import will time out after 4 minutes. For big repositories, use a clone/push combination.
- For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/importing/migrating_from_svn.html"}
+ = render "shared/import_form", f: f
+
.form-actions
= f.submit 'Start import', class: "btn btn-create", tabindex: 4
diff --git a/app/views/projects/imports/show.html.haml b/app/views/projects/imports/show.html.haml
index 06886d215a3..c0d1ce0d120 100644
--- a/app/views/projects/imports/show.html.haml
+++ b/app/views/projects/imports/show.html.haml
@@ -8,7 +8,7 @@
- else
Import in progress.
- unless @project.forked?
- %p.monospace git clone --bare #{hidden_pass_url(@project.import_url)}
+ %p.monospace git clone --bare #{@project.safe_import_url}
%p Please wait while we import the repository for you. Refresh at will.
:javascript
new ProjectImport();
diff --git a/app/views/projects/issues/_closed_by_box.html.haml b/app/views/projects/issues/_closed_by_box.html.haml
index aef352029d0..917d5181689 100644
--- a/app/views/projects/issues/_closed_by_box.html.haml
+++ b/app/views/projects/issues/_closed_by_box.html.haml
@@ -1,3 +1,3 @@
.issue-closed-by-widget
= icon('check')
- This issue will be closed automatically when merge request #{gfm(merge_requests_sentence(@closed_by_merge_requests.sort))} is accepted.
+ This issue will be closed automatically when merge request #{gfm(merge_requests_sentence(@closed_by_merge_requests))} is accepted.
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index c5fd863ae99..b5f522f2079 100644
--- a/app/views/projects/issues/_discussion.html.haml
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -7,7 +7,7 @@
= render 'shared/show_aside'
-.gray-content-block.second-block
+.gray-content-block.second-block.oneline-block
.row
.col-md-9
.votes-holder.pull-right
@@ -18,9 +18,9 @@
= link_to_member(@project, participant, name: false, size: 24)
.col-md-3
.input-group.cross-project-reference
- %span.slead.has_tooltip{title: 'Cross-project reference'}
+ %span#cross-project-reference.slead.has_tooltip{title: 'Cross-project reference'}
= cross_project_reference(@project, @issue)
- = clipboard_button
+ = clipboard_button(clipboard_target: '#cross-project-reference')
.row
%section.col-md-9
@@ -29,10 +29,3 @@
.issuable-affix
.context
= render 'shared/issuable/context', issuable: @issue
-
- - if @issue.labels.any?
- .issuable-context-title
- %label Labels
- .issue-show-labels
- - @issue.labels.each do |label|
- = link_to_label(label)
diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml
index f39bb7d2574..6588d9bdbe1 100644
--- a/app/views/projects/issues/_form.html.haml
+++ b/app/views/projects/issues/_form.html.haml
@@ -1,9 +1,5 @@
-%div.issue-form-holder
- %h3.page-title= @issue.new_record? ? "Create Issue" : "Edit Issue ##{@issue.iid}"
- %hr
-
- = form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form gfm-form' } do |f|
- = render 'shared/issuable/form', f: f, issuable: @issue
+= form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form gfm-form js-requires-input' } do |f|
+ = render 'shared/issuable/form', f: f, issuable: @issue
:javascript
$('.assign-to-me-link').on('click', function(e){
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 55ce912829d..1eb71990e55 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -6,37 +6,39 @@
.issue-title
%span.issue-title-text
= link_to_gfm issue.title, issue_path(issue), class: "row_title"
- .issue-labels
- - issue.labels.each do |label|
- = link_to_label(label, project: issue.project)
.pull-right.light
- if issue.closed?
%span
CLOSED
- if issue.assignee
- = link_to_member(@project, issue.assignee, name: false)
+ = link_to_member(@project, issue.assignee, name: false, title: "Assigned to :name")
- note_count = issue.notes.user.count
- if note_count > 0
&nbsp;
- %span
- %i.fa.fa-comments
+ = link_to issue_path(issue) + "#notes" do
+ = icon('comments')
= note_count
- else
&nbsp;
- %span.issue-no-comments
- %i.fa.fa-comments
+ = link_to issue_path(issue) + "#notes", class: "issue-no-comments" do
+ = icon('comments')
= 0
.issue-info
- = "#{issue.to_reference} opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')} by #{link_to_member(@project, issue.author, avatar: false)}".html_safe
- - if issue.votes_count > 0
- = render 'votes/votes_inline', votable: issue
+ #{issue.to_reference} &middot;
+ opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')}
+ by #{link_to_member(@project, issue.author, avatar: false)}
- if issue.milestone
&nbsp;
- %span
- %i.fa.fa-clock-o
+ = link_to namespace_project_issues_path(issue.project.namespace, issue.project, milestone_title: issue.milestone.title) do
+ = icon('clock-o')
= issue.milestone.title
+ - if issue.labels.any?
+ &nbsp;
+ - issue.labels.each do |label|
+ = link_to_label(label, project: issue.project)
- if issue.tasks?
+ &nbsp;
%span.task-status
= issue.task_status
diff --git a/app/views/projects/issues/edit.html.haml b/app/views/projects/issues/edit.html.haml
index 53b6f0879c9..20216297d25 100644
--- a/app/views/projects/issues/edit.html.haml
+++ b/app/views/projects/issues/edit.html.haml
@@ -1,2 +1,8 @@
- page_title "Edit", "#{@issue.title} (##{@issue.iid})", "Issues"
+= render "header_title"
+
+%h3.page-title
+ Edit Issue ##{@issue.iid}
+%hr
+
= render "form"
diff --git a/app/views/projects/issues/new.html.haml b/app/views/projects/issues/new.html.haml
index 153447baa1b..b317a0c1cf4 100644
--- a/app/views/projects/issues/new.html.haml
+++ b/app/views/projects/issues/new.html.haml
@@ -1,4 +1,8 @@
- page_title "New Issue"
= render "header_title"
+%h3.page-title
+ New Issue
+%hr
+
= render "form"
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index f01bf2505da..e2de17cee6d 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -11,7 +11,8 @@
Open
%span.issue-id Issue ##{@issue.iid}
%span.creator
- &middot; created by #{link_to_member(@project, @issue.author, size: 24)}
+ &middot;
+ opened by #{link_to_member(@project, @issue.author, size: 24)}
&middot;
= time_ago_with_tooltip(@issue.created_at, placement: 'bottom', html_class: 'issue_created_ago')
- if @issue.updated_at != @issue.created_at
diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml
index 4cf13492e99..5ce2a7b985d 100644
--- a/app/views/projects/labels/_form.html.haml
+++ b/app/views/projects/labels/_form.html.haml
@@ -10,9 +10,9 @@
.form-group
= f.label :title, class: 'control-label'
.col-sm-10
- = f.text_field :title, class: "form-control js-quick-submit", required: true
+ = f.text_field :title, class: "form-control js-quick-submit", required: true, autofocus: true
.form-group
- = f.label :color, "Background Color", class: 'control-label'
+ = f.label :color, "Background color", class: 'control-label'
.col-sm-10
.input-group
.input-group-addon.label-color-preview &nbsp;
@@ -28,6 +28,8 @@
&nbsp;
.form-actions
- = f.submit 'Save', class: 'btn btn-save js-save-button'
+ - if @label.persisted?
+ = f.submit 'Save changes', class: 'btn btn-save js-save-button'
+ - else
+ = f.submit 'Create Label', class: 'btn btn-create js-save-button'
= link_to "Cancel", namespace_project_labels_path(@project.namespace, @project), class: 'btn btn-cancel'
-
diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml
index c6ebfa281a1..b70a9fc9fe5 100644
--- a/app/views/projects/labels/_label.html.haml
+++ b/app/views/projects/labels/_label.html.haml
@@ -7,4 +7,4 @@
- if can? current_user, :admin_label, @project
= link_to 'Edit', edit_namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-sm'
- = link_to 'Remove', namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"}
+ = link_to 'Delete', namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"}
diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml
index bc4ab0ca27c..675a805e12f 100644
--- a/app/views/projects/labels/edit.html.haml
+++ b/app/views/projects/labels/edit.html.haml
@@ -1,11 +1,7 @@
- page_title "Edit", @label.name, "Labels"
= render "header_title"
-%h3
- Edit label
- %span.light #{@label.name}
-.back-link
- = link_to namespace_project_labels_path(@project.namespace, @project) do
- &larr; To labels list
+%h3.page-title
+ Edit Label
%hr
= render 'form'
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index fb784ee5f4f..9081bcfe9b3 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -4,6 +4,7 @@
.gray-content-block.top-block
- if can? current_user, :admin_label, @project
= link_to new_namespace_project_label_path(@project.namespace, @project), class: "pull-right btn btn-new" do
+ = icon('plus')
New label
.oneline
Labels can be applied to issues and merge requests.
diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml
index 342ad4f3f95..e20fd7d6891 100644
--- a/app/views/projects/labels/new.html.haml
+++ b/app/views/projects/labels/new.html.haml
@@ -1,9 +1,7 @@
- page_title "New Label"
= render "header_title"
-%h3 New label
-.back-link
- = link_to namespace_project_labels_path(@project.namespace, @project) do
- &larr; To labels list
+%h3.page-title
+ New Label
%hr
= render 'form'
diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml
index 7e60782ff5b..ea462561668 100644
--- a/app/views/projects/merge_requests/_discussion.html.haml
+++ b/app/views/projects/merge_requests/_discussion.html.haml
@@ -7,15 +7,17 @@
= render 'shared/show_aside'
-.gray-content-block.second-block.oneline-block
+.gray-content-block.middle-block.oneline-block
.row
.col-md-9
.votes-holder.pull-right
#votes= render 'votes/votes_block', votable: @merge_request
= render "projects/merge_requests/show/participants"
.col-md-3
- %span.slead.has_tooltip{:"data-original-title" => 'Cross-project reference'}
- = cross_project_reference(@project, @merge_request)
+ .input-group.cross-project-reference
+ %span#cross-project-reference.slead.has_tooltip{title: 'Cross-project reference'}
+ = cross_project_reference(@project, @merge_request)
+ = clipboard_button(clipboard_target: '#cross-project-reference')
.row
%section.col-md-9
@@ -24,10 +26,3 @@
.issuable-affix
.context
= render 'shared/issuable/context', issuable: @merge_request
-
- - if @merge_request.labels.any?
- .issuable-context-title
- %label Labels
- .merge-request-show-labels
- - @merge_request.labels.each do |label|
- = link_to_label(label)
diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml
index 9cf389dbe38..3e4ab09c6d4 100644
--- a/app/views/projects/merge_requests/_form.html.haml
+++ b/app/views/projects/merge_requests/_form.html.haml
@@ -1,6 +1,5 @@
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form js-requires-input' } do |f|
- .merge-request-form-info
- = render 'shared/issuable/form', f: f, issuable: @merge_request
+ = render 'shared/issuable/form', f: f, issuable: @merge_request
:javascript
$('.assign-to-me-link').on('click', function(e){
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index d10ccb30571..f7f932bdf36 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -2,50 +2,52 @@
.merge-request-title
%span.merge-request-title-text
= link_to_gfm merge_request.title, merge_request_path(merge_request), class: "row_title"
- .merge-request-labels
- - merge_request.labels.each do |label|
- = link_to_label(label, project: merge_request.project)
.pull-right.light
- if merge_request.ci_commit
= render_ci_status(merge_request.ci_commit)
- if merge_request.merged?
%span
- %i.fa.fa-check
+ = icon('check')
MERGED
- elsif merge_request.closed?
%span
- %i.fa.fa-ban
+ = icon('ban')
CLOSED
- note_count = merge_request.mr_and_commit_notes.user.count
- if merge_request.assignee
&nbsp;
- = link_to_member(merge_request.source_project, merge_request.assignee, name: false)
+ = link_to_member(merge_request.source_project, merge_request.assignee, name: false, title: "Assigned to :name")
- if note_count > 0
&nbsp;
- %span
- %i.fa.fa-comments
+ = link_to merge_request_path(merge_request) + "#notes" do
+ = icon('comments')
= note_count
- else
&nbsp;
- %span.merge-request-no-comments
- %i.fa.fa-comments
+ = link_to merge_request_path(merge_request) + "#notes", class: "merge-request-no-comments" do
+ = icon('comments')
= 0
.merge-request-info
- = "##{merge_request.iid} opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')} by #{link_to_member(@project, merge_request.author, avatar: false)}".html_safe
- - if merge_request.votes_count > 0
- = render 'votes/votes_inline', votable: merge_request
- - if merge_request.milestone_id?
- &nbsp;
- %span
- %i.fa.fa-clock-o
- = merge_request.milestone.title
+ \##{merge_request.iid} &middot;
+ opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')}
+ by #{link_to_member(@project, merge_request.author, avatar: false)}
- if merge_request.target_project.default_branch != merge_request.target_branch
&nbsp;
- %span
- %i.fa.fa-code-fork
+ = link_to namespace_project_commits_path(merge_request.project.namespace, merge_request.project, merge_request.target_branch) do
+ = icon('code-fork')
= merge_request.target_branch
+ - if merge_request.milestone
+ &nbsp;
+ = link_to namespace_project_merge_requests_path(merge_request.project.namespace, merge_request.project, milestone_title: merge_request.milestone.title) do
+ = icon('clock-o')
+ = merge_request.milestone.title
+ - if merge_request.labels.any?
+ &nbsp;
+ - merge_request.labels.each do |label|
+ = link_to_label(label, project: merge_request.project)
- if merge_request.tasks?
+ &nbsp;
%span.task-status
= merge_request.task_status
diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml
index d9eff1f9320..236a545c840 100644
--- a/app/views/projects/merge_requests/_new_compare.html.haml
+++ b/app/views/projects/merge_requests/_new_compare.html.haml
@@ -1,4 +1,5 @@
-%p.lead Compare branches for new Merge Request
+%h3.page-title
+ New Merge Request
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: new_namespace_project_merge_request_path(@project.namespace, @project), method: :get, html: { class: "merge-request-form form-inline js-requires-input" } do |f|
.hide.alert.alert-danger.mr-compare-errors
@@ -10,7 +11,7 @@
.panel-body
= f.select(:source_project_id, [[@merge_request.source_project_path,@merge_request.source_project.id]] , {}, { class: 'source_project select2 span3', disabled: @merge_request.persisted?, required: true })
&nbsp;
- = f.select(:source_branch, @merge_request.source_branches, { include_blank: "Select branch" }, {class: 'source_branch select2 span2', required: true})
+ = f.select(:source_branch, @merge_request.source_branches, { include_blank: true }, { class: 'source_branch select2 span2', required: true, data: { placeholder: "Select source branch" } })
.panel-footer
.mr_source_commit
@@ -22,7 +23,7 @@
- projects = @project.forked_from_project.nil? ? [@project] : [@project, @project.forked_from_project]
= f.select(:target_project_id, options_from_collection_for_select(projects, 'id', 'path_with_namespace', f.object.target_project_id), {}, { class: 'target_project select2 span3', disabled: @merge_request.persisted?, required: true })
&nbsp;
- = f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, {class: 'target_branch select2 span2', required: true})
+ = f.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', required: true, data: { placeholder: "Select target branch" } })
.panel-footer
.mr_target_commit
@@ -37,7 +38,7 @@
%h4 Compare failed
%p We can't compare selected branches. It may be because of huge diff. Please try again or select different branches.
- else
- .light-well.append-bottom-10
+ .light-well.append-bottom-default
.center
%h4
There isn't anything to merge.
@@ -51,8 +52,8 @@
are the same.
- %div
- = f.submit 'Compare branches', class: "btn btn-new mr-compare-btn"
+ .form-actions
+ = f.submit 'Compare branches and continue', class: "btn btn-new mr-compare-btn"
:javascript
var source_branch = $("#merge_request_source_branch")
@@ -86,4 +87,3 @@
return;
}
});
-
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index 6244d3ba0b4..156922cea41 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -1,5 +1,5 @@
%h3.page-title
- New merge request
+ New Merge Request
%p.slead
- source_title, target_title = format_mr_branch_names(@merge_request)
From
@@ -11,15 +11,14 @@
= link_to 'Change branches', mr_change_branches_path(@merge_request)
%hr
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form js-requires-input' } do |f|
- .merge-request-form-info
- = render 'shared/issuable/form', f: f, issuable: @merge_request
- = f.hidden_field :source_project_id
- = f.hidden_field :source_branch
- = f.hidden_field :target_project_id
- = f.hidden_field :target_branch
+ = render 'shared/issuable/form', f: f, issuable: @merge_request
+ = f.hidden_field :source_project_id
+ = f.hidden_field :source_branch
+ = f.hidden_field :target_project_id
+ = f.hidden_field :target_branch
.mr-compare.merge-request
- %ul.merge-request-tabs
+ %ul.merge-request-tabs.center-top-menu.no-top.no-bottom
%li.commits-tab
= link_to url_for(params), data: {target: '#commits', action: 'commits', toggle: 'tab'} do
Commits
@@ -31,7 +30,7 @@
.tab-content
#commits.commits.tab-pane
- = render "projects/commits/commits", project: @project
+ = render "projects/merge_requests/show/commits"
#diffs.diffs.tab-pane.active
- if @diffs.present?
= render "projects/diffs/diffs", diffs: @diffs, project: @project
@@ -57,4 +56,3 @@
diffs_loaded: true,
commits_loaded: true
});
-
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index eeaa72ed21b..f5aff0877e7 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -8,7 +8,7 @@
.merge-request-details.issuable-details
= render "projects/merge_requests/show/mr_title"
= render "projects/merge_requests/show/mr_box"
- .append-bottom-20.mr-source-target.prepend-top-default
+ .append-bottom-default.mr-source-target.prepend-top-default
- if @merge_request.open?
.pull-right
- if @merge_request.source_branch_exists?
@@ -26,21 +26,23 @@
%li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff)
.normal
%span Request to merge
- %span.label-branch #{source_branch_with_namespace(@merge_request)}
+ %span.label-branch
+ = source_branch_with_namespace(@merge_request)
%span into
- %span.label-branch #{@merge_request.target_branch}
+ = link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do
+ = @merge_request.target_branch
= render "projects/merge_requests/show/how_to_merge"
= render "projects/merge_requests/widget/show.html.haml"
- - if @merge_request.open? && @merge_request.can_be_merged?
- .light.append-bottom-20
+ - if @merge_request.open? && @merge_request.source_branch_exists? && @merge_request.can_be_merged? && @merge_request.can_be_merged_by?(current_user)
+ .light.prepend-top-default
You can also accept this merge request manually using the
= succeed '.' do
= link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
- if @commits.present?
- %ul.merge-request-tabs
+ %ul.merge-request-tabs.center-top-menu.no-top.no-bottom
%li.notes-tab
= link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#notes', action: 'notes', toggle: 'tab'} do
Discussion
diff --git a/app/views/projects/merge_requests/edit.html.haml b/app/views/projects/merge_requests/edit.html.haml
index 303ca0a880b..fc62bb5bce9 100644
--- a/app/views/projects/merge_requests/edit.html.haml
+++ b/app/views/projects/merge_requests/edit.html.haml
@@ -2,6 +2,6 @@
= render "header_title"
%h3.page-title
- = "Edit merge request ##{@merge_request.iid}"
+ Edit Merge Request ##{@merge_request.iid}
%hr
= render 'form'
diff --git a/app/views/projects/merge_requests/merge.js.haml b/app/views/projects/merge_requests/merge.js.haml
index 89aae17a606..92ce479d463 100644
--- a/app/views/projects/merge_requests/merge.js.haml
+++ b/app/views/projects/merge_requests/merge.js.haml
@@ -1,7 +1,7 @@
- case @status
-- when :success
+- when :success
:plain
- merge_request_widget.mergeInProgress();
+ merge_request_widget.mergeInProgress(#{params[:should_remove_source_branch] == '1'});
- when :merge_when_build_succeeds
:plain
$('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/merge_when_build_succeeds'))}");
diff --git a/app/views/projects/merge_requests/new.html.haml b/app/views/projects/merge_requests/new.html.haml
index 9fdde80c6d9..d259968030e 100644
--- a/app/views/projects/merge_requests/new.html.haml
+++ b/app/views/projects/merge_requests/new.html.haml
@@ -1,7 +1,7 @@
- page_title "New Merge Request"
= render "header_title"
-- if @merge_request.can_be_created
+- if @merge_request.can_be_created && !params[:change_branches]
= render 'new_submit'
- else
= render 'new_compare'
diff --git a/app/views/projects/merge_requests/show/_commits.html.haml b/app/views/projects/merge_requests/show/_commits.html.haml
index 478054db517..7f904ec42a0 100644
--- a/app/views/projects/merge_requests/show/_commits.html.haml
+++ b/app/views/projects/merge_requests/show/_commits.html.haml
@@ -1,4 +1,4 @@
-.gray-content-block.second-block.oneline-block
+.gray-content-block.middle-block.oneline-block
= icon("sort-amount-desc")
Most recent commits displayed first
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 2bf9cd597a4..4dfe46e2b86 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -4,7 +4,7 @@
%span.issue-id Merge Request ##{@merge_request.iid}
%span.creator
&middot;
- created by #{link_to_member(@project, @merge_request.author, size: 24)}
+ opened by #{link_to_member(@project, @merge_request.author, size: 24)}
&middot;
= time_ago_with_tooltip(@merge_request.created_at)
- if @merge_request.updated_at != @merge_request.created_at
diff --git a/app/views/projects/merge_requests/widget/_merged.html.haml b/app/views/projects/merge_requests/widget/_merged.html.haml
index 52e34ee617e..8c2b5366a06 100644
--- a/app/views/projects/merge_requests/widget/_merged.html.haml
+++ b/app/views/projects/merge_requests/widget/_merged.html.haml
@@ -7,10 +7,11 @@
by #{link_to_member(@project, @merge_request.merge_event.author, avatar: true)}
#{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
%div
- - if !@merge_request.source_branch_exists?
+ - if !@merge_request.source_branch_exists? || (params[:delete_source] == 'true')
= succeed '.' do
The changes were merged into
- %span.label-branch= @merge_request.target_branch
+ = link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do
+ = @merge_request.target_branch
The source branch has been removed.
- elsif @merge_request.can_remove_source_branch?(current_user)
@@ -18,7 +19,8 @@
%p
= succeed '.' do
The changes were merged into
- %span.label-branch= @merge_request.target_branch
+ = link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do
+ = @merge_request.target_branch
You can remove the source branch now.
= link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @merge_request.source_branch), remote: true, method: :delete, class: "btn btn-primary btn-sm remove_source_branch" do
%i.fa.fa-times
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index 24879b19d2b..39aa2437e18 100644
--- a/app/views/projects/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -1,10 +1,3 @@
-%h3.page-title= @milestone.new_record? ? "New Milestone" : "Edit Milestone ##{@milestone.iid}"
-.back-link
- = link_to namespace_project_milestones_path(@project.namespace, @project) do
- &larr; To milestones
-
-%hr
-
= form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form gfm-form js-requires-input'} do |f|
-if @milestone.errors.any?
.alert.alert-danger
@@ -16,8 +9,7 @@
.form-group
= f.label :title, "Title", class: "control-label"
.col-sm-10
- = f.text_field :title, maxlength: 255, class: "form-control js-quick-submit", required: true
- %p.hint Required
+ = f.text_field :title, maxlength: 255, class: "form-control js-quick-submit", required: true, autofocus: true
.form-group.milestone-description
= f.label :description, "Description", class: "control-label"
.col-sm-10
diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml
index 5e93d55b1fb..334172b976f 100644
--- a/app/views/projects/milestones/_milestone.html.haml
+++ b/app/views/projects/milestones/_milestone.html.haml
@@ -31,4 +31,4 @@
= link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close"
= link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove" do
%i.fa.fa-trash-o
- Remove
+ Delete
diff --git a/app/views/projects/milestones/edit.html.haml b/app/views/projects/milestones/edit.html.haml
index e9dc0b77462..43f8863163d 100644
--- a/app/views/projects/milestones/edit.html.haml
+++ b/app/views/projects/milestones/edit.html.haml
@@ -1,3 +1,9 @@
- page_title "Edit", @milestone.title, "Milestones"
= render "header_title"
+
+%h3.page-title
+ Edit Milestone ##{@milestone.iid}
+
+%hr
+
= render "form"
diff --git a/app/views/projects/milestones/new.html.haml b/app/views/projects/milestones/new.html.haml
index 9ba9acb6f77..0d016f78313 100644
--- a/app/views/projects/milestones/new.html.haml
+++ b/app/views/projects/milestones/new.html.haml
@@ -1,3 +1,9 @@
- page_title "New Milestone"
= render "header_title"
+
+%h3.page-title
+ New Milestone
+
+%hr
+
= render "form"
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 3a898dfbcfd..c3bda794c65 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -1,46 +1,50 @@
- page_title @milestone.title, "Milestones"
= render "header_title"
-%h4.page-title
- .issue-box{ class: issue_box_class(@milestone) }
- - if @milestone.closed?
- Closed
- - elsif @milestone.expired?
- Expired
- - else
- Open
- Milestone ##{@milestone.iid}
- %small.creator
- = @milestone.expires_at
- .pull-right
- - if can?(current_user, :admin_milestone, @project)
- = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped" do
- %i.fa.fa-pencil-square-o
- Edit
- - if @milestone.active?
- = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-grouped"
+.issuable-details
+ .page-title
+ .issue-box{ class: issue_box_class(@milestone) }
+ - if @milestone.closed?
+ Closed
+ - elsif @milestone.expired?
+ Expired
- else
- = link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-grouped"
- = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-remove" do
- %i.fa.fa-trash-o
- Remove
+ Open
+ Milestone ##{@milestone.iid}
+ - if @milestone.expires_at
+ %span.creator
+ &middot;
+ = @milestone.expires_at
+ .pull-right
+ - if can?(current_user, :admin_milestone, @project)
+ = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped" do
+ %i.fa.fa-pencil-square-o
+ Edit
+
+ - if @milestone.active?
+ = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-grouped"
+ - else
+ = link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-grouped"
+
+ = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-remove" do
+ %i.fa.fa-trash-o
+ Delete
+
+ .gray-content-block.middle-block
+ %h2.issue-title
+ = gfm escape_once(@milestone.title)
+ %div
+ - if @milestone.description.present?
+ .description
+ .wiki
+ = preserve do
+ = markdown @milestone.description
-%hr
- if @milestone.issues.any? && @milestone.can_be_closed?
- .alert.alert-success
+ .alert.alert-success.prepend-top-default
%span All issues for this milestone are closed. You may close milestone now.
-%h3.issue-title
- = gfm escape_once(@milestone.title)
-%div
- - if @milestone.description.present?
- .description
- .wiki
- = preserve do
- = markdown @milestone.description
-
-%hr
-.context
+.context.prepend-top-default
%p.lead
Progress:
#{@milestone.closed_items_count} closed
@@ -51,8 +55,7 @@
%span.pull-right= @milestone.expires_at
= milestone_progress_bar(@milestone)
-
-%ul.nav.nav-tabs
+%ul.center-top-menu.no-top.no-bottom
%li.active
= link_to '#tab-issues', 'data-toggle' => 'tab' do
Issues
@@ -66,17 +69,21 @@
Participants
%span.badge= @users.count
- .pull-right
- - if can?(current_user, :create_issue, @project)
- = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { milestone_id: @milestone.id }), class: "btn btn-grouped", title: "New Issue" do
- %i.fa.fa-plus
- New Issue
- - if can?(current_user, :read_issue, @project)
- = link_to 'Browse Issues', namespace_project_issues_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn edit-milestone-link btn-grouped"
-
.tab-content
.tab-pane.active#tab-issues
- .row
+ .gray-content-block.middle-block
+ .pull-right
+ - if can?(current_user, :create_issue, @project)
+ = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { milestone_id: @milestone.id }), class: "btn btn-grouped", title: "New Issue" do
+ %i.fa.fa-plus
+ New Issue
+ - if can?(current_user, :read_issue, @project)
+ = link_to 'Browse Issues', namespace_project_issues_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn btn-grouped"
+
+ .oneline
+ All issues in this milestone
+
+ .row.prepend-top-default
.col-md-4
= render('issues', title: 'Unstarted Issues (open and unassigned)', issues: @issues.opened.unassigned, id: 'unassigned')
.col-md-4
@@ -85,7 +92,15 @@
= render('issues', title: 'Completed Issues (closed)', issues: @issues.closed, id: 'closed')
.tab-pane#tab-merge-requests
- .row
+ .gray-content-block.middle-block
+ .pull-right
+ - if can?(current_user, :read_merge_request, @project)
+ = link_to 'Browse Merge Requests', namespace_project_merge_requests_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn btn-grouped"
+
+ .oneline
+ All merge requests in this milestone
+
+ .row.prepend-top-default
.col-md-3
= render('merge_requests', title: 'Work in progress (open and unassigned)', merge_requests: @merge_requests.opened.unassigned, id: 'unassigned')
.col-md-3
@@ -100,6 +115,10 @@
= render 'merge_request', merge_request: merge_request
.tab-pane#tab-participants
+ .gray-content-block.middle-block
+ .oneline
+ All participants to this milestone
+
%ul.bordered-list
- @users.each do |user|
%li
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index a02c12f06a8..25233112132 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -1,5 +1,10 @@
- page_title 'New Project'
-- header_title 'New Project'
+- header_title "Projects", root_path
+
+%h3.page-title
+ New Project
+%hr
+
.project-edit-container
.project-edit-errors
= render 'projects/errors'
@@ -11,19 +16,23 @@
Project path
.col-sm-10
.input-group
- = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 1, autofocus: true, required: true
- .input-group-addon
- \.git
-
- - if current_user.can_select_namespace?
- .form-group
- = f.label :namespace_id, class: 'control-label' do
- %span Namespace
- .col-sm-10
- = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user), {}, {class: 'select2', tabindex: 2}
+ - if current_user.can_select_namespace?
+ .input-group-addon
+ = root_url
+ = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user, display_path: true), {}, {class: 'select2', tabindex: 1}
+ .input-group-addon
+ \/
+ - else
+ .input-group-addon
+ #{root_url}#{current_user.username}/
+ = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 2, autofocus: true, required: true
+
+ - if current_user.can_create_group?
+ .help-block
+ Want to house several dependent projects under the same namespace?
+ = link_to "Create a group", new_group_path
- if import_sources_enabled?
-
.project-import.js-toggle-container
.form-group
%label.control-label Import project from
@@ -82,19 +91,7 @@
%span Any repo by URL
.js-toggle-content.hide
- .form-group.import-url-data
- = f.label :import_url, class: 'control-label' do
- %span Git repository URL
- .col-sm-10
- = f.text_field :import_url, class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git'
- .well.prepend-top-20
- %ul
- %li
- The repository must be accessible over HTTP(S). If it is not publicly accessible, you can add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>.
- %li
- The import will time out after 4 minutes. For big repositories, use a clone/push combination.
- %li
- To migrate an SVN repository, check out #{link_to "this document", "http://doc.gitlab.com/ce/workflow/importing/migrating_from_svn.html"}.
+ = render "shared/import_form", f: f
.prepend-botton-10
@@ -103,19 +100,12 @@
Description
%span.light (optional)
.col-sm-10
- = f.text_area :description, placeholder: "Awesome project", class: "form-control", rows: 3, maxlength: 250, tabindex: 3
+ = f.text_area :description, class: "form-control", rows: 3, maxlength: 250, tabindex: 3
= render 'shared/visibility_level', f: f, visibility_level: default_project_visibility, can_change_visibility_level: true, form_model: @project
.form-actions
= f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4
-
- - if current_user.can_create_group?
- .pull-right
- .light.inline
- .space-right
- Need a group for several dependent projects?
- = link_to new_group_path, class: "btn" do
- Create a group
+ = link_to 'Cancel', dashboard_projects_path, class: 'btn btn-cancel'
.save-project-loader.hide
.center
diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml
index a21c019986a..3ccda1b381c 100644
--- a/app/views/projects/notes/_edit_form.html.haml
+++ b/app/views/projects/notes/_edit_form.html.haml
@@ -6,6 +6,5 @@
= render 'projects/notes/hints'
.note-form-actions
- .buttons
- = f.submit 'Save Comment', class: 'btn btn-primary btn-save btn-grouped js-comment-button'
- = link_to 'Cancel', '#', class: 'btn btn-cancel note-edit-cancel'
+ = f.submit 'Save Comment', class: 'btn btn-primary btn-save btn-grouped js-comment-button'
+ = link_to 'Cancel', '#', class: 'btn btn-cancel note-edit-cancel'
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index 5dd84317e3b..88e711ab534 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -12,8 +12,7 @@
= render 'projects/notes/hints'
.error-alert
- .note-form-actions
- .buttons.clearfix
- = f.submit 'Add Comment', class: "btn btn-green comment-btn btn-grouped js-comment-button"
- = yield(:note_actions)
- %a.btn.grouped.js-close-discussion-note-form Cancel
+ .note-form-actions.clearfix
+ = f.submit 'Add Comment', class: "btn btn-create comment-btn btn-grouped js-comment-button"
+ = yield(:note_actions)
+ %a.btn.btn-cancel.js-close-discussion-note-form Cancel
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index efa7dd01cc2..dd0abc8c746 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -35,26 +35,6 @@
- if note.updated_by && note.updated_by != note.author
by #{link_to_member(note.project, note.updated_by, avatar: false, author_class: nil)}
- - if note.superceded?(@notes)
- - if note.upvote?
- %span.vote.upvote.label.label-gray.strikethrough
- = icon('thumbs-up')
- \+1
- - if note.downvote?
- %span.vote.downvote.label.label-gray.strikethrough
- = icon('thumbs-down')
- \-1
- - else
- - if note.upvote?
- %span.vote.upvote.label.label-success
- = icon('thumbs-up')
- \+1
- - if note.downvote?
- %span.vote.downvote.label.label-danger
- = icon('thumbs-down')
- \-1
-
-
.note-body{class: note_editable?(note) ? 'js-task-list-container' : ''}
.note-text
= preserve do
diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml
index 43e92437cf5..0c73d7e34ac 100644
--- a/app/views/projects/project_members/_group_members.html.haml
+++ b/app/views/projects/project_members/_group_members.html.haml
@@ -4,11 +4,11 @@
group members
%small
(#{members.count})
- .panel-head-actions
- = link_to group_group_members_path(@group), class: 'btn btn-sm' do
- %i.fa.fa-pencil-square-o
+ .pull-right
+ = link_to group_group_members_path(@group), class: 'btn' do
+ = icon('pencil-square-o')
Edit group members
- %ul.well-list
+ %ul.content-list
- members.each do |member|
= render 'groups/group_members/group_member', member: member, show_controls: false
- if members.count > 20
diff --git a/app/views/projects/project_members/_project_member.html.haml b/app/views/projects/project_members/_project_member.html.haml
index f07cd97e63d..05bf3a7ef6a 100644
--- a/app/views/projects/project_members/_project_member.html.haml
+++ b/app/views/projects/project_members/_project_member.html.haml
@@ -4,7 +4,7 @@
%li{class: "#{dom_class(member)} js-toggle-container project_member_row access-#{member.human_access.downcase}", id: dom_id(member)}
%span.list-item-name
- if member.user
- = image_tag avatar_icon(user, 16), class: "avatar s16", alt: ''
+ = image_tag avatar_icon(user, 24), class: "avatar s24", alt: ''
%strong
= link_to user.name, user_path(user)
%span.cgray= user.username
@@ -14,7 +14,7 @@
%label.label.label-danger
%strong Blocked
- else
- = image_tag avatar_icon(member.invite_email, 16), class: "avatar s16", alt: ''
+ = image_tag avatar_icon(member.invite_email, 24), class: "avatar s24", alt: ''
%strong
= member.invite_email
%span.cgray
diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml
index b807fb2cc9d..ccddab13aaf 100644
--- a/app/views/projects/project_members/_team.html.haml
+++ b/app/views/projects/project_members/_team.html.haml
@@ -1,9 +1,21 @@
-.panel.panel-default.prepend-top-20
+.panel.panel-default
.panel-heading
%strong #{@project.name}
project members
%small
(#{members.count})
- %ul.well-list
+ .pull-right
+ = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do
+ .form-group
+ = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control', spellcheck: false }
+ = button_tag class: 'btn', title: 'Search' do
+ = icon("search")
+ %ul.content-list
- members.each do |project_member|
= render 'project_member', member: project_member
+
+:javascript
+ $('form.member-search-form').on('submit', function (event) {
+ event.preventDefault();
+ Turbolinks.visit(this.action + '?' + $(this).serialize());
+ });
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index 9fc4be583cc..29225a36364 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -1,36 +1,21 @@
- page_title "Members"
= render "header_title"
+- @blank_container = true
-.gray-content-block.top-block
- .clearfix.js-toggle-container
- = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do
- .form-group
- = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input', spellcheck: false }
- = button_tag 'Search', class: 'btn'
-
- - if can?(current_user, :admin_project_member, @project)
- %span.pull-right
- = button_tag class: 'btn btn-new btn-grouped js-toggle-button', type: 'button' do
- Add members
- %i.fa.fa-chevron-down
- = link_to import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-grouped", title: "Import members from another project" do
- Import members
-
- .js-toggle-content.hide.new-group-member-holder
+.project-members-page
+ - if can?(current_user, :admin_project_member, @project)
+ .panel.panel-default
+ .panel-heading
+ Add new user to project
+ .pull-right
+ = link_to import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-grouped", title: "Import members from another project" do
+ Import members
+ .panel-body
+ %p.light
+ Users with access to this project are listed below.
= render "new_project_member"
-%p.prepend-top-default.light
- Users with access to this project are listed below.
- Read more about project permissions
- %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
-
-= render "team", members: @project_members
-
-- if @group
- = render "group_members", members: @group_members
+ = render "team", members: @project_members
-:javascript
- $('form.member-search-form').on('submit', function (event) {
- event.preventDefault();
- Turbolinks.visit(this.action + '?' + $(this).serialize());
- });
+ - if @group
+ = render "group_members", members: @group_members
diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml
index 52b3a50c1e6..2541105b007 100644
--- a/app/views/projects/protected_branches/index.html.haml
+++ b/app/views/projects/protected_branches/index.html.haml
@@ -22,7 +22,7 @@
.form-group
= f.label :name, "Branch", class: 'control-label'
.col-sm-10
- = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: "Select branch"}, {class: "select2"})
+ = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: true}, {class: "select2", data: {placeholder: "Select branch"}})
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
@@ -33,4 +33,3 @@
.form-actions
= f.submit 'Protect', class: "btn-create btn"
= render 'branches_list'
-
diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml
index e7db09cdaa9..bc80f2f29ad 100644
--- a/app/views/projects/releases/edit.html.haml
+++ b/app/views/projects/releases/edit.html.haml
@@ -11,10 +11,9 @@
.prepend-top-default
= form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal gfm-form release-form' }) do |f|
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
- = render 'projects/zen', f: f, attr: :description, classes: 'description js-quick-submit'
+ = render 'projects/zen', f: f, attr: :description, classes: 'description js-quick-submit form-control'
= render 'projects/notes/hints'
.error-alert
- .prepend-top-default
+ .form-actions.prepend-top-default
= f.submit 'Save changes', class: 'btn btn-save'
= link_to "Cancel", namespace_project_tag_path(@project.namespace, @project, @tag.name), class: "btn btn-default btn-cancel"
-
diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml
index 07c24950ee2..b9486a9b492 100644
--- a/app/views/projects/repositories/_download_archive.html.haml
+++ b/app/views/projects/repositories/_download_archive.html.haml
@@ -3,10 +3,10 @@
- split_button = split_button || false
- if split_button == true
%span.btn-group{class: btn_class}
- = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn btn-success col-xs-10', rel: 'nofollow' do
+ = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn col-xs-10', rel: 'nofollow' do
%i.fa.fa-download
%span Download zip
- %a.col-xs-2.btn.btn-success.dropdown-toggle{ 'data-toggle' => 'dropdown' }
+ %a.col-xs-2.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' }
%span.caret
%span.sr-only
Select Archive Format
diff --git a/app/views/projects/runners/edit.html.haml b/app/views/projects/runners/edit.html.haml
index dde9e448cb9..eba03028af8 100644
--- a/app/views/projects/runners/edit.html.haml
+++ b/app/views/projects/runners/edit.html.haml
@@ -23,7 +23,7 @@
= label_tag :tag_list, class: 'control-label' do
Tags
.col-sm-10
- = f.text_field :tag_list, class: 'form-control'
+ = f.text_field :tag_list, value: @runner.tag_list.to_s, class: 'form-control'
.help-block You can setup jobs to only use runners with specific tags
.form-actions
- = f.submit 'Save', class: 'btn btn-save'
+ = f.submit 'Save changes', class: 'btn btn-save'
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index e1823b51198..1b70880043a 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -4,18 +4,15 @@
%p= @service.description
-.back-link
- = link_to namespace_project_services_path(@project.namespace, @project) do
- &larr; to services
-
%hr
= form_for(@service, as: :service, url: namespace_project_service_path(@project.namespace, @project, @service.to_param), method: :put, html: { class: 'form-horizontal' }) do |form|
= render 'shared/service_settings', form: form
.form-actions
- = form.submit 'Save', class: 'btn btn-save'
+ = form.submit 'Save changes', class: 'btn btn-save'
&nbsp;
- if @service.valid? && @service.activated?
- disabled = @service.can_test? ? '':'disabled'
= link_to 'Test settings', test_namespace_project_service_path(@project.namespace, @project, @service.to_param), class: "btn #{disabled}"
+ = link_to "Cancel", namespace_project_services_path(@project.namespace, @project), class: "btn btn-cancel"
diff --git a/app/views/projects/show.atom.builder b/app/views/projects/show.atom.builder
index 242684e5c7c..15c49767556 100644
--- a/app/views/projects/show.atom.builder
+++ b/app/views/projects/show.atom.builder
@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: namespace_project_url(@project.namespace, @project, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: namespace_project_url(@project.namespace, @project), rel: "alternate", type: "text/html"
xml.id namespace_project_url(@project.namespace, @project)
- xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
+ xml.updated @events.latest_update_time.strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
@events.each do |event|
event_to_atom(xml, event)
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 585caf674c9..9c7a5584da9 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -11,7 +11,7 @@
= render "home_panel"
-.project-stats.gray-content-block
+.project-stats.gray-content-block.second-block
%ul.nav.nav-pills
%li
= link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
diff --git a/app/views/projects/snippets/edit.html.haml b/app/views/projects/snippets/edit.html.haml
index e69f2d99709..dc3ea1fcf12 100644
--- a/app/views/projects/snippets/edit.html.haml
+++ b/app/views/projects/snippets/edit.html.haml
@@ -2,6 +2,6 @@
= render "header_title"
%h3.page-title
- Edit snippet
+ Edit Snippet
%hr
= render "shared/snippets/form", url: namespace_project_snippet_path(@project.namespace, @project, @snippet), visibility_level: @snippet.visibility_level
diff --git a/app/views/projects/snippets/new.html.haml b/app/views/projects/snippets/new.html.haml
index 67cd69fd215..e57237991b4 100644
--- a/app/views/projects/snippets/new.html.haml
+++ b/app/views/projects/snippets/new.html.haml
@@ -2,6 +2,6 @@
= render "header_title"
%h3.page-title
- New snippet
+ New Snippet
%hr
= render "shared/snippets/form", url: namespace_project_snippets_path(@project.namespace, @project, @snippet), visibility_level: default_snippet_visibility
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index 85d76eae3b5..760347de0a9 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -6,7 +6,7 @@
- if can? current_user, :push_code, @project
.pull-right
= link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do
- %i.fa.fa-add-sign
+ = icon('plus')
New tag
.oneline
Tags give the ability to mark specific points in history as being important
diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml
index e106be794f1..3a2f75fecaa 100644
--- a/app/views/projects/tags/new.html.haml
+++ b/app/views/projects/tags/new.html.haml
@@ -7,50 +7,40 @@
= @error
%h3.page-title
- New git tag
+ New Tag
%hr
-= form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal tag-form" do
+= form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal gfm-form tag-form js-requires-input" do
.form-group
- = label_tag :tag_name, 'Name for new tag', class: 'control-label'
+ = label_tag :tag_name, nil, class: 'control-label'
.col-sm-10
- = text_field_tag :tag_name, params[:tag_name], placeholder: 'v3.0.1', required: true, tabindex: 1, class: 'form-control'
+ = text_field_tag :tag_name, params[:tag_name], required: true, tabindex: 1, autofocus: true, class: 'form-control'
.form-group
= label_tag :ref, 'Create from', class: 'control-label'
.col-sm-10
- = text_field_tag :ref, params[:ref], placeholder: 'master', required: true, tabindex: 2, class: 'form-control'
+ = text_field_tag :ref, params[:ref] || @project.default_branch, required: true, tabindex: 2, class: 'form-control'
.help-block Branch name or commit SHA
.form-group
- = label_tag :message, 'Message', class: 'control-label'
+ = label_tag :message, nil, class: 'control-label'
.col-sm-10
- = text_field_tag :message, nil, placeholder: 'Enter message.', required: false, tabindex: 3, class: 'form-control'
- .help-block (Optional) Entering a message will create an annotated tag.
+ = text_field_tag :message, nil, required: false, tabindex: 3, class: 'form-control'
+ .help-block Optionally, enter a message to create an annotated tag.
%hr
.form-group
= label_tag :release_description, 'Release notes', class: 'control-label'
.col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
- .zennable
- %input#zen-toggle-comment.zen-toggle-comment(tabindex="-1" type="checkbox")
- .zen-backdrop
- = text_area_tag :release_description, nil, class: 'js-gfm-input markdown-area description js-quick-submit form-control', placeholder: ''
- %a.zen-enter-link(tabindex="-1" href="#")
- = icon('expand')
- Edit in fullscreen
- %a.zen-leave-link(href="#")
- = icon('compress')
-
+ = render 'projects/zen', attr: :release_description, classes: 'description js-quick-submit form-control'
= render 'projects/notes/hints'
- .help-block (Optional) You can add release notes to your tag. It will be stored in the GitLab database and shown on the tags page
+ .help-block Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page.
.form-actions
= button_tag 'Create tag', class: 'btn btn-create', tabindex: 3
= link_to 'Cancel', namespace_project_tags_path(@project.namespace, @project), class: 'btn btn-cancel'
:javascript
- disableButtonIfAnyEmptyField($("#new-tag-form"), ".form-control", ".btn-create");
- var availableTags = #{@project.repository.ref_names.to_json};
+ var availableRefs = #{@project.repository.ref_names.to_json};
$("#ref").autocomplete({
- source: availableTags,
+ source: availableRefs,
minLength: 1
});
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index ebe3718afcc..879c6c7d310 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -15,7 +15,7 @@
= 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', method: :delete, data: { confirm: 'Removed tag cannot be restored. Are you sure?'} do
+ = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped', method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
%i.fa.fa-trash-o
.title
%strong= @tag.name
diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml
index ee4c9d1693d..1bc90edd8f0 100644
--- a/app/views/projects/tree/_tree_content.html.haml
+++ b/app/views/projects/tree/_tree_content.html.haml
@@ -12,7 +12,7 @@
%i.fa.fa-angle-right
&nbsp;
%small.light
- = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit)
+ = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace"
&ndash;
= truncate(@commit.title, length: 50)
= link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id), class: 'pull-right'
@@ -30,7 +30,7 @@
= render "projects/tree/readme", readme: tree.readme
- if allowed_tree_edit?
- = render 'projects/blob/upload', title: 'Upload', placeholder: 'Upload new file', button_title: 'Upload file', form_path: namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post
+ = render 'projects/blob/upload', title: 'Upload New File', placeholder: 'Upload new file', button_title: 'Upload file', form_path: namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post
= render 'projects/blob/new_dir'
:javascript
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index 9c94c43e747..1d257818dcd 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -1,4 +1,4 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form gfm-form' } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form gfm-form prepend-top-default' } do |f|
-if @page.errors.any?
#error_explanation
.alert.alert-danger
@@ -11,14 +11,7 @@
.col-sm-10
= f.select :format, options_for_select(ProjectWiki::MARKUPS, {selected: @page.format}), {}, class: "form-control"
- .row
- .col-sm-offset-2.col-sm-10
- %p.cgray
- To link to a (new) page you can just type
- %code [Link Title](page-slug)
- \.
-
- .form-group.wiki-content
+ .form-group
= f.label :content, class: 'control-label'
.col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do
@@ -27,6 +20,11 @@
.clearfix
.error-alert
+
+ .help-block
+ To link to a (new) page, simply type
+ %code [Link Title](page-slug)
+ \.
.form-group
= f.label :commit_message, class: 'control-label'
.col-sm-10= f.text_field :message, class: 'form-control', rows: 18
diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml
index 14f25822259..29bf5d62abe 100644
--- a/app/views/projects/wikis/_main_links.html.haml
+++ b/app/views/projects/wikis/_main_links.html.haml
@@ -1,9 +1,4 @@
%span.pull-right
- - if can?(current_user, :create_wiki, @project)
- = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new btn-grouped", "data-toggle" => "modal" do
- %i.fa.fa-plus
- New Page
-
- if (@page && @page.persisted?)
= link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn btn-grouped" do
Page History
@@ -11,5 +6,7 @@
= link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn btn-grouped" do
%i.fa.fa-pencil-square-o
Edit
-
-= render 'projects/wikis/new'
+ - if can?(current_user, :admin_wiki, @project)
+ = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-remove" do
+ = icon('trash')
+ Delete
diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml
index fffb4eb31ab..e6e6ad5bc4b 100644
--- a/app/views/projects/wikis/_nav.html.haml
+++ b/app/views/projects/wikis/_nav.html.haml
@@ -1,10 +1,19 @@
-%ul.center-top-menu
- = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do
- = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home)
+.project-issuable-filter
+ .controls
+ - if can?(current_user, :create_wiki, @project)
+ = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
+ %i.fa.fa-plus
+ New Page
- = nav_link(path: 'wikis#pages') do
- = link_to 'Pages', namespace_project_wiki_pages_path(@project.namespace, @project)
+ = render 'projects/wikis/new'
- = nav_link(path: 'wikis#git_access') do
- = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do
- Git Access
+ %ul.center-top-menu
+ = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do
+ = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home)
+
+ = nav_link(path: 'wikis#pages') do
+ = link_to 'Pages', namespace_project_wiki_pages_path(@project.namespace, @project)
+
+ = nav_link(path: 'wikis#git_access') do
+ = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do
+ Git Access
diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml
index dace172438c..f0547e9c057 100644
--- a/app/views/projects/wikis/_new.html.haml
+++ b/app/views/projects/wikis/_new.html.haml
@@ -12,5 +12,5 @@
The page slug is invalid. Please don't use characters other then: a-z 0-9 _ - and /
%p.hint
Please don't use spaces.
- .modal-footer
- = link_to 'Build', '#', class: 'build-new-wiki btn btn-create'
+ .form-actions
+ = link_to 'Create Page', '#', class: 'build-new-wiki btn btn-create'
diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index 0b709c3695b..23f64fbbd10 100644
--- a/app/views/projects/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -1,16 +1,16 @@
-- page_title "Edit", @page.title, "Wiki"
+- page_title "Edit", @page.title.capitalize, "Wiki"
= render "header_title"
= render 'nav'
-.pull-right
- = render 'main_links'
-%h3.page-title
- Editing -
- %span.light #{@page.title}
-%hr
-= render 'form'
+.gray-content-block
+ .pull-right
+ = render 'main_links'
+
+ %h3.page-title.oneline
+ %span.light Edit Page
+ - if @page.persisted?
+ = link_to @page.title, namespace_project_wiki_path(@project.namespace, @project, @page)
+ - else
+ = @page.title
-.pull-right
- - if @page.persisted? && can?(current_user, :admin_wiki, @project)
- = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-sm btn-remove" do
- Delete this page
+= render 'form'
diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml
index 6417ef4a38b..11c8c4f0eba 100644
--- a/app/views/projects/wikis/git_access.html.haml
+++ b/app/views/projects/wikis/git_access.html.haml
@@ -5,7 +5,7 @@
.gray-content-block
.row
.col-sm-6
- %h3.page-title
+ %h3.page-title.oneline
Git access for
%strong= @project_wiki.path_with_namespace
diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml
index d179a1abec1..aae1ad69ad9 100644
--- a/app/views/projects/wikis/pages.html.haml
+++ b/app/views/projects/wikis/pages.html.haml
@@ -1,11 +1,10 @@
-- page_title "All Pages", "Wiki"
+- page_title "Pages", "Wiki"
= render "header_title"
= render 'nav'
.gray-content-block
- = render 'main_links'
- %h3.page-title
- All Pages
+ All pages in this wiki are listed below.
+
%ul.content-list
- @wiki_pages.each do |wiki_page|
%li
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index 55fbf5a8b6e..309d40f52bc 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -5,11 +5,12 @@
.gray-content-block
= render 'main_links'
- %h3.page-title
+ %h3.page-title.oneline
= @page.title.capitalize
- .wiki-last-edit-by
- Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
+ %span.wiki-last-edit-by
+ &middot;
+ last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
- if @page.historical?
.warning_message
@@ -21,8 +22,3 @@
.wiki
= preserve do
= render_wiki_content(@page)
-
-.gray-content-block.footer-block
- .wiki-last-edit-by
- Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
-
diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index 2e4aab36301..edb5778f424 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -2,26 +2,9 @@
.git-clone-holder.input-group
.input-group-addon.git-protocols
.input-group-btn
- %button{ |
- type: 'button', |
- class: "btn #{ 'active' if default_clone_protocol == 'ssh' }#{ ' has_tooltip' if current_user && current_user.require_ssh_key? }", |
- :"data-clone" => project.ssh_url_to_repo, |
- :"data-title" => "Add an SSH key to your profile<br> to pull or push via SSH.",
- :"data-html" => "true",
- :"data-container" => "body"}
- SSH
+ = ssh_clone_button(project)
.input-group-btn
- %button{ |
- type: 'button', |
- class: "btn #{ 'active' if default_clone_protocol == 'http' }#{ ' has_tooltip' if current_user && current_user.require_password? }", |
- :"data-clone" => project.http_url_to_repo, |
- :"data-title" => "Set a password on your account<br> to pull or push via #{gitlab_config.protocol.upcase}.",
- :"data-html" => "true",
- :"data-container" => "body"}
- = gitlab_config.protocol.upcase
+ = http_clone_button(project)
= text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true
- - if project.kind_of?(Project)
- .input-group-addon
- .visibility-level-label.has_tooltip{'data-title' => "#{visibility_level_label(project.visibility_level)} project" }
- = visibility_level_icon(project.visibility_level)
-
+ .input-group-btn
+ = clipboard_button(clipboard_target: '#project_clone')
diff --git a/app/views/shared/_commit_message_container.html.haml b/app/views/shared/_commit_message_container.html.haml
index cc3f1268f8b..7c57924277e 100644
--- a/app/views/shared/_commit_message_container.html.haml
+++ b/app/views/shared/_commit_message_container.html.haml
@@ -1,13 +1,15 @@
.form-group.commit_message-group
- = label_tag 'commit_message', class: 'control-label' do
+ - nonce = SecureRandom.hex
+ = label_tag "commit_message-#{nonce}", class: 'control-label' do
Commit message
.col-sm-10
.commit-message-container
.max-width-marker
= text_area_tag 'commit_message',
(params[:commit_message] || local_assigns[:text]),
- class: 'form-control js-quick-submit', placeholder: local_assigns[:placeholder],
- required: true, rows: (local_assigns[:rows] || 3)
+ class: 'form-control js-commit-message js-quick-submit', placeholder: local_assigns[:placeholder],
+ required: true, rows: (local_assigns[:rows] || 3),
+ id: "commit_message-#{nonce}"
- if local_assigns[:hint]
%p.hint
Try to keep the first line under 52 characters
diff --git a/app/views/shared/_confirm_modal.html.haml b/app/views/shared/_confirm_modal.html.haml
index 5f51b0d450f..34241cd8aad 100644
--- a/app/views/shared/_confirm_modal.html.haml
+++ b/app/views/shared/_confirm_modal.html.haml
@@ -3,7 +3,8 @@
.modal-content
.modal-header
%a.close{href: "#", "data-dismiss" => "modal"} ×
- %h4 Confirmation required
+ %h3.page-title
+ Confirmation required
.modal-body
%p.cred.lead.js-confirm-text
@@ -14,9 +15,9 @@
%br
Please type
%code.js-confirm-danger-match #{phrase}
- to proceed or close this modal to cancel
+ to proceed or close this modal to cancel.
.form-group
= text_field_tag 'confirm_name_input', '', class: 'form-control js-confirm-danger-input'
- .form-group
+ .form-actions
= submit_tag 'Confirm', class: "btn btn-danger js-confirm-danger-submit"
diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml
index c0a9923348e..67072b9fc2a 100644
--- a/app/views/shared/_group_form.html.haml
+++ b/app/views/shared/_group_form.html.haml
@@ -23,7 +23,7 @@
%li It will change the git path to repositories under this group.
.form-group.group-description-holder
- = f.label :description, 'Details', class: 'control-label'
+ = f.label :description, class: 'control-label'
.col-sm-10
= f.text_area :description, maxlength: 250,
class: 'form-control js-gfm-input', rows: 4
diff --git a/app/views/shared/_import_form.html.haml b/app/views/shared/_import_form.html.haml
new file mode 100644
index 00000000000..285af56ad73
--- /dev/null
+++ b/app/views/shared/_import_form.html.haml
@@ -0,0 +1,16 @@
+.form-group.import-url-data
+ = f.label :import_url, class: 'control-label' do
+ %span Git repository URL
+ .col-sm-10
+ = f.text_field :import_url, class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git'
+
+ .well.prepend-top-20
+ %ul
+ %li
+ The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>.
+ %li
+ If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>.
+ %li
+ The import will time out after 4 minutes. For big repositories, use a clone/push combination.
+ %li
+ To migrate an SVN repository, check out #{link_to "this document", "http://doc.gitlab.com/ce/workflow/importing/migrating_from_svn.html"}.
diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml
index 0dbb6a04393..4b4c9e9eabe 100644
--- a/app/views/shared/_issues.html.haml
+++ b/app/views/shared/_issues.html.haml
@@ -3,8 +3,10 @@
.panel.panel-default.panel-small
- project = group[0]
.panel-heading
- = link_to_project project
- = link_to 'show all', namespace_project_issues_path(project.namespace, project), class: 'pull-right'
+ = link_to project.name_with_namespace, namespace_project_issues_path(project.namespace, project)
+ - if can?(current_user, :create_issue, project)
+ .pull-right
+ = link_to 'New issue', new_namespace_project_issue_path(project.namespace, project)
%ul.well-list.issues-list
- group[1].each do |issue|
@@ -12,4 +14,3 @@
= paginate @issues, theme: "gitlab"
- else
.nothing-here-block No issues to show
-
diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml
index c02c5af008a..be17a511b26 100644
--- a/app/views/shared/_merge_requests.html.haml
+++ b/app/views/shared/_merge_requests.html.haml
@@ -3,8 +3,11 @@
.panel.panel-default.panel-small
- project = group[0]
.panel-heading
- = link_to_project project
- = link_to 'show all', namespace_project_merge_requests_path(project.namespace, project), class: 'pull-right'
+ = link_to project.name_with_namespace, namespace_project_merge_requests_path(project.namespace, project)
+ - if can?(current_user, :create_merge_request, project)
+ .pull-right
+ = link_to 'New merge request', new_namespace_project_merge_request_path(project.namespace, project)
+
%ul.well-list.mr-list
- group[1].each do |merge_request|
= render 'projects/merge_requests/merge_request', merge_request: merge_request
diff --git a/app/views/shared/_new_commit_form.html.haml b/app/views/shared/_new_commit_form.html.haml
new file mode 100644
index 00000000000..31b02ed93d0
--- /dev/null
+++ b/app/views/shared/_new_commit_form.html.haml
@@ -0,0 +1,17 @@
+= render 'shared/commit_message_container', placeholder: placeholder
+
+- unless @project.empty_repo?
+ .form-group.branch
+ = label_tag 'new_branch', 'Target branch', class: 'control-label'
+ .col-sm-10
+ = text_field_tag 'new_branch', @new_branch || @ref, required: true, class: "form-control js-new-branch"
+
+ .form-group.js-create-merge-request-form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ - nonce = SecureRandom.hex
+ = label_tag "create_merge_request-#{nonce}" do
+ = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}"
+ Start a <strong>new merge request</strong> with this commit
+
+ = hidden_field_tag 'original_branch', @ref, class: 'js-original-branch'
diff --git a/app/views/shared/_project_limit.html.haml b/app/views/shared/_project_limit.html.haml
new file mode 100644
index 00000000000..960ff00b49d
--- /dev/null
+++ b/app/views/shared/_project_limit.html.haml
@@ -0,0 +1,8 @@
+- if cookies[:hide_project_limit_message].blank? && !current_user.hide_project_limit && !current_user.can_create_project?
+ .project-limit-message.alert.alert-warning.hidden-xs
+ You won't be able to create new projects because you have reached your project limit.
+
+ .pull-right
+ = link_to "Don't show again", profile_path(user: {hide_project_limit: true}), method: :put, class: 'alert-link'
+ |
+ = link_to 'Remind later', '#', class: 'hide-project-limit-message alert-link'
diff --git a/app/views/shared/issuable/_context.html.haml b/app/views/shared/issuable/_context.html.haml
index be66256c7b0..2aa46662613 100644
--- a/app/views/shared/issuable/_context.html.haml
+++ b/app/views/shared/issuable/_context.html.haml
@@ -1,5 +1,5 @@
= form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
- %div.prepend-top-20
+ %div.prepend-top-default
.issuable-context-title
%label
Assignee:
@@ -9,9 +9,9 @@
none
.issuable-context-selectbox
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
- = users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true, current_user: true)
+ = users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true, current_user: true, first_user: true)
- %div.prepend-top-20.clearfix
+ %div.prepend-top-default.clearfix
.issuable-context-title
%label
Milestone:
@@ -25,25 +25,32 @@
none
.issuable-context-selectbox
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
- = f.select(:milestone_id, milestone_options(issuable), { include_blank: 'Select milestone' }, {class: 'select2 select2-compact js-select2 js-milestone'})
+ = f.select(:milestone_id, milestone_options(issuable), { include_blank: true }, { class: 'select2 select2-compact js-select2 js-milestone', data: { placeholder: 'Select milestone' }})
= hidden_field_tag :issuable_context
= f.submit class: 'btn hide'
+ - if issuable.labels.any?
+ %div.prepend-top-default.clearfix
+ .issuable-context-title
+ %label Labels
+ .issuable-show-labels
+ - issuable.labels.each do |label|
+ = link_to_label(label)
+
- if current_user
- subscribed = issuable.subscribed?(current_user)
- %div.prepend-top-20.clearfix
+ %div.prepend-top-default.clearfix
.issuable-context-title
- %label
- Subscription:
- %button.btn.btn-block.subscribe-button{:type => 'button'}
- = icon('eye')
- %span= subscribed ? 'Unsubscribe' : 'Subscribe'
+ %label Subscription
- subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed'
.subscription-status{data: {status: subscribtion_status}}
.description-block.unsubscribed{class: ( 'hidden' if subscribed )}
You're not receiving notifications from this thread.
.description-block.subscribed{class: ( 'hidden' unless subscribed )}
You're receiving notifications because you're subscribed to this thread.
+ %button.btn.btn-block.subscribe-button{:type => 'button'}
+ = icon('eye')
+ %span= subscribed ? 'Unsubscribe' : 'Subscribe'
:javascript
new Subscription("#{toggle_subscription_path(issuable)}");
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index d1231438ee4..ac6c248ccf1 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -31,11 +31,11 @@
.issues-other-filters
.filter-item.inline
= users_select_tag(:assignee_id, selected: params[:assignee_id],
- placeholder: 'Assignee', class: 'trigger-submit', any_user: true, null_user: true, first_user: true, current_user: true)
+ placeholder: 'Assignee', class: 'trigger-submit', any_user: "Any Assignee", null_user: true, first_user: true, current_user: true)
.filter-item.inline
= users_select_tag(:author_id, selected: params[:author_id],
- placeholder: 'Author', class: 'trigger-submit', any_user: true, first_user: true, current_user: true)
+ placeholder: 'Author', class: 'trigger-submit', any_user: "Any Author", first_user: true, current_user: true)
.filter-item.inline.milestone-filter
= select_tag('milestone_title', projects_milestones_options,
@@ -53,12 +53,16 @@
- if controller.controller_name == 'issues'
.issues_bulk_update.hide
= form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post do
- = select_tag('update[state_event]', options_for_select([['Open', 'reopen'], ['Closed', 'close']]), prompt: "Status", class: 'form-control')
- = users_select_tag('update[assignee_id]', placeholder: 'Assignee', null_user: true, first_user: true, current_user: true)
- = select_tag('update[milestone_id]', bulk_update_milestone_options, prompt: "Milestone")
+ .filter-item.inline
+ = select_tag('update[state_event]', options_for_select([['Open', 'reopen'], ['Closed', 'close']]), include_blank: true, data: { placeholder: "Status" })
+ .filter-item.inline
+ = users_select_tag('update[assignee_id]', placeholder: 'Assignee', null_user: true, first_user: true, current_user: true)
+ .filter-item.inline
+ = select_tag('update[milestone_id]', bulk_update_milestone_options, include_blank: true, data: { placeholder: "Milestone" })
= hidden_field_tag 'update[issues_ids]', []
= hidden_field_tag :state_event, params[:state_event]
- = button_tag "Update issues", class: "btn update_selected_issues btn-save"
+ .filter-item.inline
+ = button_tag "Update issues", class: "btn update_selected_issues btn-save"
:javascript
new UsersSelect();
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 0fc74d7d2b1..91ccd1ef660 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -6,8 +6,7 @@
%span= msg
%br
.form-group
- = f.label :title, class: 'control-label' do
- %strong= 'Title *'
+ = f.label :title, class: 'control-label'
.col-sm-10
= f.text_field :title, maxlength: 255, autofocus: true, autocomplete: 'off',
class: 'form-control pad js-gfm-input js-quick-submit', required: true
@@ -30,29 +29,25 @@
= render 'projects/notes/hints'
.clearfix
.error-alert
- %hr
- if can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project)
+ %hr
.form-group
.issue-assignee
- = f.label :assignee_id, class: 'control-label' do
- %i.fa.fa-user
- Assign to
+ = f.label :assignee_id, "Assignee", class: 'control-label'
.col-sm-10
= users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]",
- placeholder: 'Select a user', class: 'custom-form-control', null_user: true,
+ placeholder: 'Select assignee', class: 'custom-form-control', null_user: true,
selected: issuable.assignee_id, project: @target_project || @project,
- first_user: true, current_user: true)
+ first_user: true, current_user: true, include_blank: true)
&nbsp;
= link_to 'Assign to me', '#', class: 'btn assign-to-me-link'
.form-group
.issue-milestone
- = f.label :milestone_id, class: 'control-label' do
- %i.fa.fa-clock-o
- Milestone
+ = f.label :milestone_id, "Milestone", class: 'control-label'
.col-sm-10
- if milestone_options(issuable).present?
= f.select(:milestone_id, milestone_options(issuable),
- { include_blank: 'Select milestone' }, { class: 'select2' })
+ { include_blank: true }, { class: 'select2', data: { placeholder: 'Select milestone' } })
- else
.prepend-top-10
%span.light No open milestones available.
@@ -60,13 +55,11 @@
- if can? current_user, :admin_milestone, issuable.project
= link_to 'Create new milestone', new_namespace_project_milestone_path(issuable.project.namespace, issuable.project), target: :blank
.form-group
- = f.label :label_ids, class: 'control-label' do
- %i.fa.fa-tag
- Labels
+ = f.label :label_ids, "Labels", class: 'control-label'
.col-sm-10
- if issuable.project.labels.any?
= f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
- { selected: issuable.label_ids }, multiple: true, class: 'select2'
+ { selected: issuable.label_ids }, multiple: true, class: 'select2', data: { placeholder: "Select labels" }
- else
.prepend-top-10
%span.light No labels yet.
@@ -78,31 +71,30 @@
%hr
- if @merge_request.new_record?
.form-group
- = f.label :source_branch, class: 'control-label' do
- %i.fa.fa-code-fork
- Source Branch
+ = 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' do
- %i.fa.fa-code-fork
- Target Branch
+ = f.label :target_branch, class: 'control-label'
.col-sm-10
- = f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, { class: 'target_branch select2 span2', disabled: @merge_request.new_record? })
+ = f.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', disabled: @merge_request.new_record?, data: {placeholder: "Select branch"} })
- if @merge_request.new_record?
%p.help-block
= link_to 'Change branches', mr_change_branches_path(@merge_request)
-.form-actions
- - if !issuable.project.empty_repo? && (guide_url = contribution_guide_path(issuable.project)) && !issuable.persisted?
- %p
- Please review the
- %strong #{link_to 'guidelines for contribution', guide_url}
- to this repository.
+- is_footer = !(issuable.is_a?(MergeRequest) && issuable.new_record?)
+.gray-content-block{class: (is_footer ? "footer-block" : "middle-block")}
- if issuable.new_record?
- = f.submit "Submit new #{issuable.class.model_name.human.downcase}", class: 'btn btn-create'
+ = f.submit "Submit #{issuable.class.model_name.human.downcase}", class: 'btn btn-create'
- else
= f.submit 'Save changes', class: 'btn btn-save'
+
+ - if !issuable.persisted? && !issuable.project.empty_repo? && (guide_url = contribution_guide_path(issuable.project))
+ .inline.prepend-left-10
+ Please review the
+ %strong #{link_to 'contribution guidelines', guide_url}
+ for this project.
+
- if issuable.new_record?
- cancel_project = issuable.source_project
- else
diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml
index 913b6744844..1041eccd1df 100644
--- a/app/views/shared/snippets/_form.html.haml
+++ b/app/views/shared/snippets/_form.html.haml
@@ -1,5 +1,5 @@
.snippet-form-holder
- = form_for @snippet, url: url, html: { class: "form-horizontal snippet-form" } do |f|
+ = form_for @snippet, url: url, html: { class: "form-horizontal snippet-form js-requires-input" } do |f|
- if @snippet.errors.any?
.alert.alert-danger
%ul
@@ -8,7 +8,8 @@
.form-group
= f.label :title, class: 'control-label'
- .col-sm-10= f.text_field :title, placeholder: "Example Snippet", class: 'form-control', required: true
+ .col-sm-10
+ = f.text_field :title, class: 'form-control', required: true, autofocus: true
= render 'shared/visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: true, form_model: @snippet
@@ -27,7 +28,7 @@
- if @snippet.new_record?
= f.submit 'Create snippet', class: "btn-create btn"
- else
- = f.submit 'Save', class: "btn-save btn"
+ = f.submit 'Save changes', class: "btn-save btn"
- if @snippet.project_id
= link_to "Cancel", namespace_project_snippets_path(@project.namespace, @project), class: "btn btn-cancel"
diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml
index 0a4a790ec5e..89c1d7122b0 100644
--- a/app/views/shared/snippets/_header.html.haml
+++ b/app/views/shared/snippets/_header.html.haml
@@ -1,9 +1,9 @@
-.snippet-details
+.issuable-details
.page-title
- .snippet-box{class: visibility_level_color(@snippet.visibility_level)}
- = visibility_level_icon(@snippet.visibility_level)
+ .snippet-box.has_tooltip{class: visibility_level_color(@snippet.visibility_level), title: snippet_visibility_level_description(@snippet.visibility_level), data: { container: 'body' }}
+ = visibility_level_icon(@snippet.visibility_level, fw: false)
= visibility_level_label(@snippet.visibility_level)
- %span.snippet-id Snippet ##{@snippet.id}
+ Snippet ##{@snippet.id}
%span.creator
&middot; created by #{link_to_member(@project, @snippet.author, size: 24)}
&middot;
@@ -19,6 +19,7 @@
= render "projects/snippets/actions"
- else
= render "snippets/actions"
+
.gray-content-block.middle-block
- %h2.snippet-title
+ %h2.issue-title
= gfm escape_once(@snippet.title)
diff --git a/app/views/sherlock/transactions/_general.html.haml b/app/views/sherlock/transactions/_general.html.haml
index 4287a0c3203..8533b130da6 100644
--- a/app/views/sherlock/transactions/_general.html.haml
+++ b/app/views/sherlock/transactions/_general.html.haml
@@ -27,6 +27,12 @@
= t('sherlock.seconds')
%li
%span.light
+ #{t('sherlock.query_time')}
+ %strong
+ = @transaction.query_duration.round(2)
+ = t('sherlock.seconds')
+ %li
+ %span.light
#{t('sherlock.finished_at')}:
%strong
= time_ago_in_words(@transaction.finished_at)
diff --git a/app/views/snippets/edit.html.haml b/app/views/snippets/edit.html.haml
index 1a380035661..82f44a9a5c3 100644
--- a/app/views/snippets/edit.html.haml
+++ b/app/views/snippets/edit.html.haml
@@ -1,5 +1,5 @@
- page_title "Edit", @snippet.title, "Snippets"
%h3.page-title
- Edit snippet
+ Edit Snippet
%hr
= render 'shared/snippets/form', url: snippet_path(@snippet), visibility_level: @snippet.visibility_level
diff --git a/app/views/snippets/new.html.haml b/app/views/snippets/new.html.haml
index a74d5e792ad..79e2392490d 100644
--- a/app/views/snippets/new.html.haml
+++ b/app/views/snippets/new.html.haml
@@ -1,5 +1,5 @@
- page_title "New Snippet"
%h3.page-title
- New snippet
+ New Snippet
%hr
= render "shared/snippets/form", url: snippets_path(@snippet), visibility_level: default_snippet_visibility
diff --git a/app/views/users/show.atom.builder b/app/views/users/show.atom.builder
index 50232dc7186..2fe5b7fac83 100644
--- a/app/views/users/show.atom.builder
+++ b/app/views/users/show.atom.builder
@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: user_url(@user, :atom), rel: "self", type: "application/atom+xml"
xml.link href: user_url(@user), rel: "alternate", type: "text/html"
xml.id user_url(@user)
- xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
+ xml.updated @events.latest_update_time.strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
@events.each do |event|
event_to_atom(xml, event)
diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml
index 36ea6742064..7eb27c12d33 100644
--- a/app/views/votes/_votes_block.html.haml
+++ b/app/views/votes/_votes_block.html.haml
@@ -1,10 +1,32 @@
-.votes.votes-block
- .btn-group
- - unless votable.upvotes.zero?
- .btn.btn-sm.disabled.cgreen
- %i.fa.fa-thumbs-up
- = votable.upvotes
- - unless votable.downvotes.zero?
- .btn.btn-sm.disabled.cred
- %i.fa.fa-thumbs-down
- = votable.downvotes
+.awards.votes-block
+ - votable.notes.awards.grouped_awards.each do |emoji, notes|
+ .award{class: (note_active_class(notes, current_user)), title: emoji_author_list(notes, current_user)}
+ .icon{"data-emoji" => "#{emoji}"}
+ = image_tag url_to_emoji(emoji), height: "20px", width: "20px"
+ .counter
+ = notes.count
+
+ - if current_user
+ .dropdown.awards-controls
+ %a.add-award{"data-toggle" => "dropdown", "data-target" => "#", "href" => "#"}
+ = icon('smile-o')
+ %ul.dropdown-menu.awards-menu
+ - emoji_list.each do |emoji|
+ %li{"data-emoji" => "#{emoji}"}= image_tag url_to_emoji(emoji), height: "20px", width: "20px"
+
+- if current_user
+ :coffeescript
+ post_emoji_url = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}"
+ noteable_type = "#{votable.class.name.underscore}"
+ noteable_id = "#{votable.id}"
+ window.awards_handler = new AwardsHandler(post_emoji_url, noteable_type, noteable_id)
+
+ $(".awards-menu li").click (e)->
+ emoji = $(this).data("emoji")
+ awards_handler.addAward(emoji)
+
+ $(".awards").on "click", ".award", (e)->
+ emoji = $(this).find(".icon").data("emoji")
+ awards_handler.addAward(emoji)
+
+ $(".award").tooltip()
diff --git a/app/views/votes/_votes_inline.html.haml b/app/views/votes/_votes_inline.html.haml
deleted file mode 100644
index 2cb3ae04e1a..00000000000
--- a/app/views/votes/_votes_inline.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
-.votes.votes-inline
- - unless votable.upvotes.zero?
- %span.upvotes.cgreen
- + #{votable.upvotes}
- - unless votable.downvotes.zero?
- \/
- - unless votable.downvotes.zero?
- %span.downvotes.cred
- \- #{votable.downvotes}
diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb
index 5a921a73fe9..f2649e38eb3 100644
--- a/app/workers/email_receiver_worker.rb
+++ b/app/workers/email_receiver_worker.rb
@@ -46,6 +46,6 @@ class EmailReceiverWorker
return
end
- EmailRejectionMailer.delay.rejection(reason, raw, can_retry)
+ EmailRejectionMailer.rejection(reason, raw, can_retry).deliver_later
end
end
diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb
index 916a99bb273..c4d8595d45d 100644
--- a/app/workers/emails_on_push_worker.rb
+++ b/app/workers/emails_on_push_worker.rb
@@ -53,7 +53,7 @@ class EmailsOnPushWorker
reverse_compare: reverse_compare,
send_from_committer_email: send_from_committer_email,
disable_diffs: disable_diffs
- ).deliver
+ ).deliver_now
# These are input errors and won't be corrected even if Sidekiq retries
rescue Net::SMTPFatalError, Net::SMTPSyntaxError => e
logger.info("Failed to send e-mail for project '#{project.name_with_namespace}' to #{recipient}: #{e}")
diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb
index acd1c43f06b..2f991c52339 100644
--- a/app/workers/repository_fork_worker.rb
+++ b/app/workers/repository_fork_worker.rb
@@ -13,22 +13,20 @@ class RepositoryForkWorker
end
result = gitlab_shell.fork_repository(source_path, target_path)
-
unless result
logger.error("Unable to fork project #{project_id} for repository #{source_path} -> #{target_path}")
+ project.update(import_error: "The project could not be forked.")
project.import_fail
- project.save
return
end
- if project.valid_repo?
- ProjectCacheWorker.perform_async(project.id)
- project.import_finish
- else
- project.import_fail
+ unless project.valid_repo?
logger.error("Project #{id} had an invalid repository after fork")
+ project.update(import_error: "The forked repository is invalid.")
+ project.import_fail
+ return
end
- project.save
+ project.import_finish
end
end
diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
index ea2808045eb..d18c0706b30 100644
--- a/app/workers/repository_import_worker.rb
+++ b/app/workers/repository_import_worker.rb
@@ -7,37 +7,49 @@ class RepositoryImportWorker
def perform(project_id)
project = Project.find(project_id)
- unless project.import_url == Project::UNKNOWN_IMPORT_URL
- import_result = gitlab_shell.send(:import_repository,
- project.path_with_namespace,
- project.import_url)
- return project.import_fail unless import_result
- else
+ if project.import_url == Project::UNKNOWN_IMPORT_URL
+ # In this case, we only want to import issues, not a repository.
unless project.create_repository
- return project.import_fail
+ project.update(import_error: "The repository could not be created.")
+ project.import_fail
+ return
+ end
+ else
+ begin
+ gitlab_shell.import_repository(project.path_with_namespace, project.import_url)
+ rescue Gitlab::Shell::Error => e
+ project.update(import_error: e.message)
+ project.import_fail
+ return
end
end
- data_import_result = case project.import_type
- when 'github'
- Gitlab::GithubImport::Importer.new(project).execute
- when 'gitlab'
- Gitlab::GitlabImport::Importer.new(project).execute
- when 'bitbucket'
- Gitlab::BitbucketImport::Importer.new(project).execute
- when 'google_code'
- Gitlab::GoogleCodeImport::Importer.new(project).execute
- when 'fogbugz'
- Gitlab::FogbugzImport::Importer.new(project).execute
- else
- true
- end
- return project.import_fail unless data_import_result
+ data_import_result =
+ case project.import_type
+ when 'github'
+ Gitlab::GithubImport::Importer.new(project).execute
+ when 'gitlab'
+ Gitlab::GitlabImport::Importer.new(project).execute
+ when 'bitbucket'
+ Gitlab::BitbucketImport::Importer.new(project).execute
+ when 'google_code'
+ Gitlab::GoogleCodeImport::Importer.new(project).execute
+ when 'fogbugz'
+ Gitlab::FogbugzImport::Importer.new(project).execute
+ else
+ true
+ end
- Gitlab::BitbucketImport::KeyDeleter.new(project).execute if project.import_type == 'bitbucket'
+ unless data_import_result
+ project.update(import_error: "The remote issue data could not be imported.")
+ project.import_fail
+ return
+ end
+
+ if project.import_type == 'bitbucket'
+ Gitlab::BitbucketImport::KeyDeleter.new(project).execute
+ end
project.import_finish
- project.save
- ProjectCacheWorker.perform_async(project.id)
end
end
diff --git a/app/workers/stuck_ci_builds_worker.rb b/app/workers/stuck_ci_builds_worker.rb
index ad02a3b16d9..4e5eddbaba1 100644
--- a/app/workers/stuck_ci_builds_worker.rb
+++ b/app/workers/stuck_ci_builds_worker.rb
@@ -14,5 +14,8 @@ class StuckCiBuildsWorker
Rails.logger.debug "Dropping stuck #{build.status} build #{build.id} for runner #{build.runner_id}"
build.drop
end
+
+ # Update builds that failed to drop
+ builds.update_all(status: 'failed')
end
end
diff --git a/bin/background_jobs b/bin/background_jobs
index d4578f6a222..5c85fb339e6 100755
--- a/bin/background_jobs
+++ b/bin/background_jobs
@@ -37,7 +37,7 @@ start_no_deamonize()
start_sidekiq()
{
- bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1
+ bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1
}
load_ok()
diff --git a/bin/ci/upgrade.rb b/bin/ci/upgrade.rb
index aab4f60ec60..aab4f60ec60 100644..100755
--- a/bin/ci/upgrade.rb
+++ b/bin/ci/upgrade.rb
diff --git a/bin/rails b/bin/rails
index 7feb6a30e69..5191e6927af 100755
--- a/bin/rails
+++ b/bin/rails
@@ -1,8 +1,4 @@
#!/usr/bin/env ruby
-begin
- load File.expand_path("../spring", __FILE__)
-rescue LoadError
-end
-APP_PATH = File.expand_path('../../config/application', __FILE__)
+APP_PATH = File.expand_path('../../config/application', __FILE__)
require_relative '../config/boot'
require 'rails/commands'
diff --git a/bin/rake b/bin/rake
index 0fb4e07e13a..17240489f64 100755
--- a/bin/rake
+++ b/bin/rake
@@ -1,7 +1,4 @@
#!/usr/bin/env ruby
-begin
- load File.expand_path("../spring", __FILE__)
-rescue LoadError
-end
-require 'bundler/setup'
-load Gem.bin_path('rake', 'rake')
+require_relative '../config/boot'
+require 'rake'
+Rake.application.run
diff --git a/bin/setup b/bin/setup
new file mode 100755
index 00000000000..acdb2c1389c
--- /dev/null
+++ b/bin/setup
@@ -0,0 +1,29 @@
+#!/usr/bin/env ruby
+require 'pathname'
+
+# path to your application root.
+APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
+
+Dir.chdir APP_ROOT do
+ # This script is a starting point to setup your application.
+ # Add necessary setup steps to this file:
+
+ puts "== Installing dependencies =="
+ system "gem install bundler --conservative"
+ system "bundle check || bundle install"
+
+ # puts "\n== Copying sample files =="
+ # unless File.exist?("config/database.yml")
+ # system "cp config/database.yml.sample config/database.yml"
+ # end
+
+ puts "\n== Preparing database =="
+ system "bin/rake db:setup"
+
+ puts "\n== Removing old logs and tempfiles =="
+ system "rm -f log/*"
+ system "rm -rf tmp/cache"
+
+ puts "\n== Restarting application server =="
+ system "touch tmp/restart.txt"
+end
diff --git a/bin/upgrade.rb b/bin/upgrade.rb
index a5caecf8526..a5caecf8526 100644..100755
--- a/bin/upgrade.rb
+++ b/bin/upgrade.rb
diff --git a/config/application.rb b/config/application.rb
index bfa2a809dd7..d255ff0719f 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -99,6 +99,10 @@ module Gitlab
redis_config_hash[:expires_in] = 2.weeks # Cache should not grow forever
config.cache_store = :redis_store, redis_config_hash
+ config.active_record.raise_in_transactional_callbacks = true
+
+ config.active_job.queue_adapter = :sidekiq
+
# This is needed for gitlab-shell
ENV['GITLAB_PATH_OUTSIDE_HOOK'] = ENV['PATH']
end
diff --git a/config/environment.rb b/config/environment.rb
index 3b186a9d57a..df3006d349c 100644
--- a/config/environment.rb
+++ b/config/environment.rb
@@ -2,4 +2,4 @@
require File.expand_path('../application', __FILE__)
# Initialize the rails application
-Gitlab::Application.initialize!
+Rails.application.initialize!
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 827a110c249..c22722c606b 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -1,4 +1,4 @@
-Gitlab::Application.configure do
+Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb
# In the development environment your application's code is reloaded on
diff --git a/config/environments/production.rb b/config/environments/production.rb
index 3316ece3873..909526605a1 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -1,4 +1,4 @@
-Gitlab::Application.configure do
+Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb
# Code is not reloaded between requests
@@ -9,7 +9,7 @@ Gitlab::Application.configure do
config.action_controller.perform_caching = true
# Disable Rails's static asset server (Apache or nginx will already do this)
- config.serve_static_assets = false
+ config.serve_static_files = false
# Compress JavaScripts and CSS.
config.assets.js_compressor = :uglifier
@@ -32,7 +32,7 @@ Gitlab::Application.configure do
# config.force_ssl = true
# See everything in the log (default is :info)
- # config.log_level = :debug
+ config.log_level = :info
# Suppress 'Rendered template ...' messages in the log
# source: http://stackoverflow.com/a/16369363
diff --git a/config/environments/test.rb b/config/environments/test.rb
index 955540837d3..f96ac6f9753 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -1,4 +1,4 @@
-Gitlab::Application.configure do
+Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb
# The test environment is used exclusively to run your application's
@@ -8,7 +8,7 @@ Gitlab::Application.configure do
config.cache_classes = false
# Configure static asset server for tests with Cache-Control for performance
- config.serve_static_assets = true
+ config.serve_static_files = true
config.static_cache_control = "public, max-age=3600"
# Show full error reports and disable caching
@@ -32,4 +32,6 @@ Gitlab::Application.configure do
config.eager_load = false
config.cache_store = :null_store
+
+ config.active_job.queue_adapter = :test
end
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 8fdb2603ce8..1da42ab38f3 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -124,9 +124,15 @@ production: &base
# The mailbox where incoming mail will end up. Usually "inbox".
mailbox: "inbox"
+ ## Build Artifacts
+ artifacts:
+ enabled: true
+ # The location where build artifacts are stored (default: shared/artifacts).
+ # path: shared/artifacts
+
## Git LFS
lfs:
- enabled: false
+ enabled: true
# The location where LFS objects are stored (default: shared/lfs-objects).
# storage_path: shared/lfs-objects
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 6b7990c0ab0..63d8ae17436 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -33,13 +33,15 @@ class Settings < Settingslogic
end
def build_gitlab_shell_ssh_path_prefix
+ user_host = "#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}"
+
if gitlab_shell.ssh_port != 22
- "ssh://#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}:#{gitlab_shell.ssh_port}/"
+ "ssh://#{user_host}:#{gitlab_shell.ssh_port}/"
else
if gitlab_shell.ssh_host.include? ':'
- "[#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}]:"
+ "[#{user_host}]:"
else
- "#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}:"
+ "#{user_host}:"
end
end
end
@@ -162,7 +164,7 @@ Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].
Settings.gitlab['twitter_sharing_enabled'] ||= true if Settings.gitlab['twitter_sharing_enabled'].nil?
Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
-Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)' if Settings.gitlab['issue_closing_pattern'].nil?
+Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)' if Settings.gitlab['issue_closing_pattern'].nil?
Settings.gitlab['default_projects_features'] ||= {}
Settings.gitlab['webhook_timeout'] ||= 10
Settings.gitlab['max_attachment_size'] ||= 10
@@ -187,7 +189,6 @@ Settings.gitlab_ci['all_broken_builds'] = true if Settings.gitlab_ci['all_br
Settings.gitlab_ci['add_pusher'] = false if Settings.gitlab_ci['add_pusher'].nil?
Settings.gitlab_ci['url'] ||= Settings.send(:build_gitlab_ci_url)
Settings.gitlab_ci['builds_path'] = File.expand_path(Settings.gitlab_ci['builds_path'] || "builds/", Rails.root)
-Settings.gitlab_ci['max_artifacts_size'] ||= 100 # in megabytes
#
# Reply by email
@@ -200,10 +201,18 @@ Settings.incoming_email['start_tls'] = false if Settings.incoming_email['start_
Settings.incoming_email['mailbox'] = "inbox" if Settings.incoming_email['mailbox'].nil?
#
+# Build Artifacts
+#
+Settings['artifacts'] ||= Settingslogic.new({})
+Settings.artifacts['enabled'] = true if Settings.artifacts['enabled'].nil?
+Settings.artifacts['path'] = File.expand_path(Settings.artifacts['path'] || File.join(Settings.shared['path'], "artifacts"), Rails.root)
+Settings.artifacts['max_size'] ||= 100 # in megabytes
+
+#
# Git LFS
#
Settings['lfs'] ||= Settingslogic.new({})
-Settings.lfs['enabled'] = false if Settings.lfs['enabled'].nil?
+Settings.lfs['enabled'] = true if Settings.lfs['enabled'].nil?
Settings.lfs['storage_path'] = File.expand_path(Settings.lfs['storage_path'] || File.join(Settings.shared['path'], "lfs-objects"), Rails.root)
#
@@ -286,3 +295,12 @@ if Rails.env.test?
Settings.gitlab['default_can_create_group'] = true
Settings.gitlab['default_can_create_team'] = false
end
+
+# Force a refresh of application settings at startup
+begin
+ ApplicationSetting.expire
+ Ci::ApplicationSetting.expire
+rescue
+ # Gracefully handle when Redis is not available. For example,
+ # omnibus may fail here during assets:precompile.
+end
diff --git a/config/initializers/cookies_serializer.rb b/config/initializers/cookies_serializer.rb
index 43adac8b2c6..54516e3f23d 100644
--- a/config/initializers/cookies_serializer.rb
+++ b/config/initializers/cookies_serializer.rb
@@ -1,3 +1,3 @@
# Be sure to restart your server when you modify this file.
-Gitlab::Application.config.action_dispatch.cookies_serializer = :hybrid
+Rails.application.config.action_dispatch.cookies_serializer = :hybrid
diff --git a/config/initializers/default_url_options.rb b/config/initializers/default_url_options.rb
index f9f88f95db9..8fd27b1d88e 100644
--- a/config/initializers/default_url_options.rb
+++ b/config/initializers/default_url_options.rb
@@ -8,4 +8,4 @@ unless Gitlab.config.gitlab_on_standard_port?
default_url_options[:port] = Gitlab.config.gitlab.port
end
-Gitlab::Application.routes.default_url_options = default_url_options
+Rails.application.routes.default_url_options = default_url_options
diff --git a/config/initializers/rack_attack.rb.example b/config/initializers/rack_attack.rb.example
index 2155ea14562..b1bbcca1d61 100644
--- a/config/initializers/rack_attack.rb.example
+++ b/config/initializers/rack_attack.rb.example
@@ -4,13 +4,13 @@
# If you change this file in a Merge Request, please also create a Merge Request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests
paths_to_be_protected = [
- "#{Gitlab::Application.config.relative_url_root}/users/password",
- "#{Gitlab::Application.config.relative_url_root}/users/sign_in",
- "#{Gitlab::Application.config.relative_url_root}/api/#{API::API.version}/session.json",
- "#{Gitlab::Application.config.relative_url_root}/api/#{API::API.version}/session",
- "#{Gitlab::Application.config.relative_url_root}/users",
- "#{Gitlab::Application.config.relative_url_root}/users/confirmation",
- "#{Gitlab::Application.config.relative_url_root}/unsubscribes/"
+ "#{Rails.application.config.relative_url_root}/users/password",
+ "#{Rails.application.config.relative_url_root}/users/sign_in",
+ "#{Rails.application.config.relative_url_root}/api/#{API::API.version}/session.json",
+ "#{Rails.application.config.relative_url_root}/api/#{API::API.version}/session",
+ "#{Rails.application.config.relative_url_root}/users",
+ "#{Rails.application.config.relative_url_root}/users/confirmation",
+ "#{Rails.application.config.relative_url_root}/unsubscribes/"
]
diff --git a/config/initializers/rack_lineprof.rb b/config/initializers/rack_lineprof.rb
index f0c006d811b..22e77a32c61 100644
--- a/config/initializers/rack_lineprof.rb
+++ b/config/initializers/rack_lineprof.rb
@@ -2,7 +2,7 @@
# with darker backgrounds. This patch tweaks the colors a bit so the output is
# actually readable.
if Rails.env.development? and RUBY_ENGINE == 'ruby' and ENV['ENABLE_LINEPROF']
- Gitlab::Application.config.middleware.use(Rack::Lineprof)
+ Rails.application.config.middleware.use(Rack::Lineprof)
module Rack
class Lineprof
diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb
index 1b518c3becf..dae3a4a9a93 100644
--- a/config/initializers/secret_token.rb
+++ b/config/initializers/secret_token.rb
@@ -22,15 +22,15 @@ def find_secure_token
end
end
-Gitlab::Application.config.secret_token = find_secure_token
-Gitlab::Application.config.secret_key_base = find_secure_token
+Rails.application.config.secret_token = find_secure_token
+Rails.application.config.secret_key_base = find_secure_token
# CI
def generate_new_secure_token
SecureRandom.hex(64)
end
-if Gitlab::Application.secrets.db_key_base.blank?
+if Rails.application.secrets.db_key_base.blank?
warn "Missing `db_key_base` for '#{Rails.env}' environment. The secrets will be generated and stored in `config/secrets.yml`"
all_secrets = YAML.load_file('config/secrets.yml') if File.exist?('config/secrets.yml')
@@ -46,5 +46,5 @@ if Gitlab::Application.secrets.db_key_base.blank?
file.write(YAML.dump(all_secrets))
end
- Gitlab::Application.secrets.db_key_base = env_secrets['db_key_base']
+ Rails.application.secrets.db_key_base = env_secrets['db_key_base']
end
diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb
index d7c5432da76..d5208b8c93e 100644
--- a/config/initializers/session_store.rb
+++ b/config/initializers/session_store.rb
@@ -3,20 +3,21 @@
require 'gitlab/current_settings'
include Gitlab::CurrentSettings
-# allow it to fail: it may to do so when create_from_defaults is executed before migrations are actually done
+# allow it to fail: it may do so when create_from_defaults is executed before migrations are actually done
begin
- Settings.gitlab['session_expire_delay'] = current_application_settings.session_expire_delay
+ Settings.gitlab['session_expire_delay'] = current_application_settings.session_expire_delay || 10080
rescue
+ Settings.gitlab['session_expire_delay'] ||= 10080
end
unless Rails.env.test?
Gitlab::Application.config.session_store(
:redis_store, # Using the cookie_store would enable session replay attacks.
- servers: Gitlab::Application.config.cache_store[1].merge(namespace: 'session:gitlab'), # re-use the Redis config from the Rails cache store
+ servers: Rails.application.config.cache_store[1].merge(namespace: 'session:gitlab'), # re-use the Redis config from the Rails cache store
key: '_gitlab_session',
secure: Gitlab.config.gitlab.https,
httponly: true,
expire_after: Settings.gitlab['session_expire_delay'] * 60,
- path: (Gitlab::Application.config.relative_url_root.nil?) ? '/' : Gitlab::Application.config.relative_url_root
+ path: (Rails.application.config.relative_url_root.nil?) ? '/' : Gitlab::Application.config.relative_url_root
)
end
diff --git a/config/initializers/sherlock.rb b/config/initializers/sherlock.rb
index 42b0d78c85f..8f2ababb712 100644
--- a/config/initializers/sherlock.rb
+++ b/config/initializers/sherlock.rb
@@ -1,5 +1,5 @@
if Gitlab::Sherlock.enabled?
- Gitlab::Application.configure do |config|
+ Rails.application.configure do |config|
config.middleware.use(Gitlab::Sherlock::Middleware)
end
end
diff --git a/config/initializers/smtp_settings.rb.sample b/config/initializers/smtp_settings.rb.sample
index 25ec247a095..ec182502d4e 100644
--- a/config/initializers/smtp_settings.rb.sample
+++ b/config/initializers/smtp_settings.rb.sample
@@ -8,7 +8,7 @@
# If you change this file in a Merge Request, please also create a Merge Request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests
if Rails.env.production?
- Gitlab::Application.config.action_mailer.delivery_method = :smtp
+ Rails.application.config.action_mailer.delivery_method = :smtp
ActionMailer::Base.smtp_settings = {
address: "email.server.com",
diff --git a/config/initializers/static_files.rb b/config/initializers/static_files.rb
index e6d5600edb7..d6dbf8b9fbf 100644
--- a/config/initializers/static_files.rb
+++ b/config/initializers/static_files.rb
@@ -1,6 +1,6 @@
-app = Gitlab::Application
+app = Rails.application
-if app.config.serve_static_assets
+if app.config.serve_static_files
# The `ActionDispatch::Static` middleware intercepts requests for static files
# by checking if they exist in the `/public` directory.
# We're replacing it with our `Gitlab::Middleware::Static` that does the same,
diff --git a/config/locales/sherlock.en.yml b/config/locales/sherlock.en.yml
index 683b09dc329..f24b825f585 100644
--- a/config/locales/sherlock.en.yml
+++ b/config/locales/sherlock.en.yml
@@ -35,3 +35,4 @@ en:
events: Events
percent: '%'
count: Count
+ query_time: Query Time
diff --git a/config/routes.rb b/config/routes.rb
index 81f568cfa74..6bb0d31df2e 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,7 +1,7 @@
require 'sidekiq/web'
require 'api/api'
-Gitlab::Application.routes.draw do
+Rails.application.routes.draw do
if Gitlab::Sherlock.enabled?
namespace :sherlock do
resources :transactions, only: [:index, :show] do
@@ -665,6 +665,10 @@ Gitlab::Application.routes.draw do
member do
delete :delete_attachment
end
+
+ collection do
+ post :award_toggle
+ end
end
resources :uploads, only: [:create] do
diff --git a/db/migrate/20151106000015_add_is_award_to_notes.rb b/db/migrate/20151106000015_add_is_award_to_notes.rb
new file mode 100644
index 00000000000..02b271637e9
--- /dev/null
+++ b/db/migrate/20151106000015_add_is_award_to_notes.rb
@@ -0,0 +1,6 @@
+class AddIsAwardToNotes < ActiveRecord::Migration
+ def change
+ add_column :notes, :is_award, :boolean, default: false, null: false
+ add_index :notes, :is_award
+ end
+end
diff --git a/db/migrate/20151109134526_add_issues_state_index.rb b/db/migrate/20151109134526_add_issues_state_index.rb
new file mode 100644
index 00000000000..1c4d2e30171
--- /dev/null
+++ b/db/migrate/20151109134526_add_issues_state_index.rb
@@ -0,0 +1,5 @@
+class AddIssuesStateIndex < ActiveRecord::Migration
+ def change
+ add_index :issues, :state
+ end
+end
diff --git a/db/migrate/20151109134916_add_projects_visibility_level_index.rb b/db/migrate/20151109134916_add_projects_visibility_level_index.rb
new file mode 100644
index 00000000000..600b4bafd98
--- /dev/null
+++ b/db/migrate/20151109134916_add_projects_visibility_level_index.rb
@@ -0,0 +1,5 @@
+class AddProjectsVisibilityLevelIndex < ActiveRecord::Migration
+ def change
+ add_index :projects, :visibility_level
+ end
+end
diff --git a/db/migrate/20151110125604_add_import_error_to_project.rb b/db/migrate/20151110125604_add_import_error_to_project.rb
new file mode 100644
index 00000000000..7fc990f8d0a
--- /dev/null
+++ b/db/migrate/20151110125604_add_import_error_to_project.rb
@@ -0,0 +1,5 @@
+class AddImportErrorToProject < ActiveRecord::Migration
+ def change
+ add_column :projects, :import_error, :text
+ end
+end
diff --git a/db/migrate/20151118162244_add_projects_public_index.rb b/db/migrate/20151118162244_add_projects_public_index.rb
new file mode 100644
index 00000000000..fded70e3c0c
--- /dev/null
+++ b/db/migrate/20151118162244_add_projects_public_index.rb
@@ -0,0 +1,5 @@
+class AddProjectsPublicIndex < ActiveRecord::Migration
+ def change
+ add_index :namespaces, :public
+ end
+end
diff --git a/db/migrate/20151203162133_add_hide_project_limit_to_users.rb b/db/migrate/20151203162133_add_hide_project_limit_to_users.rb
new file mode 100644
index 00000000000..6ffadfa1894
--- /dev/null
+++ b/db/migrate/20151203162133_add_hide_project_limit_to_users.rb
@@ -0,0 +1,5 @@
+class AddHideProjectLimitToUsers < ActiveRecord::Migration
+ def change
+ add_column :users, :hide_project_limit, :boolean, default: false
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 7d5ed559754..fb59e187625 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,12 +11,12 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20151116144118) do
+ActiveRecord::Schema.define(version: 20151203162133) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
- create_table "abuse_reports", force: true do |t|
+ create_table "abuse_reports", force: :cascade do |t|
t.integer "reporter_id"
t.integer "user_id"
t.text "message"
@@ -24,7 +24,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
t.datetime "updated_at"
end
- create_table "application_settings", force: true do |t|
+ create_table "application_settings", force: :cascade do |t|
t.integer "default_projects_limit"
t.boolean "signup_enabled"
t.boolean "signin_enabled"
@@ -51,7 +51,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
t.integer "max_artifacts_size", default: 100, null: false
end
- create_table "audit_events", force: true do |t|
+ create_table "audit_events", force: :cascade do |t|
t.integer "author_id", null: false
t.string "type", null: false
t.integer "entity_id", null: false
@@ -65,7 +65,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "audit_events", ["entity_id", "entity_type"], name: "index_audit_events_on_entity_id_and_entity_type", using: :btree
add_index "audit_events", ["type"], name: "index_audit_events_on_type", using: :btree
- create_table "broadcast_messages", force: true do |t|
+ create_table "broadcast_messages", force: :cascade do |t|
t.text "message", null: false
t.datetime "starts_at"
t.datetime "ends_at"
@@ -76,14 +76,14 @@ ActiveRecord::Schema.define(version: 20151116144118) do
t.string "font"
end
- create_table "ci_application_settings", force: true do |t|
+ create_table "ci_application_settings", force: :cascade do |t|
t.boolean "all_broken_builds"
t.boolean "add_pusher"
t.datetime "created_at"
t.datetime "updated_at"
end
- create_table "ci_builds", force: true do |t|
+ create_table "ci_builds", force: :cascade do |t|
t.integer "project_id"
t.string "status"
t.datetime "finished_at"
@@ -123,7 +123,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "ci_builds", ["status"], name: "index_ci_builds_on_status", using: :btree
add_index "ci_builds", ["type"], name: "index_ci_builds_on_type", using: :btree
- create_table "ci_commits", force: true do |t|
+ create_table "ci_commits", force: :cascade do |t|
t.integer "project_id"
t.string "ref"
t.string "sha"
@@ -144,7 +144,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "ci_commits", ["project_id"], name: "index_ci_commits_on_project_id", using: :btree
add_index "ci_commits", ["sha"], name: "index_ci_commits_on_sha", using: :btree
- create_table "ci_events", force: true do |t|
+ create_table "ci_events", force: :cascade do |t|
t.integer "project_id"
t.integer "user_id"
t.integer "is_admin"
@@ -157,7 +157,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "ci_events", ["is_admin"], name: "index_ci_events_on_is_admin", using: :btree
add_index "ci_events", ["project_id"], name: "index_ci_events_on_project_id", using: :btree
- create_table "ci_jobs", force: true do |t|
+ create_table "ci_jobs", force: :cascade do |t|
t.integer "project_id", null: false
t.text "commands"
t.boolean "active", default: true, null: false
@@ -174,7 +174,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "ci_jobs", ["deleted_at"], name: "index_ci_jobs_on_deleted_at", using: :btree
add_index "ci_jobs", ["project_id"], name: "index_ci_jobs_on_project_id", using: :btree
- create_table "ci_projects", force: true do |t|
+ create_table "ci_projects", force: :cascade do |t|
t.string "name"
t.integer "timeout", default: 3600, null: false
t.datetime "created_at"
@@ -200,7 +200,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "ci_projects", ["gitlab_id"], name: "index_ci_projects_on_gitlab_id", using: :btree
add_index "ci_projects", ["shared_runners_enabled"], name: "index_ci_projects_on_shared_runners_enabled", using: :btree
- create_table "ci_runner_projects", force: true do |t|
+ create_table "ci_runner_projects", force: :cascade do |t|
t.integer "runner_id", null: false
t.integer "project_id", null: false
t.datetime "created_at"
@@ -210,7 +210,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "ci_runner_projects", ["project_id"], name: "index_ci_runner_projects_on_project_id", using: :btree
add_index "ci_runner_projects", ["runner_id"], name: "index_ci_runner_projects_on_runner_id", using: :btree
- create_table "ci_runners", force: true do |t|
+ create_table "ci_runners", force: :cascade do |t|
t.string "token"
t.datetime "created_at"
t.datetime "updated_at"
@@ -225,7 +225,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
t.string "architecture"
end
- create_table "ci_services", force: true do |t|
+ create_table "ci_services", force: :cascade do |t|
t.string "type"
t.string "title"
t.integer "project_id", null: false
@@ -237,7 +237,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "ci_services", ["project_id"], name: "index_ci_services_on_project_id", using: :btree
- create_table "ci_sessions", force: true do |t|
+ create_table "ci_sessions", force: :cascade do |t|
t.string "session_id", null: false
t.text "data"
t.datetime "created_at"
@@ -247,7 +247,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "ci_sessions", ["session_id"], name: "index_ci_sessions_on_session_id", using: :btree
add_index "ci_sessions", ["updated_at"], name: "index_ci_sessions_on_updated_at", using: :btree
- create_table "ci_taggings", force: true do |t|
+ create_table "ci_taggings", force: :cascade do |t|
t.integer "tag_id"
t.integer "taggable_id"
t.string "taggable_type"
@@ -260,14 +260,14 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "ci_taggings", ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], name: "ci_taggings_idx", unique: true, using: :btree
add_index "ci_taggings", ["taggable_id", "taggable_type", "context"], name: "index_ci_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree
- create_table "ci_tags", force: true do |t|
+ create_table "ci_tags", force: :cascade do |t|
t.string "name"
t.integer "taggings_count", default: 0
end
add_index "ci_tags", ["name"], name: "index_ci_tags_on_name", unique: true, using: :btree
- create_table "ci_trigger_requests", force: true do |t|
+ create_table "ci_trigger_requests", force: :cascade do |t|
t.integer "trigger_id", null: false
t.text "variables"
t.datetime "created_at"
@@ -275,7 +275,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
t.integer "commit_id"
end
- create_table "ci_triggers", force: true do |t|
+ create_table "ci_triggers", force: :cascade do |t|
t.string "token"
t.integer "project_id", null: false
t.datetime "deleted_at"
@@ -285,7 +285,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "ci_triggers", ["deleted_at"], name: "index_ci_triggers_on_deleted_at", using: :btree
- create_table "ci_variables", force: true do |t|
+ create_table "ci_variables", force: :cascade do |t|
t.integer "project_id", null: false
t.string "key"
t.text "value"
@@ -296,14 +296,14 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "ci_variables", ["project_id"], name: "index_ci_variables_on_project_id", using: :btree
- create_table "ci_web_hooks", force: true do |t|
+ create_table "ci_web_hooks", force: :cascade do |t|
t.string "url", null: false
t.integer "project_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
end
- create_table "deploy_keys_projects", force: true do |t|
+ create_table "deploy_keys_projects", force: :cascade do |t|
t.integer "deploy_key_id", null: false
t.integer "project_id", null: false
t.datetime "created_at"
@@ -312,7 +312,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "deploy_keys_projects", ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree
- create_table "emails", force: true do |t|
+ create_table "emails", force: :cascade do |t|
t.integer "user_id", null: false
t.string "email", null: false
t.datetime "created_at"
@@ -322,7 +322,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "emails", ["email"], name: "index_emails_on_email", unique: true, using: :btree
add_index "emails", ["user_id"], name: "index_emails_on_user_id", using: :btree
- create_table "events", force: true do |t|
+ create_table "events", force: :cascade do |t|
t.string "target_type"
t.integer "target_id"
t.string "title"
@@ -341,7 +341,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "events", ["target_id"], name: "index_events_on_target_id", using: :btree
add_index "events", ["target_type"], name: "index_events_on_target_type", using: :btree
- create_table "forked_project_links", force: true do |t|
+ create_table "forked_project_links", force: :cascade do |t|
t.integer "forked_to_project_id", null: false
t.integer "forked_from_project_id", null: false
t.datetime "created_at"
@@ -350,7 +350,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree
- create_table "identities", force: true do |t|
+ create_table "identities", force: :cascade do |t|
t.string "extern_uid"
t.string "provider"
t.integer "user_id"
@@ -361,7 +361,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "identities", ["created_at", "id"], name: "index_identities_on_created_at_and_id", using: :btree
add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree
- create_table "issues", force: true do |t|
+ create_table "issues", force: :cascade do |t|
t.string "title"
t.integer "assignee_id"
t.integer "author_id"
@@ -384,9 +384,10 @@ ActiveRecord::Schema.define(version: 20151116144118) do
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
add_index "issues", ["project_id"], name: "index_issues_on_project_id", using: :btree
+ add_index "issues", ["state"], name: "index_issues_on_state", using: :btree
add_index "issues", ["title"], name: "index_issues_on_title", using: :btree
- create_table "keys", force: true do |t|
+ create_table "keys", force: :cascade do |t|
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
@@ -400,7 +401,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "keys", ["created_at", "id"], name: "index_keys_on_created_at_and_id", using: :btree
add_index "keys", ["user_id"], name: "index_keys_on_user_id", using: :btree
- create_table "label_links", force: true do |t|
+ create_table "label_links", force: :cascade do |t|
t.integer "label_id"
t.integer "target_id"
t.string "target_type"
@@ -411,7 +412,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "label_links", ["label_id"], name: "index_label_links_on_label_id", using: :btree
add_index "label_links", ["target_id", "target_type"], name: "index_label_links_on_target_id_and_target_type", using: :btree
- create_table "labels", force: true do |t|
+ create_table "labels", force: :cascade do |t|
t.string "title"
t.string "color"
t.integer "project_id"
@@ -422,7 +423,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree
- create_table "lfs_objects", force: true do |t|
+ create_table "lfs_objects", force: :cascade do |t|
t.string "oid", null: false
t.integer "size", null: false
t.datetime "created_at"
@@ -432,7 +433,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "lfs_objects", ["oid"], name: "index_lfs_objects_on_oid", unique: true, using: :btree
- create_table "lfs_objects_projects", force: true do |t|
+ create_table "lfs_objects_projects", force: :cascade do |t|
t.integer "lfs_object_id", null: false
t.integer "project_id", null: false
t.datetime "created_at"
@@ -441,7 +442,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "lfs_objects_projects", ["project_id"], name: "index_lfs_objects_projects_on_project_id", using: :btree
- create_table "members", force: true do |t|
+ create_table "members", force: :cascade do |t|
t.integer "access_level", null: false
t.integer "source_id", null: false
t.string "source_type", null: false
@@ -463,7 +464,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "members", ["type"], name: "index_members_on_type", using: :btree
add_index "members", ["user_id"], name: "index_members_on_user_id", using: :btree
- create_table "merge_request_diffs", force: true do |t|
+ create_table "merge_request_diffs", force: :cascade do |t|
t.string "state"
t.text "st_commits"
t.text "st_diffs"
@@ -474,10 +475,10 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", unique: true, using: :btree
- create_table "merge_requests", force: true do |t|
- t.string "target_branch", null: false
- t.string "source_branch", null: false
- t.integer "source_project_id", null: false
+ create_table "merge_requests", force: :cascade do |t|
+ t.string "target_branch", null: false
+ t.string "source_branch", null: false
+ t.integer "source_project_id", null: false
t.integer "author_id"
t.integer "assignee_id"
t.string "title"
@@ -486,16 +487,13 @@ ActiveRecord::Schema.define(version: 20151116144118) do
t.integer "milestone_id"
t.string "state"
t.string "merge_status"
- t.integer "target_project_id", null: false
+ t.integer "target_project_id", null: false
t.integer "iid"
t.text "description"
- t.integer "position", default: 0
+ t.integer "position", default: 0
t.datetime "locked_at"
t.integer "updated_by_id"
t.string "merge_error"
- t.text "merge_params"
- t.boolean "merge_when_build_succeeds", default: false, null: false
- t.integer "merge_user_id"
end
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
@@ -509,7 +507,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "merge_requests", ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid", unique: true, using: :btree
add_index "merge_requests", ["title"], name: "index_merge_requests_on_title", using: :btree
- create_table "milestones", force: true do |t|
+ create_table "milestones", force: :cascade do |t|
t.string "title", null: false
t.integer "project_id", null: false
t.text "description"
@@ -525,7 +523,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "milestones", ["project_id", "iid"], name: "index_milestones_on_project_id_and_iid", unique: true, using: :btree
add_index "milestones", ["project_id"], name: "index_milestones_on_project_id", using: :btree
- create_table "namespaces", force: true do |t|
+ create_table "namespaces", force: :cascade do |t|
t.string "name", null: false
t.string "path", null: false
t.integer "owner_id"
@@ -541,9 +539,10 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "namespaces", ["name"], name: "index_namespaces_on_name", unique: true, using: :btree
add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree
add_index "namespaces", ["path"], name: "index_namespaces_on_path", unique: true, using: :btree
+ add_index "namespaces", ["public"], name: "index_namespaces_on_public", using: :btree
add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree
- create_table "notes", force: true do |t|
+ create_table "notes", force: :cascade do |t|
t.text "note"
t.string "noteable_type"
t.integer "author_id"
@@ -557,12 +556,14 @@ ActiveRecord::Schema.define(version: 20151116144118) do
t.boolean "system", default: false, null: false
t.text "st_diff"
t.integer "updated_by_id"
+ t.boolean "is_award", default: false, null: false
end
add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree
add_index "notes", ["commit_id"], name: "index_notes_on_commit_id", using: :btree
add_index "notes", ["created_at", "id"], name: "index_notes_on_created_at_and_id", using: :btree
add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree
+ add_index "notes", ["is_award"], name: "index_notes_on_is_award", using: :btree
add_index "notes", ["line_code"], name: "index_notes_on_line_code", using: :btree
add_index "notes", ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree
add_index "notes", ["noteable_type"], name: "index_notes_on_noteable_type", using: :btree
@@ -570,7 +571,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "notes", ["project_id"], name: "index_notes_on_project_id", using: :btree
add_index "notes", ["updated_at"], name: "index_notes_on_updated_at", using: :btree
- create_table "oauth_access_grants", force: true do |t|
+ create_table "oauth_access_grants", force: :cascade do |t|
t.integer "resource_owner_id", null: false
t.integer "application_id", null: false
t.string "token", null: false
@@ -583,7 +584,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "oauth_access_grants", ["token"], name: "index_oauth_access_grants_on_token", unique: true, using: :btree
- create_table "oauth_access_tokens", force: true do |t|
+ create_table "oauth_access_tokens", force: :cascade do |t|
t.integer "resource_owner_id"
t.integer "application_id"
t.string "token", null: false
@@ -598,7 +599,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "oauth_access_tokens", ["resource_owner_id"], name: "index_oauth_access_tokens_on_resource_owner_id", using: :btree
add_index "oauth_access_tokens", ["token"], name: "index_oauth_access_tokens_on_token", unique: true, using: :btree
- create_table "oauth_applications", force: true do |t|
+ create_table "oauth_applications", force: :cascade do |t|
t.string "name", null: false
t.string "uid", null: false
t.string "secret", null: false
@@ -613,12 +614,12 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "oauth_applications", ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type", using: :btree
add_index "oauth_applications", ["uid"], name: "index_oauth_applications_on_uid", unique: true, using: :btree
- create_table "project_import_data", force: true do |t|
+ create_table "project_import_data", force: :cascade do |t|
t.integer "project_id"
t.text "data"
end
- create_table "projects", force: true do |t|
+ create_table "projects", force: :cascade do |t|
t.string "name"
t.string "path"
t.text "description"
@@ -644,6 +645,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
t.string "import_type"
t.string "import_source"
t.integer "commit_count", default: 0
+ t.text "import_error"
end
add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree
@@ -652,8 +654,9 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "projects", ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree
add_index "projects", ["path"], name: "index_projects_on_path", using: :btree
add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree
+ add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree
- create_table "protected_branches", force: true do |t|
+ create_table "protected_branches", force: :cascade do |t|
t.integer "project_id", null: false
t.string "name", null: false
t.datetime "created_at"
@@ -663,7 +666,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree
- create_table "releases", force: true do |t|
+ create_table "releases", force: :cascade do |t|
t.string "tag"
t.text "description"
t.integer "project_id"
@@ -674,7 +677,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "releases", ["project_id", "tag"], name: "index_releases_on_project_id_and_tag", using: :btree
add_index "releases", ["project_id"], name: "index_releases_on_project_id", using: :btree
- create_table "sent_notifications", force: true do |t|
+ create_table "sent_notifications", force: :cascade do |t|
t.integer "project_id"
t.integer "noteable_id"
t.string "noteable_type"
@@ -686,7 +689,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "sent_notifications", ["reply_key"], name: "index_sent_notifications_on_reply_key", unique: true, using: :btree
- create_table "services", force: true do |t|
+ create_table "services", force: :cascade do |t|
t.string "type"
t.string "title"
t.integer "project_id"
@@ -706,7 +709,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree
add_index "services", ["template"], name: "index_services_on_template", using: :btree
- create_table "snippets", force: true do |t|
+ create_table "snippets", force: :cascade do |t|
t.string "title"
t.text "content"
t.integer "author_id", null: false
@@ -726,7 +729,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree
add_index "snippets", ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree
- create_table "subscriptions", force: true do |t|
+ create_table "subscriptions", force: :cascade do |t|
t.integer "user_id"
t.integer "subscribable_id"
t.string "subscribable_type"
@@ -737,7 +740,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "subscriptions", ["subscribable_id", "subscribable_type", "user_id"], name: "subscriptions_user_id_and_ref_fields", unique: true, using: :btree
- create_table "taggings", force: true do |t|
+ create_table "taggings", force: :cascade do |t|
t.integer "tag_id"
t.integer "taggable_id"
t.string "taggable_type"
@@ -750,14 +753,14 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "taggings", ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], name: "taggings_idx", unique: true, using: :btree
add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree
- create_table "tags", force: true do |t|
+ create_table "tags", force: :cascade do |t|
t.string "name"
t.integer "taggings_count", default: 0
end
add_index "tags", ["name"], name: "index_tags_on_name", unique: true, using: :btree
- create_table "users", force: true do |t|
+ create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
@@ -811,6 +814,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
t.integer "project_view", default: 0
t.integer "consumed_timestep"
t.integer "layout", default: 0
+ t.boolean "hide_project_limit", default: false
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
@@ -823,7 +827,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
add_index "users", ["username"], name: "index_users_on_username", using: :btree
- create_table "users_star_projects", force: true do |t|
+ create_table "users_star_projects", force: :cascade do |t|
t.integer "project_id", null: false
t.integer "user_id", null: false
t.datetime "created_at"
@@ -834,7 +838,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do
add_index "users_star_projects", ["user_id", "project_id"], name: "index_users_star_projects_on_user_id_and_project_id", unique: true, using: :btree
add_index "users_star_projects", ["user_id"], name: "index_users_star_projects_on_user_id", using: :btree
- create_table "web_hooks", force: true do |t|
+ create_table "web_hooks", force: :cascade do |t|
t.string "url"
t.integer "project_id"
t.datetime "created_at"
diff --git a/doc/README.md b/doc/README.md
index 0f6866475f7..58ab5dd08e0 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -50,6 +50,7 @@
- [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page.
- [Reply by email](incoming_email/README.md) Allow users to comment on issues and merge requests by replying to notification emails.
- [Migrate GitLab CI to CE/EE](migrate_ci_to_ce/README.md) Follow this guide to migrate your existing GitLab CI data to GitLab CE/EE.
+- [Git LFS configuration](workflow/lfs/lfs_administration.md)
## Contributor documentation
diff --git a/doc/api/README.md b/doc/api/README.md
index 6b8528de50c..25a31b235cc 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -10,6 +10,7 @@
- [Repositories](repositories.md)
- [Repository Files](repository_files.md)
- [Commits](commits.md)
+- [Tags](tags.md)
- [Branches](branches.md)
- [Merge Requests](merge_requests.md)
- [Issues](issues.md)
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 19e6fbb7887..366a1f8abec 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -2,9 +2,10 @@
## List merge requests
-Get all merge requests for this project.
+Get all merge requests for this project.
The `state` parameter can be used to get only merge requests with a given state (`opened`, `closed`, or `merged`) or all of them (`all`).
-The pagination parameters `page` and `per_page` can be used to restrict the list of merge requests.
+The pagination parameters `page` and `per_page` can be used to restrict the list of merge requests. With GitLab 8.2 the return fields `upvotes` and
+`downvotes` are deprecated and always return `0`.
```
GET /projects/:id/merge_requests
@@ -57,7 +58,7 @@ Parameters:
## Get single MR
-Shows information about a single merge request.
+Shows information about a single merge request. With GitLab 8.2 the return fields `upvotes` and `downvotes` are deprecated and always return `0`.
```
GET /projects/:id/merge_request/:merge_request_id
@@ -100,9 +101,48 @@ Parameters:
}
```
+## Get single MR commits
+
+Get a list of merge request commits.
+
+```
+GET /projects/:id/merge_request/:merge_request_id/commits
+```
+
+Parameters:
+
+- `id` (required) - The ID of a project
+- `merge_request_id` (required) - The ID of MR
+
+
+```json
+[
+ {
+ "id": "ed899a2f4b50b4370feeea94676502b42383c746",
+ "short_id": "ed899a2f4b5",
+ "title": "Replace sanitize with escape once",
+ "author_name": "Dmitriy Zaporozhets",
+ "author_email": "dzaporozhets@sphereconsultinginc.com",
+ "created_at": "2012-09-20T11:50:22+03:00",
+ "message": "Replace sanitize with escape once"
+ },
+ {
+ "id": "6104942438c14ec7bd21c6cd5bd995272b3faff6",
+ "short_id": "6104942438c",
+ "title": "Sanitize for network graph",
+ "author_name": "randx",
+ "author_email": "dmitriy.zaporozhets@gmail.com",
+ "created_at": "2012-09-20T09:06:12+03:00",
+ "message": "Sanitize for network graph"
+ }
+]
+```
+
## Get single MR changes
-Shows information about the merge request including its files and changes
+Shows information about the merge request including its files and changes.
+With GitLab 8.2 the return fields `upvotes` and `downvotes` are deprecated and
+always return `0`.
```
GET /projects/:id/merge_request/:merge_request_id/changes
@@ -156,7 +196,7 @@ Parameters:
"updated_at": "2015-02-02T19:49:26.013Z",
"due_date": null
},
- "files": [
+ "changes": [
{
"old_path": "VERSION",
"new_path": "VERSION",
@@ -173,7 +213,8 @@ Parameters:
## Create MR
-Creates a new merge request.
+Creates a new merge request. With GitLab 8.2 the return fields `upvotes` and `
+downvotes` are deprecated and always return `0`.
```
POST /projects/:id/merge_requests
@@ -225,7 +266,8 @@ If an error occurs, an error number and a message explaining the reason is retur
## Update MR
-Updates an existing merge request. You can change the target branch, title, or even close the MR.
+Updates an existing merge request. You can change the target branch, title, or even close the MR. With GitLab 8.2 the return fields `upvotes` and `downvotes`
+are deprecated and always return `0`.
```
PUT /projects/:id/merge_request/:merge_request_id
@@ -276,7 +318,8 @@ If an error occurs, an error number and a message explaining the reason is retur
## Accept MR
-Merge changes submitted with MR using this API.
+Merge changes submitted with MR using this API. With GitLab 8.2 the return
+fields `upvotes` and `downvotes` are deprecated and always return `0`.
If merge success you get `200 OK`.
diff --git a/doc/api/notes.md b/doc/api/notes.md
index c683cb883d4..4d7ef288df8 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -6,7 +6,8 @@ Notes are comments on snippets, issues or merge requests.
### List project issue notes
-Gets a list of all notes for a single issue.
+Gets a list of all notes for a single issue. With GitLab 8.2 the return fields
+`upvote` and `downvote` are deprecated and always return `false`.
```
GET /projects/:id/issues/:issue_id/notes
@@ -34,7 +35,9 @@ Parameters:
"created_at": "2013-10-02T09:22:45Z",
"system": true,
"upvote": false,
- "downvote": false
+ "downvote": false,
+ "noteable_id": 377,
+ "noteable_type": "Issue"
},
{
"id": 305,
@@ -49,9 +52,11 @@ Parameters:
"created_at": "2013-09-30T13:46:01Z"
},
"created_at": "2013-10-02T09:56:03Z",
- "system": false,
+ "system": true,
"upvote": false,
- "downvote": false
+ "downvote": false,
+ "noteable_id": 121,
+ "noteable_type": "Issue"
}
]
```
@@ -218,7 +223,12 @@ Parameters:
"state": "active",
"created_at": "2013-09-30T13:46:01Z"
},
- "created_at": "2013-10-02T08:57:14Z"
+ "created_at": "2013-10-02T08:57:14Z",
+ "system": false,
+ "upvote": false,
+ "downvote": false,
+ "noteable_id": 2,
+ "noteable_type": "MergeRequest"
}
```
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 755cc6525c2..42919a312ae 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -245,9 +245,17 @@ Parameters:
"target_id": 830,
"target_type": "Issue",
"author_id": 1,
- "author_username": "john",
"data": null,
- "target_title": "Public project search field"
+ "target_title": "Public project search field",
+ "author": {
+ "name": "Dmitriy Zaporozhets",
+ "username": "root",
+ "id": 1,
+ "state": "active",
+ "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png",
+ "web_url": "http://localhost:3000/u/root"
+ },
+ "author_username": "root"
},
{
"title": null,
@@ -256,6 +264,14 @@ Parameters:
"target_id": null,
"target_type": null,
"author_id": 1,
+ "author": {
+ "name": "Dmitriy Zaporozhets",
+ "username": "root",
+ "id": 1,
+ "state": "active",
+ "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png",
+ "web_url": "http://localhost:3000/u/root"
+ },
"author_username": "john",
"data": {
"before": "50d4420237a9de7be1304607147aec22e4a14af7",
@@ -292,9 +308,56 @@ Parameters:
"target_id": 840,
"target_type": "Issue",
"author_id": 1,
- "author_username": "john",
"data": null,
- "target_title": "Finish & merge Code search PR"
+ "target_title": "Finish & merge Code search PR",
+ "author": {
+ "name": "Dmitriy Zaporozhets",
+ "username": "root",
+ "id": 1,
+ "state": "active",
+ "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png",
+ "web_url": "http://localhost:3000/u/root"
+ },
+ "author_username": "root"
+ },
+ {
+ "title": null,
+ "project_id": 15,
+ "action_name": "commented on",
+ "target_id": 1312,
+ "target_type": "Note",
+ "author_id": 1,
+ "data": null,
+ "target_title": null,
+ "created_at": "2015-12-04T10:33:58.089Z",
+ "note": {
+ "id": 1312,
+ "body": "What an awesome day!",
+ "attachment": null,
+ "author": {
+ "name": "Dmitriy Zaporozhets",
+ "username": "root",
+ "id": 1,
+ "state": "active",
+ "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png",
+ "web_url": "http://localhost:3000/u/root"
+ },
+ "created_at": "2015-12-04T10:33:56.698Z",
+ "system": false,
+ "upvote": false,
+ "downvote": false,
+ "noteable_id": 377,
+ "noteable_type": "Issue"
+ },
+ "author": {
+ "name": "Dmitriy Zaporozhets",
+ "username": "root",
+ "id": 1,
+ "state": "active",
+ "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png",
+ "web_url": "http://localhost:3000/u/root"
+ },
+ "author_username": "root"
}
]
```
diff --git a/doc/api/tags.md b/doc/api/tags.md
index b5b90cf6b82..085d387e824 100644
--- a/doc/api/tags.md
+++ b/doc/api/tags.md
@@ -29,7 +29,7 @@ Parameters:
]
},
"release": {
- "tag": "1.0.0",
+ "tag_name": "1.0.0",
"description": "Amazing release. Wow"
},
"name": "v1.0.0",
@@ -70,7 +70,7 @@ Parameters:
]
},
"release": {
- "tag": "1.0.0",
+ "tag_name": "1.0.0",
"description": "Amazing release. Wow"
},
"name": "v1.0.0",
@@ -84,23 +84,48 @@ It returns 200 if the operation succeed. In case of an error,
405 with an explaining error message is returned.
-## New release
+## Create a new release
-Add release notes to the existing git tag
+Add release notes to the existing git tag. It returns 201 if the release is
+created successfully. If the tag does not exist, 404 is returned. If there
+already exists a release for the given tag, 409 is returned.
```
-PUT /projects/:id/repository/:tag/release
+POST /projects/:id/repository/tags/:tag_name/release
```
Parameters:
- `id` (required) - The ID of a project
-- `tag` (required) - The name of a tag
+- `tag_name` (required) - The name of a tag
- `description` (required) - Release notes with markdown support
```json
{
- "tag": "1.0.0",
+ "tag_name": "1.0.0",
"description": "Amazing release. Wow"
}
```
+
+## Update a release
+
+Updates the release notes of a given release. It returns 200 if the release is
+successfully updated. If the tag or the release does not exist, it returns 404
+with a proper error message.
+
+```
+PUT /projects/:id/repository/tags/:tag_name/release
+```
+
+Parameters:
+
+- `id` (required) - The ID of a project
+- `tag_name` (required) - The name of a tag
+- `description` (required) - Release notes with markdown support
+
+```json
+{
+ "tag_name": "1.0.0",
+ "description": "Amazing release. Wow"
+}
+``` \ No newline at end of file
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index ef8a7ec1e86..64e52eba3a2 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -60,11 +60,11 @@ This is image that have fully preconfigured `wordpress` and have `MySQL` server
```
Next time when you run your application the `tutum/wordpress` will be started
-and you will have access to it from your build container under hostname: `tutum_wordpress`.
+and you will have access to it from your build container under hostname: `tutum__wordpress`.
Alias hostname for the service is made from the image name:
1. Everything after `:` is stripped,
-2. '/' is replaced to `_`.
+2. '/' is replaced with `__`.
### Configuring services
Many services accept environment variables, which allow you to easily change database names or set account names depending on the environment.
diff --git a/doc/ci/img/builds_tab.png b/doc/ci/img/builds_tab.png
new file mode 100644
index 00000000000..d088b8b329d
--- /dev/null
+++ b/doc/ci/img/builds_tab.png
Binary files differ
diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md
index d69064a91fd..a9b36139de9 100644
--- a/doc/ci/quick_start/README.md
+++ b/doc/ci/quick_start/README.md
@@ -1,44 +1,62 @@
# Quick Start
-To start building projects with GitLab CI a few steps needs to be done.
+Starting from version 8.0, GitLab Continuous Integration (CI) is fully
+integrated into GitLab itself and is enabled by default on all projects.
-## 1. Install GitLab and CI
+This guide assumes that you:
-First you need to have a working GitLab and GitLab CI instance.
+- have a working GitLab instance of version 8.0 or higher or are using
+ [GitLab.com](https://gitlab.com/users/sign_in)
+- have a project in GitLab that you would like to use CI for
-You can omit this step if you use [GitLab.com](https://GitLab.com/).
+In brief, the steps needed to have a working CI can be summed up to:
-## 2. Create repository on GitLab
+1. Create a new project
+1. Add `.gitlab-ci.yml` to the git repository and push to GitLab
+1. Configure a Runner
-Once you login on your GitLab add a new repository where you will store your source code.
-Push your application to that repository.
+From there on, on every push to your git repository the build will be
+automagically started by the Runner and will appear under the project's
+`/builds` page.
-## 3. Add project to CI
+Now, let's break it down to pieces and work on solving the GitLab CI puzzle.
-The next part is to login to GitLab CI.
-Point your browser to the URL you have set GitLab or use [gitlab.com/ci](https://gitlab.com/ci/).
+## Creating a `.gitlab-ci.yml` file
-On the first screen you will see a list of GitLab's projects that you have access to:
+Before you create `.gitlab-ci.yml` let's first explain in brief what this is
+all about.
-![Projects](projects.png)
+### What is `.gitlab-ci.yml`
-Click **Add Project to CI**.
-This will create project in CI and authorize GitLab CI to fetch sources from GitLab.
+The `.gitlab-ci.yml` file is where you configure what CI does with your project.
+It lives in the root of your repository.
-> GitLab CI creates unique token that is used to configure GitLab CI service in GitLab.
-> This token allows to access GitLab's repository and configures GitLab to trigger GitLab CI webhook on **Push events** and **Tag push events**.
-> You can see that token by going to Project's Settings > Services > GitLab CI.
-> You will see there token, the same token is assigned in GitLab CI settings of project.
+On any push to your repository, GitLab will look for the `.gitlab-ci.yml`
+file and start builds on _Runners_ according to the contents of the file,
+for that commit.
-## 4. Create project's configuration - .gitlab-ci.yml
+Because `.gitlab-ci.yml` is in the repository, it is version controlled,
+old versions still build succesfully, forks can easily make use of CI,
+branches can have separate builds and you have a single source of truth for CI.
+You can read more about the reasons why we are using `.gitlab-ci.yml`
+[in our blog about it][blog-ci].
-The next: You have to define how your project will be built.
-GitLab CI uses [YAML](https://en.wikipedia.org/wiki/YAML) file to store build configuration.
-You need to create `.gitlab-ci.yml` in root directory of your repository:
+**Note:** `.gitlab-ci.yml` is a [YAML](https://en.wikipedia.org/wiki/YAML) file
+so you have to pay extra attention to the identation. Always use spaces, not
+tabs.
+
+### Creating a simple `.gitlab-ci.yml` file
+
+You need to create a file named `.gitlab-ci.yml` in the root directory of your
+repository. Below is an example for a Ruby on Rails project.
```yaml
before_script:
- - bundle install
+ - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs
+ - ruby -v
+ - which ruby
+ - gem install bundler --no-ri --no-rdoc
+ - bundle install --jobs $(nproc) "${FLAGS[@]}"
rspec:
script:
@@ -49,71 +67,131 @@ rubocop:
- bundle exec rubocop
```
-This is the simplest possible build configuration that will work for most Ruby applications:
-1. Define two jobs `rspec` and `rubocop` with two different commands to be executed.
-1. Before every job execute commands defined by `before_script`.
+This is the simplest possible build configuration that will work for most Ruby
+applications:
+
+1. Define two jobs `rspec` and `rubocop` (the names are arbitrary) with
+ different commands to be executed.
+1. Before every job, the commands defined by `before_script` are executed.
-The `.gitlab-ci.yml` defines set of jobs with constrains how and when they should be run.
-The jobs are defined as top-level elements with name and always have to contain the `script`.
-Jobs are used to create builds, which are then picked by [runners](../runners/README.md) and executed within environment of the runner.
-What is important that each job is run independently from each other.
+The `.gitlab-ci.yml` file defines sets of jobs with constraints of how and when
+they should be run. The jobs are defined as top-level elements with a name (in
+our case `rspec` and `rubocop`) and always have to contain the `script` keyword.
+Jobs are used to create builds, which are then picked by
+[Runners](../runners/README.md) and executed within the environment of the Runner.
-For more information and complete `.gitlab-ci.yml` syntax, please check the [Configuring project (.gitlab-ci.yml)](../yaml/README.md).
+What is important is that each job is run independently from each other.
-## 5. Add file and push .gitlab-ci.yml to repository
+If you want to check whether your `.gitlab-ci.yml` file is valid, there is a
+Lint tool under the page `/ci/lint` of your GitLab instance. You can also find
+the link under **Settings > CI settings** in your project.
-Once you created `.gitlab-ci.yml` you should add it to git repository and push it to GitLab.
+For more information and a complete `.gitlab-ci.yml` syntax, please check
+[the documentation on .gitlab-ci.yml](../yaml/README.md).
+
+### Push `.gitlab-ci.yml` to GitLab
+
+Once you've created `.gitlab-ci.yml`, you should add it to your git repository
+and push it to GitLab.
```bash
git add .gitlab-ci.yml
-git commit
+git commit -m "Add .gitlab-ci.yml"
git push origin master
```
-If you refresh the project's page on GitLab CI you will notice a one new commit:
+Now if you go to the **Builds** page you will see that the builds are pending.
+
+You can also go to the **Commits** page and notice the little clock icon next
+to the commit SHA.
+
+![New commit pending](img/new_commit.png)
+
+Clicking on the clock icon you will be directed to the builds page for that
+specific commit.
+
+![Single commit builds page](img/single_commit_status_pending.png)
+
+Notice that there are two jobs pending which are named after what we wrote in
+`.gitlab-ci.yml`. The red triangle indicates that there is no Runner configured
+yet for these builds.
+
+The next step is to configure a Runner so that it picks the pending jobs.
+
+## Configuring a Runner
+
+In GitLab, Runners run the builds that you define in `.gitlab-ci.yml`.
+A Runner can be a virtual machine, a VPS, a bare-metal machine, a docker
+container or even a cluster of containers. GitLab and the Runners communicate
+through an API, so the only needed requirement is that the machine on which the
+Runner is configured to has Internet access.
+
+A Runner can be specific to a certain project or serve multiple projects in
+GitLab. If it serves all projects it's called a _Shared Runner_.
+
+Find more information about different Runners in the
+[Runners](../runners/README.md) documentation.
+
+You can find whether any Runners are assigned to your project by going to
+**Settings > Runners**. Setting up a Runner is easy and straightforward. The
+official Runner supported by GitLab is written in Go and can be found at
+<https://gitlab.com/gitlab-org/gitlab-ci-multi-runner>.
+
+In order to have a functional Runner you need to follow two steps:
+
+1. [Install it][runner-install]
+2. [Configure it](../runners/README.md#registering-a-specific-runner)
+
+Follow the links above to set up your own Runner or use a Shared Runner as
+described in the next section.
+
+For other types of unofficial Runners written in other languages, see the
+[instructions for the various GitLab Runners](https://about.gitlab.com/gitlab-ci/#gitlab-runner).
+
+Once the Runner has been set up, you should see it on the Runners page of your
+project, following **Settings > Runners**.
-![](new_commit.png)
+![Activated runners](img/runners_activated.png)
-However the commit has status **pending** which means that commit was not yet picked by runner.
+### Shared Runners
-## 6. Configure runner
+If you use [GitLab.com](https://gitlab.com/) you can use **Shared Runners**
+provided by GitLab Inc.
-In GitLab CI, Runners run your builds.
-A runner is a machine (can be virtual, bare-metal or VPS) that picks up builds through the coordinator API of GitLab CI.
+These are special virtual machines that run on GitLab's infrastructure and can
+build any project.
-A runner can be specific to a certain project or serve any project in GitLab CI.
-A runner that serves all projects is called a shared runner.
-More information about different runner types can be found in [Configuring runner](../runners/README.md).
+To enable **Shared Runners** you have to go to your project's
+**Settings > Runners** and click **Enable shared runners**.
-To check if you have runners assigned to your project go to **Runners**. You will find there information how to setup project specific runner:
+[Read more on Shared Runners](../runners/README.md).
-1. Install GitLab Runner software. Checkout the [GitLab Runner](https://about.gitlab.com/gitlab-ci/#gitlab-runner) section to install it.
-1. Specify following URL during runner setup: https://gitlab.com/ci/
-1. Use the following registration token during setup: TOKEN
+## Seeing the status of your build
-If you do it correctly your runner should be shown under **Runners activated for this project**:
+After configuring the Runner succesfully, you should see the status of your
+last commit change from _pending_ to either _running_, _success_ or _failed_.
-![](runners_activated.png)
+You can view all builds, by going to the **Builds** page in your project.
-### Shared runners
+![Commit status](img/builds_status.png)
-If you use [gitlab.com/ci](https://gitlab.com/ci/) you can use **Shared runners** provided by GitLab Inc.
-These are special virtual machines that are run on GitLab's infrastructure that can build any project.
-To enable **Shared runners** you have to go to **Runners** and click **Enable shared runners** for this project.
+By clicking on a Build ID, you will be able to see the log of that build.
+This is important to diagnose why a build failed or acted differently than
+you expected.
-## 7. Check status of commit
+![Build log](img/build_log.png)
-If everything went OK and you go to commit, the status of the commit should change from **pending** to either **running**, **success** or **failed**.
+You are also able to view the status of any commit in the various pages in
+GitLab, such as **Commits** and **Merge Requests**.
-![](commit_status.png)
+## Next steps
-You can click **Build ID** to view build log for specific job.
+Awesome! You started using CI in GitLab!
-## 8. Congratulations!
+Next you can look into doing more with the CI. Many people are using GitLab
+to package, containerize, test and deploy software.
-You managed to build your first project using GitLab CI.
-You may need to tune your `.gitlab-ci.yml` file to implement build plan for your project.
-A few examples how it can be done you can find on [Examples](../examples/README.md) page.
+Visit our various languages examples at <https://gitlab.com/groups/gitlab-examples>.
-GitLab CI also offers **the Lint** tool to verify validity of your `.gitlab-ci.yml` which can be useful to troubleshoot potential problems.
-The Lint is available from project's settings or by adding `/lint` to GitLab CI url.
+[runner-install]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/tree/master#installation
+[blog-ci]: https://about.gitlab.com/2015/05/06/why-were-replacing-gitlab-ci-jobs-with-gitlab-ci-dot-yml/
diff --git a/doc/ci/quick_start/build_status.png b/doc/ci/quick_start/build_status.png
deleted file mode 100644
index 333259e6acd..00000000000
--- a/doc/ci/quick_start/build_status.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/quick_start/commit_status.png b/doc/ci/quick_start/commit_status.png
deleted file mode 100644
index 725b79e6f91..00000000000
--- a/doc/ci/quick_start/commit_status.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/quick_start/img/build_log.png b/doc/ci/quick_start/img/build_log.png
new file mode 100644
index 00000000000..89e6cd40cb6
--- /dev/null
+++ b/doc/ci/quick_start/img/build_log.png
Binary files differ
diff --git a/doc/ci/quick_start/img/builds_status.png b/doc/ci/quick_start/img/builds_status.png
new file mode 100644
index 00000000000..b8e6c2a361a
--- /dev/null
+++ b/doc/ci/quick_start/img/builds_status.png
Binary files differ
diff --git a/doc/ci/quick_start/img/new_commit.png b/doc/ci/quick_start/img/new_commit.png
new file mode 100644
index 00000000000..3d3c9d5c0bd
--- /dev/null
+++ b/doc/ci/quick_start/img/new_commit.png
Binary files differ
diff --git a/doc/ci/quick_start/img/runners_activated.png b/doc/ci/quick_start/img/runners_activated.png
new file mode 100644
index 00000000000..eafcfd6ecd5
--- /dev/null
+++ b/doc/ci/quick_start/img/runners_activated.png
Binary files differ
diff --git a/doc/ci/quick_start/img/single_commit_status_pending.png b/doc/ci/quick_start/img/single_commit_status_pending.png
new file mode 100644
index 00000000000..23b3bb5acfc
--- /dev/null
+++ b/doc/ci/quick_start/img/single_commit_status_pending.png
Binary files differ
diff --git a/doc/ci/quick_start/img/status_pending.png b/doc/ci/quick_start/img/status_pending.png
new file mode 100644
index 00000000000..a049ec2a5ba
--- /dev/null
+++ b/doc/ci/quick_start/img/status_pending.png
Binary files differ
diff --git a/doc/ci/quick_start/new_commit.png b/doc/ci/quick_start/new_commit.png
deleted file mode 100644
index 3839e893c17..00000000000
--- a/doc/ci/quick_start/new_commit.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/quick_start/projects.png b/doc/ci/quick_start/projects.png
deleted file mode 100644
index 0b3430a69db..00000000000
--- a/doc/ci/quick_start/projects.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/quick_start/runners.png b/doc/ci/quick_start/runners.png
deleted file mode 100644
index 25b4046bc00..00000000000
--- a/doc/ci/quick_start/runners.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/quick_start/runners_activated.png b/doc/ci/quick_start/runners_activated.png
deleted file mode 100644
index c934bd12f41..00000000000
--- a/doc/ci/quick_start/runners_activated.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 3e6071a2ee7..3dbf1afc7a9 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -278,26 +278,23 @@ The above script will:
`artifacts` is used to specify list of files and directories which should be attached to build after success.
1. Send all files in `binaries` and `.config`:
-```
-artifacts:
- paths:
- - binaries/
- - .config
-```
+
+ artifacts:
+ paths:
+ - binaries/
+ - .config
2. Send all git untracked files:
-```
-artifacts:
- untracked: true
-```
+
+ artifacts:
+ untracked: true
3. Send all git untracked files and files in `binaries`:
-```
-artifacts:
- untracked: true
- paths:
- - binaries/
-```
+
+ artifacts:
+ untracked: true
+ paths:
+ - binaries/
The artifacts will be send after the build success to GitLab and will be accessible in GitLab interface to download.
@@ -307,46 +304,41 @@ This feature requires GitLab Runner v0.7.0 or higher.
`cache` is used to specify list of files and directories which should be cached between builds.
1. Cache all files in `binaries` and `.config`:
-```
-rspec:
- script: test
- cache:
- paths:
- - binaries/
- - .config
-```
+
+ rspec:
+ script: test
+ cache:
+ paths:
+ - binaries/
+ - .config
2. Cache all git untracked files:
-```
-rspec:
- script: test
- cache:
- untracked: true
-```
+ rspec:
+ script: test
+ cache:
+ untracked: true
+
3. Cache all git untracked files and files in `binaries`:
-```
-rspec:
- script: test
- cache:
- untracked: true
- paths:
- - binaries/
-```
-4. Locally defined cache overwrites globally defined options. This will cache only `binaries/`:
+ rspec:
+ script: test
+ cache:
+ untracked: true
+ paths:
+ - binaries/
-```
-cache:
- paths:
- - my/files
+4. Locally defined cache overwrites globally defined options. This will cache only `binaries/`:
-rspec:
- script: test
- cache:
- paths:
- - binaries/
-```
+ cache:
+ paths:
+ - my/files
+
+ rspec:
+ script: test
+ cache:
+ paths:
+ - binaries/
The cache is provided on best effort basis, so don't expect that cache will be present.
For implementation details please check GitLab Runner.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 7ef46b04065..618391e16d2 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -312,7 +312,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
GitLab Shell is an SSH access and repository management software developed specially for GitLab.
# Run the installation task for gitlab-shell (replace `REDIS_URL` if needed):
- sudo -u git -H bundle exec rake gitlab:shell:install[v2.6.6] REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production
+ sudo -u git -H bundle exec rake gitlab:shell:install REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production
# By default, the gitlab-shell config is generated from your main GitLab config.
# You can review (and modify) the gitlab-shell config as follows:
@@ -320,19 +320,19 @@ GitLab Shell is an SSH access and repository management software developed speci
**Note:** If you want to use HTTPS, see [Using HTTPS](#using-https) for the additional steps.
-**Note:** Make sure your hostname can be resolved on the machine itself by either a proper DNS record or an additional line in /etc/hosts ("127.0.0.1 hostname"). This might be necessary for example if you set up gitlab behind a reverse proxy. If the hostname cannot be resolved, the final installation check will fail with "Check GitLab API access: FAILED. code: 401" and pushing commits will be rejected with "[remote rejected] master -> master (hook declined)".
+**Note:** Make sure your hostname can be resolved on the machine itself by either a proper DNS record or an additional line in /etc/hosts ("127.0.0.1 hostname"). This might be necessary for example if you set up GitLab behind a reverse proxy. If the hostname cannot be resolved, the final installation check will fail with "Check GitLab API access: FAILED. code: 401" and pushing commits will be rejected with "[remote rejected] master -> master (hook declined)".
### Install gitlab-workhorse
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.4.1
+ sudo -u git -H git checkout 0.4.2
sudo -u git -H make
### Initialize Database and Activate Advanced Features
- # Go to Gitlab installation folder
+ # Go to GitLab installation folder
cd /home/git/gitlab
diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md
index 8d4c2ceab7d..bcd00cfc6bf 100644
--- a/doc/permissions/permissions.md
+++ b/doc/permissions/permissions.md
@@ -6,6 +6,9 @@ If a user is both in a project group and in the project itself, the highest perm
If a user is a GitLab administrator they receive all permissions.
+On public projects the Guest role is not enforced.
+All users will be able to create issues, leave comments, and pull or download the project code.
+
To add or import a user, you can follow the [project users and members
documentation](doc/workflow/add-user/add-user.md).
@@ -15,8 +18,8 @@ documentation](doc/workflow/add-user/add-user.md).
|---------------------------------------|---------|------------|-------------|----------|--------|
| Create new issue | ✓ | ✓ | ✓ | ✓ | ✓ |
| Leave comments | ✓ | ✓ | ✓ | ✓ | ✓ |
-| Pull project code | ✓ | ✓ | ✓ | ✓ | ✓ |
-| Download project | ✓ | ✓ | ✓ | ✓ | ✓ |
+| Pull project code | | ✓ | ✓ | ✓ | ✓ |
+| Download project | | ✓ | ✓ | ✓ | ✓ |
| Create code snippets | | ✓ | ✓ | ✓ | ✓ |
| Manage issue tracker | | ✓ | ✓ | ✓ | ✓ |
| Manage labels | | ✓ | ✓ | ✓ | ✓ |
diff --git a/doc/public_access/public_access.md b/doc/public_access/public_access.md
index bd439f7c6f3..6e22ea7b72a 100644
--- a/doc/public_access/public_access.md
+++ b/doc/public_access/public_access.md
@@ -1,44 +1,59 @@
# Public access
-GitLab allows you to open selected projects to be accessed **publicly** or **internally**.
+GitLab allows you to change your projects' visibility in order be accessed
+**publicly** or **internally**.
-Projects with either of these visibility levels will be listed in the [public access directory](/public).
+Projects with either of these visibility levels will be listed in the
+public access directory (`/public` under your GitLab instance).
+Here is the [GitLab.com example](https://gitlab.com/public).
Internal projects will only be available to authenticated users.
-## Public projects
+## Visibility of projects
+
+### Public projects
Public projects can be cloned **without any** authentication.
-It will also be listed on the [public access directory](/public).
+They will also be listed on the public access directory (`/public`).
-**Any logged in user** will have [Guest](../permissions/permissions) permissions on the repository.
+**Any logged in user** will have [Guest](../permissions/permissions)
+permissions on the repository.
-## Internal projects
+### Internal projects
Internal projects can be cloned by any logged in user.
-It will also be listed on the [public access directory](/public) for logged in users.
+They will also be listed on the public access directory (`/public`) for logged
+in users.
-Any logged in user will have [Guest](../permissions/permissions) permissions on the repository.
+Any logged in user will have [Guest](../permissions/permissions) permissions on
+the repository.
-## How to change project visibility
+### How to change project visibility
-1. Go to your project dashboard
-1. Click on the "Edit" tab
-1. Change "Visibility Level"
+1. Go to your project's **Settings**
+1. Change "Visibility Level" to either Public, Internal or Private
## Visibility of users
-The public page of users, located at `/u/username` is visible if either:
+The public page of a user, located at `/u/username`, is always visible whether
+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.
-- You are logged in.
-- You are logged out, and the target user is authorized to (is Guest, Reporter, etc.) at least one public project.
+## Visibility of groups
-Otherwise, you will be redirected to the sign in page.
+The public page of a group, located at `/groups/groupname`, is always visible
+to everyone.
-When visiting the public page of an user, you will only see listed projects which you can view yourself.
+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 you can disable public projects or public and internal projects for the entire GitLab installation to prevent people making code public by accident. The restricted visibility settings do not apply to admin users.
+In the Admin area under **Settings** (`/admin/application_settings`), you can
+restrict the use of visibility levels for users when they create a project or a
+snippet. This is useful to prevent people exposing their repositories to public
+by accident. The restricted visibility settings do not apply to admin users.
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 1a5442cdac7..b4d2786bd76 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -29,7 +29,7 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```
Also you can choose what should be backed up by adding environment variable SKIP. Available options: db,
-uploads (attachments), repositories, builds(CI build output logs), artifacts (CI build artifacts).
+uploads (attachments), repositories, builds(CI build output logs), artifacts (CI build artifacts), lfs (LFS objects).
Use a comma to specify several options at the same time.
```
@@ -274,9 +274,6 @@ sudo gitlab-rake gitlab:backup:restore BACKUP=1393513186
# Start GitLab
sudo gitlab-ctl start
-# Create satellites
-sudo gitlab-rake gitlab:satellites:create
-
# Check GitLab
sudo gitlab-rake gitlab:check SANITIZE=true
```
diff --git a/doc/release/monthly.md b/doc/release/monthly.md
index c9ab87671d2..aff3f066b24 100644
--- a/doc/release/monthly.md
+++ b/doc/release/monthly.md
@@ -159,7 +159,7 @@ Please do not raise issues directly in this issue but link to issues that might
The decision to create a patch release or not is with the release manager who is assigned to this issue.
The release manager will comment here about the plans for patch releases.
-Assign the issue to the release manager and at mention all members of gitlab core team. If there are any known bugs in the release add them immediately.
+Assign the issue to the release manager and at mention all members of GitLab core team. If there are any known bugs in the release add them immediately.
## Tweet about RC1
diff --git a/doc/release/patch.md b/doc/release/patch.md
index 6aa11b283df..3022e375aca 100644
--- a/doc/release/patch.md
+++ b/doc/release/patch.md
@@ -1,21 +1,46 @@
# Things to do when doing a patch release
-NOTE: This is a guide for GitLab developers. If you are trying to install GitLab see the latest stable [installation guide](install/installation.md) and if you are trying to upgrade, see the [upgrade guides](update).
+NOTE: This is a guide for GitLab developers. If you are trying to install GitLab
+see the latest stable [installation guide](install/installation.md) and if you
+are trying to upgrade, see the [upgrade guides](update).
## When to do a patch release
-Do a patch release when there is a critical regression that needs to be addresses before the next monthly release.
-
-Otherwise include it in the monthly release and note there was a regression fix in the release announcement.
+Patch releases are done as-needed in order to fix regressions in the current
+major release that cannot or should not wait until the next major release.
+What's included and when to release is at the discretion of the release manager.
## Release Procedure
+### Create a patch issue
+
+Create an issue in the GitLab CE project. Name it "Release x.y.z", tag it with
+the `release` label, and assign it to the milestone of the corresponding major
+release.
+
+Use the following template:
+
+```
+- Picked into respective `stable` branches:
+- [ ] Merge `x-y-stable` into `x-y-stable-ee`
+- [ ] release-tools: `x.y.z`
+- gitlab-omnibus
+ - [ ] `x.y.z+ee.0`
+ - [ ] `x.y.z+ce.0`
+- [ ] Deploy
+- [ ] Add patch notice to [x.y regressions]()
+- [ ] [Blog post]()
+- [ ] [Tweet]()
+- [ ] Add entry to version.gitlab.com
+```
+
+Update the issue with links to merge requests that need to be/have been picked
+into the `stable` branches.
+
### Preparation
1. Verify that the issue can be reproduced
1. Note in the 'GitLab X.X regressions' that you will create a patch
-1. Create an issue on private GitLab development server
-1. Name the issue "Release X.X.X CE and X.X.X EE", this will make searching easier
1. Fix the issue on a feature branch, do this on the private GitLab development server
1. If it is a security issue, then assign it to the release manager and apply a 'security' label
1. Consider creating and testing workarounds
@@ -25,7 +50,6 @@ Otherwise include it in the monthly release and note there was a regression fix
1. For EE, update the CHANGELOG-EE if it is EE specific fix. Otherwise, merge the stable CE branch and add to CHANGELOG-EE "Merge community edition changes for version X.X.X"
1. Merge CE stable branch into EE stable branch
-
### Bump version
Get release tools
@@ -54,4 +78,4 @@ bundle exec rake release["x.x.x"]
1. Note in the 'GitLab X.X regressions' issue that the patch was published (CE only)
1. Create the 'x.y.0' version on version.gitlab.com
1. [Create new AMIs](https://dev.gitlab.org/gitlab/AMI/blob/master/README.md)
-1. Create a new patch release issue for the next potential release \ No newline at end of file
+1. Create a new patch release issue for the next potential release
diff --git a/doc/ssh/README.md b/doc/ssh/README.md
index 9753504ac8b..fe5b45dd432 100644
--- a/doc/ssh/README.md
+++ b/doc/ssh/README.md
@@ -15,7 +15,8 @@ Note: It is a best practice to use a password for an SSH key, but it is not
required and you can skip creating a password by pressing enter. Note that
the password you choose here can't be altered or retrieved.
-To generate a new SSH key, use the following commandGitLab```bash
+To generate a new SSH key, use the following command:
+```bash
ssh-keygen -t rsa -C "$your_email"
```
This command will prompt you for a location and filename to store the key
@@ -108,4 +109,4 @@ Note in the gitlab.com example above a username was specified to override the de
Due to the wide variety of SSH clients and their very large number of configuration options, further explanation of these topics is beyond the scope of this document.
Public SSH keys need to be unique, as they will bind to your account. Your SSH key is the only identifier you'll
-have when pushing code via SSH. That's why it needs to uniquely map to a single user.
+have when pushing code via SSH. That's why it needs to uniquely map to a single user. \ No newline at end of file
diff --git a/doc/update/8.1-to-8.2.md b/doc/update/8.1-to-8.2.md
index 73d899f9c2e..7b228d6a22f 100644
--- a/doc/update/8.1-to-8.2.md
+++ b/doc/update/8.1-to-8.2.md
@@ -68,7 +68,7 @@ sudo -u git -H git checkout 8-2-stable-ee
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch
-sudo -u git -H git checkout v2.6.6
+sudo -u git -H git checkout v2.6.8
```
### 5. Replace gitlab-git-http-server with gitlab-workhorse
@@ -81,7 +81,7 @@ from GitLab 8.1.
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.4.1
+sudo -u git -H git checkout 0.4.2
sudo -u git -H make
```
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index 593722eb01f..957354decb7 100644
--- a/doc/update/patch_versions.md
+++ b/doc/update/patch_versions.md
@@ -6,7 +6,8 @@ For example from 7.14.0 to 7.14.3, also see the [semantic versioning specificati
### 0. Backup
It's useful to make a backup just in case things go south:
-(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version)
+(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab
+user on the database version)
```bash
cd /home/git/gitlab
@@ -15,19 +16,23 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
### 1. Stop server
- sudo service gitlab stop
+```bash
+sudo service gitlab stop
+```
### 2. Get latest code for the stable branch
+In the commands below, replace `LATEST_TAG` with the latest GitLab tag you want
+to update to, for example `v8.0.3`. Use `git tag -l 'v*.[0-9]' --sort='v:refname'`
+to see a list of all tags. Make sure to update patch versions only (check your
+current version with `cat VERSION`).
+
```bash
cd /home/git/gitlab
sudo -u git -H git fetch --all
sudo -u git -H git checkout -- Gemfile.lock db/schema.rb
sudo -u git -H git checkout LATEST_TAG -b LATEST_TAG
```
-Replace `LATEST_TAG` with the latest GitLab tag you want to update to, for example `v8.0.3`.
-Use `git tag -l 'v*.[0-9]' --sort='v:refname'` to see a list of all tags.
-Make sure to update patch versions only (check your current version with `cat VERSION`)
### 3. Update gitlab-shell to the corresponding version
@@ -37,12 +42,20 @@ sudo -u git -H git fetch
sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION` -b v`cat /home/git/gitlab/GITLAB_SHELL_VERSION`
```
-### 4. Install libs, migrations, etc.
+### 4. Update gitlab-workhorse to the corresponding version
+
+```bash
+cd /home/git/gitlab-workhorse
+sudo -u git -H git fetch
+sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_WORKHORSE_VERSION` -b v`cat /home/git/gitlab/GITLAB_WORKHORSE_VERSION`
+```
+
+### 5. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
-#PostgreSQL
+# PostgreSQL
sudo -u git -H bundle install --without development test mysql --deployment
# MySQL
@@ -52,19 +65,25 @@ sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
```
-### 5. Start application
+### 6. Start application
- sudo service gitlab start
- sudo service nginx restart
+```bash
+sudo service gitlab start
+sudo service nginx restart
+```
-### 6. Check application status
+### 7. Check application status
Check if GitLab and its environment are configured correctly:
- sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+```bash
+sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+```
To make sure you didn't miss anything run a more thorough check with:
- sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+```bash
+sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+```
If all items are green, then congratulations upgrade complete!
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index c1a4f64981f..a6b4d951188 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -17,3 +17,4 @@
- [Milestones](milestones.md)
- [Merge Requests](merge_requests.md)
- ["Work In Progress" Merge Requests](wip_merge_requests.md)
+- [Manage large binaries with Git LFS](lfs/manage_large_binaries_with_git_lfs.md)
diff --git a/doc/workflow/award_emoji.png b/doc/workflow/award_emoji.png
new file mode 100644
index 00000000000..fb26ee04393
--- /dev/null
+++ b/doc/workflow/award_emoji.png
Binary files differ
diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md
index 31495bce76e..8965e5b3654 100644
--- a/doc/workflow/gitlab_flow.md
+++ b/doc/workflow/gitlab_flow.md
@@ -244,13 +244,12 @@ Developing software happen in small messy steps and it is OK to have your histor
You can use tools to view the network graphs of commits and understand the messy history that created your code.
If you rebase code the history is incorrect, and there is no way for tools to remedy this because they can't deal with changing commit identifiers.
-## Voting on merge requests
+## Award emojis on issues and merge requests
-![Voting slider in GitLab](voting_slider.png)
+![Emoji bar in GitLab](award_emoji.png)
-It is common to voice approval or disapproval by using +1 or -1 emoticons.
-In GitLab the +1 and -1 are aggregated and shown at the top of the merge request.
-As a rule of thumb anything that doesn't have two times more +1's than -1's is suspect and should not be merged yet.
+It is common to voice approval or disapproval by using +1 or -1. In GitLab you
+can use emojis to give a virtual high five on issues and merge requests.
## Pushing and removing branches
diff --git a/doc/workflow/lfs/lfs_administration.md b/doc/workflow/lfs/lfs_administration.md
new file mode 100644
index 00000000000..5076b2697a3
--- /dev/null
+++ b/doc/workflow/lfs/lfs_administration.md
@@ -0,0 +1,41 @@
+# GitLab Git LFS Administration
+
+Documentation on how to use Git LFS are under [Managing large binary files with Git LFS doc](manage_large_binaries_with_git_lfs.md).
+
+## Requirements
+
+* Git LFS is supported in GitLab starting with version 8.2.
+* Users need to install [Git LFS client](https://git-lfs.github.com) version 1.0.1 and up.
+
+## Configuration
+
+Git LFS objects can be large in size. By default, they are stored on the server GitLab is installed on.
+
+There are two configuration options to help GitLab server administrators:
+
+* Enabling/disabling Git LFS support
+* Changing the location of LFS object storage
+
+### Omnibus packages
+
+In `/etc/gitlab/gitlab.rb`:
+
+```ruby
+gitlab_rails['lfs_enabled'] = false
+gitlab_rails['lfs_storage_path'] = "/mnt/storage/lfs-objects"
+```
+
+### Installations from source
+
+In `config/gitlab.yml`:
+
+```yaml
+ lfs:
+ enabled: false
+ storage_path: /mnt/storage/lfs-objects
+```
+
+## Known limitations
+
+* Currently, storing GitLab Git LFS objects on a non-local storage (like S3 buckets) is not supported
+* Currently, removing LFS objects from GitLab Git LFS storage is not supported
diff --git a/doc/workflow/git_lfs.md b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
index 4990a9b5aac..b59e92cb317 100644
--- a/doc/workflow/git_lfs.md
+++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
@@ -7,55 +7,27 @@ GitLab already supports [managing large files with git annex](http://doc.gitlab.
environments it is not always convenient to use different commands to differentiate between the large files and regular ones.
Git LFS makes this simpler for the end user by removing the requirement to learn new commands.
-<!-- more -->
## How it works
Git LFS client talks with the GitLab server over HTTPS. It uses HTTP Basic Authentication to authorize client requests.
Once the request is authorized, Git LFS client receives instructions from where to fetch or where to push the large file.
-## Requirements
-
-* Git LFS is supported in GitLab starting with version 8.2
-* Git LFS client version 0.6.0 and up
-
-## GitLab and Git LFS
-
-### Configuration
-
-Git LFS objects can be large in size. By default, they are stored on the server GitLab is installed on.
-
-There are two configuration options to help GitLab server administrators:
+## GitLab server configuration
-* Enabling/disabling Git LFS support
-* Changing the location of LFS object storage
+Documentation for GitLab instance administrators is under [LFS administration doc](lfs_administration.md).
-#### Omnibus packages
-
-In `/etc/gitlab/gitlab.rb`:
-
-```ruby
-gitlab_rails['lfs_enabled'] = false
-gitlab_rails['lfs_storage_path'] = "/mnt/storage/lfs-objects"
-```
-
-#### Installations from source
-
-In `config/gitlab.yml`:
+## Requirements
-```yaml
- lfs:
- enabled: false
- storage_path: /mnt/storage/lfs-objects
-```
+* Git LFS is supported in GitLab starting with version 8.2
+* [Git LFS client](https://git-lfs.github.com) version 1.0.1 and up
## Known limitations
-* Git LFS v1 original API is not supported since it was deprecated early in LFS development, starting with Git LFS version 0.6.0
+* Git LFS v1 original API is not supported since it was deprecated early in LFS development
* When SSH is set as a remote, Git LFS objects still go through HTTPS
* Any Git LFS request will ask for HTTPS credentials to be provided so good Git credentials store is recommended
-* Currently, storing GitLab Git LFS objects on a non-local storage (like S3 buckets) is not supported
-* Git LFS always assumes HTTPS so if you have GitLab server on HTTP you will have to add the url to Git config manually (see #troubleshooting-tips)
+* Git LFS always assumes HTTPS so if you have GitLab server on HTTP you will have to add the URL to Git config manually (see #troubleshooting)
## Using Git LFS
@@ -77,13 +49,17 @@ git commit -am "Added Debian iso" # commit the file meta data
git push origin master # sync the git repo and large file to the GitLab server
```
-Downloading a single large file is also very simple:
+Cloning the repository works the same as before. Git automatically detects the LFS-tracked files and clones them via HTTP. If you performed the git clone command with a SSH URL, you have to enter your GitLab credentials for HTTP authentication.
```bash
git clone git@gitlab.example.com:group/project.git
-git lfs fetch debian.iso # download the large file
```
+If you already cloned the repository and you want to get the latest LFS object that are on the remote repository, eg. from branch `master`:
+
+```bash
+git lfs fetch master
+```
## Troubleshooting
@@ -91,17 +67,31 @@ git lfs fetch debian.iso # download the large file
There are a couple of reasons why this error can occur:
-* Wrong version of LFS client used:
+* You don't have permissions to access certain LFS object
+
+Check if you have permissions to push to the project or fetch from the project.
-Check the version of Git LFS on the client machine with `git lfs version`. Only version 0.6.0 and newer are supported.
+* Project is not allowed to access the LFS object
-* Project is using deprecated LFS API
+LFS object you are trying to push to the project or fetch from the project is not available to the project anymore.
+Probably the object was removed from the server.
-Check the Git config of the project for traces of deprecated API with `git lfs -l`. If `batch = false` is set in the config, remove the line and try using Git LFS client newer than 0.6.0.
+* Local git repository is using deprecated LFS API
### Invalid status for <url> : 501
-When attempting to push a LFS object to a GitLab server that doesn't have Git LFS support enabled, server will return status `error 501`. Check with your GitLab administrator why Git LFS is not enabled on the server. See [Configuration section](#configuration) for instructions on how to enable LFS support.
+Git LFS will log the failures into a log file.
+To view this log file, while in project directory:
+
+```bash
+git lfs logs last
+```
+
+If the status `error 501` is shown, it is because:
+
+* Git LFS support is not enabled on the GitLab server. Check with your GitLab administrator why Git LFS is not enabled on the server. See [LFS administration documentation](lfs_administration.md) for instructions on how to enable LFS support.
+
+* Git LFS client version is not supported by GitLab server. Check your Git LFS version with `git lfs version`. Check the Git config of the project for traces of deprecated API with `git lfs -l`. If `batch = false` is set in the config, remove the line and try to update your Git LFS client. Only version 1.0.1 and newer are supported.
### getsockopt: connection refused
@@ -131,6 +121,6 @@ git config --global credential.helper 'cache --timeout=3600'
This will remember the credentials for an hour after which Git operations will require re-authentication.
-If you are using OS X you can use `osxkeychain` to store and encrypt your credentials. For Windows, `wincred` is available.
+If you are using OS X you can use `osxkeychain` to store and encrypt your credentials. For Windows, you can use `wincred` or Microsoft's [Git Credential Manager for Windows](https://github.com/Microsoft/Git-Credential-Manager-for-Windows/releases).
-More details about various methods of storing the user credentials can be found on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage) \ No newline at end of file
+More details about various methods of storing the user credentials can be found on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage). \ No newline at end of file
diff --git a/doc/workflow/voting_slider.png b/doc/workflow/voting_slider.png
deleted file mode 100644
index 4c660ef9593..00000000000
--- a/doc/workflow/voting_slider.png
+++ /dev/null
Binary files differ
diff --git a/features/group/members.feature b/features/group/members.feature
new file mode 100644
index 00000000000..1f9514bac39
--- /dev/null
+++ b/features/group/members.feature
@@ -0,0 +1,105 @@
+Feature: Group Members
+ Background:
+ Given I sign in as "John Doe"
+ And "John Doe" is owner of group "Owned"
+ And "John Doe" is guest of group "Guest"
+
+ @javascript
+ Scenario: I should add user to group "Owned"
+ Given User "Mary Jane" exists
+ When I visit group "Owned" members page
+ And I select user "Mary Jane" from list with role "Reporter"
+ Then I should see user "Mary Jane" in team list
+
+ @javascript
+ Scenario: Add user to group
+ Given gitlab user "Mike"
+ When I visit group "Owned" members page
+ When I select "Mike" as "Reporter"
+ Then I should see "Mike" in team list as "Reporter"
+
+ @javascript
+ Scenario: Ignore add user to group when is already Owner
+ Given gitlab user "Mike"
+ When I visit group "Owned" members page
+ When I select "Mike" as "Reporter"
+ Then I should see "Mike" in team list as "Owner"
+
+ @javascript
+ Scenario: Invite user to group
+ When I visit group "Owned" members page
+ When I select "sjobs@apple.com" as "Reporter"
+ Then I should see "sjobs@apple.com" in team list as invited "Reporter"
+
+ @javascript
+ Scenario: Edit group member permissions
+ Given "Mary Jane" is guest of group "Owned"
+ And I visit group "Owned" members page
+ When I change the "Mary Jane" role to "Developer"
+ Then I should see "Mary Jane" as "Developer"
+
+ # Leave
+
+ @javascript
+ Scenario: Owner should be able to remove himself from group if he is not the last owner
+ Given "Mary Jane" is owner of group "Owned"
+ When I visit group "Owned" members page
+ Then I should see user "John Doe" in team list
+ Then I should see user "Mary Jane" in team list
+ When I click on the "Remove User From Group" button for "John Doe"
+ And I visit group "Owned" members page
+ Then I should not see user "John Doe" in team list
+ Then I should see user "Mary Jane" in team list
+
+ @javascript
+ Scenario: Owner should not be able to remove himself from group if he is the last owner
+ Given "Mary Jane" is guest of group "Owned"
+ When I visit group "Owned" members page
+ Then I should see user "John Doe" in team list
+ Then I should see user "Mary Jane" in team list
+ Then I should not see the "Remove User From Group" button for "John Doe"
+
+ @javascript
+ Scenario: Guest should be able to remove himself from group
+ Given "Mary Jane" is guest of group "Guest"
+ When I visit group "Guest" members page
+ Then I should see user "John Doe" in team list
+ Then I should see user "Mary Jane" in team list
+ When I click on the "Remove User From Group" button for "John Doe"
+ When I visit group "Guest" members page
+ Then I should not see user "John Doe" in team list
+ Then I should see user "Mary Jane" in team list
+
+ @javascript
+ Scenario: Guest should be able to remove himself from group even if he is the only user in the group
+ When I visit group "Guest" members page
+ Then I should see user "John Doe" in team list
+ When I click on the "Remove User From Group" button for "John Doe"
+ When I visit group "Guest" members page
+ Then I should not see user "John Doe" in team list
+
+ # Remove others
+
+ Scenario: Owner should be able to remove other users from group
+ Given "Mary Jane" is owner of group "Owned"
+ When I visit group "Owned" members page
+ Then I should see user "John Doe" in team list
+ Then I should see user "Mary Jane" in team list
+ When I click on the "Remove User From Group" button for "Mary Jane"
+ When I visit group "Owned" members page
+ Then I should see user "John Doe" in team list
+ Then I should not see user "Mary Jane" in team list
+
+ Scenario: Guest should not be able to remove other users from group
+ Given "Mary Jane" is guest of group "Guest"
+ When I visit group "Guest" members page
+ Then I should see user "John Doe" in team list
+ Then I should see user "Mary Jane" in team list
+ Then I should not see the "Remove User From Group" button for "Mary Jane"
+
+ Scenario: Search member by name
+ Given "Mary Jane" is guest of group "Guest"
+ And I visit group "Guest" members page
+ When I search for 'Mary' member
+ Then I should see user "Mary Jane" in team list
+ Then I should not see user "John Doe" in team list
diff --git a/features/group/milestones.feature b/features/group/milestones.feature
new file mode 100644
index 00000000000..62ea66a783c
--- /dev/null
+++ b/features/group/milestones.feature
@@ -0,0 +1,30 @@
+Feature: Group Milestones
+ Background:
+ Given I sign in as "John Doe"
+ And "John Doe" is owner of group "Owned"
+
+ Scenario: I should see group "Owned" milestone index page with no milestones
+ When I visit group "Owned" page
+ And I click on group milestones
+ Then I should see group milestones index page has no milestones
+
+ Scenario: I should see group "Owned" milestone index page with milestones
+ Given Group has projects with milestones
+ When I visit group "Owned" page
+ And I click on group milestones
+ Then I should see group milestones index page with milestones
+
+ Scenario: I should see group "Owned" milestone show page
+ Given Group has projects with milestones
+ When I visit group "Owned" page
+ And I click on group milestones
+ And I click on one group milestone
+ Then I should see group milestone with descriptions and expiry date
+ And I should see group milestone with all issues and MRs assigned to that milestone
+
+ Scenario: Create multiple milestones with one form
+ Given I visit group "Owned" milestones page
+ And I click new milestone button
+ And I fill milestone name
+ When I press create mileston button
+ Then milestone in each project should be created
diff --git a/features/groups.feature b/features/groups.feature
index abf3769a844..c803e952980 100644
--- a/features/groups.feature
+++ b/features/groups.feature
@@ -2,7 +2,6 @@ Feature: Groups
Background:
Given I sign in as "John Doe"
And "John Doe" is owner of group "Owned"
- And "John Doe" is guest of group "Guest"
Scenario: I should have back to group button
When I visit group "Owned" page
@@ -24,13 +23,6 @@ Feature: Groups
When I visit group "Owned" merge requests page
Then I should see merge requests from group "Owned" assigned to me
- @javascript
- Scenario: I should add user to projects in group "Owned"
- Given User "Mary Jane" exists
- When I visit group "Owned" members page
- And I select user "Mary Jane" from list with role "Reporter"
- Then I should see user "Mary Jane" in team list
-
Scenario: I should see edit group "Owned" page
When I visit group "Owned" settings page
And I change group "Owned" name to "new-name"
@@ -51,123 +43,6 @@ Feature: Groups
Then I should not see group "Owned" avatar
And I should not see the "Remove avatar" button
- @javascript
- Scenario: Add user to group
- Given gitlab user "Mike"
- When I visit group "Owned" members page
- And I click link "Add members"
- When I select "Mike" as "Reporter"
- Then I should see "Mike" in team list as "Reporter"
-
- @javascript
- Scenario: Ignore add user to group when is already Owner
- Given gitlab user "Mike"
- When I visit group "Owned" members page
- And I click link "Add members"
- When I select "Mike" as "Reporter"
- Then I should see "Mike" in team list as "Owner"
-
- @javascript
- Scenario: Invite user to group
- When I visit group "Owned" members page
- And I click link "Add members"
- When I select "sjobs@apple.com" as "Reporter"
- Then I should see "sjobs@apple.com" in team list as invited "Reporter"
-
- # Leave
-
- @javascript
- Scenario: Owner should be able to remove himself from group if he is not the last owner
- Given "Mary Jane" is owner of group "Owned"
- When I visit group "Owned" members page
- Then I should see user "John Doe" in team list
- Then I should see user "Mary Jane" in team list
- When I click on the "Remove User From Group" button for "John Doe"
- And I visit group "Owned" members page
- Then I should not see user "John Doe" in team list
- Then I should see user "Mary Jane" in team list
-
- @javascript
- Scenario: Owner should not be able to remove himself from group if he is the last owner
- Given "Mary Jane" is guest of group "Owned"
- When I visit group "Owned" members page
- Then I should see user "John Doe" in team list
- Then I should see user "Mary Jane" in team list
- Then I should not see the "Remove User From Group" button for "John Doe"
-
- @javascript
- Scenario: Guest should be able to remove himself from group
- Given "Mary Jane" is guest of group "Guest"
- When I visit group "Guest" members page
- Then I should see user "John Doe" in team list
- Then I should see user "Mary Jane" in team list
- When I click on the "Remove User From Group" button for "John Doe"
- When I visit group "Guest" members page
- Then I should not see user "John Doe" in team list
- Then I should see user "Mary Jane" in team list
-
- @javascript
- Scenario: Guest should be able to remove himself from group even if he is the only user in the group
- When I visit group "Guest" members page
- Then I should see user "John Doe" in team list
- When I click on the "Remove User From Group" button for "John Doe"
- When I visit group "Guest" members page
- Then I should not see user "John Doe" in team list
-
- # Remove others
-
- Scenario: Owner should be able to remove other users from group
- Given "Mary Jane" is owner of group "Owned"
- When I visit group "Owned" members page
- Then I should see user "John Doe" in team list
- Then I should see user "Mary Jane" in team list
- When I click on the "Remove User From Group" button for "Mary Jane"
- When I visit group "Owned" members page
- Then I should see user "John Doe" in team list
- Then I should not see user "Mary Jane" in team list
-
- Scenario: Guest should not be able to remove other users from group
- Given "Mary Jane" is guest of group "Guest"
- When I visit group "Guest" members page
- Then I should see user "John Doe" in team list
- Then I should see user "Mary Jane" in team list
- Then I should not see the "Remove User From Group" button for "Mary Jane"
-
- Scenario: Search member by name
- Given "Mary Jane" is guest of group "Guest"
- And I visit group "Guest" members page
- When I search for 'Mary' member
- Then I should see user "Mary Jane" in team list
- Then I should not see user "John Doe" in team list
-
- # Group milestones
-
- Scenario: I should see group "Owned" milestone index page with no milestones
- When I visit group "Owned" page
- And I click on group milestones
- Then I should see group milestones index page has no milestones
-
- Scenario: I should see group "Owned" milestone index page with milestones
- Given Group has projects with milestones
- When I visit group "Owned" page
- And I click on group milestones
- Then I should see group milestones index page with milestones
-
- Scenario: I should see group "Owned" milestone show page
- Given Group has projects with milestones
- When I visit group "Owned" page
- And I click on group milestones
- And I click on one group milestone
- Then I should see group milestone with descriptions and expiry date
- And I should see group milestone with all issues and MRs assigned to that milestone
-
- Scenario: Create multiple milestones with one form
- Given I visit group "Owned" milestones page
- And I click new milestone button
- And I fill milestone name
- When I press create mileston button
- Then milestone in each project should be created
-
# Group projects in settings
Scenario: I should see all projects in the project list in settings
Given Group "Owned" has archived project
diff --git a/features/project/issues/award_emoji.feature b/features/project/issues/award_emoji.feature
new file mode 100644
index 00000000000..a9bc8ffb9bb
--- /dev/null
+++ b/features/project/issues/award_emoji.feature
@@ -0,0 +1,14 @@
+Feature: Award Emoji
+ Background:
+ Given I sign in as a user
+ And I own project "Shop"
+ And project "Shop" has issue "Bugfix"
+ And I visit "Bugfix" issue page
+
+ @javascript
+ Scenario: I add and remove award in the issue
+ Given I click to emoji-picker
+ And I click to emoji in the picker
+ Then I have award added
+ And I can remove it by clicking to icon
+ \ No newline at end of file
diff --git a/features/project/merge_requests/accept.feature b/features/project/merge_requests/accept.feature
new file mode 100644
index 00000000000..3e6e59a3808
--- /dev/null
+++ b/features/project/merge_requests/accept.feature
@@ -0,0 +1,17 @@
+Feature: Project Merge Requests Acceptance
+ Background:
+ Given There is an open Merge Request
+ And I am signed in as a developer of the project
+
+ @javascript
+ Scenario: Accepting the Merge Request and removing the source branch
+ Given I am on the Merge Request detail page
+ When I click on "Remove source branch" option
+ And I click on Accept Merge Request
+ Then I should not see the Remove Source Branch button
+
+ @javascript
+ Scenario: Accepting the Merge Request without removing the source branch
+ Given I am on the Merge Request detail page
+ When I click on Accept Merge Request
+ Then I should see the Remove Source Branch button
diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature
index 69aa79f2d24..e545ea63ca8 100644
--- a/features/project/source/browse_files.feature
+++ b/features/project/source/browse_files.feature
@@ -42,7 +42,7 @@ Feature: Project Source Browse Files
And I fill the new branch name
And I click on "Upload file"
Then I can see the new text file
- And I am redirected to the uploaded file on new branch
+ And I am redirected to the new merge request page
And I can see the new commit message
@javascript
@@ -64,7 +64,7 @@ Feature: Project Source Browse Files
And I fill the commit message
And I fill the new branch name
And I click on "Commit Changes"
- Then I am redirected to the new file on new branch
+ Then I am redirected to the new merge request page
And I should see its new content
@javascript
@@ -134,7 +134,7 @@ Feature: Project Source Browse Files
And I fill the commit message
And I fill the new branch name
And I click on "Commit Changes"
- Then I am redirected to the ".gitignore" on new branch
+ Then I am redirected to the new merge request page
And I should see its new content
@javascript @wip
@@ -154,7 +154,7 @@ Feature: Project Source Browse Files
And I fill the commit message
And I fill the new branch name
And I click on "Create directory"
- Then I am redirected to the new directory
+ Then I am redirected to the new merge request page
@javascript
Scenario: I attempt to create an existing directory
@@ -174,12 +174,12 @@ Feature: Project Source Browse Files
Then I see diff
@javascript
- Scenario: I can remove file and commit
+ Scenario: I can delete file and commit
Given I click on ".gitignore" file in repo
And I see the ".gitignore"
- And I click on "Remove"
+ And I click on "Delete"
And I fill the commit message
- And I click on "Remove file"
+ And I click on "Delete file"
Then I am redirected to the files URL
And I don't see the ".gitignore"
diff --git a/features/project/team_management.feature b/features/project/team_management.feature
index 09a7df59df6..06fb45c8bde 100644
--- a/features/project/team_management.feature
+++ b/features/project/team_management.feature
@@ -13,14 +13,12 @@ Feature: Project Team Management
@javascript
Scenario: Add user to project
- Given I click link "Add members"
- And I select "Mike" as "Reporter"
+ When I select "Mike" as "Reporter"
Then I should see "Mike" in team list as "Reporter"
@javascript
Scenario: Invite user to project
- Given I click link "Add members"
- And I select "sjobs@apple.com" as "Reporter"
+ When I select "sjobs@apple.com" as "Reporter"
Then I should see "sjobs@apple.com" in team list as invited "Reporter"
@javascript
diff --git a/features/steps/admin/labels.rb b/features/steps/admin/labels.rb
index b45d98658bc..2ea5dffdc66 100644
--- a/features/steps/admin/labels.rb
+++ b/features/steps/admin/labels.rb
@@ -17,7 +17,7 @@ class Spinach::Features::AdminIssuesLabels < Spinach::FeatureSteps
step 'I remove label \'bug\'' do
page.within "#label_#{bug_label.id}" do
- click_link 'Remove'
+ click_link 'Delete'
end
end
@@ -45,21 +45,21 @@ class Spinach::Features::AdminIssuesLabels < Spinach::FeatureSteps
step 'I submit new label \'support\'' do
visit new_admin_label_path
fill_in 'Title', with: 'support'
- fill_in 'Background Color', with: '#F95610'
+ fill_in 'Background color', with: '#F95610'
click_button 'Save'
end
step 'I submit new label \'bug\'' do
visit new_admin_label_path
fill_in 'Title', with: 'bug'
- fill_in 'Background Color', with: '#F95610'
+ fill_in 'Background color', with: '#F95610'
click_button 'Save'
end
step 'I submit new label with invalid color' do
visit new_admin_label_path
fill_in 'Title', with: 'support'
- fill_in 'Background Color', with: '#12'
+ fill_in 'Background color', with: '#12'
click_button 'Save'
end
@@ -101,7 +101,7 @@ class Spinach::Features::AdminIssuesLabels < Spinach::FeatureSteps
step 'I change label \'bug\' to \'fix\'' do
fill_in 'Title', with: 'fix'
- fill_in 'Background Color', with: '#F15610'
+ fill_in 'Background color', with: '#F15610'
click_button 'Save'
end
diff --git a/features/steps/dashboard/new_project.rb b/features/steps/dashboard/new_project.rb
index 44a4aa9844a..a0aad66184d 100644
--- a/features/steps/dashboard/new_project.rb
+++ b/features/steps/dashboard/new_project.rb
@@ -44,7 +44,6 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps
git_import_instructions = first('.js-toggle-content')
expect(git_import_instructions).to be_visible
expect(git_import_instructions).to have_content "Git repository URL"
- expect(git_import_instructions).to have_content "The repository must be accessible over HTTP(S). If it is not publicly accessible, you can add authentication information to the URL:"
end
step 'I click on "Google Code"' do
diff --git a/features/steps/group/members.rb b/features/steps/group/members.rb
new file mode 100644
index 00000000000..0706df3aec5
--- /dev/null
+++ b/features/steps/group/members.rb
@@ -0,0 +1,147 @@
+class Spinach::Features::GroupMembers < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedGroup
+ include SharedUser
+ include Select2Helper
+
+ step 'I select "Mike" as "Reporter"' do
+ user = User.find_by(name: "Mike")
+
+ page.within ".users-group-form" do
+ select2(user.id, from: "#user_ids", multiple: true)
+ select "Reporter", from: "access_level"
+ end
+
+ click_button "Add users to group"
+ end
+
+ step 'I select "Mike" as "Master"' do
+ user = User.find_by(name: "Mike")
+
+ page.within ".users-group-form" do
+ select2(user.id, from: "#user_ids", multiple: true)
+ select "Master", from: "access_level"
+ end
+
+ click_button "Add users to group"
+ end
+
+ step 'I should see "Mike" in team list as "Reporter"' do
+ page.within '.content-list' do
+ expect(page).to have_content('Mike')
+ expect(page).to have_content('Reporter')
+ end
+ end
+
+ step 'I should see "Mike" in team list as "Owner"' do
+ page.within '.content-list' do
+ expect(page).to have_content('Mike')
+ expect(page).to have_content('Owner')
+ end
+ end
+
+ step 'I select "sjobs@apple.com" as "Reporter"' do
+ page.within ".users-group-form" do
+ select2("sjobs@apple.com", from: "#user_ids", multiple: true)
+ select "Reporter", from: "access_level"
+ end
+
+ click_button "Add users to group"
+ end
+
+ step 'I should see "sjobs@apple.com" in team list as invited "Reporter"' do
+ page.within '.content-list' do
+ expect(page).to have_content('sjobs@apple.com')
+ expect(page).to have_content('invited')
+ expect(page).to have_content('Reporter')
+ end
+ end
+
+ step 'I select user "Mary Jane" from list with role "Reporter"' do
+ user = User.find_by(name: "Mary Jane") || create(:user, name: "Mary Jane")
+
+ page.within ".users-group-form" do
+ select2(user.id, from: "#user_ids", multiple: true)
+ select "Reporter", from: "access_level"
+ end
+
+ click_button "Add users to group"
+ end
+
+ step 'I should see user "John Doe" in team list' do
+ expect(group_members_list).to have_content("John Doe")
+ end
+
+ step 'I should not see user "John Doe" in team list' do
+ expect(group_members_list).not_to have_content("John Doe")
+ end
+
+ step 'I should see user "Mary Jane" in team list' do
+ expect(group_members_list).to have_content("Mary Jane")
+ end
+
+ step 'I should not see user "Mary Jane" in team list' do
+ expect(group_members_list).not_to have_content("Mary Jane")
+ end
+
+ step 'I click on the "Remove User From Group" button for "John Doe"' do
+ find(:css, 'li', text: "John Doe").find(:css, 'a.btn-remove').click
+ # poltergeist always confirms popups.
+ end
+
+ step 'I click on the "Remove User From Group" button for "Mary Jane"' do
+ find(:css, 'li', text: "Mary Jane").find(:css, 'a.btn-remove').click
+ # poltergeist always confirms popups.
+ end
+
+ step 'I should not see the "Remove User From Group" button for "John Doe"' do
+ expect(find(:css, 'li', text: "John Doe")).not_to have_selector(:css, 'a.btn-remove')
+ # poltergeist always confirms popups.
+ end
+
+ step 'I should not see the "Remove User From Group" button for "Mary Jane"' do
+ expect(find(:css, 'li', text: "Mary Jane")).not_to have_selector(:css, 'a.btn-remove')
+ # poltergeist always confirms popups.
+ end
+
+ step 'I search for \'Mary\' member' do
+ page.within '.member-search-form' do
+ fill_in 'search', with: 'Mary'
+ click_button 'Search'
+ end
+ end
+
+ step 'I change the "Mary Jane" role to "Developer"' do
+ member = mary_jane_member
+
+ page.within "#group_member_#{member.id}" do
+ find(".js-toggle-button").click
+ page.within "#edit_group_member_#{member.id}" do
+ select 'Developer', from: 'group_member_access_level'
+ click_on 'Save'
+ end
+ end
+ end
+
+ step 'I should see "Mary Jane" as "Developer"' do
+ member = mary_jane_member
+
+ page.within "#group_member_#{member.id}" do
+ page.within '.member-access-level' do
+ expect(page).to have_content "Developer"
+ end
+ end
+ end
+
+ private
+
+ def mary_jane_member
+ user = User.find_by(name: "Mary Jane")
+ owned_group.members.find_by(user_id: user.id)
+ end
+
+ def group_members_list
+ find(".panel .content-list")
+ end
+end
diff --git a/features/steps/group/milestones.rb b/features/steps/group/milestones.rb
new file mode 100644
index 00000000000..6e57b16ccb6
--- /dev/null
+++ b/features/steps/group/milestones.rb
@@ -0,0 +1,90 @@
+class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedGroup
+ include SharedUser
+
+ step 'I click on group milestones' do
+ click_link 'Milestones'
+ end
+
+ step 'I should see group milestones index page has no milestones' do
+ expect(page).to have_content('No milestones to show')
+ end
+
+ step 'Group has projects with milestones' do
+ group_milestone
+ end
+
+ step 'I should see group milestones index page with milestones' do
+ expect(page).to have_content('Version 7.2')
+ expect(page).to have_content('GL-113')
+ expect(page).to have_link('3 Issues', href: issues_group_path("owned", milestone_title: "Version 7.2"))
+ expect(page).to have_link('0 Merge Requests', href: merge_requests_group_path("owned", milestone_title: "GL-113"))
+ end
+
+ step 'I click on one group milestone' do
+ click_link 'GL-113'
+ end
+
+ step 'I should see group milestone with descriptions and expiry date' do
+ expect(page).to have_content('expires at Aug 20, 2114')
+ end
+
+ step 'I should see group milestone with all issues and MRs assigned to that milestone' do
+ expect(page).to have_content('Milestone GL-113')
+ expect(page).to have_content('Progress: 0 closed – 3 open')
+ issue = Milestone.find_by(name: 'GL-113').issues.first
+ expect(page).to have_link(issue.title, href: namespace_project_issue_path(issue.project.namespace, issue.project, issue))
+ end
+
+ step 'I fill milestone name' do
+ fill_in 'milestone_title', with: 'v2.9.0'
+ end
+
+ step 'I click new milestone button' do
+ click_link "New Milestone"
+ end
+
+ step 'I press create mileston button' do
+ click_button "Create Milestone"
+ end
+
+ step 'milestone in each project should be created' do
+ group = Group.find_by(name: 'Owned')
+ expect(page).to have_content "Milestone v2.9.0"
+ expect(group.projects).to be_present
+
+ group.projects.each do |project|
+ expect(page).to have_content project.name
+ end
+ end
+
+ private
+
+ def group_milestone
+ group = owned_group
+
+ %w(gitlabhq gitlab-ci cookbook-gitlab).each do |path|
+ project = create :project, path: path, group: group
+ milestone = create :milestone, title: "Version 7.2", project: project
+ create :issue,
+ project: project,
+ assignee: current_user,
+ author: current_user,
+ milestone: milestone
+
+ milestone = create :milestone,
+ title: "GL-113",
+ project: project,
+ due_date: '2114-08-20',
+ description: 'Lorem Ipsum is simply dummy text'
+
+ create :issue,
+ project: project,
+ assignee: current_user,
+ author: current_user,
+ milestone: milestone
+ end
+ end
+end
diff --git a/features/steps/groups.rb b/features/steps/groups.rb
index 9c0313537b1..f5e3fee61c0 100644
--- a/features/steps/groups.rb
+++ b/features/steps/groups.rb
@@ -3,20 +3,11 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
include SharedPaths
include SharedGroup
include SharedUser
- include Select2Helper
step 'I should see back to dashboard button' do
expect(page).to have_content 'Go to dashboard'
end
- step 'gitlab user "Mike"' do
- create(:user, name: "Mike")
- end
-
- step 'I click link "Add members"' do
- find(:css, 'button.btn-new').click
- end
-
step 'I should see group "Owned"' do
expect(page).to have_content '@owned'
end
@@ -26,7 +17,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
end
step 'Group "Owned" has a public project "Public-project"' do
- group = Group.find_by(name: "Owned")
+ group = owned_group
@project = create :empty_project, :public,
group: group,
@@ -37,61 +28,8 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
expect(page).to have_content 'Public-project'
end
- step 'I select "Mike" as "Reporter"' do
- user = User.find_by(name: "Mike")
-
- page.within ".users-group-form" do
- select2(user.id, from: "#user_ids", multiple: true)
- select "Reporter", from: "access_level"
- end
-
- click_button "Add users to group"
- end
-
- step 'I select "Mike" as "Master"' do
- user = User.find_by(name: "Mike")
-
- page.within ".users-group-form" do
- select2(user.id, from: "#user_ids", multiple: true)
- select "Master", from: "access_level"
- end
-
- click_button "Add users to group"
- end
-
- step 'I should see "Mike" in team list as "Reporter"' do
- page.within '.well-list' do
- expect(page).to have_content('Mike')
- expect(page).to have_content('Reporter')
- end
- end
-
- step 'I should see "Mike" in team list as "Owner"' do
- page.within '.well-list' do
- expect(page).to have_content('Mike')
- expect(page).to have_content('Owner')
- end
- end
-
- step 'I select "sjobs@apple.com" as "Reporter"' do
- page.within ".users-group-form" do
- select2("sjobs@apple.com", from: "#user_ids", multiple: true)
- select "Reporter", from: "access_level"
- end
-
- click_button "Add users to group"
- end
-
- step 'I should see "sjobs@apple.com" in team list as invited "Reporter"' do
- page.within '.well-list' do
- expect(page).to have_content('sjobs@apple.com')
- expect(page).to have_content('invited')
- expect(page).to have_content('Reporter')
- end
- end
-
step 'I should see group "Owned" projects list' do
- Group.find_by(name: "Owned").projects.each do |project|
+ owned_group.projects.each do |project|
expect(page).to have_link project.name
end
end
@@ -112,36 +50,6 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
end
end
- step 'I select user "Mary Jane" from list with role "Reporter"' do
- user = User.find_by(name: "Mary Jane") || create(:user, name: "Mary Jane")
- click_button 'Add members'
- page.within ".users-group-form" do
- select2(user.id, from: "#user_ids", multiple: true)
- select "Reporter", from: "access_level"
- end
- click_button "Add users to group"
- end
-
- step 'I should see user "John Doe" in team list' do
- projects_with_access = find(".panel .well-list")
- expect(projects_with_access).to have_content("John Doe")
- end
-
- step 'I should not see user "John Doe" in team list' do
- projects_with_access = find(".panel .well-list")
- expect(projects_with_access).not_to have_content("John Doe")
- end
-
- step 'I should see user "Mary Jane" in team list' do
- projects_with_access = find(".panel .well-list")
- expect(projects_with_access).to have_content("Mary Jane")
- end
-
- step 'I should not see user "Mary Jane" in team list' do
- projects_with_access = find(".panel .well-list")
- expect(projects_with_access).not_to have_content("Mary Jane")
- end
-
step 'project from group "Owned" has issues assigned to me' do
create :issue,
project: project,
@@ -172,12 +80,12 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
step 'I change group "Owned" avatar' do
attach_file(:group_avatar, File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif'))
click_button "Save group"
- Group.find_by(name: "Owned").reload
+ owned_group.reload
end
step 'I should see new group "Owned" avatar' do
- expect(Group.find_by(name: "Owned").avatar).to be_instance_of AvatarUploader
- expect(Group.find_by(name: "Owned").avatar.url).to eq "/uploads/group/avatar/#{ Group.find_by(name:"Owned").id }/banana_sample.gif"
+ expect(owned_group.avatar).to be_instance_of AvatarUploader
+ expect(owned_group.avatar.url).to eq "/uploads/group/avatar/#{ Group.find_by(name:"Owned").id }/banana_sample.gif"
end
step 'I should see the "Remove avatar" button' do
@@ -187,83 +95,22 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
step 'I have group "Owned" avatar' do
attach_file(:group_avatar, File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif'))
click_button "Save group"
- Group.find_by(name: "Owned").reload
+ owned_group.reload
end
step 'I remove group "Owned" avatar' do
click_link "Remove avatar"
- Group.find_by(name: "Owned").reload
+ owned_group.reload
end
step 'I should not see group "Owned" avatar' do
- expect(Group.find_by(name: "Owned").avatar?).to eq false
+ expect(owned_group.avatar?).to eq false
end
step 'I should not see the "Remove avatar" button' do
expect(page).not_to have_link("Remove avatar")
end
- step 'I click on the "Remove User From Group" button for "John Doe"' do
- find(:css, 'li', text: "John Doe").find(:css, 'a.btn-remove').click
- # poltergeist always confirms popups.
- end
-
- step 'I click on the "Remove User From Group" button for "Mary Jane"' do
- find(:css, 'li', text: "Mary Jane").find(:css, 'a.btn-remove').click
- # poltergeist always confirms popups.
- end
-
- step 'I should not see the "Remove User From Group" button for "John Doe"' do
- expect(find(:css, 'li', text: "John Doe")).not_to have_selector(:css, 'a.btn-remove')
- # poltergeist always confirms popups.
- end
-
- step 'I should not see the "Remove User From Group" button for "Mary Jane"' do
- expect(find(:css, 'li', text: "Mary Jane")).not_to have_selector(:css, 'a.btn-remove')
- # poltergeist always confirms popups.
- end
-
- step 'I search for \'Mary\' member' do
- page.within '.member-search-form' do
- fill_in 'search', with: 'Mary'
- click_button 'Search'
- end
- end
-
- step 'I click on group milestones' do
- click_link 'Milestones'
- end
-
- step 'I should see group milestones index page has no milestones' do
- expect(page).to have_content('No milestones to show')
- end
-
- step 'Group has projects with milestones' do
- group_milestone
- end
-
- step 'I should see group milestones index page with milestones' do
- expect(page).to have_content('Version 7.2')
- expect(page).to have_content('GL-113')
- expect(page).to have_link('2 Issues', href: issues_group_path("owned", milestone_title: "Version 7.2"))
- expect(page).to have_link('3 Merge Requests', href: merge_requests_group_path("owned", milestone_title: "GL-113"))
- end
-
- step 'I click on one group milestone' do
- click_link 'GL-113'
- end
-
- step 'I should see group milestone with descriptions and expiry date' do
- expect(page).to have_content('expires at Aug 20, 2114')
- end
-
- step 'I should see group milestone with all issues and MRs assigned to that milestone' do
- expect(page).to have_content('Milestone GL-113')
- expect(page).to have_content('Progress: 0 closed – 4 open')
- expect(page).to have_link(@issue1.title, href: namespace_project_issue_path(@project1.namespace, @project1, @issue1))
- expect(page).to have_link(@mr3.title, href: namespace_project_merge_request_path(@project3.namespace, @project3, @mr3))
- end
-
step 'Group "Owned" has archived project' do
group = Group.find_by(name: 'Owned')
create(:project, namespace: group, archived: true, path: "archived-project")
@@ -273,101 +120,13 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
expect(page).to have_xpath("//span[@class='label label-warning']", text: 'archived')
end
- step 'I fill milestone name' do
- fill_in 'milestone_title', with: 'v2.9.0'
- end
-
- step 'I click new milestone button' do
- click_link "New Milestone"
- end
-
- step 'I press create mileston button' do
- click_button "Create Milestone"
- end
-
- step 'milestone in each project should be created' do
- group = Group.find_by(name: 'Owned')
- expect(page).to have_content "Milestone v2.9.0"
- expect(group.projects).to be_present
-
- group.projects.each do |project|
- expect(page).to have_content project.name
- end
- end
-
- protected
+ private
def assigned_to_me(key)
project.send(key).where(assignee_id: current_user.id)
end
def project
- Group.find_by(name: "Owned").projects.first
- end
-
- def group_milestone
- group = Group.find_by(name: "Owned")
-
- @project1 = create :project,
- group: group
- project2 = create :project,
- path: 'gitlab-ci',
- group: group
- @project3 = create :project,
- path: 'cookbook-gitlab',
- group: group
- milestone1_project1 = create :milestone,
- title: "Version 7.2",
- project: @project1
- milestone1_project2 = create :milestone,
- title: "Version 7.2",
- project: project2
- create :milestone,
- title: "Version 7.2",
- project: @project3
- milestone2_project1 = create :milestone,
- title: "GL-113",
- project: @project1
- milestone2_project2 = create :milestone,
- title: "GL-113",
- project: project2
- milestone2_project3 = create :milestone,
- title: "GL-113",
- project: @project3,
- due_date: '2114-08-20',
- description: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry'
- @issue1 = create :issue,
- project: @project1,
- assignee: current_user,
- author: current_user,
- milestone: milestone2_project1
- create :issue,
- project: project2,
- assignee: current_user,
- author: current_user,
- milestone: milestone1_project2
- create :issue,
- project: @project3,
- assignee: current_user,
- author: current_user,
- milestone: milestone1_project1
- create :merge_request,
- source_project: @project1,
- target_project: @project1,
- assignee: current_user,
- author: current_user,
- milestone: milestone2_project1
- create :merge_request,
- source_project: project2,
- target_project: project2,
- assignee: current_user,
- author: current_user,
- milestone: milestone2_project2
- @mr3 = create :merge_request,
- source_project: @project3,
- target_project: @project3,
- assignee: current_user,
- author: current_user,
- milestone: milestone2_project3
+ owned_group.projects.first
end
end
diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb
index 2a333222fb2..d3675060994 100644
--- a/features/steps/project/forked_merge_requests.rb
+++ b/features/steps/project/forked_merge_requests.rb
@@ -39,14 +39,14 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
select "fix", from: "merge_request_source_branch"
select "master", from: "merge_request_target_branch"
- click_button "Compare branches"
+ click_button "Compare branches and continue"
- expect(page).to have_content "New merge request"
+ expect(page).to have_content "New Merge Request"
fill_in "merge_request_title", with: "Merge Request On Forked Project"
end
step 'I submit the merge request' do
- click_button "Submit new merge request"
+ click_button "Submit merge request"
end
step 'I follow the target commit link' do
@@ -112,11 +112,10 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
end
step 'I fill out an invalid "Merge Request On Forked Project" merge request' do
- select "Select branch", from: "merge_request_target_branch"
expect(find(:select, "merge_request_source_project_id", {}).value).to eq @forked_project.id.to_s
expect(find(:select, "merge_request_target_project_id", {}).value).to eq @project.id.to_s
expect(find(:select, "merge_request_source_branch", {}).value).to eq ""
- expect(find(:select, "merge_request_target_branch", {}).value).to eq ""
+ expect(find(:select, "merge_request_target_branch", {}).value).to eq "master"
click_button "Compare branches"
end
diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb
new file mode 100644
index 00000000000..8f7a45dec0e
--- /dev/null
+++ b/features/steps/project/issues/award_emoji.rb
@@ -0,0 +1,41 @@
+class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedPaths
+ include Select2Helper
+
+ step 'I visit "Bugfix" issue page' do
+ visit namespace_project_issue_path(@project.namespace, @project, @issue)
+ end
+
+ step 'I click to emoji-picker' do
+ page.within ".awards-controls" do
+ page.find(".add-award").click
+ end
+ end
+
+ step 'I click to emoji in the picker' do
+ page.within ".awards-menu" do
+ page.first("img").click
+ end
+ end
+
+ step 'I can remove it by clicking to icon' do
+ page.within ".awards" do
+ page.first(".award").click
+ expect(page).to_not have_selector ".award"
+ end
+ end
+
+ step 'I have award added' do
+ page.within ".awards" do
+ expect(page).to have_selector ".award"
+ expect(page.find(".award .counter")).to have_content "1"
+ end
+ end
+
+ step 'project "Shop" has issue "Bugfix"' do
+ @project = Project.find_by(name: "Shop")
+ @issue = create(:issue, title: "Bugfix", project: project)
+ end
+end
diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb
index af2da41badb..a13044c3ae1 100644
--- a/features/steps/project/issues/issues.rb
+++ b/features/steps/project/issues/issues.rb
@@ -65,20 +65,20 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
step 'I see current user as the first user' do
expect(page).to have_selector('.user-result', visible: true, count: 4)
users = page.all('.user-name')
- expect(users[0].text).to eq 'Any'
+ expect(users[0].text).to eq 'Any Assignee'
expect(users[1].text).to eq 'Unassigned'
expect(users[2].text).to eq current_user.name
end
step 'I submit new issue "500 error on profile"' do
fill_in "issue_title", with: "500 error on profile"
- click_button "Submit new issue"
+ click_button "Submit issue"
end
step 'I submit new issue "500 error on profile" with label \'bug\'' do
fill_in "issue_title", with: "500 error on profile"
select 'bug', from: "Labels"
- click_button "Submit new issue"
+ click_button "Submit issue"
end
step 'I click link "500 error on profile"' do
@@ -86,7 +86,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end
step 'I should see label \'bug\' with issue' do
- page.within '.issue-show-labels' do
+ page.within '.issuable-show-labels' do
expect(page).to have_content 'bug'
end
end
diff --git a/features/steps/project/issues/labels.rb b/features/steps/project/issues/labels.rb
index d656acf4220..e273bb391b3 100644
--- a/features/steps/project/issues/labels.rb
+++ b/features/steps/project/issues/labels.rb
@@ -9,7 +9,7 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
step 'I remove label \'bug\'' do
page.within "#label_#{bug_label.id}" do
- click_link 'Remove'
+ click_link 'Delete'
end
end
@@ -31,20 +31,20 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
step 'I submit new label \'support\'' do
fill_in 'Title', with: 'support'
- fill_in 'Background Color', with: '#F95610'
- click_button 'Save'
+ fill_in 'Background color', with: '#F95610'
+ click_button 'Create Label'
end
step 'I submit new label \'bug\'' do
fill_in 'Title', with: 'bug'
- fill_in 'Background Color', with: '#F95610'
- click_button 'Save'
+ fill_in 'Background color', with: '#F95610'
+ click_button 'Create Label'
end
step 'I submit new label with invalid color' do
fill_in 'Title', with: 'support'
- fill_in 'Background Color', with: '#12'
- click_button 'Save'
+ fill_in 'Background color', with: '#12'
+ click_button 'Create Label'
end
step 'I should see label label exist error message' do
@@ -85,8 +85,8 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
step 'I change label \'bug\' to \'fix\'' do
fill_in 'Title', with: 'fix'
- fill_in 'Background Color', with: '#F15610'
- click_button 'Save'
+ fill_in 'Background color', with: '#F15610'
+ click_button 'Save changes'
end
step 'I should see label \'fix\'' do
diff --git a/features/steps/project/issues/milestones.rb b/features/steps/project/issues/milestones.rb
index c8708572ec6..e2eda511497 100644
--- a/features/steps/project/issues/milestones.rb
+++ b/features/steps/project/issues/milestones.rb
@@ -63,7 +63,7 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
end
step 'I click link to remove milestone' do
- click_link 'Remove'
+ click_link 'Delete'
end
step 'I should see no milestones' do
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index d5f2c4209a1..822cf0ffe1c 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -86,7 +86,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
select "feature", from: "merge_request_target_branch"
click_button "Compare branches"
fill_in "merge_request_title", with: "Wiki Feature"
- click_button "Submit new merge request"
+ click_button "Submit merge request"
end
step 'project "Shop" have "Bug NS-04" open merge request' do
diff --git a/features/steps/project/merge_requests/acceptance.rb b/features/steps/project/merge_requests/acceptance.rb
new file mode 100644
index 00000000000..6adecaa8385
--- /dev/null
+++ b/features/steps/project/merge_requests/acceptance.rb
@@ -0,0 +1,35 @@
+class Spinach::Features::ProjectMergeRequestsAcceptance < Spinach::FeatureSteps
+ include LoginHelpers
+ include GitlabRoutingHelper
+
+ step 'I am on the Merge Request detail page' do
+ visit merge_request_path(@merge_request)
+ end
+
+ step 'I click on "Remove source branch" option' do
+ check('Remove source branch')
+ end
+
+ step 'I click on Accept Merge Request' do
+ click_button('Accept Merge Request')
+ end
+
+ step 'I should see the Remove Source Branch button' do
+ expect(page).to have_link('Remove Source Branch')
+ end
+
+ step 'I should not see the Remove Source Branch button' do
+ expect(page).not_to have_link('Remove Source Branch')
+ end
+
+ step 'There is an open Merge Request' do
+ @user = create(:user)
+ @project = create(:project, :public)
+ @project_member = create(:project_member, user: @user, project: @project, access_level: ProjectMember::DEVELOPER)
+ @merge_request = create(:merge_request, :with_diffs, :simple, source_project: @project)
+ end
+
+ step 'I am signed in as a developer of the project' do
+ login_as(@user)
+ end
+end
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index 84725b9b585..05d1346d006 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -87,7 +87,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I click link "Diff"' do
- click_link 'Preview changes'
+ click_link 'Preview Changes'
end
step 'I click on "Commit Changes"' do
@@ -98,12 +98,12 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
click_button 'Create directory'
end
- step 'I click on "Remove"' do
- click_button 'Remove'
+ step 'I click on "Delete"' do
+ click_button 'Delete'
end
- step 'I click on "Remove file"' do
- click_button 'Remove file'
+ step 'I click on "Delete file"' do
+ click_button 'Delete file'
end
step 'I click on "Replace"' do
@@ -142,7 +142,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I can see new file page' do
- expect(page).to have_content "new file"
+ expect(page).to have_content "New File"
expect(page).to have_content "Commit message"
end
@@ -192,7 +192,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I see Browse dir link' do
- expect(page).to have_link 'Browse Dir »'
+ expect(page).to have_link 'Browse Directory »'
expect(page).not_to have_link 'Browse Code »'
end
@@ -204,13 +204,13 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
step 'I see Browse file link' do
expect(page).to have_link 'Browse File »'
- expect(page).not_to have_link 'Browse Code »'
+ expect(page).not_to have_link 'Browse Files »'
end
step 'I see Browse code link' do
- expect(page).to have_link 'Browse Code »'
+ expect(page).to have_link 'Browse Files »'
expect(page).not_to have_link 'Browse File »'
- expect(page).not_to have_link 'Browse Dir »'
+ expect(page).not_to have_link 'Browse Directory »'
end
step 'I click on Permalink' do
@@ -225,10 +225,6 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
expect(current_path).to eq(namespace_project_blob_path(@project.namespace, @project, 'master/.gitignore'))
end
- step 'I am redirected to the ".gitignore" on new branch' do
- expect(current_path).to eq(namespace_project_blob_path(@project.namespace, @project, 'new_branch_name/.gitignore'))
- end
-
step 'I am redirected to the permalink URL' do
expect(current_path).to(
eq(namespace_project_blob_path(@project.namespace, @project,
@@ -247,20 +243,8 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
@project.namespace, @project, 'master/' + new_file_name_with_directory))
end
- step 'I am redirected to the new file on new branch' do
- expect(current_path).to eq(namespace_project_blob_path(
- @project.namespace, @project, 'new_branch_name/' + new_file_name))
- end
-
- step 'I am redirected to the uploaded file on new branch' do
- expect(current_path).to eq(namespace_project_blob_path(
- @project.namespace, @project,
- 'new_branch_name/' + File.basename(test_text_file)))
- end
-
- step 'I am redirected to the new directory' do
- expect(current_path).to eq(namespace_project_tree_path(
- @project.namespace, @project, 'new_branch_name/' + new_dir_name))
+ step 'I am redirected to the new merge request page' do
+ expect(current_path).to eq(new_namespace_project_merge_request_path(@project.namespace, @project))
end
step 'I am redirected to the root directory' do
diff --git a/features/steps/project/source/markdown_render.rb b/features/steps/project/source/markdown_render.rb
index c78e86fa1a7..3a4f7a6e01c 100644
--- a/features/steps/project/source/markdown_render.rb
+++ b/features/steps/project/source/markdown_render.rb
@@ -238,7 +238,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
step 'I see new wiki page named test' do
expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "test")
- expect(page).to have_content "Editing"
+ expect(page).to have_content "Edit Page test"
end
When 'I go back to wiki page home' do
@@ -252,7 +252,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
step 'I see Gitlab API document' do
expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "api")
- expect(page).to have_content "Editing"
+ expect(page).to have_content "Edit Page api"
end
step 'I click on Rake tasks link' do
@@ -261,7 +261,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
step 'I see Rake tasks directory' do
expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "raketasks")
- expect(page).to have_content "Editing"
+ expect(page).to have_content "Edit Page raketasks"
end
step 'I go directory which contains README file' do
diff --git a/features/steps/project/team_management.rb b/features/steps/project/team_management.rb
index 97d63016458..caad52def79 100644
--- a/features/steps/project/team_management.rb
+++ b/features/steps/project/team_management.rb
@@ -15,10 +15,6 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps
expect(page).to have_content(user.username)
end
- step 'I click link "Add members"' do
- find(:css, 'button.btn-new').click
- end
-
step 'I select "Mike" as "Reporter"' do
user = User.find_by(name: "Mike")
diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb
index 02207dbffa6..91d227fadbf 100644
--- a/features/steps/project/wiki.rb
+++ b/features/steps/project/wiki.rb
@@ -5,7 +5,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
include SharedPaths
step 'I click on the Cancel button' do
- page.within(:css, ".form-actions") do
+ page.within(:css, ".wiki-form .form-actions") do
click_on "Cancel"
end
end
@@ -24,7 +24,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
expect(page).to have_content "link test"
click_link "link test"
- expect(page).to have_content "Editing"
+ expect(page).to have_content "Edit Page"
end
step 'I have an existing Wiki page' do
@@ -68,7 +68,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I click on the "Delete this page" button' do
- click_on "Delete this page"
+ click_on "Delete"
end
step 'The page should be deleted' do
@@ -120,13 +120,13 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
step 'I should see the new wiki page form' do
expect(current_path).to match('wikis/image.jpg')
expect(page).to have_content('New Wiki Page')
- expect(page).to have_content('Editing - image.jpg')
+ expect(page).to have_content('Edit Page image.jpg')
end
step 'I create a New page with paths' do
click_on 'New Page'
fill_in 'Page slug', with: 'one/two/three'
- click_on 'Build'
+ click_on 'Create Page'
fill_in "wiki_content", with: 'wiki content'
click_on "Create page"
expect(current_path).to include 'one/two/three'
@@ -135,7 +135,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
step 'I create a New page with an invalid name' do
click_on 'New Page'
fill_in 'Page slug', with: 'invalid name'
- click_on 'Build'
+ click_on 'Create Page'
end
step 'I should see an error message' do
@@ -156,7 +156,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I should see the Editing page' do
- expect(page).to have_content('Editing')
+ expect(page).to have_content('Edit Page')
end
step 'I view the page history of a Wiki page that has a path' do
diff --git a/features/steps/shared/group.rb b/features/steps/shared/group.rb
index 83a04576973..58581653f28 100644
--- a/features/steps/shared/group.rb
+++ b/features/steps/shared/group.rb
@@ -41,4 +41,8 @@ module SharedGroup
project.team << [user, :master]
@project_count += 1
end
+
+ def owned_group
+ @owned_group ||= Group.find_by(name: "Owned")
+ end
end
diff --git a/features/steps/shared/user.rb b/features/steps/shared/user.rb
index fc1e8d6e889..250cc5b94f3 100644
--- a/features/steps/shared/user.rb
+++ b/features/steps/shared/user.rb
@@ -9,6 +9,10 @@ module SharedUser
user_exists("Mary Jane", { username: "mary_jane" })
end
+ step 'gitlab user "Mike"' do
+ create(:user, name: "Mike")
+ end
+
protected
def user_exists(name, options = {})
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 6f9f71b0945..81bf7a8222b 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -162,7 +162,9 @@ module API
end
class MergeRequest < ProjectEntity
- expose :target_branch, :source_branch, :upvotes, :downvotes
+ expose :target_branch, :source_branch
+ # deprecated, always returns 0
+ expose :upvotes, :downvotes
expose :author, :assignee, using: Entities::UserBasic
expose :source_project_id, :target_project_id
expose :label_names, as: :labels
@@ -193,6 +195,8 @@ module API
expose :author, using: Entities::UserBasic
expose :created_at
expose :system?, as: :system
+ expose :noteable_id, :noteable_type
+ # upvote? and downvote? are deprecated, always return false
expose :upvote?, as: :upvote
expose :downvote?, as: :downvote
end
@@ -222,6 +226,8 @@ module API
expose :target_id, :target_type, :author_id
expose :data, :target_title
expose :created_at
+ expose :note, using: Entities::Note, if: ->(event, options) { event.note? }
+ expose :author, using: Entities::UserBasic, if: ->(event, options) { event.author }
expose :author_username do |event, options|
if event.author
@@ -325,7 +331,8 @@ module API
end
class Release < Grape::Entity
- expose :tag, :description
+ expose :tag, as: :tag_name
+ expose :description
end
class RepoTag < Grape::Entity
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 32cb1137ef7..3c1c6bda260 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -76,6 +76,22 @@ module API
present merge_request, with: Entities::MergeRequest
end
+ # Show MR commits
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # merge_request_id (required) - The ID of MR
+ #
+ # Example:
+ # GET /projects/:id/merge_request/:merge_request_id/commits
+ #
+ get ':id/merge_request/:merge_request_id/commits' do
+ merge_request = user_project.merge_requests.
+ find(params[:merge_request_id])
+ authorize! :read_merge_request, merge_request
+ present merge_request.commits, with: Entities::RepoCommit
+ end
+
# Show MR changes
#
# Parameters:
diff --git a/lib/api/tags.rb b/lib/api/tags.rb
index 673342dd447..47621f443e6 100644
--- a/lib/api/tags.rb
+++ b/lib/api/tags.rb
@@ -44,17 +44,42 @@ module API
#
# Parameters:
# id (required) - The ID of a project
- # tag (required) - The name of the tag
+ # tag_name (required) - The name of the tag
+ # description (required) - Release notes with markdown support
+ # Example Request:
+ # POST /projects/:id/repository/tags/:tag_name/release
+ post ':id/repository/tags/:tag_name/release', requirements: { tag_name: /.*/ } do
+ authorize_push_project
+ required_attributes! [:description]
+ result = CreateReleaseService.new(user_project, current_user).
+ execute(params[:tag_name], params[:description])
+
+ if result[:status] == :success
+ present result[:release], with: Entities::Release
+ else
+ render_api_error!(result[:message], result[:http_status])
+ end
+ end
+
+ # Updates a release notes of a tag
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # tag_name (required) - The name of the tag
# description (required) - Release notes with markdown support
# Example Request:
- # PUT /projects/:id/repository/tags
- put ':id/repository/:tag/release', requirements: { tag: /.*/ } do
+ # PUT /projects/:id/repository/tags/:tag_name/release
+ put ':id/repository/tags/:tag_name/release', requirements: { tag_name: /.*/ } do
authorize_push_project
required_attributes! [:description]
- release = user_project.releases.find_or_initialize_by(tag: params[:tag])
- release.update_attributes(description: params[:description])
+ result = UpdateReleaseService.new(user_project, current_user).
+ execute(params[:tag_name], params[:description])
- present release, with: Entities::Release
+ if result[:status] == :success
+ present result[:release], with: Entities::Release
+ else
+ render_api_error!(result[:message], result[:http_status])
+ end
end
end
end
diff --git a/lib/award_emoji.rb b/lib/award_emoji.rb
new file mode 100644
index 00000000000..d58a196c4ef
--- /dev/null
+++ b/lib/award_emoji.rb
@@ -0,0 +1,12 @@
+class AwardEmoji
+ EMOJI_LIST = [
+ "+1", "-1", "100", "blush", "heart", "smile", "rage",
+ "beers", "disappointed", "ok_hand",
+ "helicopter", "shit", "airplane", "alarm_clock",
+ "ambulance", "anguished", "two_hearts", "wink"
+ ]
+
+ def self.path_to_emoji_image(name)
+ "emoji/#{Emoji.emoji_filename(name)}.png"
+ end
+end
diff --git a/lib/backup/lfs.rb b/lib/backup/lfs.rb
new file mode 100644
index 00000000000..4153467fbee
--- /dev/null
+++ b/lib/backup/lfs.rb
@@ -0,0 +1,13 @@
+require 'backup/files'
+
+module Backup
+ class Lfs < Files
+ def initialize
+ super('lfs', Settings.lfs.storage_path)
+ end
+
+ def create_files_dir
+ Dir.mkdir(app_files_dir, 0700)
+ end
+ end
+end
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index 9e15d5411a1..099062eeb8b 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -150,17 +150,15 @@ module Backup
private
def backup_contents
- folders_to_backup + ["uploads.tar.gz", "builds.tar.gz", "artifacts.tar.gz", "backup_information.yml"]
+ folders_to_backup + archives_to_backup + ["backup_information.yml"]
end
- def folders_to_backup
- folders = %w{repositories db}
-
- if ENV["SKIP"]
- return folders.reject{ |folder| ENV["SKIP"].include?(folder) }
- end
+ def archives_to_backup
+ %w{uploads builds artifacts lfs}.map{ |name| (name + ".tar.gz") unless skipped?(name) }.compact
+ end
- folders
+ def folders_to_backup
+ %w{repositories db}.reject{ |name| skipped?(name) }
end
def settings
diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb
index 0a586672807..15faa6edd84 100644
--- a/lib/ci/api/builds.rb
+++ b/lib/ci/api/builds.rb
@@ -58,6 +58,7 @@ module Ci
# POST /builds/:id/artifacts/authorize
post ":id/artifacts/authorize" do
require_gitlab_workhorse!
+ not_allowed! unless Gitlab.config.artifacts.enabled
build = Ci::Build.find_by_id(params[:id])
not_found! unless build
authenticate_build_token!(build)
@@ -91,6 +92,7 @@ module Ci
# POST /builds/:id/artifacts
post ":id/artifacts" do
require_gitlab_workhorse!
+ not_allowed! unless Gitlab.config.artifacts.enabled
build = Ci::Build.find_by_id(params[:id])
not_found! unless build
authenticate_build_token!(build)
diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb
index 01b8bda05c6..87ac30b5ffe 100644
--- a/lib/gitlab/backend/shell.rb
+++ b/lib/gitlab/backend/shell.rb
@@ -1,6 +1,6 @@
module Gitlab
class Shell
- class AccessDenied < StandardError; end
+ class Error < StandardError; end
class KeyAdder < Struct.new(:io)
def add_key(id, key)
@@ -36,8 +36,9 @@ module Gitlab
# import_repository("gitlab/gitlab-ci", "https://github.com/randx/six.git")
#
def import_repository(name, url)
- Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'import-project',
- "#{name}.git", url, '240'])
+ output, status = Popen::popen([gitlab_shell_projects_path, 'import-project', "#{name}.git", url, '240'])
+ raise Error, output unless status.zero?
+ true
end
# Move repository
diff --git a/lib/gitlab/closing_issue_extractor.rb b/lib/gitlab/closing_issue_extractor.rb
index aeec595782c..9bef9037ad6 100644
--- a/lib/gitlab/closing_issue_extractor.rb
+++ b/lib/gitlab/closing_issue_extractor.rb
@@ -1,6 +1,12 @@
module Gitlab
class ClosingIssueExtractor
- ISSUE_CLOSING_REGEX = Regexp.new(Gitlab.config.gitlab.issue_closing_pattern)
+ ISSUE_CLOSING_REGEX = begin
+ link_pattern = URI.regexp(%w(http https))
+
+ pattern = Gitlab.config.gitlab.issue_closing_pattern
+ pattern = pattern.sub('%{issue_ref}', "(?:(?:#{link_pattern})|(?:#{Issue.reference_pattern}))")
+ Regexp.new(pattern).freeze
+ end
def initialize(project, current_user = nil)
@extractor = Gitlab::ReferenceExtractor.new(project, current_user)
@@ -9,10 +15,12 @@ module Gitlab
def closed_by_message(message)
return [] if message.nil?
- closing_statements = message.scan(ISSUE_CLOSING_REGEX).
- map { |ref| ref[0] }.join(" ")
+ closing_statements = []
+ message.scan(ISSUE_CLOSING_REGEX) do
+ closing_statements << Regexp.last_match[0]
+ end
- @extractor.analyze(closing_statements)
+ @extractor.analyze(closing_statements.join(" "))
@extractor.issues
end
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index 2d3e32d9539..46a4ef0e31f 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -25,7 +25,7 @@ module Gitlab
session_expire_delay: Settings.gitlab['session_expire_delay'],
import_sources: Settings.gitlab['import_sources'],
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
- max_artifacts_size: Ci::Settings.gitlab_ci['max_artifacts_size'],
+ max_artifacts_size: Settings.artifacts['max_size'],
)
end
diff --git a/lib/gitlab/git/hook.rb b/lib/gitlab/git/hook.rb
index dd393fe09d2..07b856ca64c 100644
--- a/lib/gitlab/git/hook.rb
+++ b/lib/gitlab/git/hook.rb
@@ -16,6 +16,17 @@ module Gitlab
def trigger(gl_id, oldrev, newrev, ref)
return true unless exists?
+ case name
+ when "pre-receive", "post-receive"
+ call_receive_hook(gl_id, oldrev, newrev, ref)
+ when "update"
+ call_update_hook(gl_id, oldrev, newrev, ref)
+ end
+ end
+
+ private
+
+ def call_receive_hook(gl_id, oldrev, newrev, ref)
changes = [oldrev, newrev, ref].join(" ")
# function will return true if succesful
@@ -54,6 +65,12 @@ module Gitlab
exit_status
end
+
+ def call_update_hook(gl_id, oldrev, newrev, ref)
+ Dir.chdir(repo_path) do
+ system({ 'GL_ID' => gl_id }, path, ref, oldrev, newrev)
+ end
+ end
end
end
end
diff --git a/lib/gitlab/github_import/client.rb b/lib/gitlab/github_import/client.rb
index 270cbcd9ccd..74d1529e1ff 100644
--- a/lib/gitlab/github_import/client.rb
+++ b/lib/gitlab/github_import/client.rb
@@ -46,7 +46,7 @@ module Gitlab
end
def github_options
- OmniAuth::Strategies::GitHub.default_options[:client_options].symbolize_keys
+ OmniAuth::Strategies::GitHub.default_options[:client_options].to_h.symbolize_keys
end
end
end
diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb
index bd7340a80f1..b5720f6e2cb 100644
--- a/lib/gitlab/github_import/importer.rb
+++ b/lib/gitlab/github_import/importer.rb
@@ -19,7 +19,7 @@ module Gitlab
if issue.pull_request.nil?
body = @formatter.author_line(issue.user.login)
- body += issue.body
+ body += issue.body || ""
if issue.comments > 0
body += @formatter.comments_header
diff --git a/lib/gitlab/gitlab_import/client.rb b/lib/gitlab/gitlab_import/client.rb
index 9c00896c913..86fb6c51765 100644
--- a/lib/gitlab/gitlab_import/client.rb
+++ b/lib/gitlab/gitlab_import/client.rb
@@ -75,7 +75,7 @@ module Gitlab
end
def gitlab_options
- OmniAuth::Strategies::GitLab.default_options[:client_options].symbolize_keys
+ OmniAuth::Strategies::GitLab.default_options[:client_options].to_h.symbolize_keys
end
end
end
diff --git a/lib/gitlab/lfs/response.rb b/lib/gitlab/lfs/response.rb
index 4202c786466..9be9a65671b 100644
--- a/lib/gitlab/lfs/response.rb
+++ b/lib/gitlab/lfs/response.rb
@@ -10,23 +10,9 @@ module Gitlab
@request = request
end
- # Return a response for a download request
- # Can be a response to:
- # Request from a user to get the file
- # Request from gitlab-workhorse which file to serve to the user
- def render_download_hypermedia_response(oid)
- render_response_to_download do
- if check_download_accept_header?
- render_lfs_download_hypermedia(oid)
- else
- render_not_found
- end
- end
- end
-
def render_download_object_response(oid)
render_response_to_download do
- if check_download_sendfile_header? && check_download_accept_header?
+ if check_download_sendfile_header?
render_lfs_sendfile(oid)
else
render_not_found
@@ -34,20 +20,15 @@ module Gitlab
end
end
- def render_lfs_api_auth
- render_response_to_push do
- request_body = JSON.parse(@request.body.read)
- return render_not_found if request_body.empty? || request_body['objects'].empty?
-
- response = build_response(request_body['objects'])
- [
- 200,
- {
- "Content-Type" => "application/json; charset=utf-8",
- "Cache-Control" => "private",
- },
- [JSON.dump(response)]
- ]
+ def render_batch_operation_response
+ request_body = JSON.parse(@request.body.read)
+ case request_body["operation"]
+ when "download"
+ render_batch_download(request_body)
+ when "upload"
+ render_batch_upload(request_body)
+ else
+ render_not_found
end
end
@@ -71,13 +52,24 @@ module Gitlab
end
end
+ def render_unsupported_deprecated_api
+ [
+ 501,
+ { "Content-Type" => "application/json; charset=utf-8" },
+ [JSON.dump({
+ 'message' => 'Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.',
+ 'documentation_url' => "#{Gitlab.config.gitlab.url}/help",
+ })]
+ ]
+ end
+
private
def render_not_enabled
[
501,
{
- "Content-Type" => "application/vnd.git-lfs+json",
+ "Content-Type" => "application/json; charset=utf-8",
},
[JSON.dump({
'message' => 'Git LFS is not enabled on this GitLab server, contact your admin.',
@@ -142,18 +134,35 @@ module Gitlab
end
end
- def render_lfs_download_hypermedia(oid)
- return render_not_found unless oid.present?
+ def render_batch_upload(body)
+ return render_not_found if body.empty? || body['objects'].nil?
- lfs_object = object_for_download(oid)
- if lfs_object
+ render_response_to_push do
+ response = build_upload_batch_response(body['objects'])
[
200,
- { "Content-Type" => "application/vnd.git-lfs+json" },
- [JSON.dump(download_hypermedia(oid))]
+ {
+ "Content-Type" => "application/json; charset=utf-8",
+ "Cache-Control" => "private",
+ },
+ [JSON.dump(response)]
+ ]
+ end
+ end
+
+ def render_batch_download(body)
+ return render_not_found if body.empty? || body['objects'].nil?
+
+ render_response_to_download do
+ response = build_download_batch_response(body['objects'])
+ [
+ 200,
+ {
+ "Content-Type" => "application/json; charset=utf-8",
+ "Cache-Control" => "private",
+ },
+ [JSON.dump(response)]
]
- else
- render_not_found
end
end
@@ -199,10 +208,6 @@ module Gitlab
@env['HTTP_X_SENDFILE_TYPE'].to_s == "X-Sendfile"
end
- def check_download_accept_header?
- @env['HTTP_ACCEPT'].to_s == "application/vnd.git-lfs+json; charset=utf-8"
- end
-
def user_can_fetch?
# Check user access against the project they used to initiate the pull
@user.can?(:download_code, @origin_project)
@@ -255,7 +260,7 @@ module Gitlab
end
def link_to_project(object)
- if object && !object.projects.exists?(@project)
+ if object && !object.projects.exists?(@project.id)
object.projects << @project
object.save
end
@@ -266,42 +271,56 @@ module Gitlab
@project.lfs_objects.where(oid: objects_oids).pluck(:oid).to_set
end
- def build_response(objects)
+ def build_upload_batch_response(objects)
selected_objects = select_existing_objects(objects)
- upload_hypermedia(objects, selected_objects)
+ upload_hypermedia_links(objects, selected_objects)
end
- def download_hypermedia(oid)
- {
- '_links' => {
- 'download' =>
- {
- 'href' => "#{@origin_project.http_url_to_repo}/gitlab-lfs/objects/#{oid}",
- 'header' => {
- 'Accept' => "application/vnd.git-lfs+json; charset=utf-8",
- 'Authorization' => @env['HTTP_AUTHORIZATION']
- }.compact
- }
- }
- }
+ def build_download_batch_response(objects)
+ selected_objects = select_existing_objects(objects)
+
+ download_hypermedia_links(objects, selected_objects)
end
- def upload_hypermedia(all_objects, existing_objects)
+ def download_hypermedia_links(all_objects, existing_objects)
all_objects.each do |object|
- object['_links'] = hypermedia_links(object) unless existing_objects.include?(object['oid'])
+ if existing_objects.include?(object['oid'])
+ object['actions'] = {
+ 'download' => {
+ 'href' => "#{@origin_project.http_url_to_repo}/gitlab-lfs/objects/#{object['oid']}",
+ 'header' => {
+ 'Authorization' => @env['HTTP_AUTHORIZATION']
+ }.compact
+ }
+ }
+ else
+ object['error'] = {
+ 'code' => 404,
+ 'message' => "Object does not exist on the server or you don't have permissions to access it",
+ }
+ end
end
{ 'objects' => all_objects }
end
- def hypermedia_links(object)
- {
- "upload" => {
- 'href' => "#{@origin_project.http_url_to_repo}/gitlab-lfs/objects/#{object['oid']}/#{object['size']}",
- 'header' => { 'Authorization' => @env['HTTP_AUTHORIZATION'] }
- }.compact
- }
+ def upload_hypermedia_links(all_objects, existing_objects)
+ all_objects.each do |object|
+ # generate actions only for non-existing objects
+ next if existing_objects.include?(object['oid'])
+
+ object['actions'] = {
+ 'upload' => {
+ 'href' => "#{@origin_project.http_url_to_repo}/gitlab-lfs/objects/#{object['oid']}/#{object['size']}",
+ 'header' => {
+ 'Authorization' => @env['HTTP_AUTHORIZATION']
+ }.compact
+ }
+ }
+ end
+
+ { 'objects' => all_objects }
end
end
end
diff --git a/lib/gitlab/lfs/router.rb b/lib/gitlab/lfs/router.rb
index 4809e834984..78d02891102 100644
--- a/lib/gitlab/lfs/router.rb
+++ b/lib/gitlab/lfs/router.rb
@@ -34,7 +34,7 @@ module Gitlab
case path_match[1]
when "info/lfs"
- lfs.render_download_hypermedia_response(oid)
+ lfs.render_unsupported_deprecated_api
when "gitlab-lfs"
lfs.render_download_object_response(oid)
else
@@ -48,7 +48,9 @@ module Gitlab
# Check for Batch API
if post_path[0].ends_with?("/info/lfs/objects/batch")
- lfs.render_lfs_api_auth
+ lfs.render_batch_operation_response
+ elsif post_path[0].ends_with?("/info/lfs/objects")
+ lfs.render_unsupported_deprecated_api
else
nil
end
diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb
index b082bfc434b..886a09f52af 100644
--- a/lib/gitlab/markdown.rb
+++ b/lib/gitlab/markdown.rb
@@ -178,7 +178,6 @@ module Gitlab
Gitlab::Markdown::SanitizationFilter,
Gitlab::Markdown::UploadLinkFilter,
- Gitlab::Markdown::RelativeLinkFilter,
Gitlab::Markdown::EmojiFilter,
Gitlab::Markdown::TableOfContentsFilter,
Gitlab::Markdown::AutolinkFilter,
@@ -193,6 +192,8 @@ module Gitlab
Gitlab::Markdown::CommitReferenceFilter,
Gitlab::Markdown::LabelReferenceFilter,
+ Gitlab::Markdown::RelativeLinkFilter,
+
Gitlab::Markdown::TaskListFilter
]
end
diff --git a/lib/gitlab/markdown/abstract_reference_filter.rb b/lib/gitlab/markdown/abstract_reference_filter.rb
index fd5b7eb9332..9488e980c08 100644
--- a/lib/gitlab/markdown/abstract_reference_filter.rb
+++ b/lib/gitlab/markdown/abstract_reference_filter.rb
@@ -2,8 +2,8 @@ require 'gitlab/markdown'
module Gitlab
module Markdown
- # Issues, Snippets and Merge Requests shares similar functionality in refernce filtering.
- # All this functionality moved to this class
+ # Issues, Merge Requests, Snippets, Commits and Commit Ranges share
+ # similar functionality in reference filtering.
class AbstractReferenceFilter < ReferenceFilter
include CrossProjectReference
@@ -26,21 +26,20 @@ module Gitlab
# Public: Find references in text (like `!123` for merge requests)
#
- # AnyReferenceFilter.references_in(text) do |match, object|
- # "<a href=...>PREFIX#{object}</a>"
+ # AnyReferenceFilter.references_in(text) do |match, id, project_ref, matches|
+ # object = find_object(project_ref, id)
+ # "<a href=...>#{object.to_reference}</a>"
# end
#
- # PREFIX - symbol that detects reference (like ! for merge requests)
- # object - reference object (snippet, merget request etc)
# text - String text to search.
#
- # Yields the String match, the Integer referenced object ID, and an optional String
- # of the external project reference.
+ # Yields the String match, the Integer referenced object ID, an optional String
+ # of the external project reference, and all of the matchdata.
#
# Returns a String replaced with the return of the block.
- def self.references_in(text)
- text.gsub(object_class.reference_pattern) do |match|
- yield match, $~[object_sym].to_i, $~[:project]
+ def self.references_in(text, pattern = object_class.reference_pattern)
+ text.gsub(pattern) do |match|
+ yield match, $~[object_sym].to_i, $~[:project], $~
end
end
@@ -61,8 +60,27 @@ module Gitlab
end
def call
+ # `#123`
replace_text_nodes_matching(object_class.reference_pattern) do |content|
- object_link_filter(content)
+ object_link_filter(content, object_class.reference_pattern)
+ end
+
+ # `[Issue](#123)`, which is turned into
+ # `<a href="#123">Issue</a>`
+ replace_link_nodes_with_href(object_class.reference_pattern) do |link, text|
+ object_link_filter(link, object_class.reference_pattern, link_text: text)
+ end
+
+ # `http://gitlab.example.com/namespace/project/issues/123`, which is turned into
+ # `<a href="http://gitlab.example.com/namespace/project/issues/123">http://gitlab.example.com/namespace/project/issues/123</a>`
+ replace_link_nodes_with_text(object_class.link_reference_pattern) do |text|
+ object_link_filter(text, object_class.link_reference_pattern)
+ end
+
+ # `[Issue](http://gitlab.example.com/namespace/project/issues/123)`, which is turned into
+ # `<a href="http://gitlab.example.com/namespace/project/issues/123">Issue</a>`
+ replace_link_nodes_with_href(object_class.link_reference_pattern) do |link, text|
+ object_link_filter(link, object_class.link_reference_pattern, link_text: text)
end
end
@@ -70,30 +88,57 @@ module Gitlab
# to the referenced object's details page.
#
# text - String text to replace references in.
+ # pattern - Reference pattern to match against.
+ # link_text - Original content of the link being replaced.
#
# Returns a String with references replaced with links. All links
# have `gfm` and `gfm-OBJECT_NAME` class names attached for styling.
- def object_link_filter(text)
- references_in(text) do |match, id, project_ref|
+ def object_link_filter(text, pattern, link_text: nil)
+ references_in(text, pattern) do |match, id, project_ref, matches|
project = project_from_ref(project_ref)
if project && object = find_object(project, id)
- title = escape_once("#{object_title}: #{object.title}")
+ title = escape_once(object_link_title(object))
klass = reference_class(object_sym)
- data = data_attribute(project: project.id, object_sym => object.id)
- url = url_for_object(object, project)
+
+ data = data_attribute(
+ original: link_text || match,
+ project: project.id,
+ object_sym => object.id
+ )
+
+ url = matches[:url] if matches.names.include?("url")
+ url ||= url_for_object(object, project)
+
+ text = link_text
+ unless text
+ text = object.reference_link_text(context[:project])
+
+ extras = object_link_text_extras(object, matches)
+ text += " (#{extras.join(", ")})" if extras.any?
+ end
%(<a href="#{url}" #{data}
title="#{title}"
- class="#{klass}">#{match}</a>)
+ class="#{klass}">#{text}</a>)
else
match
end
end
end
- def object_title
- object_class.name.titleize
+ def object_link_text_extras(object, matches)
+ extras = []
+
+ if matches.names.include?("anchor") && matches[:anchor] && matches[:anchor] =~ /\A\#note_(\d+)\z/
+ extras << "comment #{$1}"
+ end
+
+ extras
+ end
+
+ def object_link_title(object)
+ "#{object_class.name.titleize}: #{object.title}"
end
end
end
diff --git a/lib/gitlab/markdown/commit_range_reference_filter.rb b/lib/gitlab/markdown/commit_range_reference_filter.rb
index e070edae0a4..36b3258ef76 100644
--- a/lib/gitlab/markdown/commit_range_reference_filter.rb
+++ b/lib/gitlab/markdown/commit_range_reference_filter.rb
@@ -5,24 +5,14 @@ module Gitlab
# HTML filter that replaces commit range references with links.
#
# This filter supports cross-project references.
- class CommitRangeReferenceFilter < ReferenceFilter
- include CrossProjectReference
+ class CommitRangeReferenceFilter < AbstractReferenceFilter
+ def self.object_class
+ CommitRange
+ end
- # Public: Find commit range references in text
- #
- # CommitRangeReferenceFilter.references_in(text) do |match, commit_range, project_ref|
- # "<a href=...>#{commit_range}</a>"
- # end
- #
- # text - String text to search.
- #
- # Yields the String match, the String commit range, and an optional String
- # of the external project reference.
- #
- # Returns a String replaced with the return of the block.
- def self.references_in(text)
- text.gsub(CommitRange.reference_pattern) do |match|
- yield match, $~[:commit_range], $~[:project]
+ def self.references_in(text, pattern = CommitRange.reference_pattern)
+ text.gsub(pattern) do |match|
+ yield match, $~[:commit_range], $~[:project], $~
end
end
@@ -31,9 +21,9 @@ module Gitlab
return unless project
id = node.attr("data-commit-range")
- range = CommitRange.new(id, project)
+ range = find_object(project, id)
- return unless range.valid_commits?
+ return unless range
{ commit_range: range }
end
@@ -44,49 +34,25 @@ module Gitlab
@commit_map = {}
end
- def call
- replace_text_nodes_matching(CommitRange.reference_pattern) do |content|
- commit_range_link_filter(content)
- end
- end
-
- # Replace commit range references in text with links to compare the commit
- # ranges.
- #
- # text - String text to replace references in.
- #
- # Returns a String with commit range references replaced with links. All
- # links have `gfm` and `gfm-commit_range` class names attached for
- # styling.
- def commit_range_link_filter(text)
- self.class.references_in(text) do |match, id, project_ref|
- project = self.project_from_ref(project_ref)
-
- range = CommitRange.new(id, project)
-
- if range.valid_commits?
- url = url_for_commit_range(project, range)
-
- title = range.reference_title
- klass = reference_class(:commit_range)
- data = data_attribute(project: project.id, commit_range: id)
+ def self.find_object(project, id)
+ range = CommitRange.new(id, project)
- project_ref += '@' if project_ref
+ range.valid_commits? ? range : nil
+ end
- %(<a href="#{url}" #{data}
- title="#{title}"
- class="#{klass}">#{project_ref}#{range}</a>)
- else
- match
- end
- end
+ def find_object(*args)
+ self.class.find_object(*args)
end
- def url_for_commit_range(project, range)
+ def url_for_object(range, project)
h = Gitlab::Application.routes.url_helpers
h.namespace_project_compare_url(project.namespace, project,
range.to_param.merge(only_path: context[:only_path]))
end
+
+ def object_link_title(range)
+ range.reference_title
+ end
end
end
end
diff --git a/lib/gitlab/markdown/commit_reference_filter.rb b/lib/gitlab/markdown/commit_reference_filter.rb
index 8cdbeb1f9cf..b4036578e60 100644
--- a/lib/gitlab/markdown/commit_reference_filter.rb
+++ b/lib/gitlab/markdown/commit_reference_filter.rb
@@ -5,24 +5,14 @@ module Gitlab
# HTML filter that replaces commit references with links.
#
# This filter supports cross-project references.
- class CommitReferenceFilter < ReferenceFilter
- include CrossProjectReference
+ class CommitReferenceFilter < AbstractReferenceFilter
+ def self.object_class
+ Commit
+ end
- # Public: Find commit references in text
- #
- # CommitReferenceFilter.references_in(text) do |match, commit, project_ref|
- # "<a href=...>#{commit}</a>"
- # end
- #
- # text - String text to search.
- #
- # Yields the String match, the String commit identifier, and an optional
- # String of the external project reference.
- #
- # Returns a String replaced with the return of the block.
- def self.references_in(text)
- text.gsub(Commit.reference_pattern) do |match|
- yield match, $~[:commit], $~[:project]
+ def self.references_in(text, pattern = Commit.reference_pattern)
+ text.gsub(pattern) do |match|
+ yield match, $~[:commit], $~[:project], $~
end
end
@@ -31,58 +21,32 @@ module Gitlab
return unless project
id = node.attr("data-commit")
- commit = commit_from_ref(project, id)
+ commit = find_object(project, id)
return unless commit
{ commit: commit }
end
- def call
- replace_text_nodes_matching(Commit.reference_pattern) do |content|
- commit_link_filter(content)
- end
- end
-
- # Replace commit references in text with links to the commit specified.
- #
- # text - String text to replace references in.
- #
- # Returns a String with commit references replaced with links. All links
- # have `gfm` and `gfm-commit` class names attached for styling.
- def commit_link_filter(text)
- self.class.references_in(text) do |match, id, project_ref|
- project = self.project_from_ref(project_ref)
-
- if commit = self.class.commit_from_ref(project, id)
- url = url_for_commit(project, commit)
-
- title = escape_once(commit.link_title)
- klass = reference_class(:commit)
- data = data_attribute(project: project.id, commit: id)
-
- project_ref += '@' if project_ref
-
- %(<a href="#{url}" #{data}
- title="#{title}"
- class="#{klass}">#{project_ref}#{commit.short_id}</a>)
- else
- match
- end
- end
- end
-
- def self.commit_from_ref(project, id)
+ def self.find_object(project, id)
if project && project.valid_repo?
project.commit(id)
end
end
- def url_for_commit(project, commit)
+ def find_object(*args)
+ self.class.find_object(*args)
+ end
+
+ def url_for_object(commit, project)
h = Gitlab::Application.routes.url_helpers
h.namespace_project_commit_url(project.namespace, project, commit,
only_path: context[:only_path])
end
+
+ def object_link_title(commit)
+ commit.link_title
+ end
end
end
end
diff --git a/lib/gitlab/markdown/external_issue_reference_filter.rb b/lib/gitlab/markdown/external_issue_reference_filter.rb
index 8f86f13976a..14bdf5521fc 100644
--- a/lib/gitlab/markdown/external_issue_reference_filter.rb
+++ b/lib/gitlab/markdown/external_issue_reference_filter.rb
@@ -30,6 +30,10 @@ module Gitlab
replace_text_nodes_matching(ExternalIssue.reference_pattern) do |content|
issue_link_filter(content)
end
+
+ replace_link_nodes_with_href(ExternalIssue.reference_pattern) do |link, text|
+ issue_link_filter(link, link_text: text)
+ end
end
# Replace `JIRA-123` issue references in text with links to the referenced
@@ -39,7 +43,7 @@ module Gitlab
#
# Returns a String with `JIRA-123` references replaced with links. All
# links have `gfm` and `gfm-issue` class names attached for styling.
- def issue_link_filter(text)
+ def issue_link_filter(text, link_text: nil)
project = context[:project]
self.class.references_in(text) do |match, issue|
@@ -49,9 +53,11 @@ module Gitlab
klass = reference_class(:issue)
data = data_attribute(project: project.id)
+ text = link_text || match
+
%(<a href="#{url}" #{data}
title="#{title}"
- class="#{klass}">#{match}</a>)
+ class="#{klass}">#{text}</a>)
end
end
diff --git a/lib/gitlab/markdown/external_link_filter.rb b/lib/gitlab/markdown/external_link_filter.rb
index 29e51b6ade6..e09dfcb83c8 100644
--- a/lib/gitlab/markdown/external_link_filter.rb
+++ b/lib/gitlab/markdown/external_link_filter.rb
@@ -8,9 +8,9 @@ module Gitlab
class ExternalLinkFilter < HTML::Pipeline::Filter
def call
doc.search('a').each do |node|
- next unless node.has_attribute?('href')
+ link = node.attr('href')
- link = node.attribute('href').value
+ next unless link
# Skip non-HTTP(S) links
next unless link.start_with?('http')
diff --git a/lib/gitlab/markdown/label_reference_filter.rb b/lib/gitlab/markdown/label_reference_filter.rb
index 618acb7a578..a2026eecaeb 100644
--- a/lib/gitlab/markdown/label_reference_filter.rb
+++ b/lib/gitlab/markdown/label_reference_filter.rb
@@ -30,6 +30,10 @@ module Gitlab
replace_text_nodes_matching(Label.reference_pattern) do |content|
label_link_filter(content)
end
+
+ replace_link_nodes_with_href(Label.reference_pattern) do |link, text|
+ label_link_filter(link, link_text: text)
+ end
end
# Replace label references in text with links to the label specified.
@@ -38,7 +42,7 @@ module Gitlab
#
# Returns a String with label references replaced with links. All links
# have `gfm` and `gfm-label` class names attached for styling.
- def label_link_filter(text)
+ def label_link_filter(text, link_text: nil)
project = context[:project]
self.class.references_in(text) do |match, id, name|
@@ -47,10 +51,16 @@ module Gitlab
if label = project.labels.find_by(params)
url = url_for_label(project, label)
klass = reference_class(:label)
- data = data_attribute(project: project.id, label: label.id)
+ data = data_attribute(
+ original: link_text || match,
+ project: project.id,
+ label: label.id
+ )
+
+ text = link_text || render_colored_label(label)
%(<a href="#{url}" #{data}
- class="#{klass}">#{render_colored_label(label)}</a>)
+ class="#{klass}">#{text}</a>)
else
match
end
@@ -59,9 +69,8 @@ module Gitlab
def url_for_label(project, label)
h = Gitlab::Application.routes.url_helpers
- h.namespace_project_issues_path(project.namespace, project,
- label_name: label.name,
- only_path: context[:only_path])
+ h.namespace_project_issues_url( project.namespace, project, label_name: label.name,
+ only_path: context[:only_path])
end
def render_colored_label(label)
diff --git a/lib/gitlab/markdown/merge_request_reference_filter.rb b/lib/gitlab/markdown/merge_request_reference_filter.rb
index 1f47f03c94e..de71fc76a9b 100644
--- a/lib/gitlab/markdown/merge_request_reference_filter.rb
+++ b/lib/gitlab/markdown/merge_request_reference_filter.rb
@@ -20,6 +20,16 @@ module Gitlab
h.namespace_project_merge_request_url(project.namespace, project, mr,
only_path: context[:only_path])
end
+
+ def object_link_text_extras(object, matches)
+ extras = super
+
+ if matches.names.include?("path") && matches[:path] && matches[:path] == '/diffs'
+ extras.unshift "diffs"
+ end
+
+ extras
+ end
end
end
end
diff --git a/lib/gitlab/markdown/redactor_filter.rb b/lib/gitlab/markdown/redactor_filter.rb
index a1f3a8a8ebf..bea714a01e7 100644
--- a/lib/gitlab/markdown/redactor_filter.rb
+++ b/lib/gitlab/markdown/redactor_filter.rb
@@ -12,7 +12,10 @@ module Gitlab
def call
doc.css('a.gfm').each do |node|
unless user_can_reference?(node)
- node.replace(node.text)
+ # The reference should be replaced by the original text,
+ # which is not always the same as the rendered text.
+ text = node.attr('data-original') || node.text
+ node.replace(text)
end
end
diff --git a/lib/gitlab/markdown/reference_filter.rb b/lib/gitlab/markdown/reference_filter.rb
index a4c560f578c..b6d93e05ec7 100644
--- a/lib/gitlab/markdown/reference_filter.rb
+++ b/lib/gitlab/markdown/reference_filter.rb
@@ -122,6 +122,80 @@ module Gitlab
doc
end
+ # Iterate through the document's link nodes, yielding the current node's
+ # content if:
+ #
+ # * The `project` context value is present AND
+ # * The node's content matches `pattern`
+ #
+ # pattern - Regex pattern against which to match the node's content
+ #
+ # Yields the current node's String contents. The result of the block will
+ # replace the node and update the current document.
+ #
+ # Returns the updated Nokogiri::HTML::DocumentFragment object.
+ def replace_link_nodes_with_text(pattern)
+ return doc if project.nil?
+
+ doc.search('a').each do |node|
+ klass = node.attr('class')
+ next if klass && klass.include?('gfm')
+
+ link = node.attr('href')
+ text = node.text
+
+ next unless link && text
+
+ link = URI.decode(link)
+ # Ignore ending punctionation like periods or commas
+ next unless link == text && text =~ /\A#{pattern}/
+
+ html = yield text
+
+ next if html == text
+
+ node.replace(html)
+ end
+
+ doc
+ end
+
+ # Iterate through the document's link nodes, yielding the current node's
+ # content if:
+ #
+ # * The `project` context value is present AND
+ # * The node's HREF matches `pattern`
+ #
+ # pattern - Regex pattern against which to match the node's HREF
+ #
+ # Yields the current node's String HREF and String content.
+ # The result of the block will replace the node and update the current document.
+ #
+ # Returns the updated Nokogiri::HTML::DocumentFragment object.
+ def replace_link_nodes_with_href(pattern)
+ return doc if project.nil?
+
+ doc.search('a').each do |node|
+ klass = node.attr('class')
+ next if klass && klass.include?('gfm')
+
+ link = node.attr('href')
+ text = node.text
+
+ next unless link && text
+ link = URI.decode(link)
+ next unless link && link =~ /\A#{pattern}\z/
+
+ html = yield link, text
+
+ next if html == link
+
+ node.replace(html)
+ end
+
+ doc
+ end
+
# Ensure that a :project key exists in context
#
# Note that while the key might exist, its value could be nil!
diff --git a/lib/gitlab/markdown/relative_link_filter.rb b/lib/gitlab/markdown/relative_link_filter.rb
index 632be4d7542..692c51fd324 100644
--- a/lib/gitlab/markdown/relative_link_filter.rb
+++ b/lib/gitlab/markdown/relative_link_filter.rb
@@ -17,6 +17,9 @@ module Gitlab
return doc unless linkable_files?
doc.search('a').each do |el|
+ klass = el.attr('class')
+ next if klass && klass.include?('gfm')
+
process_link_attr el.attribute('href')
end
diff --git a/lib/gitlab/markdown/user_reference_filter.rb b/lib/gitlab/markdown/user_reference_filter.rb
index ab5e1f6fe9e..0a20d9c0347 100644
--- a/lib/gitlab/markdown/user_reference_filter.rb
+++ b/lib/gitlab/markdown/user_reference_filter.rb
@@ -52,6 +52,10 @@ module Gitlab
replace_text_nodes_matching(User.reference_pattern) do |content|
user_link_filter(content)
end
+
+ replace_link_nodes_with_href(User.reference_pattern) do |link, text|
+ user_link_filter(link, link_text: text)
+ end
end
# Replace `@user` user references in text with links to the referenced
@@ -61,12 +65,12 @@ module Gitlab
#
# Returns a String with `@user` references replaced with links. All links
# have `gfm` and `gfm-project_member` class names attached for styling.
- def user_link_filter(text)
+ def user_link_filter(text, link_text: nil)
self.class.references_in(text) do |match, username|
if username == 'all'
- link_to_all
+ link_to_all(link_text: link_text)
elsif namespace = Namespace.find_by(path: username)
- link_to_namespace(namespace) || match
+ link_to_namespace(namespace, link_text: link_text) || match
else
match
end
@@ -83,36 +87,36 @@ module Gitlab
reference_class(:project_member)
end
- def link_to_all
+ def link_to_all(link_text: nil)
project = context[:project]
url = urls.namespace_project_url(project.namespace, project,
only_path: context[:only_path])
data = data_attribute(project: project.id)
- text = User.reference_prefix + 'all'
+ text = link_text || User.reference_prefix + 'all'
link_tag(url, data, text)
end
- def link_to_namespace(namespace)
+ def link_to_namespace(namespace, link_text: nil)
if namespace.is_a?(Group)
- link_to_group(namespace.path, namespace)
+ link_to_group(namespace.path, namespace, link_text: link_text)
else
- link_to_user(namespace.path, namespace)
+ link_to_user(namespace.path, namespace, link_text: link_text)
end
end
- def link_to_group(group, namespace)
+ def link_to_group(group, namespace, link_text: nil)
url = urls.group_url(group, only_path: context[:only_path])
data = data_attribute(group: namespace.id)
- text = Group.reference_prefix + group
+ text = link_text || Group.reference_prefix + group
link_tag(url, data, text)
end
- def link_to_user(user, namespace)
+ def link_to_user(user, namespace, link_text: nil)
url = urls.user_url(user, only_path: context[:only_path])
data = data_attribute(user: namespace.owner_id)
- text = User.reference_prefix + user
+ text = link_text || User.reference_prefix + user
link_tag(url, data, text)
end
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index da8df8a3025..3c3478a1271 100644
--- a/lib/gitlab/reference_extractor.rb
+++ b/lib/gitlab/reference_extractor.rb
@@ -41,14 +41,14 @@ module Gitlab
# Returns the results Array for the requested filter type
def pipeline_result(filter_type)
return [] if @text.blank?
-
+
klass = "#{filter_type.to_s.camelize}ReferenceFilter"
filter = Gitlab::Markdown.const_get(klass)
context = {
project: project,
current_user: current_user,
-
+
# We don't actually care about the links generated
only_path: true,
ignore_blockquotes: true,
@@ -58,7 +58,15 @@ module Gitlab
reference_filter: filter
}
- pipeline = HTML::Pipeline.new([filter, Gitlab::Markdown::ReferenceGathererFilter], context)
+ # We need to autolink first to finds links to referables, and to prevent
+ # numeric anchors to be parsed as issue references.
+ filters = [
+ Gitlab::Markdown::AutolinkFilter,
+ filter,
+ Gitlab::Markdown::ReferenceGathererFilter
+ ]
+
+ pipeline = HTML::Pipeline.new(filters, context)
result = pipeline.call(@text)
values = result[:references][filter_type].uniq
diff --git a/lib/gitlab/seeder.rb b/lib/gitlab/seeder.rb
index 31aa3528c4c..2ef0e982256 100644
--- a/lib/gitlab/seeder.rb
+++ b/lib/gitlab/seeder.rb
@@ -14,7 +14,7 @@ module Gitlab
def self.mute_mailer
code = <<-eos
-def Notify.delay
+def Notify.deliver_later
self
end
eos
diff --git a/lib/gitlab/sherlock/transaction.rb b/lib/gitlab/sherlock/transaction.rb
index d87a4c9bb4a..3489fb251b6 100644
--- a/lib/gitlab/sherlock/transaction.rb
+++ b/lib/gitlab/sherlock/transaction.rb
@@ -36,6 +36,11 @@ module Gitlab
@duration ||= started_at && finished_at ? finished_at - started_at : 0
end
+ # Returns the total query duration in seconds.
+ def query_duration
+ @query_duration ||= @queries.map { |q| q.duration }.inject(:+) / 1000.0
+ end
+
def to_param
@id
end
diff --git a/lib/gitlab/sql/union.rb b/lib/gitlab/sql/union.rb
new file mode 100644
index 00000000000..1cd89b3a9c4
--- /dev/null
+++ b/lib/gitlab/sql/union.rb
@@ -0,0 +1,34 @@
+module Gitlab
+ module SQL
+ # Class for building SQL UNION statements.
+ #
+ # ORDER BYs are dropped from the relations as the final sort order is not
+ # guaranteed any way.
+ #
+ # Example usage:
+ #
+ # union = Gitlab::SQL::Union.new(user.personal_projects, user.projects)
+ # sql = union.to_sql
+ #
+ # Project.where("id IN (#{sql})")
+ class Union
+ def initialize(relations)
+ @relations = relations
+ end
+
+ def to_sql
+ # Some relations may include placeholders for prepared statements, these
+ # aren't incremented properly when joining relations together this way.
+ # By using "unprepared_statements" we remove the usage of placeholders
+ # (thus fixing this problem), at a slight performance cost.
+ fragments = ActiveRecord::Base.connection.unprepared_statement do
+ @relations.map do |rel|
+ rel.reorder(nil).to_sql
+ end
+ end
+
+ fragments.join("\nUNION\n")
+ end
+ end
+ end
+end
diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab
index f0a6c2b30e9..43fda6fa92e 100755
--- a/lib/support/init.d/gitlab
+++ b/lib/support/init.d/gitlab
@@ -327,7 +327,7 @@ print_status() {
printf "The GitLab MailRoom email processor is \033[31mnot running\033[0m.\n"
fi
fi
- if [ "$web_status" = "0" ] && [ "$sidekiq_status" = "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" = "0" ]; }; then
+ if [ "$web_status" = "0" ] && [ "$sidekiq_status" = "0" ] && [ "$gitlab_workhorse_status" = "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" = "0" ]; }; then
printf "GitLab and all its components are \033[32mup and running\033[0m.\n"
fi
}
diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab
index 93f2ad07aeb..2a79fbdcf93 100644
--- a/lib/support/nginx/gitlab
+++ b/lib/support/nginx/gitlab
@@ -67,7 +67,7 @@ server {
location / {
## Serve static files from defined root folder.
## @gitlab is a named location for the upstream fallback, see below.
- try_files $uri $uri/index.html $uri.html @gitlab;
+ try_files $uri /index.html $uri.html @gitlab;
}
## We route uploads through GitLab to prevent XSS and enforce access control.
@@ -121,18 +121,21 @@ server {
}
location ~ ^/[\w\.-]+/[\w\.-]+/(info/refs|git-upload-pack|git-receive-pack)$ {
+ client_max_body_size 0;
# 'Error' 418 is a hack to re-use the @gitlab-workhorse block
error_page 418 = @gitlab-workhorse;
return 418;
}
location ~ ^/[\w\.-]+/[\w\.-]+/repository/archive {
+ client_max_body_size 0;
# 'Error' 418 is a hack to re-use the @gitlab-workhorse block
error_page 418 = @gitlab-workhorse;
return 418;
}
location ~ ^/api/v3/projects/.*/repository/archive {
+ client_max_body_size 0;
# 'Error' 418 is a hack to re-use the @gitlab-workhorse block
error_page 418 = @gitlab-workhorse;
return 418;
@@ -140,21 +143,22 @@ server {
# Build artifacts should be submitted to this location
location ~ ^/[\w\.-]+/[\w\.-]+/builds/download {
- client_max_body_size 0;
- # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
- error_page 418 = @gitlab-workhorse;
- return 418;
+ client_max_body_size 0;
+ # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
+ error_page 418 = @gitlab-workhorse;
+ return 418;
}
# Build artifacts should be submitted to this location
location ~ /ci/api/v1/builds/[0-9]+/artifacts {
- client_max_body_size 0;
- # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
- error_page 418 = @gitlab-workhorse;
- return 418;
+ client_max_body_size 0;
+ # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
+ error_page 418 = @gitlab-workhorse;
+ return 418;
}
location @gitlab-workhorse {
+ client_max_body_size 0;
## If you use HTTPS make sure you disable gzip compression
## to be safe against BREACH attack.
# gzip off;
diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl
index 90749947fa4..016f7a536fb 100644
--- a/lib/support/nginx/gitlab-ssl
+++ b/lib/support/nginx/gitlab-ssl
@@ -112,7 +112,7 @@ server {
location / {
## Serve static files from defined root folder.
## @gitlab is a named location for the upstream fallback, see below.
- try_files $uri $uri/index.html $uri.html @gitlab;
+ try_files $uri /index.html $uri.html @gitlab;
}
## We route uploads through GitLab to prevent XSS and enforce access control.
@@ -168,18 +168,21 @@ server {
}
location ~ ^/[\w\.-]+/[\w\.-]+/(info/refs|git-upload-pack|git-receive-pack)$ {
+ client_max_body_size 0;
# 'Error' 418 is a hack to re-use the @gitlab-workhorse block
error_page 418 = @gitlab-workhorse;
return 418;
}
location ~ ^/[\w\.-]+/[\w\.-]+/repository/archive {
+ client_max_body_size 0;
# 'Error' 418 is a hack to re-use the @gitlab-workhorse block
error_page 418 = @gitlab-workhorse;
return 418;
}
location ~ ^/api/v3/projects/.*/repository/archive {
+ client_max_body_size 0;
# 'Error' 418 is a hack to re-use the @gitlab-workhorse block
error_page 418 = @gitlab-workhorse;
return 418;
@@ -187,21 +190,22 @@ server {
# Build artifacts should be submitted to this location
location ~ ^/[\w\.-]+/[\w\.-]+/builds/download {
- client_max_body_size 0;
- # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
- error_page 418 = @gitlab-workhorse;
- return 418;
+ client_max_body_size 0;
+ # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
+ error_page 418 = @gitlab-workhorse;
+ return 418;
}
# Build artifacts should be submitted to this location
location ~ /ci/api/v1/builds/[0-9]+/artifacts {
- client_max_body_size 0;
- # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
- error_page 418 = @gitlab-workhorse;
- return 418;
+ client_max_body_size 0;
+ # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
+ error_page 418 = @gitlab-workhorse;
+ return 418;
}
location @gitlab-workhorse {
+ client_max_body_size 0;
## If you use HTTPS make sure you disable gzip compression
## to be safe against BREACH attack.
gzip off;
diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake
index 3c46bcea40e..cb4abe13799 100644
--- a/lib/tasks/gitlab/backup.rake
+++ b/lib/tasks/gitlab/backup.rake
@@ -13,6 +13,7 @@ namespace :gitlab do
Rake::Task["gitlab:backup:uploads:create"].invoke
Rake::Task["gitlab:backup:builds:create"].invoke
Rake::Task["gitlab:backup:artifacts:create"].invoke
+ Rake::Task["gitlab:backup:lfs:create"].invoke
backup = Backup::Manager.new
backup.pack
@@ -34,6 +35,7 @@ namespace :gitlab do
Rake::Task["gitlab:backup:uploads:restore"].invoke unless backup.skipped?("uploads")
Rake::Task["gitlab:backup:builds:restore"].invoke unless backup.skipped?("builds")
Rake::Task["gitlab:backup:artifacts:restore"].invoke unless backup.skipped?("artifacts")
+ Rake::Task["gitlab:backup:lfs:restore"].invoke unless backup.skipped?("lfs")
Rake::Task["gitlab:shell:setup"].invoke
backup.cleanup
@@ -134,6 +136,25 @@ namespace :gitlab do
end
end
+ namespace :lfs do
+ task create: :environment do
+ $progress.puts "Dumping lfs objects ... ".blue
+
+ if ENV["SKIP"] && ENV["SKIP"].include?("lfs")
+ $progress.puts "[SKIPPED]".cyan
+ else
+ Backup::Lfs.new.dump
+ $progress.puts "done".green
+ end
+ end
+
+ task restore: :environment do
+ $progress.puts "Restoring lfs objects ... ".blue
+ Backup::Lfs.new.restore
+ $progress.puts "done".green
+ end
+ end
+
def configure_cron_mode
if ENV['CRON']
# We need an object we can say 'puts' and 'print' to; let's use a
diff --git a/lib/tasks/gitlab/task_helpers.rake b/lib/tasks/gitlab/task_helpers.rake
index c95b6540ebc..efb863a8764 100644
--- a/lib/tasks/gitlab/task_helpers.rake
+++ b/lib/tasks/gitlab/task_helpers.rake
@@ -2,16 +2,6 @@ module Gitlab
class TaskAbortedByUserError < StandardError; end
end
-unless STDOUT.isatty
- module Colored
- extend self
-
- def colorize(string, options={})
- string
- end
- end
-end
-
namespace :gitlab do
# Ask if the user wants to continue
@@ -103,7 +93,7 @@ namespace :gitlab do
gitlab_user = Gitlab.config.gitlab.user
current_user = run(%W(whoami)).chomp
unless current_user == gitlab_user
- puts "#{Colored.color(:black)+Colored.color(:on_yellow)} Warning #{Colored.extra(:clear)}"
+ puts " Warning ".colorize(:black).on_yellow
puts " You are running as user #{current_user.magenta}, we hope you know what you are doing."
puts " Things may work\/fail for the wrong reasons."
puts " For correct results you should run this as user #{gitlab_user.magenta}."
diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh
index dca5e1c5db3..119cc90fc1e 100755
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -1,7 +1,7 @@
#!/bin/bash
if [ -f /.dockerinit ]; then
- wget -q http://ftp.de.debian.org/debian/pool/main/p/phantomjs/phantomjs_1.9.0-1+b1_amd64.deb
- dpkg -i phantomjs_1.9.0-1+b1_amd64.deb
+ wget -q https://gitlab.com/axil/phantomjs-debian/raw/master/phantomjs_1.9.8-0jessie_amd64.deb
+ dpkg -i phantomjs_1.9.8-0jessie_amd64.deb
apt-get update -qq
apt-get install -y -qq libicu-dev libkrb5-dev cmake nodejs postgresql-client mysql-client
diff --git a/spec/benchmarks/finders/issues_finder_spec.rb b/spec/benchmarks/finders/issues_finder_spec.rb
new file mode 100644
index 00000000000..b57a33004a4
--- /dev/null
+++ b/spec/benchmarks/finders/issues_finder_spec.rb
@@ -0,0 +1,55 @@
+require 'spec_helper'
+
+describe IssuesFinder, benchmark: true do
+ describe '#execute' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public) }
+
+ let(:label1) { create(:label, project: project, title: 'A') }
+ let(:label2) { create(:label, project: project, title: 'B') }
+
+ before do
+ 10.times do |n|
+ issue = create(:issue, author: user, project: project)
+
+ if n > 4
+ create(:label_link, label: label1, target: issue)
+ create(:label_link, label: label2, target: issue)
+ end
+ end
+ end
+
+ describe 'retrieving issues without labels' do
+ let(:finder) do
+ IssuesFinder.new(user, scope: 'all', label_name: Label::None.title,
+ state: 'opened')
+ end
+
+ benchmark_subject { finder.execute }
+
+ it { is_expected.to iterate_per_second(2000) }
+ end
+
+ describe 'retrieving issues with labels' do
+ let(:finder) do
+ IssuesFinder.new(user, scope: 'all', label_name: label1.title,
+ state: 'opened')
+ end
+
+ benchmark_subject { finder.execute }
+
+ it { is_expected.to iterate_per_second(1000) }
+ end
+
+ describe 'retrieving issues for a single project' do
+ let(:finder) do
+ IssuesFinder.new(user, scope: 'all', label_name: Label::None.title,
+ state: 'opened', project_id: project.id)
+ end
+
+ benchmark_subject { finder.execute }
+
+ it { is_expected.to iterate_per_second(2000) }
+ end
+ end
+end
diff --git a/spec/controllers/abuse_reports_controller_spec.rb b/spec/controllers/abuse_reports_controller_spec.rb
index 0faab8d7ff0..15824a1c67f 100644
--- a/spec/controllers/abuse_reports_controller_spec.rb
+++ b/spec/controllers/abuse_reports_controller_spec.rb
@@ -18,27 +18,31 @@ describe AbuseReportsController do
end
it "sends a notification email" do
- post :create,
- abuse_report: {
- user_id: user.id,
- message: message
- }
-
- email = ActionMailer::Base.deliveries.last
-
- expect(email.to).to eq([admin_email])
- expect(email.subject).to include(user.username)
- expect(email.text_part.body).to include(message)
- end
-
- it "saves the abuse report" do
- expect do
+ perform_enqueued_jobs do
post :create,
abuse_report: {
user_id: user.id,
message: message
}
- end.to change { AbuseReport.count }.by(1)
+
+ email = ActionMailer::Base.deliveries.last
+
+ expect(email.to).to eq([admin_email])
+ expect(email.subject).to include(user.username)
+ expect(email.text_part.body).to include(message)
+ end
+ end
+
+ it "saves the abuse report" do
+ perform_enqueued_jobs do
+ expect do
+ post :create,
+ abuse_report: {
+ user_id: user.id,
+ message: message
+ }
+ end.to change { AbuseReport.count }.by(1)
+ end
end
end
diff --git a/spec/controllers/admin/impersonation_controller_spec.rb b/spec/controllers/admin/impersonation_controller_spec.rb
new file mode 100644
index 00000000000..d7a7ba1c5b6
--- /dev/null
+++ b/spec/controllers/admin/impersonation_controller_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe Admin::ImpersonationController do
+ let(:admin) { create(:admin) }
+
+ before do
+ sign_in(admin)
+ end
+
+ describe 'CREATE #impersonation when blocked' do
+ let(:blocked_user) { create(:user, state: :blocked) }
+
+ it 'does not allow impersonation' do
+ post :create, id: blocked_user.username
+
+ expect(flash[:alert]).to eq 'You cannot impersonate a blocked user'
+ end
+ end
+end
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index aa8d6cb807f..85379a8e984 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -114,7 +114,7 @@ describe AutocompleteController do
get(:users, project_id: project.id)
end
- it { expect(response.status).to eq(302) }
+ it { expect(response.status).to eq(404) }
end
describe 'GET #users with unknown project' do
@@ -122,7 +122,7 @@ describe AutocompleteController do
get(:users, project_id: 'unknown')
end
- it { expect(response.status).to eq(302) }
+ it { expect(response.status).to eq(404) }
end
describe 'GET #users with inaccessible group' do
@@ -131,7 +131,7 @@ describe AutocompleteController do
get(:users, group_id: user.namespace.id)
end
- it { expect(response.status).to eq(302) }
+ it { expect(response.status).to eq(404) }
end
describe 'GET #users with no project' do
@@ -139,7 +139,8 @@ describe AutocompleteController do
get(:users)
end
- it { expect(response.status).to eq(302) }
+ it { expect(body).to be_kind_of(Array) }
+ it { expect(body.size).to eq 0 }
end
end
end
diff --git a/spec/controllers/commit_controller_spec.rb b/spec/controllers/commit_controller_spec.rb
index bb3d87f3840..5337a69e84b 100644
--- a/spec/controllers/commit_controller_spec.rb
+++ b/spec/controllers/commit_controller_spec.rb
@@ -69,6 +69,21 @@ describe Projects::CommitController do
expect(response.body).to start_with("diff --git")
end
+
+ it "should really only be a git diff without whitespace changes" do
+ get(:show,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: '66eceea0db202bb39c4e445e8ca28689645366c5',
+ # id: commit.id,
+ format: format,
+ w: 1)
+
+ expect(response.body).to start_with("diff --git")
+ # without whitespace option, there are more than 2 diff_splits
+ diff_splits = assigns(:diffs)[0].diff.split("\n")
+ expect(diff_splits.length).to be <= 2
+ end
end
describe "as patch" do
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 4bb47c6b025..665526fde93 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -88,6 +88,22 @@ describe ProjectsController do
end
end
+ describe "#destroy" do
+ let(:admin) { create(:admin) }
+
+ it "redirects to the dashboard" do
+ controller.instance_variable_set(:@project, project)
+ sign_in(admin)
+
+ orig_id = project.id
+ delete :destroy, namespace_id: project.namespace.path, id: project.path
+
+ expect { Project.find(orig_id) }.to raise_error(ActiveRecord::RecordNotFound)
+ expect(response.status).to eq(302)
+ expect(response).to redirect_to(dashboard_projects_path)
+ end
+ end
+
describe "POST #toggle_star" do
it "toggles star if user is signed in" do
sign_in(user)
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
new file mode 100644
index 00000000000..b3dcb52c500
--- /dev/null
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -0,0 +1,233 @@
+require 'spec_helper'
+
+describe SnippetsController do
+ describe 'GET #show' do
+ let(:user) { create(:user) }
+
+ context 'when the personal snippet is private' do
+ let(:personal_snippet) { create(:personal_snippet, :private, author: user) }
+
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ end
+
+ context 'when signed in user is not the author' do
+ let(:other_author) { create(:author) }
+ let(:other_personal_snippet) { create(:personal_snippet, :private, author: other_author) }
+
+ it 'responds with status 404' do
+ get :show, id: other_personal_snippet.to_param
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'when signed in user is the author' do
+ it 'renders the snippet' do
+ get :show, id: personal_snippet.to_param
+
+ expect(assigns(:snippet)).to eq(personal_snippet)
+ expect(response.status).to eq(200)
+ end
+ end
+ end
+
+ context 'when not signed in' do
+ it 'redirects to the sign in page' do
+ get :show, id: personal_snippet.to_param
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+ end
+
+ context 'when the personal snippet is internal' do
+ let(:personal_snippet) { create(:personal_snippet, :internal, author: user) }
+
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ end
+
+ it 'renders the snippet' do
+ get :show, id: personal_snippet.to_param
+
+ expect(assigns(:snippet)).to eq(personal_snippet)
+ expect(response.status).to eq(200)
+ end
+ end
+
+ context 'when not signed in' do
+ it 'redirects to the sign in page' do
+ get :show, id: personal_snippet.to_param
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+ end
+
+ context 'when the personal snippet is public' do
+ let(:personal_snippet) { create(:personal_snippet, :public, author: user) }
+
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ end
+
+ it 'renders the snippet' do
+ get :show, id: personal_snippet.to_param
+
+ expect(assigns(:snippet)).to eq(personal_snippet)
+ expect(response.status).to eq(200)
+ end
+ end
+
+ context 'when not signed in' do
+ it 'renders the snippet' do
+ get :show, id: personal_snippet.to_param
+
+ expect(assigns(:snippet)).to eq(personal_snippet)
+ expect(response.status).to eq(200)
+ end
+ end
+ end
+
+ context 'when the personal snippet does not exist' do
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ end
+
+ it 'responds with status 404' do
+ get :show, id: 'doesntexist'
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'when not signed in' do
+ it 'responds with status 404' do
+ get :show, id: 'doesntexist'
+
+ expect(response.status).to eq(404)
+ end
+ end
+ end
+ end
+
+ describe 'GET #raw' do
+ let(:user) { create(:user) }
+
+ context 'when the personal snippet is private' do
+ let(:personal_snippet) { create(:personal_snippet, :private, author: user) }
+
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ end
+
+ context 'when signed in user is not the author' do
+ let(:other_author) { create(:author) }
+ let(:other_personal_snippet) { create(:personal_snippet, :private, author: other_author) }
+
+ it 'responds with status 404' do
+ get :raw, id: other_personal_snippet.to_param
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'when signed in user is the author' do
+ it 'renders the raw snippet' do
+ get :raw, id: personal_snippet.to_param
+
+ expect(assigns(:snippet)).to eq(personal_snippet)
+ expect(response.status).to eq(200)
+ end
+ end
+ end
+
+ context 'when not signed in' do
+ it 'redirects to the sign in page' do
+ get :raw, id: personal_snippet.to_param
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+ end
+
+ context 'when the personal snippet is internal' do
+ let(:personal_snippet) { create(:personal_snippet, :internal, author: user) }
+
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ end
+
+ it 'renders the raw snippet' do
+ get :raw, id: personal_snippet.to_param
+
+ expect(assigns(:snippet)).to eq(personal_snippet)
+ expect(response.status).to eq(200)
+ end
+ end
+
+ context 'when not signed in' do
+ it 'redirects to the sign in page' do
+ get :raw, id: personal_snippet.to_param
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+ end
+
+ context 'when the personal snippet is public' do
+ let(:personal_snippet) { create(:personal_snippet, :public, author: user) }
+
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ end
+
+ it 'renders the raw snippet' do
+ get :raw, id: personal_snippet.to_param
+
+ expect(assigns(:snippet)).to eq(personal_snippet)
+ expect(response.status).to eq(200)
+ end
+ end
+
+ context 'when not signed in' do
+ it 'renders the raw snippet' do
+ get :raw, id: personal_snippet.to_param
+
+ expect(assigns(:snippet)).to eq(personal_snippet)
+ expect(response.status).to eq(200)
+ end
+ end
+ end
+
+ context 'when the personal snippet does not exist' do
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ end
+
+ it 'responds with status 404' do
+ get :raw, id: 'doesntexist'
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'when not signed in' do
+ it 'responds with status 404' do
+ get :raw, id: 'doesntexist'
+
+ expect(response.status).to eq(404)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
index 9f89101d7f7..104a5f50143 100644
--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -16,13 +16,26 @@ describe UsersController do
context 'with rendered views' do
render_views
- it 'renders the show template' do
- sign_in(user)
+ describe 'when logged in' do
+ before do
+ sign_in(user)
+ end
- get :show, username: user.username
+ it 'renders the show template' do
+ get :show, username: user.username
- expect(response).to be_success
- expect(response).to render_template('show')
+ expect(response).to be_success
+ expect(response).to render_template('show')
+ end
+ end
+
+ describe 'when logged out' do
+ it 'renders the show template' do
+ get :show, username: user.username
+
+ expect(response).to be_success
+ expect(response).to render_template('show')
+ end
end
end
end
diff --git a/spec/factories.rb b/spec/factories.rb
index 200f18f660d..4bf93adabe2 100644
--- a/spec/factories.rb
+++ b/spec/factories.rb
@@ -165,6 +165,18 @@ FactoryGirl.define do
title
content
file_name
+
+ 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
factory :snippet do
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index 4c756a8e732..4570e409128 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -87,13 +87,16 @@ describe "Admin::Users", feature: true do
end
it "should call send mail" do
- expect(Notify).to receive(:new_user_email)
+ expect_any_instance_of(NotificationService).to receive(:new_user)
click_button "Create user"
end
it "should send valid email to user with email & password" do
- click_button "Create user"
+ perform_enqueued_jobs do
+ click_button "Create user"
+ end
+
user = User.find_by(username: 'bang')
email = ActionMailer::Base.deliveries.last
expect(email.subject).to have_content('Account was created')
@@ -125,6 +128,16 @@ describe "Admin::Users", feature: true do
expect(page).not_to have_content('Impersonate')
end
+
+ it 'should not show impersonate button for blocked user' do
+ another_user.block
+
+ visit admin_user_path(another_user)
+
+ expect(page).not_to have_content('Impersonate')
+
+ another_user.activate
+ end
end
context 'when impersonating' do
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 32fd4065bb4..0af5e6fc1a6 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -61,7 +61,7 @@ describe 'Issues', feature: true do
it 'allows user to select unasigned', js: true do
visit edit_namespace_project_issue_path(project.namespace, project, issue)
- expect(page).to have_content "Assign to #{@user.name}"
+ expect(page).to have_content "Assignee #{@user.name}"
first('#s2id_issue_assignee_id').click
sleep 2 # wait for ajax stuff to complete
diff --git a/spec/finders/contributed_projects_finder_spec.rb b/spec/finders/contributed_projects_finder_spec.rb
new file mode 100644
index 00000000000..65d7f14c721
--- /dev/null
+++ b/spec/finders/contributed_projects_finder_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+describe ContributedProjectsFinder do
+ let(:source_user) { create(:user) }
+ let(:current_user) { create(:user) }
+
+ let(:finder) { described_class.new(source_user) }
+
+ let!(:public_project) { create(:project, :public) }
+ let!(:private_project) { create(:project, :private) }
+
+ before do
+ private_project.team << [source_user, Gitlab::Access::MASTER]
+ private_project.team << [current_user, Gitlab::Access::DEVELOPER]
+ public_project.team << [source_user, Gitlab::Access::MASTER]
+
+ create(:event, action: Event::PUSHED, project: public_project,
+ target: public_project, author: source_user)
+
+ create(:event, action: Event::PUSHED, project: private_project,
+ target: private_project, author: source_user)
+ end
+
+ describe 'without a current user' do
+ subject { finder.execute }
+
+ it { is_expected.to eq([public_project]) }
+ end
+
+ describe 'with a current user' do
+ subject { finder.execute(current_user) }
+
+ it { is_expected.to eq([private_project, public_project]) }
+ end
+end
diff --git a/spec/finders/group_finder_spec.rb b/spec/finders/group_finder_spec.rb
deleted file mode 100644
index 78dc027837c..00000000000
--- a/spec/finders/group_finder_spec.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-require 'spec_helper'
-
-describe GroupsFinder do
- let(:user) { create :user }
- let!(:group) { create :group }
- let!(:public_group) { create :group, public: true }
-
- describe :execute do
- it 'finds public group' do
- groups = GroupsFinder.new.execute(user)
- expect(groups.size).to eq(1)
- expect(groups.first).to eq(public_group)
- 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..4f6a000822e
--- /dev/null
+++ b/spec/finders/groups_finder_spec.rb
@@ -0,0 +1,48 @@
+require 'spec_helper'
+
+describe GroupsFinder do
+ describe '#execute' do
+ let(:user) { create(:user) }
+
+ let(:group1) { create(:group) }
+ let(:group2) { create(:group) }
+ let(:group3) { create(:group) }
+ let(:group4) { create(:group, public: true) }
+
+ let!(:public_project) { create(:project, :public, group: group1) }
+ let!(:internal_project) { create(:project, :internal, group: group2) }
+ let!(:private_project) { create(:project, :private, group: group3) }
+
+ let(:finder) { described_class.new }
+
+ describe 'with a user' do
+ subject { finder.execute(user) }
+
+ describe 'when the user is not a member of any groups' do
+ it { is_expected.to eq([group4, group2, group1]) }
+ end
+
+ describe 'when the user is a member of a group' do
+ before do
+ group3.add_user(user, Gitlab::Access::DEVELOPER)
+ end
+
+ it { is_expected.to eq([group4, group3, group2, group1]) }
+ end
+
+ describe 'when the user is a member of a private project' do
+ before do
+ private_project.team.add_user(user, Gitlab::Access::DEVELOPER)
+ end
+
+ it { is_expected.to eq([group4, group3, group2, group1]) }
+ end
+ end
+
+ describe 'without a user' do
+ subject { finder.execute }
+
+ it { is_expected.to eq([group4, group1]) }
+ 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..2d9068cc720
--- /dev/null
+++ b/spec/finders/joined_groups_finder_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+
+describe JoinedGroupsFinder do
+ describe '#execute' do
+ let(:source_user) { create(:user) }
+ let(:current_user) { create(:user) }
+
+ let(:group1) { create(:group) }
+ let(:group2) { create(:group) }
+ let(:group3) { create(:group) }
+ let(:group4) { create(:group, public: true) }
+
+ let!(:public_project) { create(:project, :public, group: group1) }
+ let!(:internal_project) { create(:project, :internal, group: group2) }
+ let!(:private_project) { create(:project, :private, group: group3) }
+
+ let(:finder) { described_class.new(source_user) }
+
+ before do
+ [group1, group2, group3, group4].each do |group|
+ group.add_user(source_user, Gitlab::Access::MASTER)
+ end
+ end
+
+ describe 'with a current user' do
+ describe 'when the current user has access to the projects of the source user' do
+ before do
+ private_project.team.add_user(current_user, Gitlab::Access::DEVELOPER)
+ end
+
+ subject { finder.execute(current_user) }
+
+ it { is_expected.to eq([group4, group3, group2, group1]) }
+ end
+
+ describe 'when the current user does not have access to the projects of the source user' do
+ subject { finder.execute(current_user) }
+
+ it { is_expected.to eq([group4, group2, group1]) }
+ end
+ end
+
+ describe 'without a current user' do
+ subject { finder.execute }
+
+ it { is_expected.to eq([group4, group1]) }
+ end
+ end
+end
diff --git a/spec/finders/personal_projects_finder_spec.rb b/spec/finders/personal_projects_finder_spec.rb
new file mode 100644
index 00000000000..38817add456
--- /dev/null
+++ b/spec/finders/personal_projects_finder_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe PersonalProjectsFinder do
+ let(:source_user) { create(:user) }
+ let(:current_user) { create(:user) }
+
+ let(:finder) { described_class.new(source_user) }
+
+ let!(:public_project) do
+ create(:project, :public, namespace: source_user.namespace, name: 'A',
+ path: 'A')
+ end
+
+ let!(:private_project) do
+ create(:project, :private, namespace: source_user.namespace, name: 'B',
+ path: 'B')
+ end
+
+ before do
+ private_project.team << [current_user, Gitlab::Access::DEVELOPER]
+ end
+
+ describe 'without a current user' do
+ subject { finder.execute }
+
+ it { is_expected.to eq([public_project]) }
+ end
+
+ describe 'with a current user' do
+ subject { finder.execute(current_user) }
+
+ it { is_expected.to eq([private_project, public_project]) }
+ end
+end
diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb
index de9d4cd6128..f32641ef0f6 100644
--- a/spec/finders/projects_finder_spec.rb
+++ b/spec/finders/projects_finder_spec.rb
@@ -1,51 +1,63 @@
require 'spec_helper'
describe ProjectsFinder do
- let(:user) { create :user }
- let(:group) { create :group }
+ describe '#execute' do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
- let(:project1) { create(:empty_project, :public, group: group) }
- let(:project2) { create(:empty_project, :internal, group: group) }
- let(:project3) { create(:empty_project, :private, group: group) }
- let(:project4) { create(:empty_project, :private, group: group) }
+ let!(:private_project) do
+ create(:project, :private, name: 'A', path: 'A')
+ end
- context 'non authenticated' do
- subject { ProjectsFinder.new.execute(nil, group: group) }
+ let!(:internal_project) do
+ create(:project, :internal, group: group, name: 'B', path: 'B')
+ end
- it { is_expected.to include(project1) }
- it { is_expected.not_to include(project2) }
- it { is_expected.not_to include(project3) }
- it { is_expected.not_to include(project4) }
- end
+ let!(:public_project) do
+ create(:project, :public, group: group, name: 'C', path: 'C')
+ end
- context 'authenticated' do
- subject { ProjectsFinder.new.execute(user, group: group) }
+ let(:finder) { described_class.new }
- it { is_expected.to include(project1) }
- it { is_expected.to include(project2) }
- it { is_expected.not_to include(project3) }
- it { is_expected.not_to include(project4) }
- end
+ describe 'without a group' do
+ describe 'without a user' do
+ subject { finder.execute }
- context 'authenticated, project member' do
- before { project3.team << [user, :developer] }
+ it { is_expected.to eq([public_project]) }
+ end
- subject { ProjectsFinder.new.execute(user, group: group) }
+ describe 'with a user' do
+ subject { finder.execute(user) }
- it { is_expected.to include(project1) }
- it { is_expected.to include(project2) }
- it { is_expected.to include(project3) }
- it { is_expected.not_to include(project4) }
- end
+ describe 'without private projects' do
+ it { is_expected.to eq([public_project, internal_project]) }
+ end
+
+ describe 'with private projects' do
+ before do
+ private_project.team.add_user(user, Gitlab::Access::MASTER)
+ end
+
+ it do
+ is_expected.to eq([public_project, internal_project,
+ private_project])
+ end
+ end
+ end
+ end
+
+ describe 'with a group' do
+ describe 'without a user' do
+ subject { finder.execute(nil, group: group) }
- context 'authenticated, group member' do
- before { group.add_developer(user) }
+ it { is_expected.to eq([public_project]) }
+ end
- subject { ProjectsFinder.new.execute(user, group: group) }
+ describe 'with a user' do
+ subject { finder.execute(user, group: group) }
- it { is_expected.to include(project1) }
- it { is_expected.to include(project2) }
- it { is_expected.to include(project3) }
- it { is_expected.to include(project4) }
+ it { is_expected.to eq([public_project, internal_project]) }
+ end
+ end
end
end
diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb
index 41d12afa9ce..e8dfc5c0eb1 100644
--- a/spec/fixtures/markdown.md.erb
+++ b/spec/fixtures/markdown.md.erb
@@ -153,6 +153,7 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Ignores invalid: <%= User.reference_prefix %>fake_user
- Ignored in code: `<%= user.to_reference %>`
- Ignored in links: [Link to <%= user.to_reference %>](#user-link)
+- Link to user by reference: [User](<%= user.to_reference %>)
#### IssueReferenceFilter
@@ -160,6 +161,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Issue in another project: <%= xissue.to_reference(project) %>
- Ignored in code: `<%= issue.to_reference %>`
- Ignored in links: [Link to <%= issue.to_reference %>](#issue-link)
+- Issue by URL: <%= urls.namespace_project_issue_url(issue.project.namespace, issue.project, issue) %>
+- Link to issue by reference: [Issue](<%= issue.to_reference %>)
+- Link to issue by URL: [Issue](<%= urls.namespace_project_issue_url(issue.project.namespace, issue.project, issue) %>)
#### MergeRequestReferenceFilter
@@ -167,6 +171,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Merge request in another project: <%= xmerge_request.to_reference(project) %>
- Ignored in code: `<%= merge_request.to_reference %>`
- Ignored in links: [Link to <%= merge_request.to_reference %>](#merge-request-link)
+- Merge request by URL: <%= urls.namespace_project_merge_request_url(merge_request.project.namespace, merge_request.project, merge_request) %>
+- Link to merge request by reference: [Merge request](<%= merge_request.to_reference %>)
+- Link to merge request by URL: [Merge request](<%= urls.namespace_project_merge_request_url(merge_request.project.namespace, merge_request.project, merge_request) %>)
#### SnippetReferenceFilter
@@ -174,6 +181,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Snippet in another project: <%= xsnippet.to_reference(project) %>
- Ignored in code: `<%= snippet.to_reference %>`
- Ignored in links: [Link to <%= snippet.to_reference %>](#snippet-link)
+- Snippet by URL: <%= urls.namespace_project_snippet_url(snippet.project.namespace, snippet.project, snippet) %>
+- Link to snippet by reference: [Snippet](<%= snippet.to_reference %>)
+- Link to snippet by URL: [Snippet](<%= urls.namespace_project_snippet_url(snippet.project.namespace, snippet.project, snippet) %>)
#### CommitRangeReferenceFilter
@@ -181,6 +191,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Range in another project: <%= xcommit_range.to_reference(project) %>
- Ignored in code: `<%= commit_range.to_reference %>`
- Ignored in links: [Link to <%= commit_range.to_reference %>](#commit-range-link)
+- Range by URL: <%= urls.namespace_project_compare_url(commit_range.project.namespace, commit_range.project, commit_range.to_param) %>
+- Link to range by reference: [Range](<%= commit_range.to_reference %>)
+- Link to range by URL: [Range](<%= urls.namespace_project_compare_url(commit_range.project.namespace, commit_range.project, commit_range.to_param) %>)
#### CommitReferenceFilter
@@ -188,6 +201,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Commit in another project: <%= xcommit.to_reference(project) %>
- Ignored in code: `<%= commit.to_reference %>`
- Ignored in links: [Link to <%= commit.to_reference %>](#commit-link)
+- Commit by URL: <%= urls.namespace_project_commit_url(commit.project.namespace, commit.project, commit) %>
+- Link to commit by reference: [Commit](<%= commit.to_reference %>)
+- Link to commit by URL: [Commit](<%= urls.namespace_project_commit_url(commit.project.namespace, commit.project, commit) %>)
#### LabelReferenceFilter
@@ -196,6 +212,7 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Label by name in quotes: <%= label.to_reference(:name) %>
- Ignored in code: `<%= simple_label.to_reference %>`
- Ignored in links: [Link to <%= simple_label.to_reference %>](#label-link)
+- Link to label by reference: [Label](<%= label.to_reference %>)
### Task Lists
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 1dfae0fbd3f..0a64b70d6a6 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -59,7 +59,7 @@ describe ApplicationHelper do
avatar_url = "http://localhost/uploads/project/avatar/#{project.id}/banana_sample.gif"
expect(helper.project_icon("#{project.namespace.to_param}/#{project.to_param}").to_s).
- to eq "<img alt=\"Banana sample\" src=\"#{avatar_url}\" />"
+ to eq "<img src=\"#{avatar_url}\" alt=\"Banana sample\" />"
end
it 'should give uploaded icon when present' do
@@ -95,9 +95,9 @@ describe ApplicationHelper do
end
it 'should call gravatar_icon when no User exists with the given email' do
- expect(helper).to receive(:gravatar_icon).with('foo@example.com', 20)
+ expect(helper).to receive(:gravatar_icon).with('foo@example.com', 20, 2)
- helper.avatar_icon('foo@example.com', 20)
+ helper.avatar_icon('foo@example.com', 20, 2)
end
describe 'using a User' do
@@ -150,15 +150,19 @@ describe ApplicationHelper do
stub_gravatar_setting(plain_url: 'http://example.local/?s=%{size}&hash=%{hash}')
expect(gravatar_icon(user_email, 20)).
- to eq('http://example.local/?s=20&hash=b58c6f14d292556214bd64909bcdb118')
+ to eq('http://example.local/?s=40&hash=b58c6f14d292556214bd64909bcdb118')
end
it 'accepts a custom size argument' do
- expect(helper.gravatar_icon(user_email, 64)).to include '?s=64'
+ expect(helper.gravatar_icon(user_email, 64)).to include '?s=128'
end
- it 'defaults size to 40 when given an invalid size' do
- expect(helper.gravatar_icon(user_email, nil)).to include '?s=40'
+ it 'defaults size to 40@2x when given an invalid size' do
+ expect(helper.gravatar_icon(user_email, nil)).to include '?s=80'
+ end
+
+ it 'accepts a scaling factor' do
+ expect(helper.gravatar_icon(user_email, 40, 3)).to include '?s=120'
end
it 'ignores case and surrounding whitespace' do
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index 78a6b631eb2..1f2c4ee77b5 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -127,4 +127,30 @@ describe IssuesHelper do
it { is_expected.to eq("!1, !2, or !3") }
end
+ describe "#url_to_emoji" do
+ it "returns url" do
+ expect(url_to_emoji("smile")).to include("emoji/1F604.png")
+ end
+ end
+
+ describe "#emoji_list" do
+ it "returns url" do
+ expect(emoji_list).to be_kind_of(Array)
+ end
+ end
+
+ describe "#note_active_class" do
+ before do
+ @note = create :note
+ @note1 = create :note
+ end
+
+ it "returns empty string for unauthenticated user" do
+ expect(note_active_class(Note.all, nil)).to eq("")
+ end
+
+ it "returns active string for author" do
+ expect(note_active_class(Note.all, @note.author)).to eq("active")
+ end
+ end
end
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index 7d90f9877c6..6f287719ba6 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -425,8 +425,12 @@ module Ci
end
describe "Error handling" do
+ it "fails to parse YAML" do
+ expect{GitlabCiYamlProcessor.new("invalid: yaml: test")}.to raise_error(Psych::SyntaxError)
+ end
+
it "indicates that object is invalid" do
- expect{GitlabCiYamlProcessor.new("invalid_yaml\n!ccdvlf%612334@@@@")}.to raise_error(GitlabCiYamlProcessor::ValidationError)
+ expect{GitlabCiYamlProcessor.new("invalid_yaml")}.to raise_error(GitlabCiYamlProcessor::ValidationError)
end
it "returns errors if tags parameter is invalid" do
diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb
index 21254f778d3..fe1b94a484e 100644
--- a/spec/lib/gitlab/closing_issue_extractor_spec.rb
+++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb
@@ -2,11 +2,18 @@ require 'spec_helper'
describe Gitlab::ClosingIssueExtractor do
let(:project) { create(:project) }
+ let(:project2) { create(:project) }
let(:issue) { create(:issue, project: project) }
+ let(:issue2) { create(:issue, project: project2) }
let(:reference) { issue.to_reference }
+ let(:cross_reference) { issue2.to_reference(project) }
subject { described_class.new(project, project.creator) }
+ before do
+ project2.team << [project.creator, :master]
+ end
+
describe "#closed_by_message" do
context 'with a single reference' do
it do
@@ -130,6 +137,27 @@ describe Gitlab::ClosingIssueExtractor do
end
end
+ context "with a cross-project reference" do
+ it do
+ message = "Closes #{cross_reference}"
+ expect(subject.closed_by_message(message)).to eq([issue2])
+ end
+ end
+
+ context "with a cross-project URL" do
+ it do
+ message = "Closes #{urls.namespace_project_issue_url(issue2.project.namespace, issue2.project, issue2)}"
+ expect(subject.closed_by_message(message)).to eq([issue2])
+ end
+ end
+
+ context "with an invalid URL" do
+ it do
+ message = "Closes https://google.com#{urls.namespace_project_issue_path(issue2.project.namespace, issue2.project, issue2)}"
+ expect(subject.closed_by_message(message)).to eq([])
+ end
+ end
+
context 'with multiple references' do
let(:other_issue) { create(:issue, project: project) }
let(:third_issue) { create(:issue, project: project) }
@@ -171,6 +199,31 @@ describe Gitlab::ClosingIssueExtractor do
expect(subject.closed_by_message(message)).
to match_array([issue, other_issue, third_issue])
end
+
+ it "fetches cross-project references" do
+ message = "Closes #{reference} and #{cross_reference}"
+
+ expect(subject.closed_by_message(message)).
+ to match_array([issue, issue2])
+ end
+
+ it "fetches cross-project URL references" do
+ message = "Closes #{urls.namespace_project_issue_url(issue2.project.namespace, issue2.project, issue2)} and #{reference}"
+
+ expect(subject.closed_by_message(message)).
+ to match_array([issue, issue2])
+ end
+
+ it "ignores invalid cross-project URL references" do
+ message = "Closes https://google.com#{urls.namespace_project_issue_path(issue2.project.namespace, issue2.project, issue2)} and #{reference}"
+
+ expect(subject.closed_by_message(message)).
+ to match_array([issue])
+ end
end
end
+
+ def urls
+ Gitlab::Application.routes.url_helpers
+ end
end
diff --git a/spec/lib/gitlab/lfs/lfs_router_spec.rb b/spec/lib/gitlab/lfs/lfs_router_spec.rb
index cebcb5bc887..5b13d65c7c0 100644
--- a/spec/lib/gitlab/lfs/lfs_router_spec.rb
+++ b/spec/lib/gitlab/lfs/lfs_router_spec.rb
@@ -26,113 +26,52 @@ describe Gitlab::Lfs::Router do
let(:sample_oid) { "b68143e6463773b1b6c6fd009a76c32aeec041faff32ba2ed42fd7f708a17f80" }
let(:sample_size) { 499013 }
+ let(:respond_with_deprecated) {[ 501, { "Content-Type"=>"application/json; charset=utf-8" }, ["{\"message\":\"Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.\",\"documentation_url\":\"#{Gitlab.config.gitlab.url}/help\"}"]]}
+ let(:respond_with_disabled) {[ 501, { "Content-Type"=>"application/json; charset=utf-8" }, ["{\"message\":\"Git LFS is not enabled on this GitLab server, contact your admin.\",\"documentation_url\":\"#{Gitlab.config.gitlab.url}/help\"}"]]}
describe 'when lfs is disabled' do
before do
allow(Gitlab.config.lfs).to receive(:enabled).and_return(false)
- env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/#{sample_oid}"
+ env['REQUEST_METHOD'] = 'POST'
+ body = {
+ 'objects' => [
+ { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+ 'size' => 1575078
+ },
+ { 'oid' => sample_oid,
+ 'size' => sample_size
+ }
+ ],
+ 'operation' => 'upload'
+ }.to_json
+ env['rack.input'] = StringIO.new(body)
+ env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/batch"
end
it 'responds with 501' do
- respond_with_disabled = [ 501,
- { "Content-Type"=>"application/vnd.git-lfs+json" },
- ["{\"message\":\"Git LFS is not enabled on this GitLab server, contact your admin.\",\"documentation_url\":\"#{Gitlab.config.gitlab.url}/help\"}"]
- ]
expect(lfs_router_auth.try_call).to match_array(respond_with_disabled)
end
end
- describe 'when fetching lfs object' do
+ describe 'when fetching lfs object using deprecated API' do
before do
enable_lfs
- env['HTTP_ACCEPT'] = "application/vnd.git-lfs+json; charset=utf-8"
env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/#{sample_oid}"
end
- describe 'when user is authenticated' do
- context 'and user has project download access' do
- before do
- @auth = authorize(user)
- env["HTTP_AUTHORIZATION"] = @auth
- project.lfs_objects << lfs_object
- project.team << [user, :master]
- end
-
- it "responds with status 200" do
- expect(lfs_router_auth.try_call.first).to eq(200)
- end
-
- it "responds with download hypermedia" do
- json_response = ActiveSupport::JSON.decode(lfs_router_auth.try_call.last.first)
-
- expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}")
- expect(json_response['_links']['download']['header']).to eq("Authorization" => @auth, "Accept" => "application/vnd.git-lfs+json; charset=utf-8")
- end
- end
-
- context 'and user does not have project access' do
- it "responds with status 403" do
- expect(lfs_router_auth.try_call.first).to eq(403)
- end
- end
- end
-
- describe 'when user is unauthenticated' do
- context 'and user does not have download access' do
- it "responds with status 401" do
- expect(lfs_router_noauth.try_call.first).to eq(401)
- end
- end
-
- context 'and user has download access' do
- before do
- project.team << [user, :master]
- end
-
- it "responds with status 401" do
- expect(lfs_router_noauth.try_call.first).to eq(401)
- end
- end
+ it 'responds with 501' do
+ expect(lfs_router_auth.try_call).to match_array(respond_with_deprecated)
end
+ end
- describe 'and project is public' do
- context 'and project has access to the lfs object' do
- before do
- public_project.lfs_objects << lfs_object
- end
-
- context 'and user is authenticated' do
- it "responds with status 200 and sends download hypermedia" do
- expect(lfs_router_public_auth.try_call.first).to eq(200)
- json_response = ActiveSupport::JSON.decode(lfs_router_public_auth.try_call.last.first)
-
- expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{public_project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}")
- expect(json_response['_links']['download']['header']).to eq("Accept" => "application/vnd.git-lfs+json; charset=utf-8")
- end
- end
-
- context 'and user is unauthenticated' do
- it "responds with status 200 and sends download hypermedia" do
- expect(lfs_router_public_noauth.try_call.first).to eq(200)
- json_response = ActiveSupport::JSON.decode(lfs_router_public_noauth.try_call.last.first)
-
- expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{public_project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}")
- expect(json_response['_links']['download']['header']).to eq("Accept" => "application/vnd.git-lfs+json; charset=utf-8")
- end
- end
- end
-
- context 'and project does not have access to the lfs object' do
- it "responds with status 404" do
- expect(lfs_router_public_auth.try_call.first).to eq(404)
- end
- end
+ describe 'when fetching lfs object' do
+ before do
+ enable_lfs
+ env['HTTP_ACCEPT'] = "application/vnd.git-lfs+json; charset=utf-8"
+ env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}"
end
describe 'and request comes from gitlab-workhorse' do
- before do
- env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}"
- end
context 'without user being authorized' do
it "responds with status 401" do
expect(lfs_router_noauth.try_call.first).to eq(401)
@@ -173,200 +112,376 @@ describe Gitlab::Lfs::Router do
end
end
end
+ end
- describe 'from a forked public project' do
- before do
- env['HTTP_ACCEPT'] = "application/vnd.git-lfs+json; charset=utf-8"
- env["PATH_INFO"] = "#{forked_project.repository.path_with_namespace}.git/info/lfs/objects/#{sample_oid}"
- end
+ describe 'when handling lfs request using deprecated API' do
+ before do
+ enable_lfs
+ env['REQUEST_METHOD'] = 'POST'
+ env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects"
+ end
+
+ it 'responds with 501' do
+ expect(lfs_router_auth.try_call).to match_array(respond_with_deprecated)
+ end
+ end
+
+ describe 'when handling lfs batch request' do
+ before do
+ enable_lfs
+ env['REQUEST_METHOD'] = 'POST'
+ env['PATH_INFO'] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/batch"
+ end
+
+ describe 'download' do
+ describe 'when user is authenticated' do
+ before do
+ body = { 'operation' => 'download',
+ 'objects' => [
+ { 'oid' => sample_oid,
+ 'size' => sample_size
+ }]
+ }.to_json
+ env['rack.input'] = StringIO.new(body)
+ end
- context "when fetching a lfs object" do
- context "and user has project download access" do
+ describe 'when user has download access' do
before do
- public_project.lfs_objects << lfs_object
+ @auth = authorize(user)
+ env["HTTP_AUTHORIZATION"] = @auth
+ project.team << [user, :reporter]
end
- it "can download the lfs object" do
- expect(lfs_router_forked_auth.try_call.first).to eq(200)
- json_response = ActiveSupport::JSON.decode(lfs_router_forked_auth.try_call.last.first)
+ context 'when downloading an lfs object that is assigned to our project' do
+ before do
+ project.lfs_objects << lfs_object
+ end
+
+ it 'responds with status 200 and href to download' do
+ response = lfs_router_auth.try_call
+ expect(response.first).to eq(200)
+ response_body = ActiveSupport::JSON.decode(response.last.first)
- expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{forked_project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}")
- expect(json_response['_links']['download']['header']).to eq("Accept" => "application/vnd.git-lfs+json; charset=utf-8")
+ expect(response_body).to eq('objects' => [
+ { 'oid' => sample_oid,
+ 'size' => sample_size,
+ 'actions' => {
+ 'download' => {
+ 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
+ 'header' => { 'Authorization' => @auth }
+ }
+ }
+ }])
+ end
end
- end
- context "and user is not authenticated but project is public" do
- before do
- public_project.lfs_objects << lfs_object
+ context 'when downloading an lfs object that is assigned to other project' do
+ before do
+ public_project.lfs_objects << lfs_object
+ end
+
+ it 'responds with status 200 and error message' do
+ response = lfs_router_auth.try_call
+ expect(response.first).to eq(200)
+ response_body = ActiveSupport::JSON.decode(response.last.first)
+
+ expect(response_body).to eq('objects' => [
+ { 'oid' => sample_oid,
+ 'size' => sample_size,
+ 'error' => {
+ 'code' => 404,
+ 'message' => "Object does not exist on the server or you don't have permissions to access it",
+ }
+ }])
+ end
end
- it "can download the lfs object" do
- expect(lfs_router_forked_auth.try_call.first).to eq(200)
+ context 'when downloading a lfs object that does not exist' do
+ before do
+ body = { 'operation' => 'download',
+ 'objects' => [
+ { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+ 'size' => 1575078
+ }]
+ }.to_json
+ env['rack.input'] = StringIO.new(body)
+ end
+
+ it "responds with status 200 and error message" do
+ response = lfs_router_auth.try_call
+ expect(response.first).to eq(200)
+ response_body = ActiveSupport::JSON.decode(response.last.first)
+
+ expect(response_body).to eq('objects' => [
+ { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+ 'size' => 1575078,
+ 'error' => {
+ 'code' => 404,
+ 'message' => "Object does not exist on the server or you don't have permissions to access it",
+ }
+ }])
+ end
+ end
+
+ context 'when downloading one new and one existing lfs object' do
+ before do
+ body = { 'operation' => 'download',
+ 'objects' => [
+ { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+ 'size' => 1575078
+ },
+ { 'oid' => sample_oid,
+ 'size' => sample_size
+ }
+ ]
+ }.to_json
+ env['rack.input'] = StringIO.new(body)
+ project.lfs_objects << lfs_object
+ end
+
+ it "responds with status 200 with upload hypermedia link for the new object" do
+ response = lfs_router_auth.try_call
+ expect(response.first).to eq(200)
+ response_body = ActiveSupport::JSON.decode(response.last.first)
+
+ expect(response_body).to eq('objects' => [
+ { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+ 'size' => 1575078,
+ 'error' => {
+ 'code' => 404,
+ 'message' => "Object does not exist on the server or you don't have permissions to access it",
+ }
+ },
+ { 'oid' => sample_oid,
+ 'size' => sample_size,
+ 'actions' => {
+ 'download' => {
+ 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
+ 'header' => { 'Authorization' => @auth }
+ }
+ }
+ }])
+ end
end
end
- context "and user has project download access" do
+ context 'when user does is not member of the project' do
before do
- env["PATH_INFO"] = "#{forked_project.repository.path_with_namespace}.git/info/lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897"
- @auth = ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password)
+ @auth = authorize(user)
env["HTTP_AUTHORIZATION"] = @auth
- lfs_object_two = create(:lfs_object, :with_file, oid: "91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897", size: 1575078)
- public_project.lfs_objects << lfs_object_two
+ project.team << [user, :guest]
end
- it "can get a lfs object that is not in the forked project" do
- expect(lfs_router_forked_auth.try_call.first).to eq(200)
-
- json_response = ActiveSupport::JSON.decode(lfs_router_forked_auth.try_call.last.first)
- expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{forked_project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897")
- expect(json_response['_links']['download']['header']).to eq("Accept" => "application/vnd.git-lfs+json; charset=utf-8", "Authorization" => @auth)
+ it 'responds with 403' do
+ expect(lfs_router_auth.try_call.first).to eq(403)
end
end
- context "and user has project download access" do
+ context 'when user does not have download access' do
before do
- env["PATH_INFO"] = "#{forked_project.repository.path_with_namespace}.git/info/lfs/objects/267c8b1d876743971e3a9978405818ff5ca731c4c870b06507619cd9b1847b6b"
- lfs_object_three = create(:lfs_object, :with_file, oid: "267c8b1d876743971e3a9978405818ff5ca731c4c870b06507619cd9b1847b6b", size: 127192524)
- project.lfs_objects << lfs_object_three
+ @auth = authorize(user)
+ env["HTTP_AUTHORIZATION"] = @auth
+ project.team << [user, :guest]
end
- it "cannot get a lfs object that is not in the project" do
- expect(lfs_router_forked_auth.try_call.first).to eq(404)
+ it 'responds with 403' do
+ expect(lfs_router_auth.try_call.first).to eq(403)
end
end
end
- end
- end
-
- describe 'when initiating pushing of the lfs object' do
- before do
- enable_lfs
- env['REQUEST_METHOD'] = 'POST'
- env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/batch"
- end
-
- describe 'when user is authenticated' do
- before do
- body = { 'objects' => [{
- 'oid' => sample_oid,
- 'size' => sample_size
- }]
- }.to_json
- env['rack.input'] = StringIO.new(body)
- end
- describe 'when user has project push access' do
+ context 'when user is not authenticated' do
before do
- @auth = authorize(user)
- env["HTTP_AUTHORIZATION"] = @auth
- project.team << [user, :master]
+ body = { 'operation' => 'download',
+ 'objects' => [
+ { 'oid' => sample_oid,
+ 'size' => sample_size
+ }],
+
+ }.to_json
+ env['rack.input'] = StringIO.new(body)
end
- context 'when pushing an lfs object that already exists' do
+ describe 'is accessing public project' do
before do
public_project.lfs_objects << lfs_object
end
- it "responds with status 200 and links the object to the project" do
- response_body = lfs_router_auth.try_call.last
- response = ActiveSupport::JSON.decode(response_body.first)
+ it 'responds with status 200 and href to download' do
+ response = lfs_router_public_noauth.try_call
+ expect(response.first).to eq(200)
+ response_body = ActiveSupport::JSON.decode(response.last.first)
- expect(response['objects']).to be_kind_of(Array)
- expect(response['objects'].first['oid']).to eq(sample_oid)
- expect(response['objects'].first['size']).to eq(sample_size)
- expect(lfs_object.projects.pluck(:id)).to_not include(project.id)
- expect(lfs_object.projects.pluck(:id)).to include(public_project.id)
- expect(response['objects'].first).to have_key('_links')
+ expect(response_body).to eq('objects' => [
+ { 'oid' => sample_oid,
+ 'size' => sample_size,
+ 'actions' => {
+ 'download' => {
+ 'href' => "#{public_project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
+ 'header' => {}
+ }
+ }
+ }])
end
end
- context 'when pushing a lfs object that does not exist' do
+ describe 'is accessing non-public project' do
before do
- body = {
- 'objects' => [{
- 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
- 'size' => 1575078
- }]
- }.to_json
- env['rack.input'] = StringIO.new(body)
+ project.lfs_objects << lfs_object
end
- it "responds with status 200 and upload hypermedia link" do
- response = lfs_router_auth.try_call
- expect(response.first).to eq(200)
-
- response_body = ActiveSupport::JSON.decode(response.last.first)
- expect(response_body['objects']).to be_kind_of(Array)
- expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897")
- expect(response_body['objects'].first['size']).to eq(1575078)
- expect(lfs_object.projects.pluck(:id)).not_to include(project.id)
- expect(response_body['objects'].first['_links']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078")
- expect(response_body['objects'].first['_links']['upload']['header']).to eq("Authorization" => @auth)
+ it 'responds with authorization required' do
+ expect(lfs_router_noauth.try_call.first).to eq(401)
end
end
+ end
+ end
- context 'when pushing one new and one existing lfs object' do
+ describe 'upload' do
+ describe 'when user is authenticated' do
+ before do
+ body = { 'operation' => 'upload',
+ 'objects' => [
+ { 'oid' => sample_oid,
+ 'size' => sample_size
+ }]
+ }.to_json
+ env['rack.input'] = StringIO.new(body)
+ end
+
+ describe 'when user has project push access' do
before do
- body = {
- 'objects' => [
- { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
- 'size' => 1575078
- },
- { 'oid' => sample_oid,
- 'size' => sample_size
- }
- ]
- }.to_json
- env['rack.input'] = StringIO.new(body)
- public_project.lfs_objects << lfs_object
+ @auth = authorize(user)
+ env["HTTP_AUTHORIZATION"] = @auth
+ project.team << [user, :developer]
end
- it "responds with status 200 with upload hypermedia link for the new object" do
- response = lfs_router_auth.try_call
- expect(response.first).to eq(200)
+ context 'when pushing an lfs object that already exists' do
+ before do
+ public_project.lfs_objects << lfs_object
+ end
- response_body = ActiveSupport::JSON.decode(response.last.first)
- expect(response_body['objects']).to be_kind_of(Array)
+ it "responds with status 200 and links the object to the project" do
+ response_body = lfs_router_auth.try_call.last
+ response = ActiveSupport::JSON.decode(response_body.first)
+ expect(response['objects']).to be_kind_of(Array)
+ expect(response['objects'].first['oid']).to eq(sample_oid)
+ expect(response['objects'].first['size']).to eq(sample_size)
+ expect(lfs_object.projects.pluck(:id)).to_not include(project.id)
+ expect(lfs_object.projects.pluck(:id)).to include(public_project.id)
+ expect(response['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}")
+ expect(response['objects'].first['actions']['upload']['header']).to eq('Authorization' => @auth)
+ end
+ end
- expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897")
- expect(response_body['objects'].first['size']).to eq(1575078)
- expect(response_body['objects'].first['_links']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078")
- expect(response_body['objects'].first['_links']['upload']['header']).to eq("Authorization" => @auth)
+ context 'when pushing a lfs object that does not exist' do
+ before do
+ body = { 'operation' => 'upload',
+ 'objects' => [
+ { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+ 'size' => 1575078
+ }]
+ }.to_json
+ env['rack.input'] = StringIO.new(body)
+ end
- expect(response_body['objects'].last['oid']).to eq(sample_oid)
- expect(response_body['objects'].last['size']).to eq(sample_size)
- expect(lfs_object.projects.pluck(:id)).to_not include(project.id)
- expect(lfs_object.projects.pluck(:id)).to include(public_project.id)
- expect(response_body['objects'].last).to have_key('_links')
+ it "responds with status 200 and upload hypermedia link" do
+ response = lfs_router_auth.try_call
+ expect(response.first).to eq(200)
+
+ response_body = ActiveSupport::JSON.decode(response.last.first)
+ expect(response_body['objects']).to be_kind_of(Array)
+ expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897")
+ expect(response_body['objects'].first['size']).to eq(1575078)
+ expect(lfs_object.projects.pluck(:id)).not_to include(project.id)
+ expect(response_body['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078")
+ expect(response_body['objects'].first['actions']['upload']['header']).to eq('Authorization' => @auth)
+ end
+ end
+
+ context 'when pushing one new and one existing lfs object' do
+ before do
+ body = { 'operation' => 'upload',
+ 'objects' => [
+ { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+ 'size' => 1575078
+ },
+ { 'oid' => sample_oid,
+ 'size' => sample_size
+ }
+ ]
+ }.to_json
+ env['rack.input'] = StringIO.new(body)
+ project.lfs_objects << lfs_object
+ end
+
+ it "responds with status 200 with upload hypermedia link for the new object" do
+ response = lfs_router_auth.try_call
+ expect(response.first).to eq(200)
+
+ response_body = ActiveSupport::JSON.decode(response.last.first)
+ expect(response_body['objects']).to be_kind_of(Array)
+
+ expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897")
+ expect(response_body['objects'].first['size']).to eq(1575078)
+ expect(response_body['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078")
+ expect(response_body['objects'].first['actions']['upload']['header']).to eq("Authorization" => @auth)
+
+ expect(response_body['objects'].last['oid']).to eq(sample_oid)
+ expect(response_body['objects'].last['size']).to eq(sample_size)
+ expect(response_body['objects'].last).to_not have_key('actions')
+ end
end
end
- end
- context 'when user does not have push access' do
- it 'responds with 403' do
- expect(lfs_router_auth.try_call.first).to eq(403)
+ context 'when user does not have push access' do
+ it 'responds with 403' do
+ expect(lfs_router_auth.try_call.first).to eq(403)
+ end
end
end
- end
- context 'when user is not authenticated' do
- context 'when user has push access' do
+ context 'when user is not authenticated' do
before do
- project.team << [user, :master]
+ env['rack.input'] = StringIO.new(
+ { 'objects' => [], 'operation' => 'upload' }.to_json
+ )
end
- it "responds with status 401" do
- expect(lfs_router_public_noauth.try_call.first).to eq(401)
+ context 'when user has push access' do
+ before do
+ project.team << [user, :master]
+ end
+
+ it "responds with status 401" do
+ expect(lfs_router_public_noauth.try_call.first).to eq(401)
+ end
end
- end
- context 'when user does not have push access' do
- it "responds with status 401" do
- expect(lfs_router_public_noauth.try_call.first).to eq(401)
+ context 'when user does not have push access' do
+ it "responds with status 401" do
+ expect(lfs_router_public_noauth.try_call.first).to eq(401)
+ end
end
end
end
+
+ describe 'unsupported' do
+ before do
+ body = { 'operation' => 'other',
+ 'objects' => [
+ { 'oid' => sample_oid,
+ 'size' => sample_size
+ }]
+ }.to_json
+ env['rack.input'] = StringIO.new(body)
+ end
+
+ it 'responds with status 404' do
+ expect(lfs_router_public_noauth.try_call.first).to eq(404)
+ end
+ end
end
describe 'when pushing a lfs object' do
diff --git a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
index e5b8d723fe5..9ce63f9af46 100644
--- a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
@@ -5,11 +5,11 @@ module Gitlab::Markdown
include FilterSpecHelper
let(:project) { create(:project, :public) }
- let(:commit1) { project.commit }
- let(:commit2) { project.commit("HEAD~2") }
+ let(:commit1) { project.commit("HEAD~2") }
+ let(:commit2) { project.commit }
- let(:range) { CommitRange.new("#{commit1.id}...#{commit2.id}") }
- let(:range2) { CommitRange.new("#{commit1.id}..#{commit2.id}") }
+ let(:range) { CommitRange.new("#{commit1.id}...#{commit2.id}", project) }
+ let(:range2) { CommitRange.new("#{commit1.id}..#{commit2.id}", project) }
it 'requires project context' do
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
@@ -18,7 +18,7 @@ module Gitlab::Markdown
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Commit Range #{range.to_reference}</#{elem}>"
- expect(filter(act).to_html).to eq exp
+ expect(reference_filter(act).to_html).to eq exp
end
end
@@ -27,14 +27,14 @@ module Gitlab::Markdown
let(:reference2) { range2.to_reference }
it 'links to a valid two-dot reference' do
- doc = filter("See #{reference2}")
+ doc = reference_filter("See #{reference2}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_compare_url(project.namespace, project, range2.to_param)
end
it 'links to a valid three-dot reference' do
- doc = filter("See #{reference}")
+ doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_compare_url(project.namespace, project, range.to_param)
@@ -46,14 +46,14 @@ module Gitlab::Markdown
exp = commit1.short_id + '...' + commit2.short_id
- expect(filter("See #{reference}").css('a').first.text).to eq exp
- expect(filter("See #{reference2}").css('a').first.text).to eq exp
+ expect(reference_filter("See #{reference}").css('a').first.text).to eq exp
+ expect(reference_filter("See #{reference2}").css('a').first.text).to eq exp
end
it 'links with adjacent text' do
- doc = filter("See (#{reference}.)")
+ doc = reference_filter("See (#{reference}.)")
- exp = Regexp.escape(range.to_s)
+ exp = Regexp.escape(range.reference_link_text)
expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
end
@@ -62,21 +62,22 @@ module Gitlab::Markdown
expect(project).to receive(:valid_repo?).and_return(true)
expect(project.repository).to receive(:commit).with(commit1.id.reverse)
- expect(filter(act).to_html).to eq exp
+ expect(project.repository).to receive(:commit).with(commit2.id)
+ expect(reference_filter(act).to_html).to eq exp
end
it 'includes a title attribute' do
- doc = filter("See #{reference}")
+ doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('title')).to eq range.reference_title
end
it 'includes default classes' do
- doc = filter("See #{reference}")
+ doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range'
end
it 'includes a data-project attribute' do
- doc = filter("See #{reference}")
+ doc = reference_filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-project')
@@ -84,15 +85,15 @@ module Gitlab::Markdown
end
it 'includes a data-commit-range attribute' do
- doc = filter("See #{reference}")
+ doc = reference_filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-commit-range')
- expect(link.attr('data-commit-range')).to eq range.to_reference
+ expect(link.attr('data-commit-range')).to eq range.to_s
end
it 'supports an :only_path option' do
- doc = filter("See #{reference}", only_path: true)
+ doc = reference_filter("See #{reference}", only_path: true)
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
@@ -115,25 +116,63 @@ module Gitlab::Markdown
end
it 'links to a valid reference' do
- doc = filter("See #{reference}")
+ doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param)
end
it 'links with adjacent text' do
- doc = filter("Fixed (#{reference}.)")
+ doc = reference_filter("Fixed (#{reference}.)")
- exp = Regexp.escape("#{project2.to_reference}@#{range.to_s}")
+ exp = Regexp.escape("#{project2.to_reference}@#{range.reference_link_text}")
expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
end
it 'ignores invalid commit IDs on the referenced project' do
exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}"
- expect(filter(act).to_html).to eq exp
+ expect(reference_filter(act).to_html).to eq exp
exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}"
- expect(filter(act).to_html).to eq exp
+ expect(reference_filter(act).to_html).to eq exp
+ end
+
+ it 'adds to the results hash' do
+ result = reference_pipeline_result("See #{reference}")
+ expect(result[:references][:commit_range]).not_to be_empty
+ end
+ end
+
+ context 'cross-project URL reference' do
+ let(:namespace) { create(:namespace, name: 'cross-reference') }
+ let(:project2) { create(:project, :public, namespace: namespace) }
+ let(:range) { CommitRange.new("#{commit1.id}...master", project) }
+ let(:reference) { urls.namespace_project_compare_url(project2.namespace, project2, from: commit1.id, to: 'master') }
+
+ before do
+ range.project = project2
+ end
+
+ it 'links to a valid reference' do
+ doc = reference_filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).
+ to eq reference
+ end
+
+ it 'links with adjacent text' do
+ doc = reference_filter("Fixed (#{reference}.)")
+
+ exp = Regexp.escape(range.reference_link_text(project))
+ expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
+ end
+
+ it 'ignores invalid commit IDs on the referenced project' do
+ exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}"
+ expect(reference_filter(act).to_html).to eq exp
+
+ exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}"
+ expect(reference_filter(act).to_html).to eq exp
end
it 'adds to the results hash' do
diff --git a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
index d080efbf3d4..462a41b4756 100644
--- a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
@@ -14,7 +14,7 @@ module Gitlab::Markdown
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Commit #{commit.id}</#{elem}>"
- expect(filter(act).to_html).to eq exp
+ expect(reference_filter(act).to_html).to eq exp
end
end
@@ -24,7 +24,7 @@ module Gitlab::Markdown
# Let's test a variety of commit SHA sizes just to be paranoid
[6, 8, 12, 18, 20, 32, 40].each do |size|
it "links to a valid reference of #{size} characters" do
- doc = filter("See #{reference[0...size]}")
+ doc = reference_filter("See #{reference[0...size]}")
expect(doc.css('a').first.text).to eq commit.short_id
expect(doc.css('a').first.attr('href')).
@@ -33,15 +33,15 @@ module Gitlab::Markdown
end
it 'always uses the short ID as the link text' do
- doc = filter("See #{commit.id}")
+ doc = reference_filter("See #{commit.id}")
expect(doc.text).to eq "See #{commit.short_id}"
- doc = filter("See #{commit.id[0...6]}")
+ doc = reference_filter("See #{commit.id[0...6]}")
expect(doc.text).to eq "See #{commit.short_id}"
end
it 'links with adjacent text' do
- doc = filter("See (#{reference}.)")
+ doc = reference_filter("See (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{commit.short_id}<\/a>\.\)/)
end
@@ -51,28 +51,28 @@ module Gitlab::Markdown
expect(project).to receive(:valid_repo?).and_return(true)
expect(project.repository).to receive(:commit).with(invalid)
- expect(filter(act).to_html).to eq exp
+ expect(reference_filter(act).to_html).to eq exp
end
it 'includes a title attribute' do
- doc = filter("See #{reference}")
+ doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('title')).to eq commit.link_title
end
it 'escapes the title attribute' do
allow_any_instance_of(Commit).to receive(:title).and_return(%{"></a>whatever<a title="})
- doc = filter("See #{reference}")
+ doc = reference_filter("See #{reference}")
expect(doc.text).to eq "See #{commit.short_id}"
end
it 'includes default classes' do
- doc = filter("See #{reference}")
+ doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit'
end
it 'includes a data-project attribute' do
- doc = filter("See #{reference}")
+ doc = reference_filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-project')
@@ -80,7 +80,7 @@ module Gitlab::Markdown
end
it 'includes a data-commit attribute' do
- doc = filter("See #{reference}")
+ doc = reference_filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-commit')
@@ -88,7 +88,7 @@ module Gitlab::Markdown
end
it 'supports an :only_path context' do
- doc = filter("See #{reference}", only_path: true)
+ doc = reference_filter("See #{reference}", only_path: true)
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
@@ -108,14 +108,14 @@ module Gitlab::Markdown
let(:reference) { commit.to_reference(project) }
it 'links to a valid reference' do
- doc = filter("See #{reference}")
+ doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id)
end
it 'links with adjacent text' do
- doc = filter("Fixed (#{reference}.)")
+ doc = reference_filter("Fixed (#{reference}.)")
exp = Regexp.escape(project2.to_reference)
expect(doc.to_html).to match(/\(<a.+>#{exp}@#{commit.short_id}<\/a>\.\)/)
@@ -123,7 +123,37 @@ module Gitlab::Markdown
it 'ignores invalid commit IDs on the referenced project' do
exp = act = "Committed #{invalidate_reference(reference)}"
- expect(filter(act).to_html).to eq exp
+ expect(reference_filter(act).to_html).to eq exp
+ end
+
+ it 'adds to the results hash' do
+ result = reference_pipeline_result("See #{reference}")
+ expect(result[:references][:commit]).not_to be_empty
+ end
+ end
+
+ context 'cross-project URL reference' do
+ let(:namespace) { create(:namespace, name: 'cross-reference') }
+ let(:project2) { create(:project, :public, namespace: namespace) }
+ let(:commit) { project2.commit }
+ let(:reference) { urls.namespace_project_commit_url(project2.namespace, project2, commit.id) }
+
+ it 'links to a valid reference' do
+ doc = reference_filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).
+ to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id)
+ end
+
+ it 'links with adjacent text' do
+ doc = reference_filter("Fixed (#{reference}.)")
+
+ expect(doc.to_html).to match(/\(<a.+>#{commit.reference_link_text(project)}<\/a>\.\)/)
+ end
+
+ it 'ignores invalid commit IDs on the referenced project' do
+ act = "Committed #{invalidate_reference(reference)}"
+ expect(reference_filter(act).to_html).to match(/<a.+>#{Regexp.escape(invalidate_reference(reference))}<\/a>/)
end
it 'adds to the results hash' do
diff --git a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
index 94c80ae6611..078ff3ed4b2 100644
--- a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
@@ -18,7 +18,7 @@ module Gitlab::Markdown
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Issue #{issue.to_reference}</#{elem}>"
- expect(filter(act).to_html).to eq exp
+ expect(reference_filter(act).to_html).to eq exp
end
end
@@ -29,18 +29,18 @@ module Gitlab::Markdown
expect(project).to receive(:get_issue).with(issue.iid).and_return(nil)
exp = act = "Issue #{reference}"
- expect(filter(act).to_html).to eq exp
+ expect(reference_filter(act).to_html).to eq exp
end
it 'links to a valid reference' do
- doc = filter("Fixed #{reference}")
+ doc = reference_filter("Fixed #{reference}")
expect(doc.css('a').first.attr('href')).
to eq helper.url_for_issue(issue.iid, project)
end
it 'links with adjacent text' do
- doc = filter("Fixed (#{reference}.)")
+ doc = reference_filter("Fixed (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
@@ -48,28 +48,28 @@ module Gitlab::Markdown
invalid = invalidate_reference(reference)
exp = act = "Fixed #{invalid}"
- expect(filter(act).to_html).to eq exp
+ expect(reference_filter(act).to_html).to eq exp
end
it 'includes a title attribute' do
- doc = filter("Issue #{reference}")
+ doc = reference_filter("Issue #{reference}")
expect(doc.css('a').first.attr('title')).to eq "Issue: #{issue.title}"
end
it 'escapes the title attribute' do
issue.update_attribute(:title, %{"></a>whatever<a title="})
- doc = filter("Issue #{reference}")
+ doc = reference_filter("Issue #{reference}")
expect(doc.text).to eq "Issue #{reference}"
end
it 'includes default classes' do
- doc = filter("Issue #{reference}")
+ doc = reference_filter("Issue #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
end
it 'includes a data-project attribute' do
- doc = filter("Issue #{reference}")
+ doc = reference_filter("Issue #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-project')
@@ -77,7 +77,7 @@ module Gitlab::Markdown
end
it 'includes a data-issue attribute' do
- doc = filter("See #{reference}")
+ doc = reference_filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-issue')
@@ -85,7 +85,7 @@ module Gitlab::Markdown
end
it 'supports an :only_path context' do
- doc = filter("Issue #{reference}", only_path: true)
+ doc = reference_filter("Issue #{reference}", only_path: true)
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
@@ -109,25 +109,97 @@ module Gitlab::Markdown
with(issue.iid).and_return(nil)
exp = act = "Issue #{reference}"
- expect(filter(act).to_html).to eq exp
+ expect(reference_filter(act).to_html).to eq exp
end
it 'links to a valid reference' do
- doc = filter("See #{reference}")
+ doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq helper.url_for_issue(issue.iid, project2)
end
it 'links with adjacent text' do
- doc = filter("Fixed (#{reference}.)")
+ doc = reference_filter("Fixed (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
it 'ignores invalid issue IDs on the referenced project' do
exp = act = "Fixed #{invalidate_reference(reference)}"
- expect(filter(act).to_html).to eq exp
+ expect(reference_filter(act).to_html).to eq exp
+ end
+
+ it 'adds to the results hash' do
+ result = reference_pipeline_result("Fixed #{reference}")
+ expect(result[:references][:issue]).to eq [issue]
+ end
+ end
+
+ context 'cross-project URL reference' do
+ let(:namespace) { create(:namespace, name: 'cross-reference') }
+ let(:project2) { create(:empty_project, :public, namespace: namespace) }
+ let(:issue) { create(:issue, project: project2) }
+ let(:reference) { helper.url_for_issue(issue.iid, project2) + "#note_123" }
+
+ it 'links to a valid reference' do
+ doc = reference_filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).
+ to eq reference
+ end
+
+ it 'links with adjacent text' do
+ doc = reference_filter("Fixed (#{reference}.)")
+ expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(issue.to_reference(project))} \(comment 123\)<\/a>\.\)/)
+ end
+
+ it 'adds to the results hash' do
+ result = reference_pipeline_result("Fixed #{reference}")
+ expect(result[:references][:issue]).to eq [issue]
+ end
+ end
+
+ context 'cross-project reference in link href' do
+ let(:namespace) { create(:namespace, name: 'cross-reference') }
+ let(:project2) { create(:empty_project, :public, namespace: namespace) }
+ let(:issue) { create(:issue, project: project2) }
+ let(:reference) { %Q{<a href="#{issue.to_reference(project)}">Reference</a>} }
+
+ it 'links to a valid reference' do
+ doc = reference_filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).
+ to eq helper.url_for_issue(issue.iid, project2)
+ end
+
+ it 'links with adjacent text' do
+ doc = reference_filter("Fixed (#{reference}.)")
+ expect(doc.to_html).to match(/\(<a.+>Reference<\/a>\.\)/)
+ end
+
+ it 'adds to the results hash' do
+ result = reference_pipeline_result("Fixed #{reference}")
+ expect(result[:references][:issue]).to eq [issue]
+ end
+ end
+
+ context 'cross-project URL in link href' do
+ let(:namespace) { create(:namespace, name: 'cross-reference') }
+ let(:project2) { create(:empty_project, :public, namespace: namespace) }
+ let(:issue) { create(:issue, project: project2) }
+ let(:reference) { %Q{<a href="#{helper.url_for_issue(issue.iid, project2) + "#note_123"}">Reference</a>} }
+
+ it 'links to a valid reference' do
+ doc = reference_filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).
+ to eq helper.url_for_issue(issue.iid, project2) + "#note_123"
+ end
+
+ it 'links with adjacent text' do
+ doc = reference_filter("Fixed (#{reference}.)")
+ expect(doc.to_html).to match(/\(<a.+>Reference<\/a>\.\)/)
end
it 'adds to the results hash' do
diff --git a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
index fc21b65a843..ef6dd524aba 100644
--- a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
@@ -16,17 +16,17 @@ module Gitlab::Markdown
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Label #{reference}</#{elem}>"
- expect(filter(act).to_html).to eq exp
+ expect(reference_filter(act).to_html).to eq exp
end
end
it 'includes default classes' do
- doc = filter("Label #{reference}")
+ doc = reference_filter("Label #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label'
end
it 'includes a data-project attribute' do
- doc = filter("Label #{reference}")
+ doc = reference_filter("Label #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-project')
@@ -34,7 +34,7 @@ module Gitlab::Markdown
end
it 'includes a data-label attribute' do
- doc = filter("See #{reference}")
+ doc = reference_filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-label')
@@ -42,7 +42,7 @@ module Gitlab::Markdown
end
it 'supports an :only_path context' do
- doc = filter("Label #{reference}", only_path: true)
+ doc = reference_filter("Label #{reference}", only_path: true)
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
@@ -56,33 +56,33 @@ module Gitlab::Markdown
describe 'label span element' do
it 'includes default classes' do
- doc = filter("Label #{reference}")
+ doc = reference_filter("Label #{reference}")
expect(doc.css('a span').first.attr('class')).to eq 'label color-label'
end
it 'includes a style attribute' do
- doc = filter("Label #{reference}")
+ doc = reference_filter("Label #{reference}")
expect(doc.css('a span').first.attr('style')).to match(/\Abackground-color: #\h{6}; color: #\h{6}\z/)
end
end
context 'Integer-based references' do
it 'links to a valid reference' do
- doc = filter("See #{reference}")
+ doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.
namespace_project_issues_url(project.namespace, project, label_name: label.name)
end
it 'links with adjacent text' do
- doc = filter("Label (#{reference}.)")
+ doc = reference_filter("Label (#{reference}.)")
expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
end
it 'ignores invalid label IDs' do
exp = act = "Label #{invalidate_reference(reference)}"
- expect(filter(act).to_html).to eq exp
+ expect(reference_filter(act).to_html).to eq exp
end
end
@@ -91,7 +91,7 @@ module Gitlab::Markdown
let(:reference) { "#{Label.reference_prefix}#{label.name}" }
it 'links to a valid reference' do
- doc = filter("See #{reference}")
+ doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.
namespace_project_issues_url(project.namespace, project, label_name: label.name)
@@ -99,14 +99,14 @@ module Gitlab::Markdown
end
it 'links with adjacent text' do
- doc = filter("Label (#{reference}.)")
+ doc = reference_filter("Label (#{reference}.)")
expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
end
it 'ignores invalid label names' do
exp = act = "Label #{Label.reference_prefix}#{label.name.reverse}"
- expect(filter(act).to_html).to eq exp
+ expect(reference_filter(act).to_html).to eq exp
end
end
@@ -115,7 +115,7 @@ module Gitlab::Markdown
let(:reference) { label.to_reference(:name) }
it 'links to a valid reference' do
- doc = filter("See #{reference}")
+ doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.
namespace_project_issues_url(project.namespace, project, label_name: label.name)
@@ -123,21 +123,58 @@ module Gitlab::Markdown
end
it 'links with adjacent text' do
- doc = filter("Label (#{reference}.)")
+ doc = reference_filter("Label (#{reference}.)")
expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
end
it 'ignores invalid label names' do
exp = act = %(Label #{Label.reference_prefix}"#{label.name.reverse}")
- expect(filter(act).to_html).to eq exp
+ expect(reference_filter(act).to_html).to eq exp
end
end
describe 'edge cases' do
it 'gracefully handles non-references matching the pattern' do
exp = act = '(format nil "~0f" 3.0) ; 3.0'
- expect(filter(act).to_html).to eq exp
+ expect(reference_filter(act).to_html).to eq exp
+ end
+ end
+
+ describe 'referencing a label in a link href' do
+ let(:reference) { %Q{<a href="#{label.to_reference}">Label</a>} }
+
+ it 'links to a valid reference' do
+ doc = reference_filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).to eq urls.
+ namespace_project_issues_url(project.namespace, project, label_name: label.name)
+ end
+
+ it 'links with adjacent text' do
+ doc = reference_filter("Label (#{reference}.)")
+ expect(doc.to_html).to match(%r(\(<a.+>Label</a>\.\)))
+ end
+
+ it 'includes a data-project attribute' do
+ doc = reference_filter("Label #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-project')
+ expect(link.attr('data-project')).to eq project.id.to_s
+ end
+
+ it 'includes a data-label attribute' do
+ doc = reference_filter("See #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-label')
+ expect(link.attr('data-label')).to eq label.id.to_s
+ end
+
+ it 'adds to the results hash' do
+ result = reference_pipeline_result("Label #{reference}")
+ expect(result[:references][:label]).to eq [label]
end
end
end
diff --git a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
index 3ef6cdfff33..4a232051127 100644
--- a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
@@ -14,7 +14,7 @@ module Gitlab::Markdown
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Merge #{merge.to_reference}</#{elem}>"
- expect(filter(act).to_html).to eq exp
+ expect(reference_filter(act).to_html).to eq exp
end
end
@@ -22,42 +22,42 @@ module Gitlab::Markdown
let(:reference) { merge.to_reference }
it 'links to a valid reference' do
- doc = filter("See #{reference}")
+ doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.
namespace_project_merge_request_url(project.namespace, project, merge)
end
it 'links with adjacent text' do
- doc = filter("Merge (#{reference}.)")
+ doc = reference_filter("Merge (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
it 'ignores invalid merge IDs' do
exp = act = "Merge #{invalidate_reference(reference)}"
- expect(filter(act).to_html).to eq exp
+ expect(reference_filter(act).to_html).to eq exp
end
it 'includes a title attribute' do
- doc = filter("Merge #{reference}")
+ doc = reference_filter("Merge #{reference}")
expect(doc.css('a').first.attr('title')).to eq "Merge Request: #{merge.title}"
end
it 'escapes the title attribute' do
merge.update_attribute(:title, %{"></a>whatever<a title="})
- doc = filter("Merge #{reference}")
+ doc = reference_filter("Merge #{reference}")
expect(doc.text).to eq "Merge #{reference}"
end
it 'includes default classes' do
- doc = filter("Merge #{reference}")
+ doc = reference_filter("Merge #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request'
end
it 'includes a data-project attribute' do
- doc = filter("Merge #{reference}")
+ doc = reference_filter("Merge #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-project')
@@ -65,7 +65,7 @@ module Gitlab::Markdown
end
it 'includes a data-merge-request attribute' do
- doc = filter("See #{reference}")
+ doc = reference_filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-merge-request')
@@ -73,7 +73,7 @@ module Gitlab::Markdown
end
it 'supports an :only_path context' do
- doc = filter("Merge #{reference}", only_path: true)
+ doc = reference_filter("Merge #{reference}", only_path: true)
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
@@ -89,26 +89,50 @@ module Gitlab::Markdown
context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:project, :public, namespace: namespace) }
- let(:merge) { create(:merge_request, source_project: project2) }
+ let(:merge) { create(:merge_request, source_project: project2, target_project: project2) }
let(:reference) { merge.to_reference(project) }
it 'links to a valid reference' do
- doc = filter("See #{reference}")
+ doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_merge_request_url(project2.namespace,
- project, merge)
+ project2, merge)
end
it 'links with adjacent text' do
- doc = filter("Merge (#{reference}.)")
+ doc = reference_filter("Merge (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
it 'ignores invalid merge IDs on the referenced project' do
exp = act = "Merge #{invalidate_reference(reference)}"
- expect(filter(act).to_html).to eq exp
+ expect(reference_filter(act).to_html).to eq exp
+ end
+
+ it 'adds to the results hash' do
+ result = reference_pipeline_result("Merge #{reference}")
+ expect(result[:references][:merge_request]).to eq [merge]
+ end
+ end
+
+ context 'cross-project URL reference' do
+ let(:namespace) { create(:namespace, name: 'cross-reference') }
+ let(:project2) { create(:project, :public, namespace: namespace) }
+ let(:merge) { create(:merge_request, source_project: project2, target_project: project2) }
+ let(:reference) { urls.namespace_project_merge_request_url(project2.namespace, project2, merge) + '/diffs#note_123' }
+
+ it 'links to a valid reference' do
+ doc = reference_filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).
+ to eq reference
+ end
+
+ it 'links with adjacent text' do
+ doc = reference_filter("Merge (#{reference}.)")
+ expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(merge.to_reference(project))} \(diffs, comment 123\)<\/a>\.\)/)
end
it 'adds to the results hash' do
diff --git a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
index 9d9652dba46..3a9acc9d6d4 100644
--- a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
@@ -15,48 +15,48 @@ module Gitlab::Markdown
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Snippet #{reference}</#{elem}>"
- expect(filter(act).to_html).to eq exp
+ expect(reference_filter(act).to_html).to eq exp
end
end
context 'internal reference' do
it 'links to a valid reference' do
- doc = filter("See #{reference}")
+ doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.
namespace_project_snippet_url(project.namespace, project, snippet)
end
it 'links with adjacent text' do
- doc = filter("Snippet (#{reference}.)")
+ doc = reference_filter("Snippet (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
it 'ignores invalid snippet IDs' do
exp = act = "Snippet #{invalidate_reference(reference)}"
- expect(filter(act).to_html).to eq exp
+ expect(reference_filter(act).to_html).to eq exp
end
it 'includes a title attribute' do
- doc = filter("Snippet #{reference}")
+ doc = reference_filter("Snippet #{reference}")
expect(doc.css('a').first.attr('title')).to eq "Snippet: #{snippet.title}"
end
it 'escapes the title attribute' do
snippet.update_attribute(:title, %{"></a>whatever<a title="})
- doc = filter("Snippet #{reference}")
+ doc = reference_filter("Snippet #{reference}")
expect(doc.text).to eq "Snippet #{reference}"
end
it 'includes default classes' do
- doc = filter("Snippet #{reference}")
+ doc = reference_filter("Snippet #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet'
end
it 'includes a data-project attribute' do
- doc = filter("Snippet #{reference}")
+ doc = reference_filter("Snippet #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-project')
@@ -64,7 +64,7 @@ module Gitlab::Markdown
end
it 'includes a data-snippet attribute' do
- doc = filter("See #{reference}")
+ doc = reference_filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-snippet')
@@ -72,7 +72,7 @@ module Gitlab::Markdown
end
it 'supports an :only_path context' do
- doc = filter("Snippet #{reference}", only_path: true)
+ doc = reference_filter("Snippet #{reference}", only_path: true)
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
@@ -92,21 +92,51 @@ module Gitlab::Markdown
let(:reference) { snippet.to_reference(project) }
it 'links to a valid reference' do
- doc = filter("See #{reference}")
+ doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
end
it 'links with adjacent text' do
- doc = filter("See (#{reference}.)")
+ doc = reference_filter("See (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
it 'ignores invalid snippet IDs on the referenced project' do
exp = act = "See #{invalidate_reference(reference)}"
- expect(filter(act).to_html).to eq exp
+ expect(reference_filter(act).to_html).to eq exp
+ end
+
+ it 'adds to the results hash' do
+ result = reference_pipeline_result("Snippet #{reference}")
+ expect(result[:references][:snippet]).to eq [snippet]
+ end
+ end
+
+ context 'cross-project URL reference' do
+ let(:namespace) { create(:namespace, name: 'cross-reference') }
+ let(:project2) { create(:empty_project, :public, namespace: namespace) }
+ let(:snippet) { create(:project_snippet, project: project2) }
+ let(:reference) { urls.namespace_project_snippet_url(project2.namespace, project2, snippet) }
+
+ it 'links to a valid reference' do
+ doc = reference_filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).
+ to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
+ end
+
+ it 'links with adjacent text' do
+ doc = reference_filter("See (#{reference}.)")
+ expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(snippet.to_reference(project))}<\/a>\.\)/)
+ end
+
+ it 'ignores invalid snippet IDs on the referenced project' do
+ act = "See #{invalidate_reference(reference)}"
+
+ expect(reference_filter(act).to_html).to match(/<a.+>#{Regexp.escape(invalidate_reference(reference))}<\/a>/)
end
it 'adds to the results hash' do
diff --git a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
index d9e0d7c42db..25379f0670e 100644
--- a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
@@ -14,13 +14,13 @@ module Gitlab::Markdown
it 'ignores invalid users' do
exp = act = "Hey #{invalidate_reference(reference)}"
- expect(filter(act).to_html).to eq(exp)
+ expect(reference_filter(act).to_html).to eq(exp)
end
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Hey #{reference}</#{elem}>"
- expect(filter(act).to_html).to eq exp
+ expect(reference_filter(act).to_html).to eq exp
end
end
@@ -32,7 +32,7 @@ module Gitlab::Markdown
end
it 'supports a special @all mention' do
- doc = filter("Hey #{reference}")
+ doc = reference_filter("Hey #{reference}")
expect(doc.css('a').length).to eq 1
expect(doc.css('a').first.attr('href'))
.to eq urls.namespace_project_url(project.namespace, project)
@@ -46,26 +46,26 @@ module Gitlab::Markdown
context 'mentioning a user' do
it 'links to a User' do
- doc = filter("Hey #{reference}")
+ doc = reference_filter("Hey #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.user_url(user)
end
it 'links to a User with a period' do
user = create(:user, name: 'alphA.Beta')
- doc = filter("Hey #{user.to_reference}")
+ doc = reference_filter("Hey #{user.to_reference}")
expect(doc.css('a').length).to eq 1
end
it 'links to a User with an underscore' do
user = create(:user, name: 'ping_pong_king')
- doc = filter("Hey #{user.to_reference}")
+ doc = reference_filter("Hey #{user.to_reference}")
expect(doc.css('a').length).to eq 1
end
it 'includes a data-user attribute' do
- doc = filter("Hey #{reference}")
+ doc = reference_filter("Hey #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-user')
@@ -83,12 +83,12 @@ module Gitlab::Markdown
let(:reference) { group.to_reference }
it 'links to the Group' do
- doc = filter("Hey #{reference}")
+ doc = reference_filter("Hey #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.group_url(group)
end
it 'includes a data-group attribute' do
- doc = filter("Hey #{reference}")
+ doc = reference_filter("Hey #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-group')
@@ -102,21 +102,48 @@ module Gitlab::Markdown
end
it 'links with adjacent text' do
- doc = filter("Mention me (#{reference}.)")
+ doc = reference_filter("Mention me (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/)
end
it 'includes default classes' do
- doc = filter("Hey #{reference}")
+ doc = reference_filter("Hey #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member'
end
it 'supports an :only_path context' do
- doc = filter("Hey #{reference}", only_path: true)
+ doc = reference_filter("Hey #{reference}", only_path: true)
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
expect(link).to eq urls.user_path(user)
end
+
+ context 'referencing a user in a link href' do
+ let(:reference) { %Q{<a href="#{user.to_reference}">User</a>} }
+
+ it 'links to a User' do
+ doc = reference_filter("Hey #{reference}")
+ expect(doc.css('a').first.attr('href')).to eq urls.user_url(user)
+ end
+
+ it 'links with adjacent text' do
+ doc = reference_filter("Mention me (#{reference}.)")
+ expect(doc.to_html).to match(/\(<a.+>User<\/a>\.\)/)
+ end
+
+ it 'includes a data-user attribute' do
+ doc = reference_filter("Hey #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-user')
+ expect(link.attr('data-user')).to eq user.namespace.owner_id.to_s
+ end
+
+ it 'adds to the results hash' do
+ result = reference_pipeline_result("Hey #{reference}")
+ expect(result[:references][:user]).to eq [user]
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/sherlock/transaction_spec.rb b/spec/lib/gitlab/sherlock/transaction_spec.rb
index bb49fb65cf8..fb80c62c794 100644
--- a/spec/lib/gitlab/sherlock/transaction_spec.rb
+++ b/spec/lib/gitlab/sherlock/transaction_spec.rb
@@ -84,6 +84,19 @@ describe Gitlab::Sherlock::Transaction do
end
end
+ describe '#query_duration' do
+ it 'returns the total query duration in seconds' do
+ time = Time.now
+ query1 = Gitlab::Sherlock::Query.new('SELECT 1', time, time + 5)
+ query2 = Gitlab::Sherlock::Query.new('SELECT 2', time, time + 2)
+
+ transaction.queries << query1
+ transaction.queries << query2
+
+ expect(transaction.query_duration).to be_within(0.1).of(7.0)
+ end
+ end
+
describe '#to_param' do
it 'returns the transaction ID' do
expect(transaction.to_param).to eq(transaction.id)
diff --git a/spec/lib/gitlab/sql/union_spec.rb b/spec/lib/gitlab/sql/union_spec.rb
new file mode 100644
index 00000000000..9e1cd4419e0
--- /dev/null
+++ b/spec/lib/gitlab/sql/union_spec.rb
@@ -0,0 +1,16 @@
+require 'spec_helper'
+
+describe Gitlab::SQL::Union do
+ describe '#to_sql' do
+ it 'returns a String joining relations together using a UNION' do
+ rel1 = User.where(email: 'alice@example.com')
+ rel2 = User.where(email: 'bob@example.com')
+ union = described_class.new([rel1, rel2])
+
+ sql1 = rel1.reorder(nil).to_sql
+ sql2 = rel2.reorder(nil).to_sql
+
+ expect(union.to_sql).to eq("#{sql1}\nUNION\n#{sql2}")
+ end
+ end
+end
diff --git a/spec/lib/votes_spec.rb b/spec/lib/votes_spec.rb
deleted file mode 100644
index 39e5d054e62..00000000000
--- a/spec/lib/votes_spec.rb
+++ /dev/null
@@ -1,188 +0,0 @@
-require 'spec_helper'
-
-describe Issue, 'Votes' do
- let(:issue) { create(:issue) }
-
- describe "#upvotes" do
- it "with no notes has a 0/0 score" do
- expect(issue.upvotes).to eq(0)
- end
-
- it "should recognize non-+1 notes" do
- add_note "No +1 here"
- expect(issue.notes.size).to eq(1)
- expect(issue.notes.first.upvote?).to be_falsey
- expect(issue.upvotes).to eq(0)
- end
-
- it "should recognize a single +1 note" do
- add_note "+1 This is awesome"
- expect(issue.upvotes).to eq(1)
- end
-
- it 'should recognize multiple +1 notes' do
- add_note '+1 This is awesome', create(:user)
- add_note '+1 I want this', create(:user)
- expect(issue.upvotes).to eq(2)
- end
-
- it 'should not count 2 +1 votes from the same user' do
- add_note '+1 This is awesome'
- add_note '+1 I want this'
- expect(issue.upvotes).to eq(1)
- end
- end
-
- describe "#downvotes" do
- it "with no notes has a 0/0 score" do
- expect(issue.downvotes).to eq(0)
- end
-
- it "should recognize non--1 notes" do
- add_note "Almost got a -1"
- expect(issue.notes.size).to eq(1)
- expect(issue.notes.first.downvote?).to be_falsey
- expect(issue.downvotes).to eq(0)
- end
-
- it "should recognize a single -1 note" do
- add_note "-1 This is bad"
- expect(issue.downvotes).to eq(1)
- end
-
- it "should recognize multiple -1 notes" do
- add_note('-1 This is bad', create(:user))
- add_note('-1 Away with this', create(:user))
- expect(issue.downvotes).to eq(2)
- end
- end
-
- describe "#votes_count" do
- it "with no notes has a 0/0 score" do
- expect(issue.votes_count).to eq(0)
- end
-
- it "should recognize non notes" do
- add_note "No +1 here"
- expect(issue.notes.size).to eq(1)
- expect(issue.votes_count).to eq(0)
- end
-
- it "should recognize a single +1 note" do
- add_note "+1 This is awesome"
- expect(issue.votes_count).to eq(1)
- end
-
- it "should recognize a single -1 note" do
- add_note "-1 This is bad"
- expect(issue.votes_count).to eq(1)
- end
-
- it "should recognize multiple notes" do
- add_note('+1 This is awesome', create(:user))
- add_note('-1 This is bad', create(:user))
- add_note('+1 I want this', create(:user))
- expect(issue.votes_count).to eq(3)
- end
-
- it 'should not count 2 -1 votes from the same user' do
- add_note '-1 This is suspicious'
- add_note '-1 This is bad'
- expect(issue.votes_count).to eq(1)
- end
- end
-
- describe "#upvotes_in_percent" do
- it "with no notes has a 0% score" do
- expect(issue.upvotes_in_percent).to eq(0)
- end
-
- it "should count a single 1 note as 100%" do
- add_note "+1 This is awesome"
- expect(issue.upvotes_in_percent).to eq(100)
- end
-
- it 'should count multiple +1 notes as 100%' do
- add_note('+1 This is awesome', create(:user))
- add_note('+1 I want this', create(:user))
- expect(issue.upvotes_in_percent).to eq(100)
- end
-
- it 'should count fractions for multiple +1 and -1 notes correctly' do
- add_note('+1 This is awesome', create(:user))
- add_note('+1 I want this', create(:user))
- add_note('-1 This is bad', create(:user))
- add_note('+1 me too', create(:user))
- expect(issue.upvotes_in_percent).to eq(75)
- end
- end
-
- describe "#downvotes_in_percent" do
- it "with no notes has a 0% score" do
- expect(issue.downvotes_in_percent).to eq(0)
- end
-
- it "should count a single -1 note as 100%" do
- add_note "-1 This is bad"
- expect(issue.downvotes_in_percent).to eq(100)
- end
-
- it 'should count multiple -1 notes as 100%' do
- add_note('-1 This is bad', create(:user))
- add_note('-1 Away with this', create(:user))
- expect(issue.downvotes_in_percent).to eq(100)
- end
-
- it 'should count fractions for multiple +1 and -1 notes correctly' do
- add_note('+1 This is awesome', create(:user))
- add_note('+1 I want this', create(:user))
- add_note('-1 This is bad', create(:user))
- add_note('+1 me too', create(:user))
- expect(issue.downvotes_in_percent).to eq(25)
- end
- end
-
- describe '#filter_superceded_votes' do
-
- it 'should count a users vote only once amongst multiple votes' do
- add_note('-1 This needs work before I will accept it')
- add_note('+1 I want this', create(:user))
- add_note('+1 This is is awesome', create(:user))
- add_note('+1 this looks good now')
- add_note('+1 This is awesome', create(:user))
- add_note('+1 me too', create(:user))
- expect(issue.downvotes).to eq(0)
- expect(issue.upvotes).to eq(5)
- end
-
- it 'should count each users vote only once' do
- add_note '-1 This needs work before it will be accepted'
- add_note '+1 I like this'
- add_note '+1 I still like this'
- add_note '+1 I really like this'
- add_note '+1 Give me this now!!!!'
- expect(issue.downvotes).to eq(0)
- expect(issue.upvotes).to eq(1)
- end
-
- it 'should count a users vote only once without caring about comments' do
- add_note '-1 This needs work before it will be accepted'
- add_note 'Comment 1'
- add_note 'Another comment'
- add_note '+1 vote'
- add_note 'final comment'
- expect(issue.downvotes).to eq(0)
- expect(issue.upvotes).to eq(1)
- end
-
- end
-
- def add_note(text, author = issue.author)
- created_at = Time.now - 1.hour + Note.count.seconds
- issue.notes << create(:note,
- note: text,
- project: issue.project,
- author_id: author.id,
- created_at: created_at)
- end
-end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 47863d54579..27e509933b2 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -77,6 +77,32 @@ describe Notify do
end
end
+ shared_examples 'it should have Gmail Actions links' do
+ it { is_expected.to have_body_text /ViewAction/ }
+ end
+
+ shared_examples 'it should not have Gmail Actions links' do
+ it { is_expected.to_not have_body_text /ViewAction/ }
+ end
+
+ shared_examples 'it should show Gmail Actions View Issue link' do
+ it_behaves_like 'it should have Gmail Actions links'
+
+ it { is_expected.to have_body_text /View Issue/ }
+ end
+
+ shared_examples 'it should show Gmail Actions View Merge request link' do
+ it_behaves_like 'it should have Gmail Actions links'
+
+ it { is_expected.to have_body_text /View Merge request/ }
+ end
+
+ shared_examples 'it should show Gmail Actions View Commit link' do
+ it_behaves_like 'it should have Gmail Actions links'
+
+ it { is_expected.to have_body_text /View Commit/ }
+ end
+
describe 'for new users, the email' do
let(:example_site_path) { root_path }
let(:new_user) { create(:user, email: new_user_address, created_by_id: 1) }
@@ -87,6 +113,7 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'a new user email', new_user_address
+ it_behaves_like 'it should not have Gmail Actions links'
it 'contains the password text' do
is_expected.to have_body_text /Click here to set your password/
@@ -115,6 +142,7 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'a new user email', new_user_address
+ it_behaves_like 'it should not have Gmail Actions links'
it 'should not contain the new user\'s password' do
is_expected.not_to have_body_text /password/
@@ -127,6 +155,7 @@ describe Notify do
subject { Notify.new_ssh_key_email(key.id) }
it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'it should not have Gmail Actions links'
it 'is sent to the new user' do
is_expected.to deliver_to key.user.email
@@ -150,6 +179,8 @@ describe Notify do
subject { Notify.new_email_email(email.id) }
+ it_behaves_like 'it should not have Gmail Actions links'
+
it 'is sent to the new user' do
is_expected.to deliver_to email.user.email
end
@@ -194,6 +225,7 @@ describe Notify do
it_behaves_like 'an assignee email'
it_behaves_like 'an email starting a new thread', 'issue'
+ it_behaves_like 'it should show Gmail Actions View Issue link'
it 'has the correct subject' do
is_expected.to have_subject /#{project.name} \| #{issue.title} \(##{issue.iid}\)/
@@ -207,16 +239,19 @@ describe Notify do
describe 'that are new with a description' do
subject { Notify.new_issue_email(issue_with_description.assignee_id, issue_with_description.id) }
+ it_behaves_like 'it should show Gmail Actions View Issue link'
+
it 'contains the description' do
is_expected.to have_body_text /#{issue_with_description.description}/
end
end
describe 'that have been reassigned' do
- subject { Notify.reassigned_issue_email(recipient.id, issue.id, previous_assignee.id, current_user) }
+ subject { Notify.reassigned_issue_email(recipient.id, issue.id, previous_assignee.id, current_user.id) }
it_behaves_like 'a multiple recipients email'
it_behaves_like 'an answer to an existing thread', 'issue'
+ it_behaves_like 'it should show Gmail Actions View Issue link'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -243,9 +278,10 @@ describe Notify do
describe 'status changed' do
let(:status) { 'closed' }
- subject { Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user) }
+ subject { Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user.id) }
it_behaves_like 'an answer to an existing thread', 'issue'
+ it_behaves_like 'it should show Gmail Actions View Issue link'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -269,7 +305,6 @@ describe Notify do
is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/
end
end
-
end
context 'for merge requests' do
@@ -282,6 +317,7 @@ describe Notify do
it_behaves_like 'an assignee email'
it_behaves_like 'an email starting a new thread', 'merge_request'
+ it_behaves_like 'it should show Gmail Actions View Merge request link'
it 'has the correct subject' do
is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/
@@ -307,6 +343,8 @@ describe Notify do
describe 'that are new with a description' do
subject { Notify.new_merge_request_email(merge_request_with_description.assignee_id, merge_request_with_description.id) }
+ it_behaves_like 'it should show Gmail Actions View Merge request link'
+
it 'contains the description' do
is_expected.to have_body_text /#{merge_request_with_description.description}/
end
@@ -317,6 +355,7 @@ describe Notify do
it_behaves_like 'a multiple recipients email'
it_behaves_like 'an answer to an existing thread', 'merge_request'
+ it_behaves_like 'it should show Gmail Actions View Merge request link'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -343,9 +382,10 @@ describe Notify do
describe 'status changed' do
let(:status) { 'reopened' }
- subject { Notify.merge_request_status_email(recipient.id, merge_request.id, status, current_user) }
+ subject { Notify.merge_request_status_email(recipient.id, merge_request.id, status, current_user.id) }
it_behaves_like 'an answer to an existing thread', 'merge_request'
+ it_behaves_like 'it should show Gmail Actions View Merge request link'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -375,6 +415,7 @@ describe Notify do
it_behaves_like 'a multiple recipients email'
it_behaves_like 'an answer to an existing thread', 'merge_request'
+ it_behaves_like 'it should show Gmail Actions View Merge request link'
it 'is sent as the merge author' do
sender = subject.header[:from].addrs[0]
@@ -403,6 +444,7 @@ describe Notify do
subject { Notify.project_was_moved_email(project.id, user.id, "gitlab/gitlab") }
it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'it should not have Gmail Actions links'
it 'has the correct subject' do
is_expected.to have_subject /Project was moved/
@@ -424,13 +466,16 @@ describe Notify do
subject { Notify.project_access_granted_email(project_member.id) }
it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'it should not have Gmail Actions links'
it 'has the correct subject' do
is_expected.to have_subject /Access to project was granted/
end
+
it 'contains name of project' do
is_expected.to have_body_text /#{project.name}/
end
+
it 'contains new user role' do
is_expected.to have_body_text /#{project_member.human_access}/
end
@@ -445,6 +490,8 @@ describe Notify do
end
shared_examples 'a note email' do
+ it_behaves_like 'it should have Gmail Actions links'
+
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
expect(sender.display_name).to eq(note_author.name)
@@ -469,6 +516,7 @@ describe Notify do
it_behaves_like 'a note email'
it_behaves_like 'an answer to an existing thread', 'commit'
+ it_behaves_like 'it should show Gmail Actions View Commit link'
it 'has the correct subject' do
is_expected.to have_subject /#{commit.title} \(#{commit.short_id}\)/
@@ -488,6 +536,7 @@ describe Notify do
it_behaves_like 'a note email'
it_behaves_like 'an answer to an existing thread', 'merge_request'
+ it_behaves_like 'it should show Gmail Actions View Merge request link'
it 'has the correct subject' do
is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/
@@ -507,6 +556,7 @@ describe Notify do
it_behaves_like 'a note email'
it_behaves_like 'an answer to an existing thread', 'issue'
+ it_behaves_like 'it should show Gmail Actions View Issue link'
it 'has the correct subject' do
is_expected.to have_subject /#{issue.title} \(##{issue.iid}\)/
@@ -527,6 +577,7 @@ describe Notify do
subject { Notify.group_access_granted_email(membership.id) }
it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'it should not have Gmail Actions links'
it 'has the correct subject' do
is_expected.to have_subject /Access to group was granted/
@@ -546,8 +597,10 @@ describe Notify do
let(:user) { create(:user, email: 'old-email@mail.com') }
before do
- user.email = "new-email@mail.com"
- user.save
+ perform_enqueued_jobs do
+ user.email = "new-email@mail.com"
+ user.save
+ end
end
subject { ActionMailer::Base.deliveries.last }
@@ -574,6 +627,8 @@ describe Notify do
subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :create) }
+ it_behaves_like 'it should not have Gmail Actions links'
+
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
expect(sender.display_name).to eq(user.name)
@@ -600,6 +655,8 @@ describe Notify do
subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/tags/v1.0', action: :create) }
+ it_behaves_like 'it should not have Gmail Actions links'
+
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
expect(sender.display_name).to eq(user.name)
@@ -625,6 +682,8 @@ describe Notify do
subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :delete) }
+ it_behaves_like 'it should not have Gmail Actions links'
+
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
expect(sender.display_name).to eq(user.name)
@@ -646,6 +705,8 @@ describe Notify do
subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/tags/v1.0', action: :delete) }
+ it_behaves_like 'it should not have Gmail Actions links'
+
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
expect(sender.display_name).to eq(user.name)
@@ -671,6 +732,8 @@ describe Notify do
subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, reverse_compare: false, send_from_committer_email: send_from_committer_email) }
+ it_behaves_like 'it should not have Gmail Actions links'
+
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
expect(sender.display_name).to eq(user.name)
@@ -774,6 +837,8 @@ describe Notify do
subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare) }
+ it_behaves_like 'it should show Gmail Actions View Commit link'
+
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
expect(sender.display_name).to eq(user.name)
diff --git a/spec/models/ci/project_services/mail_service_spec.rb b/spec/models/ci/project_services/mail_service_spec.rb
index d9b3d34ff15..c03be3ef75f 100644
--- a/spec/models/ci/project_services/mail_service_spec.rb
+++ b/spec/models/ci/project_services/mail_service_spec.rb
@@ -44,13 +44,10 @@ describe Ci::MailService do
end
it do
- should_email("git@example.com")
- mail.execute(build)
- end
-
- def should_email(email)
- expect(Ci::Notify).to receive(:build_fail_email).with(build.id, email)
- expect(Ci::Notify).not_to receive(:build_success_email).with(build.id, email)
+ perform_enqueued_jobs do
+ expect{ mail.execute(build) }.to change{ ActionMailer::Base.deliveries.size }.by(1)
+ expect(ActionMailer::Base.deliveries.last.to).to eq(["git@example.com"])
+ end
end
end
@@ -67,13 +64,10 @@ describe Ci::MailService do
end
it do
- should_email("git@example.com")
- mail.execute(build)
- end
-
- def should_email(email)
- expect(Ci::Notify).to receive(:build_success_email).with(build.id, email)
- expect(Ci::Notify).not_to receive(:build_fail_email).with(build.id, email)
+ perform_enqueued_jobs do
+ expect{ mail.execute(build) }.to change{ ActionMailer::Base.deliveries.size }.by(1)
+ expect(ActionMailer::Base.deliveries.last.to).to eq(["git@example.com"])
+ end
end
end
@@ -95,14 +89,12 @@ describe Ci::MailService do
end
it do
- should_email("git@example.com")
- should_email("jeroen@example.com")
- mail.execute(build)
- end
-
- def should_email(email)
- expect(Ci::Notify).to receive(:build_success_email).with(build.id, email)
- expect(Ci::Notify).not_to receive(:build_fail_email).with(build.id, email)
+ perform_enqueued_jobs do
+ expect{ mail.execute(build) }.to change{ ActionMailer::Base.deliveries.size }.by(2)
+ expect(
+ ActionMailer::Base.deliveries.map(&:to).flatten
+ ).to include("git@example.com", "jeroen@example.com")
+ end
end
end
@@ -124,14 +116,11 @@ describe Ci::MailService do
end
it do
- should_email(commit.git_author_email)
- should_email("jeroen@example.com")
- mail.execute(build) if mail.can_execute?(build)
- end
-
- def should_email(email)
- expect(Ci::Notify).not_to receive(:build_success_email).with(build.id, email)
- expect(Ci::Notify).not_to receive(:build_fail_email).with(build.id, email)
+ perform_enqueued_jobs do
+ expect do
+ mail.execute(build) if mail.can_execute?(build)
+ end.to_not change{ ActionMailer::Base.deliveries.size }
+ end
end
end
@@ -177,14 +166,11 @@ describe Ci::MailService do
it do
Ci::Build.retry(build)
- should_email(commit.git_author_email)
- should_email("jeroen@example.com")
- mail.execute(build) if mail.can_execute?(build)
- end
-
- def should_email(email)
- expect(Ci::Notify).not_to receive(:build_success_email).with(build.id, email)
- expect(Ci::Notify).not_to receive(:build_fail_email).with(build.id, email)
+ perform_enqueued_jobs do
+ expect do
+ mail.execute(build) if mail.can_execute?(build)
+ end.to_not change{ ActionMailer::Base.deliveries.size }
+ end
end
end
end
diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb
index 1031af097bd..3c1009a2eb0 100644
--- a/spec/models/commit_range_spec.rb
+++ b/spec/models/commit_range_spec.rb
@@ -7,50 +7,72 @@ describe CommitRange do
it { is_expected.to include_module(Referable) }
end
- let(:sha_from) { 'f3f85602' }
- let(:sha_to) { 'e86e1013' }
+ let!(:project) { create(:project, :public) }
+ let!(:commit1) { project.commit("HEAD~2") }
+ let!(:commit2) { project.commit }
- let(:range) { described_class.new("#{sha_from}...#{sha_to}") }
- let(:range2) { described_class.new("#{sha_from}..#{sha_to}") }
+ let(:sha_from) { commit1.short_id }
+ let(:sha_to) { commit2.short_id }
+
+ let(:full_sha_from) { commit1.id }
+ let(:full_sha_to) { commit2.id }
+
+ let(:range) { described_class.new("#{sha_from}...#{sha_to}", project) }
+ let(:range2) { described_class.new("#{sha_from}..#{sha_to}", project) }
it 'raises ArgumentError when given an invalid range string' do
- expect { described_class.new("Foo") }.to raise_error(ArgumentError)
+ expect { described_class.new("Foo", project) }.to raise_error(ArgumentError)
end
describe '#to_s' do
it 'is correct for three-dot syntax' do
- expect(range.to_s).to eq "#{sha_from[0..7]}...#{sha_to[0..7]}"
+ expect(range.to_s).to eq "#{full_sha_from}...#{full_sha_to}"
end
it 'is correct for two-dot syntax' do
- expect(range2.to_s).to eq "#{sha_from[0..7]}..#{sha_to[0..7]}"
+ expect(range2.to_s).to eq "#{full_sha_from}..#{full_sha_to}"
end
end
describe '#to_reference' do
- let(:project) { double('project', to_reference: 'namespace1/project') }
+ let(:cross) { create(:project) }
+
+ it 'returns a String reference to the object' do
+ expect(range.to_reference).to eq "#{full_sha_from}...#{full_sha_to}"
+ end
+
+ it 'returns a String reference to the object' do
+ expect(range2.to_reference).to eq "#{full_sha_from}..#{full_sha_to}"
+ end
+
+ it 'supports a cross-project reference' do
+ expect(range.to_reference(cross)).to eq "#{project.to_reference}@#{full_sha_from}...#{full_sha_to}"
+ end
+ end
- before do
- range.project = project
+ describe '#reference_link_text' do
+ let(:cross) { create(:project) }
+
+ it 'returns a String reference to the object' do
+ expect(range.reference_link_text).to eq "#{sha_from}...#{sha_to}"
end
it 'returns a String reference to the object' do
- expect(range.to_reference).to eq range.to_s
+ expect(range2.reference_link_text).to eq "#{sha_from}..#{sha_to}"
end
it 'supports a cross-project reference' do
- cross = double('project')
- expect(range.to_reference(cross)).to eq "#{project.to_reference}@#{range.to_s}"
+ expect(range.reference_link_text(cross)).to eq "#{project.to_reference}@#{sha_from}...#{sha_to}"
end
end
describe '#reference_title' do
it 'returns the correct String for three-dot ranges' do
- expect(range.reference_title).to eq "Commits #{sha_from} through #{sha_to}"
+ expect(range.reference_title).to eq "Commits #{full_sha_from} through #{full_sha_to}"
end
it 'returns the correct String for two-dot ranges' do
- expect(range2.reference_title).to eq "Commits #{sha_from}^ through #{sha_to}"
+ expect(range2.reference_title).to eq "Commits #{full_sha_from}^ through #{full_sha_to}"
end
end
@@ -60,11 +82,11 @@ describe CommitRange do
end
it 'includes the correct values for a three-dot range' do
- expect(range.to_param).to eq({ from: sha_from, to: sha_to })
+ expect(range.to_param).to eq({ from: full_sha_from, to: full_sha_to })
end
it 'includes the correct values for a two-dot range' do
- expect(range2.to_param).to eq({ from: sha_from + '^', to: sha_to })
+ expect(range2.to_param).to eq({ from: full_sha_from + '^', to: full_sha_to })
end
end
@@ -79,64 +101,37 @@ describe CommitRange do
end
describe '#valid_commits?' do
- context 'without a project' do
- it 'returns nil' do
- expect(range.valid_commits?).to be_nil
+ context 'with a valid repo' do
+ before do
+ expect(project).to receive(:valid_repo?).and_return(true)
end
- end
-
- it 'accepts an optional project argument' do
- project1 = double('project1').as_null_object
- project2 = double('project2').as_null_object
-
- # project1 gets assigned through the accessor, but ignored when not given
- # as an argument to `valid_commits?`
- expect(project1).not_to receive(:present?)
- range.project = project1
-
- # project2 gets passed to `valid_commits?`
- expect(project2).to receive(:present?).and_return(false)
- range.valid_commits?(project2)
- end
-
- context 'with a project' do
- let(:project) { double('project', repository: double('repository')) }
+ it 'is false when `sha_from` is invalid' do
+ expect(project).to receive(:commit).with(sha_from).and_return(nil)
+ expect(project).to receive(:commit).with(sha_to).and_call_original
- context 'with a valid repo' do
- before do
- expect(project).to receive(:valid_repo?).and_return(true)
- range.project = project
- end
+ expect(range).not_to be_valid_commits
+ end
- it 'is false when `sha_from` is invalid' do
- expect(project.repository).to receive(:commit).with(sha_from).and_return(false)
- expect(project.repository).not_to receive(:commit).with(sha_to)
- expect(range).not_to be_valid_commits
- end
+ it 'is false when `sha_to` is invalid' do
+ expect(project).to receive(:commit).with(sha_from).and_call_original
+ expect(project).to receive(:commit).with(sha_to).and_return(nil)
- it 'is false when `sha_to` is invalid' do
- expect(project.repository).to receive(:commit).with(sha_from).and_return(true)
- expect(project.repository).to receive(:commit).with(sha_to).and_return(false)
- expect(range).not_to be_valid_commits
- end
+ expect(range).not_to be_valid_commits
+ end
- it 'is true when both `sha_from` and `sha_to` are valid' do
- expect(project.repository).to receive(:commit).with(sha_from).and_return(true)
- expect(project.repository).to receive(:commit).with(sha_to).and_return(true)
- expect(range).to be_valid_commits
- end
+ it 'is true when both `sha_from` and `sha_to` are valid' do
+ expect(range).to be_valid_commits
end
+ end
- context 'without a valid repo' do
- before do
- expect(project).to receive(:valid_repo?).and_return(false)
- range.project = project
- end
+ context 'without a valid repo' do
+ before do
+ expect(project).to receive(:valid_repo?).and_return(false)
+ end
- it 'returns false' do
- expect(range).not_to be_valid_commits
- end
+ it 'returns false' do
+ expect(range).not_to be_valid_commits
end
end
end
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 90be9324951..974b52c1833 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -24,6 +24,17 @@ describe Commit do
end
end
+ describe '#reference_link_text' do
+ it 'returns a String reference to the object' do
+ expect(commit.reference_link_text).to eq commit.short_id
+ end
+
+ it 'supports a cross-project reference' do
+ cross = double('project')
+ expect(commit.reference_link_text(cross)).to eq "#{project.to_reference}@#{commit.short_id}"
+ end
+ end
+
describe '#title' do
it "returns no_commit_message when safe_message is blank" do
allow(commit).to receive(:safe_message).and_return('')
@@ -77,14 +88,10 @@ eos
let(:other_issue) { create :issue, project: other_project }
it 'detects issues that this commit is marked as closing' do
- allow(commit).to receive(:safe_message).and_return("Fixes ##{issue.iid}")
- expect(commit.closes_issues).to eq([issue])
- end
-
- it 'does not detect issues from other projects' do
ext_ref = "#{other_project.path_with_namespace}##{other_issue.iid}"
- allow(commit).to receive(:safe_message).and_return("Fixes #{ext_ref}")
- expect(commit.closes_issues).to be_empty
+ allow(commit).to receive(:safe_message).and_return("Fixes ##{issue.iid} and #{ext_ref}")
+ expect(commit.closes_issues).to include(issue)
+ expect(commit.closes_issues).to include(other_issue)
end
end
diff --git a/spec/models/concerns/strip_attribute_spec.rb b/spec/models/concerns/strip_attribute_spec.rb
new file mode 100644
index 00000000000..6445e29c3ef
--- /dev/null
+++ b/spec/models/concerns/strip_attribute_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe Milestone, "StripAttribute" do
+ let(:milestone) { create(:milestone) }
+
+ describe ".strip_attributes" do
+ it { expect(Milestone).to respond_to(:strip_attributes) }
+ it { expect(Milestone.strip_attrs).to include(:title) }
+ end
+
+ describe "#strip_attributes" do
+ before do
+ milestone.title = ' 8.3 '
+ milestone.valid?
+ end
+
+ it { expect(milestone.title).to eq('8.3') }
+ end
+
+end
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index 0f32f162a10..ae53f7a536b 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -64,4 +64,42 @@ describe Event do
it { expect(@event.branch_name).to eq("master") }
it { expect(@event.author).to eq(@user) }
end
+
+ describe '.latest_update_time' do
+ describe 'when events are present' do
+ let(:time) { Time.utc(2015, 1, 1) }
+
+ before do
+ create(:closed_issue_event, updated_at: time)
+ create(:closed_issue_event, updated_at: time + 5)
+ end
+
+ it 'returns the latest update time' do
+ expect(Event.latest_update_time).to eq(time + 5)
+ end
+ end
+
+ describe 'when no events exist' do
+ it 'returns nil' do
+ expect(Event.latest_update_time).to be_nil
+ end
+ end
+ end
+
+ describe '.limit_recent' do
+ let!(:event1) { create(:closed_issue_event) }
+ let!(:event2) { create(:closed_issue_event) }
+
+ describe 'without an explicit limit' do
+ subject { Event.limit_recent }
+
+ it { is_expected.to eq([event2, event1]) }
+ end
+
+ describe 'with an explicit limit' do
+ subject { Event.limit_recent(1) }
+
+ it { is_expected.to eq([event2]) }
+ end
+ end
end
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index bbfc5535eec..6f166b5ab75 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -38,6 +38,33 @@ describe Group do
it { is_expected.not_to validate_presence_of :owner }
end
+ describe '.public_and_given_groups' do
+ let!(:public_group) { create(:group, public: true) }
+
+ subject { described_class.public_and_given_groups([group.id]) }
+
+ it { is_expected.to eq([public_group, group]) }
+ end
+
+ describe '.visible_to_user' do
+ let!(:group) { create(:group) }
+ let!(:user) { create(:user) }
+
+ subject { described_class.visible_to_user(user) }
+
+ describe 'when the user has access to a group' do
+ before do
+ group.add_user(user, Gitlab::Access::MASTER)
+ end
+
+ it { is_expected.to eq([group]) }
+ end
+
+ describe 'when the user does not have access to any groups' do
+ it { is_expected.to eq([]) }
+ 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/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 33acfa37fea..0adf02db695 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -257,4 +257,29 @@ describe MergeRequest do
it_behaves_like 'a Taskable' do
subject { create :merge_request, :simple }
end
+
+ describe '#ci_commit' do
+ describe 'when the source project exists' do
+ it 'returns the latest commit' do
+ commit = double(:commit, id: '123abc')
+ ci_commit = double(:ci_commit)
+
+ allow(subject).to receive(:last_commit).and_return(commit)
+
+ expect(subject.source_project).to receive(:ci_commit).
+ with('123abc').
+ and_return(ci_commit)
+
+ expect(subject.ci_commit).to eq(ci_commit)
+ end
+ end
+
+ describe 'when the source project does not exist' do
+ it 'returns nil' do
+ allow(subject).to receive(:source_project).and_return(nil)
+
+ expect(subject.ci_commit).to be_nil
+ end
+ end
+ end
end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 75564839dcf..f347f537550 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -32,77 +32,6 @@ describe Note do
it { is_expected.to validate_presence_of(:project) }
end
- describe '#votable?' do
- it 'is true for issue notes' do
- note = build(:note_on_issue)
- expect(note).to be_votable
- end
-
- it 'is true for merge request notes' do
- note = build(:note_on_merge_request)
- expect(note).to be_votable
- end
-
- it 'is false for merge request diff notes' do
- note = build(:note_on_merge_request_diff)
- expect(note).not_to be_votable
- end
-
- it 'is false for commit notes' do
- note = build(:note_on_commit)
- expect(note).not_to be_votable
- end
-
- it 'is false for commit diff notes' do
- note = build(:note_on_commit_diff)
- expect(note).not_to be_votable
- end
- end
-
- describe 'voting score' do
- it 'recognizes a neutral note' do
- note = build(:votable_note, note: 'This is not a +1 note')
- expect(note).not_to be_upvote
- expect(note).not_to be_downvote
- end
-
- it 'recognizes a neutral emoji note' do
- note = build(:votable_note, note: "I would :+1: this, but I don't want to")
- expect(note).not_to be_upvote
- expect(note).not_to be_downvote
- end
-
- it 'recognizes a +1 note' do
- note = build(:votable_note, note: '+1 for this')
- expect(note).to be_upvote
- end
-
- it 'recognizes a +1 emoji as a vote' do
- note = build(:votable_note, note: ':+1: for this')
- expect(note).to be_upvote
- end
-
- it 'recognizes a thumbsup emoji as a vote' do
- note = build(:votable_note, note: ':thumbsup: for this')
- expect(note).to be_upvote
- end
-
- it 'recognizes a -1 note' do
- note = build(:votable_note, note: '-1 for this')
- expect(note).to be_downvote
- end
-
- it 'recognizes a -1 emoji as a vote' do
- note = build(:votable_note, note: ':-1: for this')
- expect(note).to be_downvote
- end
-
- it 'recognizes a thumbsdown emoji as a vote' do
- note = build(:votable_note, note: ':thumbsdown: for this')
- expect(note).to be_downvote
- end
- end
-
describe "Commit notes" do
let!(:note) { create(:note_on_commit, note: "+1 from me") }
let!(:commit) { note.noteable }
@@ -139,10 +68,6 @@ describe Note do
it "should be recognized by #for_commit_diff_line?" do
expect(note).to be_for_commit_diff_line
end
-
- it "should not be votable" do
- expect(note).not_to be_votable
- end
end
describe 'authorization' do
@@ -204,4 +129,16 @@ describe Note do
it { expect(Note.search('wow')).to include(note) }
end
+
+ describe :grouped_awards do
+ before do
+ create :note, note: "smile", is_award: true
+ create :note, note: "smile", is_award: true
+ end
+
+ it "returns grouped array of notes" do
+ expect(Note.grouped_awards.first.first).to eq("smile")
+ expect(Note.grouped_awards.first.last).to match_array(Note.all)
+ end
+ end
end
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index ddd2cce212c..576f5fc79eb 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -94,9 +94,9 @@ describe JiraService do
end
it 'should be prepopulated with the settings' do
- expect(@service.properties[:project_url]).to eq('http://jira.sample/projects/project_a')
- expect(@service.properties[:issues_url]).to eq("http://jira.sample/issues/:id")
- expect(@service.properties[:new_issue_url]).to eq("http://jira.sample/projects/project_a/issues/new")
+ expect(@service.properties["project_url"]).to eq('http://jira.sample/projects/project_a')
+ expect(@service.properties["issues_url"]).to eq("http://jira.sample/issues/:id")
+ expect(@service.properties["new_issue_url"]).to eq("http://jira.sample/projects/project_a/issues/new")
end
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 8d7e6e76766..06a02c13bf1 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -345,17 +345,6 @@ describe Project do
expect(project1.star_count).to eq(0)
expect(project2.star_count).to eq(0)
end
-
- it 'is decremented when an upvoter account is deleted' do
- user = create :user
- project = create :project, :public
- user.toggle_star(project)
- project.reload
- expect(project.star_count).to eq(1)
- user.destroy
- project.reload
- expect(project.star_count).to eq(0)
- end
end
describe :avatar_type do
@@ -455,7 +444,9 @@ describe Project do
before do
2.times do
- create(:note_on_commit, project: project2, created_at: date)
+ # Little fix for special issue related to Fractional Seconds support for MySQL.
+ # See: https://github.com/rails/rails/pull/14359/files
+ create(:note_on_commit, project: project2, created_at: date + 1)
end
end
@@ -464,4 +455,23 @@ describe Project do
end
end
end
+
+ describe '.visible_to_user' do
+ let!(:project) { create(:project, :private) }
+ let!(:user) { create(:user) }
+
+ subject { described_class.visible_to_user(user) }
+
+ describe 'when a user has access to a project' do
+ before do
+ project.team.add_user(user, Gitlab::Access::MASTER)
+ end
+
+ it { is_expected.to eq([project]) }
+ end
+
+ describe 'when a user does not have access to any projects' do
+ it { is_expected.to eq([]) }
+ end
+ end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 7d716c23120..4631b12faf1 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -686,7 +686,7 @@ describe User do
end
end
- describe "#contributed_projects_ids" do
+ describe "#contributed_projects" do
subject { create(:user) }
let!(:project1) { create(:project) }
let!(:project2) { create(:project, forked_from_project: project3) }
@@ -701,15 +701,15 @@ describe User do
end
it "includes IDs for projects the user has pushed to" do
- expect(subject.contributed_projects_ids).to include(project1.id)
+ expect(subject.contributed_projects).to include(project1)
end
it "includes IDs for projects the user has had merge requests merged into" do
- expect(subject.contributed_projects_ids).to include(project3.id)
+ expect(subject.contributed_projects).to include(project3)
end
it "doesn't include IDs for unrelated projects" do
- expect(subject.contributed_projects_ids).not_to include(project2.id)
+ expect(subject.contributed_projects).not_to include(project2)
end
end
@@ -758,4 +758,30 @@ describe User do
expect(subject.recent_push).to eq(nil)
end
end
+
+ describe '#authorized_groups' do
+ let!(:user) { create(:user) }
+ let!(:private_group) { create(:group) }
+
+ before do
+ private_group.add_user(user, Gitlab::Access::MASTER)
+ end
+
+ subject { user.authorized_groups }
+
+ it { is_expected.to eq([private_group]) }
+ end
+
+ describe '#authorized_projects' do
+ let!(:user) { create(:user) }
+ let!(:private_project) { create(:project, :private) }
+
+ before do
+ private_project.team << [user, Gitlab::Access::MASTER]
+ end
+
+ subject { user.authorized_projects }
+
+ it { is_expected.to eq([private_project]) }
+ end
end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 6b7a066ac40..a91fa735321 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -131,6 +131,23 @@ describe API::API, api: true do
end
end
+ describe 'GET /projects/:id/merge_request/:merge_request_id/commits' do
+ context 'valid merge request' do
+ before { get api("/projects/#{project.id}/merge_request/#{merge_request.id}/commits", user) }
+ let(:commit) { merge_request.commits.first }
+
+ it { expect(response.status).to eq 200 }
+ it { expect(json_response.size).to eq(merge_request.commits.size) }
+ it { expect(json_response.first['id']).to eq(commit.id) }
+ it { expect(json_response.first['title']).to eq(commit.title) }
+ end
+
+ it 'returns a 404 when merge_request_id not found' do
+ get api("/projects/#{project.id}/merge_request/999/commits", user)
+ expect(response.status).to eq(404)
+ end
+ end
+
describe 'GET /projects/:id/merge_request/:merge_request_id/changes' do
it 'should return the change information of the merge_request' do
get api("/projects/#{project.id}/merge_request/#{merge_request.id}/changes", user)
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 9fc294118ae..c59ee7af8ab 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -389,14 +389,30 @@ describe API::API, api: true do
describe 'GET /projects/:id/events' do
before { project_member2 }
- it 'should return a project events' do
- get api("/projects/#{project.id}/events", user)
- expect(response.status).to eq(200)
- json_event = json_response.first
+ context 'valid request' do
+ before do
+ note = create(:note_on_issue, note: 'What an awesome day!', project: project)
+ EventCreateService.new.leave_note(note, note.author)
+ get api("/projects/#{project.id}/events", user)
+ end
+
+ it { expect(response.status).to eq(200) }
+
+ context 'joined event' do
+ let(:json_event) { json_response[1] }
- expect(json_event['action_name']).to eq('joined')
- expect(json_event['project_id'].to_i).to eq(project.id)
- expect(json_event['author_username']).to eq(user3.username)
+ it { expect(json_event['action_name']).to eq('joined') }
+ it { expect(json_event['project_id'].to_i).to eq(project.id) }
+ it { expect(json_event['author_username']).to eq(user3.username) }
+ it { expect(json_event['author']['name']).to eq(user3.name) }
+ end
+
+ context 'comment event' do
+ let(:json_event) { json_response.first }
+
+ it { expect(json_event['action_name']).to eq('commented on') }
+ it { expect(json_event['note']['body']).to eq('What an awesome day!') }
+ end
end
it 'should return a 404 error if not found' do
diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb
index cc9a5f47582..17f2643fd45 100644
--- a/spec/requests/api/tags_spec.rb
+++ b/spec/requests/api/tags_spec.rb
@@ -28,10 +28,10 @@ describe API::API, api: true do
before do
release = project.releases.find_or_initialize_by(tag: tag_name)
release.update_attributes(description: description)
- get api("/projects/#{project.id}/repository/tags", user)
end
it "should return an array of project tags with release info" do
+ get api("/projects/#{project.id}/repository/tags", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.first['name']).to eq(tag_name)
@@ -119,17 +119,78 @@ describe API::API, api: true do
end
end
- describe 'PUT /projects/:id/repository/:tag/release' do
+ describe 'POST /projects/:id/repository/tags/:tag_name/release' do
let(:tag_name) { project.repository.tag_names.first }
let(:description) { 'Awesome release!' }
it 'should create description for existing git tag' do
- put api("/projects/#{project.id}/repository/#{tag_name}/release", user),
+ post api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user),
description: description
- expect(response.status).to eq(200)
- expect(json_response['tag']).to eq(tag_name)
+ expect(response.status).to eq(201)
+ expect(json_response['tag_name']).to eq(tag_name)
expect(json_response['description']).to eq(description)
end
+
+ it 'should return 404 if the tag does not exist' do
+ post api("/projects/#{project.id}/repository/tags/foobar/release", user),
+ description: description
+
+ expect(response.status).to eq(404)
+ expect(json_response['message']).to eq('Tag does not exist')
+ end
+
+ context 'on tag with existing release' do
+ before do
+ release = project.releases.find_or_initialize_by(tag: tag_name)
+ release.update_attributes(description: description)
+ end
+
+ it 'should return 409 if there is already a release' do
+ post api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user),
+ description: description
+
+ expect(response.status).to eq(409)
+ expect(json_response['message']).to eq('Release already exists')
+ end
+ end
+ end
+
+ describe 'PUT id/repository/tags/:tag_name/release' do
+ let(:tag_name) { project.repository.tag_names.first }
+ let(:description) { 'Awesome release!' }
+ let(:new_description) { 'The best release!' }
+
+ context 'on tag with existing release' do
+ before do
+ release = project.releases.find_or_initialize_by(tag: tag_name)
+ release.update_attributes(description: description)
+ end
+
+ it 'should update the release description' do
+ put api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user),
+ description: new_description
+
+ expect(response.status).to eq(200)
+ expect(json_response['tag_name']).to eq(tag_name)
+ expect(json_response['description']).to eq(new_description)
+ end
+ end
+
+ it 'should return 404 if the tag does not exist' do
+ put api("/projects/#{project.id}/repository/tags/foobar/release", user),
+ description: new_description
+
+ expect(response.status).to eq(404)
+ expect(json_response['message']).to eq('Tag does not exist')
+ end
+
+ it 'should return 404 if the release does not exist' do
+ put api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user),
+ description: new_description
+
+ expect(response.status).to eq(404)
+ expect(json_response['message']).to eq('Release does not exist')
+ end
end
end
diff --git a/spec/services/ci/create_commit_service_spec.rb b/spec/services/ci/create_commit_service_spec.rb
index e3a8fe9681b..e0ede1d58b7 100644
--- a/spec/services/ci/create_commit_service_spec.rb
+++ b/spec/services/ci/create_commit_service_spec.rb
@@ -53,7 +53,7 @@ module Ci
end
end
- it 'fails commits without .gitlab-ci.yml' do
+ it 'skips commits without .gitlab-ci.yml' do
stub_ci_commit_yaml_file(nil)
result = service.execute(project, user,
ref: 'refs/heads/0_1',
@@ -63,7 +63,24 @@ module Ci
)
expect(result).to be_persisted
expect(result.builds.any?).to be_falsey
- expect(result.status).to eq('failed')
+ expect(result.status).to eq('skipped')
+ expect(result.yaml_errors).to be_nil
+ end
+
+ it 'skips commits if yaml is invalid' do
+ message = 'message'
+ allow_any_instance_of(Ci::Commit).to receive(:git_commit_message) { message }
+ stub_ci_commit_yaml_file('invalid: file: file')
+ commits = [{ message: message }]
+ commit = service.execute(project, user,
+ ref: 'refs/tags/0_1',
+ before: '00000000',
+ after: '31das312',
+ commits: commits
+ )
+ expect(commit.builds.any?).to be false
+ expect(commit.status).to eq('failed')
+ expect(commit.yaml_errors).to_not be_nil
end
describe :ci_skip? do
@@ -100,7 +117,7 @@ module Ci
end
it "skips builds creation if there is [ci skip] tag in commit message and yaml is invalid" do
- stub_ci_commit_yaml_file('invalid: file')
+ stub_ci_commit_yaml_file('invalid: file: fiile')
commits = [{ message: message }]
commit = service.execute(project, user,
ref: 'refs/tags/0_1',
@@ -110,6 +127,7 @@ module Ci
)
expect(commit.builds.any?).to be false
expect(commit.status).to eq("skipped")
+ expect(commit.yaml_errors).to be_nil
end
end
diff --git a/spec/services/create_release_service_spec.rb b/spec/services/create_release_service_spec.rb
new file mode 100644
index 00000000000..26d7f365bbb
--- /dev/null
+++ b/spec/services/create_release_service_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe CreateReleaseService do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:tag_name) { project.repository.tag_names.first }
+ let(:description) { 'Awesome release!' }
+ let(:service) { CreateReleaseService.new(project, user) }
+
+ it 'creates a new release' do
+ result = service.execute(tag_name, description)
+ expect(result[:status]).to eq(:success)
+ release = project.releases.find_by(tag: tag_name)
+ expect(release).not_to be_nil
+ expect(release.description).to eq(description)
+ end
+
+ it 'raises an error if the tag does not exist' do
+ result = service.execute("foobar", description)
+ expect(result[:status]).to eq(:error)
+ end
+
+ context 'there already exists a release on a tag' do
+ before do
+ service.execute(tag_name, description)
+ end
+
+ it 'raises an error and does not update the release' do
+ result = service.execute(tag_name, 'The best release!')
+ expect(result[:status]).to eq(:error)
+ expect(project.releases.find_by(tag: tag_name).description).to eq(description)
+ end
+ end
+end
diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb
index db547ce0d50..d711e3da104 100644
--- a/spec/services/issues/close_service_spec.rb
+++ b/spec/services/issues/close_service_spec.rb
@@ -14,7 +14,9 @@ describe Issues::CloseService do
describe :execute do
context "valid params" do
before do
- @issue = Issues::CloseService.new(project, user, {}).execute(issue)
+ perform_enqueued_jobs do
+ @issue = Issues::CloseService.new(project, user, {}).execute(issue)
+ end
end
it { expect(@issue).to be_valid }
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index f55527ee9a3..73d0b7f7abe 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -15,6 +15,17 @@ describe Issues::UpdateService do
end
describe 'execute' do
+ def find_note(starting_with)
+ @issue.notes.find do |note|
+ note && note.note.start_with?(starting_with)
+ end
+ end
+
+ def update_issue(opts)
+ @issue = Issues::UpdateService.new(project, user, opts).execute(issue)
+ @issue.reload
+ end
+
context "valid params" do
before do
opts = {
@@ -25,7 +36,10 @@ describe Issues::UpdateService do
label_ids: [label.id]
}
- @issue = Issues::UpdateService.new(project, user, opts).execute(issue)
+ perform_enqueued_jobs do
+ @issue = Issues::UpdateService.new(project, user, opts).execute(issue)
+ end
+
@issue.reload
end
@@ -44,12 +58,6 @@ describe Issues::UpdateService do
expect(email.subject).to include(issue.title)
end
- def find_note(starting_with)
- @issue.notes.find do |note|
- note && note.note.start_with?(starting_with)
- end
- end
-
it 'should create system note about issue reassign' do
note = find_note('Reassigned to')
@@ -71,5 +79,71 @@ describe Issues::UpdateService do
expect(note.note).to eq 'Title changed from **Old title** to **New title**'
end
end
+
+ context 'when Issue has tasks' do
+ before { update_issue({ description: "- [ ] Task 1\n- [ ] Task 2" }) }
+
+ it { expect(@issue.tasks?).to eq(true) }
+
+ context 'when tasks are marked as completed' do
+ before { update_issue({ description: "- [x] Task 1\n- [X] Task 2" }) }
+
+ it 'creates system note about task status change' do
+ note1 = find_note('Marked the task **Task 1** as completed')
+ note2 = find_note('Marked the task **Task 2** as completed')
+
+ expect(note1).not_to be_nil
+ expect(note2).not_to be_nil
+ end
+ end
+
+ context 'when tasks are marked as incomplete' do
+ before do
+ update_issue({ description: "- [x] Task 1\n- [X] Task 2" })
+ update_issue({ description: "- [ ] Task 1\n- [ ] Task 2" })
+ end
+
+ it 'creates system note about task status change' do
+ note1 = find_note('Marked the task **Task 1** as incomplete')
+ note2 = find_note('Marked the task **Task 2** as incomplete')
+
+ expect(note1).not_to be_nil
+ expect(note2).not_to be_nil
+ end
+ end
+
+ context 'when tasks position has been modified' do
+ before do
+ update_issue({ description: "- [x] Task 1\n- [X] Task 2" })
+ update_issue({ description: "- [x] Task 1\n- [ ] Task 3\n- [ ] Task 2" })
+ end
+
+ it 'does not create a system note' do
+ note = find_note('Marked the task **Task 2** as incomplete')
+
+ expect(note).to be_nil
+ end
+ end
+
+ context 'when a Task list with a completed item is totally replaced' do
+ before do
+ update_issue({ description: "- [ ] Task 1\n- [X] Task 2" })
+ update_issue({ description: "- [ ] One\n- [ ] Two\n- [ ] Three" })
+ end
+
+ it 'does not create a system note referencing the position the old item' do
+ note = find_note('Marked the task **Two** as incomplete')
+
+ expect(note).to be_nil
+ end
+
+ it 'should not generate a new note at all' do
+ expect do
+ update_issue({ description: "- [ ] One\n- [ ] Two\n- [ ] Three" })
+ end.not_to change { Note.count }
+ end
+ end
+ end
+
end
end
diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb
index b3cbfd4b5b8..272f3897938 100644
--- a/spec/services/merge_requests/close_service_spec.rb
+++ b/spec/services/merge_requests/close_service_spec.rb
@@ -18,7 +18,9 @@ describe MergeRequests::CloseService do
before do
allow(service).to receive(:execute_hooks)
- @merge_request = service.execute(merge_request)
+ perform_enqueued_jobs do
+ @merge_request = service.execute(merge_request)
+ end
end
it { expect(@merge_request).to be_valid }
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index 242524286fa..b0b28512560 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -18,7 +18,9 @@ describe MergeRequests::MergeService do
before do
allow(service).to receive(:execute_hooks)
- service.execute(merge_request)
+ perform_enqueued_jobs do
+ service.execute(merge_request)
+ end
end
it { expect(merge_request).to be_valid }
diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb
index 9401bc3b558..05146bf43f4 100644
--- a/spec/services/merge_requests/reopen_service_spec.rb
+++ b/spec/services/merge_requests/reopen_service_spec.rb
@@ -19,7 +19,9 @@ describe MergeRequests::ReopenService do
allow(service).to receive(:execute_hooks)
merge_request.state = :closed
- service.execute(merge_request)
+ perform_enqueued_jobs do
+ service.execute(merge_request)
+ end
end
it { expect(merge_request).to be_valid }
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 2ed51d223b7..d899b1f01d1 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -14,6 +14,17 @@ describe MergeRequests::UpdateService do
end
describe 'execute' do
+ def find_note(starting_with)
+ @merge_request.notes.find do |note|
+ note && note.note.start_with?(starting_with)
+ end
+ end
+
+ def update_merge_request(opts)
+ @merge_request = MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
+ @merge_request.reload
+ end
+
context 'valid params' do
let(:opts) do
{
@@ -31,8 +42,10 @@ describe MergeRequests::UpdateService do
before do
allow(service).to receive(:execute_hooks)
- @merge_request = service.execute(merge_request)
- @merge_request.reload
+ perform_enqueued_jobs do
+ @merge_request = service.execute(merge_request)
+ @merge_request.reload
+ end
end
it { expect(@merge_request).to be_valid }
@@ -56,12 +69,6 @@ describe MergeRequests::UpdateService do
expect(email.subject).to include(merge_request.title)
end
- def find_note(starting_with)
- @merge_request.notes.find do |note|
- note && note.note.start_with?(starting_with)
- end
- end
-
it 'should create system note about merge_request reassign' do
note = find_note('Reassigned to')
@@ -90,5 +97,39 @@ describe MergeRequests::UpdateService do
expect(note.note).to eq 'Target branch changed from `master` to `target`'
end
end
+
+ context 'when MergeRequest has tasks' do
+ before { update_merge_request({ description: "- [ ] Task 1\n- [ ] Task 2" }) }
+
+ it { expect(@merge_request.tasks?).to eq(true) }
+
+ context 'when tasks are marked as completed' do
+ before { update_merge_request({ description: "- [x] Task 1\n- [X] Task 2" }) }
+
+ it 'creates system note about task status change' do
+ note1 = find_note('Marked the task **Task 1** as completed')
+ note2 = find_note('Marked the task **Task 2** as completed')
+
+ expect(note1).not_to be_nil
+ expect(note2).not_to be_nil
+ end
+ end
+
+ context 'when tasks are marked as incomplete' do
+ before do
+ update_merge_request({ description: "- [x] Task 1\n- [X] Task 2" })
+ update_merge_request({ description: "- [ ] Task 1\n- [ ] Task 2" })
+ end
+
+ it 'creates system note about task status change' do
+ note1 = find_note('Marked the task **Task 1** as incomplete')
+ note2 = find_note('Marked the task **Task 2** as incomplete')
+
+ expect(note1).not_to be_nil
+ expect(note2).not_to be_nil
+ end
+ end
+ end
+
end
end
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index f2ea0805b2f..cc38d257792 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -24,4 +24,38 @@ describe Notes::CreateService do
it { expect(@note.note).to eq('Awesome comment') }
end
end
+
+ describe "award emoji" do
+ before do
+ project.team << [user, :master]
+ end
+
+ it "creates emoji note" do
+ opts = {
+ note: ':smile: ',
+ noteable_type: 'Issue',
+ noteable_id: issue.id
+ }
+
+ @note = Notes::CreateService.new(project, user, opts).execute
+
+ expect(@note).to be_valid
+ expect(@note.note).to eq('smile')
+ expect(@note.is_award).to be_truthy
+ end
+
+ it "creates regular note if emoji name is invalid" do
+ opts = {
+ note: ':smile: moretext: ',
+ noteable_type: 'Issue',
+ noteable_id: issue.id
+ }
+
+ @note = Notes::CreateService.new(project, user, opts).execute
+
+ expect(@note).to be_valid
+ expect(@note.note).to eq(opts[:note])
+ expect(@note.is_award).to be_falsy
+ end
+ end
end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 520140917aa..35fa412ed80 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -3,6 +3,12 @@ require 'spec_helper'
describe NotificationService do
let(:notification) { NotificationService.new }
+ around(:each) do |example|
+ perform_enqueued_jobs do
+ example.run
+ end
+ end
+
describe 'Keys' do
describe :new_key do
let!(:key) { create(:personal_key) }
@@ -10,8 +16,7 @@ describe NotificationService do
it { expect(notification.new_key(key)).to be_truthy }
it 'should sent email to key owner' do
- expect(Notify).to receive(:new_ssh_key_email).with(key.id)
- notification.new_key(key)
+ expect{ notification.new_key(key) }.to change{ ActionMailer::Base.deliveries.size }.by(1)
end
end
end
@@ -23,8 +28,7 @@ describe NotificationService do
it { expect(notification.new_email(email)).to be_truthy }
it 'should send email to email owner' do
- expect(Notify).to receive(:new_email_email).with(email.id)
- notification.new_email(email)
+ expect{ notification.new_email(email) }.to change{ ActionMailer::Base.deliveries.size }.by(1)
end
end
end
@@ -41,24 +45,28 @@ describe NotificationService do
project.team << [issue.author, :master]
project.team << [issue.assignee, :master]
project.team << [note.author, :master]
+ create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: '@subscribed_participant cc this guy')
end
describe :new_note do
it do
add_users_with_subscription(note.project, issue)
- should_email(@u_watcher.id)
- should_email(note.noteable.author_id)
- should_email(note.noteable.assignee_id)
- should_email(@u_mentioned.id)
- should_email(@subscriber.id)
- should_not_email(note.author_id)
- should_not_email(@u_participating.id)
- should_not_email(@u_disabled.id)
- should_not_email(@unsubscriber.id)
- should_not_email(@u_outsider_mentioned)
+ ActionMailer::Base.deliveries.clear
notification.new_note(note)
+
+ should_email(@u_watcher)
+ should_email(note.noteable.author)
+ should_email(note.noteable.assignee)
+ should_email(@u_mentioned)
+ should_email(@subscriber)
+ should_email(@subscribed_participant)
+ should_not_email(note.author)
+ should_not_email(@u_participating)
+ should_not_email(@u_disabled)
+ should_not_email(@unsubscriber)
+ should_not_email(@u_outsider_mentioned)
end
it 'filters out "mentioned in" notes' do
@@ -82,26 +90,20 @@ describe NotificationService do
group_member = note.project.group.group_members.find_by_user_id(@u_watcher.id)
group_member.notification_level = Notification::N_GLOBAL
group_member.save
+ ActionMailer::Base.deliveries.clear
end
it do
- should_email(note.noteable.author_id)
- should_email(note.noteable.assignee_id)
- should_email(@u_mentioned.id)
- should_not_email(@u_watcher.id)
- should_not_email(note.author_id)
- should_not_email(@u_participating.id)
- should_not_email(@u_disabled.id)
notification.new_note(note)
- end
- end
- def should_email(user_id)
- expect(Notify).to receive(:note_issue_email).with(user_id, note.id)
- end
-
- def should_not_email(user_id)
- expect(Notify).not_to receive(:note_issue_email).with(user_id, note.id)
+ should_email(note.noteable.author)
+ should_email(note.noteable.assignee)
+ should_email(@u_mentioned)
+ should_not_email(@u_watcher)
+ should_not_email(note.author)
+ should_not_email(@u_participating)
+ should_not_email(@u_disabled)
+ end
end
end
@@ -113,24 +115,26 @@ describe NotificationService do
before do
build_team(note.project)
+ ActionMailer::Base.deliveries.clear
end
describe :new_note do
it do
+ notification.new_note(note)
+
# Notify all team members
note.project.team.members.each do |member|
# User with disabled notification should not be notified
next if member.id == @u_disabled.id
- should_email(member.id)
+ should_email(member)
end
- should_email(note.noteable.author_id)
- should_email(note.noteable.assignee_id)
- should_not_email(note.author_id)
- should_not_email(@u_mentioned.id)
- should_not_email(@u_disabled.id)
- should_not_email(@u_not_mentioned.id)
- notification.new_note(note)
+ should_email(note.noteable.author)
+ should_email(note.noteable.assignee)
+ should_not_email(note.author)
+ should_email(@u_mentioned)
+ should_not_email(@u_disabled)
+ should_email(@u_not_mentioned)
end
it 'filters out "mentioned in" notes' do
@@ -140,14 +144,6 @@ describe NotificationService do
notification.new_note(mentioned_note)
end
end
-
- def should_email(user_id)
- expect(Notify).to receive(:note_issue_email).with(user_id, note.id)
- end
-
- def should_not_email(user_id)
- expect(Notify).not_to receive(:note_issue_email).with(user_id, note.id)
- end
end
context 'commit note' do
@@ -156,43 +152,38 @@ describe NotificationService do
before do
build_team(note.project)
+ ActionMailer::Base.deliveries.clear
allow_any_instance_of(Commit).to receive(:author).and_return(@u_committer)
end
- describe :new_note do
+ describe :new_note, :perform_enqueued_jobs do
it do
- should_email(@u_committer.id, note)
- should_email(@u_watcher.id, note)
- should_not_email(@u_mentioned.id, note)
- should_not_email(note.author_id, note)
- should_not_email(@u_participating.id, note)
- should_not_email(@u_disabled.id, note)
notification.new_note(note)
+
+ should_email(@u_committer)
+ should_email(@u_watcher)
+ should_not_email(@u_mentioned)
+ should_not_email(note.author)
+ should_not_email(@u_participating)
+ should_not_email(@u_disabled)
end
it do
note.update_attribute(:note, '@mention referenced')
- should_email(@u_committer.id, note)
- should_email(@u_watcher.id, note)
- should_email(@u_mentioned.id, note)
- should_not_email(note.author_id, note)
- should_not_email(@u_participating.id, note)
- should_not_email(@u_disabled.id, note)
notification.new_note(note)
+
+ should_email(@u_committer)
+ should_email(@u_watcher)
+ should_email(@u_mentioned)
+ should_not_email(note.author)
+ should_not_email(@u_participating)
+ should_not_email(@u_disabled)
end
it do
@u_committer.update_attributes(notification_level: Notification::N_MENTION)
- should_not_email(@u_committer.id, note)
notification.new_note(note)
- end
-
- def should_email(user_id, n)
- expect(Notify).to receive(:note_commit_email).with(user_id, n.id)
- end
-
- def should_not_email(user_id, n)
- expect(Notify).not_to receive(:note_commit_email).with(user_id, n.id)
+ should_not_email(@u_committer)
end
end
end
@@ -205,99 +196,69 @@ describe NotificationService do
before do
build_team(issue.project)
add_users_with_subscription(issue.project, issue)
+ ActionMailer::Base.deliveries.clear
end
describe :new_issue do
it do
- should_email(issue.assignee_id)
- should_email(@u_watcher.id)
- should_email(@u_participant_mentioned.id)
- should_not_email(@u_mentioned.id)
- should_not_email(@u_participating.id)
- should_not_email(@u_disabled.id)
notification.new_issue(issue, @u_disabled)
+
+ should_email(issue.assignee)
+ should_email(@u_watcher)
+ should_email(@u_participant_mentioned)
+ should_not_email(@u_mentioned)
+ should_not_email(@u_participating)
+ should_not_email(@u_disabled)
end
it do
issue.assignee.update_attributes(notification_level: Notification::N_MENTION)
- should_not_email(issue.assignee_id)
notification.new_issue(issue, @u_disabled)
- end
-
- def should_email(user_id)
- expect(Notify).to receive(:new_issue_email).with(user_id, issue.id)
- end
- def should_not_email(user_id)
- expect(Notify).not_to receive(:new_issue_email).with(user_id, issue.id)
+ should_not_email(issue.assignee)
end
end
describe :reassigned_issue do
it 'should email new assignee' do
- should_email(issue.assignee_id)
- should_email(@u_watcher.id)
- should_email(@u_participant_mentioned.id)
- should_email(@subscriber.id)
- should_not_email(@unsubscriber.id)
- should_not_email(@u_participating.id)
- should_not_email(@u_disabled.id)
-
notification.reassigned_issue(issue, @u_disabled)
- end
-
- def should_email(user_id)
- expect(Notify).to receive(:reassigned_issue_email).with(user_id, issue.id, nil, @u_disabled.id)
- end
- def should_not_email(user_id)
- expect(Notify).not_to receive(:reassigned_issue_email).with(user_id, issue.id, issue.assignee_id, @u_disabled.id)
+ should_email(issue.assignee)
+ should_email(@u_watcher)
+ should_email(@u_participant_mentioned)
+ should_email(@subscriber)
+ should_not_email(@unsubscriber)
+ should_not_email(@u_participating)
+ should_not_email(@u_disabled)
end
end
describe :close_issue do
it 'should sent email to issue assignee and issue author' do
- should_email(issue.assignee_id)
- should_email(issue.author_id)
- should_email(@u_watcher.id)
- should_email(@u_participant_mentioned.id)
- should_email(@subscriber.id)
- should_not_email(@unsubscriber.id)
- should_not_email(@u_participating.id)
- should_not_email(@u_disabled.id)
-
notification.close_issue(issue, @u_disabled)
- end
-
- def should_email(user_id)
- expect(Notify).to receive(:closed_issue_email).with(user_id, issue.id, @u_disabled.id)
- end
- def should_not_email(user_id)
- expect(Notify).not_to receive(:closed_issue_email).with(user_id, issue.id, @u_disabled.id)
+ should_email(issue.assignee)
+ should_email(issue.author)
+ should_email(@u_watcher)
+ should_email(@u_participant_mentioned)
+ should_email(@subscriber)
+ should_not_email(@unsubscriber)
+ should_not_email(@u_participating)
+ should_not_email(@u_disabled)
end
end
describe :reopen_issue do
it 'should send email to issue assignee and issue author' do
- should_email(issue.assignee_id)
- should_email(issue.author_id)
- should_email(@u_watcher.id)
- should_email(@u_participant_mentioned.id)
- should_email(@subscriber.id)
- should_not_email(@unsubscriber.id)
- should_not_email(@u_participating.id)
- should_not_email(@u_disabled.id)
-
notification.reopen_issue(issue, @u_disabled)
- end
-
- def should_email(user_id)
- expect(Notify).to receive(:issue_status_changed_email).with(user_id, issue.id, 'reopened', @u_disabled.id)
- end
- def should_not_email(user_id)
- expect(Notify).not_to receive(:issue_status_changed_email).with(user_id, issue.id, 'reopened', @u_disabled.id)
+ should_email(issue.assignee)
+ should_email(issue.author)
+ should_email(@u_watcher)
+ should_email(@u_participant_mentioned)
+ should_email(@subscriber)
+ should_not_email(@unsubscriber)
+ should_not_email(@u_participating)
end
end
end
@@ -309,108 +270,74 @@ describe NotificationService do
before do
build_team(merge_request.target_project)
add_users_with_subscription(merge_request.target_project, merge_request)
+ ActionMailer::Base.deliveries.clear
end
describe :new_merge_request do
it do
- should_email(merge_request.assignee_id)
- should_email(@u_watcher.id)
- should_email(@u_participant_mentioned.id)
- should_not_email(@u_participating.id)
- should_not_email(@u_disabled.id)
notification.new_merge_request(merge_request, @u_disabled)
- end
-
- def should_email(user_id)
- expect(Notify).to receive(:new_merge_request_email).with(user_id, merge_request.id)
- end
- def should_not_email(user_id)
- expect(Notify).not_to receive(:new_merge_request_email).with(user_id, merge_request.id)
+ should_email(merge_request.assignee)
+ should_email(@u_watcher)
+ should_email(@u_participant_mentioned)
+ should_not_email(@u_participating)
+ should_not_email(@u_disabled)
end
end
describe :reassigned_merge_request do
it do
- should_email(merge_request.assignee_id)
- should_email(@u_watcher.id)
- should_email(@u_participant_mentioned.id)
- should_email(@subscriber.id)
- should_not_email(@unsubscriber.id)
- should_not_email(@u_participating.id)
- should_not_email(@u_disabled.id)
notification.reassigned_merge_request(merge_request, merge_request.author)
- end
- def should_email(user_id)
- expect(Notify).to receive(:reassigned_merge_request_email).with(user_id, merge_request.id, nil, merge_request.author_id)
- end
-
- def should_not_email(user_id)
- expect(Notify).not_to receive(:reassigned_merge_request_email).with(user_id, merge_request.id, merge_request.assignee_id, merge_request.author_id)
+ should_email(merge_request.assignee)
+ should_email(@u_watcher)
+ should_email(@u_participant_mentioned)
+ should_email(@subscriber)
+ should_not_email(@unsubscriber)
+ should_not_email(@u_participating)
+ should_not_email(@u_disabled)
end
end
describe :closed_merge_request do
it do
- should_email(merge_request.assignee_id)
- should_email(@u_watcher.id)
- should_email(@u_participant_mentioned.id)
- should_email(@subscriber.id)
- should_not_email(@unsubscriber.id)
- should_not_email(@u_participating.id)
- should_not_email(@u_disabled.id)
notification.close_mr(merge_request, @u_disabled)
- end
-
- def should_email(user_id)
- expect(Notify).to receive(:closed_merge_request_email).with(user_id, merge_request.id, @u_disabled.id)
- end
- def should_not_email(user_id)
- expect(Notify).not_to receive(:closed_merge_request_email).with(user_id, merge_request.id, @u_disabled.id)
+ should_email(merge_request.assignee)
+ should_email(@u_watcher)
+ should_email(@u_participant_mentioned)
+ should_email(@subscriber)
+ should_not_email(@unsubscriber)
+ should_not_email(@u_participating)
+ should_not_email(@u_disabled)
end
end
describe :merged_merge_request do
it do
- should_email(merge_request.assignee_id)
- should_email(@u_watcher.id)
- should_email(@u_participant_mentioned.id)
- should_email(@subscriber.id)
- should_not_email(@unsubscriber.id)
- should_not_email(@u_participating.id)
- should_not_email(@u_disabled.id)
notification.merge_mr(merge_request, @u_disabled)
- end
-
- def should_email(user_id)
- expect(Notify).to receive(:merged_merge_request_email).with(user_id, merge_request.id, @u_disabled.id)
- end
- def should_not_email(user_id)
- expect(Notify).not_to receive(:merged_merge_request_email).with(user_id, merge_request.id, @u_disabled.id)
+ should_email(merge_request.assignee)
+ should_email(@u_watcher)
+ should_email(@u_participant_mentioned)
+ should_email(@subscriber)
+ should_not_email(@unsubscriber)
+ should_not_email(@u_participating)
+ should_not_email(@u_disabled)
end
end
describe :reopen_merge_request do
it do
- should_email(merge_request.assignee_id)
- should_email(@u_watcher.id)
- should_email(@u_participant_mentioned.id)
- should_email(@subscriber.id)
- should_not_email(@unsubscriber.id)
- should_not_email(@u_participating.id)
- should_not_email(@u_disabled.id)
notification.reopen_mr(merge_request, @u_disabled)
- end
-
- def should_email(user_id)
- expect(Notify).to receive(:merge_request_status_email).with(user_id, merge_request.id, 'reopened', @u_disabled.id)
- end
- def should_not_email(user_id)
- expect(Notify).not_to receive(:merge_request_status_email).with(user_id, merge_request.id, 'reopened', @u_disabled.id)
+ should_email(merge_request.assignee)
+ should_email(@u_watcher)
+ should_email(@u_participant_mentioned)
+ should_email(@subscriber)
+ should_not_email(@unsubscriber)
+ should_not_email(@u_participating)
+ should_not_email(@u_disabled)
end
end
end
@@ -420,22 +347,16 @@ describe NotificationService do
before do
build_team(project)
+ ActionMailer::Base.deliveries.clear
end
describe :project_was_moved do
it do
- should_email(@u_watcher.id)
- should_email(@u_participating.id)
- should_not_email(@u_disabled.id)
notification.project_was_moved(project, "gitlab/gitlab")
- end
- def should_email(user_id)
- expect(Notify).to receive(:project_was_moved_email).with(project.id, user_id, "gitlab/gitlab")
- end
-
- def should_not_email(user_id)
- expect(Notify).not_to receive(:project_was_moved_email).with(project.id, user_id, "gitlab/gitlab")
+ should_email(@u_watcher)
+ should_email(@u_participating)
+ should_not_email(@u_disabled)
end
end
end
@@ -462,11 +383,26 @@ describe NotificationService do
def add_users_with_subscription(project, issuable)
@subscriber = create :user
@unsubscriber = create :user
+ @subscribed_participant = create(:user, username: 'subscribed_participant', notification_level: Notification::N_PARTICIPATING)
+ project.team << [@subscribed_participant, :master]
project.team << [@subscriber, :master]
project.team << [@unsubscriber, :master]
issuable.subscriptions.create(user: @subscriber, subscribed: true)
+ issuable.subscriptions.create(user: @subscribed_participant, subscribed: true)
issuable.subscriptions.create(user: @unsubscriber, subscribed: false)
end
+
+ def sent_to_user?(user)
+ ActionMailer::Base.deliveries.map(&:to).flatten.count(user.email) == 1
+ end
+
+ def should_email(user)
+ expect(sent_to_user?(user)).to be_truthy
+ end
+
+ def should_not_email(user)
+ expect(sent_to_user?(user)).to be_falsey
+ end
end
diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb
index e397b2b9b4a..1feba6ce048 100644
--- a/spec/services/projects/fork_service_spec.rb
+++ b/spec/services/projects/fork_service_spec.rb
@@ -25,13 +25,6 @@ describe Projects::ForkService do
end
end
- context 'fork project failure' do
- it "fails due to transaction failure" do
- @to_project = fork_project(@from_project, @to_user, false)
- expect(@to_project.import_failed?)
- end
- end
-
context 'project already exists' do
it "should fail due to validation, not transaction failure" do
@existing_project = create(:project, creator_id: @to_user.id, name: @from_project.name, namespace: @to_namespace)
@@ -66,7 +59,7 @@ describe Projects::ForkService do
context 'fork project for group' do
it 'group owner successfully forks project into the group' do
- to_project = fork_project(@project, @group_owner, true, @opts)
+ to_project = fork_project(@project, @group_owner, @opts)
expect(to_project.owner).to eq(@group)
expect(to_project.namespace).to eq(@group)
expect(to_project.name).to eq(@project.name)
@@ -78,7 +71,7 @@ describe Projects::ForkService do
context 'fork project for group when user not owner' do
it 'group developer should fail to fork project into the group' do
- to_project = fork_project(@project, @developer, true, @opts)
+ to_project = fork_project(@project, @developer, @opts)
expect(to_project.errors[:namespace]).to eq(['is not valid'])
end
end
@@ -87,7 +80,7 @@ describe Projects::ForkService do
it 'should fail due to validation, not transaction failure' do
existing_project = create(:project, name: @project.name,
namespace: @group)
- to_project = fork_project(@project, @group_owner, true, @opts)
+ to_project = fork_project(@project, @group_owner, @opts)
expect(existing_project.persisted?).to be_truthy
expect(to_project.errors[:name]).to eq(['has already been taken'])
expect(to_project.errors[:path]).to eq(['has already been taken'])
@@ -95,8 +88,8 @@ describe Projects::ForkService do
end
end
- def fork_project(from_project, user, fork_success = true, params = {})
- allow(RepositoryForkWorker).to receive(:perform_async).and_return(fork_success)
+ def fork_project(from_project, user, params = {})
+ allow(RepositoryForkWorker).to receive(:perform_async).and_return(true)
Projects::ForkService.new(from_project, user, params).execute
end
end
diff --git a/spec/services/update_release_service_spec.rb b/spec/services/update_release_service_spec.rb
new file mode 100644
index 00000000000..93368c45b88
--- /dev/null
+++ b/spec/services/update_release_service_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe UpdateReleaseService do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:tag_name) { project.repository.tag_names.first }
+ let(:description) { 'Awesome release!' }
+ let(:new_description) { 'The best release!' }
+ let(:service) { UpdateReleaseService.new(project, user) }
+
+ context 'with an existing release' do
+ let(:create_service) { CreateReleaseService.new(project, user) }
+
+ before do
+ create_service.execute(tag_name, description)
+ end
+
+ it 'successfully updates an existing release' do
+ result = service.execute(tag_name, new_description)
+ expect(result[:status]).to eq(:success)
+ expect(project.releases.find_by(tag: tag_name).description).to eq(new_description)
+ end
+ end
+
+ it 'raises an error if the tag does not exist' do
+ result = service.execute("foobar", description)
+ expect(result[:status]).to eq(:error)
+ end
+
+ it 'raises an error if the release does not exist' do
+ result = service.execute(tag_name, description)
+ expect(result[:status]).to eq(:error)
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 2be13bb3e6a..0225a0ee53f 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -31,6 +31,7 @@ RSpec.configure do |config|
config.include StubConfiguration
config.include RelativeUrl, type: feature
config.include TestEnv
+ config.include ActiveJob::TestHelper
config.include StubGitlabCalls
config.include StubGitlabData
config.include BenchmarkMatchers, benchmark: true
diff --git a/spec/support/filter_spec_helper.rb b/spec/support/filter_spec_helper.rb
index 97e5c270a59..91e3bee13c1 100644
--- a/spec/support/filter_spec_helper.rb
+++ b/spec/support/filter_spec_helper.rb
@@ -35,11 +35,24 @@ module FilterSpecHelper
pipeline.call(body)
end
- def reference_pipeline_result(body, contexts = {})
+ def reference_pipeline(contexts = {})
contexts.reverse_merge!(project: project) if defined?(project)
- pipeline = HTML::Pipeline.new([described_class, Gitlab::Markdown::ReferenceGathererFilter], contexts)
- pipeline.call(body)
+ filters = [
+ Gitlab::Markdown::AutolinkFilter,
+ described_class,
+ Gitlab::Markdown::ReferenceGathererFilter
+ ]
+
+ HTML::Pipeline.new(filters, contexts)
+ end
+
+ def reference_pipeline_result(body, contexts = {})
+ reference_pipeline(contexts).call(body)
+ end
+
+ def reference_filter(html, contexts = {})
+ reference_pipeline(contexts).to_document(html)
end
# Modify a String reference to make it invalid
diff --git a/spec/support/markdown_feature.rb b/spec/support/markdown_feature.rb
index bedc1a7f1db..d6d3062a197 100644
--- a/spec/support/markdown_feature.rb
+++ b/spec/support/markdown_feature.rb
@@ -93,6 +93,10 @@ class MarkdownFeature
end
end
+ def urls
+ Gitlab::Application.routes.url_helpers
+ end
+
def raw_markdown
markdown = File.read(Rails.root.join('spec/fixtures/markdown.md.erb'))
ERB.new(markdown).result(binding)
diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb
index 7500d0fdf80..7eadcd58c1f 100644
--- a/spec/support/matchers/markdown_matchers.rb
+++ b/spec/support/matchers/markdown_matchers.rb
@@ -71,7 +71,7 @@ module MarkdownMatchers
set_default_markdown_messages
match do |actual|
- expect(actual).to have_selector('a.gfm.gfm-project_member', count: 3)
+ expect(actual).to have_selector('a.gfm.gfm-project_member', count: 4)
end
end
@@ -80,7 +80,7 @@ module MarkdownMatchers
set_default_markdown_messages
match do |actual|
- expect(actual).to have_selector('a.gfm.gfm-issue', count: 3)
+ expect(actual).to have_selector('a.gfm.gfm-issue', count: 6)
end
end
@@ -89,7 +89,7 @@ module MarkdownMatchers
set_default_markdown_messages
match do |actual|
- expect(actual).to have_selector('a.gfm.gfm-merge_request', count: 3)
+ expect(actual).to have_selector('a.gfm.gfm-merge_request', count: 6)
expect(actual).to have_selector('em a.gfm-merge_request')
end
end
@@ -99,7 +99,7 @@ module MarkdownMatchers
set_default_markdown_messages
match do |actual|
- expect(actual).to have_selector('a.gfm.gfm-snippet', count: 2)
+ expect(actual).to have_selector('a.gfm.gfm-snippet', count: 5)
end
end
@@ -108,7 +108,7 @@ module MarkdownMatchers
set_default_markdown_messages
match do |actual|
- expect(actual).to have_selector('a.gfm.gfm-commit_range', count: 2)
+ expect(actual).to have_selector('a.gfm.gfm-commit_range', count: 5)
end
end
@@ -117,7 +117,7 @@ module MarkdownMatchers
set_default_markdown_messages
match do |actual|
- expect(actual).to have_selector('a.gfm.gfm-commit', count: 2)
+ expect(actual).to have_selector('a.gfm.gfm-commit', count: 5)
end
end
@@ -126,7 +126,7 @@ module MarkdownMatchers
set_default_markdown_messages
match do |actual|
- expect(actual).to have_selector('a.gfm.gfm-label', count: 3)
+ expect(actual).to have_selector('a.gfm.gfm-label', count: 4)
end
end
diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb
index 3bb568f4d49..33d2b14583c 100644
--- a/spec/support/mentionable_shared_examples.rb
+++ b/spec/support/mentionable_shared_examples.rb
@@ -10,12 +10,12 @@ def common_mentionable_setup
let(:mentioned_issue) { create(:issue, project: project) }
let!(:mentioned_mr) { create(:merge_request, :simple, source_project: project) }
- let(:mentioned_commit) { project.commit }
+ let(:mentioned_commit) { project.commit("HEAD~1") }
let(:ext_proj) { create(:project, :public) }
let(:ext_issue) { create(:issue, project: ext_proj) }
let(:ext_mr) { create(:merge_request, :simple, source_project: ext_proj) }
- let(:ext_commit) { ext_proj.commit }
+ let(:ext_commit) { ext_proj.commit("HEAD~2") }
# Override to add known commits to the repository stub.
let(:extra_commits) { [] }
@@ -45,14 +45,11 @@ def common_mentionable_setup
before do
# Wire the project's repository to return the mentioned commit, and +nil+
# for any unrecognized commits.
- commitmap = {
- mentioned_commit.id => mentioned_commit
- }
- extra_commits.each { |c| commitmap[c.short_id] = c }
-
- allow(Project).to receive(:find).and_call_original
- allow(Project).to receive(:find).with(project.id.to_s).and_return(project)
- allow(project.repository).to receive(:commit) { |sha| commitmap[sha] }
+ allow_any_instance_of(::Repository).to receive(:commit).and_call_original
+ allow_any_instance_of(::Repository).to receive(:commit).with(mentioned_commit.short_id).and_return(mentioned_commit)
+ extra_commits.each do |commit|
+ allow_any_instance_of(::Repository).to receive(:commit).with(commit.short_id).and_return(commit)
+ end
set_mentionable_text.call(ref_string)
end
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index 06559c3925d..63bed2414df 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -16,7 +16,7 @@ describe 'gitlab:app namespace rake task' do
end
def reenable_backup_sub_tasks
- %w{db repo uploads builds artifacts}.each do |subtask|
+ %w{db repo uploads builds artifacts lfs}.each do |subtask|
Rake::Task["gitlab:backup:#{subtask}:create"].reenable
end
end
@@ -49,7 +49,7 @@ describe 'gitlab:app namespace rake task' do
to raise_error(SystemExit)
end
- it 'should invoke restoration on mach' do
+ it 'should invoke restoration on match' do
allow(YAML).to receive(:load_file).
and_return({ gitlab_version: gitlab_version })
expect(Rake::Task["gitlab:backup:db:restore"]).to receive(:invoke)
@@ -57,6 +57,7 @@ describe 'gitlab:app namespace rake task' do
expect(Rake::Task["gitlab:backup:builds:restore"]).to receive(:invoke)
expect(Rake::Task["gitlab:backup:uploads:restore"]).to receive(:invoke)
expect(Rake::Task["gitlab:backup:artifacts:restore"]).to receive(:invoke)
+ expect(Rake::Task["gitlab:backup:lfs:restore"]).to receive(:invoke)
expect(Rake::Task["gitlab:shell:setup"]).to receive(:invoke)
expect { run_rake_task('gitlab:backup:restore') }.not_to raise_error
end
@@ -114,7 +115,7 @@ describe 'gitlab:app namespace rake task' do
it 'should set correct permissions on the tar contents' do
tar_contents, exit_status = Gitlab::Popen.popen(
- %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz}
+ %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz lfs.tar.gz}
)
expect(exit_status).to eq(0)
expect(tar_contents).to match('db/')
@@ -122,12 +123,13 @@ describe 'gitlab:app namespace rake task' do
expect(tar_contents).to match('repositories/')
expect(tar_contents).to match('builds.tar.gz')
expect(tar_contents).to match('artifacts.tar.gz')
+ expect(tar_contents).to match('lfs.tar.gz')
expect(tar_contents).not_to match(/^.{4,9}[rwx].* (database.sql.gz|uploads.tar.gz|repositories|builds.tar.gz|artifacts.tar.gz)\/$/)
end
it 'should delete temp directories' do
temp_dirs = Dir.glob(
- File.join(Gitlab.config.backup.path, '{db,repositories,uploads,builds,artifacts}')
+ File.join(Gitlab.config.backup.path, '{db,repositories,uploads,builds,artifacts,lfs}')
)
expect(temp_dirs).to be_empty
@@ -149,7 +151,7 @@ describe 'gitlab:app namespace rake task' do
# Redirect STDOUT and run the rake task
orig_stdout = $stdout
$stdout = StringIO.new
- ENV["SKIP"] = "repositories"
+ ENV["SKIP"] = "repositories,uploads"
run_rake_task('gitlab:backup:create')
$stdout = orig_stdout
@@ -163,13 +165,14 @@ describe 'gitlab:app namespace rake task' do
it "does not contain skipped item" do
tar_contents, _exit_status = Gitlab::Popen.popen(
- %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz}
+ %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz lfs.tar.gz}
)
expect(tar_contents).to match('db/')
expect(tar_contents).to match('uploads.tar.gz')
expect(tar_contents).to match('builds.tar.gz')
expect(tar_contents).to match('artifacts.tar.gz')
+ expect(tar_contents).to match('lfs.tar.gz')
expect(tar_contents).not_to match('repositories/')
end
@@ -180,8 +183,10 @@ describe 'gitlab:app namespace rake task' do
expect(Rake::Task["gitlab:backup:db:restore"]).to receive :invoke
expect(Rake::Task["gitlab:backup:repo:restore"]).not_to receive :invoke
+ expect(Rake::Task["gitlab:backup:uploads:restore"]).not_to receive :invoke
expect(Rake::Task["gitlab:backup:builds:restore"]).to receive :invoke
expect(Rake::Task["gitlab:backup:artifacts:restore"]).to receive :invoke
+ expect(Rake::Task["gitlab:backup:lfs:restore"]).to receive :invoke
expect(Rake::Task["gitlab:shell:setup"]).to receive :invoke
expect { run_rake_task('gitlab:backup:restore') }.not_to raise_error
end
diff --git a/spec/workers/email_receiver_worker_spec.rb b/spec/workers/email_receiver_worker_spec.rb
index 65a8d7d9197..de40a6f78af 100644
--- a/spec/workers/email_receiver_worker_spec.rb
+++ b/spec/workers/email_receiver_worker_spec.rb
@@ -21,12 +21,14 @@ describe EmailReceiverWorker do
end
it "sends out a rejection email" do
- described_class.new.perform(raw_message)
-
- email = ActionMailer::Base.deliveries.last
- expect(email).not_to be_nil
- expect(email.to).to eq(["jake@adventuretime.ooo"])
- expect(email.subject).to include("Rejected")
+ perform_enqueued_jobs do
+ described_class.new.perform(raw_message)
+
+ email = ActionMailer::Base.deliveries.last
+ expect(email).not_to be_nil
+ expect(email.to).to eq(["jake@adventuretime.ooo"])
+ expect(email.subject).to include("Rejected")
+ end
end
end
end
diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb
index aa031106968..245f066df1f 100644
--- a/spec/workers/repository_fork_worker_spec.rb
+++ b/spec/workers/repository_fork_worker_spec.rb
@@ -12,7 +12,6 @@ describe RepositoryForkWorker do
project.path_with_namespace,
fork_project.namespace.path).
and_return(true)
- expect(ProjectCacheWorker).to receive(:perform_async)
subject.perform(project.id,
project.path_with_namespace,