summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--CHANGELOG49
-rw-r--r--GITLAB_GIT_HTTP_SERVER_VERSION1
-rw-r--r--Gemfile27
-rw-r--r--Gemfile.lock54
-rw-r--r--app/assets/images/logo.svg16
-rw-r--r--app/assets/javascripts/ci/build.coffee2
-rw-r--r--app/assets/javascripts/merge_request_tabs.js.coffee8
-rw-r--r--app/assets/javascripts/shortcuts_navigation.coffee1
-rw-r--r--app/assets/stylesheets/framework/blocks.scss50
-rw-r--r--app/assets/stylesheets/framework/buttons.scss11
-rw-r--r--app/assets/stylesheets/framework/files.scss5
-rw-r--r--app/assets/stylesheets/framework/lists.scss2
-rw-r--r--app/assets/stylesheets/framework/mixins.scss6
-rw-r--r--app/assets/stylesheets/framework/selects.scss4
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss13
-rw-r--r--app/assets/stylesheets/framework/tables.scss10
-rw-r--r--app/assets/stylesheets/framework/timeline.scss6
-rw-r--r--app/assets/stylesheets/framework/typography.scss88
-rw-r--r--app/assets/stylesheets/highlight/dark.scss11
-rw-r--r--app/assets/stylesheets/highlight/monokai.scss13
-rw-r--r--app/assets/stylesheets/highlight/solarized_dark.scss9
-rw-r--r--app/assets/stylesheets/highlight/solarized_light.scss9
-rw-r--r--app/assets/stylesheets/highlight/white.scss18
-rw-r--r--app/assets/stylesheets/pages/ci_projects.scss5
-rw-r--r--app/assets/stylesheets/pages/events.scss2
-rw-r--r--app/assets/stylesheets/pages/help.scss4
-rw-r--r--app/assets/stylesheets/pages/issuable.scss8
-rw-r--r--app/assets/stylesheets/pages/issues.scss5
-rw-r--r--app/assets/stylesheets/pages/notes.scss5
-rw-r--r--app/assets/stylesheets/pages/profile.scss6
-rw-r--r--app/assets/stylesheets/pages/projects.scss72
-rw-r--r--app/assets/stylesheets/pages/runners.scss52
-rw-r--r--app/assets/stylesheets/pages/snippets.scss60
-rw-r--r--app/assets/stylesheets/pages/tree.scss16
-rw-r--r--app/controllers/abuse_reports_controller.rb4
-rw-r--r--app/controllers/admin/application_settings_controller.rb1
-rw-r--r--app/controllers/admin/broadcast_messages_controller.rb2
-rw-r--r--app/controllers/admin/hooks_controller.rb2
-rw-r--r--app/controllers/admin/services_controller.rb8
-rw-r--r--app/controllers/admin/users_controller.rb26
-rw-r--r--app/controllers/application_controller.rb13
-rw-r--r--app/controllers/ci/admin/runners_controller.rb4
-rw-r--r--app/controllers/ci/projects_controller.rb11
-rw-r--r--app/controllers/import/bitbucket_controller.rb2
-rw-r--r--app/controllers/import/fogbugz_controller.rb2
-rw-r--r--app/controllers/import/github_controller.rb6
-rw-r--r--app/controllers/import/gitlab_controller.rb2
-rw-r--r--app/controllers/import/gitorious_controller.rb2
-rw-r--r--app/controllers/import/google_code_controller.rb8
-rw-r--r--app/controllers/invites_controller.rb4
-rw-r--r--app/controllers/profiles/notifications_controller.rb2
-rw-r--r--app/controllers/profiles_controller.rb2
-rw-r--r--app/controllers/projects/avatars_controller.rb2
-rw-r--r--app/controllers/projects/blob_controller.rb6
-rw-r--r--app/controllers/projects/builds_controller.rb25
-rw-r--r--app/controllers/projects/ci_services_controller.rb10
-rw-r--r--app/controllers/projects/ci_web_hooks_controller.rb2
-rw-r--r--app/controllers/projects/compare_controller.rb3
-rw-r--r--app/controllers/projects/deploy_keys_controller.rb2
-rw-r--r--app/controllers/projects/hooks_controller.rb2
-rw-r--r--app/controllers/projects/issues_controller.rb13
-rw-r--r--app/controllers/projects/merge_requests_controller.rb9
-rw-r--r--app/controllers/projects/milestones_controller.rb6
-rw-r--r--app/controllers/projects/notes_controller.rb4
-rw-r--r--app/controllers/projects/project_members_controller.rb3
-rw-r--r--app/controllers/projects/raw_controller.rb2
-rw-r--r--app/controllers/projects/refs_controller.rb7
-rw-r--r--app/controllers/projects/repositories_controller.rb17
-rw-r--r--app/controllers/projects/runners_controller.rb2
-rw-r--r--app/controllers/projects/services_controller.rb10
-rw-r--r--app/controllers/projects/snippets_controller.rb1
-rw-r--r--app/controllers/projects/tree_controller.rb6
-rw-r--r--app/controllers/projects/uploads_controller.rb2
-rw-r--r--app/controllers/projects_controller.rb30
-rw-r--r--app/controllers/uploads_controller.rb6
-rw-r--r--app/finders/issuable_finder.rb114
-rw-r--r--app/helpers/appearances_helper.rb2
-rw-r--r--app/helpers/application_helper.rb10
-rw-r--r--app/helpers/diff_helper.rb3
-rw-r--r--app/helpers/gitlab_markdown_helper.rb21
-rw-r--r--app/helpers/gitlab_routing_helper.rb4
-rw-r--r--app/helpers/issues_helper.rb4
-rw-r--r--app/helpers/labels_helper.rb18
-rw-r--r--app/helpers/merge_requests_helper.rb2
-rw-r--r--app/helpers/preferences_helper.rb8
-rw-r--r--app/helpers/projects_helper.rb10
-rw-r--r--app/helpers/runners_helper.rb35
-rw-r--r--app/helpers/tab_helper.rb18
-rw-r--r--app/mailers/abuse_report_mailer.rb12
-rw-r--r--app/models/ability.rb5
-rw-r--r--app/models/application_setting.rb4
-rw-r--r--app/models/ci/build.rb32
-rw-r--r--app/models/ci/commit.rb54
-rw-r--r--app/models/ci/project.rb8
-rw-r--r--app/models/ci/runner.rb21
-rw-r--r--app/models/commit.rb16
-rw-r--r--app/models/commit_status.rb7
-rw-r--r--app/models/concerns/issuable.rb12
-rw-r--r--app/models/concerns/mentionable.rb52
-rw-r--r--app/models/concerns/participable.rb28
-rw-r--r--app/models/group.rb2
-rw-r--r--app/models/group_label.rb9
-rw-r--r--app/models/group_milestone.rb12
-rw-r--r--app/models/issue.rb10
-rw-r--r--app/models/merge_request.rb22
-rw-r--r--app/models/merge_request_diff.rb7
-rw-r--r--app/models/milestone.rb32
-rw-r--r--app/models/namespace.rb2
-rw-r--r--app/models/note.rb22
-rw-r--r--app/models/project.rb15
-rw-r--r--app/models/project_services/bamboo_service.rb2
-rw-r--r--app/models/project_services/teamcity_service.rb2
-rw-r--r--app/models/project_team.rb19
-rw-r--r--app/models/repository.rb41
-rw-r--r--app/models/service.rb35
-rw-r--r--app/models/user.rb27
-rw-r--r--app/services/archive_repository_service.rb47
-rw-r--r--app/services/ci/create_builds_service.rb14
-rw-r--r--app/services/ci/register_build_service.rb2
-rw-r--r--app/services/git_push_service.rb55
-rw-r--r--app/services/issues/create_service.rb2
-rw-r--r--app/services/issues/update_service.rb2
-rw-r--r--app/services/labels/group_service.rb26
-rw-r--r--app/services/merge_requests/create_service.rb2
-rw-r--r--app/services/merge_requests/post_merge_service.rb10
-rw-r--r--app/services/merge_requests/refresh_service.rb90
-rw-r--r--app/services/merge_requests/update_service.rb2
-rw-r--r--app/services/notes/create_service.rb8
-rw-r--r--app/services/notes/update_service.rb2
-rw-r--r--app/services/projects/transfer_service.rb4
-rw-r--r--app/services/system_note_service.rb27
-rw-r--r--app/uploaders/file_uploader.rb2
-rw-r--r--app/views/abuse_report_mailer/notify.html.haml11
-rw-r--r--app/views/abuse_report_mailer/notify.text.haml5
-rw-r--r--app/views/admin/abuse_reports/index.html.haml21
-rw-r--r--app/views/admin/application_settings/_form.html.haml6
-rw-r--r--app/views/admin/applications/show.html.haml37
-rw-r--r--app/views/admin/background_jobs/show.html.haml37
-rw-r--r--app/views/admin/deploy_keys/index.html.haml37
-rw-r--r--app/views/admin/identities/index.html.haml15
-rw-r--r--app/views/admin/services/index.html.haml39
-rw-r--r--app/views/admin/users/_profile.html.haml (renamed from app/views/users/_profile.html.haml)0
-rw-r--r--app/views/admin/users/show.html.haml4
-rw-r--r--app/views/ci/admin/events/index.html.haml33
-rw-r--r--app/views/ci/admin/projects/index.html.haml23
-rw-r--r--app/views/ci/admin/runners/index.html.haml27
-rw-r--r--app/views/ci/events/index.html.haml33
-rw-r--r--app/views/ci/lints/_create.html.haml45
-rw-r--r--app/views/ci/projects/index.html.haml20
-rw-r--r--app/views/dashboard/milestones/_issue.html.haml2
-rw-r--r--app/views/dashboard/milestones/_merge_request.html.haml2
-rw-r--r--app/views/dashboard/milestones/show.html.haml44
-rw-r--r--app/views/dashboard/projects/_projects.html.haml3
-rw-r--r--app/views/dashboard/snippets/index.html.haml26
-rw-r--r--app/views/doorkeeper/applications/index.html.haml30
-rw-r--r--app/views/doorkeeper/applications/show.html.haml38
-rw-r--r--app/views/doorkeeper/authorized_applications/index.html.haml25
-rw-r--r--app/views/events/_event_issue.atom.haml3
-rw-r--r--app/views/events/_event_merge_request.atom.haml3
-rw-r--r--app/views/events/_event_note.atom.haml2
-rw-r--r--app/views/events/_event_push.atom.haml2
-rw-r--r--app/views/explore/snippets/index.html.haml3
-rw-r--r--app/views/groups/_projects.html.haml3
-rw-r--r--app/views/groups/group_members/_group_member.html.haml2
-rw-r--r--app/views/groups/milestones/_issue.html.haml2
-rw-r--r--app/views/groups/milestones/_merge_request.html.haml2
-rw-r--r--app/views/groups/milestones/show.html.haml44
-rw-r--r--app/views/help/_shortcuts.html.haml6
-rw-r--r--app/views/import/bitbucket/status.html.haml77
-rw-r--r--app/views/import/fogbugz/new_user_map.html.haml31
-rw-r--r--app/views/import/fogbugz/status.html.haml63
-rw-r--r--app/views/import/github/status.html.haml63
-rw-r--r--app/views/import/gitlab/status.html.haml63
-rw-r--r--app/views/import/gitorious/status.html.haml63
-rw-r--r--app/views/import/google_code/status.html.haml77
-rw-r--r--app/views/layouts/_page.html.haml2
-rw-r--r--app/views/layouts/ci/_page.html.haml2
-rw-r--r--app/views/layouts/nav/_project.html.haml10
-rw-r--r--app/views/layouts/notify.html.haml4
-rw-r--r--app/views/notify/_note_message.html.haml2
-rw-r--r--app/views/notify/new_issue_email.html.haml2
-rw-r--r--app/views/notify/new_merge_request_email.html.haml2
-rw-r--r--app/views/profiles/_event_table.html.haml29
-rw-r--r--app/views/profiles/preferences/show.html.haml4
-rw-r--r--app/views/profiles/show.html.haml2
-rw-r--r--app/views/projects/_activity.html.haml1
-rw-r--r--app/views/projects/_files.html.haml6
-rw-r--r--app/views/projects/_last_commit.html.haml12
-rw-r--r--app/views/projects/_md_preview.html.haml4
-rw-r--r--app/views/projects/_readme.html.haml13
-rw-r--r--app/views/projects/_zen.html.haml6
-rw-r--r--app/views/projects/activity.html.haml2
-rw-r--r--app/views/projects/blob/_blob.html.haml31
-rw-r--r--app/views/projects/blob/show.html.haml3
-rw-r--r--app/views/projects/builds/_build.html.haml53
-rw-r--r--app/views/projects/builds/index.html.haml53
-rw-r--r--app/views/projects/builds/show.html.haml23
-rw-r--r--app/views/projects/buttons/_notifications.html.haml32
-rw-r--r--app/views/projects/ci_services/index.html.haml2
-rw-r--r--app/views/projects/ci_web_hooks/index.html.haml23
-rw-r--r--app/views/projects/commit/ci.html.haml58
-rw-r--r--app/views/projects/commit_statuses/_commit_status.html.haml9
-rw-r--r--app/views/projects/diffs/_diffs.html.haml4
-rw-r--r--app/views/projects/diffs/_file.html.haml4
-rw-r--r--app/views/projects/edit.html.haml18
-rw-r--r--app/views/projects/empty.html.haml85
-rw-r--r--app/views/projects/imports/new.html.haml2
-rw-r--r--app/views/projects/issues/_closed_by_box.html.haml3
-rw-r--r--app/views/projects/issues/_issues.html.haml7
-rw-r--r--app/views/projects/issues/show.html.haml3
-rw-r--r--app/views/projects/merge_requests/_merge_requests.html.haml6
-rw-r--r--app/views/projects/merge_requests/_show.html.haml3
-rw-r--r--app/views/projects/merge_requests/widget/_heading.html.haml73
-rw-r--r--app/views/projects/milestones/_issue.html.haml2
-rw-r--r--app/views/projects/milestones/_merge_request.html.haml2
-rw-r--r--app/views/projects/milestones/show.html.haml2
-rw-r--r--app/views/projects/notes/_note.html.haml6
-rw-r--r--app/views/projects/project_members/_project_member.html.haml2
-rw-r--r--app/views/projects/protected_branches/_branches_list.html.haml59
-rw-r--r--app/views/projects/remove_fork.js.haml2
-rw-r--r--app/views/projects/runners/show.html.haml101
-rw-r--r--app/views/projects/services/index.html.haml39
-rw-r--r--app/views/projects/show.html.haml17
-rw-r--r--app/views/projects/snippets/_actions.html.haml11
-rw-r--r--app/views/projects/snippets/index.html.haml20
-rw-r--r--app/views/projects/snippets/show.html.haml46
-rw-r--r--app/views/projects/tree/_blob_item.html.haml2
-rw-r--r--app/views/projects/tree/_readme.html.haml6
-rw-r--r--app/views/projects/tree/_tree_content.html.haml (renamed from app/views/projects/tree/_tree.html.haml)37
-rw-r--r--app/views/projects/tree/_tree_header.html.haml32
-rw-r--r--app/views/projects/tree/_tree_item.html.haml2
-rw-r--r--app/views/projects/tree/show.html.haml8
-rw-r--r--app/views/projects/triggers/index.html.haml13
-rw-r--r--app/views/projects/wikis/history.html.haml49
-rw-r--r--app/views/shared/_clone_panel.html.haml4
-rw-r--r--app/views/shared/_logo.svg21
-rw-r--r--app/views/shared/issuable/_filter.html.haml9
-rw-r--r--app/views/shared/snippets/_header.html.haml24
-rw-r--r--app/views/shared/snippets/_snippet.html.haml1
-rw-r--r--app/views/snippets/_actions.html.haml11
-rw-r--r--app/views/snippets/show.html.haml51
-rw-r--r--app/views/users/calendar.html.haml6
-rw-r--r--app/views/users/show.html.haml90
-rw-r--r--app/workers/repository_archive_cache_worker.rb9
-rw-r--r--app/workers/repository_archive_worker.rb43
-rw-r--r--config/environments/development.rb2
-rw-r--r--config/gitlab.yml.example24
-rw-r--r--config/initializers/1_settings.rb6
-rw-r--r--config/initializers/active_record_query_trace.rb5
-rw-r--r--config/initializers/bullet.rb6
-rw-r--r--config/initializers/rack_lineprof.rb31
-rw-r--r--config/mail_room.yml39
-rw-r--r--config/mail_room.yml.example29
-rw-r--r--config/routes.rb11
-rw-r--r--db/fixtures/development/05_users.rb4
-rw-r--r--db/fixtures/development/07_milestones.rb2
-rw-r--r--db/fixtures/development/09_issues.rb2
-rw-r--r--db/fixtures/development/12_snippets.rb2
-rw-r--r--db/migrate/20151008110232_add_users_lower_username_email_indexes.rb17
-rw-r--r--db/migrate/20151008143519_add_admin_notification_email_setting.rb5
-rw-r--r--db/migrate/20151016131433_add_ci_projects_gl_project_id_index.rb5
-rw-r--r--db/migrate/20151016195451_add_ci_builds_and_projects_indexes.rb9
-rw-r--r--db/migrate/20151016195706_add_notes_line_code_index.rb5
-rw-r--r--db/migrate/20151019111551_fix_build_tags.rb5
-rw-r--r--db/migrate/20151019111703_fail_build_without_names.rb5
-rw-r--r--db/migrate/20151020173516_ci_limits_to_mysql.rb9
-rw-r--r--db/migrate/20151020173906_add_ci_builds_index_for_status.rb5
-rw-r--r--db/migrate/limits_to_mysql.rb4
-rw-r--r--db/schema.rb11
-rw-r--r--doc/ci/docker/using_docker_images.md2
-rw-r--r--doc/ci/variables/README.md38
-rw-r--r--doc/ci/yaml/README.md52
-rw-r--r--doc/development/profiling.md56
-rw-r--r--doc/incoming_email/README.md273
-rw-r--r--doc/install/installation.md5
-rw-r--r--doc/raketasks/backup_restore.md2
-rw-r--r--doc/update/7.14-to-8.0.md1
-rw-r--r--doc/update/8.0-to-8.1.md171
-rw-r--r--doc/update/patch_versions.md4
-rw-r--r--features/dashboard/new_project.feature10
-rw-r--r--features/project/project.feature6
-rw-r--r--features/project/snippets.feature2
-rw-r--r--features/project/source/browse_files.feature6
-rw-r--r--features/snippets/snippets.feature2
-rw-r--r--features/steps/abuse_reports.rb2
-rw-r--r--features/steps/admin/projects.rb2
-rw-r--r--features/steps/dashboard/new_project.rb6
-rw-r--r--features/steps/project/commits/commits.rb2
-rw-r--r--features/steps/project/project.rb4
-rw-r--r--features/steps/project/snippets.rb8
-rw-r--r--features/steps/project/source/browse_files.rb13
-rw-r--r--features/steps/shared/project.rb7
-rw-r--r--features/steps/snippets/snippets.rb6
-rw-r--r--features/steps/snippets/user.rb6
-rw-r--r--lib/api/entities.rb12
-rw-r--r--lib/api/merge_requests.rb12
-rw-r--r--lib/api/projects.rb4
-rw-r--r--lib/api/repositories.rb13
-rw-r--r--lib/api/services.rb2
-rw-r--r--lib/ci/gitlab_ci_yaml_processor.rb43
-rw-r--r--lib/ci/migrate/builds.rb29
-rw-r--r--lib/ci/migrate/database.rb67
-rw-r--r--lib/ci/migrate/manager.rb72
-rw-r--r--lib/ci/migrate/tags.rb42
-rw-r--r--lib/ci/status.rb21
-rw-r--r--lib/extracts_path.rb2
-rw-r--r--lib/gitlab/backend/grack_auth.rb9
-rw-r--r--lib/gitlab/database.rb2
-rw-r--r--lib/gitlab/incoming_email.rb4
-rw-r--r--lib/gitlab/markdown.rb120
-rw-r--r--lib/gitlab/markdown/commit_range_reference_filter.rb16
-rw-r--r--lib/gitlab/markdown/commit_reference_filter.rb24
-rw-r--r--lib/gitlab/markdown/cross_project_reference.rb11
-rw-r--r--lib/gitlab/markdown/external_issue_reference_filter.rb3
-rw-r--r--lib/gitlab/markdown/issue_reference_filter.rb8
-rw-r--r--lib/gitlab/markdown/label_reference_filter.rb8
-rw-r--r--lib/gitlab/markdown/merge_request_reference_filter.rb8
-rw-r--r--lib/gitlab/markdown/redactor_filter.rb40
-rw-r--r--lib/gitlab/markdown/reference_filter.rb71
-rw-r--r--lib/gitlab/markdown/reference_gatherer_filter.rb63
-rw-r--r--lib/gitlab/markdown/snippet_reference_filter.rb8
-rw-r--r--lib/gitlab/markdown/upload_link_filter.rb47
-rw-r--r--lib/gitlab/markdown/user_reference_filter.rb45
-rw-r--r--lib/gitlab/reference_extractor.rb30
-rw-r--r--lib/gitlab/uploads_transfer.rb35
-rw-r--r--lib/support/nginx/gitlab20
-rw-r--r--lib/support/nginx/gitlab-ssl20
-rw-r--r--lib/tasks/ci/migrate.rake87
-rw-r--r--lib/tasks/gitlab/check.rake39
-rw-r--r--lib/tasks/migrate/setup_postgresql.rake2
-rw-r--r--public/uploads/.gitkeep0
-rw-r--r--spec/benchmarks/lib/gitlab/markdown/reference_filter_spec.rb41
-rw-r--r--spec/benchmarks/models/milestone_spec.rb17
-rw-r--r--spec/benchmarks/models/project_team_spec.rb23
-rw-r--r--spec/benchmarks/models/user_spec.rb4
-rw-r--r--spec/controllers/abuse_reports_controller_spec.rb72
-rw-r--r--spec/controllers/admin/users_controller_spec.rb26
-rw-r--r--spec/controllers/import/github_controller_spec.rb2
-rw-r--r--spec/controllers/invites_controller_spec.rb33
-rw-r--r--spec/controllers/projects/repositories_controller_spec.rb28
-rw-r--r--spec/controllers/projects/services_controller_spec.rb47
-rw-r--r--spec/controllers/projects/uploads_controller_spec.rb4
-rw-r--r--spec/controllers/projects_controller_spec.rb109
-rw-r--r--spec/features/builds_spec.rb48
-rw-r--r--spec/features/commits_spec.rb10
-rw-r--r--spec/features/markdown_spec.rb2
-rw-r--r--spec/features/projects_spec.rb29
-rw-r--r--spec/helpers/application_helper_spec.rb9
-rw-r--r--spec/helpers/gitlab_markdown_helper_spec.rb27
-rw-r--r--spec/helpers/issues_helper_spec.rb10
-rw-r--r--spec/helpers/labels_helper_spec.rb5
-rw-r--r--spec/helpers/projects_helper_spec.rb14
-rw-r--r--spec/helpers/runners_helper_spec.rb2
-rw-r--r--spec/lib/ci/gitlab_ci_yaml_processor_spec.rb45
-rw-r--r--spec/lib/gitlab/closing_issue_extractor_spec.rb10
-rw-r--r--spec/lib/gitlab/email/attachment_uploader_spec.rb1
-rw-r--r--spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb70
-rw-r--r--spec/lib/gitlab/markdown/commit_reference_filter_spec.rb66
-rw-r--r--spec/lib/gitlab/markdown/cross_project_reference_spec.rb36
-rw-r--r--spec/lib/gitlab/markdown/issue_reference_filter_spec.rb76
-rw-r--r--spec/lib/gitlab/markdown/label_reference_filter_spec.rb18
-rw-r--r--spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb66
-rw-r--r--spec/lib/gitlab/markdown/redactor_filter_spec.rb91
-rw-r--r--spec/lib/gitlab/markdown/reference_gatherer_filter_spec.rb89
-rw-r--r--spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb64
-rw-r--r--spec/lib/gitlab/markdown/upload_link_filter_spec.rb75
-rw-r--r--spec/lib/gitlab/markdown/user_reference_filter_spec.rb55
-rw-r--r--spec/lib/gitlab/reference_extractor_spec.rb14
-rw-r--r--spec/lib/gitlab/uploads_transfer_spec.rb50
-rw-r--r--spec/models/build_spec.rb135
-rw-r--r--spec/models/ci/commit_spec.rb242
-rw-r--r--spec/models/ci/project_spec.rb31
-rw-r--r--spec/models/ci/runner_spec.rb67
-rw-r--r--spec/models/commit_spec.rb4
-rw-r--r--spec/models/concerns/issuable_spec.rb1
-rw-r--r--spec/models/concerns/mentionable_spec.rb2
-rw-r--r--spec/models/issue_spec.rb39
-rw-r--r--spec/models/milestone_spec.rb28
-rw-r--r--spec/models/note_spec.rb5
-rw-r--r--spec/models/project_services/bamboo_service_spec.rb74
-rw-r--r--spec/models/project_services/teamcity_service_spec.rb73
-rw-r--r--spec/models/service_spec.rb110
-rw-r--r--spec/requests/api/merge_requests_spec.rb5
-rw-r--r--spec/requests/api/projects_spec.rb50
-rw-r--r--spec/requests/api/repositories_spec.rb9
-rw-r--r--spec/requests/api/services_spec.rb33
-rw-r--r--spec/requests/ci/api/builds_spec.rb5
-rw-r--r--spec/services/archive_repository_service_spec.rb69
-rw-r--r--spec/services/git_push_service_spec.rb10
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb21
-rw-r--r--spec/services/projects/download_service_spec.rb2
-rw-r--r--spec/services/projects/transfer_service_spec.rb2
-rw-r--r--spec/services/projects/upload_service_spec.rb4
-rw-r--r--spec/services/system_note_service_spec.rb12
-rw-r--r--spec/support/filter_spec_helper.rb23
-rw-r--r--spec/support/markdown_feature.rb32
-rw-r--r--spec/support/mentionable_shared_examples.rb17
-rw-r--r--spec/support/services_shared_context.rb8
-rw-r--r--spec/support/test_env.rb2
-rw-r--r--spec/workers/repository_archive_worker_spec.rb79
401 files changed, 5697 insertions, 3224 deletions
diff --git a/.gitignore b/.gitignore
index 2a97eacad48..73bde4cc761 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,7 +25,6 @@ config/initializers/rack_attack.rb
config/initializers/smtp_settings.rb
config/resque.yml
config/unicorn.rb
-config/mail_room.yml
config/secrets.yml
coverage/*
db/*.sqlite3
diff --git a/CHANGELOG b/CHANGELOG
index c3fd5baa67a..fa630f354c4 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,13 +1,41 @@
Please view this file on the master branch, on stable branches it's out of date.
-v 8.1.0 (unreleased)
+v 8.2.0 (unreleased)
+ - 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
+ - Adds ability to remove the forked relationship from project settings screen. (Han Loong Liauw)
+ - Improved performance of sorting milestone issues
+ - Allow users to select the Files view as default project view (Cristian Bica)
+ - Show "Empty Repository Page" for repository without branches (Artem V. Navrotskiy)
+
+v 8.1.0
+ - Ensure MySQL CI limits DB migrations occur after the fields have been created (Stan Hu)
+ - Fix duplicate repositories in GitHub import page (Stan Hu)
+ - Redirect to a default path if HTTP_REFERER is not set (Stan Hu)
+ - Fix CSS for runner status
+ - Send an email to admin email when a user is reported for spam (Jonathan Rochkind)
+ - Show notifications button when user is member of group rather than project (Grzegorz Bizon)
+ - Fix bug preventing mentioned issued from being closed when MR is merged using fast-forward merge.
+ - Fix nonatomic database update potentially causing project star counts to go negative (Stan Hu)
+ - Don't show "Add README" link in an empty repository if user doesn't have access to push (Stan Hu)
+ - Fix error preventing displaying of commit data for a directory with a leading dot (Stan Hu)
+ - Speed up load times of issue detail pages by roughly 1.5x
+ - Require CI jobs to be named
+ - If a merge request is to close an issue, show this on the issue page (Zeger-Jan van de Weg)
+ - Add a system note and update relevant merge requests when a branch is deleted or re-added (Stan Hu)
- Make diff file view easier to use on mobile screens (Stan Hu)
+ - Improved performance of finding users by username or Email address
+ - Fix 500 when editing CI service
+ - Fix bug where merge request comments created by API would not trigger notifications (Stan Hu)
- Add support for creating directories from Files page (Stan Hu)
- Allow removing of project without confirmation when JavaScript is disabled (Stan Hu)
- Support filtering by "Any" milestone or issue and fix "No Milestone" and "No Label" filters (Stan Hu)
- Improved performance of the trending projects page
+ - Remove CI migration task
- Improved performance of finding projects by their namespace
- Fix bug where transferring a project would result in stale commit links (Stan Hu)
+ - Fix build trace updating
- Include full path of source and target branch names in New Merge Request page (Stan Hu)
- Add user preference to view activities as default dashboard (Stan Hu)
- Add option to admin area to sign in as a specific user (Pavel Forkert)
@@ -17,7 +45,10 @@ v 8.1.0 (unreleased)
- Fix cases where Markdown did not render links in activity feed (Stan Hu)
- Add first and last to pagination (Zeger-Jan van de Weg)
- Added Commit Status API
+ - Added Builds View
+ - Added when to .gitlab-ci.yml
- Show CI status on commit page
+ - Added CI_BUILD_TAG, _STAGE, _NAME and _TRIGGERED to CI builds
- 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)
@@ -27,6 +58,7 @@ v 8.1.0 (unreleased)
- Move CI triggers page to project settings area
- Move CI project settings page to CE project settings area
- Fix bug when removed file was not appearing in merge request diff
+ - Show warning when build cannot be served by any of the available CI runners
- Note the original location of a moved project when notifying users of the move
- Improve error message when merging fails
- Add support of multibyte characters in LDAP UID (Roman Petrov)
@@ -38,7 +70,7 @@ v 8.1.0 (unreleased)
- Move CI web hooks page to project settings area
- Fix User Identities API. It now allows you to properly create or update user's identities.
- Add user preference to change layout width (Peter Göbel)
- - Use commit status in merge request widget as preffered source of CI status
+ - Use commit status in merge request widget as preferred source of CI status
- Integrate CI commit and build pages into project pages
- Move CI services page to project settings area
- Add "Quick Submit" behavior to input fields throughout the application. Use
@@ -46,11 +78,24 @@ v 8.1.0 (unreleased)
- Fix position of hamburger in header for smaller screens (Han Loong Liauw)
- Fix bug where Emojis in Markdown would truncate remaining text (Sakata Sinji)
- Persist filters when sorting on admin user page (Jerry Lukins)
+ - Update style of snippets pages (Han Loong Liauw)
+ - Allow dashboard and group issues/MRs to be filtered by label
- Add spellcheck=false to certain input fields
- Invalidate stored service password if the endpoint URL is changed
- Project names are not fully shown if group name is too big, even on group page view
- Apply new design for Files page
- Add "New Page" button to Wiki Pages tab (Stan Hu)
+ - Only render 404 page from /public
+ - Hide passwords from services API (Alex Lossent)
+ - Fix: Images cannot show when projects' path was changed
+ - Let gitlab-git-http-server generate and serve 'git archive' downloads
+ - Optimize query when filtering on issuables (Zeger-Jan van de Weg)
+ - Fix padding of outdated discussion item.
+ - Animate the logo on hover
+
+v 8.0.5
+ - Correct lookup-by-email for LDAP logins
+ - Fix loading spinner sometimes not being hidden on Merge Request tab switches
v 8.0.4
- Fix Message-ID header to be RFC 2111-compliant to prevent e-mails being dropped (Stan Hu)
diff --git a/GITLAB_GIT_HTTP_SERVER_VERSION b/GITLAB_GIT_HTTP_SERVER_VERSION
new file mode 100644
index 00000000000..0d91a54c7d4
--- /dev/null
+++ b/GITLAB_GIT_HTTP_SERVER_VERSION
@@ -0,0 +1 @@
+0.3.0
diff --git a/Gemfile b/Gemfile
index e8cdb7a4a97..9254ce2ccfa 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,13 +1,5 @@
source "https://rubygems.org"
-def darwin_only(require_as)
- RUBY_PLATFORM.include?('darwin') && require_as
-end
-
-def linux_only(require_as)
- RUBY_PLATFORM.include?('linux') && require_as
-end
-
gem 'rails', '4.1.12'
# Specify a sprockets version due to security issue
@@ -47,7 +39,7 @@ gem "browser", '~> 1.0.0'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
-gem "gitlab_git", '~> 7.2.18'
+gem "gitlab_git", '~> 7.2.19'
# LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes
@@ -102,7 +94,7 @@ gem "seed-fu", '~> 2.3.5'
gem 'html-pipeline', '~> 1.11.0'
gem 'task_list', '~> 1.0.2', require: 'task_list/railtie'
gem 'github-markup', '~> 1.3.1'
-gem 'redcarpet', '~> 3.3.2'
+gem 'redcarpet', '~> 3.3.3'
gem 'RedCloth', '~> 4.2.9'
gem 'rdoc', '~>3.6'
gem 'org-ruby', '~> 0.9.12'
@@ -196,7 +188,7 @@ gem 'charlock_holmes', '~> 0.6.9.4'
gem "sass-rails", '~> 4.0.5'
gem "coffee-rails", '~> 4.1.0'
-gem "uglifier", '~> 2.3.2'
+gem "uglifier", '~> 2.7.2'
gem 'turbolinks', '~> 2.5.0'
gem 'jquery-turbolinks', '~> 2.0.1'
@@ -224,6 +216,9 @@ group :development do
gem 'quiet_assets', '~> 1.0.2'
gem 'rack-mini-profiler', '~> 0.9.0', require: false
gem 'rerun', '~> 0.10.0'
+ gem 'bullet', require: false
+ gem 'active_record_query_trace', require: false
+ gem 'rack-lineprof', platform: :mri
# Better errors handler
gem 'better_errors', '~> 1.0.1'
@@ -290,7 +285,7 @@ gem 'newrelic-grape'
gem 'octokit', '~> 3.7.0'
-gem "mail_room", "~> 0.5.2"
+gem "mail_room", "~> 0.6.1"
gem 'email_reply_parser', '~> 0.5.8'
@@ -304,11 +299,3 @@ gem 'oauth2', '~> 1.0.0'
# Soft deletion
gem "paranoia", "~> 2.0"
-
-group :development, :test do
- gem 'guard-rspec', '~> 4.2.0'
-
- gem 'rb-fsevent', require: darwin_only('rb-fsevent')
- gem 'growl', require: darwin_only('growl')
- gem 'rb-inotify', require: linux_only('rb-inotify')
-end
diff --git a/Gemfile.lock b/Gemfile.lock
index 9911904d304..53122898b07 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -17,6 +17,7 @@ GEM
activesupport (= 4.1.12)
builder (~> 3.1)
erubis (~> 2.7.0)
+ active_record_query_trace (1.5)
activemodel (4.1.12)
activesupport (= 4.1.12)
builder (~> 3.1)
@@ -87,6 +88,9 @@ GEM
terminal-table (~> 1.4)
browser (1.0.0)
builder (3.2.2)
+ bullet (4.14.9)
+ activesupport (>= 3.0.0)
+ uniform_notifier (~> 1.9.0)
byebug (6.0.2)
cal-heatmap-rails (0.0.1)
capybara (2.4.4)
@@ -134,6 +138,7 @@ GEM
daemons (1.2.3)
database_cleaner (1.4.1)
debug_inspector (0.0.2)
+ debugger-ruby_core_source (1.3.8)
default_value_for (3.0.1)
activerecord (>= 3.2.0, < 5.0)
descendants_tracker (0.0.4)
@@ -278,7 +283,7 @@ GEM
mime-types (~> 1.19)
gitlab_emoji (0.1.1)
gemojione (~> 2.0)
- gitlab_git (7.2.18)
+ gitlab_git (7.2.19)
activesupport (~> 4.0)
charlock_holmes (~> 0.6)
gitlab-linguist (~> 3.0)
@@ -314,19 +319,6 @@ GEM
grape-entity (0.4.8)
activesupport
multi_json (>= 1.3.2)
- growl (1.0.3)
- guard (2.13.0)
- formatador (>= 0.2.4)
- listen (>= 2.7, <= 4.0)
- lumberjack (~> 1.0)
- nenv (~> 0.1)
- notiffany (~> 0.0)
- pry (>= 0.9.12)
- shellany (~> 0.0)
- thor (>= 0.18.1)
- guard-rspec (4.2.10)
- guard (~> 2.1)
- rspec (>= 2.14, < 4.0)
haml (4.0.7)
tilt
haml-rails (0.9.0)
@@ -387,12 +379,11 @@ GEM
celluloid (~> 0.16.0)
rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9)
- lumberjack (1.0.9)
macaddr (1.7.1)
systemu (~> 2.6.2)
mail (2.6.3)
mime-types (>= 1.16, < 3)
- mail_room (0.5.2)
+ mail_room (0.6.1)
method_source (0.8.2)
mime-types (1.25.1)
mimemagic (0.3.0)
@@ -403,7 +394,6 @@ GEM
multi_xml (0.5.5)
multipart-post (2.0.0)
mysql2 (0.3.20)
- nenv (0.2.0)
nested_form (0.3.2)
net-ldap (0.11)
net-scp (1.2.1)
@@ -416,9 +406,6 @@ GEM
newrelic_rpm (3.9.4.245)
nokogiri (1.6.6.2)
mini_portile (~> 0.6.0)
- notiffany (0.0.7)
- nenv (~> 0.1)
- shellany (~> 0.0)
nprogress-rails (0.1.2.3)
oauth (0.4.7)
oauth2 (1.0.0)
@@ -502,6 +489,10 @@ GEM
rack-attack (4.3.0)
rack
rack-cors (0.4.0)
+ rack-lineprof (0.0.3)
+ rack (~> 1.5)
+ rblineprof (~> 0.3.6)
+ term-ansicolor (~> 1.3)
rack-mini-profiler (0.9.7)
rack (>= 1.1.3)
rack-mount (0.8.3)
@@ -540,13 +531,15 @@ GEM
rb-fsevent (0.9.5)
rb-inotify (0.9.5)
ffi (>= 0.5.0)
+ rblineprof (0.3.6)
+ debugger-ruby_core_source (~> 1.3)
rbvmomi (1.8.2)
builder
nokogiri (>= 1.4.1)
trollop
rdoc (3.12.2)
json (~> 1.4)
- redcarpet (3.3.2)
+ redcarpet (3.3.3)
redis (3.2.1)
redis-actionpack (4.0.0)
actionpack (~> 4)
@@ -647,7 +640,6 @@ GEM
sexp_processor (4.6.0)
sham_rack (1.3.6)
rack
- shellany (0.0.1)
shoulda-matchers (2.8.0)
activesupport (>= 3.0.0)
sidekiq (3.3.0)
@@ -741,7 +733,7 @@ GEM
simple_oauth (~> 0.1.4)
tzinfo (1.2.2)
thread_safe (~> 0.1)
- uglifier (2.3.3)
+ uglifier (2.7.2)
execjs (>= 0.3.0)
json (>= 1.8.0)
underscore-rails (1.4.4)
@@ -755,6 +747,7 @@ GEM
unicorn-worker-killer (0.4.3)
get_process_mem (~> 0)
unicorn (~> 4)
+ uniform_notifier (1.9.0)
uuid (2.3.8)
macaddr (~> 1.0)
version_sorter (2.0.0)
@@ -784,6 +777,7 @@ PLATFORMS
DEPENDENCIES
RedCloth (~> 4.2.9)
ace-rails-ap (~> 2.0.1)
+ active_record_query_trace
activerecord-deprecated_finders (~> 1.0.3)
activerecord-session_store (~> 0.1.0)
acts-as-taggable-on (~> 3.4)
@@ -800,6 +794,7 @@ DEPENDENCIES
bootstrap-sass (~> 3.0)
brakeman (= 3.0.1)
browser (~> 1.0.0)
+ bullet
byebug
cal-heatmap-rails (~> 0.0.1)
capybara (~> 2.4.0)
@@ -834,15 +829,13 @@ DEPENDENCIES
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-linguist (~> 3.0.1)
gitlab_emoji (~> 0.1)
- gitlab_git (~> 7.2.18)
+ gitlab_git (~> 7.2.19)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.0.2)
gon (~> 5.0.0)
grape (~> 0.6.1)
grape-entity (~> 0.4.2)
- growl
- guard-rspec (~> 4.2.0)
haml-rails (~> 0.9.0)
hipchat (~> 1.5.0)
html-pipeline (~> 1.11.0)
@@ -854,7 +847,7 @@ DEPENDENCIES
jquery-ui-rails (~> 4.2.1)
kaminari (~> 0.16.3)
letter_opener (~> 1.1.2)
- mail_room (~> 0.5.2)
+ mail_room (~> 0.6.1)
minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.3.16)
@@ -882,14 +875,13 @@ DEPENDENCIES
quiet_assets (~> 1.0.2)
rack-attack (~> 4.3.0)
rack-cors (~> 0.4.0)
+ rack-lineprof
rack-mini-profiler (~> 0.9.0)
rack-oauth2 (~> 1.0.5)
rails (= 4.1.12)
raphael-rails (~> 2.1.2)
- rb-fsevent
- rb-inotify
rdoc (~> 3.6)
- redcarpet (~> 3.3.2)
+ redcarpet (~> 3.3.3)
redis-rails (~> 4.0.0)
request_store (~> 1.2.0)
rerun (~> 0.10.0)
@@ -926,7 +918,7 @@ DEPENDENCIES
thin (~> 1.6.1)
tinder (~> 1.10.0)
turbolinks (~> 2.5.0)
- uglifier (~> 2.3.2)
+ uglifier (~> 2.7.2)
underscore-rails (~> 1.4.4)
unf (~> 0.1.4)
unicorn (~> 4.8.2)
diff --git a/app/assets/images/logo.svg b/app/assets/images/logo.svg
index c09785cb96f..f4e19b67008 100644
--- a/app/assets/images/logo.svg
+++ b/app/assets/images/logo.svg
@@ -10,17 +10,17 @@
<g id="Fill-1-+-Group-24">
<g id="Group-24">
<g id="Group">
- <path d="M105.0614,193.655 L105.0614,193.655 L143.7014,74.734 L66.4214,74.734 L105.0614,193.655 L105.0614,193.655 Z" id="Fill-4" fill="#E24329"></path>
- <path d="M105.0614,193.6548 L66.4214,74.7338 L12.2684,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-8" fill="#FC6D26"></path>
- <path d="M12.2685,74.7341 L12.2685,74.7341 L0.5265,110.8731 C-0.5445,114.1691 0.6285,117.7801 3.4325,119.8171 L105.0615,193.6551 L12.2685,74.7341 L12.2685,74.7341 Z" id="Fill-12" fill="#FCA326"></path>
- <path d="M12.2685,74.7342 L66.4215,74.7342 L43.1485,3.1092 C41.9515,-0.5768 36.7375,-0.5758 35.5405,3.1092 L12.2685,74.7342 L12.2685,74.7342 Z" id="Fill-16" fill="#E24329"></path>
- <path d="M105.0614,193.6548 L143.7014,74.7338 L197.8544,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-18" fill="#FC6D26"></path>
- <path d="M197.8544,74.7341 L197.8544,74.7341 L209.5964,110.8731 C210.6674,114.1691 209.4944,117.7801 206.6904,119.8171 L105.0614,193.6551 L197.8544,74.7341 L197.8544,74.7341 Z" id="Fill-20" fill="#FCA326"></path>
- <path d="M197.8544,74.7342 L143.7014,74.7342 L166.9744,3.1092 C168.1714,-0.5768 173.3854,-0.5758 174.5824,3.1092 L197.8544,74.7342 L197.8544,74.7342 Z" id="Fill-22" fill="#E24329"></path>
+ <path d="M105.0614,193.655 L105.0614,193.655 L143.7014,74.734 L66.4214,74.734 L105.0614,193.655 L105.0614,193.655 Z" id="Fill-4" fill="#E24329" class="tanuki-shape"></path>
+ <path d="M105.0614,193.6548 L66.4214,74.7338 L12.2684,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-8" fill="#FC6D26" class="tanuki-shape"></path>
+ <path d="M12.2685,74.7341 L12.2685,74.7341 L0.5265,110.8731 C-0.5445,114.1691 0.6285,117.7801 3.4325,119.8171 L105.0615,193.6551 L12.2685,74.7341 L12.2685,74.7341 Z" id="Fill-12" fill="#FCA326" class="tanuki-shape"></path>
+ <path d="M12.2685,74.7342 L66.4215,74.7342 L43.1485,3.1092 C41.9515,-0.5768 36.7375,-0.5758 35.5405,3.1092 L12.2685,74.7342 L12.2685,74.7342 Z" id="Fill-16" fill="#E24329" class="tanuki-shape"></path>
+ <path d="M105.0614,193.6548 L143.7014,74.7338 L197.8544,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-18" fill="#FC6D26" class="tanuki-shape"></path>
+ <path d="M197.8544,74.7341 L197.8544,74.7341 L209.5964,110.8731 C210.6674,114.1691 209.4944,117.7801 206.6904,119.8171 L105.0614,193.6551 L197.8544,74.7341 L197.8544,74.7341 Z" id="Fill-20" fill="#FCA326" class="tanuki-shape"></path>
+ <path d="M197.8544,74.7342 L143.7014,74.7342 L166.9744,3.1092 C168.1714,-0.5768 173.3854,-0.5758 174.5824,3.1092 L197.8544,74.7342 L197.8544,74.7342 Z" id="Fill-22" fill="#E24329" class="tanuki-shape"></path>
</g>
</g>
</g>
</g>
</g>
</g>
-</svg> \ No newline at end of file
+</svg>
diff --git a/app/assets/javascripts/ci/build.coffee b/app/assets/javascripts/ci/build.coffee
index c30859b484b..93385b32a13 100644
--- a/app/assets/javascripts/ci/build.coffee
+++ b/app/assets/javascripts/ci/build.coffee
@@ -22,7 +22,7 @@ class CiBuild
# Only valid for runnig build when output changes during time
#
CiBuild.interval = setInterval =>
- if window.location.href is build_url
+ if window.location.href.split("#").first() is build_url
$.ajax
url: build_url
dataType: "json"
diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee
index 3e77ea515f8..593a8f42130 100644
--- a/app/assets/javascripts/merge_request_tabs.js.coffee
+++ b/app/assets/javascripts/merge_request_tabs.js.coffee
@@ -68,8 +68,8 @@ class @MergeRequestTabs
scrollToElement: (container) ->
if window.location.hash
- top = $(container + " " + window.location.hash).offset().top
- $('body').scrollTo(top)
+ $el = $("#{container} #{window.location.hash}")
+ $('body').scrollTo($el.offset().top) if $el.length
# Activate a tab based on the current action
activateTab: (action) ->
@@ -127,7 +127,7 @@ class @MergeRequestTabs
document.getElementById('commits').innerHTML = data.html
$('.js-timeago').timeago()
@commitsLoaded = true
- @scrollToElement(".commits")
+ @scrollToElement("#commits")
loadDiff: (source) ->
return if @diffsLoaded
@@ -137,7 +137,7 @@ class @MergeRequestTabs
success: (data) =>
document.getElementById('diffs').innerHTML = data.html
@diffsLoaded = true
- @scrollToElement(".diffs")
+ @scrollToElement("#diffs")
# Show or hide the loading spinner
#
diff --git a/app/assets/javascripts/shortcuts_navigation.coffee b/app/assets/javascripts/shortcuts_navigation.coffee
index 5b6f9e7e3f2..8decaedd87b 100644
--- a/app/assets/javascripts/shortcuts_navigation.coffee
+++ b/app/assets/javascripts/shortcuts_navigation.coffee
@@ -7,6 +7,7 @@ class @ShortcutsNavigation extends Shortcuts
Mousetrap.bind('g e', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-project-activity'))
Mousetrap.bind('g f', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-tree'))
Mousetrap.bind('g c', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-commits'))
+ Mousetrap.bind('g b', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-builds'))
Mousetrap.bind('g n', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-network'))
Mousetrap.bind('g g', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-graphs'))
Mousetrap.bind('g i', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-issues'))
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 6ce34b5c3e8..5949a0fd5ad 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -18,6 +18,7 @@
line-height: 36px;
}
+.content-block,
.gray-content-block {
margin: -$gl-padding;
background-color: $background-color;
@@ -27,6 +28,10 @@
border-bottom: 1px solid $border-color;
color: $gl-gray;
+ &.white {
+ background-color: white;
+ }
+
&.top-block {
border-top: none;
}
@@ -60,3 +65,48 @@
line-height: 42px;
}
}
+
+.cover-block {
+ text-align: center;
+ background: #f7f8fa;
+ margin: -$gl-padding;
+ margin-bottom: 0;
+ padding: 44px $gl-padding;
+ border-bottom: 1px solid $border-color;
+ position: relative;
+
+ .avatar-holder {
+ margin-bottom: 16px;
+
+ .avatar, .identicon {
+ margin: 0 auto;
+ float: none;
+ }
+
+ .identicon {
+ @include border-radius(50%);
+ }
+ }
+
+ .cover-title {
+ color: $gl-header-color;
+ margin: 0;
+ font-size: 23px;
+ font-weight: normal;
+ margin: 16px 0 5px 0;
+ color: #4c4e54;
+ font-size: 23px;
+ line-height: 1.1;
+ }
+
+ .cover-desc {
+ padding: 0 $gl-padding;
+ color: $gl-text-color;
+ }
+
+ .cover-controls {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ }
+}
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index e5f0c0ad9ef..04024419584 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -162,10 +162,21 @@
border-color: #e7e9ed;
width: 140px;
+ .badge {
+ font-weight: normal;
+ background-color: #eee;
+ color: #78a;
+ }
+
&.active {
border-color: $gl-info;
background: $gl-info;
color: #fff;
+
+ .badge {
+ color: $gl-info;
+ background-color: white;
+ }
}
}
}
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 9dd77747884..35db00281e5 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -10,6 +10,10 @@
border-bottom: 1px solid #E7E9EE;
margin-bottom: 1em;
+ &.readme-holder {
+ border-bottom: 0;
+ }
+
table {
@extend .table;
}
@@ -94,7 +98,6 @@
border-right: none;
}
background: #fff;
- padding: 10px $gl-padding;
}
.lines {
pre {
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index c5764c36597..f6942db5816 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -107,7 +107,7 @@ ul.content-list {
> li {
padding: $gl-padding;
- border-color: #f1f2f4;
+ border-color: $table-border-color;
margin-left: -$gl-padding;
margin-right: -$gl-padding;
color: $gl-gray;
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 089e6958eeb..fe078d016d7 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -147,14 +147,8 @@
.badge {
font-weight: normal;
- background-color: #fff;
background-color: #eee;
color: #78a;
}
}
}
-
-.fa-align {
- top: 20px;
- position: relative;
-}
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index cba621635b6..78fff58d232 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -32,7 +32,7 @@
}
.select2-results .select2-result-label {
- padding: 16px;
+ padding: 9px;
}
.select2-drop{
@@ -143,4 +143,4 @@
.ajax-users-dropdown {
min-width: 250px !important;
-}
+} \ No newline at end of file
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index c5ea3aca7ca..985ea164576 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -242,6 +242,9 @@
img {
width: 36px;
height: 36px;
+ }
+
+ #tanuki-logo, img {
float: left;
}
@@ -265,3 +268,13 @@
}
}
}
+
+
+.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 789b34020c1..66e16e8df75 100644
--- a/app/assets/stylesheets/framework/tables.scss
+++ b/app/assets/stylesheets/framework/tables.scss
@@ -1,3 +1,9 @@
+.table-holder {
+ margin: -$gl-padding;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
table {
&.table {
.dropdown-menu a {
@@ -18,15 +24,17 @@ table {
tr {
td, th {
- padding: 8px 10px;
+ padding: 10px $gl-padding;
line-height: 20px;
vertical-align: middle;
}
+
th {
font-weight: normal;
font-size: 15px;
border-bottom: 1px solid $border-color !important;
}
+
td {
border-color: $table-border-color !important;
border-bottom: 1px solid;
diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss
index bf21d7fce76..eb53c4153d3 100644
--- a/app/assets/stylesheets/framework/timeline.scss
+++ b/app/assets/stylesheets/framework/timeline.scss
@@ -6,13 +6,17 @@
.timeline-entry {
padding: $gl-padding;
- border-color: #f1f2f4;
+ border-color: $table-border-color;
margin-left: -$gl-padding;
margin-right: -$gl-padding;
color: $gl-gray;
border-bottom: 1px solid #ECEEF1;
border-right: 1px solid #ECEEF1;
+ &:target {
+ background: $hover;
+ }
+
&:last-child {
border-bottom: none;
}
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index bf36f96cc97..e6558a23858 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -1,5 +1,6 @@
@mixin md-typography {
color: $md-text-color;
+ word-wrap: break-word;
a {
color: $md-link-color;
@@ -17,7 +18,6 @@
font-family: $monospace_font;
white-space: pre;
word-wrap: normal;
- padding: 1px 2px;
}
kbd {
@@ -73,6 +73,8 @@
}
blockquote {
+ color: #7f8fa4;
+ font-size: inherit;
padding: 8px 21px;
margin: 12px 0 12px;
border-left: 3px solid #e7e9ed;
@@ -80,7 +82,7 @@
blockquote p {
color: #7f8fa4 !important;
- font-size: 15px;
+ font-size: inherit;
line-height: 1.5;
}
@@ -101,9 +103,9 @@
pre {
margin: 12px 0 12px 0 !important;
- background-color: #f8fafc !important;
+ background-color: #f8fafc;
font-size: 13px !important;
- color: #5b6169 !important;
+ color: #5b6169;
line-height: 1.6em !important;
@include border-radius(2px);
}
@@ -112,9 +114,9 @@
font-weight: inherit;
}
-
- ul {
- color: #5c5d5e;
+ ul, ol {
+ padding: 0;
+ margin: 6px 0 6px 18px !important;
}
li {
@@ -136,6 +138,33 @@
text-decoration: none;
}
}
+
+ /* Link to current header. */
+ h1, h2, h3, h4, h5, h6 {
+ position: relative;
+
+ a.anchor {
+ // Setting `display: none` would prevent the anchor being scrolled to, so
+ // instead we set the height to 0 and it gets updated on hover.
+ height: 0;
+ }
+
+ &:hover > a.anchor {
+ $size: 16px;
+ position: absolute;
+ right: 100%;
+ top: 50%;
+ margin-top: -$size/2;
+ margin-right: 0px;
+ padding-right: 20px;
+ display: inline-block;
+ width: $size;
+ height: $size;
+ background-image: image-url("icon-link.png");
+ background-size: contain;
+ background-repeat: no-repeat;
+ }
+ }
}
@@ -202,53 +231,11 @@ a > code {
}
/**
- * Wiki typography
+ * Apply Markdown typography
*
*/
.wiki {
@include md-typography;
-
- word-wrap: break-word;
- padding: 7px;
-
- /* Link to current header. */
- h1, h2, h3, h4, h5, h6 {
- position: relative;
-
- a.anchor {
- // Setting `display: none` would prevent the anchor being scrolled to, so
- // instead we set the height to 0 and it gets updated on hover.
- height: 0;
- }
-
- &:hover > a.anchor {
- $size: 16px;
- position: absolute;
- right: 100%;
- top: 50%;
- margin-top: -$size/2;
- margin-right: 0px;
- padding-right: 20px;
- display: inline-block;
- width: $size;
- height: $size;
- background-image: image-url("icon-link.png");
- background-size: contain;
- background-repeat: no-repeat;
- }
- }
-
- ul,ol {
- padding: 0;
- margin: 6px 0 6px 18px !important;
- }
- ol {
- color: #5c5d5e;
- }
-}
-
-.md-area {
- @include md-typography;
}
.md {
@@ -261,6 +248,7 @@ a > code {
*/
textarea.js-gfm-input {
font-family: $monospace_font;
+ color: $gl-text-color;
}
.md-preview {
diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss
index 8323a8598ec..6a2b25ddc67 100644
--- a/app/assets/stylesheets/highlight/dark.scss
+++ b/app/assets/stylesheets/highlight/dark.scss
@@ -1,11 +1,10 @@
/* https://github.com/MozMorris/tomorrow-pygments */
-pre.code.highlight.dark,
.code.dark {
- background-color: #1d1f21;
- color: #c5c8c6;
+ background-color: #1d1f21 !important;
+ color: #c5c8c6 !important;
- pre.code,
+ pre.highlight,
.line-numbers,
.line-numbers a {
background-color: #1d1f21 !important;
@@ -23,8 +22,8 @@ pre.code.highlight.dark,
// Search result highlight
span.highlight_word {
- background: #ffe792;
- color: #000000;
+ background-color: #ffe792 !important;
+ color: #000000 !important;
}
.hll { background-color: #373b41 }
diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss
index e8381674336..8560c3c490f 100644
--- a/app/assets/stylesheets/highlight/monokai.scss
+++ b/app/assets/stylesheets/highlight/monokai.scss
@@ -1,15 +1,14 @@
/* https://github.com/richleland/pygments-css/blob/master/monokai.css */
-pre.code.monokai,
.code.monokai {
- background: #272822;
- color: #f8f8f2;
+ background-color: #272822 !important;
+ color: #f8f8f2 !important;
pre.highlight,
.line-numbers,
.line-numbers a {
- background:#272822 !important;
- color:#f8f8f2 !important;
+ background-color :#272822 !important;
+ color: #f8f8f2 !important;
}
pre.code {
@@ -23,8 +22,8 @@ pre.code.monokai,
// Search result highlight
span.highlight_word {
- background: #ffe792;
- color: #000000;
+ background-color: #ffe792 !important;
+ color: #000000 !important;
}
.hll { background-color: #49483e }
diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss
index bd41480aefb..7d489a9666b 100644
--- a/app/assets/stylesheets/highlight/solarized_dark.scss
+++ b/app/assets/stylesheets/highlight/solarized_dark.scss
@@ -1,11 +1,10 @@
/* https://gist.github.com/qguv/7936275 */
-pre.code.highlight.solarized-dark,
.code.solarized-dark {
- background-color: #002b36;
- color: #93a1a1;
+ background-color: #002b36 !important;
+ color: #93a1a1 !important;
- pre.code,
+ pre.highlight,
.line-numbers,
.line-numbers a {
background-color: #002b36 !important;
@@ -23,7 +22,7 @@ pre.code.highlight.solarized-dark,
// Search result highlight
span.highlight_word {
- background: #094554;
+ background-color: #094554 !important;
}
/* Solarized Dark
diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss
index 4cc62863870..200ed346446 100644
--- a/app/assets/stylesheets/highlight/solarized_light.scss
+++ b/app/assets/stylesheets/highlight/solarized_light.scss
@@ -1,11 +1,10 @@
/* https://gist.github.com/qguv/7936275 */
-pre.code.highlight.solarized-light,
.code.solarized-light {
- background-color: #fdf6e3;
- color: #586e75;
+ background-color: #fdf6e3 !important;
+ color: #586e75 !important;
- pre.code,
+ pre.highlight,
.line-numbers,
.line-numbers a {
background-color: #fdf6e3 !important;
@@ -23,7 +22,7 @@ pre.code.highlight.solarized-light,
// Search result highlight
span.highlight_word {
- background: #eee8d5;
+ background-color: #eee8d5 !important;
}
/* Solarized Light
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index 20a144ef952..e2626da7871 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -1,24 +1,20 @@
/* https://github.com/aahan/pygments-github-style */
-pre.code.highlight.white,
.code.white {
- background-color: #f8fafc;
- font-size: 13px;
- color: #5b6169;
- line-height: 1.6em;
+ background-color: #f8fafc !important;
+ color: #5b6169 !important;
+
+ pre.highlight,
.line-numbers,
.line-numbers a {
background-color: $background-color !important;
color: $gl-gray !important;
}
- pre.highlight {
- background-color: #fff !important;
- color: #333 !important;
- }
-
pre.code {
border-left: 1px solid $border-color;
+ background-color: #fff !important;
+ color: #333 !important;
}
// highlight line via anchor
@@ -28,7 +24,7 @@ pre.code.highlight.white,
// Search result highlight
span.highlight_word {
- background: #fafe3d;
+ background-color: #fafe3d !important;
}
.hll { background-color: #f8f8f8 }
diff --git a/app/assets/stylesheets/pages/ci_projects.scss b/app/assets/stylesheets/pages/ci_projects.scss
index 8c5273abcda..2a7b5cfc7fd 100644
--- a/app/assets/stylesheets/pages/ci_projects.scss
+++ b/app/assets/stylesheets/pages/ci_projects.scss
@@ -6,11 +6,6 @@
line-height: 1.5;
}
- .wide-table-holder {
- margin-left: -$gl-padding;
- margin-right: -$gl-padding;
- }
-
.builds,
.projects-table {
.light {
diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss
index ca2ee455423..dfb901652bf 100644
--- a/app/assets/stylesheets/pages/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -7,7 +7,7 @@
padding: $gl-padding;
margin-left: -$gl-padding;
margin-right: -$gl-padding;
- border-bottom: 1px solid #f1f2f4;
+ border-bottom: 1px solid $table-border-color;
color: #7f8fa4;
&.event-inline {
diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss
index 6da7a2511a2..bd224705f04 100644
--- a/app/assets/stylesheets/pages/help.scss
+++ b/app/assets/stylesheets/pages/help.scss
@@ -68,3 +68,7 @@ body.modal-open {
.modal .modal-dialog {
width: 860px;
}
+
+.documentation {
+ padding: 7px;
+}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 9da085a3473..c60aa5c7fe7 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -80,3 +80,11 @@
}
}
}
+
+.issuable-filter-count {
+ span {
+ display: block;
+ margin-bottom: -16px;
+ padding: 13px 0;
+ }
+}
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 4bf58cb4a59..41c069f0ad3 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -132,6 +132,11 @@ form.edit-issue {
}
}
+.issue-closed-by-widget {
+ padding: 16px 0;
+ margin: 0px;
+}
+
.issue-form .select2-container {
width: 250px !important;
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index abb03b07f51..1980fe0d458 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -30,7 +30,6 @@ ul.notes {
.discussion-header,
.note-header {
@extend .cgray;
- padding-bottom: 15px;
a:hover {
text-decoration: none;
@@ -75,6 +74,10 @@ ul.notes {
}
}
+ .discussion-body {
+ padding-top: 15px;
+ }
+
.discussion {
overflow: hidden;
display: block;
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 8e4f0eb2b25..b7391e5303b 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -47,3 +47,9 @@
}
}
}
+
+.calendar-hint {
+ margin-top: -12px;
+ float: right;
+ font-size: 12px;
+}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index f7a22849003..6eb659dae17 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -50,7 +50,17 @@
}
.project-home-dropdown {
- margin: 11px 3px 0;
+ margin: 13px 0px 0;
+ }
+
+ .notifications-btn {
+ .fa-bell {
+ margin-right: 6px;
+ }
+
+ .fa-angle-down {
+ margin-left: 6px;
+ }
}
.project-home-desc {
@@ -85,6 +95,7 @@
color: inherit;
}
}
+
.input-group {
display: inline-table;
position: relative;
@@ -233,23 +244,11 @@
}
}
- .fa-fw {
+ i {
margin-right: 8px;
}
}
-.fa-bell {
- margin-right: 6px;
-}
-
-.fa-angle-down {
- margin-left: 6px;
-}
-
-.project-home-panel .project-home-dropdown {
- margin: 13px 0px 0;
-}
-
.project-visibility-level-holder {
.radio {
margin-bottom: 10px;
@@ -457,7 +456,7 @@ pre.light-well {
.project-row {
padding: $gl-padding;
- border-color: #f1f2f4;
+ border-color: $table-border-color;
margin-left: -$gl-padding;
margin-right: -$gl-padding;
@@ -511,3 +510,46 @@ pre.light-well {
margin-top: -1px;
}
}
+
+.project-last-commit {
+ margin: 0 7px;
+
+ .ci-status {
+ margin-right: 16px;
+ }
+
+ .commit-row-message {
+ color: $gl-gray;
+ }
+
+ .commit_short_id {
+ margin-right: 5px;
+ color: $gl-link-color;
+ font-weight: 600;
+ }
+
+ .commit-author-link {
+ margin-left: 7px;
+ text-decoration: none;
+ .avatar {
+ float: none;
+ margin-right: 4px;
+ }
+
+ .commit-author-name {
+ font-weight: 600;
+ }
+ }
+}
+
+.project-show-readme .readme-holder {
+ margin-left: -$gl-padding;
+ margin-right: -$gl-padding;
+ padding: ($gl-padding + 7px);
+ border-top: 0;
+
+ .edit-project-readme {
+ z-index: 100;
+ position: relative;
+ }
+}
diff --git a/app/assets/stylesheets/pages/runners.scss b/app/assets/stylesheets/pages/runners.scss
index 2b15ab83129..a9111a7388f 100644
--- a/app/assets/stylesheets/pages/runners.scss
+++ b/app/assets/stylesheets/pages/runners.scss
@@ -1,36 +1,34 @@
-.ci-body {
- .runner-state {
- padding: 6px 12px;
- margin-right: 10px;
- color: #FFF;
+.runner-state {
+ padding: 6px 12px;
+ margin-right: 10px;
+ color: #FFF;
- &.runner-state-shared {
- background: #32b186;
- }
- &.runner-state-specific {
- background: #3498db;
- }
+ &.runner-state-shared {
+ background: #32b186;
}
-
- .runner-status-online {
- color: green;
+ &.runner-state-specific {
+ background: #3498db;
}
+}
- .runner-status-offline {
- color: gray;
- }
+.runner-status-online {
+ color: green;
+}
- .runner-status-paused {
- color: red;
- }
+.runner-status-offline {
+ color: gray;
+}
+
+.runner-status-paused {
+ color: red;
+}
- .runner {
- .btn {
- padding: 1px 6px;
- }
+.runner {
+ .btn {
+ padding: 1px 6px;
+ }
- h4 {
- font-weight: normal;
- }
+ h4 {
+ font-weight: normal;
}
}
diff --git a/app/assets/stylesheets/pages/snippets.scss b/app/assets/stylesheets/pages/snippets.scss
index a3d7aba054d..242783a7b7e 100644
--- a/app/assets/stylesheets/pages/snippets.scss
+++ b/app/assets/stylesheets/pages/snippets.scss
@@ -1,8 +1,3 @@
-.my-snippets li:first-child {
- h4 { margin-top: 0; }
- padding-top: 0;
-}
-
.snippet-form-holder .file-holder .file-title {
padding: 2px;
}
@@ -30,3 +25,58 @@
}
}
}
+
+.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;
+ }
+ }
+ }
+
+ .file-holder {
+ border-top: 0;
+ }
+}
+
+
+.snippet-box {
+ @include border-radius(2px);
+
+ display: inline-block;
+ padding: 10px $gl-padding;
+ font-weight: normal;
+ margin-right: 10px;
+ font-size: $gl-font-size;
+ border: 1px solid;
+}
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index dadd86e88cc..d4ab6967ccd 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -1,25 +1,11 @@
.tree-holder {
- .tree-table-holder {
- margin-left: -$gl-padding;
- margin-right: -$gl-padding;
- }
-
- .tree_progress {
- display: none;
- margin: 20px;
- &.loading {
- display: block;
- }
- }
.tree-table {
margin-bottom: 0;
tr {
> td, > th {
- padding: 10px $gl-padding;
- line-height: 32px;
- border-color: $table-border-color !important;
+ line-height: 28px;
}
&:hover {
diff --git a/app/controllers/abuse_reports_controller.rb b/app/controllers/abuse_reports_controller.rb
index 65dbd5ef551..2f4054eaa11 100644
--- a/app/controllers/abuse_reports_controller.rb
+++ b/app/controllers/abuse_reports_controller.rb
@@ -9,6 +9,10 @@ class AbuseReportsController < ApplicationController
@abuse_report.reporter = current_user
if @abuse_report.save
+ if current_application_settings.admin_notification_email.present?
+ AbuseReportMailer.delay.notify(@abuse_report.id)
+ end
+
message = "Thank you for your report. A GitLab administrator will look into it shortly."
redirect_to root_path, notice: message
else
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 7c134d2ec9b..039f18f23e0 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -55,6 +55,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:default_snippet_visibility,
:restricted_signup_domains_raw,
:version_check_enabled,
+ :admin_notification_email,
:user_oauth_applications,
restricted_visibility_levels: [],
import_sources: []
diff --git a/app/controllers/admin/broadcast_messages_controller.rb b/app/controllers/admin/broadcast_messages_controller.rb
index 0808024fc39..497c34f8f49 100644
--- a/app/controllers/admin/broadcast_messages_controller.rb
+++ b/app/controllers/admin/broadcast_messages_controller.rb
@@ -19,7 +19,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
BroadcastMessage.find(params[:id]).destroy
respond_to do |format|
- format.html { redirect_to :back }
+ format.html { redirect_back_or_default(default: { action: 'index' }) }
format.js { render nothing: true }
end
end
diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb
index d670386f8c6..0bd19c49d8f 100644
--- a/app/controllers/admin/hooks_controller.rb
+++ b/app/controllers/admin/hooks_controller.rb
@@ -35,7 +35,7 @@ class Admin::HooksController < Admin::ApplicationController
}
@hook.execute(data, 'system_hooks')
- redirect_to :back
+ redirect_back_or_default
end
def hook_params
diff --git a/app/controllers/admin/services_controller.rb b/app/controllers/admin/services_controller.rb
index a62170662e1..46133588332 100644
--- a/app/controllers/admin/services_controller.rb
+++ b/app/controllers/admin/services_controller.rb
@@ -39,7 +39,13 @@ class Admin::ServicesController < Admin::ApplicationController
end
def application_services_params
- params.permit(:id,
+ application_services_params = params.permit(:id,
service: Projects::ServicesController::ALLOWED_PARAMS)
+ if application_services_params[:service].is_a?(Hash)
+ Projects::ServicesController::FILTER_BLANK_PARAMS.each do |param|
+ application_services_params[:service].delete(param) if application_services_params[:service][param].blank?
+ end
+ end
+ application_services_params
end
end
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index 00f41a10dd1..c63d0793e31 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -33,33 +33,33 @@ class Admin::UsersController < Admin::ApplicationController
def block
if user.block
- redirect_to :back, notice: "Successfully blocked"
+ redirect_back_or_admin_user(notice: "Successfully blocked")
else
- redirect_to :back, alert: "Error occurred. User was not blocked"
+ redirect_back_or_admin_user(alert: "Error occurred. User was not blocked")
end
end
def unblock
if user.activate
- redirect_to :back, notice: "Successfully unblocked"
+ redirect_back_or_admin_user(notice: "Successfully unblocked")
else
- redirect_to :back, alert: "Error occurred. User was not unblocked"
+ redirect_back_or_admin_user(alert: "Error occurred. User was not unblocked")
end
end
def unlock
if user.unlock_access!
- redirect_to :back, alert: "Successfully unlocked"
+ redirect_back_or_admin_user(alert: "Successfully unlocked")
else
- redirect_to :back, alert: "Error occurred. User was not unlocked"
+ redirect_back_or_admin_user(alert: "Error occurred. User was not unlocked")
end
end
def confirm
if user.confirm
- redirect_to :back, notice: "Successfully confirmed"
+ redirect_back_or_admin_user(notice: "Successfully confirmed")
else
- redirect_to :back, alert: "Error occurred. User was not confirmed"
+ redirect_back_or_admin_user(alert: "Error occurred. User was not confirmed")
end
end
@@ -138,7 +138,7 @@ class Admin::UsersController < Admin::ApplicationController
user.update_secondary_emails!
respond_to do |format|
- format.html { redirect_to :back, notice: "Successfully removed email." }
+ format.html { redirect_back_or_admin_user(notice: "Successfully removed email.") }
format.js { render nothing: true }
end
end
@@ -157,4 +157,12 @@ class Admin::UsersController < Admin::ApplicationController
:projects_limit, :can_create_group, :admin, :key_id
)
end
+
+ def redirect_back_or_admin_user(options = {})
+ redirect_back_or_default(default: default_route, options: options)
+ end
+
+ def default_route
+ [:admin, @user]
+ end
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 527c9da0faa..1b0609e279e 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -30,7 +30,11 @@ class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound do |exception|
log_exception(exception)
- render "errors/not_found", layout: "errors", status: 404
+ render_404
+ end
+
+ def redirect_back_or_default(default: root_path, options: {})
+ redirect_to request.referer.present? ? :back : default, options
end
protected
@@ -120,7 +124,6 @@ class ApplicationController < ActionController::Base
project_path = "#{namespace}/#{id}"
@project = Project.find_with_namespace(project_path)
-
if @project and can?(current_user, :read_project, @project)
if @project.path_with_namespace != project_path
redirect_to request.original_url.gsub(project_path, @project.path_with_namespace) and return
@@ -149,12 +152,8 @@ class ApplicationController < ActionController::Base
render "errors/access_denied", layout: "errors", status: 404
end
- def not_found!
- render "errors/not_found", layout: "errors", status: 404
- end
-
def git_not_found!
- render "errors/git_not_found", layout: "errors", status: 404
+ render html: "errors/git_not_found", layout: "errors", status: 404
end
def method_missing(method_sym, *arguments, &block)
diff --git a/app/controllers/ci/admin/runners_controller.rb b/app/controllers/ci/admin/runners_controller.rb
index 9a68add9083..110954a612d 100644
--- a/app/controllers/ci/admin/runners_controller.rb
+++ b/app/controllers/ci/admin/runners_controller.rb
@@ -6,7 +6,7 @@ module Ci
@runners = Ci::Runner.order('id DESC')
@runners = @runners.search(params[:search]) if params[:search].present?
@runners = @runners.page(params[:page]).per(30)
- @active_runners_cnt = Ci::Runner.where("contacted_at > ?", 1.minutes.ago).count
+ @active_runners_cnt = Ci::Runner.online.count
end
def show
@@ -66,7 +66,7 @@ module Ci
end
def runner_params
- params.require(:runner).permit(:token, :description, :tag_list, :contacted_at, :active)
+ params.require(:runner).permit(:token, :description, :tag_list, :active)
end
end
end
diff --git a/app/controllers/ci/projects_controller.rb b/app/controllers/ci/projects_controller.rb
index 7777aa18031..809b44387ba 100644
--- a/app/controllers/ci/projects_controller.rb
+++ b/app/controllers/ci/projects_controller.rb
@@ -1,12 +1,17 @@
module Ci
class ProjectsController < Ci::ApplicationController
- before_action :project
- before_action :authenticate_user!, except: [:build, :badge]
- before_action :authorize_access_project!, except: [:badge]
+ before_action :project, except: [:index]
+ before_action :authenticate_user!, except: [:index, :build, :badge]
+ before_action :authorize_access_project!, except: [:index, :badge]
before_action :authorize_manage_project!, only: [:toggle_shared_runners, :dumped_yaml]
before_action :no_cache, only: [:badge]
protect_from_forgery
+ def show
+ # Temporary compatibility with CI badges pointing to CI project page
+ redirect_to namespace_project_path(project.gl_project.namespace, project.gl_project)
+ end
+
# Project status badge
# Image with build status for sha or ref
def badge
diff --git a/app/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb
index f84f85a7df8..25e58724860 100644
--- a/app/controllers/import/bitbucket_controller.rb
+++ b/app/controllers/import/bitbucket_controller.rb
@@ -62,7 +62,7 @@ class Import::BitbucketController < Import::BaseController
end
def verify_bitbucket_import_enabled
- not_found! unless bitbucket_import_enabled?
+ render_404 unless bitbucket_import_enabled?
end
def bitbucket_auth
diff --git a/app/controllers/import/fogbugz_controller.rb b/app/controllers/import/fogbugz_controller.rb
index 849646cd665..18300390851 100644
--- a/app/controllers/import/fogbugz_controller.rb
+++ b/app/controllers/import/fogbugz_controller.rb
@@ -99,6 +99,6 @@ class Import::FogbugzController < Import::BaseController
end
def verify_fogbugz_import_enabled
- not_found! unless fogbugz_import_enabled?
+ render_404 unless fogbugz_import_enabled?
end
end
diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb
index f21fbd9ecca..67bf4190e7e 100644
--- a/app/controllers/import/github_controller.rb
+++ b/app/controllers/import/github_controller.rb
@@ -11,10 +11,6 @@ class Import::GithubController < Import::BaseController
def status
@repos = client.repos
- client.orgs.each do |org|
- @repos += client.org_repos(org.login)
- end
-
@already_added_projects = current_user.created_projects.where(import_type: "github")
already_added_projects_names = @already_added_projects.pluck(:import_source)
@@ -47,7 +43,7 @@ class Import::GithubController < Import::BaseController
end
def verify_github_import_enabled
- not_found! unless github_import_enabled?
+ render_404 unless github_import_enabled?
end
def github_auth
diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb
index 27af19f5f61..23a396e8084 100644
--- a/app/controllers/import/gitlab_controller.rb
+++ b/app/controllers/import/gitlab_controller.rb
@@ -44,7 +44,7 @@ class Import::GitlabController < Import::BaseController
end
def verify_gitlab_import_enabled
- not_found! unless gitlab_import_enabled?
+ render_404 unless gitlab_import_enabled?
end
def gitlab_auth
diff --git a/app/controllers/import/gitorious_controller.rb b/app/controllers/import/gitorious_controller.rb
index f24cdb3709a..eecbe380c9e 100644
--- a/app/controllers/import/gitorious_controller.rb
+++ b/app/controllers/import/gitorious_controller.rb
@@ -42,7 +42,7 @@ class Import::GitoriousController < Import::BaseController
end
def verify_gitorious_import_enabled
- not_found! unless gitorious_import_enabled?
+ render_404 unless gitorious_import_enabled?
end
end
diff --git a/app/controllers/import/google_code_controller.rb b/app/controllers/import/google_code_controller.rb
index 82fadeb7e83..e0de31f2251 100644
--- a/app/controllers/import/google_code_controller.rb
+++ b/app/controllers/import/google_code_controller.rb
@@ -10,18 +10,18 @@ class Import::GoogleCodeController < Import::BaseController
dump_file = params[:dump_file]
unless dump_file.respond_to?(:read)
- return redirect_to :back, alert: "You need to upload a Google Takeout archive."
+ return redirect_back_or_default(options: { alert: "You need to upload a Google Takeout archive." })
end
begin
dump = JSON.parse(dump_file.read)
rescue
- return redirect_to :back, alert: "The uploaded file is not a valid Google Takeout archive."
+ return redirect_back_or_default(options: { alert: "The uploaded file is not a valid Google Takeout archive." })
end
client = Gitlab::GoogleCodeImport::Client.new(dump)
unless client.valid?
- return redirect_to :back, alert: "The uploaded file is not a valid Google Takeout archive."
+ return redirect_back_or_default(options: { alert: "The uploaded file is not a valid Google Takeout archive." })
end
session[:google_code_dump] = dump
@@ -106,7 +106,7 @@ class Import::GoogleCodeController < Import::BaseController
end
def verify_google_code_import_enabled
- not_found! unless google_code_import_enabled?
+ render_404 unless google_code_import_enabled?
end
def user_map
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index 8ef10a17f55..94bb108c5f5 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -14,7 +14,7 @@ class InvitesController < ApplicationController
redirect_to path, notice: "You have been granted #{member.human_access} access to #{label}."
else
- redirect_to :back, alert: "The invitation could not be accepted."
+ redirect_back_or_default(options: { alert: "The invitation could not be accepted." })
end
end
@@ -31,7 +31,7 @@ class InvitesController < ApplicationController
redirect_to path, notice: "You have declined the invitation to join #{label}."
else
- redirect_to :back, alert: "The invitation could not be declined."
+ redirect_back_or_default(options: { alert: "The invitation could not be declined." })
end
end
diff --git a/app/controllers/profiles/notifications_controller.rb b/app/controllers/profiles/notifications_controller.rb
index 22423651c17..1fd1d6882df 100644
--- a/app/controllers/profiles/notifications_controller.rb
+++ b/app/controllers/profiles/notifications_controller.rb
@@ -29,7 +29,7 @@ class Profiles::NotificationsController < Profiles::ApplicationController
flash[:alert] = "Failed to save new settings"
end
- redirect_to :back
+ redirect_back_or_default(default: profile_notifications_path)
end
format.js
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index 26a4de15462..8da7b4d50ea 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -26,7 +26,7 @@ class ProfilesController < Profiles::ApplicationController
end
respond_to do |format|
- format.html { redirect_to :back }
+ format.html { redirect_back_or_default(default: { action: 'show' }) }
end
end
diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb
index 9c3763d5934..548f1b9ebfe 100644
--- a/app/controllers/projects/avatars_controller.rb
+++ b/app/controllers/projects/avatars_controller.rb
@@ -12,7 +12,7 @@ class Projects::AvatarsController < Projects::ApplicationController
filename: @blob.name
)
else
- not_found!
+ render_404
end
end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index ae9b1384463..8cc2f21d887 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -113,14 +113,14 @@ class Projects::BlobController < Projects::ApplicationController
end
end
- return not_found!
+ return render_404
end
end
def commit
@commit = @repository.commit(@ref)
- return not_found! unless @commit
+ return render_404 unless @commit
end
def assign_blob_vars
@@ -128,7 +128,7 @@ class Projects::BlobController < Projects::ApplicationController
@ref, @path = extract_ref(@id)
rescue InvalidPathError
- not_found!
+ render_404
end
def after_edit_path
diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb
index 4e4ac6689d3..816012762ce 100644
--- a/app/controllers/projects/builds_controller.rb
+++ b/app/controllers/projects/builds_controller.rb
@@ -1,11 +1,32 @@
class Projects::BuildsController < Projects::ApplicationController
before_action :ci_project
- before_action :build
+ before_action :build, except: [:index, :cancel_all]
- before_action :authorize_admin_project!, except: [:show, :status]
+ before_action :authorize_admin_project!, except: [:index, :show, :status]
layout "project"
+ def index
+ @scope = params[:scope]
+ @all_builds = project.ci_builds
+ @builds =
+ case @scope
+ when 'all'
+ @all_builds
+ when 'finished'
+ @all_builds.finished
+ else
+ @all_builds.running_or_pending
+ end
+ @builds = @builds.order('created_at DESC').page(params[:page]).per(30)
+ end
+
+ def cancel_all
+ @project.ci_builds.running_or_pending.each(&:cancel)
+
+ redirect_to namespace_project_builds_path(project.namespace, project)
+ end
+
def show
@builds = @ci_project.commits.find_by_sha(@build.sha).builds.order('id DESC')
@builds = @builds.where("id not in (?)", @build.id).page(params[:page]).per(20)
diff --git a/app/controllers/projects/ci_services_controller.rb b/app/controllers/projects/ci_services_controller.rb
index 6d2756eba3d..550a019e8e2 100644
--- a/app/controllers/projects/ci_services_controller.rb
+++ b/app/controllers/projects/ci_services_controller.rb
@@ -14,23 +14,23 @@ class Projects::CiServicesController < Projects::ApplicationController
end
def update
- if @service.update_attributes(service_params)
- redirect_to edit_namespace_project_ci_service_path(@project, @project.namespace, @service.to_param)
+ if service.update_attributes(service_params)
+ redirect_to edit_namespace_project_ci_service_path(@project.namespace, @project, service.to_param)
else
render 'edit'
end
end
def test
- last_build = @project.builds.last
+ last_build = @project.ci_builds.last
- if @service.execute(last_build)
+ if service.execute(last_build)
message = { notice: 'We successfully tested the service' }
else
message = { alert: 'We tried to test the service but error occurred' }
end
- redirect_to :back, message
+ redirect_back_or_default(options: message)
end
private
diff --git a/app/controllers/projects/ci_web_hooks_controller.rb b/app/controllers/projects/ci_web_hooks_controller.rb
index 7f40ddcb3f3..a2d470d4a69 100644
--- a/app/controllers/projects/ci_web_hooks_controller.rb
+++ b/app/controllers/projects/ci_web_hooks_controller.rb
@@ -24,7 +24,7 @@ class Projects::CiWebHooksController < Projects::ApplicationController
def test
Ci::TestHookService.new.execute(hook, current_user)
- redirect_to :back
+ redirect_back_or_default(default: { action: 'index' })
end
def destroy
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index d15004f93a6..71aaad1fad6 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -17,9 +17,10 @@ class Projects::CompareController < Projects::ApplicationController
execute(@project, head_ref, @project, base_ref)
if compare_result
- @commits = compare_result.commits
+ @commits = Commit.decorate(compare_result.commits, @project)
@diffs = compare_result.diffs
@commit = @commits.last
+ @first_commit = @commits.first
@line_notes = []
end
end
diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb
index 40e2b37912b..7d09288bc80 100644
--- a/app/controllers/projects/deploy_keys_controller.rb
+++ b/app/controllers/projects/deploy_keys_controller.rb
@@ -46,7 +46,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
def disable
@project.deploy_keys_projects.find_by(deploy_key_id: params[:id]).destroy
- redirect_to :back
+ redirect_back_or_default(default: { action: 'index' })
end
protected
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index 4e5b4125f5a..c7569541899 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -37,7 +37,7 @@ class Projects::HooksController < Projects::ApplicationController
flash[:alert] = 'Hook execution failed. Ensure the project has commits.'
end
- redirect_to :back
+ redirect_back_or_default(default: { action: 'index' })
end
def destroy
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 0f89f2e88cc..e767efbdc0c 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -14,6 +14,9 @@ class Projects::IssuesController < Projects::ApplicationController
# Allow issues bulk update
before_action :authorize_admin_issues!, only: [:bulk_update]
+ # Cross-reference merge requests
+ before_action :closed_by_merge_requests, only: [:show]
+
respond_to :html
def index
@@ -55,9 +58,9 @@ class Projects::IssuesController < Projects::ApplicationController
end
def show
- @participants = @issue.participants(current_user, @project)
+ @participants = @issue.participants(current_user)
@note = @project.notes.new(noteable: @issue)
- @notes = @issue.notes.inc_author.fresh
+ @notes = @issue.notes.with_associations.fresh
@noteable = @issue
respond_with(@issue)
@@ -103,7 +106,7 @@ class Projects::IssuesController < Projects::ApplicationController
def bulk_update
result = Issues::BulkUpdateService.new(project, current_user, bulk_update_params).execute
- redirect_to :back, notice: "#{result[:count]} issues updated"
+ redirect_back_or_default(default: { action: 'index' }, options: { notice: "#{result[:count]} issues updated" })
end
def toggle_subscription
@@ -112,6 +115,10 @@ class Projects::IssuesController < Projects::ApplicationController
render nothing: true
end
+ def closed_by_merge_requests
+ @closed_by_merge_requests ||= @issue.closed_by_merge_requests(current_user)
+ end
+
protected
def issue
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 7570934e727..16c42386623 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -56,6 +56,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def diffs
@commit = @merge_request.last_commit
+ @first_commit = @merge_request.first_commit
+
@comments_allowed = @reply_allowed = true
@comments_target = {
noteable_type: 'MergeRequest',
@@ -89,7 +91,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@target_project = merge_request.target_project
@source_project = merge_request.source_project
@commits = @merge_request.compare_commits
- @commit = @merge_request.compare_commits.last
+ @commit = @merge_request.last_commit
+ @first_commit = @merge_request.first_commit
@diffs = @merge_request.compare_diffs
@note_counts = Note.where(commit_id: @commits.map(&:id)).
group(:commit_id).count
@@ -246,7 +249,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def define_show_vars
- @participants = @merge_request.participants(current_user, @project)
+ @participants = @merge_request.participants(current_user)
# Build a note object for comment form
@note = @project.notes.new(noteable: @merge_request)
@@ -259,7 +262,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@commits = @merge_request.commits
@merge_request_diff = @merge_request.merge_request_diff
-
+
if @merge_request.locked_long_ago?
@merge_request.unlock_mr
@merge_request.close
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index 86f4a02a6e9..15506bd677a 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -75,11 +75,7 @@ class Projects::MilestonesController < Projects::ApplicationController
end
def sort_issues
- @issues = @milestone.issues.where(id: params['sortable_issue'])
- @issues.each do |issue|
- issue.position = params['sortable_issue'].index(issue.id.to_s) + 1
- issue.save
- end
+ @milestone.sort_issues(params['sortable_issue'].map(&:to_i))
render json: { saved: true }
end
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 0f5d82ce133..41cd08c93c6 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -25,7 +25,7 @@ class Projects::NotesController < Projects::ApplicationController
respond_to do |format|
format.json { render_note_json(@note) }
- format.html { redirect_to :back }
+ format.html { redirect_back_or_default }
end
end
@@ -34,7 +34,7 @@ class Projects::NotesController < Projects::ApplicationController
respond_to do |format|
format.json { render_note_json(@note) }
- format.html { redirect_to :back }
+ format.html { redirect_back_or_default }
end
end
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index cf73bc01c8f..9de5269cd25 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -72,7 +72,8 @@ class Projects::ProjectMembersController < Projects::ApplicationController
def leave
if @project.namespace == current_user.namespace
- return redirect_to(:back, alert: 'You can not leave your own project. Transfer or delete the project.')
+ message = 'You can not leave your own project. Transfer or delete the project.'
+ return redirect_back_or_default(default: { action: 'index' }, options: { alert: message })
end
@project.project_members.find_by(user_id: current_user).destroy
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
index 5f6fbce795e..d5ee6ac8663 100644
--- a/app/controllers/projects/raw_controller.rb
+++ b/app/controllers/projects/raw_controller.rb
@@ -20,7 +20,7 @@ class Projects::RawController < Projects::ApplicationController
disposition: 'inline'
)
else
- not_found!
+ render_404
end
end
diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb
index 6080c849c8d..c4e18c17077 100644
--- a/app/controllers/projects/refs_controller.rb
+++ b/app/controllers/projects/refs_controller.rb
@@ -3,6 +3,7 @@ class Projects::RefsController < Projects::ApplicationController
include TreeHelper
before_action :require_non_empty_project
+ before_action :validate_ref_id
before_action :assign_ref_vars
before_action :authorize_download_code!
@@ -71,4 +72,10 @@ class Projects::RefsController < Projects::ApplicationController
format.js
end
end
+
+ private
+
+ def validate_ref_id
+ return not_found! if params[:id].present? && params[:id] !~ Gitlab::Regex.git_reference_regex
+ end
end
diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb
index c4a5e2d6359..ba9aea1c165 100644
--- a/app/controllers/projects/repositories_controller.rb
+++ b/app/controllers/projects/repositories_controller.rb
@@ -11,18 +11,9 @@ class Projects::RepositoriesController < Projects::ApplicationController
end
def archive
- begin
- file_path = ArchiveRepositoryService.new(@project, params[:ref], params[:format]).execute
- rescue
- return head :not_found
- end
-
- if file_path
- # Send file to user
- response.headers["Content-Length"] = File.open(file_path).size.to_s
- send_file file_path
- else
- redirect_to request.fullpath
- end
+ render json: ArchiveRepositoryService.new(@project, params[:ref], params[:format]).execute
+ rescue => ex
+ logger.error("#{self.class.name}: #{ex}")
+ return git_not_found!
end
end
diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb
index 6cb6e3ef6d4..deb07a21416 100644
--- a/app/controllers/projects/runners_controller.rb
+++ b/app/controllers/projects/runners_controller.rb
@@ -60,6 +60,6 @@ class Projects::RunnersController < Projects::ApplicationController
end
def runner_params
- params.require(:runner).permit(:description, :tag_list, :contacted_at, :active)
+ params.require(:runner).permit(:description, :tag_list, :active)
end
end
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index 3047ee8a1ff..42dbb497e01 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -9,6 +9,10 @@ class Projects::ServicesController < Projects::ApplicationController
:note_events, :send_from_committer_email, :disable_diffs, :external_wiki_url,
:notify, :color,
:server_host, :server_port, :default_irc_uri, :enable_ssl_verification]
+
+ # Parameters to ignore if no value is specified
+ FILTER_BLANK_PARAMS = [:password]
+
# Authorize
before_action :authorize_admin_project!
before_action :service, only: [:edit, :update, :test]
@@ -48,7 +52,7 @@ class Projects::ServicesController < Projects::ApplicationController
message = { alert: error_message }
end
- redirect_to :back, message
+ redirect_back_or_default(options: message)
end
private
@@ -59,7 +63,9 @@ class Projects::ServicesController < Projects::ApplicationController
def service_params
service_params = params.require(:service).permit(ALLOWED_PARAMS)
- service_params.delete("password") if service_params["password"].blank?
+ FILTER_BLANK_PARAMS.each do |param|
+ service_params.delete(param) if service_params[param].blank?
+ end
service_params
end
end
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index b07a2a8db2f..2104c7a7a71 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -21,6 +21,7 @@ class Projects::SnippetsController < Projects::ApplicationController
filter: :by_project,
project: @project
})
+ @snippets = @snippets.page(params[:page]).per(PER_PAGE)
end
def new
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index 7eaff1d61ee..bdcb1a3e297 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -10,7 +10,7 @@ class Projects::TreeController < Projects::ApplicationController
before_action :authorize_push_code!, only: [:create_dir]
def show
- return not_found! unless @repository.commit(@ref)
+ return render_404 unless @repository.commit(@ref)
if tree.entries.empty?
if @repository.blob_at(@commit.id, @path)
@@ -19,7 +19,7 @@ class Projects::TreeController < Projects::ApplicationController
File.join(@ref, @path))
) and return
elsif @path.present?
- return not_found!
+ return render_404
end
end
@@ -31,7 +31,7 @@ class Projects::TreeController < Projects::ApplicationController
end
def create_dir
- return not_found! unless @commit_params.values.all?
+ return render_404 unless @commit_params.values.all?
begin
result = Files::CreateDirService.new(@project, current_user, @commit_params).execute
diff --git a/app/controllers/projects/uploads_controller.rb b/app/controllers/projects/uploads_controller.rb
index 71ecc20dd95..e1fe7ea2114 100644
--- a/app/controllers/projects/uploads_controller.rb
+++ b/app/controllers/projects/uploads_controller.rb
@@ -20,7 +20,7 @@ class Projects::UploadsController < Projects::ApplicationController
end
def show
- return not_found! if uploader.nil? || !uploader.file.exists?
+ return render_404 if uploader.nil? || !uploader.file.exists?
disposition = uploader.image? ? 'inline' : 'attachment'
send_file uploader.file.path, disposition: disposition
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 213c2a7173b..82119022cf9 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -1,11 +1,14 @@
class ProjectsController < ApplicationController
+ include ExtractsPath
+
prepend_before_filter :render_go_import, only: [:show]
skip_before_action :authenticate_user!, only: [:show, :activity]
before_action :project, except: [:new, :create]
before_action :repository, except: [:new, :create]
+ before_action :assign_ref_vars, :tree, only: [:show], if: :repo_exists?
# Authorize
- before_action :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive]
+ before_action :authorize_admin_project!, only: [:edit, :update]
before_action :event_filter, only: [:show, :activity]
layout :determine_layout
@@ -56,6 +59,8 @@ class ProjectsController < ApplicationController
end
def transfer
+ return access_denied! unless can?(current_user, :change_namespace, @project)
+
namespace = Namespace.find_by(id: params[:new_namespace_id])
::Projects::TransferService.new(project, current_user).execute(namespace)
@@ -64,6 +69,15 @@ class ProjectsController < ApplicationController
end
end
+ def remove_fork
+ return access_denied! unless can?(current_user, :remove_fork_project, @project)
+
+ if @project.forked?
+ @project.forked_project_link.destroy
+ flash[:notice] = 'The fork relationship has been removed.'
+ end
+ end
+
def activity
respond_to do |format|
format.html
@@ -87,7 +101,7 @@ class ProjectsController < ApplicationController
render 'projects/empty'
else
if current_user
- @membership = @project.project_member_by_id(current_user.id)
+ @membership = @project.team.find_member(current_user.id)
end
render :show
@@ -139,6 +153,7 @@ class ProjectsController < ApplicationController
def archive
return access_denied! unless can?(current_user, :archive_project, @project)
+
@project.archive!
respond_to do |format|
@@ -148,6 +163,7 @@ class ProjectsController < ApplicationController
def unarchive
return access_denied! unless can?(current_user, :archive_project, @project)
+
@project.unarchive!
respond_to do |format|
@@ -225,4 +241,14 @@ class ProjectsController < ApplicationController
render "go_import", layout: false
end
+
+ def repo_exists?
+ project.repository_exists? && !project.empty_repo?
+ end
+
+ # Override get_id from ExtractsPath, which returns the branch and file path
+ # for the blob/tree, which in this case is just the root of the default branch.
+ def get_id
+ project.repository.root_ref
+ end
end
diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb
index 28536e359e5..868b05929d7 100644
--- a/app/controllers/uploads_controller.rb
+++ b/app/controllers/uploads_controller.rb
@@ -10,7 +10,7 @@ class UploadsController < ApplicationController
end
unless uploader.file && uploader.file.exists?
- return not_found!
+ return render_404
end
disposition = uploader.image? ? 'inline' : 'attachment'
@@ -21,7 +21,7 @@ class UploadsController < ApplicationController
def find_model
unless upload_model && upload_mount
- return not_found!
+ return render_404
end
@model = upload_model.find(params[:id])
@@ -44,7 +44,7 @@ class UploadsController < ApplicationController
return if authorized
if current_user
- not_found!
+ render_404
else
authenticate_user!
end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 97c7e74c294..c407dfc163a 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -53,15 +53,36 @@ class IssuableFinder
end
end
+ def project?
+ params[:project_id].present?
+ end
+
def project
return @project if defined?(@project)
- @project =
- if params[:project_id].present?
- Project.find(params[:project_id])
- else
- nil
- end
+ if project?
+ @project = Project.find(params[:project_id])
+
+ unless Ability.abilities.allowed?(current_user, :read_project, @project)
+ @project = nil
+ end
+ else
+ @project = nil
+ end
+
+ @project
+ end
+
+ def projects
+ return @projects if defined?(@projects)
+
+ if project?
+ project
+ elsif current_user && params[:authorized_only].presence && !current_user_related?
+ current_user.authorized_projects
+ else
+ ProjectsFinder.new.execute(current_user)
+ end
end
def search
@@ -72,7 +93,7 @@ class IssuableFinder
params[:milestone_title].present?
end
- def no_milestones?
+ def filter_by_no_milestone?
milestones? && params[:milestone_title] == Milestone::None.title
end
@@ -81,12 +102,22 @@ class IssuableFinder
@milestones =
if milestones?
- Milestone.where(title: params[:milestone_title])
+ scope = Milestone.where(project_id: projects)
+
+ scope.where(title: params[:milestone_title])
else
nil
end
end
+ def labels?
+ params[:label_name].present?
+ end
+
+ def filter_by_no_label?
+ labels? && params[:label_name] == Label::None.title
+ end
+
def assignee?
params[:assignee_id].present?
end
@@ -120,19 +151,7 @@ class IssuableFinder
private
def init_collection
- table_name = klass.table_name
-
- if project
- if Ability.abilities.allowed?(current_user, :read_project, project)
- project.send(table_name)
- else
- []
- end
- elsif current_user && params[:authorized_only].presence && !current_user_related?
- klass.of_projects(current_user.authorized_projects).references(:project)
- else
- klass.of_projects(ProjectsFinder.new.execute(current_user)).references(:project)
- end
+ klass.all
end
def by_scope(items)
@@ -170,7 +189,12 @@ class IssuableFinder
end
def by_project(items)
- items = items.of_projects(project.id) if project
+ items =
+ if projects
+ items.of_projects(projects).references(:project)
+ else
+ items.none
+ end
items
end
@@ -185,18 +209,6 @@ class IssuableFinder
items.sort(params[:sort])
end
- def by_milestone(items)
- if milestones?
- if no_milestones?
- items = items.where(milestone_id: [-1, nil])
- else
- items = items.where(milestone_id: milestones.try(:pluck, :id))
- end
- end
-
- items
- end
-
def by_assignee(items)
if assignee?
items = items.where(assignee_id: assignee.try(:id))
@@ -213,20 +225,36 @@ class IssuableFinder
items
end
- def by_label(items)
- if params[:label_name].present?
- if params[:label_name] == Label::None.title
- item_ids = LabelLink.where(target_type: klass.name).pluck(:target_id)
+ def by_milestone(items)
+ if milestones?
+ if filter_by_no_milestone?
+ items = items.where(milestone_id: [-1, nil])
+ else
+ items = items.joins(:milestone).where(milestones: { title: params[:milestone_title] })
+
+ if projects
+ items = items.where(milestones: { project_id: projects })
+ end
+ end
+ end
+
+ items
+ end
- items = items.where('id NOT IN (?)', item_ids)
+ def by_label(items)
+ if labels?
+ if filter_by_no_label?
+ items = items.
+ joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{klass.name}' AND label_links.target_id = #{klass.table_name}.id").
+ where(label_links: { id: nil })
else
label_names = params[:label_name].split(",")
- item_ids = LabelLink.joins(:label).
- where('labels.title in (?)', label_names).
- where(target_type: klass.name).pluck(:target_id)
+ items = items.joins(:labels).where(labels: { title: label_names })
- items = items.where(id: item_ids)
+ if projects
+ items = items.where(labels: { project_id: projects })
+ end
end
end
diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb
index 14df8d4cbd7..c5820bf4c50 100644
--- a/app/helpers/appearances_helper.rb
+++ b/app/helpers/appearances_helper.rb
@@ -16,6 +16,6 @@ module AppearancesHelper
end
def brand_header_logo
- image_tag 'logo.svg'
+ render 'shared/logo.svg'
end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index cab2278adb7..8ecdeaf8e76 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -68,13 +68,17 @@ module ApplicationHelper
end
end
- def avatar_icon(user_email = '', size = nil)
- user = User.find_by(email: user_email)
+ def avatar_icon(user_or_email = nil, size = nil)
+ if user_or_email.is_a?(User)
+ user = user_or_email
+ else
+ user = User.find_by(email: user_or_email)
+ end
if user
user.avatar_url(size) || default_avatar
else
- gravatar_icon(user_email, size)
+ gravatar_icon(user_or_email, size)
end
end
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index b896fba3704..e65e37211c4 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -170,7 +170,8 @@ module DiffHelper
def commit_for_diff(diff)
if diff.deleted_file
- @merge_request ? @merge_request.commits.last : @commit.parents.first
+ first_commit = @first_commit || @commit
+ first_commit.parent
else
@commit
end
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index 12b87dca798..65813482120 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -19,7 +19,8 @@ module GitlabMarkdownHelper
escape_once(body)
end
- gfm_body = Gitlab::Markdown.gfm(escaped_body, project: @project, current_user: current_user)
+ user = current_user if defined?(current_user)
+ gfm_body = Gitlab::Markdown.gfm(escaped_body, project: @project, current_user: user)
fragment = Nokogiri::HTML::DocumentFragment.parse(gfm_body)
if fragment.children.size == 1 && fragment.children[0].name == 'a'
@@ -45,29 +46,39 @@ module GitlabMarkdownHelper
end
def markdown(text, context = {})
+ return "" unless text.present?
+
context.reverse_merge!(
- current_user: current_user,
path: @path,
+ pipeline: :default,
project: @project,
project_wiki: @project_wiki,
ref: @ref
)
- Gitlab::Markdown.render(text, context)
+ user = current_user if defined?(current_user)
+
+ html = Gitlab::Markdown.render(text, context)
+ Gitlab::Markdown.post_process(html, pipeline: context[:pipeline], project: @project, user: user)
end
# TODO (rspeicher): Remove all usages of this helper and just call `markdown`
# with a custom pipeline depending on the content being rendered
def gfm(text, options = {})
+ return "" unless text.present?
+
options.reverse_merge!(
- current_user: current_user,
path: @path,
+ pipeline: :default,
project: @project,
project_wiki: @project_wiki,
ref: @ref
)
- Gitlab::Markdown.gfm(text, options)
+ user = current_user if defined?(current_user)
+
+ html = Gitlab::Markdown.gfm(text, options)
+ Gitlab::Markdown.post_process(html, pipeline: options[:pipeline], project: @project, user: user)
end
def asciidoc(text)
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index 4d9da6ff837..b0b536d4649 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -25,6 +25,10 @@ module GitlabRoutingHelper
namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref)
end
+ def project_builds_path(project, *args)
+ namespace_project_builds_path(project.namespace, project, *args)
+ end
+
def activity_project_path(project, *args)
activity_namespace_project_path(project.namespace, project, *args)
end
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 6ddb37cd0dc..fda18e7b316 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -83,6 +83,10 @@ module IssuesHelper
end
end
+ def merge_requests_sentence(merge_requests)
+ merge_requests.map(&:to_reference).to_sentence(last_word_connector: ', or ')
+ end
+
# Required for Gitlab::Markdown::IssueReferenceFilter
module_function :url_for_issue
end
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 66b18eea699..ee04ace35d0 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -92,11 +92,19 @@ module LabelsHelper
end
end
- def project_labels_options(project)
- labels = project.labels.to_a
- labels.unshift(Label::None)
- labels.unshift(Label::Any)
- options_from_collection_for_select(labels, 'name', 'title', params[:label_name])
+ def projects_labels_options
+ labels =
+ if @project
+ @project.labels
+ else
+ Label.where(project_id: @projects)
+ end
+
+ grouped_labels = Labels::GroupService.new(labels).execute
+ grouped_labels.unshift(Label::None)
+ grouped_labels.unshift(Label::Any)
+
+ options_from_collection_for_select(grouped_labels, 'name', 'title', params[:label_name])
end
# Required for Gitlab::Markdown::LabelReferenceFilter
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 81773e7afcf..728d877ace2 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -47,7 +47,7 @@ module MergeRequestsHelper
end
def issues_sentence(issues)
- issues.map { |i| "##{i.iid}" }.to_sentence
+ issues.map(&:to_reference).to_sentence
end
def mr_change_branches_path(merge_request)
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index 4710171ebaa..c73cb3028ee 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -34,7 +34,8 @@ module PreferencesHelper
def project_view_choices
[
['Readme (default)', :readme],
- ['Activity view', :activity]
+ ['Activity view', :activity],
+ ['Files view', :files]
]
end
@@ -46,8 +47,7 @@ module PreferencesHelper
Gitlab::ColorSchemes.for_user(current_user).css_class
end
- def prefer_readme?
- !current_user ||
- current_user.project_view == 'readme'
+ def default_project_view
+ current_user ? current_user.project_view : 'readme'
end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index a0220af4c30..5301c2ccf76 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -29,7 +29,7 @@ module ProjectsHelper
author_html = ""
# Build avatar image tag
- author_html << image_tag(avatar_icon(author.try(:email), opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar]
+ author_html << image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar]
# Build name span tag
author_html << content_tag(:span, sanitize(author.name), class: opts[:author_class]) if opts[:name]
@@ -70,6 +70,10 @@ module ProjectsHelper
"You are going to transfer #{project.name_with_namespace} to another owner. Are you ABSOLUTELY sure?"
end
+ def remove_fork_project_message(project)
+ "You are going to remove the fork relationship to source project #{@project.forked_from_project.name_with_namespace}. Are you ABSOLUTELY sure?"
+ end
+
def project_nav_tabs
@nav_tabs ||= get_project_nav_tabs(@project, current_user)
end
@@ -113,6 +117,10 @@ module ProjectsHelper
nav_tabs << :merge_requests
end
+ if project.gitlab_ci? && can?(current_user, :read_build, project)
+ nav_tabs << :builds
+ end
+
if can?(current_user, :admin_project, project)
nav_tabs << :settings
end
diff --git a/app/helpers/runners_helper.rb b/app/helpers/runners_helper.rb
index 5d7d06c8490..46eb82a354f 100644
--- a/app/helpers/runners_helper.rb
+++ b/app/helpers/runners_helper.rb
@@ -1,20 +1,29 @@
module RunnersHelper
def runner_status_icon(runner)
- unless runner.contacted_at
- return content_tag :i, nil,
- class: "fa fa-warning-sign",
- title: "New runner. Has not connected yet"
+ status = runner.status
+ case status
+ when :not_connected
+ content_tag :i, nil,
+ class: "fa fa-warning",
+ title: "New runner. Has not connected yet"
+
+ when :online, :offline, :paused
+ content_tag :i, nil,
+ class: "fa fa-circle runner-status-#{status}",
+ title: "Runner is #{status}, last contact was #{time_ago_in_words(runner.contacted_at)} ago"
end
+ end
- status =
- if runner.active?
- runner.contacted_at > 3.hour.ago ? :online : :offline
- else
- :paused
- end
+ def runner_link(runner)
+ display_name = truncate(runner.display_name, length: 15)
+ id = "\##{runner.id}"
- content_tag :i, nil,
- class: "fa fa-circle runner-status-#{status}",
- title: "Runner is #{status}, last contact was #{time_ago_in_words(runner.contacted_at)} ago"
+ if current_user && current_user.admin
+ link_to ci_admin_runner_path(runner) do
+ display_name + id
+ end
+ else
+ display_name + id
+ end
end
end
diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb
index 0e7d8065ac7..04e53fe7c61 100644
--- a/app/helpers/tab_helper.rb
+++ b/app/helpers/tab_helper.rb
@@ -110,22 +110,4 @@ module TabHelper
'active'
end
end
-
- # Use nav_tab for save controller/action but different params
- def nav_tab(key, value, &block)
- o = {}
- o[:class] = ""
-
- if value.nil?
- o[:class] << " active" if params[key].blank?
- else
- o[:class] << " active" if params[key] == value
- end
-
- if block_given?
- content_tag(:li, capture(&block), o)
- else
- content_tag(:li, nil, o)
- end
- end
end
diff --git a/app/mailers/abuse_report_mailer.rb b/app/mailers/abuse_report_mailer.rb
new file mode 100644
index 00000000000..f0c41f69a5c
--- /dev/null
+++ b/app/mailers/abuse_report_mailer.rb
@@ -0,0 +1,12 @@
+class AbuseReportMailer < BaseMailer
+ include Gitlab::CurrentSettings
+
+ def notify(abuse_report_id)
+ @abuse_report = AbuseReport.find(abuse_report_id)
+
+ mail(
+ to: current_application_settings.admin_notification_email,
+ subject: "#{@abuse_report.user.name} (#{@abuse_report.user.username}) was reported for abuse"
+ )
+ end
+end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index 77c121ca5e8..b72178fa126 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -41,6 +41,7 @@ class Ability
:read_project_member,
:read_merge_request,
:read_note,
+ :read_build,
:download_code
]
@@ -127,6 +128,7 @@ class Ability
:read_project_member,
:read_merge_request,
:read_note,
+ :read_build,
:create_project,
:create_issue,
:create_note
@@ -187,7 +189,8 @@ class Ability
:change_visibility_level,
:rename_project,
:remove_project,
- :archive_project
+ :archive_project,
+ :remove_fork_project
]
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index c8841178e93..05430c2ee18 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -44,6 +44,10 @@ class ApplicationSetting < ActiveRecord::Base
allow_blank: true,
format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }
+ validates :admin_notification_email,
+ allow_blank: true,
+ email: true
+
validates_each :restricted_visibility_levels do |record, attr, value|
unless value.nil?
value.each do |level|
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index f8c731a7bf7..b19e2ac1363 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -93,10 +93,7 @@ module Ci
Ci::WebHookService.new.build_end(build)
end
- if build.commit.should_create_next_builds?(build)
- build.commit.create_next_builds(build.ref, build.tag, build.user, build.trigger_request)
- end
-
+ build.commit.create_next_builds(build)
project.execute_services(build)
if project.coverage_enabled?
@@ -119,7 +116,7 @@ module Ci
end
def variables
- yaml_variables + project_variables + trigger_variables
+ predefined_variables + yaml_variables + project_variables + trigger_variables
end
def project
@@ -220,17 +217,29 @@ module Ci
def cancel_url
if active?
Gitlab::Application.routes.url_helpers.
- cancel_namespace_project_build_path(gl_project.namespace, gl_project, self, return_to: request.original_url)
+ cancel_namespace_project_build_path(gl_project.namespace, gl_project, self)
end
end
def retry_url
if commands.present?
Gitlab::Application.routes.url_helpers.
- cancel_namespace_project_build_path(gl_project.namespace, gl_project, self, return_to: request.original_url)
+ retry_namespace_project_build_path(gl_project.namespace, gl_project, self)
end
end
+ def can_be_served?(runner)
+ (tag_list - runner.tag_list).empty?
+ end
+
+ def any_runners_online?
+ project.any_runners? { |runner| runner.active? && runner.online? && can_be_served?(runner) }
+ end
+
+ def show_warning?
+ pending? && !any_runners_online?
+ end
+
private
def yaml_variables
@@ -258,5 +267,14 @@ module Ci
[]
end
end
+
+ def predefined_variables
+ variables = []
+ variables << { key: :CI_BUILD_TAG, value: ref, public: true } if tag?
+ variables << { key: :CI_BUILD_NAME, value: name, public: true }
+ variables << { key: :CI_BUILD_STAGE, value: stage, public: true }
+ variables << { key: :CI_BUILD_TRIGGERED, value: 'true', public: true } if trigger_request
+ variables
+ end
end
end
diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb
index 68864edfbbf..13437b2483f 100644
--- a/app/models/ci/commit.rb
+++ b/app/models/ci/commit.rb
@@ -24,6 +24,8 @@ module Ci
has_many :builds, class_name: 'Ci::Build'
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
+ scope :ordered, -> { order('CASE WHEN ci_commits.committed_at IS NULL THEN 0 ELSE 1 END', :committed_at, :id) }
+
validates_presence_of :sha
validate :valid_commit_sha
@@ -89,19 +91,28 @@ module Ci
def create_builds(ref, tag, user, trigger_request = nil)
return unless config_processor
config_processor.stages.any? do |stage|
- CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request).present?
+ CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request, 'success').present?
end
end
- def create_next_builds(ref, tag, user, trigger_request)
+ def create_next_builds(build)
return unless config_processor
- stages = builds.where(ref: ref, tag: tag, trigger_request: trigger_request).group_by(&:stage)
+ # don't create other builds if this one is retried
+ latest_builds = builds.similar(build).latest
+ return unless latest_builds.exists?(build.id)
- config_processor.stages.any? do |stage|
- unless stages.include?(stage)
- CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request).present?
- end
+ # get list of stages after this build
+ next_stages = config_processor.stages.drop_while { |stage| stage != build.stage }
+ next_stages.delete(build.stage)
+
+ # get status for all prior builds
+ prior_builds = latest_builds.reject { |other_build| next_stages.include?(other_build.stage) }
+ status = Ci::Status.get_status(prior_builds)
+
+ # create builds for next stages based
+ next_stages.any? do |stage|
+ CreateBuildsService.new.execute(self, stage, build.ref, build.tag, build.user, build.trigger_request, status).present?
end
end
@@ -130,24 +141,7 @@ module Ci
return 'failed'
end
- @status ||= begin
- latest = latest_statuses
- latest.reject! { |status| status.try(&:allow_failure?) }
-
- if latest.none?
- 'skipped'
- elsif latest.all?(&:success?)
- 'success'
- elsif latest.all?(&:pending?)
- 'pending'
- elsif latest.any?(&:running?) || latest.any?(&:pending?)
- 'running'
- elsif latest.all?(&:canceled?)
- 'canceled'
- else
- 'failed'
- end
- end
+ @status ||= Ci::Status.get_status(latest_statuses)
end
def pending?
@@ -217,16 +211,6 @@ module Ci
update!(committed_at: DateTime.now)
end
- def should_create_next_builds?(build)
- # don't create other builds if this one is retried
- other_builds = builds.similar(build).latest
- return false unless other_builds.include?(build)
-
- other_builds.all? do |build|
- build.success? || build.ignored?
- end
- end
-
private
def save_yaml_error(error)
diff --git a/app/models/ci/project.rb b/app/models/ci/project.rb
index 88ba933a434..eb65c773570 100644
--- a/app/models/ci/project.rb
+++ b/app/models/ci/project.rb
@@ -115,12 +115,12 @@ module Ci
web_url
end
- def any_runners?
- if runners.active.any?
+ def any_runners?(&block)
+ if runners.active.any?(&block)
return true
end
- shared_runners_enabled && Ci::Runner.shared.active.any?
+ shared_runners_enabled && Ci::Runner.shared.active.any?(&block)
end
def set_default_values
@@ -205,7 +205,7 @@ module Ci
end
def commits
- gl_project.ci_commits
+ gl_project.ci_commits.ordered
end
def builds
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 6838ccfaaab..1b3669f1b7a 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -20,6 +20,8 @@
module Ci
class Runner < ActiveRecord::Base
extend Ci::Model
+
+ LAST_CONTACT_TIME = 5.minutes.ago
has_many :builds, class_name: 'Ci::Build'
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
@@ -33,6 +35,7 @@ module Ci
scope :shared, ->() { where(is_shared: true) }
scope :active, ->() { where(active: true) }
scope :paused, ->() { where(active: false) }
+ scope :online, ->() { where('contacted_at > ?', LAST_CONTACT_TIME) }
acts_as_taggable
@@ -56,7 +59,7 @@ module Ci
end
def display_name
- return token unless !description.blank?
+ return short_sha unless !description.blank?
description
end
@@ -65,6 +68,20 @@ module Ci
is_shared
end
+ def online?
+ contacted_at && contacted_at > LAST_CONTACT_TIME
+ end
+
+ def status
+ if contacted_at.nil?
+ :not_connected
+ elsif active?
+ online? ? :online : :offline
+ else
+ :paused
+ end
+ end
+
def belongs_to_one_project?
runner_projects.count == 1
end
@@ -78,7 +95,7 @@ module Ci
end
def short_sha
- token[0...10]
+ token[0...8] if token
end
end
end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index d5c50013525..492f6be1ce3 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -2,13 +2,13 @@ class Commit
extend ActiveModel::Naming
include ActiveModel::Conversion
- include Mentionable
include Participable
+ include Mentionable
include Referable
include StaticModel
attr_mentionable :safe_message
- participant :author, :committer, :notes, :mentioned_users
+ participant :author, :committer, :notes
attr_accessor :project
@@ -164,6 +164,14 @@ class Commit
@committer ||= User.find_by_any_email(committer_email)
end
+ def parents
+ @parents ||= parent_ids.map { |id| project.commit(id) }
+ end
+
+ def parent
+ @parent ||= project.commit(self.parent_id) if self.parent_id
+ end
+
def notes
project.notes.for_commit_id(self.id)
end
@@ -181,10 +189,6 @@ class Commit
@raw.short_id(7)
end
- def parents
- @parents ||= Commit.decorate(super, project)
- end
-
def ci_commit
project.ci_commit(sha)
end
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index b4d91b1b0c3..8188ba3a28e 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -16,6 +16,7 @@ class CommitStatus < ActiveRecord::Base
scope :success, -> { where(status: 'success') }
scope :failed, -> { where(status: 'failed') }
scope :running_or_pending, -> { where(status:[:running, :pending]) }
+ scope :finished, -> { where(status:[:success, :failed, :canceled]) }
scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :ref)) }
scope :ordered, -> { order(:ref, :stage_idx, :name) }
scope :for_ref, ->(ref) { where(ref: ref) }
@@ -27,7 +28,7 @@ class CommitStatus < ActiveRecord::Base
end
event :drop do
- transition running: :failed
+ transition [:pending, :running] => :failed
end
event :success do
@@ -88,4 +89,8 @@ class CommitStatus < ActiveRecord::Base
def retry_url
nil
end
+
+ def show_warning?
+ false
+ end
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 4db4ffb2e79..5e964f04ef5 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -6,8 +6,8 @@
#
module Issuable
extend ActiveSupport::Concern
- include Mentionable
include Participable
+ include Mentionable
included do
belongs_to :author, class_name: "User"
@@ -47,7 +47,7 @@ module Issuable
prefix: true
attr_mentionable :title, :description
- participant :author, :assignee, :notes, :mentioned_users
+ participant :author, :assignee, :notes_with_associations
end
module ClassMethods
@@ -85,6 +85,10 @@ module Issuable
assignee_id_changed?
end
+ def open?
+ opened? || reopened?
+ end
+
#
# Votes
#
@@ -176,6 +180,10 @@ module Issuable
self.class.to_s.underscore
end
+ def notes_with_associations
+ notes.includes(:author, :project)
+ end
+
private
def filter_superceded_votes(votes, notes)
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index 5b0ae411642..193c91f1742 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -20,6 +20,12 @@ module Mentionable
end
end
+ included do
+ if self < Participable
+ participant ->(current_user) { mentioned_users(current_user, load_lazy_references: false) }
+ end
+ end
+
# Returns the text used as the body of a Note when this object is referenced
#
# By default this will be the class name and the result of calling
@@ -41,55 +47,49 @@ module Mentionable
self
end
- # Determine whether or not a cross-reference Note has already been created between this Mentionable and
- # the specified target.
- def has_mentioned?(target)
- SystemNoteService.cross_reference_exists?(target, local_reference)
+ def all_references(current_user = self.author, text = self.mentionable_text, load_lazy_references: true)
+ ext = Gitlab::ReferenceExtractor.new(self.project, current_user, load_lazy_references: load_lazy_references)
+ ext.analyze(text)
+ ext
end
- def mentioned_users(current_user = nil)
- return [] if mentionable_text.blank?
-
- ext = Gitlab::ReferenceExtractor.new(self.project, current_user)
- ext.analyze(mentionable_text)
- ext.users.uniq
+ def mentioned_users(current_user = nil, load_lazy_references: true)
+ all_references(current_user, load_lazy_references: load_lazy_references).users
end
# Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference.
- def references(p = project, current_user = self.author, text = mentionable_text)
+ def referenced_mentionables(current_user = self.author, text = self.mentionable_text, load_lazy_references: true)
return [] if text.blank?
- ext = Gitlab::ReferenceExtractor.new(p, current_user)
- ext.analyze(text)
-
- (ext.issues + ext.merge_requests + ext.commits).uniq - [local_reference]
+ refs = all_references(current_user, text, load_lazy_references: load_lazy_references)
+ (refs.issues + refs.merge_requests + refs.commits) - [local_reference]
end
# Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
- def create_cross_references!(p = project, a = author, without = [])
- refs = references(p)
-
+ 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.
- refs.reject! { |ref| without.include?(ref) }
+ refs.reject! { |ref| without.include?(ref) || cross_reference_exists?(ref) }
refs.each do |ref|
- SystemNoteService.cross_reference(ref, local_reference, a)
+ SystemNoteService.cross_reference(ref, local_reference, author)
end
end
# When a mentionable field is changed, creates cross-reference notes that
# don't already exist
- def create_new_cross_references!(p = project, a = author)
+ def create_new_cross_references!(author = self.author)
changes = detect_mentionable_changes
return if changes.empty?
original_text = changes.collect { |_, vals| vals.first }.join(' ')
- preexisting = references(p, self.author, original_text)
- create_cross_references!(p, a, preexisting)
+ preexisting = referenced_mentionables(author, original_text)
+ create_cross_references!(author, preexisting)
end
private
@@ -111,4 +111,10 @@ 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)
+ SystemNoteService.cross_reference_exists?(target, local_reference)
+ end
end
diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb
index 7c9597333dd..85367f89f4f 100644
--- a/app/models/concerns/participable.rb
+++ b/app/models/concerns/participable.rb
@@ -12,7 +12,7 @@
#
# # ...
#
-# participant :author, :assignee, :mentioned_users, :notes
+# participant :author, :assignee, :notes, ->(current_user) { mentioned_users(current_user) }
# end
#
# issue = Issue.last
@@ -27,7 +27,7 @@ module Participable
module ClassMethods
def participant(*attrs)
- participant_attrs.concat(attrs.map(&:to_s))
+ participant_attrs.concat(attrs)
end
def participant_attrs
@@ -37,21 +37,21 @@ module Participable
# Be aware that this method makes a lot of sql queries.
# Save result into variable if you are going to reuse it inside same request
- def participants(current_user = self.author, project = self.project)
+ def participants(current_user = self.author, load_lazy_references: true)
participants = self.class.participant_attrs.flat_map do |attr|
- meth = method(attr)
-
value =
- if meth.arity == 1 || meth.arity == -1
- meth.call(current_user)
+ if attr.respond_to?(:call)
+ instance_exec(current_user, &attr)
else
- meth.call
+ send(attr)
end
- participants_for(value, current_user, project)
+ participants_for(value, current_user)
end.compact.uniq
- if project
+ if load_lazy_references
+ participants = Gitlab::Markdown::ReferenceFilter::LazyReference.load(participants).uniq
+
participants.select! do |user|
user.can?(:read_project, project)
end
@@ -62,14 +62,14 @@ module Participable
private
- def participants_for(value, current_user = nil, project = nil)
+ def participants_for(value, current_user = nil)
case value
- when User
+ when User, Gitlab::Markdown::ReferenceFilter::LazyReference
[value]
when Enumerable, ActiveRecord::Relation
- value.flat_map { |v| participants_for(v, current_user, project) }
+ value.flat_map { |v| participants_for(v, current_user) }
when Participable
- value.participants(current_user, project)
+ value.participants(current_user, load_lazy_references: false)
end
end
end
diff --git a/app/models/group.rb b/app/models/group.rb
index 9cd146bb73b..465c22d23ac 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -64,7 +64,7 @@ class Group < Namespace
end
def owners
- @owners ||= group_members.owners.map(&:user)
+ @owners ||= group_members.owners.includes(:user).map(&:user)
end
def add_users(user_ids, access_level, current_user = nil)
diff --git a/app/models/group_label.rb b/app/models/group_label.rb
new file mode 100644
index 00000000000..0fc39cb8771
--- /dev/null
+++ b/app/models/group_label.rb
@@ -0,0 +1,9 @@
+class GroupLabel
+ attr_accessor :title, :labels
+ alias_attribute :name, :title
+
+ def initialize(title, labels)
+ @title = title
+ @labels = labels
+ end
+end
diff --git a/app/models/group_milestone.rb b/app/models/group_milestone.rb
index 1dd2be68ebf..91844da62e2 100644
--- a/app/models/group_milestone.rb
+++ b/app/models/group_milestone.rb
@@ -1,5 +1,5 @@
class GroupMilestone
-
+ attr_accessor :title, :milestones
alias_attribute :name, :title
def initialize(title, milestones)
@@ -7,18 +7,10 @@ class GroupMilestone
@milestones = milestones
end
- def title
- @title
- end
-
def safe_title
@title.parameterize
end
-
- def milestones
- @milestones
- end
-
+
def projects
milestones.map { |milestone| milestone.project }
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index fc7e9abe29e..72183108033 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -95,4 +95,14 @@ class Issue < ActiveRecord::Base
def source_project
project
end
+
+ # From all notes on this issue, we'll select the system notes about linked
+ # merge requests. Of those, the MRs closing `self` are returned.
+ def closed_by_merge_requests(current_user = nil)
+ return [] unless open?
+
+ notes.system.flat_map do |note|
+ note.all_references(current_user).merge_requests
+ end.uniq.select { |mr| mr.open? && mr.closes_issue?(self) }
+ end
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index c83b15c7d39..21861a46a84 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -40,7 +40,7 @@ class MergeRequest < ActiveRecord::Base
after_create :create_merge_request_diff
after_update :update_merge_request_diff
- delegate :commits, :diffs, :last_commit, :last_commit_short_sha, to: :merge_request_diff, prefix: nil
+ delegate :commits, :diffs, to: :merge_request_diff, prefix: nil
# When this attribute is true some MR validation is ignored
# It allows us to close or modify broken merge requests
@@ -157,6 +157,18 @@ class MergeRequest < ActiveRecord::Base
reference
end
+ def last_commit
+ merge_request_diff ? merge_request_diff.last_commit : compare_commits.last
+ end
+
+ def first_commit
+ merge_request_diff ? merge_request_diff.first_commit : compare_commits.first
+ end
+
+ def last_commit_short_sha
+ last_commit.short_id
+ end
+
def validate_branches
if target_project == source_project && target_branch == source_branch
errors.add :branch_conflict, "You can not use same project/branch for source and target"
@@ -222,10 +234,6 @@ class MergeRequest < ActiveRecord::Base
self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last
end
- def open?
- opened? || reopened?
- end
-
def work_in_progress?
!!(title =~ /\A\[?WIP\]?:? /i)
end
@@ -294,6 +302,10 @@ class MergeRequest < ActiveRecord::Base
target_project
end
+ def closes_issue?(issue)
+ closes_issues.include?(issue)
+ end
+
# Return the set of issues that will be closed if this merge request is accepted.
def closes_issues(current_user = self.author)
if target_branch == project.default_branch
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index c9ef8023aea..6575d0bc81f 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -55,6 +55,10 @@ class MergeRequestDiff < ActiveRecord::Base
commits.first
end
+ def first_commit
+ commits.last
+ end
+
def last_commit_short_sha
@last_commit_short_sha ||= last_commit.short_id
end
@@ -163,7 +167,8 @@ class MergeRequestDiff < ActiveRecord::Base
merge_request.fetch_ref
# Get latest sha of branch from source project
- source_sha = merge_request.source_project.commit(source_branch).sha
+ source_commit = merge_request.source_project.commit(source_branch)
+ source_sha = source_commit.try(:sha)
Gitlab::CompareResult.new(
Gitlab::Git::Compare.new(
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 84acba30b6b..2ff16e2825c 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -105,4 +105,36 @@ class Milestone < ActiveRecord::Base
def author_id
nil
end
+
+ # Sorts the issues for the given IDs.
+ #
+ # This method runs a single SQL query using a CASE statement to update the
+ # position of all issues in the current milestone (scoped to the list of IDs).
+ #
+ # Given the ids [10, 20, 30] this method produces a SQL query something like
+ # the following:
+ #
+ # UPDATE issues
+ # SET position = CASE
+ # WHEN id = 10 THEN 1
+ # WHEN id = 20 THEN 2
+ # WHEN id = 30 THEN 3
+ # ELSE position
+ # END
+ # WHERE id IN (10, 20, 30);
+ #
+ # This method expects that the IDs given in `ids` are already Fixnums.
+ def sort_issues(ids)
+ pairs = []
+
+ ids.each_with_index do |id, index|
+ pairs << id
+ pairs << index + 1
+ end
+
+ conditions = 'WHEN id = ? THEN ? ' * ids.length
+
+ issues.where(id: ids).
+ update_all(["position = CASE #{conditions} ELSE position END", *pairs])
+ end
end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index bc8525df5a5..5782e649f8b 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -118,6 +118,8 @@ class Namespace < ActiveRecord::Base
gitlab_shell.add_namespace(path_was)
if gitlab_shell.mv_namespace(path_was, path)
+ Gitlab::UploadsTransfer.new.rename_namespace(path_was, path)
+
# If repositories moved successfully we need to
# send update instructions to users.
# However we cannot allow rollback since we moved namespace dir
diff --git a/app/models/note.rb b/app/models/note.rb
index de3b6df88f7..0b3aa30abd7 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -22,14 +22,14 @@ require 'carrierwave/orm/activerecord'
require 'file_size_validator'
class Note < ActiveRecord::Base
- include Mentionable
include Gitlab::CurrentSettings
include Participable
+ include Mentionable
default_value_for :system, false
attr_mentionable :note
- participant :author, :mentioned_users
+ participant :author
belongs_to :project
belongs_to :noteable, polymorphic: true
@@ -60,9 +60,13 @@ class Note < ActiveRecord::Base
scope :inc_author_project, ->{ includes(:project, :author) }
scope :inc_author, ->{ includes(:author) }
+ scope :with_associations, -> do
+ includes(:author, :noteable, :updated_by,
+ project: [:project_members, { group: [:group_members] }])
+ end
+
serialize :st_diff
before_create :set_diff, if: ->(n) { n.line_code.present? }
- after_update :set_references
class << self
def discussions_from_notes(notes)
@@ -333,15 +337,13 @@ class Note < ActiveRecord::Base
end
def noteable_type_name
- if noteable_type.present?
- noteable_type.downcase
- end
+ noteable_type.downcase if noteable_type.present?
end
# FIXME: Hack for polymorphic associations with STI
# For more information visit http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations
- def noteable_type=(sType)
- super(sType.to_s.classify.constantize.base_class.to_s)
+ def noteable_type=(noteable_type)
+ super(noteable_type.to_s.classify.constantize.base_class.to_s)
end
# Reset notes events cache
@@ -357,10 +359,6 @@ class Note < ActiveRecord::Base
Event.reset_event_cache_for(self)
end
- def set_references
- create_new_cross_references!(project, author)
- end
-
def system?
read_attribute(:system)
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 021920008ad..74b89aad499 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -119,7 +119,7 @@ class Project < ActiveRecord::Base
has_many :deploy_keys, through: :deploy_keys_projects
has_many :users_star_projects, dependent: :destroy
has_many :starrers, through: :users_star_projects, source: :user
- has_many :ci_commits, ->() { order('CASE WHEN ci_commits.committed_at IS NULL THEN 0 ELSE 1 END', :committed_at, :id) }, dependent: :destroy, class_name: 'Ci::Commit', foreign_key: :gl_project_id
+ has_many :ci_commits, dependent: :destroy, class_name: 'Ci::Commit', foreign_key: :gl_project_id
has_many :ci_builds, through: :ci_commits, source: :builds, dependent: :destroy, class_name: 'Ci::Build'
has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
@@ -243,11 +243,12 @@ class Project < ActiveRecord::Base
# Use of unscoped ensures we're not secretly adding any ORDER BYs, which
# have a negative impact on performance (and aren't needed for this
# query).
- unscoped.
+ projects = unscoped.
joins(:namespace).
- iwhere('namespaces.path' => namespace_path).
- iwhere('projects.path' => project_path).
- take
+ iwhere('namespaces.path' => namespace_path)
+
+ projects.where('projects.path' => project_path).take ||
+ projects.iwhere('projects.path' => project_path).take
end
def visibility_levels
@@ -567,7 +568,7 @@ class Project < ActiveRecord::Base
end
def empty_repo?
- !repository.exists? || repository.empty?
+ !repository.exists? || !repository.has_visible_content?
end
def repo
@@ -656,6 +657,8 @@ class Project < ActiveRecord::Base
# db changes in order to prevent out of sync between db and fs
raise Exception.new('repository cannot be renamed')
end
+
+ Gitlab::UploadsTransfer.new.rename_project(path_was, path, namespace.path)
end
def hook_attrs
diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb
index 5f5255ab487..d31b12f539e 100644
--- a/app/models/project_services/bamboo_service.rb
+++ b/app/models/project_services/bamboo_service.rb
@@ -48,7 +48,7 @@ class BambooService < CiService
end
def reset_password
- if prop_updated?(:bamboo_url)
+ if bamboo_url_changed? && !password_touched?
self.password = nil
end
end
diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb
index fb11cad352e..0b022461250 100644
--- a/app/models/project_services/teamcity_service.rb
+++ b/app/models/project_services/teamcity_service.rb
@@ -45,7 +45,7 @@ class TeamcityService < CiService
end
def reset_password
- if prop_updated?(:teamcity_url)
+ if teamcity_url_changed? && !password_touched?
self.password = nil
end
end
diff --git a/app/models/project_team.rb b/app/models/project_team.rb
index f602a965364..9f380a382cb 100644
--- a/app/models/project_team.rb
+++ b/app/models/project_team.rb
@@ -139,15 +139,28 @@ class ProjectTeam
Gitlab::Access.options.key max_member_access(user_id)
end
+ # This method assumes project and group members are eager loaded for optimal
+ # performance.
def max_member_access(user_id)
access = []
- access << project.project_members.find_by(user_id: user_id).try(:access_field)
+
+ project.project_members.each do |member|
+ if member.user_id == user_id
+ access << member.access_field if member.access_field
+ break
+ end
+ end
if group
- access << group.group_members.find_by(user_id: user_id).try(:access_field)
+ group.group_members.each do |member|
+ if member.user_id == user_id
+ access << member.access_field if member.access_field
+ break
+ end
+ end
end
- access.compact.max
+ access.max
end
private
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 8b51602bc23..88d3d73a40e 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -8,6 +8,14 @@ class Repository
attr_accessor :raw_repository, :path_with_namespace, :project
+ def self.clean_old_archives
+ repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path
+
+ return unless File.directory?(repository_downloads_path)
+
+ Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
+ end
+
def initialize(path_with_namespace, default_branch = nil, project = nil)
@path_with_namespace = path_with_namespace
@project = project
@@ -36,6 +44,19 @@ class Repository
raw_repository.empty?
end
+ #
+ # Git repository can contains some hidden refs like:
+ # /refs/notes/*
+ # /refs/git-as-svn/*
+ # /refs/pulls/*
+ # This refs by default not visible in project page and not cloned to client side.
+ #
+ # This method return true if repository contains some content visible in project page.
+ #
+ def has_visible_content?
+ !raw_repository.branches.empty?
+ end
+
def commit(id = 'HEAD')
return nil unless raw_repository
commit = Gitlab::Git::Commit.find(raw_repository, id)
@@ -269,14 +290,6 @@ class Repository
end
# Remove archives older than 2 hours
- def clean_old_archives
- repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path
-
- return unless File.directory?(repository_downloads_path)
-
- Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
- end
-
def branches_sorted_by(value)
case value
when 'recently_updated'
@@ -312,13 +325,7 @@ class Repository
end
def blob_for_diff(commit, diff)
- file = blob_at(commit.id, diff.new_path)
-
- unless file
- file = prev_blob_for_diff(commit, diff)
- end
-
- file
+ blob_at(commit.id, diff.file_path)
end
def prev_blob_for_diff(commit, diff)
@@ -480,6 +487,10 @@ class Repository
end
end
+ def merge_base(first_commit_id, second_commit_id)
+ rugged.merge_base(first_commit_id, second_commit_id)
+ end
+
def search_files(query, ref)
offset = 2
args = %W(git grep -i -n --before-context #{offset} --after-context #{offset} #{query} #{ref || root_ref})
diff --git a/app/models/service.rb b/app/models/service.rb
index 7e845d565b1..d610abd1683 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -33,6 +33,8 @@ class Service < ActiveRecord::Base
after_initialize :initialize_properties
+ after_commit :reset_updated_properties
+
belongs_to :project
has_one :service_hook
@@ -103,6 +105,7 @@ class Service < ActiveRecord::Base
# Provide convenient accessor methods
# for each serialized property.
+ # Also keep track of updated properties in a similar way as ActiveModel::Dirty
def self.prop_accessor(*args)
args.each do |arg|
class_eval %{
@@ -111,21 +114,39 @@ class Service < ActiveRecord::Base
end
def #{arg}=(value)
+ updated_properties['#{arg}'] = #{arg} unless #{arg}_changed?
self.properties['#{arg}'] = value
end
+
+ def #{arg}_changed?
+ #{arg}_touched? && #{arg} != #{arg}_was
+ end
+
+ def #{arg}_touched?
+ updated_properties.include?('#{arg}')
+ end
+
+ def #{arg}_was
+ updated_properties['#{arg}']
+ end
}
end
end
- # ActiveRecord does not provide a mechanism to track changes in serialized keys.
- # This is why we need to perform extra query to do it mannually.
- def prop_updated?(prop_name)
- relation_name = self.type.underscore
- previous_value = project.send(relation_name).send(prop_name)
- return false if previous_value.nil?
- previous_value != send(prop_name)
+ # Returns a hash of the properties that have been assigned a new value since last save,
+ # indicating their original values (attr => original value).
+ # ActiveRecord does not provide a mechanism to track changes in serialized keys,
+ # so we need a specific implementation for service properties.
+ # This allows to track changes to properties set with the accessor methods,
+ # but not direct manipulation of properties hash.
+ def updated_properties
+ @updated_properties ||= ActiveSupport::HashWithIndifferentAccess.new
end
+ def reset_updated_properties
+ @updated_properties = nil
+ end
+
def async_execute(data)
return unless supported_events.include?(data[:object_kind])
diff --git a/app/models/user.rb b/app/models/user.rb
index 889d2d3b867..7e4321d5376 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -68,6 +68,7 @@ class User < ActiveRecord::Base
include Referable
include Sortable
include TokenAuthenticatable
+ include CaseSensitivity
default_value_for :admin, false
default_value_for :can_create_group, gitlab_config.default_can_create_group
@@ -182,7 +183,7 @@ class User < ActiveRecord::Base
# User's Project preference
# Note: When adding an option, it MUST go on the end of the array.
- enum project_view: [:readme, :activity]
+ enum project_view: [:readme, :activity, :files]
alias_attribute :private_token, :authentication_token
@@ -273,8 +274,13 @@ class User < ActiveRecord::Base
end
def by_login(login)
- where('lower(username) = :value OR lower(email) = :value',
- value: login.to_s.downcase).first
+ return nil unless login
+
+ if login.include?('@'.freeze)
+ unscoped.iwhere(email: login).take
+ else
+ unscoped.iwhere(username: login).take
+ end
end
def find_by_username!(username)
@@ -700,12 +706,15 @@ class User < ActiveRecord::Base
end
def toggle_star(project)
- user_star_project = users_star_projects.
- where(project: project, user: self).take
- if user_star_project
- user_star_project.destroy
- else
- UsersStarProject.create!(project: project, user: self)
+ UsersStarProject.transaction do
+ user_star_project = users_star_projects.
+ where(project: project, user: self).lock(true).first
+
+ if user_star_project
+ user_star_project.destroy
+ else
+ UsersStarProject.create!(project: project, user: self)
+ end
end
end
diff --git a/app/services/archive_repository_service.rb b/app/services/archive_repository_service.rb
index e1b41527d8d..2160bf13e6d 100644
--- a/app/services/archive_repository_service.rb
+++ b/app/services/archive_repository_service.rb
@@ -7,19 +7,12 @@ class ArchiveRepositoryService
end
def execute(options = {})
- project.repository.clean_old_archives
+ RepositoryArchiveCacheWorker.perform_async
- raise "No archive file path" unless file_path
+ metadata = project.repository.archive_metadata(ref, storage_path, format)
+ raise "Repository or ref not found" if metadata.empty?
- return file_path if archived?
-
- unless archiving?
- RepositoryArchiveWorker.perform_async(project.id, ref, format)
- end
-
- archived = wait_until_archived(options[:timeout] || 5.0)
-
- file_path if archived
+ metadata
end
private
@@ -27,36 +20,4 @@ class ArchiveRepositoryService
def storage_path
Gitlab.config.gitlab.repository_downloads_path
end
-
- def file_path
- @file_path ||= project.repository.archive_file_path(ref, storage_path, format)
- end
-
- def pid_file_path
- @pid_file_path ||= project.repository.archive_pid_file_path(ref, storage_path, format)
- end
-
- def archived?
- File.exist?(file_path)
- end
-
- def archiving?
- File.exist?(pid_file_path)
- end
-
- def wait_until_archived(timeout = 5.0)
- return archived? if timeout == 0.0
-
- t1 = Time.now
-
- begin
- sleep 0.1
-
- success = archived?
-
- t2 = Time.now
- end until success || t2 - t1 >= timeout
-
- success
- end
end
diff --git a/app/services/ci/create_builds_service.rb b/app/services/ci/create_builds_service.rb
index c420f3268fd..912eb6258a4 100644
--- a/app/services/ci/create_builds_service.rb
+++ b/app/services/ci/create_builds_service.rb
@@ -1,8 +1,20 @@
module Ci
class CreateBuildsService
- def execute(commit, stage, ref, tag, user, trigger_request)
+ def execute(commit, stage, ref, tag, user, trigger_request, status)
builds_attrs = commit.config_processor.builds_for_stage_and_ref(stage, ref, tag)
+ # check when to create next build
+ builds_attrs = builds_attrs.select do |build_attrs|
+ case build_attrs[:when]
+ when 'on_success'
+ status == 'success'
+ when 'on_failure'
+ status == 'failed'
+ when 'always'
+ %w(success failed).include?(status)
+ end
+ end
+
builds_attrs.map do |build_attrs|
# don't create the same build twice
unless commit.builds.find_by(ref: ref, tag: tag, trigger_request: trigger_request, name: build_attrs[:name])
diff --git a/app/services/ci/register_build_service.rb b/app/services/ci/register_build_service.rb
index 71b61bbe389..7beb098659c 100644
--- a/app/services/ci/register_build_service.rb
+++ b/app/services/ci/register_build_service.rb
@@ -17,7 +17,7 @@ module Ci
builds = builds.order('created_at ASC')
build = builds.find do |build|
- (build.tag_list - current_runner.tag_list).empty?
+ build.can_be_served?(current_runner)
end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index f9a8265d2d4..3de7bb9dcaa 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -49,10 +49,13 @@ class GitPushService
elsif push_to_existing_branch?(ref, oldrev)
# Collect data for this git push
@push_commits = project.repository.commits_between(oldrev, newrev)
- project.update_merge_requests(oldrev, newrev, ref, @user)
process_commit_messages(ref)
end
+ # Update merge requests that may be affected by this push. A new branch
+ # could cause the last commit of a merge request to change.
+ project.update_merge_requests(oldrev, newrev, ref, @user)
+
@push_data = build_push_data(oldrev, newrev, ref)
# If CI was disabled but .gitlab-ci.yml file was pushed
@@ -74,48 +77,30 @@ class GitPushService
def process_commit_messages(ref)
is_default_branch = is_default_branch?(ref)
- @push_commits.each do |commit|
- # Close issues if these commits were pushed to the project's default branch and the commit message matches the
- # closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to
- # a different branch.
- issues_to_close = commit.closes_issues(user)
+ authors = Hash.new do |hash, commit|
+ email = commit.author_email
+ next hash[email] if hash.has_key?(email)
- # Load commit author only if needed.
- # For push with 1k commits it prevents 900+ requests in database
- author = nil
+ hash[email] = commit_user(commit)
+ end
+ @push_commits.each do |commit|
# Keep track of the issues that will be actually closed because they are on a default branch.
# Hence, when creating cross-reference notes, the not-closed issues (on non-default branches)
# will also have cross-reference.
- actually_closed_issues = []
-
- if issues_to_close.present? && is_default_branch
- author ||= commit_user(commit)
- actually_closed_issues = issues_to_close
- issues_to_close.each do |issue|
- Issues::CloseService.new(project, author, {}).execute(issue, commit)
+ closed_issues = []
+
+ if is_default_branch
+ # Close issues if these commits were pushed to the project's default branch and the commit message matches the
+ # closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to
+ # a different branch.
+ closed_issues = commit.closes_issues(user)
+ closed_issues.each do |issue|
+ Issues::CloseService.new(project, authors[commit], {}).execute(issue, commit)
end
end
- if project.default_issues_tracker?
- create_cross_reference_notes(commit, actually_closed_issues)
- end
- end
- end
-
- def create_cross_reference_notes(commit, issues_to_close)
- # Create cross-reference notes for any other references than those given in issues_to_close.
- # Omit any issues that were referenced in an issue-closing phrase, or have already been
- # mentioned from this commit (probably from this commit being pushed to a different branch).
- refs = commit.references(project, user) - issues_to_close
- refs.reject! { |r| commit.has_mentioned?(r) }
-
- if refs.present?
- author ||= commit_user(commit)
-
- refs.each do |r|
- SystemNoteService.cross_reference(r, commit, author)
- end
+ commit.create_cross_references!(authors[commit], closed_issues)
end
end
diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb
index 1ea4b72216c..bcb380d3215 100644
--- a/app/services/issues/create_service.rb
+++ b/app/services/issues/create_service.rb
@@ -10,7 +10,7 @@ module Issues
issue.update_attributes(label_ids: label_params)
notification_service.new_issue(issue, current_user)
event_service.open_issue(issue, current_user)
- issue.create_cross_references!(issue.project, current_user)
+ issue.create_cross_references!(current_user)
execute_hooks(issue, 'open')
end
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index 2fc6ef7f356..2b5426ad452 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -35,7 +35,7 @@ module Issues
create_title_change_note(issue, issue.previous_changes['title'].first)
end
- issue.create_new_cross_references!(issue.project, current_user)
+ issue.create_new_cross_references!
execute_hooks(issue, 'update')
end
diff --git a/app/services/labels/group_service.rb b/app/services/labels/group_service.rb
new file mode 100644
index 00000000000..b26cee24d56
--- /dev/null
+++ b/app/services/labels/group_service.rb
@@ -0,0 +1,26 @@
+module Labels
+ class GroupService < ::BaseService
+ def initialize(project_labels)
+ @project_labels = project_labels.group_by(&:title)
+ end
+
+ def execute
+ build(@project_labels)
+ end
+
+ def label(title)
+ if title
+ group_label = @project_labels[title].group_by(&:title)
+ build(group_label).first
+ else
+ nil
+ end
+ end
+
+ private
+
+ def build(label)
+ label.map { |title, labels| GroupLabel.new(title, labels) }
+ end
+ end
+end
diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb
index 9651b16462c..009d5a6867e 100644
--- a/app/services/merge_requests/create_service.rb
+++ b/app/services/merge_requests/create_service.rb
@@ -18,7 +18,7 @@ module MergeRequests
merge_request.update_attributes(label_ids: label_params)
event_service.open_mr(merge_request, current_user)
notification_service.new_merge_request(merge_request, current_user)
- merge_request.create_cross_references!(merge_request.project, current_user)
+ merge_request.create_cross_references!(current_user)
execute_hooks(merge_request)
end
diff --git a/app/services/merge_requests/post_merge_service.rb b/app/services/merge_requests/post_merge_service.rb
index aceb8cb9021..8f25c5e2496 100644
--- a/app/services/merge_requests/post_merge_service.rb
+++ b/app/services/merge_requests/post_merge_service.rb
@@ -6,6 +6,7 @@ module MergeRequests
#
class PostMergeService < MergeRequests::BaseService
def execute(merge_request)
+ close_issues(merge_request)
merge_request.mark_as_merged
create_merge_event(merge_request, current_user)
create_note(merge_request)
@@ -15,6 +16,15 @@ module MergeRequests
private
+ def close_issues(merge_request)
+ return unless merge_request.target_branch == project.default_branch
+
+ closed_issues = merge_request.closes_issues(current_user)
+ closed_issues.each do |issue|
+ Issues::CloseService.new(project, current_user, {}).execute(issue, merge_request)
+ end
+ end
+
def create_merge_event(merge_request, current_user)
EventCreateService.new.merge_mr(merge_request, current_user)
end
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index e903e48e3cd..d68bc79ecc0 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -5,13 +5,20 @@ module MergeRequests
@oldrev, @newrev = oldrev, newrev
@branch_name = Gitlab::Git.ref_name(ref)
- @fork_merge_requests = @project.fork_merge_requests.opened
- @commits = @project.repository.commits_between(oldrev, newrev)
- close_merge_requests
+ find_new_commits
reload_merge_requests
+
+ # Leave a system note if a branch was deleted/added
+ if branch_added? || branch_removed?
+ comment_mr_branch_presence_changed
+ comment_mr_with_commits
+ else
+ comment_mr_with_commits
+ close_merge_requests
+ end
+
execute_mr_web_hooks
- comment_mr_with_commits
true
end
@@ -31,7 +38,6 @@ module MergeRequests
commit_ids.include?(merge_request.last_commit.id)
end
-
merge_requests.uniq.select(&:source_project).each do |merge_request|
MergeRequests::PostMergeService.
new(merge_request.target_project, @current_user).
@@ -47,7 +53,7 @@ module MergeRequests
# Note: we should update merge requests from forks too
def reload_merge_requests
merge_requests = @project.merge_requests.opened.by_branch(@branch_name).to_a
- merge_requests += @fork_merge_requests.by_branch(@branch_name).to_a
+ merge_requests += fork_merge_requests.by_branch(@branch_name).to_a
merge_requests = filter_merge_requests(merge_requests)
merge_requests.each do |merge_request|
@@ -70,13 +76,48 @@ module MergeRequests
end
end
+ def find_new_commits
+ if branch_added?
+ @commits = []
+
+ merge_request = merge_requests_for_source_branch.first
+ return unless merge_request
+
+ last_commit = merge_request.last_commit
+
+ begin
+ # Since any number of commits could have been made to the restored branch,
+ # find the common root to see what has been added.
+ common_ref = @project.repository.merge_base(last_commit.id, @newrev)
+ # If the a commit no longer exists in this repo, gitlab_git throws
+ # a Rugged::OdbError. This is fixed in https://gitlab.com/gitlab-org/gitlab_git/merge_requests/52
+ @commits = @project.repository.commits_between(common_ref, @newrev) if common_ref
+ rescue
+ end
+ elsif branch_removed?
+ # No commits for a deleted branch.
+ @commits = []
+ else
+ @commits = @project.repository.commits_between(@oldrev, @newrev)
+ end
+ end
+
+ # Add comment about branches being deleted or added to merge requests
+ def comment_mr_branch_presence_changed
+ presence = branch_added? ? :add : :delete
+
+ merge_requests_for_source_branch.each do |merge_request|
+ SystemNoteService.change_branch_presence(
+ merge_request, merge_request.project, @current_user,
+ :source, @branch_name, presence)
+ end
+ end
+
# Add comment about pushing new commits to merge requests
def comment_mr_with_commits
- merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a
- merge_requests += @fork_merge_requests.where(source_branch: @branch_name).to_a
- merge_requests = filter_merge_requests(merge_requests)
+ return unless @commits.present?
- merge_requests.each do |merge_request|
+ merge_requests_for_source_branch.each do |merge_request|
mr_commit_ids = Set.new(merge_request.commits.map(&:id))
new_commits, existing_commits = @commits.partition do |commit|
@@ -91,14 +132,7 @@ module MergeRequests
# Call merge request webhook with update branches
def execute_mr_web_hooks
- merge_requests = @project.origin_merge_requests.opened
- .where(source_branch: @branch_name)
- .to_a
- merge_requests += @fork_merge_requests.where(source_branch: @branch_name)
- .to_a
- merge_requests = filter_merge_requests(merge_requests)
-
- merge_requests.each do |merge_request|
+ merge_requests_for_source_branch.each do |merge_request|
execute_hooks(merge_request, 'update')
end
end
@@ -106,5 +140,25 @@ module MergeRequests
def filter_merge_requests(merge_requests)
merge_requests.uniq.select(&:source_project)
end
+
+ def merge_requests_for_source_branch
+ @source_merge_requests ||= begin
+ merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a
+ merge_requests += fork_merge_requests.where(source_branch: @branch_name).to_a
+ filter_merge_requests(merge_requests)
+ end
+ end
+
+ def fork_merge_requests
+ @fork_merge_requests ||= @project.fork_merge_requests.opened
+ end
+
+ def branch_added?
+ Gitlab::Git.blank_ref?(@oldrev)
+ end
+
+ def branch_removed?
+ Gitlab::Git.blank_ref?(@newrev)
+ end
end
end
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 25d79e22e39..ebbe0af803b 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -59,7 +59,7 @@ module MergeRequests
merge_request.mark_as_unchecked
end
- merge_request.create_new_cross_references!(merge_request.project, current_user)
+ merge_request.create_new_cross_references!
execute_hooks(merge_request, 'update')
end
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index 482c0444049..2001dc89c33 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -11,13 +11,7 @@ module Notes
# Skip system notes, like status changes and cross-references.
unless note.system
event_service.leave_note(note, note.author)
-
- # Create a cross-reference note if this Note contains GFM that names an
- # issue, merge request, or commit.
- note.references.each do |mentioned|
- SystemNoteService.cross_reference(mentioned, note.noteable, note.author)
- end
-
+ note.create_cross_references!
execute_hooks(note)
end
end
diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb
index c22a9333ef6..6c2f08e5963 100644
--- a/app/services/notes/update_service.rb
+++ b/app/services/notes/update_service.rb
@@ -4,7 +4,7 @@ module Notes
return note unless note.editable?
note.update_attributes(params.merge(updated_by: current_user))
-
+ note.create_new_cross_references!
note.reset_events_cache
note
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index c327c244f0d..64ea6dd42eb 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -27,6 +27,7 @@ module Projects
def transfer(project, new_namespace)
Project.transaction do
old_path = project.path_with_namespace
+ old_namespace = project.namespace
new_path = File.join(new_namespace.try(:path) || '', project.path)
if Project.where(path: project.path, namespace_id: new_namespace.try(:id)).present?
@@ -51,6 +52,9 @@ module Projects
# clear project cached events
project.reset_events_cache
+ # Move uploads
+ Gitlab::UploadsTransfer.new.move_project(project.path, old_namespace.path, new_namespace.path)
+
true
end
end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 8253c1f780d..708c2f00486 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -168,6 +168,31 @@ class SystemNoteService
create_note(noteable: noteable, project: project, author: author, note: body)
end
+ # Called when a branch in Noteable is added or deleted
+ #
+ # noteable - Noteable object
+ # project - Project owning noteable
+ # author - User performing the change
+ # branch_type - :source or :target
+ # branch - branch name
+ # presence - :add or :delete
+ #
+ # Example Note text:
+ #
+ # "Restored target branch `feature`"
+ #
+ # Returns the created Note object
+ def self.change_branch_presence(noteable, project, author, branch_type, branch, presence)
+ verb =
+ if presence == :add
+ 'restored'
+ else
+ 'deleted'
+ end
+ body = "#{verb} #{branch_type.to_s} branch `#{branch}`".capitalize
+ create_note(noteable: noteable, project: project, author: author, note: body)
+ end
+
# Called when a Mentionable references a Noteable
#
# noteable - Noteable object being referenced
@@ -302,7 +327,7 @@ class SystemNoteService
commit_ids = if count == 1
existing_commits.first.short_id
else
- if oldrev
+ if oldrev && !Gitlab::Git.blank_ref?(oldrev)
"#{Commit.truncate_sha(oldrev)}...#{existing_commits.last.short_id}"
else
"#{existing_commits.first.short_id}..#{existing_commits.last.short_id}"
diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb
index f9673abbfe8..e8211585834 100644
--- a/app/uploaders/file_uploader.rb
+++ b/app/uploaders/file_uploader.rb
@@ -26,7 +26,7 @@ class FileUploader < CarrierWave::Uploader::Base
end
def secure_url
- File.join(Gitlab.config.gitlab.url, @project.path_with_namespace, "uploads", @secret, file.filename)
+ File.join("/uploads", @secret, file.filename)
end
def file_storage?
diff --git a/app/views/abuse_report_mailer/notify.html.haml b/app/views/abuse_report_mailer/notify.html.haml
new file mode 100644
index 00000000000..619533e09a7
--- /dev/null
+++ b/app/views/abuse_report_mailer/notify.html.haml
@@ -0,0 +1,11 @@
+%p
+ #{link_to @abuse_report.user.name, user_url(@abuse_report.user)}
+ (@#{@abuse_report.user.username}) was reported for abuse by
+ #{link_to @abuse_report.reporter.name, user_url(@abuse_report.reporter)}
+ (@#{@abuse_report.reporter.username}).
+
+%blockquote
+ = @abuse_report.message
+
+%p
+ = link_to "View details", abuse_reports_url
diff --git a/app/views/abuse_report_mailer/notify.text.haml b/app/views/abuse_report_mailer/notify.text.haml
new file mode 100644
index 00000000000..7dacf857035
--- /dev/null
+++ b/app/views/abuse_report_mailer/notify.text.haml
@@ -0,0 +1,5 @@
+#{@abuse_report.user.name} (@#{@abuse_report.user.username}) was reported for abuse by #{@abuse_report.reporter.name} (@#{@abuse_report.reporter.username}).
+\
+> #{@abuse_report.message}
+\
+View details: #{admin_abuse_reports_url}
diff --git a/app/views/admin/abuse_reports/index.html.haml b/app/views/admin/abuse_reports/index.html.haml
index 2e8746146d1..40a5fe4628b 100644
--- a/app/views/admin/abuse_reports/index.html.haml
+++ b/app/views/admin/abuse_reports/index.html.haml
@@ -2,16 +2,17 @@
%h3.page-title Abuse Reports
%hr
- if @abuse_reports.present?
- %table.table
- %thead
- %tr
- %th Reported by
- %th Reported at
- %th Message
- %th User
- %th Primary action
- %th
- = render @abuse_reports
+ .table-holder
+ %table.table
+ %thead
+ %tr
+ %th Reported by
+ %th Reported at
+ %th Message
+ %th User
+ %th Primary action
+ %th
+ = render @abuse_reports
= paginate @abuse_reports
- else
%h4 There are no abuse reports
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index a36ae0b766c..7a78526e09a 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -47,6 +47,12 @@
= f.label :version_check_enabled do
= f.check_box :version_check_enabled
Version check enabled
+ .form-group
+ = f.label :admin_notification_email, class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.text_field :admin_notification_email, class: 'form-control'
+ .help-block
+ Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area.
%fieldset
%legend Account and Limit Settings
diff --git a/app/views/admin/applications/show.html.haml b/app/views/admin/applications/show.html.haml
index 0ea2ffeda99..3eb9d61972b 100644
--- a/app/views/admin/applications/show.html.haml
+++ b/app/views/admin/applications/show.html.haml
@@ -3,25 +3,26 @@
Application: #{@application.name}
-%table.table
- %tr
- %td
- Application Id
- %td
- %code#application_id= @application.uid
- %tr
- %td
- Secret:
- %td
- %code#secret= @application.secret
+.table-holder
+ %table.table
+ %tr
+ %td
+ Application Id
+ %td
+ %code#application_id= @application.uid
+ %tr
+ %td
+ Secret:
+ %td
+ %code#secret= @application.secret
- %tr
- %td
- Callback url
- %td
- - @application.redirect_uri.split.each do |uri|
- %div
- %span.monospace= uri
+ %tr
+ %td
+ Callback url
+ %td
+ - @application.redirect_uri.split.each do |uri|
+ %div
+ %span.monospace= uri
.form-actions
= link_to 'Edit', edit_admin_application_path(@application), class: 'btn btn-primary wide pull-left'
= render 'delete_form', application: @application, submit_btn_css: 'btn btn-danger prepend-left-10'
diff --git a/app/views/admin/background_jobs/show.html.haml b/app/views/admin/background_jobs/show.html.haml
index 3a01e115109..de5bc050cf0 100644
--- a/app/views/admin/background_jobs/show.html.haml
+++ b/app/views/admin/background_jobs/show.html.haml
@@ -12,24 +12,25 @@
%i.fa.fa-exclamation-triangle
There are no running sidekiq processes. Please restart GitLab
- else
- %table.table
- %thead
- %th USER
- %th PID
- %th CPU
- %th MEM
- %th STATE
- %th START
- %th COMMAND
- %tbody
- - @sidekiq_processes.each do |process|
- - next unless process.match(/(sidekiq \d+\.\d+\.\d+.+$)/)
- - data = process.strip.split(' ')
- %tr
- %td= gitlab_config.user
- - 5.times do
- %td= data.shift
- %td= data.join(' ')
+ .table-holder
+ %table.table
+ %thead
+ %th USER
+ %th PID
+ %th CPU
+ %th MEM
+ %th STATE
+ %th START
+ %th COMMAND
+ %tbody
+ - @sidekiq_processes.each do |process|
+ - next unless process.match(/(sidekiq \d+\.\d+\.\d+.+$)/)
+ - data = process.strip.split(' ')
+ %tr
+ %td= gitlab_config.user
+ - 5.times do
+ %td= data.shift
+ %td= data.join(' ')
.clearfix
%p
diff --git a/app/views/admin/deploy_keys/index.html.haml b/app/views/admin/deploy_keys/index.html.haml
index 2bf1689cbc6..841e6971fb2 100644
--- a/app/views/admin/deploy_keys/index.html.haml
+++ b/app/views/admin/deploy_keys/index.html.haml
@@ -5,22 +5,23 @@
.panel-head-actions
= link_to 'New Deploy Key', new_admin_deploy_key_path, class: "btn btn-new btn-sm"
- if @deploy_keys.any?
- %table.table
- %thead.panel-heading
- %tr
- %th Title
- %th Fingerprint
- %th Added at
- %th
- %tbody
- - @deploy_keys.each do |deploy_key|
+ .table-holder
+ %table.table
+ %thead.panel-heading
%tr
- %td
- %strong= deploy_key.title
- %td
- %code.key-fingerprint= deploy_key.fingerprint
- %td
- %span.cgray
- added #{time_ago_with_tooltip(deploy_key.created_at)}
- %td
- = link_to 'Remove', admin_deploy_key_path(deploy_key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-sm btn-remove delete-key pull-right"
+ %th Title
+ %th Fingerprint
+ %th Added at
+ %th
+ %tbody
+ - @deploy_keys.each do |deploy_key|
+ %tr
+ %td
+ %strong= deploy_key.title
+ %td
+ %code.key-fingerprint= deploy_key.fingerprint
+ %td
+ %span.cgray
+ added #{time_ago_with_tooltip(deploy_key.created_at)}
+ %td
+ = link_to 'Remove', admin_deploy_key_path(deploy_key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-sm btn-remove delete-key pull-right"
diff --git a/app/views/admin/identities/index.html.haml b/app/views/admin/identities/index.html.haml
index ae57e3adc4d..8358a14445b 100644
--- a/app/views/admin/identities/index.html.haml
+++ b/app/views/admin/identities/index.html.haml
@@ -2,12 +2,13 @@
= render 'admin/users/head'
- if @identities.present?
- %table.table
- %thead
- %tr
- %th Provider
- %th Identifier
- %th
- = render @identities
+ .table-holder
+ %table.table
+ %thead
+ %tr
+ %th Provider
+ %th Identifier
+ %th
+ = render @identities
- else
%h4 This user has no identities
diff --git a/app/views/admin/services/index.html.haml b/app/views/admin/services/index.html.haml
index e2377291142..6a5986f496a 100644
--- a/app/views/admin/services/index.html.haml
+++ b/app/views/admin/services/index.html.haml
@@ -2,22 +2,23 @@
%h3.page-title Service templates
%p.light Service template allows you to set default values for project services
-%table.table
- %thead
- %tr
- %th
- %th Service
- %th Description
- %th Last edit
- - @services.sort_by(&:title).each do |service|
- %tr
- %td
- = icon("copy", class: 'clgray')
- %td
- = link_to edit_admin_application_settings_service_path(service.id) do
- %strong= service.title
- %td
- = service.description
- %td.light
- = time_ago_in_words service.updated_at
- ago
+.table-holder
+ %table.table
+ %thead
+ %tr
+ %th
+ %th Service
+ %th Description
+ %th Last edit
+ - @services.sort_by(&:title).each do |service|
+ %tr
+ %td
+ = icon("copy", class: 'clgray')
+ %td
+ = link_to edit_admin_application_settings_service_path(service.id) do
+ %strong= service.title
+ %td
+ = service.description
+ %td.light
+ = time_ago_in_words service.updated_at
+ ago
diff --git a/app/views/users/_profile.html.haml b/app/views/admin/users/_profile.html.haml
index 90d9980c85c..90d9980c85c 100644
--- a/app/views/users/_profile.html.haml
+++ b/app/views/admin/users/_profile.html.haml
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index a383ea57384..0848504b7a6 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -8,13 +8,13 @@
= @user.name
%ul.well-list
%li
- = image_tag avatar_icon(@user.email, 60), class: "avatar s60"
+ = image_tag avatar_icon(@user, 60), class: "avatar s60"
%li
%span.light Profile page:
%strong
= link_to user_path(@user) do
= @user.username
- = render 'users/profile', user: @user
+ = render 'admin/users/profile', user: @user
.panel.panel-default
.panel-heading
diff --git a/app/views/ci/admin/events/index.html.haml b/app/views/ci/admin/events/index.html.haml
index f9ab0994304..5a5b4dc7c35 100644
--- a/app/views/ci/admin/events/index.html.haml
+++ b/app/views/ci/admin/events/index.html.haml
@@ -1,17 +1,18 @@
-%table.table
- %thead
- %tr
- %th User ID
- %th Description
- %th When
- - @events.each do |event|
- %tr
- %td
- = event.user_id
- %td
- = event.description
- %td.light
- = time_ago_in_words event.updated_at
- ago
+.table-holder
+ %table.table
+ %thead
+ %tr
+ %th User ID
+ %th Description
+ %th When
+ - @events.each do |event|
+ %tr
+ %td
+ = event.user_id
+ %td
+ = event.description
+ %td.light
+ = time_ago_in_words event.updated_at
+ ago
-= paginate @events \ No newline at end of file
+= paginate @events
diff --git a/app/views/ci/admin/projects/index.html.haml b/app/views/ci/admin/projects/index.html.haml
index dc7b041473b..0da8547924b 100644
--- a/app/views/ci/admin/projects/index.html.haml
+++ b/app/views/ci/admin/projects/index.html.haml
@@ -1,15 +1,16 @@
-%table.table
- %thead
- %tr
- %th ID
- %th Name
- %th Last build
- %th Access
- %th Builds
- %th
+.table-holder
+ %table.table
+ %thead
+ %tr
+ %th ID
+ %th Name
+ %th Last build
+ %th Access
+ %th Builds
+ %th
- - @projects.each do |project|
- = render "ci/admin/projects/project", project: project
+ - @projects.each do |project|
+ = render "ci/admin/projects/project", project: project
= paginate @projects
diff --git a/app/views/ci/admin/runners/index.html.haml b/app/views/ci/admin/runners/index.html.haml
index 01ce81b4476..bb213fbffc4 100644
--- a/app/views/ci/admin/runners/index.html.haml
+++ b/app/views/ci/admin/runners/index.html.haml
@@ -35,18 +35,19 @@
%br
-%table.table
- %thead
- %tr
- %th Type
- %th Runner token
- %th Description
- %th Projects
- %th Builds
- %th Tags
- %th Last contact
- %th
+.table-holder
+ %table.table
+ %thead
+ %tr
+ %th Type
+ %th Runner token
+ %th Description
+ %th Projects
+ %th Builds
+ %th Tags
+ %th Last contact
+ %th
- - @runners.each do |runner|
- = render "ci/admin/runners/runner", runner: runner
+ - @runners.each do |runner|
+ = render "ci/admin/runners/runner", runner: runner
= paginate @runners
diff --git a/app/views/ci/events/index.html.haml b/app/views/ci/events/index.html.haml
index 779f49b3d3a..9824e85b1af 100644
--- a/app/views/ci/events/index.html.haml
+++ b/app/views/ci/events/index.html.haml
@@ -1,19 +1,20 @@
%h3.page-title Events
-%table.table
- %thead
- %tr
- %th User ID
- %th Description
- %th When
- - @events.each do |event|
- %tr
- %td
- = event.user_id
- %td
- = event.description
- %td.light
- = time_ago_in_words event.updated_at
- ago
+.table-holder
+ %table.table
+ %thead
+ %tr
+ %th User ID
+ %th Description
+ %th When
+ - @events.each do |event|
+ %tr
+ %td
+ = event.user_id
+ %td
+ = event.description
+ %td.light
+ = time_ago_in_words event.updated_at
+ ago
-= paginate @events \ No newline at end of file
+= paginate @events
diff --git a/app/views/ci/lints/_create.html.haml b/app/views/ci/lints/_create.html.haml
index e2179e60f3e..f45cd05aec0 100644
--- a/app/views/ci/lints/_create.html.haml
+++ b/app/views/ci/lints/_create.html.haml
@@ -4,29 +4,30 @@
syntax is correct
%i.fa.fa-ok.correct-syntax
- %table.table.table-bordered
- %thead
- %tr
- %th Parameter
- %th Value
- %tbody
- - @stages.each do |stage|
- - @builds.select { |build| build[:stage] == stage }.each do |build|
- %tr
- %td #{stage.capitalize} Job - #{build[:name]}
- %td
- %pre
- = simple_format build[:script]
+ .table-holder
+ %table.table.table-bordered
+ %thead
+ %tr
+ %th Parameter
+ %th Value
+ %tbody
+ - @stages.each do |stage|
+ - @builds.select { |build| build[:stage] == stage }.each do |build|
+ %tr
+ %td #{stage.capitalize} Job - #{build[:name]}
+ %td
+ %pre
+ = simple_format build[:script]
- %br
- %b Tag list:
- = build[:tags]
- %br
- %b Refs only:
- = build[:only] && build[:only].join(", ")
- %br
- %b Refs except:
- = build[:except] && build[:except].join(", ")
+ %br
+ %b Tag list:
+ = build[:tags]
+ %br
+ %b Refs only:
+ = build[:only] && build[:only].join(", ")
+ %br
+ %b Refs except:
+ = build[:except] && build[:except].join(", ")
-else
%p
diff --git a/app/views/ci/projects/index.html.haml b/app/views/ci/projects/index.html.haml
new file mode 100644
index 00000000000..9c2290bc4a5
--- /dev/null
+++ b/app/views/ci/projects/index.html.haml
@@ -0,0 +1,20 @@
+.wiki
+ %h1
+ GitLab CI is now integrated in GitLab UI
+ %h2 For existing projects
+
+ %p
+ Check the following pages to find the CI status you're looking for:
+
+ %ul
+ %li Projects page - shows CI status for each project.
+ %li Project commits page - show CI status for each commit.
+
+
+
+ %h2 For new projects
+
+ %p
+ If you want to enable CI for a new project it is easy as adding
+ = link_to ".gitlab-ci.yml", "http://doc.gitlab.com/ce/ci/yaml/README.html"
+ file to your repository
diff --git a/app/views/dashboard/milestones/_issue.html.haml b/app/views/dashboard/milestones/_issue.html.haml
index f689b9698eb..1408ebdd5dc 100644
--- a/app/views/dashboard/milestones/_issue.html.haml
+++ b/app/views/dashboard/milestones/_issue.html.haml
@@ -7,4 +7,4 @@
= link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title
.pull-right.assignee-icon
- if issue.assignee
- = image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16"
+ = image_tag avatar_icon(issue.assignee, 16), class: "avatar s16"
diff --git a/app/views/dashboard/milestones/_merge_request.html.haml b/app/views/dashboard/milestones/_merge_request.html.haml
index 8f5c4cce529..77c46de030b 100644
--- a/app/views/dashboard/milestones/_merge_request.html.haml
+++ b/app/views/dashboard/milestones/_merge_request.html.haml
@@ -7,4 +7,4 @@
= link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title
.pull-right.assignee-icon
- if merge_request.assignee
- = image_tag avatar_icon(merge_request.assignee.email, 16), class: "avatar s16"
+ = image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16"
diff --git a/app/views/dashboard/milestones/show.html.haml b/app/views/dashboard/milestones/show.html.haml
index 0d204ced7ea..2fe14c6388c 100644
--- a/app/views/dashboard/milestones/show.html.haml
+++ b/app/views/dashboard/milestones/show.html.haml
@@ -13,26 +13,28 @@
%span All issues for this milestone are closed. You may close the milestone now.
.description
-%table.table
- %thead
- %tr
- %th Project
- %th Open issues
- %th State
- %th Due date
- - @dashboard_milestone.milestones.each do |milestone|
- %tr
- %td
- = link_to "#{milestone.project.name_with_namespace}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
- %td
- = milestone.issues.opened.count
- %td
- - if milestone.closed?
- Closed
- - else
- Open
- %td
- = milestone.expires_at
+
+.table-holder
+ %table.table
+ %thead
+ %tr
+ %th Project
+ %th Open issues
+ %th State
+ %th Due date
+ - @dashboard_milestone.milestones.each do |milestone|
+ %tr
+ %td
+ = link_to "#{milestone.project.name_with_namespace}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
+ %td
+ = milestone.issues.opened.count
+ %td
+ - if milestone.closed?
+ Closed
+ - else
+ Open
+ %td
+ = milestone.expires_at
.context
%p.lead
@@ -79,7 +81,7 @@
- @dashboard_milestone.participants.each do |user|
%li
= link_to user, title: user.name, class: "darken" do
- = image_tag avatar_icon(user.email, 32), class: "avatar s32"
+ = image_tag avatar_icon(user, 32), class: "avatar s32"
%strong= truncate(user.name, lenght: 40)
%br
%small.cgray= user.username
diff --git a/app/views/dashboard/projects/_projects.html.haml b/app/views/dashboard/projects/_projects.html.haml
index d0194a17b01..81a5909e2d2 100644
--- a/app/views/dashboard/projects/_projects.html.haml
+++ b/app/views/dashboard/projects/_projects.html.haml
@@ -5,6 +5,7 @@
- if current_user.can_create_project?
%span.input-group-btn
= link_to new_project_path, class: 'btn btn-green' do
- New project
+ %i.fa.fa-plus
+ New Project
= render 'shared/projects/list', projects: @projects, ci: true
diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml
index d3908062f43..07b6d57932e 100644
--- a/app/views/dashboard/snippets/index.html.haml
+++ b/app/views/dashboard/snippets/index.html.haml
@@ -6,33 +6,29 @@
.gray-content-block
.pull-right
= link_to new_snippet_path, class: "btn btn-new", title: "New Snippet" do
- Add new snippet
+ = icon('plus')
+ New Snippet
- .oneline
- Share code pastes with others out of git repository
-
-%ul.nav.nav-tabs.prepend-top-20
- = nav_tab :scope, nil do
- = link_to dashboard_snippets_path do
+ .btn-group.btn-group-next.snippet-scope-menu
+ = link_to dashboard_snippets_path, class: "btn btn-default #{"active" unless params[:scope]}" do
All
%span.badge
= current_user.snippets.count
- = nav_tab :scope, 'are_private' do
- = link_to dashboard_snippets_path(scope: 'are_private') do
+
+ = link_to dashboard_snippets_path(scope: 'are_private'), class: "btn btn-default #{"active" if params[:scope] == "are_private"}" do
Private
%span.badge
= current_user.snippets.are_private.count
- = nav_tab :scope, 'are_internal' do
- = link_to dashboard_snippets_path(scope: 'are_internal') do
+
+ = link_to dashboard_snippets_path(scope: 'are_internal'), class: "btn btn-default #{"active" if params[:scope] == "are_internal"}" do
Internal
%span.badge
= current_user.snippets.are_internal.count
- = nav_tab :scope, 'are_public' do
- = link_to dashboard_snippets_path(scope: 'are_public') do
+
+ = link_to dashboard_snippets_path(scope: 'are_public'), class: "btn btn-default #{"active" if params[:scope] == "are_public"}" do
Public
%span.badge
= current_user.snippets.are_public.count
-.my-snippets
- = render 'snippets/snippets'
+= render 'snippets/snippets'
diff --git a/app/views/doorkeeper/applications/index.html.haml b/app/views/doorkeeper/applications/index.html.haml
index 3b0b19107ca..ba4c5b86efb 100644
--- a/app/views/doorkeeper/applications/index.html.haml
+++ b/app/views/doorkeeper/applications/index.html.haml
@@ -1,17 +1,19 @@
- page_title "Applications"
%h3.page-title Your applications
%p= link_to 'New Application', new_oauth_application_path, class: 'btn btn-success'
-%table.table.table-striped
- %thead
- %tr
- %th Name
- %th Callback URL
- %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
- %td= link_to 'Edit', edit_oauth_application_path(application), class: 'btn btn-link'
- %td= render 'delete_form', application: application
+
+.table-holder
+ %table.table.table-striped
+ %thead
+ %tr
+ %th Name
+ %th Callback URL
+ %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
+ %td= link_to 'Edit', edit_oauth_application_path(application), class: 'btn btn-link'
+ %td= render 'delete_form', application: application
diff --git a/app/views/doorkeeper/applications/show.html.haml b/app/views/doorkeeper/applications/show.html.haml
index 80340aca54c..47442b78d48 100644
--- a/app/views/doorkeeper/applications/show.html.haml
+++ b/app/views/doorkeeper/applications/show.html.haml
@@ -2,26 +2,26 @@
%h3.page-title
Application: #{@application.name}
+.table-holder
+ %table.table
+ %tr
+ %td
+ Application Id
+ %td
+ %code#application_id= @application.uid
+ %tr
+ %td
+ Secret:
+ %td
+ %code#secret= @application.secret
-%table.table
- %tr
- %td
- Application Id
- %td
- %code#application_id= @application.uid
- %tr
- %td
- Secret:
- %td
- %code#secret= @application.secret
-
- %tr
- %td
- Callback url
- %td
- - @application.redirect_uri.split.each do |uri|
- %div
- %span.monospace= uri
+ %tr
+ %td
+ Callback url
+ %td
+ - @application.redirect_uri.split.each do |uri|
+ %div
+ %span.monospace= uri
.form-actions
= link_to 'Edit', edit_oauth_application_path(@application), class: 'btn btn-primary wide pull-left'
= render 'delete_form', application: @application, submit_btn_css: 'btn btn-danger prepend-left-10'
diff --git a/app/views/doorkeeper/authorized_applications/index.html.haml b/app/views/doorkeeper/authorized_applications/index.html.haml
index 814cdc987ef..b184b9c01d4 100644
--- a/app/views/doorkeeper/authorized_applications/index.html.haml
+++ b/app/views/doorkeeper/authorized_applications/index.html.haml
@@ -1,16 +1,17 @@
%header.page-header
%h1 Your authorized applications
%main{:role => "main"}
- %table.table.table-striped
- %thead
- %tr
- %th Application
- %th Created At
- %th
- %th
- %tbody
- - @applications.each do |application|
+ .table-holder
+ %table.table.table-striped
+ %thead
%tr
- %td= application.name
- %td= application.created_at.strftime('%Y-%m-%d %H:%M:%S')
- %td= render 'delete_form', application: application \ No newline at end of file
+ %th Application
+ %th Created At
+ %th
+ %th
+ %tbody
+ - @applications.each do |application|
+ %tr
+ %td= application.name
+ %td= application.created_at.strftime('%Y-%m-%d %H:%M:%S')
+ %td= render 'delete_form', application: application
diff --git a/app/views/events/_event_issue.atom.haml b/app/views/events/_event_issue.atom.haml
index 4259f64c191..fad65310021 100644
--- a/app/views/events/_event_issue.atom.haml
+++ b/app/views/events/_event_issue.atom.haml
@@ -1,3 +1,2 @@
%div{xmlns: "http://www.w3.org/1999/xhtml"}
- - if issue.description.present?
- = markdown(issue.description, xhtml: true, reference_only_path: false, project: issue.project)
+ = markdown(issue.description, pipeline: :atom, project: issue.project)
diff --git a/app/views/events/_event_merge_request.atom.haml b/app/views/events/_event_merge_request.atom.haml
index e8ed13df783..19bdc7b9ca5 100644
--- a/app/views/events/_event_merge_request.atom.haml
+++ b/app/views/events/_event_merge_request.atom.haml
@@ -1,3 +1,2 @@
%div{xmlns: "http://www.w3.org/1999/xhtml"}
- - if merge_request.description.present?
- = markdown(merge_request.description, xhtml: true, reference_only_path: false, project: merge_request.project)
+ = markdown(merge_request.description, pipeline: :atom, project: merge_request.project)
diff --git a/app/views/events/_event_note.atom.haml b/app/views/events/_event_note.atom.haml
index cfbfba50202..b730ebbd5f9 100644
--- a/app/views/events/_event_note.atom.haml
+++ b/app/views/events/_event_note.atom.haml
@@ -1,2 +1,2 @@
%div{xmlns: "http://www.w3.org/1999/xhtml"}
- = markdown(note.note, xhtml: true, reference_only_path: false, project: note.project)
+ = markdown(note.note, pipeline: :atom, project: note.project)
diff --git a/app/views/events/_event_push.atom.haml b/app/views/events/_event_push.atom.haml
index 3625cb49d8b..b271b9daff1 100644
--- a/app/views/events/_event_push.atom.haml
+++ b/app/views/events/_event_push.atom.haml
@@ -6,7 +6,7 @@
%i
at
= commit[:timestamp].to_time.to_s(:short)
- %blockquote= markdown(escape_once(commit[:message]), xhtml: true, reference_only_path: false, project: event.project)
+ %blockquote= markdown(escape_once(commit[:message]), pipeline: :atom, project: event.project)
- if event.commits_count > 15
%p
%i
diff --git a/app/views/explore/snippets/index.html.haml b/app/views/explore/snippets/index.html.haml
index 7e4fa7d4873..0f100c39ffb 100644
--- a/app/views/explore/snippets/index.html.haml
+++ b/app/views/explore/snippets/index.html.haml
@@ -10,7 +10,8 @@
- if current_user
.pull-right
= link_to new_snippet_path, class: "btn btn-new", title: "New Snippet" do
- Add new snippet
+ = icon('plus')
+ New Snippet
.oneline
Public snippets created by you and other users are listed here
diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml
index 133f3e2d5a8..11d69977ef9 100644
--- a/app/views/groups/_projects.html.haml
+++ b/app/views/groups/_projects.html.haml
@@ -5,6 +5,7 @@
- if can? current_user, :create_projects, @group
%span.input-group-btn
= link_to new_project_path(namespace_id: @group.id), class: 'btn btn-green' do
- New project
+ %i.fa.fa-plus
+ New Project
= render 'shared/projects/list', projects: @projects, projects_limit: 20, stars: false, skip_namespace: true
diff --git a/app/views/groups/group_members/_group_member.html.haml b/app/views/groups/group_members/_group_member.html.haml
index b5f359279d5..3c19381321a 100644
--- a/app/views/groups/group_members/_group_member.html.haml
+++ b/app/views/groups/group_members/_group_member.html.haml
@@ -5,7 +5,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.email, 16), class: "avatar s16", alt: ''
+ = image_tag avatar_icon(user, 16), class: "avatar s16", alt: ''
%strong
= link_to user.name, user_path(user)
%span.cgray= user.username
diff --git a/app/views/groups/milestones/_issue.html.haml b/app/views/groups/milestones/_issue.html.haml
index 09f9b4b8969..9b85d83d6d8 100644
--- a/app/views/groups/milestones/_issue.html.haml
+++ b/app/views/groups/milestones/_issue.html.haml
@@ -7,4 +7,4 @@
= link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title
.pull-right.assignee-icon
- if issue.assignee
- = image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16", alt: ''
+ = image_tag avatar_icon(issue.assignee, 16), class: "avatar s16", alt: ''
diff --git a/app/views/groups/milestones/_merge_request.html.haml b/app/views/groups/milestones/_merge_request.html.haml
index d0d1426762b..e3aa4aad198 100644
--- a/app/views/groups/milestones/_merge_request.html.haml
+++ b/app/views/groups/milestones/_merge_request.html.haml
@@ -7,4 +7,4 @@
= link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title
.pull-right.assignee-icon
- if merge_request.assignee
- = image_tag avatar_icon(merge_request.assignee.email, 16), class: "avatar s16", alt: ''
+ = image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16", alt: ''
diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml
index 0c213f42186..a92ad5d751b 100644
--- a/app/views/groups/milestones/show.html.haml
+++ b/app/views/groups/milestones/show.html.haml
@@ -21,26 +21,28 @@
%span All issues for this milestone are closed. You may close the milestone now.
.description
-%table.table
- %thead
- %tr
- %th Project
- %th Open issues
- %th State
- %th Due date
- - @group_milestone.milestones.each do |milestone|
- %tr
- %td
- = link_to "#{milestone.project.name}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
- %td
- = milestone.issues.opened.count
- %td
- - if milestone.closed?
- Closed
- - else
- Open
- %td
- = milestone.expires_at
+
+.table-holder
+ %table.table
+ %thead
+ %tr
+ %th Project
+ %th Open issues
+ %th State
+ %th Due date
+ - @group_milestone.milestones.each do |milestone|
+ %tr
+ %td
+ = link_to "#{milestone.project.name}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
+ %td
+ = milestone.issues.opened.count
+ %td
+ - if milestone.closed?
+ Closed
+ - else
+ Open
+ %td
+ = milestone.expires_at
.context
%p.lead
@@ -87,7 +89,7 @@
- @group_milestone.participants.each do |user|
%li
= link_to user, title: user.name, class: "darken" do
- = image_tag avatar_icon(user.email, 32), class: "avatar s32"
+ = image_tag avatar_icon(user, 32), class: "avatar s32"
%strong= truncate(user.name, lenght: 40)
%br
%small.cgray= user.username
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index e809d99ba71..67349fcbd78 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -102,6 +102,12 @@
%tr
%td.shortcut
.key g
+ .key b
+ %td
+ Go to builds
+ %tr
+ %td.shortcut
+ .key g
.key n
%td
Go to network graph
diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml
index 777eb482714..30bcdb86827 100644
--- a/app/views/import/bitbucket/status.html.haml
+++ b/app/views/import/bitbucket/status.html.haml
@@ -14,45 +14,46 @@
= button_tag 'Import all projects', class: "btn btn-success js-import-all"
-%table.table.import-jobs
- %thead
- %tr
- %th From Bitbucket
- %th To GitLab
- %th Status
- %tbody
- - @already_added_projects.each do |project|
- %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
- %td
- = link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: "_blank"
- %td
- %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
- %td.job-status
- - if project.import_status == 'finished'
- %span
- %i.fa.fa-check
- done
- - elsif project.import_status == 'started'
- %i.fa.fa-spinner.fa-spin
- started
- - else
- = project.human_import_status_name
+.table-holder
+ %table.table.import-jobs
+ %thead
+ %tr
+ %th From Bitbucket
+ %th To GitLab
+ %th Status
+ %tbody
+ - @already_added_projects.each do |project|
+ %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
+ %td
+ = link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: "_blank"
+ %td
+ %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ %td.job-status
+ - if project.import_status == 'finished'
+ %span
+ %i.fa.fa-check
+ done
+ - elsif project.import_status == 'started'
+ %i.fa.fa-spinner.fa-spin
+ started
+ - else
+ = project.human_import_status_name
- - @repos.each do |repo|
- %tr{id: "repo_#{repo["owner"]}___#{repo["slug"]}"}
- %td
- = link_to "#{repo["owner"]}/#{repo["slug"]}", "https://bitbucket.org/#{repo["owner"]}/#{repo["slug"]}", target: "_blank"
- %td.import-target
- = "#{repo["owner"]}/#{repo["slug"]}"
- %td.import-actions.job-status
- = button_tag "Import", class: "btn js-add-to-import"
- - @incompatible_repos.each do |repo|
- %tr{id: "repo_#{repo["owner"]}___#{repo["slug"]}"}
- %td
- = link_to "#{repo["owner"]}/#{repo["slug"]}", "https://bitbucket.org/#{repo["owner"]}/#{repo["slug"]}", target: "_blank"
- %td.import-target
- %td.import-actions-job-status
- = label_tag "Incompatible Project", nil, class: "label label-danger"
+ - @repos.each do |repo|
+ %tr{id: "repo_#{repo["owner"]}___#{repo["slug"]}"}
+ %td
+ = link_to "#{repo["owner"]}/#{repo["slug"]}", "https://bitbucket.org/#{repo["owner"]}/#{repo["slug"]}", target: "_blank"
+ %td.import-target
+ = "#{repo["owner"]}/#{repo["slug"]}"
+ %td.import-actions.job-status
+ = button_tag "Import", class: "btn js-add-to-import"
+ - @incompatible_repos.each do |repo|
+ %tr{id: "repo_#{repo["owner"]}___#{repo["slug"]}"}
+ %td
+ = link_to "#{repo["owner"]}/#{repo["slug"]}", "https://bitbucket.org/#{repo["owner"]}/#{repo["slug"]}", target: "_blank"
+ %td.import-target
+ %td.import-actions-job-status
+ = label_tag "Incompatible Project", nil, class: "label label-danger"
- if @incompatible_repos.any?
%p
diff --git a/app/views/import/fogbugz/new_user_map.html.haml b/app/views/import/fogbugz/new_user_map.html.haml
index 25cebfb3665..a701e49ac56 100644
--- a/app/views/import/fogbugz/new_user_map.html.haml
+++ b/app/views/import/fogbugz/new_user_map.html.haml
@@ -25,22 +25,23 @@
of issues and comments (e.g. "By <a href="#">@johnsmith</a>"). It will also
associate and/or assign these issues and comments with the selected user.
- %table.table
- %thead
- %tr
- %th ID
- %th Name
- %th Email
- %th GitLab User
- %tbody
- - @user_map.each do |id, user|
+ .table-holder
+ %table.table
+ %thead
%tr
- %td= id
- %td= text_field_tag "users[#{id}][name]", user[:name], class: 'form-control'
- %td= text_field_tag "users[#{id}][email]", user[:email], class: 'form-control'
- %td
- = users_select_tag("users[#{id}][gitlab_user]", class: 'custom-form-control',
- scope: :all, email_user: true, selected: user[:gitlab_user])
+ %th ID
+ %th Name
+ %th Email
+ %th GitLab User
+ %tbody
+ - @user_map.each do |id, user|
+ %tr
+ %td= id
+ %td= text_field_tag "users[#{id}][name]", user[:name], class: 'form-control'
+ %td= text_field_tag "users[#{id}][email]", user[:email], class: 'form-control'
+ %td
+ = users_select_tag("users[#{id}][gitlab_user]", class: 'custom-form-control',
+ scope: :all, email_user: true, selected: user[:gitlab_user])
.form-actions
= submit_tag 'Continue to the next step', class: 'btn btn-create'
diff --git a/app/views/import/fogbugz/status.html.haml b/app/views/import/fogbugz/status.html.haml
index f179ece402d..beca6ab1423 100644
--- a/app/views/import/fogbugz/status.html.haml
+++ b/app/views/import/fogbugz/status.html.haml
@@ -14,38 +14,39 @@
%p
= button_tag 'Import all projects', class: 'btn btn-success js-import-all'
-%table.table.import-jobs
- %thead
- %tr
- %th From FogBugz
- %th To GitLab
- %th Status
- %tbody
- - @already_added_projects.each do |project|
- %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
- %td
- = project.import_source
- %td
- %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
- %td.job-status
- - if project.import_status == 'finished'
- %span
- %i.fa.fa-check
- done
- - elsif project.import_status == 'started'
- %i.fa.fa-spinner.fa-spin
- started
- - else
- = project.human_import_status_name
+.table-holder
+ %table.table.import-jobs
+ %thead
+ %tr
+ %th From FogBugz
+ %th To GitLab
+ %th Status
+ %tbody
+ - @already_added_projects.each do |project|
+ %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
+ %td
+ = project.import_source
+ %td
+ %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ %td.job-status
+ - if project.import_status == 'finished'
+ %span
+ %i.fa.fa-check
+ done
+ - elsif project.import_status == 'started'
+ %i.fa.fa-spinner.fa-spin
+ started
+ - else
+ = project.human_import_status_name
- - @repos.each do |repo|
- %tr{id: "repo_#{repo.id}"}
- %td
- = repo.name
- %td.import-target
- = "#{current_user.username}/#{repo.name}"
- %td.import-actions.job-status
- = button_tag "Import", class: "btn js-add-to-import"
+ - @repos.each do |repo|
+ %tr{id: "repo_#{repo.id}"}
+ %td
+ = repo.name
+ %td.import-target
+ = "#{current_user.username}/#{repo.name}"
+ %td.import-actions.job-status
+ = button_tag "Import", class: "btn js-add-to-import"
:coffeescript
new ImporterStatus("#{jobs_import_fogbugz_path}", "#{import_fogbugz_path}")
diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml
index ef552498239..0669b05adca 100644
--- a/app/views/import/github/status.html.haml
+++ b/app/views/import/github/status.html.haml
@@ -9,38 +9,39 @@
%p
= button_tag 'Import all projects', class: "btn btn-success js-import-all"
-%table.table.import-jobs
- %thead
- %tr
- %th From GitHub
- %th To GitLab
- %th Status
- %tbody
- - @already_added_projects.each do |project|
- %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
- %td
- = link_to project.import_source, "https://github.com/#{project.import_source}", target: "_blank"
- %td
- %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
- %td.job-status
- - if project.import_status == 'finished'
- %span
- %i.fa.fa-check
- done
- - elsif project.import_status == 'started'
- %i.fa.fa-spinner.fa-spin
- started
- - else
- = project.human_import_status_name
+.table-holder
+ %table.table.import-jobs
+ %thead
+ %tr
+ %th From GitHub
+ %th To GitLab
+ %th Status
+ %tbody
+ - @already_added_projects.each do |project|
+ %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
+ %td
+ = link_to project.import_source, "https://github.com/#{project.import_source}", target: "_blank"
+ %td
+ %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ %td.job-status
+ - if project.import_status == 'finished'
+ %span
+ %i.fa.fa-check
+ done
+ - elsif project.import_status == 'started'
+ %i.fa.fa-spinner.fa-spin
+ started
+ - else
+ = project.human_import_status_name
- - @repos.each do |repo|
- %tr{id: "repo_#{repo.id}"}
- %td
- = link_to repo.full_name, "https://github.com/#{repo.full_name}", target: "_blank"
- %td.import-target
- = repo.full_name
- %td.import-actions.job-status
- = button_tag "Import", class: "btn js-add-to-import"
+ - @repos.each do |repo|
+ %tr{id: "repo_#{repo.id}"}
+ %td
+ = link_to repo.full_name, "https://github.com/#{repo.full_name}", target: "_blank"
+ %td.import-target
+ = repo.full_name
+ %td.import-actions.job-status
+ = button_tag "Import", class: "btn js-add-to-import"
:coffeescript
new ImporterStatus("#{jobs_import_github_path}", "#{import_github_path}")
diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml
index 727f3c7e7fa..3bc85059e7d 100644
--- a/app/views/import/gitlab/status.html.haml
+++ b/app/views/import/gitlab/status.html.haml
@@ -9,38 +9,39 @@
%p
= button_tag 'Import all projects', class: "btn btn-success js-import-all"
-%table.table.import-jobs
- %thead
- %tr
- %th From GitLab.com
- %th To this GitLab instance
- %th Status
- %tbody
- - @already_added_projects.each do |project|
- %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
- %td
- = link_to project.import_source, "https://gitlab.com/#{project.import_source}", target: "_blank"
- %td
- %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
- %td.job-status
- - if project.import_status == 'finished'
- %span
- %i.fa.fa-check
- done
- - elsif project.import_status == 'started'
- %i.fa.fa-spinner.fa-spin
- started
- - else
- = project.human_import_status_name
+.table-holder
+ %table.table.import-jobs
+ %thead
+ %tr
+ %th From GitLab.com
+ %th To this GitLab instance
+ %th Status
+ %tbody
+ - @already_added_projects.each do |project|
+ %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
+ %td
+ = link_to project.import_source, "https://gitlab.com/#{project.import_source}", target: "_blank"
+ %td
+ %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ %td.job-status
+ - if project.import_status == 'finished'
+ %span
+ %i.fa.fa-check
+ done
+ - elsif project.import_status == 'started'
+ %i.fa.fa-spinner.fa-spin
+ started
+ - else
+ = project.human_import_status_name
- - @repos.each do |repo|
- %tr{id: "repo_#{repo["id"]}"}
- %td
- = link_to repo["path_with_namespace"], "https://gitlab.com/#{repo["path_with_namespace"]}", target: "_blank"
- %td.import-target
- = repo["path_with_namespace"]
- %td.import-actions.job-status
- = button_tag "Import", class: "btn js-add-to-import"
+ - @repos.each do |repo|
+ %tr{id: "repo_#{repo["id"]}"}
+ %td
+ = link_to repo["path_with_namespace"], "https://gitlab.com/#{repo["path_with_namespace"]}", target: "_blank"
+ %td.import-target
+ = repo["path_with_namespace"]
+ %td.import-actions.job-status
+ = button_tag "Import", class: "btn js-add-to-import"
:coffeescript
new ImporterStatus("#{jobs_import_gitlab_path}", "#{import_gitlab_path}")
diff --git a/app/views/import/gitorious/status.html.haml b/app/views/import/gitorious/status.html.haml
index bff7ee7c85d..2e3a535737f 100644
--- a/app/views/import/gitorious/status.html.haml
+++ b/app/views/import/gitorious/status.html.haml
@@ -9,38 +9,39 @@
%p
= button_tag 'Import all projects', class: "btn btn-success js-import-all"
-%table.table.import-jobs
- %thead
- %tr
- %th From Gitorious.org
- %th To GitLab
- %th Status
- %tbody
- - @already_added_projects.each do |project|
- %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
- %td
- = link_to project.import_source, "https://gitorious.org/#{project.import_source}", target: "_blank"
- %td
- %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
- %td.job-status
- - if project.import_status == 'finished'
- %span
- %i.fa.fa-check
- done
- - elsif project.import_status == 'started'
- %i.fa.fa-spinner.fa-spin
- started
- - else
- = project.human_import_status_name
+.table-holder
+ %table.table.import-jobs
+ %thead
+ %tr
+ %th From Gitorious.org
+ %th To GitLab
+ %th Status
+ %tbody
+ - @already_added_projects.each do |project|
+ %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
+ %td
+ = link_to project.import_source, "https://gitorious.org/#{project.import_source}", target: "_blank"
+ %td
+ %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ %td.job-status
+ - if project.import_status == 'finished'
+ %span
+ %i.fa.fa-check
+ done
+ - elsif project.import_status == 'started'
+ %i.fa.fa-spinner.fa-spin
+ started
+ - else
+ = project.human_import_status_name
- - @repos.each do |repo|
- %tr{id: "repo_#{repo.id}"}
- %td
- = link_to repo.full_name, "https://gitorious.org/#{repo.full_name}", target: "_blank"
- %td.import-target
- = repo.full_name
- %td.import-actions.job-status
- = button_tag "Import", class: "btn js-add-to-import"
+ - @repos.each do |repo|
+ %tr{id: "repo_#{repo.id}"}
+ %td
+ = link_to repo.full_name, "https://gitorious.org/#{repo.full_name}", target: "_blank"
+ %td.import-target
+ = repo.full_name
+ %td.import-actions.job-status
+ = button_tag "Import", class: "btn js-add-to-import"
:coffeescript
new ImporterStatus("#{jobs_import_gitorious_path}", "#{import_gitorious_path}")
diff --git a/app/views/import/google_code/status.html.haml b/app/views/import/google_code/status.html.haml
index e8ec79e72f7..c5af06edf87 100644
--- a/app/views/import/google_code/status.html.haml
+++ b/app/views/import/google_code/status.html.haml
@@ -17,45 +17,46 @@
- else
= button_tag 'Import all projects', class: "btn btn-success js-import-all"
-%table.table.import-jobs
- %thead
- %tr
- %th From Google Code
- %th To GitLab
- %th Status
- %tbody
- - @already_added_projects.each do |project|
- %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
- %td
- = link_to project.import_source, "https://code.google.com/p/#{project.import_source}", target: "_blank"
- %td
- %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
- %td.job-status
- - if project.import_status == 'finished'
- %span
- %i.fa.fa-check
- done
- - elsif project.import_status == 'started'
- %i.fa.fa-spinner.fa-spin
- started
- - else
- = project.human_import_status_name
+.table-holder
+ %table.table.import-jobs
+ %thead
+ %tr
+ %th From Google Code
+ %th To GitLab
+ %th Status
+ %tbody
+ - @already_added_projects.each do |project|
+ %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
+ %td
+ = link_to project.import_source, "https://code.google.com/p/#{project.import_source}", target: "_blank"
+ %td
+ %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ %td.job-status
+ - if project.import_status == 'finished'
+ %span
+ %i.fa.fa-check
+ done
+ - elsif project.import_status == 'started'
+ %i.fa.fa-spinner.fa-spin
+ started
+ - else
+ = project.human_import_status_name
- - @repos.each do |repo|
- %tr{id: "repo_#{repo.id}"}
- %td
- = link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank"
- %td.import-target
- = "#{current_user.username}/#{repo.name}"
- %td.import-actions.job-status
- = button_tag "Import", class: "btn js-add-to-import"
- - @incompatible_repos.each do |repo|
- %tr{id: "repo_#{repo.id}"}
- %td
- = link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank"
- %td.import-target
- %td.import-actions-job-status
- = label_tag "Incompatible Project", nil, class: "label label-danger"
+ - @repos.each do |repo|
+ %tr{id: "repo_#{repo.id}"}
+ %td
+ = link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank"
+ %td.import-target
+ = "#{current_user.username}/#{repo.name}"
+ %td.import-actions.job-status
+ = button_tag "Import", class: "btn js-add-to-import"
+ - @incompatible_repos.each do |repo|
+ %tr{id: "repo_#{repo.id}"}
+ %td
+ = link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank"
+ %td.import-target
+ %td.import-actions-job-status
+ = label_tag "Incompatible Project", nil, class: "label label-danger"
- if @incompatible_repos.any?
%p
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 1a883e20e89..352b8040cf4 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -18,7 +18,7 @@
= render partial: 'layouts/collapse_button'
- if current_user
= link_to current_user, class: 'sidebar-user' do
- = image_tag avatar_icon(current_user.email, 60), alt: 'User activity', class: 'avatar avatar s36'
+ = image_tag avatar_icon(current_user, 60), alt: 'User activity', class: 'avatar avatar s36'
.username
= current_user.username
.content-wrapper
diff --git a/app/views/layouts/ci/_page.html.haml b/app/views/layouts/ci/_page.html.haml
index bb5ec727bff..ab3e29c3f42 100644
--- a/app/views/layouts/ci/_page.html.haml
+++ b/app/views/layouts/ci/_page.html.haml
@@ -15,7 +15,7 @@
= render partial: 'layouts/collapse_button'
- if current_user
= link_to current_user, class: 'sidebar-user' do
- = image_tag avatar_icon(current_user.email, 60), alt: 'User activity', class: 'avatar avatar s36'
+ = image_tag avatar_icon(current_user, 60), alt: 'User activity', class: 'avatar avatar s36'
.username
= current_user.username
.content-wrapper
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index e4c285d8023..53a913fe8f3 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -32,12 +32,20 @@
Files
- if project_nav_tab? :commits
- = nav_link(controller: %w(commit commits compare repositories tags branches builds)) do
+ = nav_link(controller: %w(commit commits compare repositories tags branches)) do
= link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits', data: {placement: 'right'} 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
+ = icon('cubes fw')
+ %span
+ Builds
+ %span.count.builds_counter= @project.ci_builds.running_or_pending.count(:all)
+
- 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
diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml
index 2f7d7e86f56..854cda57c39 100644
--- a/app/views/layouts/notify.html.haml
+++ b/app/views/layouts/notify.html.haml
@@ -41,4 +41,8 @@
#{link_to "view it on GitLab", @target_url}.
- else
#{link_to "View it on GitLab", @target_url}
+ %br
+ You're receiving this email because of your account on #{link_to Gitlab.config.gitlab.host, root_url}.
+ If you'd like to receive fewer emails, you can adjust your notification settings.
+
= email_action @target_url
diff --git a/app/views/notify/_note_message.html.haml b/app/views/notify/_note_message.html.haml
index 3fd4b04ac84..00cb4aa24cc 100644
--- a/app/views/notify/_note_message.html.haml
+++ b/app/views/notify/_note_message.html.haml
@@ -1,2 +1,2 @@
%div
- = markdown(@note.note, reference_only_path: false)
+ = markdown(@note.note, pipeline: :email)
diff --git a/app/views/notify/new_issue_email.html.haml b/app/views/notify/new_issue_email.html.haml
index 53a068be52e..d3b799fca23 100644
--- a/app/views/notify/new_issue_email.html.haml
+++ b/app/views/notify/new_issue_email.html.haml
@@ -1,5 +1,5 @@
-if @issue.description
- = markdown(@issue.description, reference_only_path: false)
+ = markdown(@issue.description, pipeline: :email)
- if @issue.assignee_id.present?
%p
diff --git a/app/views/notify/new_merge_request_email.html.haml b/app/views/notify/new_merge_request_email.html.haml
index 5b7dd117c16..90ebdfc3fe2 100644
--- a/app/views/notify/new_merge_request_email.html.haml
+++ b/app/views/notify/new_merge_request_email.html.haml
@@ -6,4 +6,4 @@
Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name}
-if @merge_request.description
- = markdown(@merge_request.description, reference_only_path: false)
+ = markdown(@merge_request.description, pipeline: :email)
diff --git a/app/views/profiles/_event_table.html.haml b/app/views/profiles/_event_table.html.haml
index c19ac429d52..58af79716a7 100644
--- a/app/views/profiles/_event_table.html.haml
+++ b/app/views/profiles/_event_table.html.haml
@@ -1,16 +1,17 @@
-%table.table#audits
- %thead
- %tr
- %th Action
- %th When
-
- %tbody
- - events.each do |event|
+.table-holder
+ %table.table#audits
+ %thead
%tr
- %td
- %span
- Signed in with
- %b= event.details[:with]
- authentication
- %td #{time_ago_in_words event.created_at} ago
+ %th Action
+ %th When
+
+ %tbody
+ - events.each do |event|
+ %tr
+ %td
+ %span
+ Signed in with
+ %b= event.details[:with]
+ authentication
+ %td #{time_ago_in_words event.created_at} ago
= paginate events, theme: "gitlab"
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index 01e285a8dfa..cc41d7dd813 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -38,7 +38,7 @@
.col-sm-10
= f.select :layout, layout_choices, {}, class: 'form-control'
.help-block
- Choose between fixed (max. 1200px) and fluid (100%) application layout
+ Choose between fixed (max. 1200px) and fluid (100%) application layout.
.form-group
= f.label :dashboard, class: 'control-label' do
Default Dashboard
@@ -52,6 +52,6 @@
.col-sm-10
= f.select :project_view, project_view_choices, {}, class: 'form-control'
.help-block
- Choose what content you want to see when visit project page
+ Choose what content you want to see on a project's home page.
.panel-footer
= f.submit 'Save', class: 'btn btn-save'
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index 47412e2ef0c..ac7355dde1f 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -68,7 +68,7 @@
.col-md-5
.light-well
- = image_tag avatar_icon(@user.email, 160), alt: '', class: 'avatar s160'
+ = image_tag avatar_icon(@user, 160), alt: '', class: 'avatar s160'
.clearfix
.profile-avatar-form-option
diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml
index c2683bc6219..012858f70b4 100644
--- a/app/views/projects/_activity.html.haml
+++ b/app/views/projects/_activity.html.haml
@@ -1,4 +1,3 @@
-= render 'projects/last_push'
.gray-content-block.activity-filter-block
- if current_user
.pull-right
diff --git a/app/views/projects/_files.html.haml b/app/views/projects/_files.html.haml
new file mode 100644
index 00000000000..fa978325ddd
--- /dev/null
+++ b/app/views/projects/_files.html.haml
@@ -0,0 +1,6 @@
+#tree-holder.tree-holder.clearfix
+ .gray-content-block.second-block
+ = render 'projects/tree/tree_header', tree: @tree
+
+ = render 'projects/tree/tree_content', tree: @tree
+
diff --git a/app/views/projects/_last_commit.html.haml b/app/views/projects/_last_commit.html.haml
new file mode 100644
index 00000000000..d7b20bfc6b1
--- /dev/null
+++ b/app/views/projects/_last_commit.html.haml
@@ -0,0 +1,12 @@
+.project-last-commit
+ - ci_commit = project.ci_commit(commit.sha)
+ - if ci_commit
+ = link_to ci_status_path(ci_commit), class: "ci-status ci-#{ci_commit.status}" do
+ = ci_status_icon(ci_commit)
+ = ci_commit.status
+
+ = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
+ = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
+ &middot;
+ #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} by
+ = commit_author_link(commit, avatar: true, size: 24)
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 507757f6a2b..7b21095ea3e 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -2,10 +2,10 @@
.md-header.clearfix
%ul.center-top-menu
%li.active
- = link_to '#md-write-holder', class: 'js-md-write-button', tabindex: '-1' do
+ %a.js-md-write-button(href="#md-write-holder" tabindex="-1")
Write
%li
- = link_to '#md-preview-holder', class: 'js-md-preview-button', tabindex: '-1' do
+ %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 5bc1999ec9d..b5ef0aca540 100644
--- a/app/views/projects/_readme.html.haml
+++ b/app/views/projects/_readme.html.haml
@@ -1,12 +1,9 @@
- if readme = @repository.readme
- %article.readme-holder#README
- .clearfix
- .pull-right
- &nbsp;
- - if can?(current_user, :push_code, @project)
- = link_to namespace_project_edit_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)), class: 'light' do
- %i.fa-align.fa.fa-pencil
- .wiki
+ %article.readme-holder
+ .pull-right
+ - if can?(current_user, :push_code, @project)
+ = link_to icon('pencil'), namespace_project_edit_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)), class: 'light edit-project-readme'
+ .file-content.wiki
= cache(readme_cache_key) do
= render_readme(readme)
- else
diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml
index 6a41cdbc907..63ebfc9381f 100644
--- a/app/views/projects/_zen.html.haml
+++ b/app/views/projects/_zen.html.haml
@@ -1,10 +1,10 @@
.zennable
- %input#zen-toggle-comment.zen-toggle-comment{ tabindex: '-1', type: 'checkbox' }
+ %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: ''
- = link_to nil, class: 'zen-enter-link', tabindex: '-1' do
+ %a.zen-enter-link(tabindex="-1" href="#")
%i.fa.fa-expand
Edit in fullscreen
- = link_to nil, class: 'zen-leave-link' do
+ %a.zen-leave-link(href="#")
%i.fa.fa-compress
diff --git a/app/views/projects/activity.html.haml b/app/views/projects/activity.html.haml
index 555ed76426d..69fa4ad37c4 100644
--- a/app/views/projects/activity.html.haml
+++ b/app/views/projects/activity.html.haml
@@ -1,4 +1,6 @@
- page_title "Activity"
- header_title project_title(@project, "Activity", activity_project_path(@project))
+= render 'projects/last_push'
+
= render 'projects/activity'
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index a1ae1397584..42f632b38ef 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -1,19 +1,22 @@
-%ul.breadcrumb.repo-breadcrumb
- %li
- %i.fa.fa-angle-right
- = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
- = @project.path
- - tree_breadcrumbs(@tree, 6) do |title, path|
+.gray-content-block.top-block
+ .tree-ref-holder
+ = render 'shared/ref_switcher', destination: 'blob', path: @path
+
+ %ul.breadcrumb.repo-breadcrumb
%li
- - if path
- - if path.end_with?(@path)
- = link_to namespace_project_blob_path(@project.namespace, @project, path) do
- %strong
- = truncate(title, length: 40)
+ = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
+ = @project.path
+ - tree_breadcrumbs(@tree, 6) do |title, path|
+ %li
+ - if path
+ - if path.end_with?(@path)
+ = link_to namespace_project_blob_path(@project.namespace, @project, path) do
+ %strong
+ = truncate(title, length: 40)
+ - else
+ = link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path)
- else
- = link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path)
- - else
- = link_to title, '#'
+ = link_to title, '#'
%ul.blob-commit-info.hidden-xs
- blob_commit = @repository.last_commit_for_path(@commit.id, blob.path)
diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml
index fa4be4a1bc4..f52b89f6921 100644
--- a/app/views/projects/blob/show.html.haml
+++ b/app/views/projects/blob/show.html.haml
@@ -3,9 +3,6 @@
= render 'projects/last_push'
-%div.tree-ref-holder
- = render 'shared/ref_switcher', destination: 'blob', path: @path
-
%div#tree-holder.tree-holder
= render 'blob', blob: @blob
diff --git a/app/views/projects/builds/_build.html.haml b/app/views/projects/builds/_build.html.haml
new file mode 100644
index 00000000000..4ce4ed63b40
--- /dev/null
+++ b/app/views/projects/builds/_build.html.haml
@@ -0,0 +1,53 @@
+%tr.build
+ %td.status
+ = ci_status_with_icon(build.status)
+
+ %td.commit_status-link
+ - if build.target_url
+ = link_to build.target_url do
+ %strong Build ##{build.id}
+ - else
+ %strong Build ##{build.id}
+
+ - if build.show_warning?
+ %i.fa.fa-warning.text-warning
+
+ %td
+ = link_to build.short_sha, namespace_project_commit_path(@project.namespace, @project, build.sha)
+
+ %td
+ = link_to build.ref, namespace_project_commits_path(@project.namespace, @project, build.ref)
+
+ %td
+ - if build.runner
+ = runner_link(build.runner)
+ - else
+ .light none
+
+ %td
+ = build.name
+
+ .pull-right
+ - if build.tags.any?
+ - build.tags.each do |tag|
+ %span.label.label-primary
+ = tag
+ - if build.trigger_request
+ %span.label.label-info triggered
+ - if build.allow_failure
+ %span.label.label-danger allowed to fail
+
+ %td.duration
+ - if build.duration
+ #{duration_in_words(build.finished_at, build.started_at)}
+
+ %td.timestamp
+ - if build.finished_at
+ %span #{time_ago_in_words build.finished_at} ago
+
+ %td
+ .pull-right
+ - if current_user && can?(current_user, :manage_builds, @project)
+ - if build.cancel_url
+ = link_to build.cancel_url, title: 'Cancel' do
+ %i.fa.fa-remove.cred
diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml
new file mode 100644
index 00000000000..e08556673ed
--- /dev/null
+++ b/app/views/projects/builds/index.html.haml
@@ -0,0 +1,53 @@
+- page_title "Builds"
+- header_title project_title(@project, "Builds", project_builds_path(@project))
+
+.project-issuable-filter
+ .controls
+ - if @ci_project && current_user && can?(current_user, :manage_builds, @project)
+ .pull-left.hidden-xs
+ - if @all_builds.running_or_pending.any?
+ = link_to 'Cancel all', cancel_all_namespace_project_builds_path(@project.namespace, @project), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger'
+
+ %ul.center-top-menu
+ %li{class: ('active' if @scope.nil?)}
+ = link_to project_builds_path(@project) do
+ Running
+ %span.badge.js-running-count= @all_builds.running_or_pending.count(:id)
+
+ %li{class: ('active' if @scope == 'finished')}
+ = link_to project_builds_path(@project, scope: :finished) do
+ Finished
+ %span.badge.js-running-count= @all_builds.finished.count(:id)
+
+ %li{class: ('active' if @scope == 'all')}
+ = link_to project_builds_path(@project, scope: :all) do
+ All
+ %span.badge.js-totalbuilds-count= @all_builds.count(:id)
+
+.gray-content-block
+ List of #{@scope || 'running'} builds from this project
+
+%ul.content-list
+ - if @builds.blank?
+ %li
+ .nothing-here-block No builds to show
+ - else
+ .table-holder
+ %table.table.builds
+ %thead
+ %tr
+ %th Status
+ %th Build ID
+ %th Commit
+ %th Ref
+ %th Runner
+ %th Name
+ %th Duration
+ %th Finished at
+ %th
+
+ - @builds.each do |build|
+ = render 'projects/builds/build', build: build
+
+ = paginate @builds
+
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index 9c3ae622b72..3a8172dc8e6 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -25,7 +25,7 @@
%a
Build ##{@build.id}
&middot;
- %i.fa.fa-warning-sign
+ %i.fa.fa-warning
This build was retried.
.gray-content-block.second-block
@@ -39,6 +39,25 @@
.pull-right
= @build.updated_at.stamp('19:00 Aug 27')
+ - if @build.show_warning?
+ - unless @build.any_runners_online?
+ .bs-callout.bs-callout-warning
+ %p
+ - if no_runners_for_project?(@build.project)
+ This build is stuck, because the project doesn't have any runners online assigned to it.
+ - elsif @build.tags.any?
+ This build is stuck, because you don't have any active runners online with any of these tags assigned to them:
+ - @build.tags.each do |tag|
+ %span.label.label-primary
+ = tag
+ - else
+ This build is stuck, because you don't have any active runners that can run this build.
+
+ %br
+ Go to
+ = link_to namespace_project_runners_path(@build.gl_project.namespace, @build.gl_project) do
+ Runners page
+
.row.prepend-top-default
.col-md-9
.clearfix
@@ -156,4 +175,4 @@
:javascript
- new CiBuild("#{namespace_project_build_path(@project.namespace, @project, @build)}", "#{@build.status}")
+ new CiBuild("#{namespace_project_build_url(@project.namespace, @project, @build)}", "#{@build.status}")
diff --git a/app/views/projects/buttons/_notifications.html.haml b/app/views/projects/buttons/_notifications.html.haml
index 3bc2daeec4e..3e83ec3912f 100644
--- a/app/views/projects/buttons/_notifications.html.haml
+++ b/app/views/projects/buttons/_notifications.html.haml
@@ -1,14 +1,20 @@
-- return unless @membership
+- case @membership
+- when ProjectMember
+ = form_tag profile_notifications_path, method: :put, remote: true, class: 'inline', id: 'notification-form' do
+ = hidden_field_tag :notification_type, 'project'
+ = hidden_field_tag :notification_id, @membership.id
+ = hidden_field_tag :notification_level
+ %span.dropdown
+ %a.dropdown-new.btn.notifications-btn#notifications-button{href: '#', "data-toggle" => "dropdown"}
+ = icon('bell')
+ = notification_label(@membership)
+ = icon('angle-down')
+ %ul.dropdown-menu.dropdown-menu-right.project-home-dropdown
+ - Notification.project_notification_levels.each do |level|
+ = notification_list_item(level, @membership)
-= form_tag profile_notifications_path, method: :put, remote: true, class: 'inline', id: 'notification-form' do
- = hidden_field_tag :notification_type, 'project'
- = hidden_field_tag :notification_id, @membership.id
- = hidden_field_tag :notification_level
- %span.dropdown
- %a.dropdown-new.btn.btn-new#notifications-button{href: '#', "data-toggle" => "dropdown"}
- = icon('bell')
- = notification_label(@membership)
- = icon('angle-down')
- %ul.dropdown-menu.dropdown-menu-right.project-home-dropdown
- - Notification.project_notification_levels.each do |level|
- = notification_list_item(level, @membership)
+- when GroupMember
+ .btn.disabled.notifications-btn.has_tooltip{title: "To change the notification level, you need to be a member of the project itself, not only its group."}
+ = icon('bell')
+ = notification_label(@membership)
+ = icon('angle-down')
diff --git a/app/views/projects/ci_services/index.html.haml b/app/views/projects/ci_services/index.html.haml
index c78b21884a3..c164b2d4bc0 100644
--- a/app/views/projects/ci_services/index.html.haml
+++ b/app/views/projects/ci_services/index.html.haml
@@ -6,7 +6,7 @@
%tr
%th
%th Service
- %th Desription
+ %th Description
%th Last edit
- @services.sort_by(&:title).each do |service|
%tr
diff --git a/app/views/projects/ci_web_hooks/index.html.haml b/app/views/projects/ci_web_hooks/index.html.haml
index 6aebd7cfc4d..369086b39ed 100644
--- a/app/views/projects/ci_web_hooks/index.html.haml
+++ b/app/views/projects/ci_web_hooks/index.html.haml
@@ -20,17 +20,18 @@
-if @web_hooks.any?
%h4 Activated web hooks (#{@web_hooks.count})
- %table.table
- - @web_hooks.each do |hook|
- %tr
- %td
- .clearfix
- %span.monospace= hook.url
- %td
- .pull-right
- - if @ci_project.commits.any?
- = link_to 'Test Hook', test_namespace_project_ci_web_hook_path(@project.namespace, @project, hook), class: "btn btn-sm btn-grouped"
- = link_to 'Remove', namespace_project_ci_web_hook_path(@project.namespace, @project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped"
+ .table-holder
+ %table.table
+ - @web_hooks.each do |hook|
+ %tr
+ %td
+ .clearfix
+ %span.monospace= hook.url
+ %td
+ .pull-right
+ - if @ci_project.commits.any?
+ = link_to 'Test Hook', test_namespace_project_ci_web_hook_path(@project.namespace, @project, hook), class: "btn btn-sm btn-grouped"
+ = link_to 'Remove', namespace_project_ci_web_hook_path(@project.namespace, @project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped"
%h4 Web Hook data example
diff --git a/app/views/projects/commit/ci.html.haml b/app/views/projects/commit/ci.html.haml
index 4a1ef378a30..43033cad24c 100644
--- a/app/views/projects/commit/ci.html.haml
+++ b/app/views/projects/commit/ci.html.haml
@@ -3,11 +3,6 @@
= render "commit_box"
= render "ci_menu"
-- if @ci_project && current_user && can?(current_user, :manage_builds, @project)
- .pull-right
- - if @ci_commit.builds.running_or_pending.any?
- = link_to "Cancel", cancel_builds_namespace_project_commit_path(@project.namespace, @project, @commit.sha), class: 'btn btn-sm btn-danger'
-
- if @ci_commit.yaml_errors.present?
.bs-callout.bs-callout-danger
@@ -22,31 +17,19 @@
.gray-content-block.second-block
Latest builds
- - if @ci_commit.duration > 0
- %small.pull-right
+
+ .pull-right
+ - if @ci_commit.duration > 0
%i.fa.fa-time
#{time_interval_in_words @ci_commit.duration}
-%table.table.builds
- %thead
- %tr
- %th Status
- %th Build ID
- %th Ref
- %th Stage
- %th Name
- %th Duration
- %th Finished at
- - if @ci_project && @ci_project.coverage_enabled?
- %th Coverage
- %th
- - @ci_commit.refs.each do |ref|
- = render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.statuses.for_ref(ref).latest.ordered, coverage: @ci_project.try(:coverage_enabled?), controls: true
+ &nbsp;
-- if @ci_commit.retried.any?
- .gray-content-block.second-block
- Retried builds
+ - if @ci_project && current_user && can?(current_user, :manage_builds, @project)
+ - if @ci_commit.builds.running_or_pending.any?
+ = link_to "Cancel all", cancel_builds_namespace_project_commit_path(@project.namespace, @project, @commit.sha), class: 'btn btn-xs btn-danger'
+.table-holder
%table.table.builds
%thead
%tr
@@ -60,4 +43,27 @@
- if @ci_project && @ci_project.coverage_enabled?
%th Coverage
%th
- = render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.retried, coverage: @ci_project.try(:coverage_enabled?)
+ - @ci_commit.refs.each do |ref|
+ = render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.statuses.for_ref(ref).latest.ordered,
+ locals: { coverage: @ci_project.try(:coverage_enabled?), allow_retry: true }
+
+- if @ci_commit.retried.any?
+ .gray-content-block.second-block
+ Retried builds
+
+ .table-holder
+ %table.table.builds
+ %thead
+ %tr
+ %th Status
+ %th Build ID
+ %th Ref
+ %th Stage
+ %th Name
+ %th Duration
+ %th Finished at
+ - if @ci_project && @ci_project.coverage_enabled?
+ %th Coverage
+ %th
+ = render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.retried,
+ locals: { coverage: @ci_project.try(:coverage_enabled?) }
diff --git a/app/views/projects/commit_statuses/_commit_status.html.haml b/app/views/projects/commit_statuses/_commit_status.html.haml
index e3a17faf0bd..637154f56aa 100644
--- a/app/views/projects/commit_statuses/_commit_status.html.haml
+++ b/app/views/projects/commit_statuses/_commit_status.html.haml
@@ -9,6 +9,9 @@
- else
%strong Build ##{commit_status.id}
+ - if commit_status.show_warning?
+ %i.fa.fa-warning.text-warning
+
%td
= commit_status.ref
@@ -41,11 +44,11 @@
#{commit_status.coverage}%
%td
- - if defined?(controls) && controls && current_user && can?(current_user, :manage_builds, gl_project)
- .pull-right
+ .pull-right
+ - if current_user && can?(current_user, :manage_builds, commit_status.gl_project)
- if commit_status.cancel_url
= link_to commit_status.cancel_url, title: 'Cancel' do
%i.fa.fa-remove.cred
- - elsif commit_status.retry_url
+ - elsif defined?(allow_retry) && allow_retry && commit_status.retry_url
= link_to commit_status.retry_url, method: :post, title: 'Retry' do
%i.fa.fa-repeat
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index 4f1965bfb39..56b51f038ba 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -15,8 +15,8 @@
.files
- diff_files.each_with_index do |diff_file, index|
- - diff_commit = commit_for_diff(diff_file.diff)
- - blob = project.repository.blob_for_diff(diff_commit, diff_file.diff)
+ - diff_commit = commit_for_diff(diff_file)
+ - blob = project.repository.blob_for_diff(diff_commit, diff_file)
- next unless blob
= render 'projects/diffs/file', i: index, project: project,
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index 9698921f6da..410ff6abb43 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -1,5 +1,5 @@
.diff-file{id: "diff-#{i}", data: diff_file_html_data(project, diff_commit, diff_file)}
- .diff-header{id: "file-path-#{hexdigest(diff_file.new_path || diff_file.old_path)}"}
+ .diff-header{id: "file-path-#{hexdigest(diff_file.file_path)}"}
- if diff_file.diff.submodule?
%span
- submodule_item = project.repository.blob_at(@commit.id, diff_file.file_path)
@@ -38,7 +38,7 @@
- else
= render "projects/diffs/text_file", diff_file: diff_file, index: i
- elsif blob.image?
- - old_file = project.repository.prev_blob_for_diff(@commit, diff_file)
+ - old_file = project.repository.prev_blob_for_diff(diff_commit, diff_file)
= render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i
- else
.nothing-here-block No preview for this file type
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 1882a82fba5..afbf88b5507 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -189,6 +189,21 @@
- else
.nothing-here-block Only the project owner can transfer a project
+ - if @project.forked?
+ - if can?(current_user, :remove_fork_project, @project)
+ = form_for([@project.namespace.becomes(Namespace), @project], url: remove_fork_namespace_project_path(@project.namespace, @project), method: :delete, remote: true, html: { class: 'transfer-project form-horizontal' }) do |f|
+ .panel.panel-default.panel.panel-danger
+ .panel-heading Remove fork relationship
+ .panel-body
+ %p
+ This will remove the fork relationship to source project
+ #{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) }
+ - else
+ .nothing-here-block Only the project owner can remove the fork relationship.
+
- if can?(current_user, :remove_project, @project)
.panel.panel-default.panel.panel-danger
.panel-heading Remove project
@@ -201,7 +216,8 @@
= button_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) }
- else
- .nothing-here-block Only project owner can remove a project
+ .nothing-here-block Only the project owner can remove a project.
+
.save-project-loader.hide
.center
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index e06454fd148..c3858e78cad 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -2,53 +2,56 @@
- if current_user && can?(current_user, :download_code, @project)
= render 'shared/no_ssh'
= render 'shared/no_password'
-
+
= render "home_panel"
.gray-content-block.center
%h3.page-title
The repository for this project is empty
- %p
- If you already have files you can push them using command line instructions below.
- %br
- 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)
+ %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.
-.prepend-top-20
-.empty_wrapper
- %h3.page-title-empty
- Command line instructions
- %div.git-empty
- %fieldset
- %h5 Git global setup
- %pre.light-well
- :preserve
- git config --global user.name "#{h git_user_name}"
- git config --global user.email "#{h git_user_email}"
+- if can?(current_user, :download_code, @project)
+ .prepend-top-20
+ .empty_wrapper
+ %h3.page-title-empty
+ Command line instructions
+ %div.git-empty
+ %fieldset
+ %h5 Git global setup
+ %pre.light-well
+ :preserve
+ git config --global user.name "#{h git_user_name}"
+ git config --global user.email "#{h git_user_email}"
- %fieldset
- %h5 Create a new repository
- %pre.light-well
- :preserve
- git clone #{ content_tag(:span, default_url_to_repo, class: 'clone')}
- cd #{h @project.path}
- touch README.md
- git add README.md
- git commit -m "add README"
- git push -u origin master
+ %fieldset
+ %h5 Create a new repository
+ %pre.light-well
+ :preserve
+ git clone #{ content_tag(:span, default_url_to_repo, class: 'clone')}
+ cd #{h @project.path}
+ touch README.md
+ git add README.md
+ git commit -m "add README"
+ git push -u origin master
- %fieldset
- %h5 Existing folder or Git repository
- %pre.light-well
- :preserve
- cd existing_folder
- git init
- git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
- git add .
- git commit
- git push -u origin master
+ %fieldset
+ %h5 Existing folder or Git repository
+ %pre.light-well
+ :preserve
+ cd existing_folder
+ git init
+ git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
+ git add .
+ git commit
+ git push -u origin master
- - if can? current_user, :remove_project, @project
- .prepend-top-20
- = link_to 'Remove project', [@project.namespace.becomes(Namespace), @project], data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right"
+ - if can? current_user, :remove_project, @project
+ .prepend-top-20
+ = link_to 'Remove project', [@project.namespace.becomes(Namespace), @project], data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right"
diff --git a/app/views/projects/imports/new.html.haml b/app/views/projects/imports/new.html.haml
index f8f2e192e29..92a87690c54 100644
--- a/app/views/projects/imports/new.html.haml
+++ b/app/views/projects/imports/new.html.haml
@@ -17,6 +17,6 @@
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/migrating_from_svn.html"}
+ For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/importing/migrating_from_svn.html"}
.form-actions
= f.submit 'Start import', class: "btn btn-create", tabindex: 4
diff --git a/app/views/projects/issues/_closed_by_box.html.haml b/app/views/projects/issues/_closed_by_box.html.haml
new file mode 100644
index 00000000000..aef352029d0
--- /dev/null
+++ b/app/views/projects/issues/_closed_by_box.html.haml
@@ -0,0 +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.
diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml
index a3399c57aa2..ca5b1a8386d 100644
--- a/app/views/projects/issues/_issues.html.haml
+++ b/app/views/projects/issues/_issues.html.haml
@@ -5,8 +5,9 @@
.nothing-here-block No issues to show
- if @issues.present?
- .pull-right
- %span.issue_counter #{@issues.total_count}
- issues for this filter
+ .issuable-filter-count
+ %span.pull-right
+ = @issues.total_count
+ issues for this filter
= paginate @issues, theme: "gitlab"
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 5cb814c9ea8..f01bf2505da 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -46,6 +46,7 @@
= markdown(@issue.description)
%textarea.hidden.js-task-list-field
= @issue.description
-
+ - if @closed_by_merge_requests.present?
+ = render 'projects/issues/closed_by_box'
.issue-discussion
= render 'projects/issues/discussion'
diff --git a/app/views/projects/merge_requests/_merge_requests.html.haml b/app/views/projects/merge_requests/_merge_requests.html.haml
index d86707b3d97..0af970e4b92 100644
--- a/app/views/projects/merge_requests/_merge_requests.html.haml
+++ b/app/views/projects/merge_requests/_merge_requests.html.haml
@@ -5,8 +5,10 @@
.nothing-here-block No merge requests to show
- if @merge_requests.present?
- .pull-right
- %span.cgray.pull-right #{@merge_requests.total_count} merge requests for this filter
+ .issuable-filter-count
+ %span.pull-right
+ = @merge_requests.total_count
+ merge requests for this filter
= paginate @merge_requests, theme: "gitlab"
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index e7ac7a0eaa4..eeaa72ed21b 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -36,7 +36,8 @@
- if @merge_request.open? && @merge_request.can_be_merged?
.light.append-bottom-20
You can also accept this merge request manually using the
- = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
+ = 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
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
index 68dda1424cf..10efb811939 100644
--- a/app/views/projects/merge_requests/widget/_heading.html.haml
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -1,44 +1,43 @@
-- if @merge_request.has_ci?
- - ci_commit = @merge_request.source_project.ci_commit(@merge_request.source_sha)
- - if ci_commit
- - status = ci_commit.status
- .mr-widget-heading
- .ci_widget{class: "ci-#{status}"}
- = ci_status_icon(ci_commit)
+- ci_commit = @merge_request.source_project.ci_commit(@merge_request.source_sha)
+- if ci_commit
+ - status = ci_commit.status
+ .mr-widget-heading
+ .ci_widget{class: "ci-#{status}"}
+ = ci_status_icon(ci_commit)
+ %span CI build #{status}
+ for #{@merge_request.last_commit_short_sha}.
+ %span.ci-coverage
+ = link_to "View build details", ci_status_path(ci_commit)
+
+- elsif @merge_request.has_ci?
+ - # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX
+ - # Remove in later versions when services like Jenkins will set CI status via Commit status API
+ .mr-widget-heading
+ - [:success, :skipped, :canceled, :failed, :running, :pending].each do |status|
+ .ci_widget{class: "ci-#{status}", style: "display:none"}
+ - if status == :success
+ - status = "passed"
+ = icon("check-circle")
+ - else
+ = icon("circle")
%span CI build #{status}
for #{@merge_request.last_commit_short_sha}.
%span.ci-coverage
- = link_to "View build details", ci_status_path(ci_commit)
-
- - else
- - # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX
- - # Remove in later versions when services like Jenkins will set CI status via Commit status API
- .mr-widget-heading
- - [:success, :skipped, :canceled, :failed, :running, :pending].each do |status|
- .ci_widget{class: "ci-#{status}", style: "display:none"}
- - if status == :success
- - status = "passed"
- = icon("check-circle")
- - else
- = icon("circle")
- %span CI build #{status}
- for #{@merge_request.last_commit_short_sha}.
- %span.ci-coverage
- - if ci_build_details_path(@merge_request)
- = link_to "View build details", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
+ - if ci_build_details_path(@merge_request)
+ = link_to "View build details", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
- .ci_widget
- = icon("spinner spin")
- Checking CI status for #{@merge_request.last_commit_short_sha}&hellip;
+ .ci_widget
+ = icon("spinner spin")
+ Checking CI status for #{@merge_request.last_commit_short_sha}&hellip;
- .ci_widget.ci-not_found{style: "display:none"}
- = icon("times-circle")
- Could not find CI status for #{@merge_request.last_commit_short_sha}.
+ .ci_widget.ci-not_found{style: "display:none"}
+ = icon("times-circle")
+ Could not find CI status for #{@merge_request.last_commit_short_sha}.
- .ci_widget.ci-error{style: "display:none"}
- = icon("times-circle")
- Could not connect to the CI server. Please check your settings and try again.
+ .ci_widget.ci-error{style: "display:none"}
+ = icon("times-circle")
+ Could not connect to the CI server. Please check your settings and try again.
- :coffeescript
- $ ->
- merge_request_widget.getCiStatus()
+ :coffeescript
+ $ ->
+ merge_request_widget.getCiStatus()
diff --git a/app/views/projects/milestones/_issue.html.haml b/app/views/projects/milestones/_issue.html.haml
index 88fccfe4981..133d802aaca 100644
--- a/app/views/projects/milestones/_issue.html.haml
+++ b/app/views/projects/milestones/_issue.html.haml
@@ -1,7 +1,7 @@
%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid, 'data-url' => issue_path(issue) }
.pull-right.assignee-icon
- if issue.assignee
- = image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16", alt: ''
+ = image_tag avatar_icon(issue.assignee, 16), class: "avatar s16", alt: ''
%span
= link_to [@project.namespace.becomes(Namespace), @project, issue] do
%span.cgray ##{issue.iid}
diff --git a/app/views/projects/milestones/_merge_request.html.haml b/app/views/projects/milestones/_merge_request.html.haml
index 0d7a118569a..a1033607c5d 100644
--- a/app/views/projects/milestones/_merge_request.html.haml
+++ b/app/views/projects/milestones/_merge_request.html.haml
@@ -5,4 +5,4 @@
= link_to_gfm merge_request.title, [@project.namespace.becomes(Namespace), @project, merge_request], title: merge_request.title
.pull-right.assignee-icon
- if merge_request.assignee
- = image_tag avatar_icon(merge_request.assignee.email, 16), class: "avatar s16", alt: ''
+ = image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16", alt: ''
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 4eeb0621e52..3a898dfbcfd 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -104,7 +104,7 @@
- @users.each do |user|
%li
= link_to user, title: user.name, class: "darken" do
- = image_tag avatar_icon(user.email, 32), class: "avatar s32"
+ = image_tag avatar_icon(user, 32), class: "avatar s32"
%strong= truncate(user.name, lenght: 40)
%br
%small.cgray= user.username
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 1638ad6891a..5d184730796 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -1,8 +1,8 @@
%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)], data: { discussion: note.discussion_id } }
.timeline-entry-inner
.timeline-icon
- = link_to user_path(note.author) do
- = image_tag avatar_icon(note.author_email), class: 'avatar s40', alt: ''
+ %a{href: user_path(note.author)}
+ %img.avatar.s40{src: avatar_icon(note.author), alt: ''}
.timeline-content
.note-header
- if note_editable?(note)
@@ -25,7 +25,7 @@
= '@' + note.author.username
%span.note-last-update
- = link_to "##{dom_id(note)}", name: dom_id(note), title: "Link here" do
+ %a{name: dom_id(note), href: "##{dom_id(note)}", title: 'Link here'}
= time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note_created_ago')
- if note.updated_at != note.created_at
%span
diff --git a/app/views/projects/project_members/_project_member.html.haml b/app/views/projects/project_members/_project_member.html.haml
index 860a997cff8..76c46d1d806 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.email, 16), class: "avatar s16", alt: ''
+ = image_tag avatar_icon(user, 16), class: "avatar s16", alt: ''
%strong
= link_to user.name, user_path(user)
%span.cgray= user.username
diff --git a/app/views/projects/protected_branches/_branches_list.html.haml b/app/views/projects/protected_branches/_branches_list.html.haml
index bb49f4de873..f68449b1863 100644
--- a/app/views/projects/protected_branches/_branches_list.html.haml
+++ b/app/views/projects/protected_branches/_branches_list.html.haml
@@ -1,34 +1,35 @@
- unless @branches.empty?
%br
%h4 Already Protected:
- %table.table.protected-branches-list
- %thead
- %tr.no-border
- %th Branch
- %th Developers can push
- %th Last commit
- %th
+ .table-holder
+ %table.table.protected-branches-list
+ %thead
+ %tr.no-border
+ %th Branch
+ %th Developers can push
+ %th Last commit
+ %th
- %tbody
- - @branches.each do |branch|
- - @url = namespace_project_protected_branch_path(@project.namespace, @project, branch)
- %tr
- %td
- = link_to namespace_project_commits_path(@project.namespace, @project, branch.name) do
- %strong= branch.name
- - if @project.root_ref?(branch.name)
- %span.label.label-info default
+ %tbody
+ - @branches.each do |branch|
+ - @url = namespace_project_protected_branch_path(@project.namespace, @project, branch)
+ %tr
%td
- = check_box_tag "developers_can_push", branch.id, branch.developers_can_push, "data-url" => @url
- %td
- - if commit = branch.commit
- = link_to namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit_short_id' do
- = commit.short_id
- &middot;
- #{time_ago_with_tooltip(commit.committed_date)}
- - else
- (branch was removed from repository)
- %td
- .pull-right
- - if can? current_user, :admin_project, @project
- = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-remove btn-sm"
+ = link_to namespace_project_commits_path(@project.namespace, @project, branch.name) do
+ %strong= branch.name
+ - if @project.root_ref?(branch.name)
+ %span.label.label-info default
+ %td
+ = check_box_tag "developers_can_push", branch.id, branch.developers_can_push, "data-url" => @url
+ %td
+ - if commit = branch.commit
+ = link_to namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit_short_id' do
+ = commit.short_id
+ &middot;
+ #{time_ago_with_tooltip(commit.committed_date)}
+ - else
+ (branch was removed from repository)
+ %td
+ .pull-right
+ - if can? current_user, :admin_project, @project
+ = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-remove btn-sm"
diff --git a/app/views/projects/remove_fork.js.haml b/app/views/projects/remove_fork.js.haml
new file mode 100644
index 00000000000..17b9fecfeb1
--- /dev/null
+++ b/app/views/projects/remove_fork.js.haml
@@ -0,0 +1,2 @@
+:plain
+ location.href = "#{edit_namespace_project_path(@project.namespace, @project)}";
diff --git a/app/views/projects/runners/show.html.haml b/app/views/projects/runners/show.html.haml
index ffec495f85a..c255cd51bd2 100644
--- a/app/views/projects/runners/show.html.haml
+++ b/app/views/projects/runners/show.html.haml
@@ -9,56 +9,57 @@
%span.runner-state.runner-state-specific
Specific
-%table.table
- %thead
+.table-holder
+ %table.table
+ %thead
+ %tr
+ %th Property Name
+ %th Value
%tr
- %th Property Name
- %th Value
- %tr
- %td
- Tags
- %td
- - @runner.tag_list.each do |tag|
- %span.label.label-primary
- = tag
- %tr
- %td
- Name
- %td
- = @runner.name
- %tr
- %td
- Version
- %td
- = @runner.version
- %tr
- %td
- Revision
- %td
- = @runner.revision
- %tr
- %td
- Platform
- %td
- = @runner.platform
- %tr
- %td
- Architecture
- %td
- = @runner.architecture
- %tr
- %td
- Description
- %td
- = @runner.description
- %tr
- %td
- Last contact
- %td
- - if @runner.contacted_at
- #{time_ago_in_words(@runner.contacted_at)} ago
- - else
- Never
+ %td
+ Tags
+ %td
+ - @runner.tag_list.each do |tag|
+ %span.label.label-primary
+ = tag
+ %tr
+ %td
+ Name
+ %td
+ = @runner.name
+ %tr
+ %td
+ Version
+ %td
+ = @runner.version
+ %tr
+ %td
+ Revision
+ %td
+ = @runner.revision
+ %tr
+ %td
+ Platform
+ %td
+ = @runner.platform
+ %tr
+ %td
+ Architecture
+ %td
+ = @runner.architecture
+ %tr
+ %td
+ Description
+ %td
+ = @runner.description
+ %tr
+ %td
+ Last contact
+ %td
+ - if @runner.contacted_at
+ #{time_ago_in_words(@runner.contacted_at)} ago
+ - else
+ Never
-
+
diff --git a/app/views/projects/services/index.html.haml b/app/views/projects/services/index.html.haml
index 1065def693b..c1356f6db02 100644
--- a/app/views/projects/services/index.html.haml
+++ b/app/views/projects/services/index.html.haml
@@ -2,22 +2,23 @@
%h3.page-title Project services
%p.light Project services allow you to integrate GitLab with other applications
-%table.table
- %thead
- %tr
- %th
- %th Service
- %th Description
- %th Last edit
- - @services.sort_by(&:title).each do |service|
- %tr
- %td
- = boolean_to_icon service.activated?
- %td
- = link_to edit_namespace_project_service_path(@project.namespace, @project, service.to_param) do
- %strong= service.title
- %td
- = service.description
- %td.light
- = time_ago_in_words service.updated_at
- ago
+.table-holder
+ %table.table
+ %thead
+ %tr
+ %th
+ %th Service
+ %th Description
+ %th Last edit
+ - @services.sort_by(&:title).each do |service|
+ %tr
+ %td
+ = boolean_to_icon service.activated?
+ %td
+ = link_to edit_namespace_project_service_path(@project.namespace, @project, service.to_param) do
+ %strong= service.title
+ %td
+ = service.description
+ %td.light
+ = time_ago_in_words service.updated_at
+ ago
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index e95d987d74c..585caf674c9 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -7,8 +7,7 @@
= render 'shared/no_ssh'
= render 'shared/no_password'
-- if prefer_readme?
- = render 'projects/last_push'
+= render 'projects/last_push'
= render "home_panel"
@@ -28,7 +27,7 @@
= link_to project_files_path(@project) do
= repository_size
- - if !prefer_readme? && @repository.readme
+ - if default_project_view != 'readme' && @repository.readme
%li
= link_to 'Readme', readme_path(@project)
@@ -64,14 +63,12 @@
= icon("exclamation-triangle fw")
Archived project! Repository is read-only
-%section
- - if prefer_readme?
- .project-show-readme
- = render 'projects/readme'
- - else
- .project-show-activity
- = render 'projects/activity'
+- if @repository.commit
+ .content-block.second-block.white
+ = render 'projects/last_commit', commit: @repository.commit, project: @project
+%div{class: "project-show-#{default_project_view}"}
+ = render default_project_view
- if current_user
- access = user_max_access_in_project(current_user, @project)
diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml
new file mode 100644
index 00000000000..4a515469422
--- /dev/null
+++ b/app/views/projects/snippets/_actions.html.haml
@@ -0,0 +1,11 @@
+= link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped new-snippet-link', title: "New Snippet" do
+ = icon('plus')
+ New Snippet
+- if can?(current_user, :admin_project_snippet, @snippet)
+ = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-remove", title: 'Delete Snippet' do
+ = icon('trash-o')
+ Delete
+- if can?(current_user, :update_project_snippet, @snippet)
+ = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped snippable-edit" do
+ = icon('pencil-square-o')
+ Edit
diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml
index 3fed2c9949d..4af963e14da 100644
--- a/app/views/projects/snippets/index.html.haml
+++ b/app/views/projects/snippets/index.html.haml
@@ -1,17 +1,13 @@
- page_title "Snippets"
= render "header_title"
-%h3.page-title
- Snippets
- - if can? current_user, :create_project_snippet, @project
- = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new pull-right", title: "New Snippet" do
- Add new snippet
+.gray-content-block.top-block
+ .pull-right
+ = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New Snippet" do
+ = icon('plus')
+ New Snippet
-%p.light
- Share code pastes with others out of git repository
+ .oneline
+ Share code pastes with others out of git repository
-%ul.bordered-list
- = render partial: "shared/snippets/snippet", collection: @snippets
- - if @snippets.empty?
- %li
- .nothing-here-block Nothing here.
+= render 'snippets/snippets'
diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml
index be7d4d486fa..5d706942f2d 100644
--- a/app/views/projects/snippets/show.html.haml
+++ b/app/views/projects/snippets/show.html.haml
@@ -1,40 +1,18 @@
- page_title @snippet.title, "Snippets"
= render "header_title"
-%h3.page-title
- = @snippet.title
+.snippet-holder
+ = render 'shared/snippets/header'
- .pull-right
- = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New Snippet" do
- Add new snippet
+ %article.file-holder
+ .file-title
+ = blob_icon 0, @snippet.file_name
+ %strong
+ = @snippet.file_name
+ .file-actions.hidden-xs
+ .btn-group.tree-btn-group
+ = link_to 'Raw', raw_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", target: "_blank"
-%hr
+ = render 'shared/snippets/blob'
-.append-bottom-20
- .pull-right
- = "##{@snippet.id}"
- %span.light
- by
- = link_to user_path(@snippet.author) do
- = image_tag avatar_icon(@snippet.author_email), class: "avatar avatar-inline s16"
- = @snippet.author_name
-
- .back-link
- = link_to namespace_project_snippets_path(@project.namespace, @project) do
- &larr; project snippets
-
-.file-holder
- .file-title
- %i.fa.fa-file
- %strong
- = @snippet.file_name
- .file-actions
- .btn-group
- - if can?(current_user, :update_project_snippet, @snippet)
- = link_to "edit", edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", title: 'Edit Snippet'
- = link_to "raw", raw_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", target: "_blank"
- - if can?(current_user, :admin_project_snippet, @snippet)
- = link_to "remove", namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-sm btn-remove", title: 'Delete Snippet'
- = render 'shared/snippets/blob'
-
-%div#notes= render "projects/notes/notes_with_form"
+ %div#notes= render "projects/notes/notes_with_form"
diff --git a/app/views/projects/tree/_blob_item.html.haml b/app/views/projects/tree/_blob_item.html.haml
index 02ecbade219..2ddc5d504fa 100644
--- a/app/views/projects/tree/_blob_item.html.haml
+++ b/app/views/projects/tree/_blob_item.html.haml
@@ -4,5 +4,5 @@
%span.str-truncated
= link_to blob_item.name, namespace_project_blob_path(@project.namespace, @project, tree_join(@id || @commit.id, blob_item.name))
%td.tree_time_ago.cgray
- = render 'spinner'
+ = render 'projects/tree/spinner'
%td.hidden-xs.tree_commit
diff --git a/app/views/projects/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml
index 7e9af19c8ba..3c5edf4b033 100644
--- a/app/views/projects/tree/_readme.html.haml
+++ b/app/views/projects/tree/_readme.html.haml
@@ -1,8 +1,8 @@
-%article.file-holder.readme-holder#README
+%article.file-holder.readme-holder
.file-title
- = link_to '#README' do
+ = blob_icon readme.mode, readme.name
+ = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)) do
%strong
- %i.fa.fa-file
= readme.name
.file-content.wiki
= render_readme(readme)
diff --git a/app/views/projects/tree/_tree.html.haml b/app/views/projects/tree/_tree_content.html.haml
index 7ff48e32e60..ee4c9d1693d 100644
--- a/app/views/projects/tree/_tree.html.haml
+++ b/app/views/projects/tree/_tree_content.html.haml
@@ -1,36 +1,5 @@
-.gray-content-block
- %ul.breadcrumb.repo-breadcrumb
- %li
- = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
- = @project.path
- - tree_breadcrumbs(tree, 6) do |title, path|
- %li
- - if path
- = link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path)
- - else
- = link_to title, '#'
- - if allowed_tree_edit?
- %li
- %span.dropdown
- %a.dropdown-toggle.btn.add-to-tree{href: '#', "data-toggle" => "dropdown"}
- = icon('plus')
- %ul.dropdown-menu
- %li
- = link_to namespace_project_new_blob_path(@project.namespace, @project, @id), title: 'Create file', id: 'new-file-link' do
- = icon('pencil fw')
- Create file
- %li
- = link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal'} do
- = icon('file fw')
- Upload file
- %li.divider
- %li
- = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal'} do
- = icon('folder fw')
- New directory
-
-%div#tree-content-holder.tree-content-holder
- .tree-table-holder
+%div.tree-content-holder
+ .table-holder
%table.table#tree-slider{class: "table_#{@hex_path} tree-table table-striped" }
%thead
%tr
@@ -60,8 +29,6 @@
- if tree.readme
= render "projects/tree/readme", readme: tree.readme
-%div.tree_progress
-
- 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/new_dir'
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
new file mode 100644
index 00000000000..1115ca6b4ca
--- /dev/null
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -0,0 +1,32 @@
+.tree-ref-holder
+ = render 'shared/ref_switcher', destination: 'tree', path: @path
+
+%ul.breadcrumb.repo-breadcrumb
+ %li
+ = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
+ = @project.path
+ - tree_breadcrumbs(tree, 6) do |title, path|
+ %li
+ - if path
+ = link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path)
+ - else
+ = link_to title, '#'
+ - if allowed_tree_edit?
+ %li
+ %span.dropdown
+ %a.dropdown-toggle.btn.add-to-tree{href: '#', "data-toggle" => "dropdown"}
+ = icon('plus')
+ %ul.dropdown-menu
+ %li
+ = link_to namespace_project_new_blob_path(@project.namespace, @project, @id), title: 'Create file', id: 'new-file-link' do
+ = icon('pencil fw')
+ Create file
+ %li
+ = link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal'} do
+ = icon('file fw')
+ Upload file
+ %li.divider
+ %li
+ = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal'} do
+ = icon('folder fw')
+ New directory
diff --git a/app/views/projects/tree/_tree_item.html.haml b/app/views/projects/tree/_tree_item.html.haml
index e87138bf980..cf65057e704 100644
--- a/app/views/projects/tree/_tree_item.html.haml
+++ b/app/views/projects/tree/_tree_item.html.haml
@@ -5,5 +5,5 @@
- path = flatten_tree(tree_item)
= link_to path, namespace_project_tree_path(@project.namespace, @project, tree_join(@id || @commit.id, path))
%td.tree_time_ago.cgray
- = render 'spinner'
+ = render 'projects/tree/spinner'
%td.hidden-xs.tree_commit
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index dec4677f830..ec14bd7f65a 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -6,12 +6,12 @@
= render 'projects/last_push'
-.tree-ref-holder
- = render 'shared/ref_switcher', destination: 'tree', path: @path
-
- if can? current_user, :download_code, @project
.tree-download-holder
= render 'projects/repositories/download_archive', ref: @ref, btn_class: 'btn-group pull-right hidden-xs hidden-sm', split_button: true
#tree-holder.tree-holder.clearfix
- = render "tree", tree: @tree
+ .gray-content-block.top-block
+ = render 'projects/tree/tree_header', tree: @tree
+
+ = render 'projects/tree/tree_content', tree: @tree
diff --git a/app/views/projects/triggers/index.html.haml b/app/views/projects/triggers/index.html.haml
index 17dcb78e256..18a37302c3e 100644
--- a/app/views/projects/triggers/index.html.haml
+++ b/app/views/projects/triggers/index.html.haml
@@ -7,12 +7,13 @@
%hr.clearfix
-if @triggers.any?
- %table.table
- %thead
- %th Token
- %th Last used
- %th
- = render partial: 'trigger', collection: @triggers, as: :trigger
+ .table-holder
+ %table.table
+ %thead
+ %th Token
+ %th Last used
+ %th
+ = render partial: 'trigger', collection: @triggers, as: :trigger
- else
%h4 No triggers
diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml
index bfbef823b35..4322146ce34 100644
--- a/app/views/projects/wikis/history.html.haml
+++ b/app/views/projects/wikis/history.html.haml
@@ -7,28 +7,29 @@
%span.light History for
= link_to @page.title, namespace_project_wiki_path(@project.namespace, @project, @page)
-%table.table
- %thead
- %tr
- %th Page version
- %th Author
- %th Commit Message
- %th Last updated
- %th Format
- %tbody
- - @page.versions.each_with_index do |version, index|
- - commit = version
+.table-holder
+ %table.table
+ %thead
%tr
- %td
- = link_to project_wiki_path_with_version(@project, @page,
- commit.id, index == 0) do
- = truncate_sha(commit.id)
- %td
- = commit.author.name
- %td
- = commit.message
- %td
- #{time_ago_with_tooltip(version.authored_date)}
- %td
- %strong
- = @page.page.wiki.page(@page.page.name, commit.id).try(:format)
+ %th Page version
+ %th Author
+ %th Commit Message
+ %th Last updated
+ %th Format
+ %tbody
+ - @page.versions.each_with_index do |version, index|
+ - commit = version
+ %tr
+ %td
+ = link_to project_wiki_path_with_version(@project, @page,
+ commit.id, index == 0) do
+ = truncate_sha(commit.id)
+ %td
+ = commit.author.name
+ %td
+ = commit.message
+ %td
+ #{time_ago_with_tooltip(version.authored_date)}
+ %td
+ %strong
+ = @page.page.wiki.page(@page.page.name, commit.id).try(:format)
diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index b23b2f0d5eb..2e4aab36301 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -6,7 +6,7 @@
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-title" => "Add an SSH key to your profile<br> to pull or push via SSH.",
:"data-html" => "true",
:"data-container" => "body"}
SSH
@@ -15,7 +15,7 @@
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-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
diff --git a/app/views/shared/_logo.svg b/app/views/shared/_logo.svg
new file mode 100644
index 00000000000..da49c48acd3
--- /dev/null
+++ b/app/views/shared/_logo.svg
@@ -0,0 +1,21 @@
+<svg width="36px" height="36px" viewBox="0 0 210 210" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="tanuki-logo">
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
+ <g id="logo" sketch:type="MSLayerGroup" transform="translate(0.000000, 10.000000)">
+ <g id="Page-1" sketch:type="MSShapeGroup">
+ <g id="Fill-1-+-Group-24">
+ <g id="Group-24">
+ <g id="Group">
+ <path d="M105.0614,193.655 L105.0614,193.655 L143.7014,74.734 L66.4214,74.734 L105.0614,193.655 L105.0614,193.655 Z" id="Fill-4" fill="#E24329" class="tanuki-shape"></path>
+ <path d="M105.0614,193.6548 L66.4214,74.7338 L12.2684,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-8" fill="#FC6D26" class="tanuki-shape"></path>
+ <path d="M12.2685,74.7341 L12.2685,74.7341 L0.5265,110.8731 C-0.5445,114.1691 0.6285,117.7801 3.4325,119.8171 L105.0615,193.6551 L12.2685,74.7341 L12.2685,74.7341 Z" id="Fill-12" fill="#FCA326" class="tanuki-shape"></path>
+ <path d="M12.2685,74.7342 L66.4215,74.7342 L43.1485,3.1092 C41.9515,-0.5768 36.7375,-0.5758 35.5405,3.1092 L12.2685,74.7342 L12.2685,74.7342 Z" id="Fill-16" fill="#E24329" class="tanuki-shape"></path>
+ <path d="M105.0614,193.6548 L143.7014,74.7338 L197.8544,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-18" fill="#FC6D26" class="tanuki-shape"></path>
+ <path d="M197.8544,74.7341 L197.8544,74.7341 L209.5964,110.8731 C210.6674,114.1691 209.4944,117.7801 206.6904,119.8171 L105.0614,193.6551 L197.8544,74.7341 L197.8544,74.7341 Z" id="Fill-20" fill="#FCA326" class="tanuki-shape"></path>
+ <path d="M197.8544,74.7342 L143.7014,74.7342 L166.9744,3.1092 C168.1714,-0.5768 173.3854,-0.5758 174.5824,3.1092 L197.8544,74.7342 L197.8544,74.7342 Z" id="Fill-22" fill="#E24329" class="tanuki-shape"></path>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index 8f16773077e..0e4e9c0987a 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -42,11 +42,10 @@
class: 'select2 trigger-submit', include_blank: true,
data: {placeholder: 'Milestone'})
- - if @project
- .filter-item.inline.labels-filter
- = select_tag('label_name', project_labels_options(@project),
- class: 'select2 trigger-submit', include_blank: true,
- data: {placeholder: 'Label'})
+ .filter-item.inline.labels-filter
+ = select_tag('label_name', projects_labels_options,
+ class: 'select2 trigger-submit', include_blank: true,
+ data: {placeholder: 'Label'})
.pull-right
= render 'shared/sort_dropdown'
diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml
new file mode 100644
index 00000000000..0a4a790ec5e
--- /dev/null
+++ b/app/views/shared/snippets/_header.html.haml
@@ -0,0 +1,24 @@
+.snippet-details
+ .page-title
+ .snippet-box{class: visibility_level_color(@snippet.visibility_level)}
+ = visibility_level_icon(@snippet.visibility_level)
+ = visibility_level_label(@snippet.visibility_level)
+ %span.snippet-id Snippet ##{@snippet.id}
+ %span.creator
+ &middot; created by #{link_to_member(@project, @snippet.author, size: 24)}
+ &middot;
+ = time_ago_with_tooltip(@snippet.created_at, placement: 'bottom', html_class: 'snippet_updated_ago')
+ - if @snippet.updated_at != @snippet.created_at
+ %span
+ &middot;
+ = icon('edit', title: 'edited')
+ = time_ago_with_tooltip(@snippet.updated_at, placement: 'bottom', html_class: 'snippet_edited_ago')
+
+ .pull-right
+ - if @snippet.project_id?
+ = render "projects/snippets/actions"
+ - else
+ = render "snippets/actions"
+ .gray-content-block.middle-block
+ %h2.snippet-title
+ = gfm escape_once(@snippet.title)
diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml
index 69a713ad9aa..c6294caddc7 100644
--- a/app/views/shared/snippets/_snippet.html.haml
+++ b/app/views/shared/snippets/_snippet.html.haml
@@ -18,4 +18,3 @@
= image_tag avatar_icon(snippet.author_email), class: "avatar s24", alt: ''
= snippet.author_name
authored #{time_ago_with_tooltip(snippet.created_at)}
-
diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml
new file mode 100644
index 00000000000..751fafa8942
--- /dev/null
+++ b/app/views/snippets/_actions.html.haml
@@ -0,0 +1,11 @@
+= link_to new_snippet_path, class: 'btn btn-grouped new-snippet-link', title: "New Snippet" do
+ = icon('plus')
+ New Snippet
+- if can?(current_user, :admin_personal_snippet, @snippet)
+ = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-remove", title: 'Delete Snippet' do
+ = icon('trash-o')
+ Delete
+- if can?(current_user, :update_personal_snippet, @snippet)
+ = link_to edit_snippet_path(@snippet), class: "btn btn-grouped snippable-edit" do
+ = icon('pencil-square-o')
+ Edit
diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml
index 97374e073dc..69d8899d4c1 100644
--- a/app/views/snippets/show.html.haml
+++ b/app/views/snippets/show.html.haml
@@ -1,41 +1,14 @@
- page_title @snippet.title, "Snippets"
-%h4.page-title
- = @snippet.title
- - if @snippet.private?
- %span.label.label-success
- %i.fa.fa-lock
- private
-
- .pull-right
- = link_to new_snippet_path, class: "btn btn-new btn-sm", title: "New Snippet" do
- Add new snippet
-
-.append-bottom-10.prepend-top-10
- .pull-right
- %span.light
- created by
- = link_to user_snippets_path(@snippet.author) do
- = @snippet.author_name
-
- .back-link
- - if @snippet.author == current_user
- = link_to dashboard_snippets_path do
- &larr; your snippets
- - else
- = link_to explore_snippets_path do
- &larr; explore snippets
-
-.file-holder
- .file-title
- %i.fa.fa-file
- %strong
- = @snippet.file_name
- .file-actions
- .btn-group
- - if can?(current_user, :update_personal_snippet, @snippet)
- = link_to "edit", edit_snippet_path(@snippet), class: "btn btn-sm", title: 'Edit Snippet'
- = link_to "raw", raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank"
- - if can?(current_user, :admin_personal_snippet, @snippet)
- = link_to "remove", snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-sm btn-remove", title: 'Delete Snippet'
- = render 'shared/snippets/blob'
+.snippet-holder
+ = render 'shared/snippets/header'
+
+ %article.file-holder
+ .file-title
+ = blob_icon 0, @snippet.file_name
+ %strong
+ = @snippet.file_name
+ .file-actions.hidden-xs
+ .btn-group.tree-btn-group
+ = link_to 'Raw', raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank"
+ = render 'shared/snippets/blob'
diff --git a/app/views/users/calendar.html.haml b/app/views/users/calendar.html.haml
index 922b0c6cebf..7f29918dba3 100644
--- a/app/views/users/calendar.html.haml
+++ b/app/views/users/calendar.html.haml
@@ -1,7 +1,3 @@
-%h4
- Contributions calendar
- .pull-right
- %small Issues, merge requests and push events
#cal-heatmap.calendar
:javascript
new Calendar(
@@ -10,3 +6,5 @@
#{@starting_month},
'#{user_calendar_activities_path}'
);
+
+.calendar-hint Summary of issues, merge requests and push events
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 11beb3e3239..4ea4a1f92c2 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -6,47 +6,72 @@
= render 'shared/show_aside'
-.row
- %section.col-md-7
- .header-with-avatar
- = link_to avatar_icon(@user.email, 400), target: '_blank' do
- = image_tag avatar_icon(@user.email, 90), class: "avatar avatar-tile s90", alt: ''
- %h3
- = @user.name
- - if @user == current_user
- .pull-right.hidden-xs
- = link_to profile_path, class: 'btn btn-sm' do
- = icon('user')
- Profile settings
- - elsif current_user
- .report_abuse.pull-right
- - if @user.abuse_report
- %span#report_abuse_btn.light.btn.btn-sm.btn-close{title: 'Already reported for abuse', data: {toggle: 'tooltip', placement: 'right', container: 'body'}}
- = icon('exclamation-circle')
- - else
- %a.light.btn.btn-sm{href: new_abuse_report_path(user_id: @user.id), title: 'Report abuse', data: {toggle: 'tooltip', placement: 'right', container: 'body'}}
- = icon('exclamation-circle')
+.cover-block
+ .avatar-holder
+ = link_to avatar_icon(@user, 400), target: '_blank' do
+ = image_tag avatar_icon(@user, 90), class: "avatar s90", alt: ''
+ .cover-title
+ = @user.name
+
+ .cover-desc
+ %span
+ @#{@user.username}.
+ - if @user.bio.present?
+ %span
+ #{@user.bio}.
+ %span
+ Member since #{@user.created_at.stamp("Aug 21, 2011")}
+
+ .cover-desc
+ - unless @user.public_email.blank?
+ = link_to @user.public_email, "mailto:#{@user.public_email}"
+ - unless @user.skype.blank?
+ &middot;
+ = link_to "Skype", "skype:#{@user.skype}"
+ - unless @user.linkedin.blank?
+ &middot;
+ = link_to "LinkedIn", "http://www.linkedin.com/in/#{@user.linkedin}"
+ - unless @user.twitter.blank?
+ &middot;
+ = link_to "Twitter", "http://www.twitter.com/#{@user.twitter}"
+ - unless @user.website_url.blank?
+ &middot;
+ = link_to @user.short_website_url, @user.full_website_url
+ - unless @user.location.blank?
+ &middot;
+ = @user.location
- .username
- @#{@user.username}
- .description
- - if @user.bio.present?
- = @user.bio
- .clearfix
+ .cover-controls
+ - if @user == current_user
+ = link_to profile_path, class: 'btn btn-gray' do
+ = icon('pencil')
+ - elsif current_user
+ .report-abuse
+ - if @user.abuse_report
+ %button.btn.btn-danger{ title: 'Already reported for abuse',
+ data: { toggle: 'tooltip', placement: 'left', container: 'body' }}
+ = icon('exclamation-circle')
+ - else
+ = link_to new_abuse_report_path(user_id: @user.id), class: 'btn btn-gray',
+ title: 'Report abuse', data: {toggle: 'tooltip', placement: 'left', container: 'body'} do
+ = icon('exclamation-circle')
+.gray-content-block.second-block
+ .user-calendar
+ %h4.center.light
+ %i.fa.fa-spinner.fa-spin
+ .user-calendar-activities
+
+
+.row.prepend-top-20
+ %section.col-md-7
- if @groups.any?
.prepend-top-20
%h4 Groups
= render 'groups', groups: @groups
%hr
- .hidden-xs
- .user-calendar
- %h4.center.light
- %i.fa.fa-spinner.fa-spin
- .user-calendar-activities
- %hr
%h4
User Activity
@@ -59,7 +84,6 @@
.content_list
= spinner
%aside.col-md-5
- = render 'profile', user: @user
= render 'projects', projects: @projects, contributed_projects: @contributed_projects
:coffeescript
diff --git a/app/workers/repository_archive_cache_worker.rb b/app/workers/repository_archive_cache_worker.rb
new file mode 100644
index 00000000000..47c5a670ed4
--- /dev/null
+++ b/app/workers/repository_archive_cache_worker.rb
@@ -0,0 +1,9 @@
+class RepositoryArchiveCacheWorker
+ include Sidekiq::Worker
+
+ sidekiq_options queue: :default
+
+ def perform
+ Repository.clean_old_archives
+ end
+end
diff --git a/app/workers/repository_archive_worker.rb b/app/workers/repository_archive_worker.rb
deleted file mode 100644
index 021c1139568..00000000000
--- a/app/workers/repository_archive_worker.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-class RepositoryArchiveWorker
- include Sidekiq::Worker
-
- sidekiq_options queue: :archive_repo
-
- attr_accessor :project, :ref, :format
-
- def perform(project_id, ref, format)
- @project = Project.find(project_id)
- @ref, @format = ref, format.downcase
-
- repository = project.repository
-
- repository.clean_old_archives
-
- return unless file_path
- return if archived? || archiving?
-
- repository.archive_repo(ref, storage_path, format)
- end
-
- private
-
- def storage_path
- Gitlab.config.gitlab.repository_downloads_path
- end
-
- def file_path
- @file_path ||= project.repository.archive_file_path(ref, storage_path, format)
- end
-
- def pid_file_path
- @pid_file_path ||= project.repository.archive_pid_file_path(ref, storage_path, format)
- end
-
- def archived?
- File.exist?(file_path)
- end
-
- def archiving?
- File.exist?(pid_file_path)
- end
-end
diff --git a/config/environments/development.rb b/config/environments/development.rb
index d7d6aed1602..827a110c249 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -24,7 +24,7 @@ Gitlab::Application.configure do
# Expands the lines which load the assets
# config.assets.debug = true
-
+
# Adds additional error checking when serving assets at runtime.
# Checks for improperly declared sprockets dependencies.
# Raises helpful error messages.
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 4f7f0b6ef19..8b85981497a 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -99,7 +99,29 @@ production: &base
# For documentation on how to set this up, see http://doc.gitlab.com/ce/incoming_email/README.html
incoming_email:
enabled: false
- address: "incoming+%{key}@gitlab.example.com"
+
+ # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
+ # The `%{key}` placeholder is added after the user part, after a `+` character, before the `@`.
+ address: "gitlab-incoming+%{key}@gmail.com"
+
+ # Email account username
+ # With third party providers, this is usually the full email address.
+ # With self-hosted email servers, this is usually the user part of the email address.
+ user: "gitlab-incoming@gmail.com"
+ # Email account password
+ password: "[REDACTED]"
+
+ # IMAP server host
+ host: "imap.gmail.com"
+ # IMAP server port
+ port: 993
+ # Whether the IMAP server uses SSL
+ ssl: true
+ # Whether the IMAP server uses StartTLS
+ start_tls: false
+
+ # The mailbox where incoming mail will end up. Usually "inbox".
+ mailbox: "inbox"
## Gravatar
## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 4c78bd6e2fa..d5493ca038d 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -187,7 +187,11 @@ Settings.gitlab_ci['builds_path'] = File.expand_path(Settings.gitlab_ci[
# Reply by email
#
Settings['incoming_email'] ||= Settingslogic.new({})
-Settings.incoming_email['enabled'] = false if Settings.incoming_email['enabled'].nil?
+Settings.incoming_email['enabled'] = false if Settings.incoming_email['enabled'].nil?
+Settings.incoming_email['port'] = 143 if Settings.incoming_email['port'].nil?
+Settings.incoming_email['ssl'] = 143 if Settings.incoming_email['ssl'].nil?
+Settings.incoming_email['start_tls'] = 143 if Settings.incoming_email['start_tls'].nil?
+Settings.incoming_email['mailbox'] = "inbox" if Settings.incoming_email['mailbox'].nil?
#
# Gravatar
diff --git a/config/initializers/active_record_query_trace.rb b/config/initializers/active_record_query_trace.rb
new file mode 100644
index 00000000000..4b3c2803b3b
--- /dev/null
+++ b/config/initializers/active_record_query_trace.rb
@@ -0,0 +1,5 @@
+if ENV['ENABLE_QUERY_TRACE']
+ require 'active_record_query_trace'
+
+ ActiveRecordQueryTrace.enabled = 'true'
+end
diff --git a/config/initializers/bullet.rb b/config/initializers/bullet.rb
new file mode 100644
index 00000000000..95e82966c7a
--- /dev/null
+++ b/config/initializers/bullet.rb
@@ -0,0 +1,6 @@
+if ENV['ENABLE_BULLET']
+ require 'bullet'
+
+ Bullet.enable = true
+ Bullet.console = true
+end
diff --git a/config/initializers/rack_lineprof.rb b/config/initializers/rack_lineprof.rb
new file mode 100644
index 00000000000..f0c006d811b
--- /dev/null
+++ b/config/initializers/rack_lineprof.rb
@@ -0,0 +1,31 @@
+# The default colors of rack-lineprof can be very hard to look at in terminals
+# 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)
+
+ module Rack
+ class Lineprof
+ class Sample < Rack::Lineprof::Sample.superclass
+ def format(*)
+ formatted = if level == CONTEXT
+ sprintf " | % 3i %s", line, code
+ else
+ sprintf "% 8.1fms %5i | % 3i %s", ms, calls, line, code
+ end
+
+ case level
+ when CRITICAL
+ color.red formatted
+ when WARNING
+ color.yellow formatted
+ when NOMINAL
+ color.white formatted
+ else # CONTEXT
+ formatted
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/config/mail_room.yml b/config/mail_room.yml
new file mode 100644
index 00000000000..42f6f74c465
--- /dev/null
+++ b/config/mail_room.yml
@@ -0,0 +1,39 @@
+:mailboxes:
+<%
+require_relative 'config/environment.rb'
+
+if Gitlab::IncomingEmail.enabled?
+ config = Gitlab::IncomingEmail.config
+
+ redis_config_file = "config/resque.yml"
+ redis_url =
+ if File.exists?(redis_config_file)
+ YAML.load_file(redis_config_file)[Rails.env]
+ else
+ "redis://localhost:6379"
+ end
+ %>
+ -
+ :host: <%= config.host.to_json %>
+ :port: <%= config.port.to_json %>
+ :ssl: <%= config.ssl.to_json %>
+ :start_tls: <%= config.start_tls.to_json %>
+ :email: <%= config.user.to_json %>
+ :password: <%= config.password.to_json %>
+
+ :name: <%= config.mailbox.to_json %>
+
+ :delete_after_delivery: true
+
+ :delivery_method: sidekiq
+ :delivery_options:
+ :redis_url: <%= redis_url.to_json %>
+ :namespace: resque:gitlab
+ :queue: incoming_email
+ :worker: EmailReceiverWorker
+
+ :arbitration_method: redis
+ :arbitration_options:
+ :redis_url: <%= redis_url.to_json %>
+ :namespace: mail_room:gitlab
+<% end %>
diff --git a/config/mail_room.yml.example b/config/mail_room.yml.example
deleted file mode 100644
index ed4a5193a6a..00000000000
--- a/config/mail_room.yml.example
+++ /dev/null
@@ -1,29 +0,0 @@
-:mailboxes:
- -
- # # IMAP server host
- # :host: "imap.gmail.com"
- # # IMAP server port
- # :port: 993
- # # Whether the IMAP server uses SSL
- # :ssl: true
- # # Whether the IMAP server uses StartTLS
- # :start_tls: false
- # # Email account username. Usually the full email address.
- # :email: "gitlab-incoming@gmail.com"
- # # Email account password
- # :password: "password"
- # # The name of the mailbox where incoming mail will end up. Usually "inbox".
- # :name: "inbox"
- # # Always "sidekiq".
- # :delivery_method: sidekiq
- # # Always true.
- # :delete_after_delivery: true
- # :delivery_options:
- # # The URL to the Redis server used by Sidekiq. Should match the URL in config/resque.yml.
- # :redis_url: redis://localhost:6379
- # # Always "resque:gitlab".
- # :namespace: resque:gitlab
- # # Always "incoming_email".
- # :queue: incoming_email
- # # Always "EmailReceiverWorker"
- # :worker: EmailReceiverWorker
diff --git a/config/routes.rb b/config/routes.rb
index 8e6fbf6340c..f6812c9280a 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -378,6 +378,7 @@ Gitlab::Application.routes.draw do
[:new, :create, :index], path: "/") do
member do
put :transfer
+ delete :remove_fork
post :archive
post :unarchive
post :toggle_star
@@ -543,8 +544,10 @@ Gitlab::Application.routes.draw do
member do
# tree viewer logs
get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex }
+ # Directories with leading dots erroneously get rejected if git
+ # ref regex used in constraints. Regex verification now done in controller.
get 'logs_tree/*path' => 'refs#logs_tree', as: :logs_file, constraints: {
- id: Gitlab::Regex.git_reference_regex,
+ id: /.*/,
path: /.*/
}
end
@@ -585,7 +588,11 @@ Gitlab::Application.routes.draw do
end
end
- resources :builds, only: [:show] do
+ resources :builds, only: [:index, :show] do
+ collection do
+ get :cancel_all
+ end
+
member do
get :cancel
get :status
diff --git a/db/fixtures/development/05_users.rb b/db/fixtures/development/05_users.rb
index 378354efd5a..03da29c4c68 100644
--- a/db/fixtures/development/05_users.rb
+++ b/db/fixtures/development/05_users.rb
@@ -1,5 +1,5 @@
Gitlab::Seeder.quiet do
- (2..20).each do |i|
+ 20.times do |i|
begin
User.create!(
username: FFaker::Internet.user_name,
@@ -15,7 +15,7 @@ Gitlab::Seeder.quiet do
end
end
- (1..5).each do |i|
+ 5.times do |i|
begin
User.create!(
username: "user#{i}",
diff --git a/db/fixtures/development/07_milestones.rb b/db/fixtures/development/07_milestones.rb
index a43116829d9..e028ac82ba3 100644
--- a/db/fixtures/development/07_milestones.rb
+++ b/db/fixtures/development/07_milestones.rb
@@ -1,6 +1,6 @@
Gitlab::Seeder.quiet do
Project.all.each do |project|
- (1..5).each do |i|
+ 5.times do |i|
milestone_params = {
title: "v#{i}.0",
description: FFaker::Lorem.sentence,
diff --git a/db/fixtures/development/09_issues.rb b/db/fixtures/development/09_issues.rb
index c636e96381c..4fa572fca9b 100644
--- a/db/fixtures/development/09_issues.rb
+++ b/db/fixtures/development/09_issues.rb
@@ -1,6 +1,6 @@
Gitlab::Seeder.quiet do
Project.all.each do |project|
- (1..10).each do |i|
+ 10.times do
issue_params = {
title: FFaker::Lorem.sentence(6),
description: FFaker::Lorem.sentence,
diff --git a/db/fixtures/development/12_snippets.rb b/db/fixtures/development/12_snippets.rb
index 3bd4b442ade..74898544a69 100644
--- a/db/fixtures/development/12_snippets.rb
+++ b/db/fixtures/development/12_snippets.rb
@@ -22,7 +22,7 @@ class Member < ActiveRecord::Base
end
eos
- (1..50).each do |i|
+ 50.times do |i|
user = User.all.sample
PersonalSnippet.seed(:id, [{
diff --git a/db/migrate/20151008110232_add_users_lower_username_email_indexes.rb b/db/migrate/20151008110232_add_users_lower_username_email_indexes.rb
new file mode 100644
index 00000000000..2f2dc776785
--- /dev/null
+++ b/db/migrate/20151008110232_add_users_lower_username_email_indexes.rb
@@ -0,0 +1,17 @@
+class AddUsersLowerUsernameEmailIndexes < ActiveRecord::Migration
+ disable_ddl_transaction!
+
+ def up
+ return unless Gitlab::Database.postgresql?
+
+ execute 'CREATE INDEX CONCURRENTLY index_on_users_lower_username ON users (LOWER(username));'
+ execute 'CREATE INDEX CONCURRENTLY index_on_users_lower_email ON users (LOWER(email));'
+ end
+
+ def down
+ return unless Gitlab::Database.postgresql?
+
+ remove_index :users, :index_on_users_lower_username
+ remove_index :users, :index_on_users_lower_email
+ end
+end
diff --git a/db/migrate/20151008143519_add_admin_notification_email_setting.rb b/db/migrate/20151008143519_add_admin_notification_email_setting.rb
new file mode 100644
index 00000000000..0bb581efe2c
--- /dev/null
+++ b/db/migrate/20151008143519_add_admin_notification_email_setting.rb
@@ -0,0 +1,5 @@
+class AddAdminNotificationEmailSetting < ActiveRecord::Migration
+ def change
+ add_column :application_settings, :admin_notification_email, :string
+ end
+end
diff --git a/db/migrate/20151016131433_add_ci_projects_gl_project_id_index.rb b/db/migrate/20151016131433_add_ci_projects_gl_project_id_index.rb
new file mode 100644
index 00000000000..52a47aa9c54
--- /dev/null
+++ b/db/migrate/20151016131433_add_ci_projects_gl_project_id_index.rb
@@ -0,0 +1,5 @@
+class AddCiProjectsGlProjectIdIndex < ActiveRecord::Migration
+ def change
+ add_index :ci_commits, :gl_project_id
+ end
+end
diff --git a/db/migrate/20151016195451_add_ci_builds_and_projects_indexes.rb b/db/migrate/20151016195451_add_ci_builds_and_projects_indexes.rb
new file mode 100644
index 00000000000..7f1af1c7583
--- /dev/null
+++ b/db/migrate/20151016195451_add_ci_builds_and_projects_indexes.rb
@@ -0,0 +1,9 @@
+class AddCiBuildsAndProjectsIndexes < ActiveRecord::Migration
+ def change
+ add_index :ci_projects, :gitlab_id
+ add_index :ci_projects, :shared_runners_enabled
+
+ add_index :ci_builds, :type
+ add_index :ci_builds, :status
+ end
+end
diff --git a/db/migrate/20151016195706_add_notes_line_code_index.rb b/db/migrate/20151016195706_add_notes_line_code_index.rb
new file mode 100644
index 00000000000..aeeb1a759fa
--- /dev/null
+++ b/db/migrate/20151016195706_add_notes_line_code_index.rb
@@ -0,0 +1,5 @@
+class AddNotesLineCodeIndex < ActiveRecord::Migration
+ def change
+ add_index :notes, :line_code
+ end
+end
diff --git a/db/migrate/20151019111551_fix_build_tags.rb b/db/migrate/20151019111551_fix_build_tags.rb
new file mode 100644
index 00000000000..84b142183f8
--- /dev/null
+++ b/db/migrate/20151019111551_fix_build_tags.rb
@@ -0,0 +1,5 @@
+class FixBuildTags < ActiveRecord::Migration
+ def change
+ execute("UPDATE taggings SET taggable_type='CommitStatus' WHERE taggable_type='Ci::Build'")
+ end
+end
diff --git a/db/migrate/20151019111703_fail_build_without_names.rb b/db/migrate/20151019111703_fail_build_without_names.rb
new file mode 100644
index 00000000000..546b03d8129
--- /dev/null
+++ b/db/migrate/20151019111703_fail_build_without_names.rb
@@ -0,0 +1,5 @@
+class FailBuildWithoutNames < ActiveRecord::Migration
+ def change
+ execute("UPDATE ci_builds SET status='failed' WHERE name IS NULL AND status='pending'")
+ end
+end
diff --git a/db/migrate/20151020173516_ci_limits_to_mysql.rb b/db/migrate/20151020173516_ci_limits_to_mysql.rb
new file mode 100644
index 00000000000..9bb960082f5
--- /dev/null
+++ b/db/migrate/20151020173516_ci_limits_to_mysql.rb
@@ -0,0 +1,9 @@
+class CiLimitsToMysql < ActiveRecord::Migration
+ def change
+ return unless ActiveRecord::Base.configurations[Rails.env]['adapter'] =~ /^mysql/
+
+ # CI
+ change_column :ci_builds, :trace, :text, limit: 1073741823
+ change_column :ci_commits, :push_data, :text, limit: 16777215
+ end
+end
diff --git a/db/migrate/20151020173906_add_ci_builds_index_for_status.rb b/db/migrate/20151020173906_add_ci_builds_index_for_status.rb
new file mode 100644
index 00000000000..c3f0e0606da
--- /dev/null
+++ b/db/migrate/20151020173906_add_ci_builds_index_for_status.rb
@@ -0,0 +1,5 @@
+class AddCiBuildsIndexForStatus < ActiveRecord::Migration
+ def change
+ add_index :ci_builds, [:commit_id, :status, :type]
+ end
+end
diff --git a/db/migrate/limits_to_mysql.rb b/db/migrate/limits_to_mysql.rb
index 73605d4c5e3..2b7afae6d7c 100644
--- a/db/migrate/limits_to_mysql.rb
+++ b/db/migrate/limits_to_mysql.rb
@@ -6,9 +6,5 @@ class LimitsToMysql < ActiveRecord::Migration
change_column :merge_request_diffs, :st_diffs, :text, limit: 2147483647
change_column :snippets, :content, :text, limit: 2147483647
change_column :notes, :st_diff, :text, limit: 2147483647
-
- # CI
- change_column :ci_builds, :trace, :text, limit: 1073741823
- change_column :ci_commits, :push_data, :text, limit: 16777215
end
end
diff --git a/db/schema.rb b/db/schema.rb
index 7a11dfca034..0fec00ebf8f 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20151008130321) do
+ActiveRecord::Schema.define(version: 20151020173906) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -46,6 +46,7 @@ ActiveRecord::Schema.define(version: 20151008130321) do
t.integer "session_expire_delay", default: 10080, null: false
t.text "import_sources"
t.text "help_page_text"
+ t.string "admin_notification_email"
end
create_table "audit_events", force: true do |t|
@@ -109,12 +110,15 @@ ActiveRecord::Schema.define(version: 20151008130321) do
end
add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree
+ add_index "ci_builds", ["commit_id", "status", "type"], name: "index_ci_builds_on_commit_id_and_status_and_type", using: :btree
add_index "ci_builds", ["commit_id", "type", "name", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_name_and_ref", using: :btree
add_index "ci_builds", ["commit_id", "type", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_ref", using: :btree
add_index "ci_builds", ["commit_id"], name: "index_ci_builds_on_commit_id", using: :btree
add_index "ci_builds", ["project_id", "commit_id"], name: "index_ci_builds_on_project_id_and_commit_id", using: :btree
add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree
add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree
+ 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|
t.integer "project_id"
@@ -130,6 +134,7 @@ ActiveRecord::Schema.define(version: 20151008130321) do
t.integer "gl_project_id"
end
+ add_index "ci_commits", ["gl_project_id"], name: "index_ci_commits_on_gl_project_id", using: :btree
add_index "ci_commits", ["project_id", "committed_at", "id"], name: "index_ci_commits_on_project_id_and_committed_at_and_id", using: :btree
add_index "ci_commits", ["project_id", "committed_at"], name: "index_ci_commits_on_project_id_and_committed_at", using: :btree
add_index "ci_commits", ["project_id", "sha"], name: "index_ci_commits_on_project_id_and_sha", using: :btree
@@ -189,6 +194,9 @@ ActiveRecord::Schema.define(version: 20151008130321) do
t.text "generated_yaml_config"
end
+ 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|
t.integer "runner_id", null: false
t.integer "project_id", null: false
@@ -529,6 +537,7 @@ ActiveRecord::Schema.define(version: 20151008130321) do
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", ["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
add_index "notes", ["project_id", "noteable_type"], name: "index_notes_on_project_id_and_noteable_type", using: :btree
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index 191e3a8144d..ef8a7ec1e86 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -90,7 +90,7 @@ you need to set MYSQL_ALLOW_EMPTY_PASSWORD.
- mysql
variables:
- MYSQL_ALLOW_EMPTY_PASSWORD: yes
+ MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
```
For other possible configuration variables check the
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 04c6bf1e3a3..022afb70042 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -15,21 +15,27 @@ The API_TOKEN will take the Secure Variable value: `SECURE`.
### Predefined variables (Environment Variables)
-| Variable | Description |
+| Variable | Runner | Description |
|-------------------------|-------------|
-| **CI** | Mark that build is executed in CI environment |
-| **GITLAB_CI** | Mark that build is executed in GitLab CI environment |
-| **CI_SERVER** | Mark that build is executed in CI environment |
-| **CI_SERVER_NAME** | CI server that is used to coordinate builds |
-| **CI_SERVER_VERSION** | Not yet defined |
-| **CI_SERVER_REVISION** | Not yet defined |
-| **CI_BUILD_REF** | The commit revision for which project is built |
-| **CI_BUILD_BEFORE_SHA** | The first commit that were included in push request |
-| **CI_BUILD_REF_NAME** | The branch or tag name for which project is built |
-| **CI_BUILD_ID** | The unique id of the current build that GitLab CI uses internally |
-| **CI_BUILD_REPO** | The URL to clone the Git repository |
-| **CI_PROJECT_ID** | The unique id of the current project that GitLab CI uses internally |
-| **CI_PROJECT_DIR** | The full path where the repository is cloned and where the build is ran |
+| **CI** | 0.4 | Mark that build is executed in CI environment |
+| **GITLAB_CI** | all | Mark that build is executed in GitLab CI environment |
+| **CI_SERVER** | all | Mark that build is executed in CI environment |
+| **CI_SERVER_NAME** | all | CI server that is used to coordinate builds |
+| **CI_SERVER_VERSION** | all | Not yet defined |
+| **CI_SERVER_REVISION** | all | Not yet defined |
+| **CI_BUILD_REF** | all | The commit revision for which project is built |
+| **CI_BUILD_TAG** | 0.5 | The commit tag name. Present only when building tags. |
+| **CI_BUILD_NAME** | 0.5 | The name of the build as defined in `.gitlab-ci.yml` |
+| **CI_BUILD_STAGE** | 0.5 | The name of the stage as defined in `.gitlab-ci.yml` |
+| **CI_BUILD_BEFORE_SHA** | all | The first commit that were included in push request |
+| **CI_BUILD_REF_NAME** | all | The branch or tag name for which project is built |
+| **CI_BUILD_ID** | all | The unique id of the current build that GitLab CI uses internally |
+| **CI_BUILD_REPO** | all | The URL to clone the Git repository |
+| **CI_BUILD_TRIGGERED** | 0.5 | The flag to indicate that build was triggered |
+| **CI_PROJECT_ID** | all | The unique id of the current project that GitLab CI uses internally |
+| **CI_PROJECT_DIR** | all | The full path where the repository is cloned and where the build is ran |
+
+**Some of the variables are only available when using runner with at least defined version.**
Example values:
@@ -39,6 +45,10 @@ export CI_BUILD_ID="50"
export CI_BUILD_REF="1ecfd275763eff1d6b4844ea3168962458c9f27a"
export CI_BUILD_REF_NAME="master"
export CI_BUILD_REPO="https://gitlab.com/gitlab-org/gitlab-ce.git"
+export CI_BUILD_TAG="1.0.0"
+export CI_BUILD_NAME="spec:other"
+export CI_BUILD_STAGE="test"
+export CI_BUILD_TRIGGERED="true"
export CI_PROJECT_DIR="/builds/gitlab-org/gitlab-ce"
export CI_PROJECT_ID="34"
export CI_SERVER="yes"
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 4caeccacb7f..ea8f72bc135 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -140,6 +140,7 @@ job_name:
| except | optional | Defines a list of git refs for which build is not created |
| tags | optional | Defines a list of tags which are used to select runner |
| allow_failure | optional | Allow build to fail. Failed build doesn't contribute to commit status |
+| when | optional | Define when to run build. Can be `on_success`, `on_failure` or `always` |
### script
`script` is a shell script which is executed by runner. The shell script is prepended with `before_script`.
@@ -196,9 +197,58 @@ job:
The above specification will make sure that `job` is built by a runner that have `ruby` AND `postgres` tags defined.
+### when
+`when` is used to implement jobs that are run in case of failure or despite the failure.
+
+`when` can be set to one of the following values:
+
+1. `on_success` - execute build only when all builds from prior stages succeeded. This is the default.
+1. `on_failure` - execute build only when at least one build from prior stages failed.
+1. `always` - execute build despite the status of builds from prior stages.
+
+```
+stages:
+- build
+- cleanup_build
+- test
+- deploy
+- cleanup
+
+build:
+ stage: build
+ script:
+ - make build
+
+cleanup_build:
+ stage: cleanup_build
+ script:
+ - cleanup build when failed
+ when: on_failure
+
+test:
+ stage: test
+ script:
+ - make test
+
+deploy:
+ stage: deploy
+ script:
+ - make deploy
+
+cleanup:
+ stage: cleanup
+ script:
+ - cleanup after builds
+ when: always
+```
+
+The above script will:
+1. Execute `cleanup_build` only when the `build` failed,
+2. Always execute `cleanup` as the last step in pipeline.
+
## Validate the .gitlab-ci.yml
Each instance of GitLab CI has an embedded debug tool called Lint.
You can find the link to the Lint in the project's settings page or use short url `/lint`.
## Skipping builds
-There is one more way to skip all builds, if your commit message contains tag [ci skip]. In this case, commit will be created but builds will be skipped \ No newline at end of file
+There is one more way to skip all builds, if your commit message contains tag [ci skip]. In this case, commit will be created but builds will be skipped
diff --git a/doc/development/profiling.md b/doc/development/profiling.md
new file mode 100644
index 00000000000..80c86ef921e
--- /dev/null
+++ b/doc/development/profiling.md
@@ -0,0 +1,56 @@
+# Profiling
+
+To make it easier to track down performance problems GitLab comes with a set of
+profiling tools, some of these are available by default while others need to be
+explicitly enabled.
+
+## rack-mini-profiler
+
+This Gem is enabled by default in development only. It allows you to see the
+timings of the various components that made up a web request (e.g. the SQL
+queries executed and their execution timings).
+
+## Bullet
+
+Bullet is a Gem that can be used to track down N+1 query problems. Because
+Bullet adds quite a bit of logging noise it's disabled by default. To enable
+Bullet, set the environment variable `ENABLE_BULLET` to a non-empty value before
+starting GitLab. For example:
+
+ ENABLE_BULLET=true bundle exec rails s
+
+Bullet will log query problems to both the Rails log as well as the Chrome
+console.
+
+## ActiveRecord Query Trace
+
+This Gem adds backtraces for every ActiveRecord query in the Rails console. This
+can be useful to track down where a query was executed. Because this Gem adds
+quite a bit of noise (5-10 extra lines per ActiveRecord query) it's disabled by
+default. To use this Gem you'll need to set `ENABLE_QUERY_TRACE` to a non empty
+file before starting GitLab. For example:
+
+ ENABLE_QUERY_TRACE=true bundle exec rails s
+
+## rack-lineprof
+
+This is a Gem that can trace the execution time of code on a per line basis.
+Because this Gem can add quite a bit of overhead it's disabled by default. To
+enable it, set the environment variable `ENABLE_LINEPROF` to a non-empty value.
+For example:
+
+ ENABLE_LINEPROF=true bundle exec rails s
+
+Once enabled you'll need to add a query string parameter to a request to
+actually profile code execution. The name of the parameter is `lineprof` and
+should be set to a regular expression (minus the starting/ending slash) used to
+select what files to profile. To profile all files containing "foo" somewhere in
+the path you'd use the following parameter:
+
+ ?lineprof=foo
+
+Or when filtering for files containing "foo" and "bar" in their path:
+
+ ?lineprof=foo|bar
+
+Once set the profiling output will be displayed in your terminal.
diff --git a/doc/incoming_email/README.md b/doc/incoming_email/README.md
index 87267423471..86d205ba7a5 100644
--- a/doc/incoming_email/README.md
+++ b/doc/incoming_email/README.md
@@ -2,15 +2,11 @@
GitLab can be set up to allow users to comment on issues and merge requests by replying to notification emails.
-**Warning**: Do not enable Reply by email if you have **multiple GitLab application servers**.
-Due to an issue with the way incoming emails are read from the mail server, every incoming reply-by-email email will result in as many comments being created as you have application servers.
-[A fix is being worked on.](https://github.com/tpitale/mail_room/issues/46)
-
## Get a mailbox
-Reply by email requires an IMAP-enabled email account, with a provider or server that supports [email sub-addressing](https://en.wikipedia.org/wiki/Email_address#Sub-addressing). Sub-addressing is a feature where any email to `user+some_arbitrary_tag@example.com` will end up in the mailbox for `user@example.com`, and is supported by providers such as Gmail, Yahoo! Mail, Outlook.com and iCloud, as well as the Postfix mail server which you can run on-premises.
+Reply by email requires an IMAP-enabled email account, with a provider or server that supports [email sub-addressing](https://en.wikipedia.org/wiki/Email_address#Sub-addressing). Sub-addressing is a feature where any email to `user+some_arbitrary_tag@example.com` will end up in the mailbox for `user@example.com`, and is supported by providers such as Gmail, Google Apps, Yahoo! Mail, Outlook.com and iCloud, as well as the Postfix mail server which you can run on-premises.
-If you want to use Gmail with Reply by email, make sure you have [IMAP access enabled](https://support.google.com/mail/troubleshooter/1668960?hl=en#ts=1665018) and [allow less secure apps to access the account](https://support.google.com/accounts/answer/6010255).
+If you want to use Gmail / Google Apps with Reply by email, make sure you have [IMAP access enabled](https://support.google.com/mail/troubleshooter/1668960?hl=en#ts=1665018) and [allow less secure apps to access the account](https://support.google.com/accounts/answer/6010255).
To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these instructions](./postfix.md).
@@ -18,30 +14,62 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these
### Omnibus package installations
-1. Find the `incoming_email` section in `/etc/gitlab/gitlab.rb`, enable the feature, enter the email address including a placeholder for the `key` that references the item being replied to and fill in the details for your specific IMAP server and email account:
+1. Find the `incoming_email` section in `/etc/gitlab/gitlab.rb`, enable the feature and fill in the details for your specific IMAP server and email account:
```ruby
- # Postfix mail server, assumes mailbox incoming@gitlab.example.com
+ # Configuration for Postfix mail server, assumes mailbox incoming@gitlab.example.com
gitlab_rails['incoming_email_enabled'] = true
+
+ # The email address including a placeholder for the key that references the item being replied to.
+ # The `%{key}` placeholder is added after the user part, before the `@`.
gitlab_rails['incoming_email_address'] = "incoming+%{key}@gitlab.example.com"
- gitlab_rails['incoming_email_host'] = "gitlab.example.com" # IMAP server host
- gitlab_rails['incoming_email_port'] = 143 # IMAP server port
- gitlab_rails['incoming_email_ssl'] = false # Whether the IMAP server uses SSL
- gitlab_rails['incoming_email_email'] = "incoming" # Email account username. Usually the full email address.
- gitlab_rails['incoming_email_password'] = "[REDACTED]" # Email account password
- gitlab_rails['incoming_email_mailbox_name'] = "inbox" # The name of the mailbox where incoming mail will end up. Usually "inbox".
+
+ # Email account username
+ # With third party providers, this is usually the full email address.
+ # With self-hosted email servers, this is usually the user part of the email address.
+ gitlab_rails['incoming_email_email'] = "incoming"
+ # Email account password
+ gitlab_rails['incoming_email_password'] = "[REDACTED]"
+
+ # IMAP server host
+ gitlab_rails['incoming_email_host'] = "gitlab.example.com"
+ # IMAP server port
+ gitlab_rails['incoming_email_port'] = 143
+ # Whether the IMAP server uses SSL
+ gitlab_rails['incoming_email_ssl'] = false
+ # Whether the IMAP server uses StartTLS
+ gitlab_rails['incoming_email_start_tls'] = false
+
+ # The mailbox where incoming mail will end up. Usually "inbox".
+ gitlab_rails['incoming_email_mailbox_name'] = "inbox"
```
```ruby
- # Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com
+ # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com
gitlab_rails['incoming_email_enabled'] = true
+
+ # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
+ # The `%{key}` placeholder is added after the user part, after a `+` character, before the `@`.
gitlab_rails['incoming_email_address'] = "gitlab-incoming+%{key}@gmail.com"
- gitlab_rails['incoming_email_host'] = "imap.gmail.com" # IMAP server host
- gitlab_rails['incoming_email_port'] = 993 # IMAP server port
- gitlab_rails['incoming_email_ssl'] = true # Whether the IMAP server uses SSL
- gitlab_rails['incoming_email_email'] = "gitlab-incoming@gmail.com" # Email account username. Usually the full email address.
- gitlab_rails['incoming_email_password'] = "[REDACTED]" # Email account password
- gitlab_rails['incoming_email_mailbox_name'] = "inbox" # The name of the mailbox where incoming mail will end up. Usually "inbox".
+
+ # Email account username
+ # With third party providers, this is usually the full email address.
+ # With self-hosted email servers, this is usually the user part of the email address.
+ gitlab_rails['incoming_email_email'] = "gitlab-incoming@gmail.com"
+ # Email account password
+ gitlab_rails['incoming_email_password'] = "[REDACTED]"
+
+ # IMAP server host
+ gitlab_rails['incoming_email_host'] = "imap.gmail.com"
+ # IMAP server port
+ gitlab_rails['incoming_email_port'] = 993
+ # Whether the IMAP server uses SSL
+ gitlab_rails['incoming_email_ssl'] = true
+ # Whether the IMAP server uses StartTLS
+ gitlab_rails['incoming_email_start_tls'] = false
+
+ # The mailbox where incoming mail will end up. Usually "inbox".
+ gitlab_rails['incoming_email_mailbox_name'] = "inbox"
```
As mentioned, the part after `+` in the address is ignored, and any email sent here will end up in the mailbox for `incoming@gitlab.example.com`/`gitlab-incoming@gmail.com`.
@@ -68,199 +96,146 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these
cd /home/git/gitlab
```
-1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature and enter the email address including a placeholder for the `key` that references the item being replied to:
+1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature and fill in the details for your specific IMAP server and email account:
```sh
sudo editor config/gitlab.yml
```
```yaml
- # Postfix mail server, assumes mailbox incoming@gitlab.example.com
+ # Configuration for Postfix mail server, assumes mailbox incoming@gitlab.example.com
incoming_email:
enabled: true
+
+ # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
+ # The `%{key}` placeholder is added after the user part, after a `+` character, before the `@`.
address: "incoming+%{key}@gitlab.example.com"
+
+ # Email account username
+ # With third party providers, this is usually the full email address.
+ # With self-hosted email servers, this is usually the user part of the email address.
+ user: "incoming"
+ # Email account password
+ password: "[REDACTED]"
+
+ # IMAP server host
+ host: "gitlab.example.com"
+ # IMAP server port
+ port: 143
+ # Whether the IMAP server uses SSL
+ ssl: false
+ # Whether the IMAP server uses StartTLS
+ start_tls: false
+
+ # The mailbox where incoming mail will end up. Usually "inbox".
+ mailbox: "inbox"
```
```yaml
- # Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com
+ # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com
incoming_email:
enabled: true
- address: "gitlab-incoming+%{key}@gmail.com"
- ```
-
- As mentioned, the part after `+` in the address is ignored, and any email sent here will end up in the mailbox for `incoming@gitlab.example.com`/`gitlab-incoming@gmail.com`.
-
-2. Copy `config/mail_room.yml.example` to `config/mail_room.yml`:
- ```sh
- sudo cp config/mail_room.yml.example config/mail_room.yml
- ```
+ # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
+ # The `%{key}` placeholder is added after the user part, after a `+` character, before the `@`.
+ address: "gitlab-incoming+%{key}@gmail.com"
-3. Uncomment the configuration options in `config/mail_room.yml` and fill in the details for your specific IMAP server and email account:
+ # Email account username
+ # With third party providers, this is usually the full email address.
+ # With self-hosted email servers, this is usually the user part of the email address.
+ user: "gitlab-incoming@gmail.com"
+ # Email account password
+ password: "[REDACTED]"
- ```sh
- sudo editor config/mail_room.yml
- ```
+ # IMAP server host
+ host: "imap.gmail.com"
+ # IMAP server port
+ port: 993
+ # Whether the IMAP server uses SSL
+ ssl: true
+ # Whether the IMAP server uses StartTLS
+ start_tls: false
- ```yaml
- # Postfix mail server
- :mailboxes:
- -
- # IMAP server host
- :host: "gitlab.example.com"
- # IMAP server port
- :port: 143
- # Whether the IMAP server uses SSL
- :ssl: false
- # Whether the IMAP server uses StartTLS
- :start_tls: false
- # Email account username. Usually the full email address.
- :email: "incoming"
- # Email account password
- :password: "[REDACTED]"
- # The name of the mailbox where incoming mail will end up. Usually "inbox".
- :name: "inbox"
- # Always "sidekiq".
- :delivery_method: sidekiq
- # Always true.
- :delete_after_delivery: true
- :delivery_options:
- # The URL to the Redis server used by Sidekiq. Should match the URL in config/resque.yml.
- :redis_url: redis://localhost:6379
- # Always "resque:gitlab".
- :namespace: resque:gitlab
- # Always "incoming_email".
- :queue: incoming_email
- # Always "EmailReceiverWorker"
- :worker: EmailReceiverWorker
+ # The mailbox where incoming mail will end up. Usually "inbox".
+ mailbox: "inbox"
```
- ```yaml
- # Gmail / Google Apps
- :mailboxes:
- -
- # IMAP server host
- :host: "imap.gmail.com"
- # IMAP server port
- :port: 993
- # Whether the IMAP server uses SSL
- :ssl: true
- # Whether the IMAP server uses StartTLS
- :start_tls: false
- # Email account username. Usually the full email address.
- :email: "gitlab-incoming@gmail.com"
- # Email account password
- :password: "[REDACTED]"
- # The name of the mailbox where incoming mail will end up. Usually "inbox".
- :name: "inbox"
- # Always "sidekiq".
- :delivery_method: sidekiq
- # Always true.
- :delete_after_delivery: true
- :delivery_options:
- # The URL to the Redis server used by Sidekiq. Should match the URL in config/resque.yml.
- :redis_url: redis://localhost:6379
- # Always "resque:gitlab".
- :namespace: resque:gitlab
- # Always "incoming_email".
- :queue: incoming_email
- # Always "EmailReceiverWorker"
- :worker: EmailReceiverWorker
- ```
+ As mentioned, the part after `+` in the address is ignored, and any email sent here will end up in the mailbox for `incoming@gitlab.example.com`/`gitlab-incoming@gmail.com`.
-5. Edit the init script configuration at `/etc/default/gitlab` to enable `mail_room`:
+1. Enable `mail_room` in the init script at `/etc/default/gitlab`:
```sh
sudo mkdir -p /etc/default
echo 'mail_room_enabled=true' | sudo tee -a /etc/default/gitlab
```
-6. Restart GitLab:
+1. Restart GitLab:
```sh
sudo service gitlab restart
```
-7. Verify that everything is configured correctly:
+1. Verify that everything is configured correctly:
```sh
sudo -u git -H bundle exec rake gitlab:incoming_email:check RAILS_ENV=production
```
-8. Reply by email should now be working.
+1. Reply by email should now be working.
### Development
1. Go to the GitLab installation directory.
-1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature and enter the email address including a placeholder for the `key` that references the item being replied to:
+1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature and fill in the details for your specific IMAP server and email account:
```yaml
- # Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com
+ # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com
incoming_email:
enabled: true
+
+ # The email address including a placeholder for the key that references the item being replied to.
+ # The `%{key}` placeholder is added after the user part, before the `@`.
address: "gitlab-incoming+%{key}@gmail.com"
- ```
- As mentioned, the part after `+` is ignored, and this will end up in the mailbox for `gitlab-incoming@gmail.com`.
+ # Email account username
+ # With third party providers, this is usually the full email address.
+ # With self-hosted email servers, this is usually the user part of the email address.
+ user: "gitlab-incoming@gmail.com"
+ # Email account password
+ password: "[REDACTED]"
-2. Copy `config/mail_room.yml.example` to `config/mail_room.yml`:
+ # IMAP server host
+ host: "imap.gmail.com"
+ # IMAP server port
+ port: 993
+ # Whether the IMAP server uses SSL
+ ssl: true
+ # Whether the IMAP server uses StartTLS
+ start_tls: false
- ```sh
- sudo cp config/mail_room.yml.example config/mail_room.yml
+ # The mailbox where incoming mail will end up. Usually "inbox".
+ mailbox: "inbox"
```
-3. Uncomment the configuration options in `config/mail_room.yml` and fill in the details for your specific IMAP server and email account:
-
- ```yaml
- # Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com
- :mailboxes:
- -
- # IMAP server host
- :host: "imap.gmail.com"
- # IMAP server port
- :port: 993
- # Whether the IMAP server uses SSL
- :ssl: true
- # Whether the IMAP server uses StartTLS
- :start_tls: false
- # Email account username. Usually the full email address.
- :email: "gitlab-incoming@gmail.com"
- # Email account password
- :password: "[REDACTED]"
- # The name of the mailbox where incoming mail will end up. Usually "inbox".
- :name: "inbox"
- # Always "sidekiq".
- :delivery_method: sidekiq
- # Always true.
- :delete_after_delivery: true
- :delivery_options:
- # The URL to the Redis server used by Sidekiq. Should match the URL in config/resque.yml.
- :redis_url: redis://localhost:6379
- # Always "resque:gitlab".
- :namespace: resque:gitlab
- # Always "incoming_email".
- :queue: incoming_email
- # Always "EmailReceiverWorker"
- :worker: EmailReceiverWorker
- ```
+ As mentioned, the part after `+` is ignored, and this will end up in the mailbox for `gitlab-incoming@gmail.com`.
-4. Uncomment the `mail_room` line in your `Procfile`:
+1. Uncomment the `mail_room` line in your `Procfile`:
```yaml
mail_room: bundle exec mail_room -q -c config/mail_room.yml
```
-6. Restart GitLab:
+1. Restart GitLab:
```sh
bundle exec foreman start
```
-7. Verify that everything is configured correctly:
+1. Verify that everything is configured correctly:
```sh
bundle exec rake gitlab:incoming_email:check RAILS_ENV=development
```
-8. Reply by email should now be working.
+1. Reply by email should now be working.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 3c62b11988e..2e9ac7393e3 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -211,9 +211,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
### Clone the Source
# Clone GitLab repository
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-0-stable gitlab
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-1-stable gitlab
-**Note:** You can change `8-0-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+**Note:** You can change `8-1-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It
@@ -325,6 +325,7 @@ GitLab Shell is an SSH access and repository management software developed speci
cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-git-http-server.git
cd gitlab-git-http-server
+ sudo -u git -H git checkout 0.3.0
sudo -u git -H make
### Initialize Database and Activate Advanced Features
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index db3f6bb40bd..06f582dcee8 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -16,7 +16,7 @@ You need to keep a separate copy of `/etc/gitlab/gitlab-secrets.json`
from source). This file contains the database encryption key used
for two-factor authentication. If you restore a GitLab backup without
restoring the database encryption key, users who have two-factor
-authentication enabled will loose access to your GitLab server.
+authentication enabled will lose access to your GitLab server.
If you are interested in GitLab CI backup please follow to the [CI backup documentation](https://gitlab.com/gitlab-org/gitlab-ci/blob/master/doc/raketasks/backup_restore.md)*
diff --git a/doc/update/7.14-to-8.0.md b/doc/update/7.14-to-8.0.md
index 7ad4935e839..305017b7048 100644
--- a/doc/update/7.14-to-8.0.md
+++ b/doc/update/7.14-to-8.0.md
@@ -84,6 +84,7 @@ Now we download `gitlab-git-http-server` and install it in `/home/git/gitlab-git
cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-git-http-server.git
cd gitlab-git-http-server
+sudo -u git -H git checkout 0.2.14
sudo -u git -H make
```
diff --git a/doc/update/8.0-to-8.1.md b/doc/update/8.0-to-8.1.md
new file mode 100644
index 00000000000..d57c0d0674d
--- /dev/null
+++ b/doc/update/8.0-to-8.1.md
@@ -0,0 +1,171 @@
+# From 8.0 to 8.1
+
+**NOTE:** GitLab 8.0 introduced several significant changes related to
+installation and configuration which *are not duplicated here*. Be sure you're
+already running a working version of 8.0 before proceeding with this guide.
+
+### 0. Double-check your Git version
+
+**This notice applies only to /usr/local/bin/git**
+
+If you compiled Git from source on your GitLab server then please double-check
+that you are using a version that protects against CVE-2014-9390. For six
+months after this vulnerability became known the GitLab installation guide
+still contained instructions that would install the outdated, 'vulnerable' Git
+version 2.1.2.
+
+Run the following command to get your current Git version:
+
+```sh
+/usr/local/bin/git --version
+```
+
+If you see 'No such file or directory' then you did not install Git according
+to the outdated instructions from the GitLab installation guide and you can go
+to the next step 'Stop server' below.
+
+If you see a version string then it should be v1.8.5.6, v1.9.5, v2.0.5, v2.1.4,
+v2.2.1 or newer. You can use the [instructions in the GitLab source
+installation
+guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies)
+to install a newer version of Git.
+
+### 1. Stop server
+
+ sudo service gitlab stop
+
+### 2. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 3. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 8-1-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 8-1-stable-ee
+```
+
+### 4. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v2.6.5
+```
+
+### 5. Update gitlab-git-http-server
+
+```bash
+cd /home/git/gitlab-git-http-server
+sudo -u git -H git fetch origin
+sudo -u git -H git checkout 0.3.0
+sudo -u git -H make
+```
+
+### 6. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without postgres')
+sudo -u git -H bundle install --without postgres development test --deployment
+
+# PostgreSQL installations (note: the line below states '--without mysql')
+sudo -u git -H bundle install --without mysql development test --deployment
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Update init.d script
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+### 7. Update configuration files
+
+#### New configuration options for `gitlab.yml`
+
+There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
+
+```sh
+git diff origin/8-0-stable:config/gitlab.yml.example origin/8-1-stable:config/gitlab.yml.example
+```
+
+#### Nginx configuration
+
+View changes between the previous recommended Nginx configuration and the
+current one:
+
+```sh
+# For HTTPS configurations
+git diff origin/8-0-stable:lib/support/nginx/gitlab-ssl origin/8-1-stable:lib/support/nginx/gitlab-ssl
+
+# For HTTP configurations
+git diff origin/8-0-stable:lib/support/nginx/gitlab origin/8-1-stable:lib/support/nginx/gitlab
+```
+
+If you are using Apache instead of NGINX please see the updated [Apache templates].
+Also note that because Apache does not support upstreams behind Unix sockets you
+will need to let gitlab-git-http-server listen on a TCP port. You can do this
+via [/etc/default/gitlab].
+
+[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
+[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-1-stable/lib/support/init.d/gitlab.default.example#L34
+
+### 8. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 9. 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
+
+To make sure you didn't miss anything run a more thorough check:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations, the upgrade is complete!
+
+## Things went south? Revert to previous version (8.0)
+
+### 1. Revert the code to the previous version
+
+Follow the [upgrade guide from 7.14 to 8.0](7.14-to-8.0.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+
+If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above.
+
+## Troubleshooting
+
+### "You appear to have cloned an empty repository."
+
+See the [7.14 to 8.0 update guide](7.14-to-8.0.md#troubleshooting).
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index da719229ab6..593722eb01f 100644
--- a/doc/update/patch_versions.md
+++ b/doc/update/patch_versions.md
@@ -49,9 +49,7 @@ sudo -u git -H bundle install --without development test mysql --deployment
sudo -u git -H bundle install --without development test postgres --deployment
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
-sudo -u git -H bundle exec rake assets:clean RAILS_ENV=production
-sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
-sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
```
### 5. Start application
diff --git a/features/dashboard/new_project.feature b/features/dashboard/new_project.feature
index bbd82a85e3a..76392068357 100644
--- a/features/dashboard/new_project.feature
+++ b/features/dashboard/new_project.feature
@@ -7,24 +7,24 @@ Background:
And I click "New project" link
@javascript
- Scenario: I should see New projects page
- Then I see "New project" page
+ Scenario: I should see New Projects page
+ Then I see "New Project" page
Then I see all possible import optios
@javascript
Scenario: I should see instructions on how to import from Git URL
- Given I see "New project" page
+ Given I see "New Project" page
When I click on "Any repo by URL"
Then I see instructions on how to import from Git URL
@javascript
Scenario: I should see instructions on how to import from GitHub
- Given I see "New project" page
+ Given I see "New Project" page
When I click on "Import project from GitHub"
Then I see instructions on how to import from GitHub
@javascript
Scenario: I should see Google Code import page
- Given I see "New project" page
+ Given I see "New Project" page
When I click on "Google Code"
Then I redirected to Google Code import page
diff --git a/features/project/project.feature b/features/project/project.feature
index b3fb0794547..1a53945eb04 100644
--- a/features/project/project.feature
+++ b/features/project/project.feature
@@ -31,6 +31,12 @@ Feature: Project
And I visit project "Shop" page
Then I should see project "Shop" README
+ Scenario: I should see last commit with CI
+ Given project "Shop" has CI enabled
+ Given project "Shop" has CI build
+ And I visit project "Shop" page
+ And I should see last commit with CI status
+
@javascript
Scenario: I should see project activity
When I visit project "Shop" activity page
diff --git a/features/project/snippets.feature b/features/project/snippets.feature
index 77e42a1a38b..270557cbde7 100644
--- a/features/project/snippets.feature
+++ b/features/project/snippets.feature
@@ -30,5 +30,5 @@ Feature: Project Snippets
Scenario: I destroy "Snippet one"
Given I visit snippet page "Snippet one"
- And I click link "Remove Snippet"
+ And I click link "Delete"
Then I should not see "Snippet one" in snippets
diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature
index 377c5e1a9a7..6b0484b6a38 100644
--- a/features/project/source/browse_files.feature
+++ b/features/project/source/browse_files.feature
@@ -205,3 +205,9 @@ Feature: Project Source Browse Files
And I see the ref 'test' has been selected
And I visit the 'test' tree
Then I see the commit data
+
+ @javascript
+ Scenario: I browse code with a leading dot in the directory
+ Given I switch ref to fix
+ And I visit the fix tree
+ Then I see the commit data for a directory with a leading dot
diff --git a/features/snippets/snippets.feature b/features/snippets/snippets.feature
index 4f617b6bed8..e15d7c79342 100644
--- a/features/snippets/snippets.feature
+++ b/features/snippets/snippets.feature
@@ -24,7 +24,7 @@ Feature: Snippets
Scenario: I destroy "Personal snippet one"
Given I visit snippet page "Personal snippet one"
- And I click link "Destroy"
+ And I click link "Delete"
Then I should not see "Personal snippet one" in snippets
Scenario: I create new internal snippet
diff --git a/features/steps/abuse_reports.rb b/features/steps/abuse_reports.rb
index 56652ff6f05..499accb0b08 100644
--- a/features/steps/abuse_reports.rb
+++ b/features/steps/abuse_reports.rb
@@ -23,7 +23,7 @@ class Spinach::Features::AbuseReports < Spinach::FeatureSteps
end
step 'I should see a red "Report abuse" button' do
- expect(find(:css, '.report_abuse')).to have_selector(:css, 'span.btn-close')
+ expect(page).to have_button("Already reported for abuse")
end
def user_mike
diff --git a/features/steps/admin/projects.rb b/features/steps/admin/projects.rb
index 17233f89f38..5a1cc9aa151 100644
--- a/features/steps/admin/projects.rb
+++ b/features/steps/admin/projects.rb
@@ -41,6 +41,8 @@ class Spinach::Features::AdminProjects < Spinach::FeatureSteps
end
step 'I transfer project to group \'Web\'' do
+ allow_any_instance_of(Projects::TransferService).
+ to receive(:move_uploads_to_new_namespace).and_return(true)
find(:xpath, "//input[@id='new_namespace_id']").set group.id
click_button 'Transfer'
end
diff --git a/features/steps/dashboard/new_project.rb b/features/steps/dashboard/new_project.rb
index 1e09162a5b5..44a4aa9844a 100644
--- a/features/steps/dashboard/new_project.rb
+++ b/features/steps/dashboard/new_project.rb
@@ -3,13 +3,13 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps
include SharedPaths
include SharedProject
- step 'I click "New project" link' do
+ step 'I click "New Project" link' do
page.within('.content') do
- click_link "New project"
+ click_link "New Project"
end
end
- step 'I see "New project" page' do
+ step 'I see "New Project" page' do
expect(page).to have_content('Project path')
end
diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb
index a3cb83880e3..e5b3f27135d 100644
--- a/features/steps/project/commits/commits.rb
+++ b/features/steps/project/commits/commits.rb
@@ -113,7 +113,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
end
step 'I click status link' do
- click_link "Builds"
+ find('.commit-ci-menu').click_link "Builds"
end
step 'I see builds list' do
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index 15f77734cb2..d76891d5bde 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -86,13 +86,13 @@ class Spinach::Features::Project < Spinach::FeatureSteps
end
step 'I should see project "Forum" README' do
- page.within('#README') do
+ page.within('.readme-holder') do
expect(page).to have_content 'Sample repo for testing gitlab features'
end
end
step 'I should see project "Shop" README' do
- page.within('#README') do
+ page.within('.readme-holder') do
expect(page).to have_content 'testme'
end
end
diff --git a/features/steps/project/snippets.rb b/features/steps/project/snippets.rb
index db8ad08bb9e..a3aef9bf8c3 100644
--- a/features/steps/project/snippets.rb
+++ b/features/steps/project/snippets.rb
@@ -22,7 +22,7 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
end
step 'I click link "New Snippet"' do
- click_link "Add new snippet"
+ click_link "New Snippet"
end
step 'I click link "Snippet one"' do
@@ -42,13 +42,13 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
end
step 'I click link "Edit"' do
- page.within ".file-title" do
+ page.within ".page-title" do
click_link "Edit"
end
end
- step 'I click link "Remove Snippet"' do
- click_link "remove"
+ step 'I click link "Delete"' do
+ click_link "Delete"
end
step 'I submit new snippet "Snippet three"' do
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index cb100ca0f54..1b27500497a 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -286,6 +286,10 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
select "'test'", from: 'ref'
end
+ step "I switch ref to fix" do
+ select "fix", from: 'ref'
+ end
+
step "I see the ref 'test' has been selected" do
expect(page).to have_selector '.select2-chosen', text: "'test'"
end
@@ -294,11 +298,20 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
visit namespace_project_tree_path(@project.namespace, @project, "'test'")
end
+ step "I visit the fix tree" do
+ visit namespace_project_tree_path(@project.namespace, @project, "fix/.testdir")
+ end
+
step 'I see the commit data' do
expect(page).to have_css('.tree-commit-link', visible: true)
expect(page).not_to have_content('Loading commit data...')
end
+ step 'I see the commit data for a directory with a leading dot' do
+ expect(page).to have_css('.tree-commit-link', visible: true)
+ expect(page).not_to have_content('Loading commit data...')
+ end
+
private
def set_new_content
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index 5744e455ebd..7021fac5fe4 100644
--- a/features/steps/shared/project.rb
+++ b/features/steps/shared/project.rb
@@ -206,4 +206,11 @@ module SharedProject
project = Project.find_by(name: "Shop")
create :ci_commit, gl_project: project, sha: project.commit.sha
end
+
+ step 'I should see last commit with CI status' do
+ page.within ".project-last-commit" do
+ expect(page).to have_content(project.commit.sha[0..6])
+ expect(page).to have_content("skipped")
+ end
+ end
end
diff --git a/features/steps/snippets/snippets.rb b/features/steps/snippets/snippets.rb
index 6ff48e0c6b8..80d1ddeef05 100644
--- a/features/steps/snippets/snippets.rb
+++ b/features/steps/snippets/snippets.rb
@@ -13,13 +13,13 @@ class Spinach::Features::Snippets < Spinach::FeatureSteps
end
step 'I click link "Edit"' do
- page.within ".file-title" do
+ page.within ".page-title" do
click_link "Edit"
end
end
- step 'I click link "Destroy"' do
- click_link "remove"
+ step 'I click link "Delete"' do
+ click_link "Delete"
end
step 'I submit new snippet "Personal snippet three"' do
diff --git a/features/steps/snippets/user.rb b/features/steps/snippets/user.rb
index dea3256229f..997c605bce2 100644
--- a/features/steps/snippets/user.rb
+++ b/features/steps/snippets/user.rb
@@ -32,19 +32,19 @@ class Spinach::Features::SnippetsUser < Spinach::FeatureSteps
end
step 'I click "Internal" filter' do
- page.within('.nav-tabs') do
+ page.within('.snippet-scope-menu') do
click_link "Internal"
end
end
step 'I click "Private" filter' do
- page.within('.nav-tabs') do
+ page.within('.snippet-scope-menu') do
click_link "Private"
end
end
step 'I click "Public" filter' do
- page.within('.nav-tabs') do
+ page.within('.snippet-scope-menu') do
click_link "Public"
end
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 519072d0157..883a5e14b17 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -262,6 +262,18 @@ module API
expose :notification_level
end
+ class ProjectService < Grape::Entity
+ expose :id, :title, :created_at, :updated_at, :active
+ expose :push_events, :issues_events, :merge_requests_events, :tag_push_events, :note_events
+ # Expose serialized properties
+ expose :properties do |service, options|
+ field_names = service.fields.
+ select { |field| options[:include_passwords] || field[:type] != 'password' }.
+ map { |field| field[:name] }
+ service.properties.slice(*field_names)
+ end
+ end
+
class ProjectWithAccess < Project
expose :permissions do
expose :project_access, using: Entities::ProjectAccess do |project, options|
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index f3a59fadf24..6eb84baf9cb 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -249,8 +249,16 @@ module API
required_attributes! [:note]
merge_request = user_project.merge_requests.find(params[:merge_request_id])
- note = merge_request.notes.new(note: params[:note], project_id: user_project.id)
- note.author = current_user
+
+ authorize! :create_note, merge_request
+
+ opts = {
+ note: params[:note],
+ noteable_type: 'MergeRequest',
+ noteable_id: merge_request.id
+ }
+
+ note = ::Notes::CreateService.new(user_project, current_user, opts).execute
if note.save
present note, with: Entities::MRNote
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index c2fb36b4143..67ee66a2058 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -246,8 +246,8 @@ module API
# Example Request:
# DELETE /projects/:id/fork
delete ":id/fork" do
- authenticated_as_admin!
- unless user_project.forked_project_link.nil?
+ authorize! :remove_fork_project, user_project
+ if user_project.forked?
user_project.forked_project_link.destroy
end
end
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 2d96c9666d2..20d568cf462 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -133,7 +133,7 @@ module API
authorize! :download_code, user_project
begin
- file_path = ArchiveRepositoryService.new(
+ ArchiveRepositoryService.new(
user_project,
params[:sha],
params[:format]
@@ -141,17 +141,6 @@ module API
rescue
not_found!('File')
end
-
- if file_path && File.exists?(file_path)
- data = File.open(file_path, 'rb').read
- basename = File.basename(file_path)
- header['Content-Disposition'] = "attachment; filename=\"#{basename}\""
- content_type MIME::Types.type_for(file_path).first.content_type
- env['api.format'] = :binary
- present data
- else
- redirect request.fullpath
- end
end
# Compare two branches, tags or commits
diff --git a/lib/api/services.rb b/lib/api/services.rb
index 6727e80ac1e..203f04a6259 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -57,7 +57,7 @@ module API
# GET /project/:id/services/gitlab-ci
#
get ':id/services/:service_slug' do
- present project_service
+ present project_service, with: Entities::ProjectService, include_passwords: current_user.is_admin?
end
end
end
diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb
index c47951bc5d1..efcd2faffc7 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -5,7 +5,7 @@ module Ci
DEFAULT_STAGES = %w(build test deploy)
DEFAULT_STAGE = 'test'
ALLOWED_YAML_KEYS = [:before_script, :image, :services, :types, :stages, :variables]
- ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services, :allow_failure, :type, :stage]
+ ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services, :allow_failure, :type, :stage, :when]
attr_reader :before_script, :image, :services, :variables
@@ -93,6 +93,7 @@ module Ci
only: job[:only],
except: job[:except],
allow_failure: job[:allow_failure] || false,
+ when: job[:when] || 'on_success',
options: {
image: job[:image] || @image,
services: job[:services] || @services
@@ -138,62 +139,74 @@ module Ci
end
@jobs.each do |name, job|
- validate_job!("#{name} job", job)
+ validate_job!(name, job)
end
true
end
def validate_job!(name, job)
+ if name.blank? || !validate_string(name)
+ raise ValidationError, "job name should be non-empty string"
+ end
+
job.keys.each do |key|
unless ALLOWED_JOB_KEYS.include? key
- raise ValidationError, "#{name}: unknown parameter #{key}"
+ raise ValidationError, "#{name} job: unknown parameter #{key}"
end
end
- if !job[:script].is_a?(String) && !validate_array_of_strings(job[:script])
- raise ValidationError, "#{name}: script should be a string or an array of a strings"
+ if !validate_string(job[:script]) && !validate_array_of_strings(job[:script])
+ raise ValidationError, "#{name} job: script should be a string or an array of a strings"
end
if job[:stage]
unless job[:stage].is_a?(String) && job[:stage].in?(stages)
- raise ValidationError, "#{name}: stage parameter should be #{stages.join(", ")}"
+ raise ValidationError, "#{name} job: stage parameter should be #{stages.join(", ")}"
end
end
- if job[:image] && !job[:image].is_a?(String)
- raise ValidationError, "#{name}: image should be a string"
+ if job[:image] && !validate_string(job[:image])
+ raise ValidationError, "#{name} job: image should be a string"
end
if job[:services] && !validate_array_of_strings(job[:services])
- raise ValidationError, "#{name}: services should be an array of strings"
+ raise ValidationError, "#{name} job: services should be an array of strings"
end
if job[:tags] && !validate_array_of_strings(job[:tags])
- raise ValidationError, "#{name}: tags parameter should be an array of strings"
+ raise ValidationError, "#{name} job: tags parameter should be an array of strings"
end
if job[:only] && !validate_array_of_strings(job[:only])
- raise ValidationError, "#{name}: only parameter should be an array of strings"
+ raise ValidationError, "#{name} job: only parameter should be an array of strings"
end
if job[:except] && !validate_array_of_strings(job[:except])
- raise ValidationError, "#{name}: except parameter should be an array of strings"
+ raise ValidationError, "#{name} job: except parameter should be an array of strings"
end
if job[:allow_failure] && !job[:allow_failure].in?([true, false])
- raise ValidationError, "#{name}: allow_failure parameter should be an boolean"
+ raise ValidationError, "#{name} job: allow_failure parameter should be an boolean"
+ end
+
+ if job[:when] && !job[:when].in?(%w(on_success on_failure always))
+ raise ValidationError, "#{name} job: when parameter should be on_success, on_failure or always"
end
end
private
def validate_array_of_strings(values)
- values.is_a?(Array) && values.all? {|tag| tag.is_a?(String)}
+ values.is_a?(Array) && values.all? { |value| validate_string(value) }
end
def validate_variables(variables)
- variables.is_a?(Hash) && variables.all? {|key, value| key.is_a?(Symbol) && value.is_a?(String)}
+ variables.is_a?(Hash) && variables.all? { |key, value| validate_string(key) && validate_string(value) }
+ end
+
+ def validate_string(value)
+ value.is_a?(String) || value.is_a?(Symbol)
end
end
end
diff --git a/lib/ci/migrate/builds.rb b/lib/ci/migrate/builds.rb
deleted file mode 100644
index c4f62e55295..00000000000
--- a/lib/ci/migrate/builds.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-module Ci
- module Migrate
- class Builds
- attr_reader :app_builds_dir, :backup_builds_tarball, :backup_dir
-
- def initialize
- @app_builds_dir = Settings.gitlab_ci.builds_path
- @backup_dir = Gitlab.config.backup.path
- @backup_builds_tarball = File.join(backup_dir, 'builds/builds.tar.gz')
- end
-
- def restore
- backup_existing_builds_dir
-
- FileUtils.mkdir_p(app_builds_dir, mode: 0700)
- unless system('tar', '-C', app_builds_dir, '-zxf', backup_builds_tarball)
- abort 'Restore failed'.red
- end
- end
-
- def backup_existing_builds_dir
- timestamped_builds_path = File.join(app_builds_dir, '..', "builds.#{Time.now.to_i}")
- if File.exists?(app_builds_dir)
- FileUtils.mv(app_builds_dir, File.expand_path(timestamped_builds_path))
- end
- end
- end
- end
-end
diff --git a/lib/ci/migrate/database.rb b/lib/ci/migrate/database.rb
deleted file mode 100644
index bf9b80f1f62..00000000000
--- a/lib/ci/migrate/database.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-require 'yaml'
-
-module Ci
- module Migrate
- class Database
- attr_reader :config
-
- def initialize
- @config = YAML.load_file(File.join(Rails.root, 'config', 'database.yml'))[Rails.env]
- end
-
- def restore
- decompress_rd, decompress_wr = IO.pipe
- decompress_pid = spawn(*%W(gzip -cd), out: decompress_wr, in: db_file_name)
- decompress_wr.close
-
- restore_pid = case config["adapter"]
- when /^mysql/ then
- $progress.print "Restoring MySQL database #{config['database']} ... "
- # Workaround warnings from MySQL 5.6 about passwords on cmd line
- ENV['MYSQL_PWD'] = config["password"].to_s if config["password"]
- spawn('mysql', *mysql_args, config['database'], in: decompress_rd)
- when "postgresql" then
- $progress.print "Restoring PostgreSQL database #{config['database']} ... "
- pg_env
- spawn('psql', config['database'], in: decompress_rd)
- end
- decompress_rd.close
-
- success = [decompress_pid, restore_pid].all? { |pid| Process.waitpid(pid); $?.success? }
- abort 'Restore failed' unless success
- end
-
- protected
-
- def db_file_name
- File.join(Gitlab.config.backup.path, 'db', 'database.sql.gz')
- end
-
- def mysql_args
- args = {
- 'host' => '--host',
- 'port' => '--port',
- 'socket' => '--socket',
- 'username' => '--user',
- 'encoding' => '--default-character-set'
- }
- args.map { |opt, arg| "#{arg}=#{config[opt]}" if config[opt] }.compact
- end
-
- def pg_env
- ENV['PGUSER'] = config["username"] if config["username"]
- ENV['PGHOST'] = config["host"] if config["host"]
- ENV['PGPORT'] = config["port"].to_s if config["port"]
- ENV['PGPASSWORD'] = config["password"].to_s if config["password"]
- end
-
- def report_success(success)
- if success
- puts '[DONE]'.green
- else
- puts '[FAILED]'.red
- end
- end
- end
- end
-end
diff --git a/lib/ci/migrate/manager.rb b/lib/ci/migrate/manager.rb
deleted file mode 100644
index e5e4fb784eb..00000000000
--- a/lib/ci/migrate/manager.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-module Ci
- module Migrate
- class Manager
- CI_IMPORT_PREFIX = '8.0' # Only allow imports from CI 8.0.x
-
- def cleanup
- $progress.print "Deleting tmp directories ... "
-
- backup_contents.each do |dir|
- next unless File.exist?(File.join(Gitlab.config.backup.path, dir))
-
- if FileUtils.rm_rf(File.join(Gitlab.config.backup.path, dir))
- $progress.puts "done".green
- else
- puts "deleting tmp directory '#{dir}' failed".red
- abort 'Backup failed'
- end
- end
- end
-
- def unpack
- Dir.chdir(Gitlab.config.backup.path)
-
- # check for existing backups in the backup dir
- file_list = Dir.glob("*_gitlab_ci_backup.tar").each.map { |f| f.split(/_/).first.to_i }
- puts "no backups found" if file_list.count == 0
-
- if file_list.count > 1 && ENV["BACKUP"].nil?
- puts "Found more than one backup, please specify which one you want to restore:"
- puts "rake gitlab:backup:restore BACKUP=timestamp_of_backup"
- exit 1
- end
-
- tar_file = ENV["BACKUP"].nil? ? File.join("#{file_list.first}_gitlab_ci_backup.tar") : File.join(ENV["BACKUP"] + "_gitlab_ci_backup.tar")
-
- unless File.exists?(tar_file)
- puts "The specified CI backup doesn't exist!"
- exit 1
- end
-
- $progress.print "Unpacking backup ... "
-
- unless Kernel.system(*%W(tar -xf #{tar_file}))
- puts "unpacking backup failed".red
- exit 1
- else
- $progress.puts "done".green
- end
-
- ENV["VERSION"] = "#{settings[:db_version]}" if settings[:db_version].to_i > 0
-
- # restoring mismatching backups can lead to unexpected problems
- if !settings[:gitlab_version].start_with?(CI_IMPORT_PREFIX)
- puts "GitLab CI version mismatch:".red
- puts " Your current GitLab CI version (#{GitlabCi::VERSION}) differs from the GitLab CI (#{settings[:gitlab_version]}) version in the backup!".red
- exit 1
- end
- end
-
- private
-
- def backup_contents
- ["db", "builds", "backup_information.yml"]
- end
-
- def settings
- @settings ||= YAML.load_file("backup_information.yml")
- end
- end
- end
-end
-
diff --git a/lib/ci/migrate/tags.rb b/lib/ci/migrate/tags.rb
deleted file mode 100644
index 97e043ece27..00000000000
--- a/lib/ci/migrate/tags.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-require 'yaml'
-
-module Ci
- module Migrate
- class Tags
- def restore
- puts 'Inserting tags...'
- connection.select_all('SELECT ci_tags.name FROM ci_tags').each do |tag|
- begin
- connection.execute("INSERT INTO tags (name) VALUES(#{ActiveRecord::Base::sanitize(tag['name'])})")
- rescue ActiveRecord::RecordNotUnique
- end
- end
-
- ActiveRecord::Base.transaction do
- puts 'Deleting old taggings...'
- connection.execute "DELETE FROM taggings WHERE context = 'tags' AND taggable_type LIKE 'Ci::%'"
-
- puts 'Inserting taggings...'
- connection.execute(
- 'INSERT INTO taggings (taggable_type, taggable_id, tag_id, context) ' +
- "SELECT CONCAT('Ci::', ci_taggings.taggable_type), ci_taggings.taggable_id, tags.id, 'tags' FROM ci_taggings " +
- 'JOIN ci_tags ON ci_tags.id = ci_taggings.tag_id ' +
- 'JOIN tags ON tags.name = ci_tags.name '
- )
-
- puts 'Resetting counters... '
- connection.execute(
- 'UPDATE tags SET ' +
- 'taggings_count = (SELECT COUNT(*) FROM taggings WHERE tags.id = taggings.tag_id)'
- )
- end
- end
-
- protected
-
- def connection
- ActiveRecord::Base.connection
- end
- end
- end
-end
diff --git a/lib/ci/status.rb b/lib/ci/status.rb
new file mode 100644
index 00000000000..c02b3b8f3e4
--- /dev/null
+++ b/lib/ci/status.rb
@@ -0,0 +1,21 @@
+module Ci
+ class Status
+ def self.get_status(statuses)
+ statuses.reject! { |status| status.try(&:allow_failure?) }
+
+ if statuses.none?
+ 'skipped'
+ elsif statuses.all?(&:success?)
+ 'success'
+ elsif statuses.all?(&:pending?)
+ 'pending'
+ elsif statuses.any?(&:running?) || statuses.any?(&:pending?)
+ 'running'
+ elsif statuses.all?(&:canceled?)
+ 'canceled'
+ else
+ 'failed'
+ end
+ end
+ end
+end
diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb
index 322aed5e27c..51e46da82cc 100644
--- a/lib/extracts_path.rb
+++ b/lib/extracts_path.rb
@@ -110,7 +110,7 @@ module ExtractsPath
@project, @ref, @path)
rescue RuntimeError, NoMethodError, InvalidPathError
- not_found!
+ render_404
end
def tree
diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb
index 0353b3b7ed3..6830a916bcb 100644
--- a/lib/gitlab/backend/grack_auth.rb
+++ b/lib/gitlab/backend/grack_auth.rb
@@ -193,7 +193,14 @@ module Grack
end
def render_grack_auth_ok
- [200, { "Content-Type" => "application/json" }, [JSON.dump({ 'GL_ID' => Gitlab::ShellEnv.gl_id(@user) })]]
+ [
+ 200,
+ { "Content-Type" => "application/json" },
+ [JSON.dump({
+ 'GL_ID' => Gitlab::ShellEnv.gl_id(@user),
+ 'RepoPath' => project.repository.path_to_repo,
+ })]
+ ]
end
def render_not_found
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index 741a52714ac..71f37f1fef8 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -1,7 +1,7 @@
module Gitlab
module Database
def self.mysql?
- ActiveRecord::Base.connection.adapter_name.downcase == 'mysql'
+ ActiveRecord::Base.connection.adapter_name.downcase == 'mysql2'
end
def self.postgresql?
diff --git a/lib/gitlab/incoming_email.rb b/lib/gitlab/incoming_email.rb
index 856ccc71084..9068d79c95e 100644
--- a/lib/gitlab/incoming_email.rb
+++ b/lib/gitlab/incoming_email.rb
@@ -24,12 +24,12 @@ module Gitlab
match[1]
end
- private
-
def config
Gitlab.config.incoming_email
end
+ private
+
def address_regex
wildcard_address = config.address
return nil unless wildcard_address
diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb
index ae5f2544691..b082bfc434b 100644
--- a/lib/gitlab/markdown.rb
+++ b/lib/gitlab/markdown.rb
@@ -7,6 +7,14 @@ module Gitlab
module Markdown
# Convert a Markdown String into an HTML-safe String of HTML
#
+ # Note that while the returned HTML will have been sanitized of dangerous
+ # HTML, it may post a risk of information leakage if it's not also passed
+ # through `post_process`.
+ #
+ # Also note that the returned String is always HTML, not XHTML. Views
+ # requiring XHTML, such as Atom feeds, need to call `post_process` on the
+ # result, providing the appropriate `pipeline` option.
+ #
# markdown - Markdown String
# context - Hash of context options passed to our HTML Pipeline
#
@@ -31,6 +39,33 @@ module Gitlab
renderer.render(markdown)
end
+ # Perform post-processing on an HTML String
+ #
+ # This method is used to perform state-dependent changes to a String of
+ # HTML, such as removing references that the current user doesn't have
+ # permission to make (`RedactorFilter`).
+ #
+ # html - String to process
+ # options - Hash of options to customize output
+ # :pipeline - Symbol pipeline type
+ # :project - Project
+ # :user - User object
+ #
+ # Returns an HTML-safe String
+ def self.post_process(html, options)
+ context = {
+ project: options[:project],
+ current_user: options[:user]
+ }
+ doc = post_processor.to_document(html, context)
+
+ if options[:pipeline] == :atom
+ doc.to_html(save_with: Nokogiri::XML::Node::SaveOptions::AS_XHTML)
+ else
+ doc.to_html
+ end.html_safe
+ end
+
# Provide autoload paths for filters to prevent a circular dependency error
autoload :AutolinkFilter, 'gitlab/markdown/autolink_filter'
autoload :CommitRangeReferenceFilter, 'gitlab/markdown/commit_range_reference_filter'
@@ -41,6 +76,7 @@ module Gitlab
autoload :IssueReferenceFilter, 'gitlab/markdown/issue_reference_filter'
autoload :LabelReferenceFilter, 'gitlab/markdown/label_reference_filter'
autoload :MergeRequestReferenceFilter, 'gitlab/markdown/merge_request_reference_filter'
+ autoload :RedactorFilter, 'gitlab/markdown/redactor_filter'
autoload :RelativeLinkFilter, 'gitlab/markdown/relative_link_filter'
autoload :SanitizationFilter, 'gitlab/markdown/sanitization_filter'
autoload :SnippetReferenceFilter, 'gitlab/markdown/snippet_reference_filter'
@@ -48,27 +84,22 @@ module Gitlab
autoload :TableOfContentsFilter, 'gitlab/markdown/table_of_contents_filter'
autoload :TaskListFilter, 'gitlab/markdown/task_list_filter'
autoload :UserReferenceFilter, 'gitlab/markdown/user_reference_filter'
+ autoload :UploadLinkFilter, 'gitlab/markdown/upload_link_filter'
- # Public: Parse the provided text with GitLab-Flavored Markdown
+ # Public: Parse the provided HTML with GitLab-Flavored Markdown
+ #
+ # html - HTML String
+ # options - A Hash of options used to customize output (default: {})
+ # :no_header_anchors - Disable header anchors in TableOfContentsFilter
+ # :path - Current path String
+ # :pipeline - Symbol pipeline type
+ # :project - Current Project object
+ # :project_wiki - Current ProjectWiki object
+ # :ref - Current ref String
#
- # text - the source text
- # options - A Hash of options used to customize output (default: {}):
- # :xhtml - output XHTML instead of HTML
- # :reference_only_path - Use relative path for reference links
- def self.gfm(text, options = {})
- return text if text.nil?
-
- # Duplicate the string so we don't alter the original, then call to_str
- # to cast it back to a String instead of a SafeBuffer. This is required
- # for gsub calls to work as we need them to.
- text = text.dup.to_str
-
- options.reverse_merge!(
- xhtml: false,
- reference_only_path: true,
- project: options[:project],
- current_user: options[:current_user]
- )
+ # Returns an HTML-safe String
+ def self.gfm(html, options = {})
+ return '' unless html.present?
@pipeline ||= HTML::Pipeline.new(filters)
@@ -77,41 +108,36 @@ module Gitlab
pipeline: options[:pipeline],
# EmojiFilter
- asset_root: Gitlab.config.gitlab.base_url,
asset_host: Gitlab::Application.config.asset_host,
-
- # TableOfContentsFilter
- no_header_anchors: options[:no_header_anchors],
+ asset_root: Gitlab.config.gitlab.base_url,
# ReferenceFilter
- current_user: options[:current_user],
- only_path: options[:reference_only_path],
- project: options[:project],
+ only_path: only_path_pipeline?(options[:pipeline]),
+ project: options[:project],
# RelativeLinkFilter
+ project_wiki: options[:project_wiki],
ref: options[:ref],
requested_path: options[:path],
- project_wiki: options[:project_wiki]
- }
-
- result = @pipeline.call(text, context)
- save_options = 0
- if options[:xhtml]
- save_options |= Nokogiri::XML::Node::SaveOptions::AS_XHTML
- end
-
- text = result[:output].to_html(save_with: save_options)
+ # TableOfContentsFilter
+ no_header_anchors: options[:no_header_anchors]
+ }
- text.html_safe
+ @pipeline.to_html(html, context).html_safe
end
private
- def self.renderer
- @markdown ||= begin
- renderer = Redcarpet::Render::HTML.new
- Redcarpet::Markdown.new(renderer, redcarpet_options)
+ # Check if a pipeline enables the `only_path` context option
+ #
+ # Returns Boolean
+ def self.only_path_pipeline?(pipeline)
+ case pipeline
+ when :atom, :email
+ false
+ else
+ true
end
end
@@ -129,6 +155,17 @@ module Gitlab
}.freeze
end
+ def self.renderer
+ @markdown ||= begin
+ renderer = Redcarpet::Render::HTML.new
+ Redcarpet::Markdown.new(renderer, redcarpet_options)
+ end
+ end
+
+ def self.post_processor
+ @post_processor ||= HTML::Pipeline.new([Gitlab::Markdown::RedactorFilter])
+ end
+
# Filters used in our pipeline
#
# SanitizationFilter should come first so that all generated reference HTML
@@ -140,6 +177,7 @@ module Gitlab
Gitlab::Markdown::SyntaxHighlightFilter,
Gitlab::Markdown::SanitizationFilter,
+ Gitlab::Markdown::UploadLinkFilter,
Gitlab::Markdown::RelativeLinkFilter,
Gitlab::Markdown::EmojiFilter,
Gitlab::Markdown::TableOfContentsFilter,
diff --git a/lib/gitlab/markdown/commit_range_reference_filter.rb b/lib/gitlab/markdown/commit_range_reference_filter.rb
index bb496135d92..e070edae0a4 100644
--- a/lib/gitlab/markdown/commit_range_reference_filter.rb
+++ b/lib/gitlab/markdown/commit_range_reference_filter.rb
@@ -26,6 +26,18 @@ module Gitlab
end
end
+ def self.referenced_by(node)
+ project = Project.find(node.attr("data-project")) rescue nil
+ return unless project
+
+ id = node.attr("data-commit-range")
+ range = CommitRange.new(id, project)
+
+ return unless range.valid_commits?
+
+ { commit_range: range }
+ end
+
def initialize(*args)
super
@@ -53,13 +65,11 @@ module Gitlab
range = CommitRange.new(id, project)
if range.valid_commits?
- push_result(:commit_range, range)
-
url = url_for_commit_range(project, range)
title = range.reference_title
klass = reference_class(:commit_range)
- data = data_attribute(project.id)
+ data = data_attribute(project: project.id, commit_range: id)
project_ref += '@' if project_ref
diff --git a/lib/gitlab/markdown/commit_reference_filter.rb b/lib/gitlab/markdown/commit_reference_filter.rb
index fcbb2e936a5..8cdbeb1f9cf 100644
--- a/lib/gitlab/markdown/commit_reference_filter.rb
+++ b/lib/gitlab/markdown/commit_reference_filter.rb
@@ -26,6 +26,18 @@ module Gitlab
end
end
+ def self.referenced_by(node)
+ project = Project.find(node.attr("data-project")) rescue nil
+ return unless project
+
+ id = node.attr("data-commit")
+ commit = commit_from_ref(project, id)
+
+ return unless commit
+
+ { commit: commit }
+ end
+
def call
replace_text_nodes_matching(Commit.reference_pattern) do |content|
commit_link_filter(content)
@@ -39,17 +51,15 @@ module Gitlab
# 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, commit_ref, project_ref|
+ self.class.references_in(text) do |match, id, project_ref|
project = self.project_from_ref(project_ref)
- if commit = commit_from_ref(project, commit_ref)
- push_result(:commit, commit)
-
+ 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.id)
+ data = data_attribute(project: project.id, commit: id)
project_ref += '@' if project_ref
@@ -62,9 +72,9 @@ module Gitlab
end
end
- def commit_from_ref(project, commit_ref)
+ def self.commit_from_ref(project, id)
if project && project.valid_repo?
- project.commit(commit_ref)
+ project.commit(id)
end
end
diff --git a/lib/gitlab/markdown/cross_project_reference.rb b/lib/gitlab/markdown/cross_project_reference.rb
index 855748fdccc..6ab04a584b0 100644
--- a/lib/gitlab/markdown/cross_project_reference.rb
+++ b/lib/gitlab/markdown/cross_project_reference.rb
@@ -13,18 +13,11 @@ module Gitlab
#
# ref - String reference.
#
- # Returns a Project, or nil if the reference can't be accessed
+ # Returns a Project, or nil if the reference can't be found
def project_from_ref(ref)
return context[:project] unless ref
- other = Project.find_with_namespace(ref)
- return nil unless other && user_can_reference_project?(other)
-
- other
- end
-
- def user_can_reference_project?(project, user = context[:current_user])
- Ability.abilities.allowed?(user, :read_project, project)
+ Project.find_with_namespace(ref)
end
end
end
diff --git a/lib/gitlab/markdown/external_issue_reference_filter.rb b/lib/gitlab/markdown/external_issue_reference_filter.rb
index f7c43e1ca89..8f86f13976a 100644
--- a/lib/gitlab/markdown/external_issue_reference_filter.rb
+++ b/lib/gitlab/markdown/external_issue_reference_filter.rb
@@ -47,8 +47,9 @@ module Gitlab
title = escape_once("Issue in #{project.external_issue_tracker.title}")
klass = reference_class(:issue)
+ data = data_attribute(project: project.id)
- %(<a href="#{url}"
+ %(<a href="#{url}" #{data}
title="#{title}"
class="#{klass}">#{match}</a>)
end
diff --git a/lib/gitlab/markdown/issue_reference_filter.rb b/lib/gitlab/markdown/issue_reference_filter.rb
index 01320f80796..481d282f7b1 100644
--- a/lib/gitlab/markdown/issue_reference_filter.rb
+++ b/lib/gitlab/markdown/issue_reference_filter.rb
@@ -27,6 +27,10 @@ module Gitlab
end
end
+ def self.referenced_by(node)
+ { issue: LazyReference.new(Issue, node.attr("data-issue")) }
+ end
+
def call
replace_text_nodes_matching(Issue.reference_pattern) do |content|
issue_link_filter(content)
@@ -45,13 +49,11 @@ module Gitlab
project = self.project_from_ref(project_ref)
if project && issue = project.get_issue(id)
- push_result(:issue, issue)
-
url = url_for_issue(id, project, only_path: context[:only_path])
title = escape_once("Issue: #{issue.title}")
klass = reference_class(:issue)
- data = data_attribute(project.id)
+ data = data_attribute(project: project.id, issue: issue.id)
%(<a href="#{url}" #{data}
title="#{title}"
diff --git a/lib/gitlab/markdown/label_reference_filter.rb b/lib/gitlab/markdown/label_reference_filter.rb
index 1e5cb12071e..618acb7a578 100644
--- a/lib/gitlab/markdown/label_reference_filter.rb
+++ b/lib/gitlab/markdown/label_reference_filter.rb
@@ -22,6 +22,10 @@ module Gitlab
end
end
+ def self.referenced_by(node)
+ { label: LazyReference.new(Label, node.attr("data-label")) }
+ end
+
def call
replace_text_nodes_matching(Label.reference_pattern) do |content|
label_link_filter(content)
@@ -41,11 +45,9 @@ module Gitlab
params = label_params(id, name)
if label = project.labels.find_by(params)
- push_result(:label, label)
-
url = url_for_label(project, label)
klass = reference_class(:label)
- data = data_attribute(project.id)
+ data = data_attribute(project: project.id, label: label.id)
%(<a href="#{url}" #{data}
class="#{klass}">#{render_colored_label(label)}</a>)
diff --git a/lib/gitlab/markdown/merge_request_reference_filter.rb b/lib/gitlab/markdown/merge_request_reference_filter.rb
index ecbd263d0e0..5bc63269808 100644
--- a/lib/gitlab/markdown/merge_request_reference_filter.rb
+++ b/lib/gitlab/markdown/merge_request_reference_filter.rb
@@ -27,6 +27,10 @@ module Gitlab
end
end
+ def self.referenced_by(node)
+ { merge_request: LazyReference.new(MergeRequest, node.attr("data-merge-request")) }
+ end
+
def call
replace_text_nodes_matching(MergeRequest.reference_pattern) do |content|
merge_request_link_filter(content)
@@ -45,11 +49,9 @@ module Gitlab
project = self.project_from_ref(project_ref)
if project && merge_request = project.merge_requests.find_by(iid: id)
- push_result(:merge_request, merge_request)
-
title = escape_once("Merge Request: #{merge_request.title}")
klass = reference_class(:merge_request)
- data = data_attribute(project.id)
+ data = data_attribute(project: project.id, merge_request: merge_request.id)
url = url_for_merge_request(merge_request, project)
diff --git a/lib/gitlab/markdown/redactor_filter.rb b/lib/gitlab/markdown/redactor_filter.rb
new file mode 100644
index 00000000000..a1f3a8a8ebf
--- /dev/null
+++ b/lib/gitlab/markdown/redactor_filter.rb
@@ -0,0 +1,40 @@
+require 'gitlab/markdown'
+require 'html/pipeline/filter'
+
+module Gitlab
+ module Markdown
+ # HTML filter that removes references to records that the current user does
+ # not have permission to view.
+ #
+ # Expected to be run in its own post-processing pipeline.
+ #
+ class RedactorFilter < HTML::Pipeline::Filter
+ def call
+ doc.css('a.gfm').each do |node|
+ unless user_can_reference?(node)
+ node.replace(node.text)
+ end
+ end
+
+ doc
+ end
+
+ private
+
+ def user_can_reference?(node)
+ if node.has_attribute?('data-reference-filter')
+ reference_type = node.attr('data-reference-filter')
+ reference_filter = reference_type.constantize
+
+ reference_filter.user_can_reference?(current_user, node, context)
+ else
+ true
+ end
+ end
+
+ def current_user
+ context[:current_user]
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/markdown/reference_filter.rb b/lib/gitlab/markdown/reference_filter.rb
index 9b293c957d6..a4c560f578c 100644
--- a/lib/gitlab/markdown/reference_filter.rb
+++ b/lib/gitlab/markdown/reference_filter.rb
@@ -11,30 +11,57 @@ module Gitlab
# Context options:
# :project (required) - Current project, ignored if reference is cross-project.
# :only_path - Generate path-only links.
- #
- # Results:
- # :references - A Hash of references that were found and replaced.
class ReferenceFilter < HTML::Pipeline::Filter
- def initialize(*args)
- super
+ LazyReference = Struct.new(:klass, :ids) do
+ def self.load(refs)
+ lazy_references, values = refs.partition { |ref| ref.is_a?(self) }
+
+ lazy_values = lazy_references.group_by(&:klass).flat_map do |klass, refs|
+ ids = refs.flat_map(&:ids)
+ klass.where(id: ids)
+ end
+
+ values + lazy_values
+ end
+
+ def load
+ self.klass.where(id: self.ids)
+ end
+ end
+
+ def self.user_can_reference?(user, node, context)
+ if node.has_attribute?('data-project')
+ project_id = node.attr('data-project').to_i
+ return true if project_id == context[:project].try(:id)
+
+ project = Project.find(project_id) rescue nil
+ Ability.abilities.allowed?(user, :read_project, project)
+ else
+ true
+ end
+ end
- result[:references] = Hash.new { |hash, type| hash[type] = [] }
+ def self.referenced_by(node)
+ raise NotImplementedError, "#{self} does not implement #{__method__}"
end
# Returns a data attribute String to attach to a reference link
#
- # id - Object ID
- # type - Object type (default: :project)
+ # attributes - Hash, where the key becomes the data attribute name and the
+ # value is the data attribute value
#
# Examples:
#
- # data_attribute(1) # => "data-project-id=\"1\""
- # data_attribute(2, :user) # => "data-user-id=\"2\""
- # data_attribute(3, :group) # => "data-group-id=\"3\""
+ # data_attribute(project: 1, issue: 2)
+ # # => "data-reference-filter=\"Gitlab::Markdown::SomeReferenceFilter\" data-project=\"1\" data-issue=\"2\""
+ #
+ # data_attribute(project: 3, merge_request: 4)
+ # # => "data-reference-filter=\"Gitlab::Markdown::SomeReferenceFilter\" data-project=\"3\" data-merge-request=\"4\""
#
# Returns a String
- def data_attribute(id, type = :project)
- %Q(data-#{type}-id="#{id}")
+ def data_attribute(attributes = {})
+ attributes[:reference_filter] = self.class.name
+ attributes.map { |key, value| %Q(data-#{key.to_s.dasherize}="#{value}") }.join(" ")
end
def escape_once(html)
@@ -59,16 +86,6 @@ module Gitlab
context[:project]
end
- # Add a reference to the pipeline's result Hash
- #
- # type - Singular Symbol reference type (e.g., :issue, :user, etc.)
- # values - One or more Objects to add
- def push_result(type, *values)
- return if values.empty?
-
- result[:references][type].push(*values)
- end
-
def reference_class(type)
"gfm gfm-#{type}"
end
@@ -85,15 +102,15 @@ module Gitlab
# Yields the current node's String contents. The result of the block will
# replace the node's existing content and update the current document.
#
- # Returns the updated Nokogiri::XML::Document object.
+ # Returns the updated Nokogiri::HTML::DocumentFragment object.
def replace_text_nodes_matching(pattern)
return doc if project.nil?
search_text_nodes(doc).each do |node|
- content = node.to_html
-
- next unless content.match(pattern)
next if ignored_ancestry?(node)
+ next unless node.text =~ pattern
+
+ content = node.to_html
html = yield content
diff --git a/lib/gitlab/markdown/reference_gatherer_filter.rb b/lib/gitlab/markdown/reference_gatherer_filter.rb
new file mode 100644
index 00000000000..00f983675e6
--- /dev/null
+++ b/lib/gitlab/markdown/reference_gatherer_filter.rb
@@ -0,0 +1,63 @@
+require 'gitlab/markdown'
+require 'html/pipeline/filter'
+
+module Gitlab
+ module Markdown
+ # HTML filter that gathers all referenced records that the current user has
+ # permission to view.
+ #
+ # Expected to be run in its own post-processing pipeline.
+ #
+ class ReferenceGathererFilter < HTML::Pipeline::Filter
+ def initialize(*)
+ super
+
+ result[:references] ||= Hash.new { |hash, type| hash[type] = [] }
+ end
+
+ def call
+ doc.css('a.gfm').each do |node|
+ gather_references(node)
+ end
+
+ load_lazy_references unless context[:load_lazy_references] == false
+
+ doc
+ end
+
+ private
+
+ def gather_references(node)
+ return unless node.has_attribute?('data-reference-filter')
+
+ reference_type = node.attr('data-reference-filter')
+ reference_filter = reference_type.constantize
+
+ return if context[:reference_filter] && reference_filter != context[:reference_filter]
+
+ return unless reference_filter.user_can_reference?(current_user, node, context)
+
+ references = reference_filter.referenced_by(node)
+ return unless references
+
+ references.each do |type, values|
+ Array.wrap(values).each do |value|
+ result[:references][type] << value
+ end
+ end
+ end
+
+ # Will load all references of one type using one query.
+ def load_lazy_references
+ refs = result[:references]
+ refs.each do |type, values|
+ refs[type] = ReferenceFilter::LazyReference.load(values)
+ end
+ end
+
+ def current_user
+ context[:current_user]
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/markdown/snippet_reference_filter.rb b/lib/gitlab/markdown/snippet_reference_filter.rb
index e2cf89cb1d8..f783f951711 100644
--- a/lib/gitlab/markdown/snippet_reference_filter.rb
+++ b/lib/gitlab/markdown/snippet_reference_filter.rb
@@ -27,6 +27,10 @@ module Gitlab
end
end
+ def self.referenced_by(node)
+ { snippet: LazyReference.new(Snippet, node.attr("data-snippet")) }
+ end
+
def call
replace_text_nodes_matching(Snippet.reference_pattern) do |content|
snippet_link_filter(content)
@@ -45,11 +49,9 @@ module Gitlab
project = self.project_from_ref(project_ref)
if project && snippet = project.snippets.find_by(id: id)
- push_result(:snippet, snippet)
-
title = escape_once("Snippet: #{snippet.title}")
klass = reference_class(:snippet)
- data = data_attribute(project.id)
+ data = data_attribute(project: project.id, snippet: snippet.id)
url = url_for_snippet(snippet, project)
diff --git a/lib/gitlab/markdown/upload_link_filter.rb b/lib/gitlab/markdown/upload_link_filter.rb
new file mode 100644
index 00000000000..fbada73ab86
--- /dev/null
+++ b/lib/gitlab/markdown/upload_link_filter.rb
@@ -0,0 +1,47 @@
+require 'gitlab/markdown'
+require 'html/pipeline/filter'
+require 'uri'
+
+module Gitlab
+ module Markdown
+ # HTML filter that "fixes" relative upload links to files.
+ # Context options:
+ # :project (required) - Current project
+ #
+ class UploadLinkFilter < HTML::Pipeline::Filter
+ def call
+ doc.search('a').each do |el|
+ process_link_attr el.attribute('href')
+ end
+
+ doc.search('img').each do |el|
+ process_link_attr el.attribute('src')
+ end
+
+ doc
+ end
+
+ protected
+
+ def process_link_attr(html_attr)
+ return if html_attr.blank?
+
+ uri = html_attr.value
+ if uri.starts_with?("/uploads/")
+ html_attr.value = build_url(uri).to_s
+ end
+ end
+
+ def build_url(uri)
+ File.join(Gitlab.config.gitlab.url, context[:project].path_with_namespace, uri)
+ end
+
+ # Ensure that a :project key exists in context
+ #
+ # Note that while the key might exist, its value could be nil!
+ def validate
+ needs :project
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/markdown/user_reference_filter.rb b/lib/gitlab/markdown/user_reference_filter.rb
index 6f436ea7167..2a594e1662e 100644
--- a/lib/gitlab/markdown/user_reference_filter.rb
+++ b/lib/gitlab/markdown/user_reference_filter.rb
@@ -23,6 +23,31 @@ module Gitlab
end
end
+ def self.referenced_by(node)
+ if node.has_attribute?('data-group')
+ group = Group.find(node.attr('data-group')) rescue nil
+ return unless group
+
+ { user: group.users }
+ elsif node.has_attribute?('data-user')
+ { user: LazyReference.new(User, node.attr('data-user')) }
+ elsif node.has_attribute?('data-project')
+ project = Project.find(node.attr('data-project')) rescue nil
+ return unless project
+
+ { user: project.team.members.flatten }
+ end
+ end
+
+ def self.user_can_reference?(user, node, context)
+ if node.has_attribute?('data-group')
+ group = Group.find(node.attr('data-group')) rescue nil
+ Ability.abilities.allowed?(user, :read_group, group)
+ else
+ super
+ end
+ end
+
def call
replace_text_nodes_matching(User.reference_pattern) do |content|
user_link_filter(content)
@@ -61,14 +86,12 @@ module Gitlab
def link_to_all
project = context[:project]
- # FIXME (rspeicher): Law of Demeter
- push_result(:user, *project.team.members.flatten)
-
url = urls.namespace_project_url(project.namespace, project,
only_path: context[:only_path])
+ data = data_attribute(project: project.id)
text = User.reference_prefix + 'all'
- %(<a href="#{url}" class="#{link_class}">#{text}</a>)
+ %(<a href="#{url}" #{data} class="#{link_class}">#{text}</a>)
end
def link_to_namespace(namespace)
@@ -80,30 +103,20 @@ module Gitlab
end
def link_to_group(group, namespace)
- return unless user_can_reference_group?(namespace)
-
- push_result(:user, *namespace.users)
-
url = urls.group_url(group, only_path: context[:only_path])
- data = data_attribute(namespace.id, :group)
+ data = data_attribute(group: namespace.id)
text = Group.reference_prefix + group
%(<a href="#{url}" #{data} class="#{link_class}">#{text}</a>)
end
def link_to_user(user, namespace)
- push_result(:user, namespace.owner)
-
url = urls.user_url(user, only_path: context[:only_path])
- data = data_attribute(namespace.owner_id, :user)
+ data = data_attribute(user: namespace.owner_id)
text = User.reference_prefix + user
%(<a href="#{url}" #{data} class="#{link_class}">#{text}</a>)
end
-
- def user_can_reference_group?(group)
- Ability.abilities.allowed?(context[:current_user], :read_group, group)
- end
end
end
end
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index 0961bd80421..da8df8a3025 100644
--- a/lib/gitlab/reference_extractor.rb
+++ b/lib/gitlab/reference_extractor.rb
@@ -3,11 +3,12 @@ require 'gitlab/markdown'
module Gitlab
# Extract possible GFM references from an arbitrary String for further processing.
class ReferenceExtractor
- attr_accessor :project, :current_user
+ attr_accessor :project, :current_user, :load_lazy_references
- def initialize(project, current_user = nil)
+ def initialize(project, current_user = nil, load_lazy_references: true)
@project = project
@current_user = current_user
+ @load_lazy_references = load_lazy_references
end
def analyze(text)
@@ -26,9 +27,9 @@ module Gitlab
def references
@references ||= Hash.new do |references, type|
type = type.to_sym
- return references[type] if references.has_key?(type)
+ next references[type] if references.has_key?(type)
- references[type] = pipeline_result(type).uniq
+ references[type] = pipeline_result(type)
end
end
@@ -39,21 +40,34 @@ module Gitlab
#
# Returns the results Array for the requested filter type
def pipeline_result(filter_type)
- klass = filter_type.to_s.camelize + 'ReferenceFilter'
+ 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
+ ignore_blockquotes: true,
+
+ # ReferenceGathererFilter
+ load_lazy_references: false,
+ reference_filter: filter
}
- pipeline = HTML::Pipeline.new([filter], context)
+ pipeline = HTML::Pipeline.new([filter, Gitlab::Markdown::ReferenceGathererFilter], context)
result = pipeline.call(@text)
- result[:references][filter_type]
+ values = result[:references][filter_type].uniq
+
+ if @load_lazy_references
+ values = Gitlab::Markdown::ReferenceFilter::LazyReference.load(values).uniq
+ end
+
+ values
end
end
end
diff --git a/lib/gitlab/uploads_transfer.rb b/lib/gitlab/uploads_transfer.rb
new file mode 100644
index 00000000000..be8fcc7b2d2
--- /dev/null
+++ b/lib/gitlab/uploads_transfer.rb
@@ -0,0 +1,35 @@
+module Gitlab
+ class UploadsTransfer
+ def move_project(project_path, namespace_path_was, namespace_path)
+ new_namespace_folder = File.join(root_dir, namespace_path)
+ FileUtils.mkdir_p(new_namespace_folder) unless Dir.exist?(new_namespace_folder)
+ from = File.join(root_dir, namespace_path_was, project_path)
+ to = File.join(root_dir, namespace_path, project_path)
+ move(from, to, "")
+ end
+
+ def rename_project(path_was, path, namespace_path)
+ base_dir = File.join(root_dir, namespace_path)
+ move(path_was, path, base_dir)
+ end
+
+ def rename_namespace(path_was, path)
+ move(path_was, path)
+ end
+
+ private
+
+ def move(path_was, path, base_dir = nil)
+ base_dir = root_dir unless base_dir
+ from = File.join(base_dir, path_was)
+ to = File.join(base_dir, path)
+ FileUtils.mv(from, to)
+ rescue Errno::ENOENT
+ false
+ end
+
+ def root_dir
+ File.join(Rails.root, "public", "uploads")
+ end
+ end
+end
diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab
index 7218a4d2f20..1e55c5a0486 100644
--- a/lib/support/nginx/gitlab
+++ b/lib/support/nginx/gitlab
@@ -113,7 +113,25 @@ server {
proxy_pass http://gitlab;
}
- location ~ [-\/\w\.]+\.git\/ {
+ location ~ ^/[\w\.-]+/[\w\.-]+/(info/refs|git-upload-pack|git-receive-pack)$ {
+ # 'Error' 418 is a hack to re-use the @gitlab-git-http-server block
+ error_page 418 = @gitlab-git-http-server;
+ return 418;
+ }
+
+ location ~ ^/[\w\.-]+/[\w\.-]+/repository/archive {
+ # 'Error' 418 is a hack to re-use the @gitlab-git-http-server block
+ error_page 418 = @gitlab-git-http-server;
+ return 418;
+ }
+
+ location ~ ^/api/v3/projects/.*/repository/archive {
+ # 'Error' 418 is a hack to re-use the @gitlab-git-http-server block
+ error_page 418 = @gitlab-git-http-server;
+ return 418;
+ }
+
+ location @gitlab-git-http-server {
## 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 7dabfba87e2..08641bbcc17 100644
--- a/lib/support/nginx/gitlab-ssl
+++ b/lib/support/nginx/gitlab-ssl
@@ -160,7 +160,25 @@ server {
proxy_pass http://gitlab;
}
- location ~ [-\/\w\.]+\.git\/ {
+ location ~ ^/[\w\.-]+/[\w\.-]+/(info/refs|git-upload-pack|git-receive-pack)$ {
+ # 'Error' 418 is a hack to re-use the @gitlab-git-http-server block
+ error_page 418 = @gitlab-git-http-server;
+ return 418;
+ }
+
+ location ~ ^/[\w\.-]+/[\w\.-]+/repository/archive {
+ # 'Error' 418 is a hack to re-use the @gitlab-git-http-server block
+ error_page 418 = @gitlab-git-http-server;
+ return 418;
+ }
+
+ location ~ ^/api/v3/projects/.*/repository/archive {
+ # 'Error' 418 is a hack to re-use the @gitlab-git-http-server block
+ error_page 418 = @gitlab-git-http-server;
+ return 418;
+ }
+
+ location @gitlab-git-http-server {
## If you use HTTPS make sure you disable gzip compression
## to be safe against BREACH attack.
gzip off;
diff --git a/lib/tasks/ci/migrate.rake b/lib/tasks/ci/migrate.rake
deleted file mode 100644
index 1de664c85e1..00000000000
--- a/lib/tasks/ci/migrate.rake
+++ /dev/null
@@ -1,87 +0,0 @@
-namespace :ci do
- desc 'GitLab | Import and migrate CI database'
- task migrate: :environment do
- warn_user_is_not_gitlab
- configure_cron_mode
-
- unless ENV['force'] == 'yes'
- puts 'This will remove all CI related data and restore it from the provided backup.'
- ask_to_continue
- puts ''
- end
-
- # disable CI for time of migration
- enable_ci(false)
-
- # unpack archives
- migrate = Ci::Migrate::Manager.new
- migrate.unpack
-
- Rake::Task['ci:migrate:db'].invoke
- Rake::Task['ci:migrate:builds'].invoke
- Rake::Task['ci:migrate:tags'].invoke
- Rake::Task['ci:migrate:services'].invoke
-
- # enable CI for time of migration
- enable_ci(true)
-
- migrate.cleanup
- end
-
- namespace :migrate do
- desc 'GitLab | Import CI database'
- task db: :environment do
- configure_cron_mode
- $progress.puts 'Restoring database ... '.blue
- Ci::Migrate::Database.new.restore
- $progress.puts 'done'.green
- end
-
- desc 'GitLab | Import CI builds'
- task builds: :environment do
- configure_cron_mode
- $progress.puts 'Restoring builds ... '.blue
- Ci::Migrate::Builds.new.restore
- $progress.puts 'done'.green
- end
-
- desc 'GitLab | Migrate CI tags'
- task tags: :environment do
- configure_cron_mode
- $progress.puts 'Migrating tags ... '.blue
- ::Ci::Migrate::Tags.new.restore
- $progress.puts 'done'.green
- end
-
- desc 'GitLab | Migrate CI auto-increments'
- task autoincrements: :environment do
- c = ActiveRecord::Base.connection
- c.tables.select { |t| t.start_with?('ci_') }.each do |table|
- result = c.select_one("SELECT id FROM #{table} ORDER BY id DESC LIMIT 1")
- if result
- ai_val = result['id'].to_i + 1
- puts "Resetting auto increment ID for #{table} to #{ai_val}"
- if c.adapter_name == 'PostgreSQL'
- c.execute("ALTER SEQUENCE #{table}_id_seq RESTART WITH #{ai_val}")
- else
- c.execute("ALTER TABLE #{table} AUTO_INCREMENT = #{ai_val}")
- end
- end
- end
- end
-
- desc 'GitLab | Migrate CI services'
- task services: :environment do
- $progress.puts 'Migrating services ... '.blue
- c = ActiveRecord::Base.connection
- c.execute("UPDATE ci_services SET type=CONCAT('Ci::', type) WHERE type NOT LIKE 'Ci::%'")
- $progress.puts 'done'.green
- end
- end
-
- def enable_ci(enabled)
- settings = ApplicationSetting.current || ApplicationSetting.create_from_defaults
- settings.ci_enabled = enabled
- settings.save!
- end
-end
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 66f1ecf385f..2e73f792a9d 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -335,7 +335,7 @@ namespace :gitlab do
print "Redis version >= #{min_redis_version}? ... "
redis_version = run(%W(redis-cli --version))
- redis_version = redis_version.try(:match, /redis-cli (.*)/)
+ redis_version = redis_version.try(:match, /redis-cli (\d+\.\d+\.\d+)/)
if redis_version &&
(Gem::Version.new(redis_version[1]) > Gem::Version.new(min_redis_version))
puts "yes".green
@@ -642,7 +642,6 @@ namespace :gitlab do
if Gitlab.config.incoming_email.enabled
check_address_formatted_correctly
- check_mail_room_config_exists
check_imap_authentication
if Rails.env.production?
@@ -744,42 +743,16 @@ namespace :gitlab do
end
end
- def check_mail_room_config_exists
- print "MailRoom config exists? ... "
-
- mail_room_config_file = Rails.root.join("config", "mail_room.yml")
-
- if File.exists?(mail_room_config_file)
- puts "yes".green
- else
- puts "no".red
- try_fixing_it(
- "Copy config/mail_room.yml.example to config/mail_room.yml",
- "Check that the information in config/mail_room.yml is correct"
- )
- for_more_information(
- "doc/incoming_email/README.md"
- )
- fix_and_rerun
- end
- end
-
def check_imap_authentication
print "IMAP server credentials are correct? ... "
- mail_room_config_file = Rails.root.join("config", "mail_room.yml")
-
- unless File.exists?(mail_room_config_file)
- puts "can't check because of previous errors".magenta
- return
- end
-
- config = YAML.load_file(mail_room_config_file)[:mailboxes].first rescue nil
+ config = Gitlab.config.incoming_email
if config
begin
- imap = Net::IMAP.new(config[:host], port: config[:port], ssl: config[:ssl])
- imap.login(config[:email], config[:password])
+ imap = Net::IMAP.new(config.host, port: config.port, ssl: config.ssl)
+ imap.starttls if config.start_tls
+ imap.login(config.user, config.password)
connected = true
rescue
connected = false
@@ -791,7 +764,7 @@ namespace :gitlab do
else
puts "no".red
try_fixing_it(
- "Check that the information in config/mail_room.yml is correct"
+ "Check that the information in config/gitlab.yml is correct"
)
for_more_information(
"doc/incoming_email/README.md"
diff --git a/lib/tasks/migrate/setup_postgresql.rake b/lib/tasks/migrate/setup_postgresql.rake
index bf6894a8351..141a0b74ec0 100644
--- a/lib/tasks/migrate/setup_postgresql.rake
+++ b/lib/tasks/migrate/setup_postgresql.rake
@@ -1,6 +1,8 @@
require Rails.root.join('db/migrate/20151007120511_namespaces_projects_path_lower_indexes')
+require Rails.root.join('db/migrate/20151008110232_add_users_lower_username_email_indexes')
desc 'GitLab | Sets up PostgreSQL'
task setup_postgresql: :environment do
NamespacesProjectsPathLowerIndexes.new.up
+ AddUsersLowerUsernameEmailIndexes.new.up
end
diff --git a/public/uploads/.gitkeep b/public/uploads/.gitkeep
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/public/uploads/.gitkeep
+++ /dev/null
diff --git a/spec/benchmarks/lib/gitlab/markdown/reference_filter_spec.rb b/spec/benchmarks/lib/gitlab/markdown/reference_filter_spec.rb
new file mode 100644
index 00000000000..34cd9f7e4eb
--- /dev/null
+++ b/spec/benchmarks/lib/gitlab/markdown/reference_filter_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+describe Gitlab::Markdown::ReferenceFilter, benchmark: true do
+ let(:input) do
+ html = <<-EOF
+<p>Hello @alice and @bob, how are you doing today?</p>
+<p>This is simple @dummy text to see how the @ReferenceFilter class performs
+when @processing HTML.</p>
+ EOF
+
+ Nokogiri::HTML.fragment(html)
+ end
+
+ let(:project) { create(:empty_project) }
+
+ let(:filter) { described_class.new(input, project: project) }
+
+ describe '#replace_text_nodes_matching' do
+ let(:iterations) { 6000 }
+
+ describe 'with identical input and output HTML' do
+ benchmark_subject do
+ filter.replace_text_nodes_matching(User.reference_pattern) do |content|
+ content
+ end
+ end
+
+ it { is_expected.to iterate_per_second(iterations) }
+ end
+
+ describe 'with different input and output HTML' do
+ benchmark_subject do
+ filter.replace_text_nodes_matching(User.reference_pattern) do |content|
+ '@eve'
+ end
+ end
+
+ it { is_expected.to iterate_per_second(iterations) }
+ end
+ end
+end
diff --git a/spec/benchmarks/models/milestone_spec.rb b/spec/benchmarks/models/milestone_spec.rb
new file mode 100644
index 00000000000..a94afc4c40d
--- /dev/null
+++ b/spec/benchmarks/models/milestone_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe Milestone, benchmark: true do
+ describe '#sort_issues' do
+ let(:milestone) { create(:milestone) }
+
+ let(:issue1) { create(:issue, milestone: milestone) }
+ let(:issue2) { create(:issue, milestone: milestone) }
+ let(:issue3) { create(:issue, milestone: milestone) }
+
+ let(:issue_ids) { [issue3.id, issue2.id, issue1.id] }
+
+ benchmark_subject { milestone.sort_issues(issue_ids) }
+
+ it { is_expected.to iterate_per_second(500) }
+ end
+end
diff --git a/spec/benchmarks/models/project_team_spec.rb b/spec/benchmarks/models/project_team_spec.rb
new file mode 100644
index 00000000000..8b039ef7317
--- /dev/null
+++ b/spec/benchmarks/models/project_team_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe ProjectTeam, benchmark: true do
+ describe '#max_member_access' do
+ let(:group) { create(:group) }
+ let(:project) { create(:empty_project, group: group) }
+ let(:user) { create(:user) }
+
+ before do
+ project.team << [user, :master]
+
+ 5.times do
+ project.team << [create(:user), :reporter]
+
+ project.group.add_user(create(:user), :reporter)
+ end
+ end
+
+ benchmark_subject { project.team.max_member_access(user.id) }
+
+ it { is_expected.to iterate_per_second(35000) }
+ end
+end
diff --git a/spec/benchmarks/models/user_spec.rb b/spec/benchmarks/models/user_spec.rb
index 168be20b7a5..cc5c3904193 100644
--- a/spec/benchmarks/models/user_spec.rb
+++ b/spec/benchmarks/models/user_spec.rb
@@ -11,7 +11,9 @@ describe User, benchmark: true do
end
end
- let(:iterations) { 1000 }
+ # The iteration count is based on the query taking little over 1 ms when
+ # using PostgreSQL.
+ let(:iterations) { 900 }
describe 'using a capitalized username' do
benchmark_subject { User.by_login('Alice') }
diff --git a/spec/controllers/abuse_reports_controller_spec.rb b/spec/controllers/abuse_reports_controller_spec.rb
new file mode 100644
index 00000000000..0faab8d7ff0
--- /dev/null
+++ b/spec/controllers/abuse_reports_controller_spec.rb
@@ -0,0 +1,72 @@
+require 'spec_helper'
+
+describe AbuseReportsController do
+ let(:reporter) { create(:user) }
+ let(:user) { create(:user) }
+ let(:message) { "This user is a spammer" }
+
+ before do
+ sign_in(reporter)
+ end
+
+ describe "POST create" do
+ context "with admin notification email set" do
+ let(:admin_email) { "admin@example.com"}
+
+ before(:each) do
+ stub_application_setting(admin_notification_email: admin_email)
+ 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
+ post :create,
+ abuse_report: {
+ user_id: user.id,
+ message: message
+ }
+ end.to change { AbuseReport.count }.by(1)
+ end
+ end
+
+ context "without admin notification email set" do
+ before(:each) do
+ stub_application_setting(admin_notification_email: nil)
+ end
+
+ it "does not send a notification email" do
+ expect do
+ post :create,
+ abuse_report: {
+ user_id: user.id,
+ message: message
+ }
+ end.not_to change { ActionMailer::Base.deliveries.count }
+ end
+
+ it "saves the abuse report" do
+ expect do
+ post :create,
+ abuse_report: {
+ user_id: user.id,
+ message: message
+ }
+ end.to change { AbuseReport.count }.by(1)
+ end
+ end
+ end
+
+end
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index 7168db117d6..fcbe62cace8 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -37,6 +37,32 @@ describe Admin::UsersController do
end
end
+ describe 'PUT block/:id' do
+ let(:user) { create(:user) }
+
+ it 'blocks user' do
+ put :block, id: user.username
+ user.reload
+ expect(user.blocked?).to be_truthy
+ expect(flash[:notice]).to eq 'Successfully blocked'
+ end
+ end
+
+ describe 'PUT unblock/:id' do
+ let(:user) { create(:user) }
+
+ before do
+ user.block
+ end
+
+ it 'unblocks user' do
+ put :unblock, id: user.username
+ user.reload
+ expect(user.blocked?).to be_falsey
+ expect(flash[:notice]).to eq 'Successfully unblocked'
+ end
+ end
+
describe 'PUT unlock/:id' do
let(:user) { create(:user) }
diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb
index 766be578f7f..bbf8adef534 100644
--- a/spec/controllers/import/github_controller_spec.rb
+++ b/spec/controllers/import/github_controller_spec.rb
@@ -41,7 +41,7 @@ describe Import::GithubController do
it "assigns variables" do
@project = create(:project, import_type: 'github', creator_id: user.id)
- stub_client(repos: [@repo], orgs: [@org], org_repos: [@org_repo])
+ stub_client(repos: [@repo, @org_repo], orgs: [@org], org_repos: [@org_repo])
get :status
diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb
new file mode 100644
index 00000000000..3c6e54839b5
--- /dev/null
+++ b/spec/controllers/invites_controller_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe InvitesController do
+ let(:token) { '123456' }
+ let(:user) { create(:user) }
+ let(:member) { create(:project_member, invite_token: token, invite_email: 'test@abc.com', user: user) }
+
+ before do
+ controller.instance_variable_set(:@member, member)
+ sign_in(user)
+ end
+
+ describe 'GET #accept' do
+ it 'accepts user' do
+ get :accept, id: token
+ member.reload
+
+ expect(response.status).to eq(302)
+ expect(member.user).to eq(user)
+ expect(flash[:notice]).to include 'You have been granted'
+ end
+ end
+
+ describe 'GET #decline' do
+ it 'declines user' do
+ get :decline, id: token
+ expect{member.reload}.to raise_error ActiveRecord::RecordNotFound
+
+ expect(response.status).to eq(302)
+ expect(flash[:notice]).to include 'You have declined the invitation to join'
+ end
+ end
+end
diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb
index 91856ed0cc0..18a30033ed8 100644
--- a/spec/controllers/projects/repositories_controller_spec.rb
+++ b/spec/controllers/projects/repositories_controller_spec.rb
@@ -33,33 +33,5 @@ describe Projects::RepositoriesController do
expect(response.status).to eq(404)
end
end
-
- context "when the service doesn't return a path" do
-
- before do
- allow(service).to receive(:execute).and_return(nil)
- end
-
- it "reloads the page" do
- get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip"
-
- expect(response).to redirect_to(archive_namespace_project_repository_path(project.namespace, project, ref: "master", format: "zip"))
- end
- end
-
- context "when the service returns a path" do
-
- let(:path) { Rails.root.join("spec/fixtures/dk.png").to_s }
-
- before do
- allow(service).to receive(:execute).and_return(path)
- end
-
- it "sends the file" do
- get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip"
-
- expect(response.body).to eq(File.binread(path))
- end
- end
end
end
diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb
index d4ecd98e12d..ccd8c741c83 100644
--- a/spec/controllers/projects/services_controller_spec.rb
+++ b/spec/controllers/projects/services_controller_spec.rb
@@ -10,26 +10,43 @@ describe Projects::ServicesController do
project.team << [user, :master]
controller.instance_variable_set(:@project, project)
controller.instance_variable_set(:@service, service)
- request.env["HTTP_REFERER"] = "/"
end
- describe "#test" do
- context 'success' do
- it "should redirect and show success message" do
- expect(service).to receive(:test).and_return({ success: true, result: 'done' })
- get :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, format: :html
- expect(response.status).to redirect_to('/')
- expect(flash[:notice]).to eq('We sent a request to the provided URL')
- end
+ shared_examples_for 'services controller' do |referrer|
+ before do
+ request.env["HTTP_REFERER"] = referrer
end
- context 'failure' do
- it "should redirect and show failure message" do
- expect(service).to receive(:test).and_return({ success: false, result: 'Bad test' })
- get :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, format: :html
- expect(response.status).to redirect_to('/')
- expect(flash[:alert]).to eq('We tried to send a request to the provided URL but an error occurred: Bad test')
+ describe "#test" do
+ context 'success' do
+ it "should redirect and show success message" do
+ expect(service).to receive(:test).and_return({ success: true, result: 'done' })
+ get :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, format: :html
+ expect(response.status).to redirect_to('/')
+ expect(flash[:notice]).to eq('We sent a request to the provided URL')
+ end
+ end
+
+ context 'failure' do
+ it "should redirect and show failure message" do
+ expect(service).to receive(:test).and_return({ success: false, result: 'Bad test' })
+ get :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, format: :html
+ expect(response.status).to redirect_to('/')
+ expect(flash[:alert]).to eq('We tried to send a request to the provided URL but an error occurred: Bad test')
+ end
end
end
end
+
+ describe 'referrer defined' do
+ it_should_behave_like 'services controller' do
+ let!(:referrer) { "/" }
+ end
+ end
+
+ describe 'referrer undefined' do
+ it_should_behave_like 'services controller' do
+ let!(:referrer) { nil }
+ end
+ end
end
diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb
index f51abfedae5..93c4494c660 100644
--- a/spec/controllers/projects/uploads_controller_spec.rb
+++ b/spec/controllers/projects/uploads_controller_spec.rb
@@ -33,7 +33,7 @@ describe Projects::UploadsController do
it 'returns a content with original filename, new link, and correct type.' do
expect(response.body).to match '\"alt\":\"rails_sample\"'
- expect(response.body).to match "\"url\":\"http://localhost/#{project.path_with_namespace}/uploads"
+ expect(response.body).to match "\"url\":\"/uploads"
expect(response.body).to match '\"is_image\":true'
end
end
@@ -49,7 +49,7 @@ describe Projects::UploadsController do
it 'returns a content with original filename, new link, and correct type.' do
expect(response.body).to match '\"alt\":\"doc_sample.txt\"'
- expect(response.body).to match "\"url\":\"http://localhost/#{project.path_with_namespace}/uploads"
+ expect(response.body).to match "\"url\":\"/uploads"
expect(response.body).to match '\"is_image\":false'
end
end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 21beaf37fce..4bb47c6b025 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -22,17 +22,68 @@ describe ProjectsController do
end
end
- context "when requested with case sensitive namespace and project path" do
- it "redirects to the normalized path for case mismatch" do
- get :show, namespace_id: public_project.namespace.path, id: public_project.path.upcase
+ context "rendering default project view" do
+ render_views
+
+ it "renders the activity view" do
+ allow(controller).to receive(:current_user).and_return(user)
+ allow(user).to receive(:project_view).and_return('activity')
+
+ get :show, namespace_id: public_project.namespace.path, id: public_project.path
+ expect(response).to render_template('_activity')
+ end
+
+ it "renders the readme view" do
+ allow(controller).to receive(:current_user).and_return(user)
+ allow(user).to receive(:project_view).and_return('readme')
- expect(response).to redirect_to("/#{public_project.path_with_namespace}")
+ get :show, namespace_id: public_project.namespace.path, id: public_project.path
+ expect(response).to render_template('_readme')
end
- it "loads the page if normalized path matches request path" do
+ it "renders the files view" do
+ allow(controller).to receive(:current_user).and_return(user)
+ allow(user).to receive(:project_view).and_return('files')
+
get :show, namespace_id: public_project.namespace.path, id: public_project.path
+ expect(response).to render_template('_files')
+ end
+ end
+
+ context "when requested with case sensitive namespace and project path" do
+ context "when there is a match with the same casing" do
+ it "loads the project" do
+ get :show, namespace_id: public_project.namespace.path, id: public_project.path
+
+ expect(assigns(:project)).to eq(public_project)
+ expect(response.status).to eq(200)
+ end
+ end
+
+ context "when there is a match with different casing" do
+ it "redirects to the normalized path" do
+ get :show, namespace_id: public_project.namespace.path, id: public_project.path.upcase
+
+ expect(assigns(:project)).to eq(public_project)
+ expect(response).to redirect_to("/#{public_project.path_with_namespace}")
+ end
+
+
+ # MySQL queries are case insensitive by default, so this spec would fail.
+ if Gitlab::Database.postgresql?
+ context "when there is also a match with the same casing" do
- expect(response.status).to eq(200)
+ let!(:other_project) { create(:project, :public, namespace: public_project.namespace, path: public_project.path.upcase) }
+
+ it "loads the exactly matched project" do
+
+ get :show, namespace_id: public_project.namespace.path, id: public_project.path.upcase
+
+ expect(assigns(:project)).to eq(other_project)
+ expect(response.status).to eq(200)
+ end
+ end
+ end
end
end
end
@@ -62,4 +113,50 @@ describe ProjectsController do
expect(user.starred?(public_project)).to be_falsey
end
end
+
+ describe "DELETE remove_fork" do
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ end
+
+ context 'with forked project' do
+ let(:project_fork) { create(:project, namespace: user.namespace) }
+
+ before do
+ create(:forked_project_link, forked_to_project: project_fork)
+ end
+
+ it 'should remove fork from project' do
+ delete(:remove_fork,
+ namespace_id: project_fork.namespace.to_param,
+ id: project_fork.to_param, format: :js)
+
+ expect(project_fork.forked?).to be_falsey
+ expect(flash[:notice]).to eq('The fork relationship has been removed.')
+ expect(response).to render_template(:remove_fork)
+ end
+ end
+
+ context 'when project not forked' do
+ let(:unforked_project) { create(:project, namespace: user.namespace) }
+
+ it 'should do nothing if project was not forked' do
+ delete(:remove_fork,
+ namespace_id: unforked_project.namespace.to_param,
+ id: unforked_project.to_param, format: :js)
+
+ expect(flash[:notice]).to be_nil
+ expect(response).to render_template(:remove_fork)
+ end
+ end
+ end
+
+ it "does nothing if user is not signed in" do
+ delete(:remove_fork,
+ namespace_id: project.namespace.to_param,
+ id: project.to_param, format: :js)
+ expect(response.status).to eq(401)
+ end
+ end
end
diff --git a/spec/features/builds_spec.rb b/spec/features/builds_spec.rb
index 924047a0d8f..154857e77fe 100644
--- a/spec/features/builds_spec.rb
+++ b/spec/features/builds_spec.rb
@@ -9,6 +9,54 @@ describe "Builds" do
@gl_project.team << [@user, :master]
end
+ describe "GET /:project/builds" do
+ context "Running scope" do
+ before do
+ @build.run!
+ visit namespace_project_builds_path(@gl_project.namespace, @gl_project)
+ end
+
+ it { expect(page).to have_content 'Running' }
+ it { expect(page).to have_content 'Cancel all' }
+ it { expect(page).to have_content @build.short_sha }
+ it { expect(page).to have_content @build.ref }
+ it { expect(page).to have_content @build.name }
+ end
+
+ context "Finished scope" do
+ before do
+ @build.run!
+ visit namespace_project_builds_path(@gl_project.namespace, @gl_project, scope: :finished)
+ end
+
+ it { expect(page).to have_content 'No builds to show' }
+ it { expect(page).to have_content 'Cancel all' }
+ end
+
+ context "All builds" do
+ before do
+ @gl_project.ci_builds.running_or_pending.each(&:success)
+ visit namespace_project_builds_path(@gl_project.namespace, @gl_project, scope: :all)
+ end
+
+ it { expect(page).to have_content 'All' }
+ it { expect(page).to have_content @build.short_sha }
+ it { expect(page).to have_content @build.ref }
+ it { expect(page).to have_content @build.name }
+ it { expect(page).to_not have_content 'Cancel all' }
+ end
+ end
+
+ describe "GET /:project/builds/:id/cancel_all" do
+ before do
+ @build.run!
+ visit cancel_all_namespace_project_builds_path(@gl_project.namespace, @gl_project)
+ end
+
+ it { expect(page).to have_content 'No builds to show' }
+ it { expect(page).to_not have_content 'Cancel all' }
+ end
+
describe "GET /:project/builds/:id" do
before do
visit namespace_project_build_path(@gl_project.namespace, @gl_project, @build)
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index cbb6360069b..1adc2cdf70a 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -29,9 +29,17 @@ describe "Commits" do
it { expect(page).to have_content @commit.git_author_name }
end
- describe "Cancel commit" do
+ describe "Cancel all builds" do
it "cancels commit" do
visit ci_status_path(@commit)
+ click_on "Cancel all"
+ expect(page).to have_content "canceled"
+ end
+ end
+
+ describe "Cancel build" do
+ it "cancels build" do
+ visit ci_status_path(@commit)
click_on "Cancel"
expect(page).to have_content "canceled"
end
diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb
index c557a1061af..fdd8cf07b12 100644
--- a/spec/features/markdown_spec.rb
+++ b/spec/features/markdown_spec.rb
@@ -220,7 +220,7 @@ describe 'GitLab Markdown', feature: true do
end
end
- # `markdown` calls these two methods
+ # Fake a `current_user` helper
def current_user
@feat.user
end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index aac93b17a38..09fcff2444a 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -34,6 +34,27 @@ feature 'Project', feature: true do
end
end
+ describe 'remove forked relationship', js: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, namespace: user.namespace) }
+
+ before do
+ login_with user
+ create(:forked_project_link, forked_to_project: project)
+ visit edit_namespace_project_path(project.namespace, project)
+ end
+
+ it 'should remove fork' do
+ expect(page).to have_content 'Remove fork relationship'
+
+ remove_with_confirm('Remove fork relationship', project.path)
+
+ expect(page).to have_content 'The fork relationship has been removed.'
+ expect(project.forked?).to be_falsey
+ expect(page).not_to have_content 'Remove fork relationship'
+ end
+ end
+
describe 'removal', js: true do
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
@@ -45,13 +66,13 @@ feature 'Project', feature: true do
end
it 'should remove project' do
- expect { remove_project }.to change {Project.count}.by(-1)
+ expect { remove_with_confirm('Remove project', project.path) }.to change {Project.count}.by(-1)
end
end
- def remove_project
- click_button "Remove project"
- fill_in 'confirm_name_input', with: project.path
+ def remove_with_confirm(button_text, confirm_with)
+ click_button button_text
+ fill_in 'confirm_name_input', with: confirm_with
click_button 'Confirm'
end
end
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 742420f550e..1dfae0fbd3f 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -99,6 +99,15 @@ describe ApplicationHelper do
helper.avatar_icon('foo@example.com', 20)
end
+
+ describe 'using a User' do
+ it 'should return an URL for the avatar' do
+ user = create(:user, avatar: File.open(avatar_file_path))
+
+ expect(helper.avatar_icon(user).to_s).
+ to match("/uploads/user/avatar/#{user.id}/banana_sample.gif")
+ end
+ end
end
describe 'gravatar_icon' do
diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb
index 20ae29e2bd3..762ec25c4f5 100644
--- a/spec/helpers/gitlab_markdown_helper_spec.rb
+++ b/spec/helpers/gitlab_markdown_helper_spec.rb
@@ -11,12 +11,15 @@ describe GitlabMarkdownHelper do
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let(:snippet) { create(:project_snippet, project: project) }
- # Helper expects a current_user method.
- let(:current_user) { user }
-
before do
+ # Ensure the generated reference links aren't redacted
+ project.team << [user, :master]
+
# Helper expects a @project instance variable
- @project = project
+ helper.instance_variable_set(:@project, project)
+
+ # Stub the `current_user` helper
+ allow(helper).to receive(:current_user).and_return(user)
end
describe "#markdown" do
@@ -25,23 +28,23 @@ describe GitlabMarkdownHelper do
it "should link to the merge request" do
expected = namespace_project_merge_request_path(project.namespace, project, merge_request)
- expect(markdown(actual)).to match(expected)
+ expect(helper.markdown(actual)).to match(expected)
end
it "should link to the commit" do
expected = namespace_project_commit_path(project.namespace, project, commit)
- expect(markdown(actual)).to match(expected)
+ expect(helper.markdown(actual)).to match(expected)
end
it "should link to the issue" do
expected = namespace_project_issue_path(project.namespace, project, issue)
- expect(markdown(actual)).to match(expected)
+ expect(helper.markdown(actual)).to match(expected)
end
end
describe "override default project" do
let(:actual) { issue.to_reference }
- let(:second_project) { create(:project) }
+ let(:second_project) { create(:project, :public) }
let(:second_issue) { create(:issue, project: second_project) }
it 'should link to the issue' do
@@ -56,7 +59,7 @@ describe GitlabMarkdownHelper do
let(:issues) { create_list(:issue, 2, project: project) }
it 'should handle references nested in links with all the text' do
- actual = link_to_gfm("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real", commit_path)
+ actual = helper.link_to_gfm("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real", commit_path)
doc = Nokogiri::HTML.parse(actual)
# Make sure we didn't create invalid markup
@@ -86,7 +89,7 @@ describe GitlabMarkdownHelper do
end
it 'should forward HTML options' do
- actual = link_to_gfm("Fixed in #{commit.id}", commit_path, class: 'foo')
+ actual = helper.link_to_gfm("Fixed in #{commit.id}", commit_path, class: 'foo')
doc = Nokogiri::HTML.parse(actual)
expect(doc.css('a')).to satisfy do |v|
@@ -97,13 +100,13 @@ describe GitlabMarkdownHelper do
it "escapes HTML passed in as the body" do
actual = "This is a <h1>test</h1> - see #{issues[0].to_reference}"
- expect(link_to_gfm(actual, commit_path)).
+ expect(helper.link_to_gfm(actual, commit_path)).
to match('&lt;h1&gt;test&lt;/h1&gt;')
end
it 'ignores reference links when they are the entire body' do
text = issues[0].to_reference
- act = link_to_gfm(text, '/foo')
+ act = helper.link_to_gfm(text, '/foo')
expect(act).to eq %Q(<a href="/foo">#{issues[0].to_reference}</a>)
end
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index c08ddb4cae1..78a6b631eb2 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -117,4 +117,14 @@ describe IssuesHelper do
end
end
+ describe "#merge_requests_sentence" do
+ subject { merge_requests_sentence(merge_requests)}
+ let(:merge_requests) do
+ [ build(:merge_request, iid: 1), build(:merge_request, iid: 2),
+ build(:merge_request, iid: 3)]
+ end
+
+ it { is_expected.to eq("!1, !2, or !3") }
+ end
+
end
diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index fb70a36dc02..0c8d06b7059 100644
--- a/spec/helpers/labels_helper_spec.rb
+++ b/spec/helpers/labels_helper_spec.rb
@@ -14,11 +14,6 @@ describe LabelsHelper do
expect(label).not_to receive(:project)
link_to_label(label)
end
-
- it 'includes option for "No Label"' do
- result = project_labels_options(project)
- expect(result).to include('No Label')
- end
end
context 'without @project set' do
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 53e56ebff44..f2efb528aeb 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -70,4 +70,18 @@ describe ProjectsHelper do
expect(helper.send(:readme_cache_key)).to eq("#{project.path_with_namespace}-nil-readme")
end
end
+
+ describe 'link_to_member' do
+ let(:group) { create(:group) }
+ let(:project) { create(:empty_project, group: group) }
+ let(:user) { create(:user) }
+
+ describe 'using the default options' do
+ it 'returns an HTML link to the user' do
+ link = helper.link_to_member(project, user)
+
+ expect(link).to match(%r{/u/#{user.username}})
+ end
+ end
+ end
end
diff --git a/spec/helpers/runners_helper_spec.rb b/spec/helpers/runners_helper_spec.rb
index b3d635a1932..35f91b7decf 100644
--- a/spec/helpers/runners_helper_spec.rb
+++ b/spec/helpers/runners_helper_spec.rb
@@ -12,7 +12,7 @@ describe RunnersHelper do
end
it "returns online text" do
- runner = FactoryGirl.build(:ci_runner, contacted_at: 1.hour.ago, active: true)
+ runner = FactoryGirl.build(:ci_runner, contacted_at: 1.second.ago, active: true)
expect(runner_status_icon(runner)).to include("Runner is online")
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 aba957da488..abdb6b89ac5 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -24,7 +24,8 @@ module Ci
commands: "pwd\nrspec",
tag_list: [],
options: {},
- allow_failure: false
+ allow_failure: false,
+ when: "on_success"
})
end
@@ -125,7 +126,8 @@ module Ci
image: "ruby:2.1",
services: ["mysql"]
},
- allow_failure: false
+ allow_failure: false,
+ when: "on_success"
})
end
@@ -152,7 +154,8 @@ module Ci
image: "ruby:2.5",
services: ["postgresql"]
},
- allow_failure: false
+ allow_failure: false,
+ when: "on_success"
})
end
end
@@ -174,6 +177,21 @@ module Ci
end
end
+ describe "When" do
+ %w(on_success on_failure always).each do |when_state|
+ it "returns #{when_state} when defined" do
+ config = YAML.dump({
+ rspec: { script: "rspec", when: when_state }
+ })
+
+ config_processor = GitlabCiYamlProcessor.new(config)
+ builds = config_processor.builds_for_stage_and_ref("test", "master")
+ expect(builds.size).to eq(1)
+ expect(builds.first[:when]).to eq(when_state)
+ end
+ end
+ end
+
describe "Error handling" do
it "indicates that object is invalid" do
expect{GitlabCiYamlProcessor.new("invalid_yaml\n!ccdvlf%612334@@@@")}.to raise_error(GitlabCiYamlProcessor::ValidationError)
@@ -200,6 +218,20 @@ module Ci
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "image should be a string")
end
+ it "returns errors if job name is blank" do
+ config = YAML.dump({ '' => { script: "test" } })
+ expect do
+ GitlabCiYamlProcessor.new(config)
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "job name should be non-empty string")
+ end
+
+ it "returns errors if job name is non-string" do
+ config = YAML.dump({ 10 => { script: "test" } })
+ expect do
+ GitlabCiYamlProcessor.new(config)
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "job name should be non-empty string")
+ end
+
it "returns errors if job image parameter is invalid" do
config = YAML.dump({ rspec: { script: "test", image: ["test"] } })
expect do
@@ -311,6 +343,13 @@ module Ci
GitlabCiYamlProcessor.new(config)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-valued strings")
end
+
+ it "returns errors if job when is not on_success, on_failure or always" do
+ config = YAML.dump({ rspec: { script: "test", when: 1 } })
+ expect do
+ GitlabCiYamlProcessor.new(config)
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: when parameter should be on_success, on_failure or always")
+ end
end
end
end
diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb
index 5d7ff4f6122..21254f778d3 100644
--- a/spec/lib/gitlab/closing_issue_extractor_spec.rb
+++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb
@@ -140,28 +140,28 @@ describe Gitlab::ClosingIssueExtractor do
message = "Closes #{reference} and fix #{reference2}"
expect(subject.closed_by_message(message)).
- to eq([issue, other_issue])
+ to match_array([issue, other_issue])
end
it 'fetches comma-separated issues references in single line message' do
message = "Closes #{reference}, closes #{reference2}"
expect(subject.closed_by_message(message)).
- to eq([issue, other_issue])
+ to match_array([issue, other_issue])
end
it 'fetches comma-separated issues numbers in single line message' do
message = "Closes #{reference}, #{reference2} and #{reference3}"
expect(subject.closed_by_message(message)).
- to eq([issue, other_issue, third_issue])
+ to match_array([issue, other_issue, third_issue])
end
it 'fetches issues in multi-line message' do
message = "Awesome commit (closes #{reference})\nAlso fixes #{reference2}"
expect(subject.closed_by_message(message)).
- to eq([issue, other_issue])
+ to match_array([issue, other_issue])
end
it 'fetches issues in hybrid message' do
@@ -169,7 +169,7 @@ describe Gitlab::ClosingIssueExtractor do
"Also fixing issues #{reference2}, #{reference3} and #4"
expect(subject.closed_by_message(message)).
- to eq([issue, other_issue, third_issue])
+ to match_array([issue, other_issue, third_issue])
end
end
end
diff --git a/spec/lib/gitlab/email/attachment_uploader_spec.rb b/spec/lib/gitlab/email/attachment_uploader_spec.rb
index e8208e15e29..8fb432367b6 100644
--- a/spec/lib/gitlab/email/attachment_uploader_spec.rb
+++ b/spec/lib/gitlab/email/attachment_uploader_spec.rb
@@ -13,7 +13,6 @@ describe Gitlab::Email::AttachmentUploader do
expect(link).not_to be_nil
expect(link[:is_image]).to be_truthy
expect(link[:alt]).to eq("bricks")
- expect(link[:url]).to include("/#{project.path_with_namespace}")
expect(link[:url]).to include("bricks.png")
end
end
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 3c6c84a0416..e5b8d723fe5 100644
--- a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
@@ -4,7 +4,7 @@ module Gitlab::Markdown
describe CommitRangeReferenceFilter do
include FilterSpecHelper
- let(:project) { create(:project) }
+ let(:project) { create(:project, :public) }
let(:commit1) { project.commit }
let(:commit2) { project.commit("HEAD~2") }
@@ -75,12 +75,20 @@ module Gitlab::Markdown
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range'
end
- it 'includes a data-project-id attribute' do
+ it 'includes a data-project attribute' do
doc = filter("See #{reference}")
link = doc.css('a').first
- expect(link).to have_attribute('data-project-id')
- expect(link.attr('data-project-id')).to eq project.id.to_s
+ expect(link).to have_attribute('data-project')
+ expect(link.attr('data-project')).to eq project.id.to_s
+ end
+
+ it 'includes a data-commit-range attribute' do
+ doc = 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
end
it 'supports an :only_path option' do
@@ -92,59 +100,45 @@ module Gitlab::Markdown
end
it 'adds to the results hash' do
- result = pipeline_result("See #{reference}")
+ result = reference_pipeline_result("See #{reference}")
expect(result[:references][:commit_range]).not_to be_empty
end
end
context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
- let(:project2) { create(:project, namespace: namespace) }
+ let(:project2) { create(:project, :public, namespace: namespace) }
let(:reference) { range.to_reference(project) }
before do
range.project = project2
end
- context 'when user can access reference' do
- before { allow_cross_reference! }
-
- it 'links to a valid reference' do
- doc = 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}.)")
-
- exp = Regexp.escape("#{project2.to_reference}@#{range.to_s}")
- expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
- end
+ it 'links to a valid reference' do
+ doc = filter("See #{reference}")
- 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(doc.css('a').first.attr('href')).
+ to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param)
+ end
- exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}"
- expect(filter(act).to_html).to eq exp
- end
+ it 'links with adjacent text' do
+ doc = filter("Fixed (#{reference}.)")
- it 'adds to the results hash' do
- result = pipeline_result("See #{reference}")
- expect(result[:references][:commit_range]).not_to be_empty
- end
+ exp = Regexp.escape("#{project2.to_reference}@#{range.to_s}")
+ expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
end
- context 'when user cannot access reference' do
- before { disallow_cross_reference! }
+ 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
- it 'ignores valid references' do
- exp = act = "See #{reference}"
+ exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}"
+ expect(filter(act).to_html).to eq exp
+ end
- expect(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
end
diff --git a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
index 9ed438252b3..d080efbf3d4 100644
--- a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
@@ -4,7 +4,7 @@ module Gitlab::Markdown
describe CommitReferenceFilter do
include FilterSpecHelper
- let(:project) { create(:project) }
+ let(:project) { create(:project, :public) }
let(:commit) { project.commit }
it 'requires project context' do
@@ -71,12 +71,20 @@ module Gitlab::Markdown
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit'
end
- it 'includes a data-project-id attribute' do
+ it 'includes a data-project attribute' do
doc = filter("See #{reference}")
link = doc.css('a').first
- expect(link).to have_attribute('data-project-id')
- expect(link.attr('data-project-id')).to eq project.id.to_s
+ expect(link).to have_attribute('data-project')
+ expect(link.attr('data-project')).to eq project.id.to_s
+ end
+
+ it 'includes a data-commit attribute' do
+ doc = filter("See #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-commit')
+ expect(link.attr('data-commit')).to eq commit.id
end
it 'supports an :only_path context' do
@@ -88,53 +96,39 @@ module Gitlab::Markdown
end
it 'adds to the results hash' do
- result = pipeline_result("See #{reference}")
+ result = reference_pipeline_result("See #{reference}")
expect(result[:references][:commit]).not_to be_empty
end
end
context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
- let(:project2) { create(:project, namespace: namespace) }
+ let(:project2) { create(:project, :public, namespace: namespace) }
let(:commit) { project2.commit }
let(:reference) { commit.to_reference(project) }
- context 'when user can access reference' do
- before { allow_cross_reference! }
-
- it 'links to a valid reference' do
- doc = 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}.)")
+ it 'links to a valid reference' do
+ doc = filter("See #{reference}")
- exp = Regexp.escape(project2.to_reference)
- expect(doc.to_html).to match(/\(<a.+>#{exp}@#{commit.short_id}<\/a>\.\)/)
- end
+ expect(doc.css('a').first.attr('href')).
+ to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id)
+ end
- it 'ignores invalid commit IDs on the referenced project' do
- exp = act = "Committed #{invalidate_reference(reference)}"
- expect(filter(act).to_html).to eq exp
- end
+ it 'links with adjacent text' do
+ doc = filter("Fixed (#{reference}.)")
- it 'adds to the results hash' do
- result = pipeline_result("See #{reference}")
- expect(result[:references][:commit]).not_to be_empty
- end
+ exp = Regexp.escape(project2.to_reference)
+ expect(doc.to_html).to match(/\(<a.+>#{exp}@#{commit.short_id}<\/a>\.\)/)
end
- context 'when user cannot access reference' do
- before { disallow_cross_reference! }
-
- it 'ignores valid references' do
- exp = act = "See #{reference}"
+ it 'ignores invalid commit IDs on the referenced project' do
+ exp = act = "Committed #{invalidate_reference(reference)}"
+ expect(filter(act).to_html).to eq exp
+ end
- expect(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
end
diff --git a/spec/lib/gitlab/markdown/cross_project_reference_spec.rb b/spec/lib/gitlab/markdown/cross_project_reference_spec.rb
index 4698d6138c2..8d4f9e403a6 100644
--- a/spec/lib/gitlab/markdown/cross_project_reference_spec.rb
+++ b/spec/lib/gitlab/markdown/cross_project_reference_spec.rb
@@ -2,20 +2,16 @@ require 'spec_helper'
module Gitlab::Markdown
describe CrossProjectReference do
- # context in the html-pipeline sense, not in the rspec sense
- let(:context) do
- {
- current_user: double('user'),
- project: double('project')
- }
- end
-
include described_class
describe '#project_from_ref' do
context 'when no project was referenced' do
it 'returns the project from context' do
- expect(project_from_ref(nil)).to eq context[:project]
+ project = double
+
+ allow(self).to receive(:context).and_return({ project: project })
+
+ expect(project_from_ref(nil)).to eq project
end
end
@@ -26,29 +22,13 @@ module Gitlab::Markdown
end
context 'when referenced project exists' do
- let(:project2) { double('referenced project') }
+ it 'returns the referenced project' do
+ project2 = double('referenced project')
- before do
expect(Project).to receive(:find_with_namespace).
with('cross/reference').and_return(project2)
- end
-
- context 'and the user has permission to read it' do
- it 'returns the referenced project' do
- expect(self).to receive(:user_can_reference_project?).
- with(project2).and_return(true)
-
- expect(project_from_ref('cross/reference')).to eq project2
- end
- end
-
- context 'and the user does not have permission to read it' do
- it 'returns nil' do
- expect(self).to receive(:user_can_reference_project?).
- with(project2).and_return(false)
- expect(project_from_ref('cross/reference')).to be_nil
- end
+ expect(project_from_ref('cross/reference')).to eq project2
end
end
end
diff --git a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
index 1dd54f58588..94c80ae6611 100644
--- a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
@@ -8,7 +8,7 @@ module Gitlab::Markdown
IssuesHelper
end
- let(:project) { create(:empty_project) }
+ let(:project) { create(:empty_project, :public) }
let(:issue) { create(:issue, project: project) }
it 'requires project context' do
@@ -68,12 +68,20 @@ module Gitlab::Markdown
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
end
- it 'includes a data-project-id attribute' do
+ it 'includes a data-project attribute' do
doc = filter("Issue #{reference}")
link = doc.css('a').first
- expect(link).to have_attribute('data-project-id')
- expect(link.attr('data-project-id')).to eq project.id.to_s
+ expect(link).to have_attribute('data-project')
+ expect(link.attr('data-project')).to eq project.id.to_s
+ end
+
+ it 'includes a data-issue attribute' do
+ doc = filter("See #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-issue')
+ expect(link.attr('data-issue')).to eq issue.id.to_s
end
it 'supports an :only_path context' do
@@ -85,60 +93,46 @@ module Gitlab::Markdown
end
it 'adds to the results hash' do
- result = pipeline_result("Fixed #{reference}")
+ result = reference_pipeline_result("Fixed #{reference}")
expect(result[:references][:issue]).to eq [issue]
end
end
context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
- let(:project2) { create(:empty_project, namespace: namespace) }
+ let(:project2) { create(:empty_project, :public, namespace: namespace) }
let(:issue) { create(:issue, project: project2) }
let(:reference) { issue.to_reference(project) }
- context 'when user can access reference' do
- before { allow_cross_reference! }
-
- it 'ignores valid references when cross-reference project uses external tracker' do
- expect_any_instance_of(Project).to receive(:get_issue).
- with(issue.iid).and_return(nil)
-
- exp = act = "Issue #{reference}"
- expect(filter(act).to_html).to eq exp
- end
+ it 'ignores valid references when cross-reference project uses external tracker' do
+ expect_any_instance_of(Project).to receive(:get_issue).
+ with(issue.iid).and_return(nil)
- it 'links to a valid reference' do
- doc = 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}.)")
- expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
- end
+ exp = act = "Issue #{reference}"
+ expect(filter(act).to_html).to eq exp
+ end
- it 'ignores invalid issue IDs on the referenced project' do
- exp = act = "Fixed #{invalidate_reference(reference)}"
+ it 'links to a valid reference' do
+ doc = filter("See #{reference}")
- expect(filter(act).to_html).to eq exp
- end
+ expect(doc.css('a').first.attr('href')).
+ to eq helper.url_for_issue(issue.iid, project2)
+ end
- it 'adds to the results hash' do
- result = pipeline_result("Fixed #{reference}")
- expect(result[:references][:issue]).to eq [issue]
- end
+ it 'links with adjacent text' do
+ doc = filter("Fixed (#{reference}.)")
+ expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
- context 'when user cannot access reference' do
- before { disallow_cross_reference! }
+ it 'ignores invalid issue IDs on the referenced project' do
+ exp = act = "Fixed #{invalidate_reference(reference)}"
- it 'ignores valid references' do
- exp = act = "See #{reference}"
+ expect(filter(act).to_html).to eq exp
+ end
- expect(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
end
diff --git a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
index e32089de376..fc21b65a843 100644
--- a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
@@ -5,7 +5,7 @@ module Gitlab::Markdown
describe LabelReferenceFilter do
include FilterSpecHelper
- let(:project) { create(:empty_project) }
+ let(:project) { create(:empty_project, :public) }
let(:label) { create(:label, project: project) }
let(:reference) { label.to_reference }
@@ -25,12 +25,20 @@ module Gitlab::Markdown
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label'
end
- it 'includes a data-project-id attribute' do
+ it 'includes a data-project attribute' do
doc = filter("Label #{reference}")
link = doc.css('a').first
- expect(link).to have_attribute('data-project-id')
- expect(link.attr('data-project-id')).to eq project.id.to_s
+ 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 = 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 'supports an :only_path context' do
@@ -42,7 +50,7 @@ module Gitlab::Markdown
end
it 'adds to the results hash' do
- result = pipeline_result("Label #{reference}")
+ result = reference_pipeline_result("Label #{reference}")
expect(result[:references][:label]).to eq [label]
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 66616b93368..3ef6cdfff33 100644
--- a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
@@ -4,7 +4,7 @@ module Gitlab::Markdown
describe MergeRequestReferenceFilter do
include FilterSpecHelper
- let(:project) { create(:project) }
+ let(:project) { create(:project, :public) }
let(:merge) { create(:merge_request, source_project: project) }
it 'requires project context' do
@@ -56,12 +56,20 @@ module Gitlab::Markdown
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request'
end
- it 'includes a data-project-id attribute' do
+ it 'includes a data-project attribute' do
doc = filter("Merge #{reference}")
link = doc.css('a').first
- expect(link).to have_attribute('data-project-id')
- expect(link.attr('data-project-id')).to eq project.id.to_s
+ expect(link).to have_attribute('data-project')
+ expect(link.attr('data-project')).to eq project.id.to_s
+ end
+
+ it 'includes a data-merge-request attribute' do
+ doc = filter("See #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-merge-request')
+ expect(link.attr('data-merge-request')).to eq merge.id.to_s
end
it 'supports an :only_path context' do
@@ -73,53 +81,39 @@ module Gitlab::Markdown
end
it 'adds to the results hash' do
- result = pipeline_result("Merge #{reference}")
+ result = reference_pipeline_result("Merge #{reference}")
expect(result[:references][:merge_request]).to eq [merge]
end
end
context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
- let(:project2) { create(:project, namespace: namespace) }
+ let(:project2) { create(:project, :public, namespace: namespace) }
let(:merge) { create(:merge_request, source_project: project2) }
let(:reference) { merge.to_reference(project) }
- context 'when user can access reference' do
- before { allow_cross_reference! }
-
- it 'links to a valid reference' do
- doc = filter("See #{reference}")
-
- expect(doc.css('a').first.attr('href')).
- to eq urls.namespace_project_merge_request_url(project2.namespace,
- project, merge)
- end
-
- it 'links with adjacent text' do
- doc = 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)}"
+ it 'links to a valid reference' do
+ doc = filter("See #{reference}")
- expect(filter(act).to_html).to eq exp
- end
+ expect(doc.css('a').first.attr('href')).
+ to eq urls.namespace_project_merge_request_url(project2.namespace,
+ project, merge)
+ end
- it 'adds to the results hash' do
- result = pipeline_result("Merge #{reference}")
- expect(result[:references][:merge_request]).to eq [merge]
- end
+ it 'links with adjacent text' do
+ doc = filter("Merge (#{reference}.)")
+ expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
- context 'when user cannot access reference' do
- before { disallow_cross_reference! }
+ it 'ignores invalid merge IDs on the referenced project' do
+ exp = act = "Merge #{invalidate_reference(reference)}"
- it 'ignores valid references' do
- exp = act = "See #{reference}"
+ expect(filter(act).to_html).to eq exp
+ end
- expect(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
end
diff --git a/spec/lib/gitlab/markdown/redactor_filter_spec.rb b/spec/lib/gitlab/markdown/redactor_filter_spec.rb
new file mode 100644
index 00000000000..eea3f1cf370
--- /dev/null
+++ b/spec/lib/gitlab/markdown/redactor_filter_spec.rb
@@ -0,0 +1,91 @@
+require 'spec_helper'
+
+module Gitlab::Markdown
+ describe RedactorFilter do
+ include ActionView::Helpers::UrlHelper
+ include FilterSpecHelper
+
+ it 'ignores non-GFM links' do
+ html = %(See <a href="https://google.com/">Google</a>)
+ doc = filter(html, current_user: double)
+
+ expect(doc.css('a').length).to eq 1
+ end
+
+ def reference_link(data)
+ link_to('text', '', class: 'gfm', data: data)
+ end
+
+ context 'with data-project' do
+ it 'removes unpermitted Project references' do
+ user = create(:user)
+ project = create(:empty_project)
+
+ link = reference_link(project: project.id, reference_filter: Gitlab::Markdown::ReferenceFilter.name)
+ doc = filter(link, current_user: user)
+
+ expect(doc.css('a').length).to eq 0
+ end
+
+ it 'allows permitted Project references' do
+ user = create(:user)
+ project = create(:empty_project)
+ project.team << [user, :master]
+
+ link = reference_link(project: project.id, reference_filter: Gitlab::Markdown::ReferenceFilter.name)
+ doc = filter(link, current_user: user)
+
+ expect(doc.css('a').length).to eq 1
+ end
+
+ it 'handles invalid Project references' do
+ link = reference_link(project: 12345, reference_filter: Gitlab::Markdown::ReferenceFilter.name)
+
+ expect { filter(link) }.not_to raise_error
+ end
+ end
+
+ context "for user references" do
+
+ context 'with data-group' do
+ it 'removes unpermitted Group references' do
+ user = create(:user)
+ group = create(:group)
+
+ link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
+ doc = filter(link, current_user: user)
+
+ expect(doc.css('a').length).to eq 0
+ end
+
+ it 'allows permitted Group references' do
+ user = create(:user)
+ group = create(:group)
+ group.add_developer(user)
+
+ link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
+ doc = filter(link, current_user: user)
+
+ expect(doc.css('a').length).to eq 1
+ end
+
+ it 'handles invalid Group references' do
+ link = reference_link(group: 12345, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
+
+ expect { filter(link) }.not_to raise_error
+ end
+ end
+
+ context 'with data-user' do
+ it 'allows any User reference' do
+ user = create(:user)
+
+ link = reference_link(user: user.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
+ doc = filter(link)
+
+ expect(doc.css('a').length).to eq 1
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/markdown/reference_gatherer_filter_spec.rb b/spec/lib/gitlab/markdown/reference_gatherer_filter_spec.rb
new file mode 100644
index 00000000000..4fa473ad191
--- /dev/null
+++ b/spec/lib/gitlab/markdown/reference_gatherer_filter_spec.rb
@@ -0,0 +1,89 @@
+require 'spec_helper'
+
+module Gitlab::Markdown
+ describe ReferenceGathererFilter do
+ include ActionView::Helpers::UrlHelper
+ include FilterSpecHelper
+
+ def reference_link(data)
+ link_to('text', '', class: 'gfm', data: data)
+ end
+
+ context "for issue references" do
+
+ context 'with data-project' do
+ it 'removes unpermitted Project references' do
+ user = create(:user)
+ project = create(:empty_project)
+ issue = create(:issue, project: project)
+
+ link = reference_link(project: project.id, issue: issue.id, reference_filter: Gitlab::Markdown::IssueReferenceFilter.name)
+ result = pipeline_result(link, current_user: user)
+
+ expect(result[:references][:issue]).to be_empty
+ end
+
+ it 'allows permitted Project references' do
+ user = create(:user)
+ project = create(:empty_project)
+ issue = create(:issue, project: project)
+ project.team << [user, :master]
+
+ link = reference_link(project: project.id, issue: issue.id, reference_filter: Gitlab::Markdown::IssueReferenceFilter.name)
+ result = pipeline_result(link, current_user: user)
+
+ expect(result[:references][:issue]).to eq([issue])
+ end
+
+ it 'handles invalid Project references' do
+ link = reference_link(project: 12345, issue: 12345, reference_filter: Gitlab::Markdown::IssueReferenceFilter.name)
+
+ expect { pipeline_result(link) }.not_to raise_error
+ end
+ end
+ end
+
+ context "for user references" do
+
+ context 'with data-group' do
+ it 'removes unpermitted Group references' do
+ user = create(:user)
+ group = create(:group)
+
+ link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
+ result = pipeline_result(link, current_user: user)
+
+ expect(result[:references][:user]).to be_empty
+ end
+
+ it 'allows permitted Group references' do
+ user = create(:user)
+ group = create(:group)
+ group.add_developer(user)
+
+ link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
+ result = pipeline_result(link, current_user: user)
+
+ expect(result[:references][:user]).to eq([user])
+ end
+
+ it 'handles invalid Group references' do
+ link = reference_link(group: 12345, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
+
+ expect { pipeline_result(link) }.not_to raise_error
+ end
+ end
+
+ context 'with data-user' do
+ it 'allows any User reference' do
+ user = create(:user)
+
+ link = reference_link(user: user.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
+ result = pipeline_result(link)
+
+ expect(result[:references][:user]).to eq([user])
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
index fd3f0d20fad..9d9652dba46 100644
--- a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
@@ -4,7 +4,7 @@ module Gitlab::Markdown
describe SnippetReferenceFilter do
include FilterSpecHelper
- let(:project) { create(:empty_project) }
+ let(:project) { create(:empty_project, :public) }
let(:snippet) { create(:project_snippet, project: project) }
let(:reference) { snippet.to_reference }
@@ -55,12 +55,20 @@ module Gitlab::Markdown
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet'
end
- it 'includes a data-project-id attribute' do
+ it 'includes a data-project attribute' do
doc = filter("Snippet #{reference}")
link = doc.css('a').first
- expect(link).to have_attribute('data-project-id')
- expect(link.attr('data-project-id')).to eq project.id.to_s
+ expect(link).to have_attribute('data-project')
+ expect(link.attr('data-project')).to eq project.id.to_s
+ end
+
+ it 'includes a data-snippet attribute' do
+ doc = filter("See #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-snippet')
+ expect(link.attr('data-snippet')).to eq snippet.id.to_s
end
it 'supports an :only_path context' do
@@ -72,52 +80,38 @@ module Gitlab::Markdown
end
it 'adds to the results hash' do
- result = pipeline_result("Snippet #{reference}")
+ result = reference_pipeline_result("Snippet #{reference}")
expect(result[:references][:snippet]).to eq [snippet]
end
end
context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
- let(:project2) { create(:empty_project, namespace: namespace) }
+ let(:project2) { create(:empty_project, :public, namespace: namespace) }
let(:snippet) { create(:project_snippet, project: project2) }
let(:reference) { snippet.to_reference(project) }
- context 'when user can access reference' do
- before { allow_cross_reference! }
-
- it 'links to a valid reference' do
- doc = 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}.)")
- 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)}"
+ it 'links to a valid reference' do
+ doc = filter("See #{reference}")
- expect(filter(act).to_html).to eq exp
- end
+ expect(doc.css('a').first.attr('href')).
+ to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
+ end
- it 'adds to the results hash' do
- result = pipeline_result("Snippet #{reference}")
- expect(result[:references][:snippet]).to eq [snippet]
- end
+ it 'links with adjacent text' do
+ doc = filter("See (#{reference}.)")
+ expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
- context 'when user cannot access reference' do
- before { disallow_cross_reference! }
+ it 'ignores invalid snippet IDs on the referenced project' do
+ exp = act = "See #{invalidate_reference(reference)}"
- it 'ignores valid references' do
- exp = act = "See #{reference}"
+ expect(filter(act).to_html).to eq exp
+ end
- expect(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
end
diff --git a/spec/lib/gitlab/markdown/upload_link_filter_spec.rb b/spec/lib/gitlab/markdown/upload_link_filter_spec.rb
new file mode 100644
index 00000000000..9ae45a6f559
--- /dev/null
+++ b/spec/lib/gitlab/markdown/upload_link_filter_spec.rb
@@ -0,0 +1,75 @@
+# encoding: UTF-8
+
+require 'spec_helper'
+
+module Gitlab::Markdown
+ describe UploadLinkFilter do
+ def filter(doc, contexts = {})
+ contexts.reverse_merge!({
+ project: project
+ })
+
+ described_class.call(doc, contexts)
+ end
+
+ def image(path)
+ %(<img src="#{path}" />)
+ end
+
+ def link(path)
+ %(<a href="#{path}">#{path}</a>)
+ end
+
+ let(:project) { create(:project) }
+
+ shared_examples :preserve_unchanged do
+ it 'does not modify any relative URL in anchor' do
+ doc = filter(link('README.md'))
+ expect(doc.at_css('a')['href']).to eq 'README.md'
+ end
+
+ it 'does not modify any relative URL in image' do
+ doc = filter(image('files/images/logo-black.png'))
+ expect(doc.at_css('img')['src']).to eq 'files/images/logo-black.png'
+ end
+ end
+
+ it 'does not raise an exception on invalid URIs' do
+ act = link("://foo")
+ expect { filter(act) }.not_to raise_error
+ end
+
+ context 'with a valid repository' do
+ it 'rebuilds relative URL for a link' do
+ doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
+ expect(doc.at_css('a')['href']).
+ to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
+ end
+
+ it 'rebuilds relative URL for an image' do
+ doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
+ expect(doc.at_css('a')['href']).
+ to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
+ end
+
+ it 'does not modify absolute URL' do
+ doc = filter(link('http://example.com'))
+ expect(doc.at_css('a')['href']).to eq 'http://example.com'
+ end
+
+ it 'supports Unicode filenames' do
+ path = '/uploads/한글.png'
+ escaped = Addressable::URI.escape(path)
+
+ # Stub these methods so the file doesn't actually need to be in the repo
+ allow_any_instance_of(described_class).
+ to receive(:file_exists?).and_return(true)
+ allow_any_instance_of(described_class).
+ to receive(:image?).with(path).and_return(true)
+
+ doc = filter(image(escaped))
+ expect(doc.at_css('img')['src']).to match "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/%ED%95%9C%EA%B8%80.png"
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
index b2155fab59b..d9e0d7c42db 100644
--- a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
@@ -4,7 +4,7 @@ module Gitlab::Markdown
describe UserReferenceFilter do
include FilterSpecHelper
- let(:project) { create(:empty_project) }
+ let(:project) { create(:empty_project, :public) }
let(:user) { create(:user) }
let(:reference) { user.to_reference }
@@ -39,7 +39,7 @@ module Gitlab::Markdown
end
it 'adds to the results hash' do
- result = pipeline_result("Hey #{reference}")
+ result = reference_pipeline_result("Hey #{reference}")
expect(result[:references][:user]).to eq [project.creator]
end
end
@@ -64,59 +64,40 @@ module Gitlab::Markdown
expect(doc.css('a').length).to eq 1
end
- it 'includes a data-user-id attribute' do
+ it 'includes a data-user attribute' do
doc = filter("Hey #{reference}")
link = doc.css('a').first
- expect(link).to have_attribute('data-user-id')
- expect(link.attr('data-user-id')).to eq user.namespace.owner_id.to_s
+ 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 = pipeline_result("Hey #{reference}")
+ result = reference_pipeline_result("Hey #{reference}")
expect(result[:references][:user]).to eq [user]
end
end
context 'mentioning a group' do
let(:group) { create(:group) }
- let(:user) { create(:user) }
let(:reference) { group.to_reference }
- context 'that the current user can read' do
- before do
- group.add_developer(user)
- end
-
- it 'links to the Group' do
- doc = filter("Hey #{reference}", current_user: user)
- expect(doc.css('a').first.attr('href')).to eq urls.group_url(group)
- end
-
- it 'includes a data-group-id attribute' do
- doc = filter("Hey #{reference}", current_user: user)
- link = doc.css('a').first
+ it 'links to the Group' do
+ doc = filter("Hey #{reference}")
+ expect(doc.css('a').first.attr('href')).to eq urls.group_url(group)
+ end
- expect(link).to have_attribute('data-group-id')
- expect(link.attr('data-group-id')).to eq group.id.to_s
- end
+ it 'includes a data-group attribute' do
+ doc = filter("Hey #{reference}")
+ link = doc.css('a').first
- it 'adds to the results hash' do
- result = pipeline_result("Hey #{reference}", current_user: user)
- expect(result[:references][:user]).to eq group.users
- end
+ expect(link).to have_attribute('data-group')
+ expect(link.attr('data-group')).to eq group.id.to_s
end
- context 'that the current user cannot read' do
- it 'ignores references to the Group' do
- doc = filter("Hey #{reference}", current_user: user)
- expect(doc.to_html).to eq "Hey #{reference}"
- end
-
- it 'does not add to the results hash' do
- result = pipeline_result("Hey #{reference}", current_user: user)
- expect(result[:references][:user]).to eq []
- end
+ it 'adds to the results hash' do
+ result = reference_pipeline_result("Hey #{reference}")
+ expect(result[:references][:user]).to eq group.users
end
end
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index 088e34f050c..ad84d2274e8 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -13,7 +13,7 @@ describe Gitlab::ReferenceExtractor do
project.team << [@u_bar, :guest]
subject.analyze('@foo, @baduser, @bar, and @offteam')
- expect(subject.users).to eq([@u_foo, @u_bar, @u_offteam])
+ expect(subject.users).to match_array([@u_foo, @u_bar, @u_offteam])
end
it 'ignores user mentions inside specific elements' do
@@ -37,7 +37,7 @@ describe Gitlab::ReferenceExtractor do
> @offteam
})
- expect(subject.users).to eq([])
+ expect(subject.users).to match_array([])
end
it 'accesses valid issue objects' do
@@ -45,7 +45,7 @@ describe Gitlab::ReferenceExtractor do
@i1 = create(:issue, project: project)
subject.analyze("#{@i0.to_reference}, #{@i1.to_reference}, and #{Issue.reference_prefix}999.")
- expect(subject.issues).to eq([@i0, @i1])
+ expect(subject.issues).to match_array([@i0, @i1])
end
it 'accesses valid merge requests' do
@@ -53,7 +53,7 @@ describe Gitlab::ReferenceExtractor do
@m1 = create(:merge_request, source_project: project, target_project: project, source_branch: 'feature_conflict')
subject.analyze("!999, !#{@m1.iid}, and !#{@m0.iid}.")
- expect(subject.merge_requests).to eq([@m1, @m0])
+ expect(subject.merge_requests).to match_array([@m1, @m0])
end
it 'accesses valid labels' do
@@ -62,7 +62,7 @@ describe Gitlab::ReferenceExtractor do
@l2 = create(:label)
subject.analyze("~#{@l0.id}, ~999, ~#{@l2.id}, ~#{@l1.id}")
- expect(subject.labels).to eq([@l0, @l1])
+ expect(subject.labels).to match_array([@l0, @l1])
end
it 'accesses valid snippets' do
@@ -71,7 +71,7 @@ describe Gitlab::ReferenceExtractor do
@s2 = create(:project_snippet)
subject.analyze("$#{@s0.id}, $999, $#{@s2.id}, $#{@s1.id}")
- expect(subject.snippets).to eq([@s0, @s1])
+ expect(subject.snippets).to match_array([@s0, @s1])
end
it 'accesses valid commits' do
@@ -109,7 +109,7 @@ describe Gitlab::ReferenceExtractor do
subject.analyze("this refers issue #{issue.to_reference(project)}")
extracted = subject.issues
expect(extracted.size).to eq(1)
- expect(extracted).to eq([issue])
+ expect(extracted).to match_array([issue])
end
end
end
diff --git a/spec/lib/gitlab/uploads_transfer_spec.rb b/spec/lib/gitlab/uploads_transfer_spec.rb
new file mode 100644
index 00000000000..260364a513e
--- /dev/null
+++ b/spec/lib/gitlab/uploads_transfer_spec.rb
@@ -0,0 +1,50 @@
+require 'spec_helper'
+
+describe Gitlab::UploadsTransfer do
+ before do
+ @root_dir = File.join(Rails.root, "public", "uploads")
+ @upload_transfer = Gitlab::UploadsTransfer.new
+
+ @project_path_was = "test_project_was"
+ @project_path = "test_project"
+ @namespace_path_was = "test_namespace_was"
+ @namespace_path = "test_namespace"
+ end
+
+ after do
+ FileUtils.rm_rf([
+ File.join(@root_dir, @namespace_path),
+ File.join(@root_dir, @namespace_path_was)
+ ])
+ end
+
+ describe '#move_project' do
+ it "moves project upload to another namespace" do
+ FileUtils.mkdir_p(File.join(@root_dir, @namespace_path_was, @project_path))
+ @upload_transfer.move_project(@project_path, @namespace_path_was, @namespace_path)
+
+ expected_path = File.join(@root_dir, @namespace_path, @project_path)
+ expect(Dir.exist?(expected_path)).to be_truthy
+ end
+ end
+
+ describe '#rename_project' do
+ it "renames project" do
+ FileUtils.mkdir_p(File.join(@root_dir, @namespace_path, @project_path_was))
+ @upload_transfer.rename_project(@project_path_was, @project_path, @namespace_path)
+
+ expected_path = File.join(@root_dir, @namespace_path, @project_path)
+ expect(Dir.exist?(expected_path)).to be_truthy
+ end
+ end
+
+ describe '#rename_namespace' do
+ it "renames namespace" do
+ FileUtils.mkdir_p(File.join(@root_dir, @namespace_path_was, @project_path))
+ @upload_transfer.rename_namespace(@namespace_path_was, @namespace_path)
+
+ expected_path = File.join(@root_dir, @namespace_path, @project_path)
+ expect(Dir.exist?(expected_path)).to be_truthy
+ end
+ end
+end
diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb
index d875015b991..7f5abb83ac2 100644
--- a/spec/models/build_spec.rb
+++ b/spec/models/build_spec.rb
@@ -200,13 +200,34 @@ describe Ci::Build do
context 'returns variables' do
subject { build.variables }
- let(:variables) do
+ let(:predefined_variables) do
+ [
+ { key: :CI_BUILD_NAME, value: 'test', public: true },
+ { key: :CI_BUILD_STAGE, value: 'stage', public: true },
+ ]
+ end
+
+ let(:yaml_variables) do
[
{ key: :DB_NAME, value: 'postgres', public: true }
]
end
- it { is_expected.to eq(variables) }
+ before { build.update_attributes(stage: 'stage') }
+
+ it { is_expected.to eq(predefined_variables + yaml_variables) }
+
+ context 'for tag' do
+ let(:tag_variable) do
+ [
+ { key: :CI_BUILD_TAG, value: 'master', public: true }
+ ]
+ end
+
+ before { build.update_attributes(tag: true) }
+
+ it { is_expected.to eq(tag_variable + predefined_variables + yaml_variables) }
+ end
context 'and secure variables' do
let(:secure_variables) do
@@ -219,7 +240,7 @@ describe Ci::Build do
build.project.variables << Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value')
end
- it { is_expected.to eq(variables + secure_variables) }
+ it { is_expected.to eq(predefined_variables + yaml_variables + secure_variables) }
context 'and trigger variables' do
let(:trigger) { FactoryGirl.create :ci_trigger, project: project }
@@ -229,12 +250,17 @@ describe Ci::Build do
{ key: :TRIGGER_KEY, value: 'TRIGGER_VALUE', public: false }
]
end
+ let(:predefined_trigger_variable) do
+ [
+ { key: :CI_BUILD_TRIGGERED, value: 'true', public: true }
+ ]
+ end
before do
build.trigger_request = trigger_request
end
- it { is_expected.to eq(variables + secure_variables + trigger_variables) }
+ it { is_expected.to eq(predefined_variables + predefined_trigger_variable + yaml_variables + secure_variables + trigger_variables) }
end
end
end
@@ -273,4 +299,105 @@ describe Ci::Build do
is_expected.to eq(['rec1', pusher_email])
end
end
+
+ describe :can_be_served? do
+ let(:runner) { FactoryGirl.create :ci_specific_runner }
+
+ before { build.project.runners << runner }
+
+ context 'runner without tags' do
+ it 'can handle builds without tags' do
+ expect(build.can_be_served?(runner)).to be_truthy
+ end
+
+ it 'cannot handle build with tags' do
+ build.tag_list = ['aa']
+ expect(build.can_be_served?(runner)).to be_falsey
+ end
+ end
+
+ context 'runner with tags' do
+ before { runner.tag_list = ['bb', 'cc'] }
+
+ it 'can handle builds without tags' do
+ expect(build.can_be_served?(runner)).to be_truthy
+ end
+
+ it 'can handle build with matching tags' do
+ build.tag_list = ['bb']
+ expect(build.can_be_served?(runner)).to be_truthy
+ end
+
+ it 'cannot handle build with not matching tags' do
+ build.tag_list = ['aa']
+ expect(build.can_be_served?(runner)).to be_falsey
+ end
+ end
+ end
+
+ describe :any_runners_online? do
+ subject { build.any_runners_online? }
+
+ context 'when no runners' do
+ it { is_expected.to be_falsey }
+ end
+
+ context 'if there are runner' do
+ let(:runner) { FactoryGirl.create :ci_specific_runner }
+
+ before do
+ build.project.runners << runner
+ runner.update_attributes(contacted_at: 1.second.ago)
+ end
+
+ it { is_expected.to be_truthy }
+
+ it 'that is inactive' do
+ runner.update_attributes(active: false)
+ is_expected.to be_falsey
+ end
+
+ it 'that is not online' do
+ runner.update_attributes(contacted_at: nil)
+ is_expected.to be_falsey
+ end
+
+ it 'that cannot handle build' do
+ expect_any_instance_of(Ci::Build).to receive(:can_be_served?).and_return(false)
+ is_expected.to be_falsey
+ end
+
+ end
+ end
+
+ describe :show_warning? do
+ subject { build.show_warning? }
+
+ %w(pending).each do |state|
+ context "if commit_status.status is #{state}" do
+ before { build.status = state }
+
+ it { is_expected.to be_truthy }
+
+ context "and there are specific runner" do
+ let(:runner) { FactoryGirl.create :ci_specific_runner, contacted_at: 1.second.ago }
+
+ before do
+ build.project.runners << runner
+ runner.save
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+ end
+
+ %w(success failed canceled running).each do |state|
+ context "if commit_status.status is #{state}" do
+ before { build.status = state }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+ end
end
diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb
index 330971174fb..44dbd083f06 100644
--- a/spec/models/ci/commit_spec.rb
+++ b/spec/models/ci/commit_spec.rb
@@ -32,6 +32,24 @@ describe Ci::Commit do
it { is_expected.to respond_to :git_author_email }
it { is_expected.to respond_to :short_sha }
+ describe :ordered do
+ let(:project) { FactoryGirl.create :empty_project }
+
+ it 'returns ordered list of commits' do
+ commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, gl_project: project
+ commit2 = FactoryGirl.create :ci_commit, committed_at: 2.hour.ago, gl_project: project
+ expect(project.ci_commits.ordered).to eq([commit2, commit1])
+ end
+
+ it 'returns commits ordered by committed_at and id, with nulls last' do
+ commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, gl_project: project
+ commit2 = FactoryGirl.create :ci_commit, committed_at: nil, gl_project: project
+ commit3 = FactoryGirl.create :ci_commit, committed_at: 2.hour.ago, gl_project: project
+ commit4 = FactoryGirl.create :ci_commit, committed_at: nil, gl_project: project
+ expect(project.ci_commits.ordered).to eq([commit2, commit4, commit3, commit1])
+ end
+ end
+
describe :last_build do
subject { commit.last_build }
before do
@@ -143,28 +161,28 @@ describe Ci::Commit do
end
describe :create_builds do
- let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
+ let!(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
def create_builds(trigger_request = nil)
commit.create_builds('master', false, nil, trigger_request)
end
- def create_next_builds(trigger_request = nil)
- commit.create_next_builds('master', false, nil, trigger_request)
+ def create_next_builds
+ commit.create_next_builds(commit.builds.order(:id).last)
end
it 'creates builds' do
expect(create_builds).to be_truthy
- commit.builds.reload
- expect(commit.builds.size).to eq(2)
+ commit.builds.update_all(status: "success")
+ expect(commit.builds.count(:all)).to eq(2)
expect(create_next_builds).to be_truthy
- commit.builds.reload
- expect(commit.builds.size).to eq(4)
+ commit.builds.update_all(status: "success")
+ expect(commit.builds.count(:all)).to eq(4)
expect(create_next_builds).to be_truthy
- commit.builds.reload
- expect(commit.builds.size).to eq(5)
+ commit.builds.update_all(status: "success")
+ expect(commit.builds.count(:all)).to eq(5)
expect(create_next_builds).to be_falsey
end
@@ -176,12 +194,12 @@ describe Ci::Commit do
it 'creates builds' do
expect(create_builds).to be_truthy
- commit.builds.reload
- expect(commit.builds.size).to eq(2)
+ commit.builds.update_all(status: "success")
+ expect(commit.builds.count(:all)).to eq(2)
expect(create_develop_builds).to be_truthy
- commit.builds.reload
- expect(commit.builds.size).to eq(4)
+ commit.builds.update_all(status: "success")
+ expect(commit.builds.count(:all)).to eq(4)
expect(commit.refs.size).to eq(2)
expect(commit.builds.pluck(:name).uniq.size).to eq(2)
end
@@ -193,28 +211,24 @@ describe Ci::Commit do
it 'creates builds' do
expect(create_builds(trigger_request)).to be_truthy
- commit.builds.reload
- expect(commit.builds.size).to eq(2)
+ expect(commit.builds.count(:all)).to eq(2)
end
it 'rebuilds commit' do
expect(create_builds).to be_truthy
- commit.builds.reload
- expect(commit.builds.size).to eq(2)
+ expect(commit.builds.count(:all)).to eq(2)
expect(create_builds(trigger_request)).to be_truthy
- commit.builds.reload
- expect(commit.builds.size).to eq(4)
+ expect(commit.builds.count(:all)).to eq(4)
end
it 'creates next builds' do
expect(create_builds(trigger_request)).to be_truthy
- commit.builds.reload
- expect(commit.builds.size).to eq(2)
+ expect(commit.builds.count(:all)).to eq(2)
+ commit.builds.update_all(status: "success")
- expect(create_next_builds(trigger_request)).to be_truthy
- commit.builds.reload
- expect(commit.builds.size).to eq(4)
+ expect(create_next_builds).to be_truthy
+ expect(commit.builds.count(:all)).to eq(4)
end
context 'for [ci skip]' do
@@ -224,7 +238,7 @@ describe Ci::Commit do
it 'rebuilds commit' do
expect(commit.status).to eq('skipped')
- expect(create_builds(trigger_request)).to be_truthy
+ expect(create_builds).to be_truthy
# since everything in Ci::Commit is cached we need to fetch a new object
new_commit = Ci::Commit.find_by_id(commit.id)
@@ -232,6 +246,129 @@ describe Ci::Commit do
end
end
end
+
+ context 'properly creates builds when "when" is defined' do
+ let(:yaml) do
+ {
+ stages: ["build", "test", "test_failure", "deploy", "cleanup"],
+ build: {
+ stage: "build",
+ script: "BUILD",
+ },
+ test: {
+ stage: "test",
+ script: "TEST",
+ },
+ test_failure: {
+ stage: "test_failure",
+ script: "ON test failure",
+ when: "on_failure",
+ },
+ deploy: {
+ stage: "deploy",
+ script: "PUBLISH",
+ },
+ cleanup: {
+ stage: "cleanup",
+ script: "TIDY UP",
+ when: "always",
+ }
+ }
+ end
+
+ before do
+ stub_ci_commit_yaml_file(YAML.dump(yaml))
+ end
+
+ it 'properly creates builds' do
+ expect(create_builds).to be_truthy
+ expect(commit.builds.pluck(:name)).to contain_exactly('build')
+ expect(commit.builds.pluck(:status)).to contain_exactly('pending')
+ commit.builds.running_or_pending.each(&:success)
+
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
+ commit.builds.running_or_pending.each(&:success)
+
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'pending')
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy')
+ commit.builds.running_or_pending.each(&:success)
+
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy', 'cleanup')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'pending')
+ commit.builds.running_or_pending.each(&:success)
+
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'success')
+ expect(commit.status).to eq('success')
+ end
+
+ it 'properly creates builds when test fails' do
+ expect(create_builds).to be_truthy
+ expect(commit.builds.pluck(:name)).to contain_exactly('build')
+ expect(commit.builds.pluck(:status)).to contain_exactly('pending')
+ commit.builds.running_or_pending.each(&:success)
+
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
+ commit.builds.running_or_pending.each(&:drop)
+
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'pending')
+ commit.builds.running_or_pending.each(&:success)
+
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'pending')
+ commit.builds.running_or_pending.each(&:success)
+
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'success')
+ expect(commit.status).to eq('failed')
+ end
+
+ it 'properly creates builds when test and test_failure fails' do
+ expect(create_builds).to be_truthy
+ expect(commit.builds.pluck(:name)).to contain_exactly('build')
+ expect(commit.builds.pluck(:status)).to contain_exactly('pending')
+ commit.builds.running_or_pending.each(&:success)
+
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
+ commit.builds.running_or_pending.each(&:drop)
+
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'pending')
+ commit.builds.running_or_pending.each(&:drop)
+
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'pending')
+ commit.builds.running_or_pending.each(&:success)
+
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'success')
+ expect(commit.status).to eq('failed')
+ end
+
+ it 'properly creates builds when deploy fails' do
+ expect(create_builds).to be_truthy
+ expect(commit.builds.pluck(:name)).to contain_exactly('build')
+ expect(commit.builds.pluck(:status)).to contain_exactly('pending')
+ commit.builds.running_or_pending.each(&:success)
+
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
+ commit.builds.running_or_pending.each(&:success)
+
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'pending')
+ commit.builds.running_or_pending.each(&:drop)
+
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy', 'cleanup')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'pending')
+ commit.builds.running_or_pending.each(&:success)
+
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'success')
+ expect(commit.status).to eq('failed')
+ end
+ end
end
describe "#finished_at" do
@@ -281,59 +418,4 @@ describe Ci::Commit do
expect(commit.coverage).to be_nil
end
end
-
- describe :should_create_next_builds? do
- before do
- @build1 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', ref: 'master', tag: false, status: 'success'
- @build2 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', ref: 'develop', tag: false, status: 'failed'
- @build3 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', ref: 'master', tag: true, status: 'failed'
- @build4 = FactoryGirl.create :ci_build, commit: commit, name: 'build4', ref: 'master', tag: false, status: 'success'
- end
-
- context 'for success' do
- it 'to create if all succeeded' do
- expect(commit.should_create_next_builds?(@build4)).to be_truthy
- end
- end
-
- context 'for failed' do
- before do
- @build4.update_attributes(status: 'failed')
- end
-
- it 'to not create' do
- expect(commit.should_create_next_builds?(@build4)).to be_falsey
- end
-
- context 'and ignore failures for current' do
- before do
- @build4.update_attributes(allow_failure: true)
- end
-
- it 'to create' do
- expect(commit.should_create_next_builds?(@build4)).to be_truthy
- end
- end
- end
-
- context 'for running' do
- before do
- @build4.update_attributes(status: 'running')
- end
-
- it 'to not create' do
- expect(commit.should_create_next_builds?(@build4)).to be_falsey
- end
- end
-
- context 'for retried' do
- before do
- @build5 = FactoryGirl.create :ci_build, commit: commit, name: 'build4', ref: 'master', tag: false, status: 'failed'
- end
-
- it 'to not create' do
- expect(commit.should_create_next_builds?(@build4)).to be_falsey
- end
- end
- end
end
diff --git a/spec/models/ci/project_spec.rb b/spec/models/ci/project_spec.rb
index dec4720a711..490c6a67982 100644
--- a/spec/models/ci/project_spec.rb
+++ b/spec/models/ci/project_spec.rb
@@ -131,24 +131,6 @@ describe Ci::Project do
end
end
- describe 'ordered commits' do
- let(:project) { FactoryGirl.create :empty_project }
-
- it 'returns ordered list of commits' do
- commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, gl_project: project
- commit2 = FactoryGirl.create :ci_commit, committed_at: 2.hour.ago, gl_project: project
- expect(project.ci_commits).to eq([commit2, commit1])
- end
-
- it 'returns commits ordered by committed_at and id, with nulls last' do
- commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, gl_project: project
- commit2 = FactoryGirl.create :ci_commit, committed_at: nil, gl_project: project
- commit3 = FactoryGirl.create :ci_commit, committed_at: 2.hour.ago, gl_project: project
- commit4 = FactoryGirl.create :ci_commit, committed_at: nil, gl_project: project
- expect(project.ci_commits).to eq([commit2, commit4, commit3, commit1])
- end
- end
-
context :valid_project do
let(:commit) { FactoryGirl.create(:ci_commit) }
@@ -259,5 +241,18 @@ describe Ci::Project do
FactoryGirl.create(:ci_shared_runner)
expect(project.any_runners?).to be_falsey
end
+
+ it "checks the presence of specific runner" do
+ project = FactoryGirl.create(:ci_project)
+ specific_runner = FactoryGirl.create(:ci_specific_runner)
+ project.runners << specific_runner
+ expect(project.any_runners? { |runner| runner == specific_runner }).to be_truthy
+ end
+
+ it "checks the presence of shared runner" do
+ project = FactoryGirl.create(:ci_project, shared_runners_enabled: true)
+ shared_runner = FactoryGirl.create(:ci_shared_runner)
+ expect(project.any_runners? { |runner| runner == shared_runner }).to be_truthy
+ end
end
end
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 757593a7ab8..f8a51c29dc2 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -32,7 +32,7 @@ describe Ci::Runner do
end
it 'should return the token if the description is an empty string' do
- runner = FactoryGirl.build(:ci_runner, description: '')
+ runner = FactoryGirl.build(:ci_runner, description: '', token: 'token')
expect(runner.display_name).to eq runner.token
end
end
@@ -48,6 +48,71 @@ describe Ci::Runner do
it { expect(shared_runner.only_for?(project)).to be_truthy }
end
+ describe :online do
+ subject { Ci::Runner.online }
+
+ before do
+ @runner1 = FactoryGirl.create(:ci_shared_runner, contacted_at: 1.year.ago)
+ @runner2 = FactoryGirl.create(:ci_shared_runner, contacted_at: 1.second.ago)
+ end
+
+ it { is_expected.to eq([@runner2])}
+ end
+
+ describe :online? do
+ let(:runner) { FactoryGirl.create(:ci_shared_runner) }
+
+ subject { runner.online? }
+
+ context 'never contacted' do
+ before { runner.contacted_at = nil }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'contacted long time ago time' do
+ before { runner.contacted_at = 1.year.ago }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'contacted 1s ago' do
+ before { runner.contacted_at = 1.second.ago }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ describe :status do
+ let(:runner) { FactoryGirl.create(:ci_shared_runner, contacted_at: 1.second.ago) }
+
+ subject { runner.status }
+
+ context 'never connected' do
+ before { runner.contacted_at = nil }
+
+ it { is_expected.to eq(:not_connected) }
+ end
+
+ context 'contacted 1s ago' do
+ before { runner.contacted_at = 1.second.ago }
+
+ it { is_expected.to eq(:online) }
+ end
+
+ context 'contacted long time ago' do
+ before { runner.contacted_at = 1.year.ago }
+
+ it { is_expected.to eq(:offline) }
+ end
+
+ context 'inactive' do
+ before { runner.active = false }
+
+ it { is_expected.to eq(:paused) }
+ end
+ end
+
describe "belongs_to_one_project?" do
it "returns false if there are two projects runner assigned to" do
runner = FactoryGirl.create(:ci_specific_runner)
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index e303a97e6b5..90be9324951 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -89,9 +89,9 @@ eos
end
it_behaves_like 'a mentionable' do
- subject { commit }
+ subject { create(:project).commit }
- let(:author) { create(:user, email: commit.author_email) }
+ let(:author) { create(:user, email: subject.author_email) }
let(:backref_text) { "commit #{subject.id}" }
let(:set_mentionable_text) do
->(txt) { allow(subject).to receive(:safe_message).and_return(txt) }
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 8f706f8934b..0f13c4410cd 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -68,7 +68,6 @@ describe Issue, "Issuable" do
end
end
-
describe "#to_hook_data" do
let(:hook_data) { issue.to_hook_data(user) }
diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb
index 2d6fe003215..6179882e935 100644
--- a/spec/models/concerns/mentionable_spec.rb
+++ b/spec/models/concerns/mentionable_spec.rb
@@ -25,7 +25,7 @@ describe Issue, "Mentionable" do
it 'correctly removes already-mentioned Commits' do
expect(SystemNoteService).not_to receive(:cross_reference)
- issue.create_cross_references!(project, author, [commit2])
+ issue.create_cross_references!(author, [commit2])
end
end
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index cf336d82957..c9aa1b063c6 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -68,8 +68,45 @@ describe Issue do
end
end
+ describe '#closed_by_merge_requests' do
+ let(:project) { create(:project) }
+ let(:issue) { create(:issue, project: project, state: "opened")}
+ let(:closed_issue) { build(:issue, project: project, state: "closed")}
+
+ let(:mr) do
+ opts = {
+ title: 'Awesome merge_request',
+ description: "Fixes #{issue.to_reference}",
+ source_branch: 'feature',
+ target_branch: 'master'
+ }
+ MergeRequests::CreateService.new(project, project.owner, opts).execute
+ end
+
+ let(:closed_mr) do
+ opts = {
+ title: 'Awesome merge_request 2',
+ description: "Fixes #{issue.to_reference}",
+ source_branch: 'feature',
+ target_branch: 'master',
+ state: 'closed'
+ }
+ MergeRequests::CreateService.new(project, project.owner, opts).execute
+ end
+
+ it 'returns the merge request to close this issue' do
+ allow(mr).to receive(:closes_issue?).with(issue).and_return(true)
+
+ expect(issue.closed_by_merge_requests).to eq([mr])
+ end
+
+ it "returns an empty array when the current issue is closed already" do
+ expect(closed_issue.closed_by_merge_requests).to eq([])
+ end
+ end
+
it_behaves_like 'an editable mentionable' do
- subject { create(:issue, project: project) }
+ subject { create(:issue) }
let(:backref_text) { "issue #{subject.to_reference}" }
let(:set_mentionable_text) { ->(txt){ subject.description = txt } }
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index c88d5349663..77c58627322 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -140,4 +140,32 @@ describe Milestone do
end
end
+ describe '#sort_issues' do
+ let(:milestone) { create(:milestone) }
+
+ let(:issue1) { create(:issue, milestone: milestone, position: 1) }
+ let(:issue2) { create(:issue, milestone: milestone, position: 2) }
+ let(:issue3) { create(:issue, milestone: milestone, position: 3) }
+ let(:issue4) { create(:issue, position: 42) }
+
+ it 'sorts the given issues' do
+ milestone.sort_issues([issue3.id, issue2.id, issue1.id])
+
+ issue1.reload
+ issue2.reload
+ issue3.reload
+
+ expect(issue1.position).to eq(3)
+ expect(issue2.position).to eq(2)
+ expect(issue3.position).to eq(1)
+ end
+
+ it 'ignores issues not part of the milestone' do
+ milestone.sort_issues([issue3.id, issue2.id, issue1.id, issue4.id])
+
+ issue4.reload
+
+ expect(issue4.position).to eq(42)
+ end
+ end
end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 3a0b194ba1e..75564839dcf 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -192,10 +192,9 @@ describe Note do
end
it_behaves_like 'an editable mentionable' do
- subject { create :note, noteable: issue, project: project }
+ subject { create :note, noteable: issue, project: issue.project }
- let(:project) { create(:project) }
- let(:issue) { create :issue, project: project }
+ let(:issue) { create :issue }
let(:backref_text) { issue.gfm_reference }
let(:set_mentionable_text) { ->(txt) { subject.note = txt } }
end
diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb
index f8a3493f52d..c34b2487ecf 100644
--- a/spec/models/project_services/bamboo_service_spec.rb
+++ b/spec/models/project_services/bamboo_service_spec.rb
@@ -30,27 +30,65 @@ describe BambooService, models: true do
let(:user) { create(:user) }
let(:project) { create(:project) }
- before do
- @bamboo_service = BambooService.create(
- project: create(:project),
- properties: {
- bamboo_url: 'http://gitlab.com',
- username: 'mic',
- password: "password"
- }
- )
- end
+ context "when a password was previously set" do
+ before do
+ @bamboo_service = BambooService.create(
+ project: create(:project),
+ properties: {
+ bamboo_url: 'http://gitlab.com',
+ username: 'mic',
+ password: "password"
+ }
+ )
+ end
+
+ it "reset password if url changed" do
+ @bamboo_service.bamboo_url = 'http://gitlab1.com'
+ @bamboo_service.save
+ expect(@bamboo_service.password).to be_nil
+ end
+
+ it "does not reset password if username changed" do
+ @bamboo_service.username = "some_name"
+ @bamboo_service.save
+ expect(@bamboo_service.password).to eq("password")
+ end
+
+ it "does not reset password if new url is set together with password, even if it's the same password" do
+ @bamboo_service.bamboo_url = 'http://gitlab_edited.com'
+ @bamboo_service.password = 'password'
+ @bamboo_service.save
+ expect(@bamboo_service.password).to eq("password")
+ expect(@bamboo_service.bamboo_url).to eq("http://gitlab_edited.com")
+ end
- it "reset password if url changed" do
- @bamboo_service.bamboo_url = 'http://gitlab1.com'
- @bamboo_service.save
- expect(@bamboo_service.password).to be_nil
+ it "should reset password if url changed, even if setter called multiple times" do
+ @bamboo_service.bamboo_url = 'http://gitlab1.com'
+ @bamboo_service.bamboo_url = 'http://gitlab1.com'
+ @bamboo_service.save
+ expect(@bamboo_service.password).to be_nil
+ end
end
+
+ context "when no password was previously set" do
+ before do
+ @bamboo_service = BambooService.create(
+ project: create(:project),
+ properties: {
+ bamboo_url: 'http://gitlab.com',
+ username: 'mic'
+ }
+ )
+ end
+
+ it "saves password if new url is set together with password" do
+ @bamboo_service.bamboo_url = 'http://gitlab_edited.com'
+ @bamboo_service.password = 'password'
+ @bamboo_service.save
+ expect(@bamboo_service.password).to eq("password")
+ expect(@bamboo_service.bamboo_url).to eq("http://gitlab_edited.com")
+ end
- it "does not reset password if username changed" do
- @bamboo_service.username = "some_name"
- @bamboo_service.save
- expect(@bamboo_service.password).to eq("password")
end
end
end
diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb
index 3dbd2346bcc..f26b47a856c 100644
--- a/spec/models/project_services/teamcity_service_spec.rb
+++ b/spec/models/project_services/teamcity_service_spec.rb
@@ -30,27 +30,64 @@ describe TeamcityService, models: true do
let(:user) { create(:user) }
let(:project) { create(:project) }
- before do
- @teamcity_service = TeamcityService.create(
- project: create(:project),
- properties: {
- teamcity_url: 'http://gitlab.com',
- username: 'mic',
- password: "password"
- }
- )
- end
+ context "when a password was previously set" do
+ before do
+ @teamcity_service = TeamcityService.create(
+ project: create(:project),
+ properties: {
+ teamcity_url: 'http://gitlab.com',
+ username: 'mic',
+ password: "password"
+ }
+ )
+ end
+
+ it "reset password if url changed" do
+ @teamcity_service.teamcity_url = 'http://gitlab1.com'
+ @teamcity_service.save
+ expect(@teamcity_service.password).to be_nil
+ end
+
+ it "does not reset password if username changed" do
+ @teamcity_service.username = "some_name"
+ @teamcity_service.save
+ expect(@teamcity_service.password).to eq("password")
+ end
+
+ it "does not reset password if new url is set together with password, even if it's the same password" do
+ @teamcity_service.teamcity_url = 'http://gitlab_edited.com'
+ @teamcity_service.password = 'password'
+ @teamcity_service.save
+ expect(@teamcity_service.password).to eq("password")
+ expect(@teamcity_service.teamcity_url).to eq("http://gitlab_edited.com")
+ end
- it "reset password if url changed" do
- @teamcity_service.teamcity_url = 'http://gitlab1.com'
- @teamcity_service.save
- expect(@teamcity_service.password).to be_nil
+ it "should reset password if url changed, even if setter called multiple times" do
+ @teamcity_service.teamcity_url = 'http://gitlab1.com'
+ @teamcity_service.teamcity_url = 'http://gitlab1.com'
+ @teamcity_service.save
+ expect(@teamcity_service.password).to be_nil
+ end
end
+
+ context "when no password was previously set" do
+ before do
+ @teamcity_service = TeamcityService.create(
+ project: create(:project),
+ properties: {
+ teamcity_url: 'http://gitlab.com',
+ username: 'mic'
+ }
+ )
+ end
- it "does not reset password if username changed" do
- @teamcity_service.username = "some_name"
- @teamcity_service.save
- expect(@teamcity_service.password).to eq("password")
+ it "saves password if new url is set together with password" do
+ @teamcity_service.teamcity_url = 'http://gitlab_edited.com'
+ @teamcity_service.password = 'password'
+ @teamcity_service.save
+ expect(@teamcity_service.password).to eq("password")
+ expect(@teamcity_service.teamcity_url).to eq("http://gitlab_edited.com")
+ end
end
end
end
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index da87ea5b84f..692e5fda3ba 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -104,7 +104,7 @@ describe Service do
end
end
- describe "#prop_updated?" do
+ describe "{property}_changed?" do
let(:service) do
BambooService.create(
project: create(:project),
@@ -116,14 +116,112 @@ describe Service do
)
end
- it "returns false" do
+ it "returns false when the property has not been assigned a new value" do
service.username = "key_changed"
- expect(service.prop_updated?(:bamboo_url)).to be_falsy
+ expect(service.bamboo_url_changed?).to be_falsy
end
- it "returns true" do
- service.bamboo_url = "http://other.com"
- expect(service.prop_updated?(:bamboo_url)).to be_truthy
+ it "returns true when the property has been assigned a different value" do
+ service.bamboo_url = "http://example.com"
+ expect(service.bamboo_url_changed?).to be_truthy
+ end
+
+ it "returns true when the property has been assigned a different value twice" do
+ service.bamboo_url = "http://example.com"
+ service.bamboo_url = "http://example.com"
+ expect(service.bamboo_url_changed?).to be_truthy
+ end
+
+ it "returns false when the property has been re-assigned the same value" do
+ service.bamboo_url = 'http://gitlab.com'
+ expect(service.bamboo_url_changed?).to be_falsy
+ end
+
+ it "returns false when the property has been assigned a new value then saved" do
+ service.bamboo_url = 'http://example.com'
+ service.save
+ expect(service.bamboo_url_changed?).to be_falsy
+ end
+ end
+
+ describe "{property}_touched?" do
+ let(:service) do
+ BambooService.create(
+ project: create(:project),
+ properties: {
+ bamboo_url: 'http://gitlab.com',
+ username: 'mic',
+ password: "password"
+ }
+ )
+ end
+
+ it "returns false when the property has not been assigned a new value" do
+ service.username = "key_changed"
+ expect(service.bamboo_url_touched?).to be_falsy
+ end
+
+ it "returns true when the property has been assigned a different value" do
+ service.bamboo_url = "http://example.com"
+ expect(service.bamboo_url_touched?).to be_truthy
+ end
+
+ it "returns true when the property has been assigned a different value twice" do
+ service.bamboo_url = "http://example.com"
+ service.bamboo_url = "http://example.com"
+ expect(service.bamboo_url_touched?).to be_truthy
+ end
+
+ it "returns true when the property has been re-assigned the same value" do
+ service.bamboo_url = 'http://gitlab.com'
+ expect(service.bamboo_url_touched?).to be_truthy
+ end
+
+ it "returns false when the property has been assigned a new value then saved" do
+ service.bamboo_url = 'http://example.com'
+ service.save
+ expect(service.bamboo_url_changed?).to be_falsy
+ end
+ end
+
+ describe "{property}_was" do
+ let(:service) do
+ BambooService.create(
+ project: create(:project),
+ properties: {
+ bamboo_url: 'http://gitlab.com',
+ username: 'mic',
+ password: "password"
+ }
+ )
+ end
+
+
+ it "returns nil when the property has not been assigned a new value" do
+ service.username = "key_changed"
+ expect(service.bamboo_url_was).to be_nil
+ end
+
+ it "returns the previous value when the property has been assigned a different value" do
+ service.bamboo_url = "http://example.com"
+ expect(service.bamboo_url_was).to eq('http://gitlab.com')
+ end
+
+ it "returns initial value when the property has been re-assigned the same value" do
+ service.bamboo_url = 'http://gitlab.com'
+ expect(service.bamboo_url_was).to eq('http://gitlab.com')
+ end
+
+ it "returns initial value when the property has been assigned multiple values" do
+ service.bamboo_url = "http://example.com"
+ service.bamboo_url = "http://example2.com"
+ expect(service.bamboo_url_was).to eq('http://gitlab.com')
+ end
+
+ it "returns nil when the property has been assigned a new value then saved" do
+ service.bamboo_url = 'http://example.com'
+ service.save
+ expect(service.bamboo_url_was).to be_nil
end
end
end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 35b3d3e296a..a68c7b1e461 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -379,9 +379,14 @@ describe API::API, api: true do
describe "POST /projects/:id/merge_request/:merge_request_id/comments" do
it "should return comment" do
+ original_count = merge_request.notes.size
+
post api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user), note: "My comment"
expect(response.status).to eq(201)
expect(json_response['note']).to eq('My comment')
+ expect(json_response['author']['name']).to eq(user.name)
+ expect(json_response['author']['username']).to eq(user.username)
+ expect(merge_request.notes.size).to eq(original_count + 1)
end
it "should return 400 if note is missing" do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 580bbec77d1..e9de9e0826d 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -606,28 +606,42 @@ describe API::API, api: true do
describe 'DELETE /projects/:id/fork' do
- it "shouldn't available for non admin users" do
+ it "shouldn't be visible to users outside group" do
delete api("/projects/#{project_fork_target.id}/fork", user)
- expect(response.status).to eq(403)
+ expect(response.status).to eq(404)
end
- it 'should make forked project unforked' do
- post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
- project_fork_target.reload
- expect(project_fork_target.forked_from_project).not_to be_nil
- expect(project_fork_target.forked?).to be_truthy
- delete api("/projects/#{project_fork_target.id}/fork", admin)
- expect(response.status).to eq(200)
- project_fork_target.reload
- expect(project_fork_target.forked_from_project).to be_nil
- expect(project_fork_target.forked?).not_to be_truthy
- end
+ context 'when users belong to project group' do
+ let(:project_fork_target) { create(:project, group: create(:group)) }
- it 'should be idempotent if not forked' do
- expect(project_fork_target.forked_from_project).to be_nil
- delete api("/projects/#{project_fork_target.id}/fork", admin)
- expect(response.status).to eq(200)
- expect(project_fork_target.reload.forked_from_project).to be_nil
+ before do
+ project_fork_target.group.add_owner user
+ project_fork_target.group.add_developer user2
+ end
+
+ it 'should be forbidden to non-owner users' do
+ delete api("/projects/#{project_fork_target.id}/fork", user2)
+ expect(response.status).to eq(403)
+ end
+
+ it 'should make forked project unforked' do
+ post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
+ project_fork_target.reload
+ expect(project_fork_target.forked_from_project).not_to be_nil
+ expect(project_fork_target.forked?).to be_truthy
+ delete api("/projects/#{project_fork_target.id}/fork", admin)
+ expect(response.status).to eq(200)
+ project_fork_target.reload
+ expect(project_fork_target.forked_from_project).to be_nil
+ expect(project_fork_target.forked?).not_to be_truthy
+ end
+
+ it 'should be idempotent if not forked' do
+ expect(project_fork_target.forked_from_project).to be_nil
+ delete api("/projects/#{project_fork_target.id}/fork", admin)
+ expect(response.status).to eq(200)
+ expect(project_fork_target.reload.forked_from_project).to be_nil
+ end
end
end
end
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index 09a79553f72..1149f7e7989 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -166,24 +166,21 @@ describe API::API, api: true do
get api("/projects/#{project.id}/repository/archive", user)
repo_name = project.repository.name.gsub("\.git", "")
expect(response.status).to eq(200)
- expect(response.headers['Content-Disposition']).to match(/filename\=\"#{repo_name}\-[^\.]+\.tar.gz\"/)
- expect(response.content_type).to eq(MIME::Types.type_for('file.tar.gz').first.content_type)
+ expect(json_response['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.gz/)
end
it "should get the archive.zip" do
get api("/projects/#{project.id}/repository/archive.zip", user)
repo_name = project.repository.name.gsub("\.git", "")
expect(response.status).to eq(200)
- expect(response.headers['Content-Disposition']).to match(/filename\=\"#{repo_name}\-[^\.]+\.zip\"/)
- expect(response.content_type).to eq(MIME::Types.type_for('file.zip').first.content_type)
+ expect(json_response['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.zip/)
end
it "should get the archive.tar.bz2" do
get api("/projects/#{project.id}/repository/archive.tar.bz2", user)
repo_name = project.repository.name.gsub("\.git", "")
expect(response.status).to eq(200)
- expect(response.headers['Content-Disposition']).to match(/filename\=\"#{repo_name}\-[^\.]+\.tar.bz2\"/)
- expect(response.content_type).to eq(MIME::Types.type_for('file.tar.bz2').first.content_type)
+ expect(json_response['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.bz2/)
end
it "should return 404 for invalid sha" do
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index 9aa60826f21..c0226605a23 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -3,6 +3,8 @@ require "spec_helper"
describe API::API, api: true do
include ApiHelpers
let(:user) { create(:user) }
+ let(:admin) { create(:admin) }
+ let(:user2) { create(:user) }
let(:project) {create(:project, creator_id: user.id, namespace: user.namespace) }
Service.available_services_names.each do |service|
@@ -51,11 +53,40 @@ describe API::API, api: true do
describe "GET /projects/:id/services/#{service.dasherize}" do
include_context service
- it "should get #{service} settings" do
+ # inject some properties into the service
+ before do
+ project.build_missing_services
+ service_object = project.send(service_method)
+ service_object.properties = service_attrs
+ service_object.save
+ end
+
+ it 'should return authentication error when unauthenticated' do
+ get api("/projects/#{project.id}/services/#{dashed_service}")
+ expect(response.status).to eq(401)
+ end
+
+ it "should return all properties of service #{service} when authenticated as admin" do
+ get api("/projects/#{project.id}/services/#{dashed_service}", admin)
+
+ expect(response.status).to eq(200)
+ expect(json_response['properties'].keys.map(&:to_sym)).to match_array(service_attrs_list.map)
+ end
+
+ it "should return properties of service #{service} other than passwords when authenticated as project owner" do
get api("/projects/#{project.id}/services/#{dashed_service}", user)
expect(response.status).to eq(200)
+ expect(json_response['properties'].keys.map(&:to_sym)).to match_array(service_attrs_list_without_passwords)
end
+
+ it "should return error when authenticated but not a project owner" do
+ project.team << [user2, :developer]
+ get api("/projects/#{project.id}/services/#{dashed_service}", user2)
+
+ expect(response.status).to eq(403)
+ end
+
end
end
end
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index 54c1d0199f6..88218a93e1f 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -76,6 +76,8 @@ describe Ci::API::API do
expect(response.status).to eq(201)
expect(json_response["variables"]).to eq([
+ { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true },
+ { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true },
{ "key" => "DB_NAME", "value" => "postgres", "public" => true },
{ "key" => "SECRET_KEY", "value" => "secret_value", "public" => false },
])
@@ -93,6 +95,9 @@ describe Ci::API::API do
expect(response.status).to eq(201)
expect(json_response["variables"]).to eq([
+ { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true },
+ { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true },
+ { "key" => "CI_BUILD_TRIGGERED", "value" => "true", "public" => true },
{ "key" => "DB_NAME", "value" => "postgres", "public" => true },
{ "key" => "SECRET_KEY", "value" => "secret_value", "public" => false },
{ "key" => "TRIGGER_KEY", "value" => "TRIGGER_VALUE", "public" => false },
diff --git a/spec/services/archive_repository_service_spec.rb b/spec/services/archive_repository_service_spec.rb
index 0ec70c51b3a..f7a36cd9670 100644
--- a/spec/services/archive_repository_service_spec.rb
+++ b/spec/services/archive_repository_service_spec.rb
@@ -6,14 +6,14 @@ describe ArchiveRepositoryService do
describe "#execute" do
it "cleans old archives" do
- expect(project.repository).to receive(:clean_old_archives)
+ expect(RepositoryArchiveCacheWorker).to receive(:perform_async)
subject.execute(timeout: 0.0)
end
context "when the repository doesn't have an archive file path" do
before do
- allow(project.repository).to receive(:archive_file_path).and_return(nil)
+ allow(project.repository).to receive(:archive_metadata).and_return(Hash.new)
end
it "raises an error" do
@@ -21,70 +21,5 @@ describe ArchiveRepositoryService do
end
end
- context "when the repository has an archive file path" do
- let(:file_path) { "/archive.zip" }
- let(:pid_file_path) { "/archive.zip.pid" }
-
- before do
- allow(project.repository).to receive(:archive_file_path).and_return(file_path)
- allow(project.repository).to receive(:archive_pid_file_path).and_return(pid_file_path)
- end
-
- context "when the archive file already exists" do
- before do
- allow(File).to receive(:exist?).with(file_path).and_return(true)
- end
-
- it "returns the file path" do
- expect(subject.execute(timeout: 0.0)).to eq(file_path)
- end
- end
-
- context "when the archive file doesn't exist yet" do
- before do
- allow(File).to receive(:exist?).with(file_path).and_return(false)
- allow(File).to receive(:exist?).with(pid_file_path).and_return(true)
- end
-
- context "when the archive pid file doesn't exist yet" do
- before do
- allow(File).to receive(:exist?).with(pid_file_path).and_return(false)
- end
-
- it "queues the RepositoryArchiveWorker" do
- expect(RepositoryArchiveWorker).to receive(:perform_async)
-
- subject.execute(timeout: 0.0)
- end
- end
-
- context "when the archive pid file already exists" do
- it "doesn't queue the RepositoryArchiveWorker" do
- expect(RepositoryArchiveWorker).not_to receive(:perform_async)
-
- subject.execute(timeout: 0.0)
- end
- end
-
- context "when the archive file exists after a little while" do
- before do
- Thread.new do
- sleep 0.1
- allow(File).to receive(:exist?).with(file_path).and_return(true)
- end
- end
-
- it "returns the file path" do
- expect(subject.execute(timeout: 0.2)).to eq(file_path)
- end
- end
-
- context "when the archive file doesn't exist after the timeout" do
- it "returns nil" do
- expect(subject.execute(timeout: 0.0)).to eq(nil)
- end
- end
- end
- end
end
end
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index c483060fd73..17015d29e51 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -112,6 +112,14 @@ describe GitPushService do
it { expect(@event.project).to eq(project) }
it { expect(@event.action).to eq(Event::PUSHED) }
it { expect(@event.data).to eq(service.push_data) }
+
+ context "Updates merge requests" do
+ it "when pushing a new branch for the first time" do
+ expect(project).to receive(:update_merge_requests).
+ with(@blankrev, 'newrev', 'refs/heads/master', user)
+ service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master')
+ end
+ end
end
describe "Web Hooks" do
@@ -155,7 +163,7 @@ describe GitPushService do
before do
allow(commit).to receive_messages(
- safe_message: "this commit \n mentions ##{issue.id}",
+ safe_message: "this commit \n mentions #{issue.to_reference}",
references: [issue],
author_name: commit_author.name,
author_email: commit_author.email
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 9516e7936d8..227ac995ec2 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -106,6 +106,27 @@ describe MergeRequests::RefreshService do
it { expect(@fork_merge_request.notes).to be_empty }
end
+ context 'push new branch that exists in a merge request' do
+ let(:refresh_service) { service.new(@fork_project, @user) }
+
+ it 'refreshes the merge request' do
+ expect(refresh_service).to receive(:execute_hooks).
+ with(@fork_merge_request, 'update')
+ allow_any_instance_of(Repository).to receive(:merge_base).and_return(@oldrev)
+
+ refresh_service.execute(Gitlab::Git::BLANK_SHA, @newrev, 'refs/heads/master')
+ reload_mrs
+
+ expect(@merge_request.notes).to be_empty
+ expect(@merge_request).to be_open
+
+ notes = @fork_merge_request.notes.reorder(:created_at).map(&:note)
+ expect(notes[0]).to include('Restored source branch `master`')
+ expect(notes[1]).to include('Added 4 commits')
+ expect(@fork_merge_request).to be_open
+ end
+ end
+
def reload_mrs
@merge_request.reload
@fork_merge_request.reload
diff --git a/spec/services/projects/download_service_spec.rb b/spec/services/projects/download_service_spec.rb
index f12e09c58c3..ddee2e62dfc 100644
--- a/spec/services/projects/download_service_spec.rb
+++ b/spec/services/projects/download_service_spec.rb
@@ -37,7 +37,6 @@ describe Projects::DownloadService do
it { expect(@link_to_file).to have_key('url') }
it { expect(@link_to_file).to have_key('is_image') }
it { expect(@link_to_file['is_image']).to be true }
- it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") }
it { expect(@link_to_file['url']).to match('rails_sample.jpg') }
it { expect(@link_to_file['alt']).to eq('rails_sample') }
end
@@ -52,7 +51,6 @@ describe Projects::DownloadService do
it { expect(@link_to_file).to have_key('url') }
it { expect(@link_to_file).to have_key('is_image') }
it { expect(@link_to_file['is_image']).to be false }
- it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") }
it { expect(@link_to_file['url']).to match('doc_sample.txt') }
it { expect(@link_to_file['alt']).to eq('doc_sample.txt') }
end
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index bb7da33b12e..47755bfc990 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -7,6 +7,8 @@ describe Projects::TransferService do
context 'namespace -> namespace' do
before do
+ allow_any_instance_of(Gitlab::UploadsTransfer).
+ to receive(:move_project).and_return(true)
group.add_owner(user)
@result = transfer_project(project, user, group)
end
diff --git a/spec/services/projects/upload_service_spec.rb b/spec/services/projects/upload_service_spec.rb
index fa4ff6b01ad..1b1a80d1fe7 100644
--- a/spec/services/projects/upload_service_spec.rb
+++ b/spec/services/projects/upload_service_spec.rb
@@ -18,7 +18,6 @@ describe Projects::UploadService do
it { expect(@link_to_file).to have_key(:is_image) }
it { expect(@link_to_file).to have_value('banana_sample') }
it { expect(@link_to_file[:is_image]).to equal(true) }
- it { expect(@link_to_file[:url]).to match("/#{@project.path_with_namespace}") }
it { expect(@link_to_file[:url]).to match('banana_sample.gif') }
end
@@ -34,7 +33,6 @@ describe Projects::UploadService do
it { expect(@link_to_file).to have_value('dk') }
it { expect(@link_to_file).to have_key(:is_image) }
it { expect(@link_to_file[:is_image]).to equal(true) }
- it { expect(@link_to_file[:url]).to match("/#{@project.path_with_namespace}") }
it { expect(@link_to_file[:url]).to match('dk.png') }
end
@@ -49,7 +47,6 @@ describe Projects::UploadService do
it { expect(@link_to_file).to have_key(:is_image) }
it { expect(@link_to_file).to have_value('rails_sample') }
it { expect(@link_to_file[:is_image]).to equal(true) }
- it { expect(@link_to_file[:url]).to match("/#{@project.path_with_namespace}") }
it { expect(@link_to_file[:url]).to match('rails_sample.jpg') }
end
@@ -64,7 +61,6 @@ describe Projects::UploadService do
it { expect(@link_to_file).to have_key(:is_image) }
it { expect(@link_to_file).to have_value('doc_sample.txt') }
it { expect(@link_to_file[:is_image]).to equal(false) }
- it { expect(@link_to_file[:url]).to match("/#{@project.path_with_namespace}") }
it { expect(@link_to_file[:url]).to match('doc_sample.txt') }
end
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 2658576640c..a45130bd473 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -242,6 +242,18 @@ describe SystemNoteService do
end
end
+ describe '.change_branch_presence' do
+ subject { described_class.change_branch_presence(noteable, project, author, :source, 'feature', :delete) }
+
+ it_behaves_like 'a system note'
+
+ context 'when source branch deleted' do
+ it 'sets the note text' do
+ expect(subject.note).to eq "Deleted source branch `feature`"
+ end
+ end
+ end
+
describe '.cross_reference' do
subject { described_class.cross_reference(noteable, mentioner, author) }
diff --git a/spec/support/filter_spec_helper.rb b/spec/support/filter_spec_helper.rb
index 203117aee70..97e5c270a59 100644
--- a/spec/support/filter_spec_helper.rb
+++ b/spec/support/filter_spec_helper.rb
@@ -29,12 +29,19 @@ module FilterSpecHelper
#
# Returns the Hash
def pipeline_result(body, contexts = {})
- contexts.reverse_merge!(project: project)
+ contexts.reverse_merge!(project: project) if defined?(project)
pipeline = HTML::Pipeline.new([described_class], contexts)
pipeline.call(body)
end
+ def reference_pipeline_result(body, contexts = {})
+ contexts.reverse_merge!(project: project) if defined?(project)
+
+ pipeline = HTML::Pipeline.new([described_class, Gitlab::Markdown::ReferenceGathererFilter], contexts)
+ pipeline.call(body)
+ end
+
# Modify a String reference to make it invalid
#
# Commit SHAs get reversed, IDs get incremented by 1, all other Strings get
@@ -55,20 +62,6 @@ module FilterSpecHelper
end
end
- # Stub CrossProjectReference#user_can_reference_project? to return true for
- # the current test
- def allow_cross_reference!
- allow_any_instance_of(described_class).
- to receive(:user_can_reference_project?).and_return(true)
- end
-
- # Stub CrossProjectReference#user_can_reference_project? to return false for
- # the current test
- def disallow_cross_reference!
- allow_any_instance_of(described_class).
- to receive(:user_can_reference_project?).and_return(false)
- end
-
# Shortcut to Rails' auto-generated routes helpers, to avoid including the
# module
def urls
diff --git a/spec/support/markdown_feature.rb b/spec/support/markdown_feature.rb
index 39a64391460..bedc1a7f1db 100644
--- a/spec/support/markdown_feature.rb
+++ b/spec/support/markdown_feature.rb
@@ -15,18 +15,17 @@ class MarkdownFeature
end
def group
- unless @group
- @group = create(:group)
- @group.add_developer(user)
+ @group ||= create(:group).tap do |group|
+ group.add_developer(user)
end
-
- @group
end
# Direct references ----------------------------------------------------------
def project
- @project ||= create(:project)
+ @project ||= create(:project).tap do |project|
+ project.team << [user, :master]
+ end
end
def issue
@@ -46,12 +45,10 @@ class MarkdownFeature
end
def commit_range
- unless @commit_range
+ @commit_range ||= begin
commit2 = project.commit('HEAD~3')
- @commit_range = CommitRange.new("#{commit.id}...#{commit2.id}", project)
+ CommitRange.new("#{commit.id}...#{commit2.id}", project)
end
-
- @commit_range
end
def simple_label
@@ -65,13 +62,12 @@ class MarkdownFeature
# Cross-references -----------------------------------------------------------
def xproject
- unless @xproject
+ @xproject ||= begin
namespace = create(:namespace, name: 'cross-reference')
- @xproject = create(:project, namespace: namespace)
- @xproject.team << [user, :developer]
+ create(:project, namespace: namespace) do |project|
+ project.team << [user, :developer]
+ end
end
-
- @xproject
end
def xissue
@@ -91,12 +87,10 @@ class MarkdownFeature
end
def xcommit_range
- unless @xcommit_range
+ @xcommit_range ||= begin
xcommit2 = xproject.commit('HEAD~2')
- @xcommit_range = CommitRange.new("#{xcommit.id}...#{xcommit2.id}", xproject)
+ CommitRange.new("#{xcommit.id}...#{xcommit2.id}", xproject)
end
-
- @xcommit_range
end
def raw_markdown
diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb
index e3de0afb448..3bb568f4d49 100644
--- a/spec/support/mentionable_shared_examples.rb
+++ b/spec/support/mentionable_shared_examples.rb
@@ -5,7 +5,7 @@
# - let(:set_mentionable_text) { lambda { |txt| "block that assigns txt to the subject's mentionable_text" } }
def common_mentionable_setup
- let(:project) { create :project }
+ let(:project) { subject.project }
let(:author) { subject.author }
let(:mentioned_issue) { create(:issue, project: project) }
@@ -50,6 +50,8 @@ def common_mentionable_setup
}
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] }
set_mentionable_text.call(ref_string)
@@ -65,7 +67,7 @@ shared_examples 'a mentionable' do
it "extracts references from its reference property" do
# De-duplicate and omit itself
- refs = subject.references(project)
+ refs = subject.referenced_mentionables
expect(refs.size).to eq(6)
expect(refs).to include(mentioned_issue)
expect(refs).to include(mentioned_mr)
@@ -84,14 +86,7 @@ shared_examples 'a mentionable' do
with(referenced, subject.local_reference, author)
end
- subject.create_cross_references!(project, author)
- end
-
- it 'detects existing cross-references' do
- SystemNoteService.cross_reference(mentioned_issue, subject.local_reference, author)
-
- expect(subject).to have_mentioned(mentioned_issue)
- expect(subject).not_to have_mentioned(mentioned_mr)
+ subject.create_cross_references!
end
end
@@ -143,6 +138,6 @@ shared_examples 'an editable mentionable' do
end
set_mentionable_text.call(new_text)
- subject.create_new_cross_references!(project, author)
+ subject.create_new_cross_references!(author)
end
end
diff --git a/spec/support/services_shared_context.rb b/spec/support/services_shared_context.rb
index 4d007ae55ee..d1c999cad4d 100644
--- a/spec/support/services_shared_context.rb
+++ b/spec/support/services_shared_context.rb
@@ -3,7 +3,13 @@ Service.available_services_names.each do |service|
let(:dashed_service) { service.dasherize }
let(:service_method) { "#{service}_service".to_sym }
let(:service_klass) { "#{service}_service".classify.constantize }
- let(:service_attrs_list) { service_klass.new.fields.inject([]) {|arr, hash| arr << hash[:name].to_sym } }
+ let(:service_fields) { service_klass.new.fields }
+ let(:service_attrs_list) { service_fields.inject([]) {|arr, hash| arr << hash[:name].to_sym } }
+ let(:service_attrs_list_without_passwords) do
+ service_fields.
+ select { |field| field[:type] != 'password' }.
+ map { |field| field[:name].to_sym}
+ end
let(:service_attrs) do
service_attrs_list.inject({}) do |hash, k|
if k =~ /^(token*|.*_token|.*_key)/
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 3eab74ba986..d12ba25b71b 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -9,7 +9,7 @@ module TestEnv
'flatten-dir' => 'e56497b',
'feature' => '0b4bc9a',
'feature_conflict' => 'bb5206f',
- 'fix' => '12d65c8',
+ 'fix' => '48f0be4',
'improve/awesome' => '5937ac0',
'markdown' => '0ed8c6c',
'master' => '5937ac0',
diff --git a/spec/workers/repository_archive_worker_spec.rb b/spec/workers/repository_archive_worker_spec.rb
deleted file mode 100644
index a914d0ac8dc..00000000000
--- a/spec/workers/repository_archive_worker_spec.rb
+++ /dev/null
@@ -1,79 +0,0 @@
-require 'spec_helper'
-
-describe RepositoryArchiveWorker do
- let(:project) { create(:project) }
- subject { RepositoryArchiveWorker.new }
-
- before do
- allow(Project).to receive(:find).and_return(project)
- end
-
- describe "#perform" do
- it "cleans old archives" do
- expect(project.repository).to receive(:clean_old_archives)
-
- subject.perform(project.id, "master", "zip")
- end
-
- context "when the repository doesn't have an archive file path" do
- before do
- allow(project.repository).to receive(:archive_file_path).and_return(nil)
- end
-
- it "doesn't archive the repo" do
- expect(project.repository).not_to receive(:archive_repo)
-
- subject.perform(project.id, "master", "zip")
- end
- end
-
- context "when the repository has an archive file path" do
- let(:file_path) { "/archive.zip" }
- let(:pid_file_path) { "/archive.zip.pid" }
-
- before do
- allow(project.repository).to receive(:archive_file_path).and_return(file_path)
- allow(project.repository).to receive(:archive_pid_file_path).and_return(pid_file_path)
- end
-
- context "when the archive file already exists" do
- before do
- allow(File).to receive(:exist?).with(file_path).and_return(true)
- end
-
- it "doesn't archive the repo" do
- expect(project.repository).not_to receive(:archive_repo)
-
- subject.perform(project.id, "master", "zip")
- end
- end
-
- context "when the archive file doesn't exist yet" do
- before do
- allow(File).to receive(:exist?).with(file_path).and_return(false)
- allow(File).to receive(:exist?).with(pid_file_path).and_return(true)
- end
-
- context "when the archive pid file doesn't exist yet" do
- before do
- allow(File).to receive(:exist?).with(pid_file_path).and_return(false)
- end
-
- it "archives the repo" do
- expect(project.repository).to receive(:archive_repo)
-
- subject.perform(project.id, "master", "zip")
- end
- end
-
- context "when the archive pid file already exists" do
- it "doesn't archive the repo" do
- expect(project.repository).not_to receive(:archive_repo)
-
- subject.perform(project.id, "master", "zip")
- end
- end
- end
- end
- end
-end